Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14087962
D13626.id32935.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
75 KB
Referenced Files
None
Subscribers
None
D13626.id32935.diff
View Options
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,52 @@
+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,
+ UNIQUE KEY `key_phid` (phid)
+) 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,35 @@
'PhabricatorAuthValidateController' => 'PhabricatorAuthController',
'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAutoEventListener' => 'PhabricatorEventListener',
+ 'PhabricatorBadge' => array(
+ 'PhabricatorBadgesDAO',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorApplicationTransactionInterface',
+ '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 +6429,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,228 @@
+<?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);
+
+ return id(new AphrontRedirectResponse())
+ ->setURI('/badges/');
+ } 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,145 @@
+<?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;
+ }
+
+}
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,122 @@
+<?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;
+
+ $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,188 @@
+<?php
+
+final class PhabricatorBadge extends PhabricatorBadgesDAO
+ implements
+ PhabricatorPolicyInterface,
+ PhabricatorApplicationTransactionInterface,
+ PhabricatorDestructibleInterface {
+
+ protected $name;
+ protected $flavor;
+ protected $description;
+ protected $icon;
+ protected $quality;
+ protected $viewPolicy;
+ protected $editPolicy;
+ protected $status;
+
+ 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)
+ ->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',
+ ),
+ ) + 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 false;
+ }
+
+ public function shouldShowSubscribersProperty() {
+ return false;
+ }
+
+ public function shouldAllowSubscription($phid) {
+ return false;
+ }
+
+
+/* -( 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/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
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 25, 12:48 AM (21 h, 13 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6784447
Default Alt Text
D13626.id32935.diff (75 KB)
Attached To
Mode
D13626: Badges v0.1
Attached
Detach File
Event Timeline
Log In to Comment