1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-28 16:30:59 +01:00

Badges v0.1

Summary:
Basic plumbing for Badges application.

 - You can make Badges.
 - You can look at a list of them.
 - They can be edited.
 - They can be assigned to people.
 - You can revoke them from people.
 - You can subscribe to them.

Test Plan: Make Badges with various options. Give them to people. Take them away from people.

Reviewers: lpriestley, epriestley

Reviewed By: epriestley

Subscribers: tycho.tatitscheff, johnny-bit, epriestley, Korvin

Maniphest Tasks: T6526

Differential Revision: https://secure.phabricator.com/D13626
This commit is contained in:
Chad Little 2015-07-22 13:37:20 -07:00
parent fdd6351a64
commit ffadf64751
34 changed files with 2317 additions and 8 deletions

View file

@ -7,7 +7,7 @@
*/
return array(
'names' => array(
'core.pkg.css' => 'b5cc2f39',
'core.pkg.css' => 'f79ebe46',
'core.pkg.js' => 'a590b451',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '9451634c',
@ -142,10 +142,10 @@ return array(
'rsrc/css/phui/phui-info-view.css' => '5b16bac6',
'rsrc/css/phui/phui-list.css' => '125599df',
'rsrc/css/phui/phui-object-box.css' => '407eaf5a',
'rsrc/css/phui/phui-object-item-list-view.css' => 'fc19bfc1',
'rsrc/css/phui/phui-object-item-list-view.css' => 'a1b990b7',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-property-list-view.css' => '1baf23eb',
'rsrc/css/phui/phui-property-list-view.css' => 'aeb09581',
'rsrc/css/phui/phui-remarkup-preview.css' => '867f85b3',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => '888cedb8',
@ -797,10 +797,10 @@ return array(
'phui-inline-comment-view-css' => '9fadd6b8',
'phui-list-view-css' => '125599df',
'phui-object-box-css' => '407eaf5a',
'phui-object-item-list-view-css' => 'fc19bfc1',
'phui-object-item-list-view-css' => 'a1b990b7',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
'phui-property-list-view-css' => '1baf23eb',
'phui-property-list-view-css' => 'aeb09581',
'phui-remarkup-preview-css' => '867f85b3',
'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8',

View file

@ -0,0 +1,54 @@
CREATE TABLE {$NAMESPACE}_badges.badges_badge (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
flavor VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
description LONGTEXT NOT NULL,
icon VARCHAR(255) NOT NULL,
quality VARCHAR(255) NOT NULL,
status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
creatorPHID varbinary(64) NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_creator` (creatorPHID, dateModified)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
CREATE TABLE {$NAMESPACE}_badges.badges_transaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64),
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
oldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
newValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
contentSource LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
metadata LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
CREATE TABLE {$NAMESPACE}_badges.edge (
src VARBINARY(64) NOT NULL,
type INT UNSIGNED NOT NULL,
dst VARBINARY(64) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY `src` (src, type, dateCreated, seq),
UNIQUE KEY `key_dst` (dst, type, src)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
CREATE TABLE {$NAMESPACE}_badges.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -1617,6 +1617,29 @@ phutil_register_library_map(array(
'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php',
'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php',
'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php',
'PhabricatorBadgeHasRecipientEdgeType' => 'applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php',
'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.php',
'PhabricatorBadgesBadge' => 'applications/badges/storage/PhabricatorBadgesBadge.php',
'PhabricatorBadgesController' => 'applications/badges/controller/PhabricatorBadgesController.php',
'PhabricatorBadgesCreateCapability' => 'applications/badges/capability/PhabricatorBadgesCreateCapability.php',
'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php',
'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php',
'PhabricatorBadgesDefaultViewCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultViewCapability.php',
'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php',
'PhabricatorBadgesEditIconController' => 'applications/badges/controller/PhabricatorBadgesEditIconController.php',
'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php',
'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php',
'PhabricatorBadgesIcon' => 'applications/badges/icon/PhabricatorBadgesIcon.php',
'PhabricatorBadgesListController' => 'applications/badges/controller/PhabricatorBadgesListController.php',
'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php',
'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.php',
'PhabricatorBadgesRecipientsListView' => 'applications/badges/view/PhabricatorBadgesRecipientsListView.php',
'PhabricatorBadgesRemoveRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php',
'PhabricatorBadgesSchemaSpec' => 'applications/badges/storage/PhabricatorBadgesSchemaSpec.php',
'PhabricatorBadgesSearchEngine' => 'applications/badges/query/PhabricatorBadgesSearchEngine.php',
'PhabricatorBadgesTransaction' => 'applications/badges/storage/PhabricatorBadgesTransaction.php',
'PhabricatorBadgesTransactionQuery' => 'applications/badges/query/PhabricatorBadgesTransactionQuery.php',
'PhabricatorBadgesViewController' => 'applications/badges/controller/PhabricatorBadgesViewController.php',
'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php',
'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php',
'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php',
@ -2561,6 +2584,7 @@ phutil_register_library_map(array(
'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php',
'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php',
'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php',
'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php',
'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php',
'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php',
@ -5339,6 +5363,36 @@ phutil_register_library_map(array(
'PhabricatorAuthValidateController' => 'PhabricatorAuthController',
'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAutoEventListener' => 'PhabricatorEventListener',
'PhabricatorBadgeHasRecipientEdgeType' => 'PhabricatorEdgeType',
'PhabricatorBadgesApplication' => 'PhabricatorApplication',
'PhabricatorBadgesBadge' => array(
'PhabricatorBadgesDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSubscribableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorBadgesController' => 'PhabricatorController',
'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability',
'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO',
'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorBadgesDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorBadgesEditController' => 'PhabricatorBadgesController',
'PhabricatorBadgesEditIconController' => 'PhabricatorBadgesController',
'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController',
'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorBadgesIcon' => 'Phobject',
'PhabricatorBadgesListController' => 'PhabricatorBadgesController',
'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType',
'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorBadgesRecipientsListView' => 'AphrontTagView',
'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController',
'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorBadgesViewController' => 'PhabricatorBadgesController',
'PhabricatorBarePageUIExample' => 'PhabricatorUIExample',
'PhabricatorBarePageView' => 'AphrontPageView',
'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck',
@ -6448,6 +6502,7 @@ phutil_register_library_map(array(
'Iterator',
),
'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType',
'PhabricatorRedirectController' => 'PhabricatorController',
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
'PhabricatorRegistrationProfile' => 'Phobject',

View file

@ -0,0 +1,79 @@
<?php
final class PhabricatorBadgesApplication extends PhabricatorApplication {
public function getName() {
return pht('Badges');
}
public function getBaseURI() {
return '/badges/';
}
public function getShortDescription() {
return pht('Achievements and Notority');
}
public function getFontIcon() {
return 'fa-trophy';
}
public function getFlavorText() {
return pht('Build self esteem through gamification.');
}
public function getApplicationGroup() {
return self::GROUP_UTILITIES;
}
public function canUninstall() {
return true;
}
public function isPrototype() {
return true;
}
public function getRoutes() {
return array(
'/badges/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorBadgesListController',
'create/'
=> 'PhabricatorBadgesEditController',
'edit/(?:(?P<id>\d+)/)?'
=> 'PhabricatorBadgesEditController',
'view/(?:(?P<id>\d+)/)?'
=> 'PhabricatorBadgesViewController',
'icon/(?P<id>[1-9]\d*)/'
=> 'PhabricatorBadgesEditIconController',
'icon/'
=> 'PhabricatorBadgesEditIconController',
'recipients/(?P<id>[1-9]\d*)/'
=> 'PhabricatorBadgesEditRecipientsController',
'recipients/(?P<id>[1-9]\d*)/remove/'
=> 'PhabricatorBadgesRemoveRecipientsController',
),
);
}
protected function getCustomCapabilities() {
return array(
PhabricatorBadgesCreateCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
'caption' => pht('Default create policy for badges.'),
),
PhabricatorBadgesDefaultEditCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
'caption' => pht('Default edit policy for badges.'),
'template' => PhabricatorBadgesPHIDType::TYPECONST,
),
PhabricatorBadgesDefaultViewCapability::CAPABILITY => array(
'caption' => pht('Default view policy for badges.'),
'template' => PhabricatorBadgesPHIDType::TYPECONST,
),
);
}
}

View file

@ -0,0 +1,16 @@
<?php
final class PhabricatorBadgesCreateCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'badges.default.create';
public function getCapabilityName() {
return pht('Can Create Badges');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to create badges.');
}
}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorBadgesDefaultEditCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'badges.default.edit';
public function getCapabilityName() {
return pht('Default Edit Badges');
}
}

View file

@ -0,0 +1,16 @@
<?php
final class PhabricatorBadgesDefaultViewCapability extends
PhabricatorPolicyCapability {
const CAPABILITY = 'badges.default.view';
public function getCapabilityName() {
return pht('Default View Policy');
}
public function shouldAllowPublicPolicySetting() {
return true;
}
}

View file

@ -0,0 +1,3 @@
<?php
abstract class PhabricatorBadgesController extends PhabricatorController {}

View file

@ -0,0 +1,222 @@
<?php
final class PhabricatorBadgesEditController
extends PhabricatorBadgesController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if ($id) {
$badge = id(new PhabricatorBadgesQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$badge) {
return new Aphront404Response();
}
$is_new = false;
} else {
$badge = PhabricatorBadgesBadge::initializeNewBadge($viewer);
$is_new = true;
}
if ($is_new) {
$title = pht('Create Badge');
$button_text = pht('Create Badge');
$cancel_uri = $this->getApplicationURI();
} else {
$title = pht(
'Edit %s',
$badge->getName());
$button_text = pht('Save Changes');
$cancel_uri = $this->getApplicationURI('view/'.$id.'/');
}
$e_name = true;
$v_name = $badge->getName();
$v_icon = $badge->getIcon();
$v_flav = $badge->getFlavor();
$v_desc = $badge->getDescription();
$v_qual = $badge->getQuality();
$v_stat = $badge->getStatus();
$v_edit = $badge->getEditPolicy();
$v_view = $badge->getViewPolicy();
$validation_exception = null;
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
$v_flav = $request->getStr('flavor');
$v_desc = $request->getStr('description');
$v_icon = $request->getStr('icon');
$v_stat = $request->getStr('status');
$v_qual = $request->getStr('quality');
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$type_name = PhabricatorBadgesTransaction::TYPE_NAME;
$type_flav = PhabricatorBadgesTransaction::TYPE_FLAVOR;
$type_desc = PhabricatorBadgesTransaction::TYPE_DESCRIPTION;
$type_icon = PhabricatorBadgesTransaction::TYPE_ICON;
$type_qual = PhabricatorBadgesTransaction::TYPE_QUALITY;
$type_stat = PhabricatorBadgesTransaction::TYPE_STATUS;
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
$xactions = array();
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType($type_name)
->setNewValue($v_name);
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType($type_flav)
->setNewValue($v_flav);
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType($type_desc)
->setNewValue($v_desc);
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType($type_icon)
->setNewValue($v_icon);
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType($type_qual)
->setNewValue($v_qual);
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType($type_stat)
->setNewValue($v_stat);
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType($type_view)
->setNewValue($v_view);
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType($type_edit)
->setNewValue($v_edit);
$editor = id(new PhabricatorBadgesEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$editor->applyTransactions($badge, $xactions);
$return_uri = $this->getApplicationURI('view/'.$badge->getID().'/');
return id(new AphrontRedirectResponse())->setURI($return_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_name = $ex->getShortMessage($type_name);
}
}
if ($is_new) {
$icon_uri = $this->getApplicationURI('icon/');
} else {
$icon_uri = $this->getApplicationURI('icon/'.$badge->getID().'/');
}
$icon_display = PhabricatorBadgesIcon::renderIconForChooser($v_icon);
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($badge)
->execute();
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setName('name')
->setLabel(pht('Name'))
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new AphrontFormTextControl())
->setName('flavor')
->setLabel(pht('Flavor Text'))
->setValue($v_flav))
->appendChild(
id(new AphrontFormChooseButtonControl())
->setLabel(pht('Icon'))
->setName('icon')
->setDisplayValue($icon_display)
->setButtonText(pht('Choose Icon...'))
->setChooseURI($icon_uri)
->setValue($v_icon))
->appendChild(
id(new AphrontFormSelectControl())
->setName('quality')
->setLabel(pht('Quality'))
->setValue($v_qual)
->setOptions($badge->getQualityNameMap()))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Status'))
->setName('status')
->setValue($v_stat)
->setOptions($badge->getStatusNameMap()))
->appendChild(
id(new PhabricatorRemarkupControl())
->setUser($viewer)
->setName('description')
->setLabel(pht('Description'))
->setValue($v_desc))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($badge)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setValue($v_view)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($badge)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setValue($v_edit)
->setPolicies($policies))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($button_text)
->addCancelButton($cancel_uri));
$crumbs = $this->buildApplicationCrumbs();
if ($is_new) {
$crumbs->addTextCrumb(pht('Create Badge'));
} else {
$crumbs->addTextCrumb(
$badge->getName(),
'/badges/view/'.$badge->getID().'/');
$crumbs->addTextCrumb(pht('Edit'));
}
$box = id(new PHUIObjectBoxView())
->setValidationException($validation_exception)
->setHeaderText($title)
->appendChild($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
}
}

View file

@ -0,0 +1,101 @@
<?php
final class PhabricatorBadgesEditIconController
extends PhabricatorBadgesController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if ($id) {
$badge = id(new PhabricatorBadgesQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$badge) {
return new Aphront404Response();
}
$cancel_uri =
$this->getApplicationURI('view/'.$badge->getID().'/');
$badge_icon = $badge->getIcon();
} else {
$this->requireApplicationCapability(
PhabricatorBadgesCreateCapability::CAPABILITY);
$cancel_uri = '/badges/';
$badge_icon = $request->getStr('value');
}
require_celerity_resource('project-icon-css');
Javelin::initBehavior('phabricator-tooltips');
$badge_icons = PhabricatorBadgesIcon::getIconMap();
if ($request->isFormPost()) {
$v_icon = $request->getStr('icon');
return id(new AphrontAjaxResponse())->setContent(
array(
'value' => $v_icon,
'display' => PhabricatorBadgesIcon::renderIconForChooser($v_icon),
));
}
$ii = 0;
$buttons = array();
foreach ($badge_icons as $icon => $label) {
$view = id(new PHUIIconView())
->setIconFont($icon);
$aural = javelin_tag(
'span',
array(
'aural' => true,
),
pht('Choose "%s" Icon', $label));
if ($icon == $badge_icon) {
$class_extra = ' selected';
} else {
$class_extra = null;
}
$buttons[] = javelin_tag(
'button',
array(
'class' => 'icon-button'.$class_extra,
'name' => 'icon',
'value' => $icon,
'type' => 'submit',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $label,
),
),
array(
$aural,
$view,
));
if ((++$ii % 4) == 0) {
$buttons[] = phutil_tag('br');
}
}
$buttons = phutil_tag(
'div',
array(
'class' => 'icon-grid',
),
$buttons);
return $this->newDialog()
->setTitle(pht('Choose Badge Icon'))
->appendChild($buttons)
->addCancelButton($cancel_uri);
}
}

View file

@ -0,0 +1,120 @@
<?php
final class PhabricatorBadgesEditRecipientsController
extends PhabricatorBadgesController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$badge = id(new PhabricatorBadgesQuery())
->setViewer($viewer)
->withIDs(array($id))
->needRecipients(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_EDIT,
PhabricatorPolicyCapability::CAN_VIEW,
))
->executeOne();
if (!$badge) {
return new Aphront404Response();
}
$recipient_phids = $badge->getRecipientPHIDs();
if ($request->isFormPost()) {
$recipient_spec = array();
$remove = $request->getStr('remove');
if ($remove) {
$recipient_spec['-'] = array_fuse(array($remove));
}
$add_recipients = $request->getArr('phids');
if ($add_recipients) {
$recipient_spec['+'] = array_fuse($add_recipients);
}
$type_recipient = PhabricatorBadgeHasRecipientEdgeType::EDGECONST;
$xactions = array();
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $type_recipient)
->setNewValue($recipient_spec);
$editor = id(new PhabricatorBadgesEditor($badge))
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($badge, $xactions);
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI());
}
$recipient_phids = array_reverse($recipient_phids);
$handles = $this->loadViewerHandles($recipient_phids);
$state = array();
foreach ($handles as $handle) {
$state[] = array(
'phid' => $handle->getPHID(),
'name' => $handle->getFullName(),
);
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$badge,
PhabricatorPolicyCapability::CAN_EDIT);
$form_box = null;
$title = pht('Add Recipient');
if ($can_edit) {
$header_name = pht('Edit Recipients');
$view_uri = $this->getApplicationURI('view/'.$badge->getID().'/');
$form = new AphrontFormView();
$form
->setUser($viewer)
->appendControl(
id(new AphrontFormTokenizerControl())
->setName('phids')
->setLabel(pht('Add Recipients'))
->setDatasource(new PhabricatorPeopleDatasource()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($view_uri)
->setValue(pht('Add Recipients')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
}
$recipient_list = id(new PhabricatorBadgesRecipientsListView())
->setBadge($badge)
->setHandles($handles)
->setUser($viewer);
$badge_url = $this->getApplicationURI('view/'.$id.'/');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($badge->getName(), $badge_url);
$crumbs->addTextCrumb(pht('Recipients'));
return $this->buildApplicationPage(
array(
$crumbs,
$form_box,
$recipient_list,
),
array(
'title' => $title,
));
}
}

View file

@ -0,0 +1,52 @@
<?php
final class PhabricatorBadgesListController
extends PhabricatorBadgesController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$query_key = $request->getURIData('queryKey');
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($query_key)
->setSearchEngine(new PhabricatorBadgesSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
}
public function buildSideNavView() {
$user = $this->getRequest()->getUser();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new PhabricatorBadgesSearchEngine())
->setViewer($user)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$can_create = $this->hasApplicationCapability(
PhabricatorBadgesCreateCapability::CAPABILITY);
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Badge'))
->setHref($this->getApplicationURI('create/'))
->setIcon('fa-plus-square')
->setDisabled(!$can_create)
->setWorkflow(!$can_create));
return $crumbs;
}
}

View file

@ -0,0 +1,77 @@
<?php
final class PhabricatorBadgesRemoveRecipientsController
extends PhabricatorBadgesController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$badge = id(new PhabricatorBadgesQuery())
->setViewer($viewer)
->withIDs(array($id))
->needRecipients(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$badge) {
return new Aphront404Response();
}
$recipient_phids = $badge->getRecipientPHIDs();
$remove_phid = $request->getStr('phid');
if (!in_array($remove_phid, $recipient_phids)) {
return new Aphront404Response();
}
$recipients_uri =
$this->getApplicationURI('recipients/'.$badge->getID().'/');
if ($request->isFormPost()) {
$recipient_spec = array();
$recipient_spec['-'] = array($remove_phid => $remove_phid);
$type_recipient = PhabricatorBadgeHasRecipientEdgeType::EDGECONST;
$xactions = array();
$xactions[] = id(new PhabricatorBadgesTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $type_recipient)
->setNewValue($recipient_spec);
$editor = id(new PhabricatorBadgesEditor($badge))
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($badge, $xactions);
return id(new AphrontRedirectResponse())
->setURI($recipients_uri);
}
$handle = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array($remove_phid))
->executeOne();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Really Revoke Badge?'))
->appendParagraph(
pht(
'Really revoke the badge "%s" from %s?',
phutil_tag('strong', array(), $badge->getName()),
phutil_tag('strong', array(), $handle->getName())))
->addCancelButton($recipients_uri)
->addSubmitButton(pht('Revoke Badge'));
return $dialog;
}
}

View file

@ -0,0 +1,157 @@
<?php
final class PhabricatorBadgesViewController
extends PhabricatorBadgesController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$badge = id(new PhabricatorBadgesQuery())
->setViewer($viewer)
->withIDs(array($id))
->needRecipients(true)
->executeOne();
if (!$badge) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($badge->getName());
$title = $badge->getName();
if ($badge->isClosed()) {
$status_icon = 'fa-ban';
$status_color = 'dark';
} else {
$status_icon = 'fa-check';
$status_color = 'bluegrey';
}
$status_name = idx(
PhabricatorBadgesBadge::getStatusNameMap(),
$badge->getStatus());
$header = id(new PHUIHeaderView())
->setHeader($badge->getName())
->setUser($viewer)
->setPolicyObject($badge)
->setStatus($status_icon, $status_color, $status_name);
$properties = $this->buildPropertyListView($badge);
$actions = $this->buildActionListView($badge);
$properties->setActionList($actions);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$timeline = $this->buildTransactionTimeline(
$badge,
new PhabricatorBadgesTransactionQuery());
$timeline
->setShouldTerminate(true);
$recipient_phids = $badge->getRecipientPHIDs();
$recipient_phids = array_reverse($recipient_phids);
$handles = $this->loadViewerHandles($recipient_phids);
$recipient_list = id(new PhabricatorBadgesRecipientsListView())
->setBadge($badge)
->setHandles($handles)
->setUser($viewer);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
$recipient_list,
$timeline,
),
array(
'title' => $title,
'pageObjects' => array($badge->getPHID()),
));
}
private function buildPropertyListView(PhabricatorBadgesBadge $badge) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($badge);
$quality = idx($badge->getQualityNameMap(), $badge->getQuality());
$icon = idx($badge->getIconNameMap(), $badge->getIcon());
$view->addProperty(
pht('Quality'),
$quality);
$view->addProperty(
pht('Icon'),
$icon);
$view->addProperty(
pht('Flavor'),
$badge->getFlavor());
$view->invokeWillRenderEvent();
$description = $badge->getDescription();
if (strlen($description)) {
$description = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($description),
'default',
$viewer);
$view->addSectionHeader(pht('Description'));
$view->addTextContent($description);
}
$badge = id(new PHUIBadgeView())
->setIcon($badge->getIcon())
->setHeader($badge->getName())
->setSubhead($badge->getFlavor())
->setQuality($badge->getQuality());
$view->addTextContent($badge);
return $view;
}
private function buildActionListView(PhabricatorBadgesBadge $badge) {
$viewer = $this->getViewer();
$id = $badge->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$badge,
PhabricatorPolicyCapability::CAN_EDIT);
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($badge);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Badge'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref($this->getApplicationURI("/edit/{$id}/")));
$view->addAction(
id(new PhabricatorActionView())
->setName('Manage Recipients')
->setIcon('fa-users')
->setDisabled(!$can_edit)
->setHref($this->getApplicationURI("/recipients/{$id}/")));
return $view;
}
}

View file

@ -0,0 +1,103 @@
<?php
final class PhabricatorBadgeHasRecipientEdgeType
extends PhabricatorEdgeType {
const EDGECONST = 59;
public function getInverseEdgeConstant() {
return PhabricatorRecipientHasBadgeEdgeType::EDGECONST;
}
public function shouldWriteInverseTransactions() {
return true;
}
public function getTransactionAddString(
$actor,
$add_count,
$add_edges) {
return pht(
'%s awarded %s recipients(s): %s.',
$actor,
$add_count,
$add_edges);
}
public function getTransactionRemoveString(
$actor,
$rem_count,
$rem_edges) {
return pht(
'%s revoked %s recipients(s): %s.',
$actor,
$rem_count,
$rem_edges);
}
public function getTransactionEditString(
$actor,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
return pht(
'%s edited recipient(s), awarded %s: %s; revoked %s: %s.',
$actor,
$add_count,
$add_edges,
$rem_count,
$rem_edges);
}
public function getFeedAddString(
$actor,
$object,
$add_count,
$add_edges) {
return pht(
'%s awarded %s recipient(s) for %s: %s.',
$actor,
$add_count,
$object,
$add_edges);
}
public function getFeedRemoveString(
$actor,
$object,
$rem_count,
$rem_edges) {
return pht(
'%s revoked %s recipient(s) for %s: %s.',
$actor,
$rem_count,
$object,
$rem_edges);
}
public function getFeedEditString(
$actor,
$object,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
return pht(
'%s edited recipient(s) for %s, awarded %s: %s; revoked %s: %s.',
$actor,
$object,
$add_count,
$add_edges,
$rem_count,
$rem_edges);
}
}

View file

@ -0,0 +1,103 @@
<?php
final class PhabricatorRecipientHasBadgeEdgeType
extends PhabricatorEdgeType {
const EDGECONST = 58;
public function getInverseEdgeConstant() {
return PhabricatorBadgeHasRecipientEdgeType::EDGECONST;
}
public function shouldWriteInverseTransactions() {
return true;
}
public function getTransactionAddString(
$actor,
$add_count,
$add_edges) {
return pht(
'%s added %s badge(s): %s.',
$actor,
$add_count,
$add_edges);
}
public function getTransactionRemoveString(
$actor,
$rem_count,
$rem_edges) {
return pht(
'%s revoked %s badge(s): %s.',
$actor,
$rem_count,
$rem_edges);
}
public function getTransactionEditString(
$actor,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
return pht(
'%s edited badge(s), added %s: %s; revoked %s: %s.',
$actor,
$add_count,
$add_edges,
$rem_count,
$rem_edges);
}
public function getFeedAddString(
$actor,
$object,
$add_count,
$add_edges) {
return pht(
'%s added %s badge(s) for %s: %s.',
$actor,
$add_count,
$object,
$add_edges);
}
public function getFeedRemoveString(
$actor,
$object,
$rem_count,
$rem_edges) {
return pht(
'%s revoked %s badge(s) for %s: %s.',
$actor,
$rem_count,
$object,
$rem_edges);
}
public function getFeedEditString(
$actor,
$object,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
return pht(
'%s edited badge(s) for %s, added %s: %s; revoked %s: %s.',
$actor,
$object,
$add_count,
$add_edges,
$rem_count,
$rem_edges);
}
}

View file

@ -0,0 +1,194 @@
<?php
final class PhabricatorBadgesEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorBadgesApplication';
}
public function getEditorObjectsDescription() {
return pht('Badges');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorBadgesTransaction::TYPE_NAME;
$types[] = PhabricatorBadgesTransaction::TYPE_FLAVOR;
$types[] = PhabricatorBadgesTransaction::TYPE_DESCRIPTION;
$types[] = PhabricatorBadgesTransaction::TYPE_ICON;
$types[] = PhabricatorBadgesTransaction::TYPE_STATUS;
$types[] = PhabricatorBadgesTransaction::TYPE_QUALITY;
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorBadgesTransaction::TYPE_NAME:
return $object->getName();
case PhabricatorBadgesTransaction::TYPE_FLAVOR:
return $object->getFlavor();
case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
return $object->getDescription();
case PhabricatorBadgesTransaction::TYPE_ICON:
return $object->getIcon();
case PhabricatorBadgesTransaction::TYPE_QUALITY:
return $object->getQuality();
case PhabricatorBadgesTransaction::TYPE_STATUS:
return $object->getStatus();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorBadgesTransaction::TYPE_NAME:
case PhabricatorBadgesTransaction::TYPE_FLAVOR:
case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
case PhabricatorBadgesTransaction::TYPE_ICON:
case PhabricatorBadgesTransaction::TYPE_STATUS:
case PhabricatorBadgesTransaction::TYPE_QUALITY:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
switch ($type) {
case PhabricatorBadgesTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case PhabricatorBadgesTransaction::TYPE_FLAVOR:
$object->setFlavor($xaction->getNewValue());
return;
case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
$object->setDescription($xaction->getNewValue());
return;
case PhabricatorBadgesTransaction::TYPE_ICON:
$object->setIcon($xaction->getNewValue());
return;
case PhabricatorBadgesTransaction::TYPE_QUALITY:
$object->setQuality($xaction->getNewValue());
return;
case PhabricatorBadgesTransaction::TYPE_STATUS:
$object->setStatus($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
switch ($type) {
case PhabricatorBadgesTransaction::TYPE_NAME:
case PhabricatorBadgesTransaction::TYPE_FLAVOR:
case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
case PhabricatorBadgesTransaction::TYPE_ICON:
case PhabricatorBadgesTransaction::TYPE_STATUS:
case PhabricatorBadgesTransaction::TYPE_QUALITY:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case PhabricatorBadgesTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Badge name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
}
return $errors;
}
protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new PhabricatorMacroReplyHandler())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$name = $object->getName();
$id = $object->getID();
$name = pht('Badge %d', $id);
return id(new PhabricatorMetaMTAMail())
->setSubject($name)
->addHeader('Thread-Topic', $name);
}
protected function getMailTo(PhabricatorLiskDAO $object) {
return array(
$this->requireActor()->getPHID(),
);
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$description = $object->getDescription();
$body = parent::buildMailBody($object, $xactions);
if (strlen($description)) {
$body->addTextSection(
pht('BADGE DESCRIPTION'),
$object->getDescription());
}
$body->addLinkSection(
pht('BADGE DETAIL'),
PhabricatorEnv::getProductionURI('/badge/view/'.$object->getID().'/'));
return $body;
}
protected function getMailSubjectPrefix() {
return pht('[Badge]');
}
}

View file

@ -0,0 +1,53 @@
<?php
final class PhabricatorBadgesIcon extends Phobject {
public static function getIconMap() {
return
array(
'fa-star' => pht('Superstar'),
'fa-user' => pht('Average Person'),
'fa-bug' => pht('Ladybug'),
'fa-users' => pht('Triplets'),
'fa-book' => pht('Nominomicon'),
'fa-rocket' => pht('Escape Route'),
'fa-life-ring' => pht('Foam Circle'),
'fa-birthday-cake' => pht('Cake Day'),
'fa-camera-retro' => pht('Leica Enthusiast'),
'fa-beer' => pht('Liquid Lunch'),
'fa-gift' => pht('Free Stuff'),
'fa-eye' => pht('Eye See You'),
'fa-heart' => pht('Love is Love'),
'fa-trophy' => pht('Winner at Things'),
'fa-umbrella' => pht('Rain Defender'),
'fa-graduation-cap' => pht('In Debt'),
);
}
public static function getLabel($key) {
$map = self::getIconMap();
return $map[$key];
}
public static function getAPIName($key) {
return substr($key, 3);
}
public static function renderIconForChooser($icon) {
$badge_icons = self::getIconMap();
return phutil_tag(
'span',
array(),
array(
id(new PHUIIconView())->setIconFont($icon),
' ',
idx($badge_icons, $icon, pht('Unknown Icon')),
));
}
}

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorBadgesPHIDType extends PhabricatorPHIDType {
const TYPECONST = 'BDGE';
public function getTypeName() {
return pht('Badge');
}
public function newObject() {
return new PhabricatorBadgesBadge();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorBadgesApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorBadgesQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$badge = $objects[$phid];
$id = $badge->getID();
$name = $badge->getName();
if ($badge->isClosed()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
$handle->setName($name);
$handle->setURI("/badges/view/{$id}/");
}
}
}

View file

@ -0,0 +1,113 @@
<?php
final class PhabricatorBadgesQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $qualities;
private $statuses;
private $recipientPHIDs;
private $needRecipients;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withQualities(array $qualities) {
$this->qualities = $qualities;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withRecipientPHIDs(array $recipient_phids) {
$this->recipientPHIDs = $recipient_phids;
return $this;
}
public function needRecipients($need_recipients) {
$this->needRecipients = $need_recipients;
return $this;
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
public function newResultObject() {
return new PhabricatorBadgesBadge();
}
protected function didFilterPage(array $badges) {
if ($this->needRecipients) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($badges, 'getPHID'))
->withEdgeTypes(
array(
PhabricatorBadgeHasRecipientEdgeType::EDGECONST,
));
$edge_query->execute();
foreach ($badges as $badge) {
$phids = $edge_query->getDestinationPHIDs(
array(
$badge->getPHID(),
));
$badge->attachRecipientPHIDs($phids);
}
}
return $badges;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->qualities !== null) {
$where[] = qsprintf(
$conn,
'quality IN (%Ls)',
$this->qualities);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorBadgesApplication';
}
}

View file

@ -0,0 +1,143 @@
<?php
final class PhabricatorBadgesSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Badge');
}
public function getApplicationClassName() {
return 'PhabricatorBadgesApplication';
}
public function newQuery() {
return new PhabricatorBadgesQuery();
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->setParameter(
'statuses',
$this->readListFromRequest($request, 'statuses'));
$saved->setParameter(
'qualities',
$this->readListFromRequest($request, 'qualities'));
return $saved;
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchCheckboxesField())
->setKey('qualities')
->setLabel(pht('Quality'))
->setOptions(
id(new PhabricatorBadgesBadge())
->getQualityNameMap()),
id(new PhabricatorSearchCheckboxesField())
->setKey('statuses')
->setLabel(pht('Status'))
->setOptions(
id(new PhabricatorBadgesBadge())
->getStatusNameMap()),
);
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['statuses']) {
$query->withStatuses($map['statuses']);
}
if ($map['qualities']) {
$query->withQualities($map['qualities']);
}
return $query;
}
protected function getURI($path) {
return '/badges/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array();
$names['open'] = pht('Active Badges');
$names['all'] = pht('All Badges');
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
case 'open':
return $query->setParameter(
'statuses',
array(
PhabricatorBadgesBadge::STATUS_OPEN,
));
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function getRequiredHandlePHIDsForResultList(
array $badges,
PhabricatorSavedQuery $query) {
$phids = array();
return $phids;
}
protected function renderResultList(
array $badges,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($badges, 'PhabricatorBadgesBadge');
$viewer = $this->requireViewer();
$list = id(new PHUIObjectItemListView());
foreach ($badges as $badge) {
$quality = idx($badge->getQualityNameMap(), $badge->getQuality());
$mini_badge = id(new PHUIBadgeMiniView())
->setHeader($badge->getName())
->setIcon($badge->getIcon())
->setQuality($badge->getQuality());
$item = id(new PHUIObjectItemView())
->setHeader($badge->getName())
->setBadge($mini_badge)
->setHref('/badges/view/'.$badge->getID().'/')
->addAttribute($quality)
->addAttribute($badge->getFlavor());
if ($badge->isClosed()) {
$item->setDisabled(true);
$item->addIcon('fa-ban', pht('Archived'));
}
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No badges found.'));
return $result;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorBadgesTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new PhabricatorBadgesTransaction();
}
}

View file

@ -0,0 +1,197 @@
<?php
final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO
implements
PhabricatorPolicyInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSubscribableInterface,
PhabricatorFlaggableInterface,
PhabricatorDestructibleInterface {
protected $name;
protected $flavor;
protected $description;
protected $icon;
protected $quality;
protected $viewPolicy;
protected $editPolicy;
protected $status;
protected $creatorPHID;
private $recipientPHIDs = self::ATTACHABLE;
const STATUS_OPEN = 'open';
const STATUS_CLOSED = 'closed';
const DEFAULT_ICON = 'fa-star';
const DEFAULT_QUALITY = 'green';
const POOR = 'grey';
const COMMON = 'white';
const UNCOMMON = 'green';
const RARE = 'blue';
const EPIC = 'indigo';
const LEGENDARY = 'orange';
const HEIRLOOM = 'yellow';
public static function getStatusNameMap() {
return array(
self::STATUS_OPEN => pht('Active'),
self::STATUS_CLOSED => pht('Archived'),
);
}
public static function getQualityNameMap() {
return array(
self::POOR => pht('Poor'),
self::COMMON => pht('Common'),
self::UNCOMMON => pht('Uncommon'),
self::RARE => pht('Rare'),
self::EPIC => pht('Epic'),
self::LEGENDARY => pht('Legendary'),
self::HEIRLOOM => pht('Heirloom'),
);
}
public static function getIconNameMap() {
return PhabricatorBadgesIcon::getIconMap();
}
public static function initializeNewBadge(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorBadgesApplication'))
->executeOne();
$view_policy =
$app->getPolicy(PhabricatorBadgesDefaultViewCapability::CAPABILITY);
$edit_policy =
$app->getPolicy(PhabricatorBadgesDefaultEditCapability::CAPABILITY);
return id(new PhabricatorBadgesBadge())
->setIcon(self::DEFAULT_ICON)
->setQuality(self::DEFAULT_QUALITY)
->setCreatorPHID($actor->getPHID())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setStatus(self::STATUS_OPEN);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'flavor' => 'text255',
'description' => 'text',
'icon' => 'text255',
'quality' => 'text255',
'status' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_creator' => array(
'columns' => array('creatorPHID', 'dateModified'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return
PhabricatorPHID::generateNewPHID(PhabricatorBadgesPHIDType::TYPECONST);
}
public function isClosed() {
return ($this->getStatus() == self::STATUS_CLOSED);
}
public function attachRecipientPHIDs(array $phids) {
$this->recipientPHIDs = $phids;
return $this;
}
public function getRecipientPHIDs() {
return $this->assertAttached($this->recipientPHIDs);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorBadgesEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorBadgesTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return ($this->creatorPHID == $phid);
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
}

View file

@ -0,0 +1,9 @@
<?php
abstract class PhabricatorBadgesDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'badges';
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorBadgesSchemaSpec
extends PhabricatorConfigSchemaSpec {
public function buildSchemata() {
$this->buildEdgeSchemata(new PhabricatorBadgesBadge());
}
}

View file

@ -0,0 +1,195 @@
<?php
final class PhabricatorBadgesTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'badges:name';
const TYPE_DESCRIPTION = 'badges:description';
const TYPE_QUALITY = 'badges:quality';
const TYPE_ICON = 'badges:icon';
const TYPE_STATUS = 'badges:status';
const TYPE_FLAVOR = 'badges:flavor';
public function getApplicationName() {
return 'badges';
}
public function getApplicationTransactionType() {
return PhabricatorBadgesPHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this badge.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s renamed this badge from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case self::TYPE_FLAVOR:
if ($old === null) {
return pht(
'%s set the flavor text for this badge.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s updated the flavor text for this badge.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_DESCRIPTION:
if ($old === null) {
return pht(
'%s set the description for this badge.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s updated the description for this badge.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_ICON:
if ($old === null) {
return pht(
'%s set the icon for this badge as "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
$icon_map = PhabricatorBadgesBadge::getIconNameMap();
$icon_new = idx($icon_map, $new, $new);
$icon_old = idx($icon_map, $old, $old);
return pht(
'%s updated the icon for this badge from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$icon_old,
$icon_new);
}
break;
case self::TYPE_QUALITY:
if ($old === null) {
return pht(
'%s set the quality for this badge as "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
$qual_map = PhabricatorBadgesBadge::getQualityNameMap();
$qual_new = idx($qual_map, $new, $new);
$qual_old = idx($qual_map, $old, $old);
return pht(
'%s updated the quality for this badge from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$qual_old,
$qual_new);
}
break;
}
return parent::getTitle();
}
public function getTitleForFeed() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s renamed %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
case self::TYPE_FLAVOR:
return pht(
'%s updated the flavor text for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_ICON:
return pht(
'%s updated the icon for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_QUALITY:
return pht(
'%s updated the quality level for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_DESCRIPTION:
return pht(
'%s updated the description for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_STATUS:
switch ($new) {
case PhabricatorBadgesBadge::STATUS_OPEN:
return pht(
'%s activated %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorBadgesBadge::STATUS_CLOSED:
return pht(
'%s archived %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
}
return parent::getTitleForFeed();
}
public function shouldHide() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION:
return ($old === null);
}
return parent::shouldHide();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION:
return ($this->getOldValue() !== null);
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
}

View file

@ -0,0 +1,61 @@
<?php
final class PhabricatorBadgesRecipientsListView extends AphrontTagView {
private $badge;
private $handles;
public function setBadge(PhabricatorBadgesBadge $badge) {
$this->badge = $badge;
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
protected function getTagContent() {
$viewer = $this->user;
$badge = $this->badge;
$handles = $this->handles;
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$badge,
PhabricatorPolicyCapability::CAN_EDIT);
$list = id(new PHUIObjectItemListView())
->setNoDataString(pht('This badge does not have any recipients.'));
foreach ($handles as $handle) {
$remove_uri = '/badges/recipients/'.
$badge->getID().'/remove/?phid='.$handle->getPHID();
$item = id(new PHUIObjectItemView())
->setHeader($handle->getFullName())
->setHref($handle->getURI())
->setImageURI($handle->getImageURI());
if ($can_edit) {
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->setName(pht('Remove'))
->setHref($remove_uri)
->setWorkflow(true));
}
$list->addItem($item);
}
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Recipients'))
->setObjectList($list);
return $box;
}
}

View file

@ -11,7 +11,7 @@ final class PhabricatorTokensApplication extends PhabricatorApplication {
}
public function getFontIcon() {
return 'fa-trophy';
return 'fa-thumbs-up';
}
public function getTitleGlyph() {

View file

@ -89,7 +89,7 @@ final class PHUIBadgeExample extends PhabricatorUIExample {
$badges2[] = id(new PHUIBadgeView())
->setIcon('fa-users')
->setHeader(pht('Security Team'))
->setSubhead(pht('<sripts>alert(1);</script>'))
->setSubhead(pht('<script>alert(1);</script>'))
->setQuality(PHUIBadgeView::EPIC)
->setSource('Projects (automatic)')
->addByline(pht('Dec 31, 1969'))
@ -152,7 +152,7 @@ final class PHUIBadgeExample extends PhabricatorUIExample {
$flex2->addItems($badges2);
$box2 = id(new PHUIObjectBoxView())
->setHeaderText(pht('Acheivements'))
->setHeaderText(pht('Achievements'))
->appendChild($flex2);
$flex3 = new PHUIBadgeBoxView();

View file

@ -1182,6 +1182,80 @@ final class PhabricatorUSEnglishTranslation
'%s Task',
'%s Tasks',
),
'%s added %s badge(s) for %s: %s.' => array(
array(
'%s added a badge for %s: %3$s.',
'%s added badges for %s: %3$s.',
),
),
'%s added %s badge(s): %s.' => array(
array(
'%s added a badge: %3$s.',
'%s added badges: %3$s.',
),
),
'%s awarded %s recipient(s) for %s: %s.' => array(
array(
'%s awarded %3$s to %4$s.',
'%s awarded %3$s to multiple recipients: %4$s.',
),
),
'%s awarded %s recipients(s): %s.' => array(
array(
'%s awarded a recipient: %3$s.',
'%s awarded multiple recipients: %3$s.',
),
),
'%s edited badge(s) for %s, added %s: %s; revoked %s: %s.' => array(
array(
'%s edited badges for %s, added %s: %s; revoked %s: %s.',
'%s edited badges for %s, added %s: %s; revoked %s: %s.',
),
),
'%s edited badge(s), added %s: %s; revoked %s: %s.' => array(
array(
'%s edited badges, added %s: %s; revoked %s: %s.',
'%s edited badges, added %s: %s; revoked %s: %s.',
),
),
'%s edited recipient(s) for %s, awarded %s: %s; revoked %s: %s.' => array(
array(
'%s edited recipients for %s, awarded %s: %s; revoked %s: %s.',
'%s edited recipients for %s, awarded %s: %s; revoked %s: %s.',
),
),
'%s edited recipient(s), awarded %s: %s; revoked %s: %s.' => array(
array(
'%s edited recipients, awarded %s: %s; revoked %s: %s.',
'%s edited recipients, awarded %s: %s; revoked %s: %s.',
),
),
'%s revoked %s badge(s) for %s: %s.' => array(
array(
'%s revoked a badge for %3$s: %4$s.',
'%s revoked multiple badges for %3$s: %4$s.',
),
),
'%s revoked %s badge(s): %s.' => array(
array(
'%s revoked a badge: %3$s.',
'%s revoked multiple badges: %3$s.',
),
),
'%s revoked %s recipient(s) for %s: %s.' => array(
array(
'%s revoked %3$s from %4$s.',
'%s revoked multiple recipients for %3$s: %4$s.',
),
),
'%s revoked %s recipients(s): %s.' => array(
array(
'%s revoked a recipient: %3$s.',
'%s revoked multiple recipients: %3$s.',
),
),
);
}

View file

@ -106,6 +106,7 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'db.almanac' => array(),
'db.multimeter' => array(),
'db.spaces' => array(),
'db.badges' => array(),
'0000.legacy.sql' => array(
'legacy' => 0,
),

View file

@ -23,6 +23,7 @@ final class PHUIObjectItemView extends AphrontTagView {
private $fontIcon;
private $imageIcon;
private $titleText;
private $badge;
const AGE_FRESH = 'fresh';
const AGE_STALE = 'stale';
@ -99,6 +100,11 @@ final class PHUIObjectItemView extends AphrontTagView {
return $this;
}
public function setBadge(PHUIBadgeMiniView $badge) {
$this->badge = $badge;
return $this;
}
public function setTitleText($title_text) {
$this->titleText = $title_text;
return $this;
@ -588,6 +594,15 @@ final class PHUIObjectItemView extends AphrontTagView {
$status);
}
if ($this->badge) {
$column0 = phutil_tag(
'div',
array(
'class' => 'phui-object-item-col0 phui-object-item-badge',
),
$this->badge);
}
$column1 = phutil_tag(
'div',
array(

View file

@ -617,6 +617,17 @@ ul.phui-object-item-list-view .phui-object-item-selected
border: none;
}
/* - Badges ---------------------------------------------------------------- */
.phui-object-item-col0.phui-object-item-badge {
width: 28px;
}
.phui-object-item-col0.phui-object-item-badge .phui-icon-view {
left: 0;
}
/* - Dashboards ------------------------------------------------------------ */
.phui-object-box .phui-object-item-list-view .phui-object-item-frame {

View file

@ -98,6 +98,12 @@
border-width: 1px 0 0;
}
.phui-property-list-container + .phui-property-list-text-content {
border-color: {$thinblueborder};
border-style: solid;
border-width: 1px 0 0;
}
.phui-property-list-section-noninitial .phui-property-list-section-header {
border-top: none;
}