Page MenuHomePhabricator

D13626.id32962.diff
No OneTemporary

D13626.id32962.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,7 +7,7 @@
*/
return array(
'names' => array(
- 'core.pkg.css' => '1716a671',
+ 'core.pkg.css' => '0730cb6d',
'core.pkg.js' => 'a590b451',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '9451634c',
@@ -142,10 +142,10 @@
'rsrc/css/phui/phui-info-view.css' => '5b16bac6',
'rsrc/css/phui/phui-list.css' => '125599df',
'rsrc/css/phui/phui-object-box.css' => '3db9f358',
- '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 @@
'phui-inline-comment-view-css' => '9fadd6b8',
'phui-list-view-css' => '125599df',
'phui-object-box-css' => '3db9f358',
- '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',
diff --git a/resources/sql/autopatches/20150712.badges.1.sql b/resources/sql/autopatches/20150712.badges.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150712.badges.1.sql
@@ -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};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1600,6 +1600,30 @@
'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php',
'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php',
'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php',
+ 'PhabricatorBadge' => 'applications/badges/storage/PhabricatorBadge.php',
+ 'PhabricatorBadgeHasRecipientEdgeType' => 'applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php',
+ 'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.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',
+ 'PhabricatorBadgesInterface' => 'applications/badges/interface/PhabricatorBadgesInterface.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',
@@ -2533,6 +2557,7 @@
'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',
@@ -5286,6 +5311,36 @@
'PhabricatorAuthValidateController' => 'PhabricatorAuthController',
'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAutoEventListener' => 'PhabricatorEventListener',
+ 'PhabricatorBadge' => array(
+ 'PhabricatorBadgesDAO',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorApplicationTransactionInterface',
+ 'PhabricatorSubscribableInterface',
+ 'PhabricatorDestructibleInterface',
+ ),
+ 'PhabricatorBadgeHasRecipientEdgeType' => 'PhabricatorEdgeType',
+ 'PhabricatorBadgesApplication' => 'PhabricatorApplication',
+ 'PhabricatorBadgesController' => 'PhabricatorController',
+ 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability',
+ 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO',
+ 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability',
+ 'PhabricatorBadgesDefaultViewCapability' => 'PhabricatorPolicyCapability',
+ 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController',
+ 'PhabricatorBadgesEditIconController' => 'PhabricatorBadgesController',
+ 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController',
+ 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'PhabricatorBadgesIcon' => 'Phobject',
+ 'PhabricatorBadgesInterface' => 'PhabricatorPHIDInterface',
+ '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',
@@ -6375,6 +6430,7 @@
'Iterator',
),
'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
+ 'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType',
'PhabricatorRedirectController' => 'PhabricatorController',
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
'PhabricatorRegistrationProfile' => 'Phobject',
diff --git a/src/applications/badges/application/PhabricatorBadgesApplication.php b/src/applications/badges/application/PhabricatorBadgesApplication.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/application/PhabricatorBadgesApplication.php
@@ -0,0 +1,77 @@
+<?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.'),
+ ),
+ PhabricatorBadgesDefaultViewCapability::CAPABILITY => array(
+ 'caption' => pht('Default view policy for badges.'),
+ ),
+ );
+ }
+
+}
diff --git a/src/applications/badges/capability/PhabricatorBadgesCreateCapability.php b/src/applications/badges/capability/PhabricatorBadgesCreateCapability.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/capability/PhabricatorBadgesCreateCapability.php
@@ -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.');
+ }
+
+}
diff --git a/src/applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php b/src/applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php
@@ -0,0 +1,16 @@
+<?php
+
+final class PhabricatorBadgesDefaultEditCapability
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'badges.default.edit';
+
+ public function getCapabilityName() {
+ return pht('Can Edit Badges');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to edit badges.');
+ }
+
+}
diff --git a/src/applications/badges/capability/PhabricatorBadgesDefaultViewCapability.php b/src/applications/badges/capability/PhabricatorBadgesDefaultViewCapability.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/capability/PhabricatorBadgesDefaultViewCapability.php
@@ -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;
+ }
+
+}
diff --git a/src/applications/badges/controller/PhabricatorBadgesController.php b/src/applications/badges/controller/PhabricatorBadgesController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/controller/PhabricatorBadgesController.php
@@ -0,0 +1,3 @@
+<?php
+
+abstract class PhabricatorBadgesController extends PhabricatorController {}
diff --git a/src/applications/badges/controller/PhabricatorBadgesEditController.php b/src/applications/badges/controller/PhabricatorBadgesEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/controller/PhabricatorBadgesEditController.php
@@ -0,0 +1,232 @@
+<?php
+
+final class PhabricatorBadgesEditController
+ extends PhabricatorBadgesController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = idx($data, 'id');
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ if ($this->id) {
+ $badge = id(new PhabricatorBadgesQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
+ if (!$badge) {
+ return new Aphront404Response();
+ }
+ $is_new = false;
+ } else {
+ $badge = PhabricatorBadge::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/'.$this->id.'/';
+ }
+
+ $e_name = true;
+ $v_name = $badge->getName();
+
+ $e_icon = true;
+ $v_icon = $badge->getIcon();
+
+ $v_flav = $badge->getFlavor();
+ $v_desc = $badge->getDescription();
+ $v_qual = $badge->getQuality();
+ $v_stat = $badge->getStatus();
+
+ $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);
+ if ($this->id) {
+ $return_uri = '/badges/view/'.$this->id.'/';
+ } else {
+ $return_uri = $this->getApplicationURI();
+ }
+ return id(new AphrontRedirectResponse())
+ ->setURI($return_uri);
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $validation_exception = $ex;
+
+ $e_name = $ex->getShortMessage($type_name);
+ $e_desc = $ex->getShortMessage($type_desc);
+
+ $badge->setViewPolicy($v_view);
+ $badge->setEditPolicy($v_edit);
+ }
+ }
+
+ 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)
+ ->setPolicies($policies))
+ ->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setName('editPolicy')
+ ->setPolicyObject($badge)
+ ->setCapability(PhabricatorPolicyCapability::CAN_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,
+ ));
+ }
+
+}
diff --git a/src/applications/badges/controller/PhabricatorBadgesEditIconController.php b/src/applications/badges/controller/PhabricatorBadgesEditIconController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/controller/PhabricatorBadgesEditIconController.php
@@ -0,0 +1,107 @@
+<?php
+
+final class PhabricatorBadgesEditIconController
+ extends PhabricatorBadgesController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = idx($data, 'id');
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ if ($this->id) {
+ $badge = id(new PhabricatorBadgesQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->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);
+ }
+}
diff --git a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php
@@ -0,0 +1,126 @@
+<?php
+
+final class PhabricatorBadgesEditRecipientsController
+ extends PhabricatorBadgesController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+ $id = $request->getURIData('id');
+
+ $badge = id(new PhabricatorBadgesQuery())
+ ->setViewer($user)
+ ->withIDs(array($this->id))
+ ->needRecipients(true)
+ ->requireCapabilities(
+ array(
+ 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($user)
+ ->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(
+ $user,
+ $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($user)
+ ->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($user);
+
+ $badge_url = $this->getApplicationURI().'view/'.$this->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,
+ ));
+ }
+
+}
diff --git a/src/applications/badges/controller/PhabricatorBadgesListController.php b/src/applications/badges/controller/PhabricatorBadgesListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/controller/PhabricatorBadgesListController.php
@@ -0,0 +1,57 @@
+<?php
+
+final class PhabricatorBadgesListController
+ extends PhabricatorBadgesController {
+
+ private $queryKey;
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function willProcessRequest(array $data) {
+ $this->queryKey = idx($data, 'queryKey');
+ }
+
+ public function processRequest() {
+ $controller = id(new PhabricatorApplicationSearchController())
+ ->setQueryKey($this->queryKey)
+ ->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;
+ }
+
+}
diff --git a/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php
@@ -0,0 +1,83 @@
+<?php
+
+final class PhabricatorBadgesRemoveRecipientsController
+ extends PhabricatorBadgesController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $badge = id(new PhabricatorBadgesQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->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 id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+}
diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php
@@ -0,0 +1,163 @@
+<?php
+
+final class PhabricatorBadgesViewController
+ extends PhabricatorBadgesController {
+
+ private $id;
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $badge = id(new PhabricatorBadgesQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->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(
+ PhabricatorBadge::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(PhabricatorBadge $badge) {
+ $viewer = $this->getRequest()->getUser();
+
+ $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(PhabricatorBadge $badge) {
+ $viewer = $this->getRequest()->getUser();
+ $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;
+ }
+
+}
diff --git a/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php b/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php
@@ -0,0 +1,103 @@
+<?php
+
+final class PhabricatorBadgeHasRecipientEdgeType
+ extends PhabricatorEdgeType {
+
+ const EDGECONST = 101;
+
+ 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);
+ }
+}
diff --git a/src/applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php b/src/applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php
@@ -0,0 +1,103 @@
+<?php
+
+final class PhabricatorRecipientHasBadgeEdgeType
+ extends PhabricatorEdgeType {
+
+ const EDGECONST = 100;
+
+ 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);
+ }
+}
diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php
@@ -0,0 +1,193 @@
+<?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();
+ $name = pht('Badge: %s', $name);
+ 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]');
+ }
+
+}
diff --git a/src/applications/badges/icon/PhabricatorBadgesIcon.php b/src/applications/badges/icon/PhabricatorBadgesIcon.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/icon/PhabricatorBadgesIcon.php
@@ -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')),
+ ));
+ }
+
+}
diff --git a/src/applications/badges/interface/PhabricatorBadgesInterface.php b/src/applications/badges/interface/PhabricatorBadgesInterface.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/interface/PhabricatorBadgesInterface.php
@@ -0,0 +1,3 @@
+<?php
+
+interface PhabricatorBadgesInterface extends PhabricatorPHIDInterface {}
diff --git a/src/applications/badges/phid/PhabricatorBadgesPHIDType.php b/src/applications/badges/phid/PhabricatorBadgesPHIDType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/phid/PhabricatorBadgesPHIDType.php
@@ -0,0 +1,73 @@
+<?php
+
+final class PhabricatorBadgesPHIDType extends PhabricatorPHIDType {
+
+ const TYPECONST = 'BDGE';
+
+ public function getTypeName() {
+ return pht('Badge');
+ }
+
+ public function newObject() {
+ return new PhabricatorBadge();
+ }
+
+ 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->setFullName("{$name}");
+ $handle->setURI("/badges/view/{$id}/");
+ }
+ }
+
+ public function loadNamedObjects(
+ PhabricatorObjectQuery $query,
+ array $names) {
+
+ $id_map = array();
+ foreach ($names as $name) {
+ $id = (int)substr($name, 1);
+ $id_map[$id][] = $name;
+ }
+
+ $objects = id(new PhabricatorBadgesQuery())
+ ->setViewer($query->getViewer())
+ ->withIDs(array_keys($id_map))
+ ->execute();
+
+ $results = array();
+ foreach ($objects as $id => $object) {
+ foreach (idx($id_map, $id, array()) as $name) {
+ $results[$name] = $object;
+ }
+ }
+
+ return $results;
+ }
+
+}
diff --git a/src/applications/badges/query/PhabricatorBadgesQuery.php b/src/applications/badges/query/PhabricatorBadgesQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/query/PhabricatorBadgesQuery.php
@@ -0,0 +1,124 @@
+<?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() {
+ $table = new PhabricatorBadge();
+ $conn_r = $table->establishConnection('r');
+
+ $rows = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ $badges = $table->loadAllFromArray($rows);
+ $viewer_phid = $this->getViewer()->getPHID();
+ $badge_phids = mpull($badges, 'getPHID');
+
+ $recipient_type = PhabricatorBadgeHasRecipientEdgeType::EDGECONST;
+ $need_edge_types = array();
+ $need_edge_types[] = $recipient_type;
+
+ if ($badge_phids) {
+ $edges = id(new PhabricatorEdgeQuery())
+ ->withSourcePHIDs($badge_phids)
+ ->withEdgeTypes($need_edge_types)
+ ->execute();
+
+ foreach ($badges as $badge) {
+ $phid = $badge->getPHID();
+ $badge->attachRecipientPHIDs(
+ array_keys($edges[$phid][$recipient_type]));
+ }
+ }
+
+ return $badges;
+ }
+
+ protected function didFilterPage(array $badges) {
+ return $badges;
+ }
+
+ protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ if ($this->ids !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->qualities !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'quality IN (%Ls)',
+ $this->qualities);
+ }
+
+ if ($this->statuses !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'status IN (%Ls)',
+ $this->statuses);
+ }
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorBadgesApplication';
+ }
+
+}
diff --git a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php
@@ -0,0 +1,140 @@
+<?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'));
+
+ return $saved;
+ }
+
+ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ $query = new PhabricatorBadgesQuery();
+
+ $statuses = $saved->getParameter('statuses');
+ if ($statuses) {
+ $query->withStatuses($statuses);
+ }
+
+ return $query;
+ }
+
+ protected function buildCustomSearchFields() {
+ return array(
+ id(new PhabricatorSearchCheckboxesField())
+ ->setKey('statuses')
+ ->setLabel(pht('Status'))
+ ->setOptions(
+ id(new PhabricatorBadge())
+ ->getStatusNameMap()),
+ );
+ }
+
+ protected function buildQueryFromParameters(array $map) {
+ $query = $this->newQuery();
+
+ if ($map['statuses']) {
+ $query->withStatuses($map['statuses']);
+ }
+
+ 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(
+ PhabricatorBadge::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, 'PhabricatorBadge');
+
+ $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;
+
+ }
+
+}
diff --git a/src/applications/badges/query/PhabricatorBadgesTransactionQuery.php b/src/applications/badges/query/PhabricatorBadgesTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/query/PhabricatorBadgesTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorBadgesTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new PhabricatorBadgesTransaction();
+ }
+
+}
diff --git a/src/applications/badges/storage/PhabricatorBadge.php b/src/applications/badges/storage/PhabricatorBadge.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/storage/PhabricatorBadge.php
@@ -0,0 +1,196 @@
+<?php
+
+final class PhabricatorBadge extends PhabricatorBadgesDAO
+ implements
+ PhabricatorPolicyInterface,
+ PhabricatorApplicationTransactionInterface,
+ PhabricatorSubscribableInterface,
+ 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 PhabricatorBadge())
+ ->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();
+ }
+
+}
diff --git a/src/applications/badges/storage/PhabricatorBadgesDAO.php b/src/applications/badges/storage/PhabricatorBadgesDAO.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/storage/PhabricatorBadgesDAO.php
@@ -0,0 +1,9 @@
+<?php
+
+abstract class PhabricatorBadgesDAO extends PhabricatorLiskDAO {
+
+ public function getApplicationName() {
+ return 'badges';
+ }
+
+}
diff --git a/src/applications/badges/storage/PhabricatorBadgesSchemaSpec.php b/src/applications/badges/storage/PhabricatorBadgesSchemaSpec.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/storage/PhabricatorBadgesSchemaSpec.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorBadgesSchemaSpec
+ extends PhabricatorConfigSchemaSpec {
+
+ public function buildSchemata() {
+ $this->buildEdgeSchemata(new PhabricatorBadge());
+ }
+
+}
diff --git a/src/applications/badges/storage/PhabricatorBadgesTransaction.php b/src/applications/badges/storage/PhabricatorBadgesTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/storage/PhabricatorBadgesTransaction.php
@@ -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 = PhabricatorBadge::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 = PhabricatorBadge::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 PhabricatorBadge::STATUS_OPEN:
+ return pht(
+ '%s activated %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ case PhabricatorBadge::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());
+ }
+}
diff --git a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php
@@ -0,0 +1,61 @@
+<?php
+
+final class PhabricatorBadgesRecipientsListView extends AphrontTagView {
+
+ private $badge;
+ private $handles;
+
+ public function setBadge(PhabricatorBadge $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;
+ }
+
+}
diff --git a/src/applications/tokens/application/PhabricatorTokensApplication.php b/src/applications/tokens/application/PhabricatorTokensApplication.php
--- a/src/applications/tokens/application/PhabricatorTokensApplication.php
+++ b/src/applications/tokens/application/PhabricatorTokensApplication.php
@@ -11,7 +11,7 @@
}
public function getFontIcon() {
- return 'fa-trophy';
+ return 'fa-heart';
}
public function getTitleGlyph() {
diff --git a/src/applications/uiexample/examples/PHUIBadgeExample.php b/src/applications/uiexample/examples/PHUIBadgeExample.php
--- a/src/applications/uiexample/examples/PHUIBadgeExample.php
+++ b/src/applications/uiexample/examples/PHUIBadgeExample.php
@@ -89,7 +89,7 @@
$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 @@
$flex2->addItems($badges2);
$box2 = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Acheivements'))
+ ->setHeaderText(pht('Achievements'))
->appendChild($flex2);
$flex3 = new PHUIBadgeBoxView();
diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
--- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
+++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
@@ -1182,6 +1182,80 @@
'%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.',
+ ),
+ ),
+
);
}
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -106,6 +106,7 @@
'db.almanac' => array(),
'db.multimeter' => array(),
'db.spaces' => array(),
+ 'db.badges' => array(),
'0000.legacy.sql' => array(
'legacy' => 0,
),
diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php
--- a/src/view/phui/PHUIObjectItemView.php
+++ b/src/view/phui/PHUIObjectItemView.php
@@ -23,6 +23,7 @@
private $fontIcon;
private $imageIcon;
private $titleText;
+ private $badge;
const AGE_FRESH = 'fresh';
const AGE_STALE = 'stale';
@@ -99,6 +100,11 @@
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,16 @@
$status);
}
+ /* Can't set a Badge and a Status Icon */
+ if ($this->badge) {
+ $column0 = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-object-item-col0 phui-object-item-badge',
+ ),
+ $this->badge);
+ }
+
$column1 = phutil_tag(
'div',
array(
diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css
--- a/webroot/rsrc/css/phui/phui-object-item-list-view.css
+++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css
@@ -617,6 +617,17 @@
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 {
diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css
--- a/webroot/rsrc/css/phui/phui-property-list-view.css
+++ b/webroot/rsrc/css/phui/phui-property-list-view.css
@@ -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;
}

File Metadata

Mime Type
text/plain
Expires
Thu, Nov 28, 7:02 PM (21 h, 15 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6801828
Default Alt Text
D13626.id32962.diff (80 KB)

Event Timeline