Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14088093
D13626.id32984.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
79 KB
Referenced Files
None
Subscribers
None
D13626.id32984.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' => '0e143972',
+ 'core.pkg.css' => '7ac262b3',
'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' => '407eaf5a',
- 'rsrc/css/phui/phui-object-item-list-view.css' => 'fc19bfc1',
+ 'rsrc/css/phui/phui-object-item-list-view.css' => 'a1b990b7',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
- 'rsrc/css/phui/phui-property-list-view.css' => '1baf23eb',
+ 'rsrc/css/phui/phui-property-list-view.css' => 'aeb09581',
'rsrc/css/phui/phui-remarkup-preview.css' => '867f85b3',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => '888cedb8',
@@ -797,10 +797,10 @@
'phui-inline-comment-view-css' => '9fadd6b8',
'phui-list-view-css' => '125599df',
'phui-object-box-css' => '407eaf5a',
- 'phui-object-item-list-view-css' => 'fc19bfc1',
+ 'phui-object-item-list-view-css' => 'a1b990b7',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
- 'phui-property-list-view-css' => '1baf23eb',
+ 'phui-property-list-view-css' => 'aeb09581',
'phui-remarkup-preview-css' => '867f85b3',
'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8',
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
@@ -1617,6 +1617,29 @@
'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php',
'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php',
'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php',
+ 'PhabricatorBadgeHasRecipientEdgeType' => 'applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php',
+ 'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.php',
+ 'PhabricatorBadgesBadge' => 'applications/badges/storage/PhabricatorBadgesBadge.php',
+ 'PhabricatorBadgesController' => 'applications/badges/controller/PhabricatorBadgesController.php',
+ 'PhabricatorBadgesCreateCapability' => 'applications/badges/capability/PhabricatorBadgesCreateCapability.php',
+ 'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php',
+ 'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php',
+ 'PhabricatorBadgesDefaultViewCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultViewCapability.php',
+ 'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php',
+ 'PhabricatorBadgesEditIconController' => 'applications/badges/controller/PhabricatorBadgesEditIconController.php',
+ 'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php',
+ 'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php',
+ 'PhabricatorBadgesIcon' => 'applications/badges/icon/PhabricatorBadgesIcon.php',
+ 'PhabricatorBadgesListController' => 'applications/badges/controller/PhabricatorBadgesListController.php',
+ 'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php',
+ 'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.php',
+ 'PhabricatorBadgesRecipientsListView' => 'applications/badges/view/PhabricatorBadgesRecipientsListView.php',
+ 'PhabricatorBadgesRemoveRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php',
+ 'PhabricatorBadgesSchemaSpec' => 'applications/badges/storage/PhabricatorBadgesSchemaSpec.php',
+ 'PhabricatorBadgesSearchEngine' => 'applications/badges/query/PhabricatorBadgesSearchEngine.php',
+ 'PhabricatorBadgesTransaction' => 'applications/badges/storage/PhabricatorBadgesTransaction.php',
+ 'PhabricatorBadgesTransactionQuery' => 'applications/badges/query/PhabricatorBadgesTransactionQuery.php',
+ 'PhabricatorBadgesViewController' => 'applications/badges/controller/PhabricatorBadgesViewController.php',
'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php',
'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php',
'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php',
@@ -2551,6 +2574,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',
@@ -5326,6 +5350,35 @@
'PhabricatorAuthValidateController' => 'PhabricatorAuthController',
'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAutoEventListener' => 'PhabricatorEventListener',
+ 'PhabricatorBadgeHasRecipientEdgeType' => 'PhabricatorEdgeType',
+ 'PhabricatorBadgesApplication' => 'PhabricatorApplication',
+ 'PhabricatorBadgesBadge' => array(
+ 'PhabricatorBadgesDAO',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorApplicationTransactionInterface',
+ 'PhabricatorSubscribableInterface',
+ 'PhabricatorDestructibleInterface',
+ ),
+ 'PhabricatorBadgesController' => 'PhabricatorController',
+ 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability',
+ 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO',
+ 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability',
+ 'PhabricatorBadgesDefaultViewCapability' => 'PhabricatorPolicyCapability',
+ 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController',
+ 'PhabricatorBadgesEditIconController' => 'PhabricatorBadgesController',
+ 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController',
+ 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'PhabricatorBadgesIcon' => 'Phobject',
+ 'PhabricatorBadgesListController' => 'PhabricatorBadgesController',
+ 'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType',
+ 'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorBadgesRecipientsListView' => 'AphrontTagView',
+ 'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController',
+ 'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
+ 'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction',
+ 'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'PhabricatorBadgesViewController' => 'PhabricatorBadgesController',
'PhabricatorBarePageUIExample' => 'PhabricatorUIExample',
'PhabricatorBarePageView' => 'AphrontPageView',
'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck',
@@ -6417,6 +6470,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,80 @@
+<?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.'),
+ 'template' => PhabricatorBadgesPHIDType::TYPECONST,
+ ),
+ PhabricatorBadgesDefaultEditCapability::CAPABILITY => array(
+ 'default' => PhabricatorPolicies::POLICY_ADMIN,
+ 'caption' => pht('Default edit policy for badges.'),
+ 'template' => PhabricatorBadgesPHIDType::TYPECONST,
+ ),
+ PhabricatorBadgesDefaultViewCapability::CAPABILITY => array(
+ 'caption' => pht('Default view policy for badges.'),
+ 'template' => PhabricatorBadgesPHIDType::TYPECONST,
+ ),
+ );
+ }
+
+}
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,12 @@
+<?php
+
+final class PhabricatorBadgesDefaultEditCapability
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'badges.default.edit';
+
+ public function getCapabilityName() {
+ return pht('Default 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,227 @@
+<?php
+
+final class PhabricatorBadgesEditController
+ extends PhabricatorBadgesController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = idx($data, 'id');
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ 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 = PhabricatorBadgesBadge::initializeNewBadge($viewer);
+ $is_new = true;
+ }
+
+ if ($is_new) {
+ $title = pht('Create Badge');
+ $button_text = pht('Create Badge');
+ $cancel_uri = $this->getApplicationURI();
+ } else {
+ $title = pht(
+ 'Edit %s',
+ $badge->getName());
+ $button_text = pht('Save Changes');
+ $cancel_uri = $this->getApplicationURI().'view/'.$this->id.'/';
+ }
+
+ $e_name = true;
+ $v_name = $badge->getName();
+
+ $v_icon = $badge->getIcon();
+
+ $v_flav = $badge->getFlavor();
+ $v_desc = $badge->getDescription();
+ $v_qual = $badge->getQuality();
+ $v_stat = $badge->getStatus();
+
+ $v_edit = $badge->getEditPolicy();
+ $v_view = $badge->getViewPolicy();
+
+ $validation_exception = null;
+ if ($request->isFormPost()) {
+ $v_name = $request->getStr('name');
+ $v_flav = $request->getStr('flavor');
+ $v_desc = $request->getStr('description');
+ $v_icon = $request->getStr('icon');
+ $v_stat = $request->getStr('status');
+ $v_qual = $request->getStr('quality');
+
+ $v_view = $request->getStr('viewPolicy');
+ $v_edit = $request->getStr('editPolicy');
+
+ $type_name = PhabricatorBadgesTransaction::TYPE_NAME;
+ $type_flav = PhabricatorBadgesTransaction::TYPE_FLAVOR;
+ $type_desc = PhabricatorBadgesTransaction::TYPE_DESCRIPTION;
+ $type_icon = PhabricatorBadgesTransaction::TYPE_ICON;
+ $type_qual = PhabricatorBadgesTransaction::TYPE_QUALITY;
+ $type_stat = PhabricatorBadgesTransaction::TYPE_STATUS;
+
+ $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
+ $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
+
+ $xactions = array();
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType($type_name)
+ ->setNewValue($v_name);
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType($type_flav)
+ ->setNewValue($v_flav);
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType($type_desc)
+ ->setNewValue($v_desc);
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType($type_icon)
+ ->setNewValue($v_icon);
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType($type_qual)
+ ->setNewValue($v_qual);
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType($type_stat)
+ ->setNewValue($v_stat);
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType($type_view)
+ ->setNewValue($v_view);
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType($type_edit)
+ ->setNewValue($v_edit);
+
+ $editor = id(new PhabricatorBadgesEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true);
+
+ try {
+ $editor->applyTransactions($badge, $xactions);
+ $return_uri = $this->getApplicationURI('view/'.$badge->getID().'/');
+ return id(new AphrontRedirectResponse())->setURI($return_uri);
+
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $validation_exception = $ex;
+
+ $e_name = $ex->getShortMessage($type_name);
+ }
+ }
+
+ if ($is_new) {
+ $icon_uri = $this->getApplicationURI('icon/');
+ } else {
+ $icon_uri = $this->getApplicationURI('icon/'.$badge->getID().'/');
+ }
+ $icon_display = PhabricatorBadgesIcon::renderIconForChooser($v_icon);
+
+ $policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($viewer)
+ ->setObject($badge)
+ ->execute();
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setName('name')
+ ->setLabel(pht('Name'))
+ ->setValue($v_name)
+ ->setError($e_name))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setName('flavor')
+ ->setLabel(pht('Flavor Text'))
+ ->setValue($v_flav))
+ ->appendChild(
+ id(new AphrontFormChooseButtonControl())
+ ->setLabel(pht('Icon'))
+ ->setName('icon')
+ ->setDisplayValue($icon_display)
+ ->setButtonText(pht('Choose Icon...'))
+ ->setChooseURI($icon_uri)
+ ->setValue($v_icon))
+ ->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setName('quality')
+ ->setLabel(pht('Quality'))
+ ->setValue($v_qual)
+ ->setOptions($badge->getQualityNameMap()))
+ ->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setLabel(pht('Status'))
+ ->setName('status')
+ ->setValue($v_stat)
+ ->setOptions($badge->getStatusNameMap()))
+ ->appendChild(
+ id(new PhabricatorRemarkupControl())
+ ->setUser($viewer)
+ ->setName('description')
+ ->setLabel(pht('Description'))
+ ->setValue($v_desc))
+ ->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setName('viewPolicy')
+ ->setPolicyObject($badge)
+ ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
+ ->setValue($v_view)
+ ->setPolicies($policies))
+ ->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setName('editPolicy')
+ ->setPolicyObject($badge)
+ ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
+ ->setValue($v_edit)
+ ->setPolicies($policies))
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue($button_text)
+ ->addCancelButton($cancel_uri));
+
+ $crumbs = $this->buildApplicationCrumbs();
+ if ($is_new) {
+ $crumbs->addTextCrumb(pht('Create Badge'));
+ } else {
+ $crumbs->addTextCrumb(
+ $badge->getName(),
+ '/badges/view/'.$badge->getID().'/');
+ $crumbs->addTextCrumb(pht('Edit'));
+ }
+
+ $box = id(new PHUIObjectBoxView())
+ ->setValidationException($validation_exception)
+ ->setHeaderText($title)
+ ->appendChild($form);
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ ),
+ array(
+ 'title' => $title,
+ ));
+ }
+
+}
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,106 @@
+<?php
+
+final class PhabricatorBadgesEditIconController
+ extends PhabricatorBadgesController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = idx($data, 'id');
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ 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 handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+ $id = $request->getURIData('id');
+
+ $badge = id(new PhabricatorBadgesQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->needRecipients(true)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_EDIT,
+ PhabricatorPolicyCapability::CAN_VIEW,
+ ))
+ ->executeOne();
+ if (!$badge) {
+ return new Aphront404Response();
+ }
+
+ $recipient_phids = $badge->getRecipientPHIDs();
+
+ if ($request->isFormPost()) {
+ $recipient_spec = array();
+
+ $remove = $request->getStr('remove');
+ if ($remove) {
+ $recipient_spec['-'] = array_fuse(array($remove));
+ }
+
+ $add_recipients = $request->getArr('phids');
+ if ($add_recipients) {
+ $recipient_spec['+'] = array_fuse($add_recipients);
+ }
+
+ $type_recipient = PhabricatorBadgeHasRecipientEdgeType::EDGECONST;
+
+ $xactions = array();
+
+ $xactions[] = id(new PhabricatorBadgesTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
+ ->setMetadataValue('edge:type', $type_recipient)
+ ->setNewValue($recipient_spec);
+
+ $editor = id(new PhabricatorBadgesEditor($badge))
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true)
+ ->applyTransactions($badge, $xactions);
+
+ return id(new AphrontRedirectResponse())
+ ->setURI($request->getRequestURI());
+ }
+
+ $recipient_phids = array_reverse($recipient_phids);
+ $handles = $this->loadViewerHandles($recipient_phids);
+
+ $state = array();
+ foreach ($handles as $handle) {
+ $state[] = array(
+ 'phid' => $handle->getPHID(),
+ 'name' => $handle->getFullName(),
+ );
+ }
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $badge,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $form_box = null;
+ $title = pht('Add Recipient');
+ if ($can_edit) {
+ $header_name = pht('Edit Recipients');
+ $view_uri = $this->getApplicationURI('view/'.$badge->getID().'/');
+
+ $form = new AphrontFormView();
+ $form
+ ->setUser($viewer)
+ ->appendControl(
+ id(new AphrontFormTokenizerControl())
+ ->setName('phids')
+ ->setLabel(pht('Add Recipients'))
+ ->setDatasource(new PhabricatorPeopleDatasource()))
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->addCancelButton($view_uri)
+ ->setValue(pht('Add Recipients')));
+ $form_box = id(new PHUIObjectBoxView())
+ ->setHeaderText($title)
+ ->setForm($form);
+ }
+
+ $recipient_list = id(new PhabricatorBadgesRecipientsListView())
+ ->setBadge($badge)
+ ->setHandles($handles)
+ ->setUser($viewer);
+
+ $badge_url = $this->getApplicationURI('view/'.$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 handleRequest(AphrontRequest $request) {
+ $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,82 @@
+<?php
+
+final class PhabricatorBadgesRemoveRecipientsController
+ extends PhabricatorBadgesController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ $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 $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 handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+ $id = $request->getURIData('id');
+
+ $badge = id(new PhabricatorBadgesQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->needRecipients(true)
+ ->executeOne();
+ if (!$badge) {
+ return new Aphront404Response();
+ }
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb($badge->getName());
+ $title = $badge->getName();
+
+ if ($badge->isClosed()) {
+ $status_icon = 'fa-ban';
+ $status_color = 'dark';
+ } else {
+ $status_icon = 'fa-check';
+ $status_color = 'bluegrey';
+ }
+ $status_name = idx(
+ PhabricatorBadgesBadge::getStatusNameMap(),
+ $badge->getStatus());
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader($badge->getName())
+ ->setUser($viewer)
+ ->setPolicyObject($badge)
+ ->setStatus($status_icon, $status_color, $status_name);
+
+ $properties = $this->buildPropertyListView($badge);
+ $actions = $this->buildActionListView($badge);
+ $properties->setActionList($actions);
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->addPropertyList($properties);
+
+ $timeline = $this->buildTransactionTimeline(
+ $badge,
+ new PhabricatorBadgesTransactionQuery());
+ $timeline
+ ->setShouldTerminate(true);
+
+ $recipient_phids = $badge->getRecipientPHIDs();
+ $recipient_phids = array_reverse($recipient_phids);
+ $handles = $this->loadViewerHandles($recipient_phids);
+
+ $recipient_list = id(new PhabricatorBadgesRecipientsListView())
+ ->setBadge($badge)
+ ->setHandles($handles)
+ ->setUser($viewer);
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ $recipient_list,
+ $timeline,
+ ),
+ array(
+ 'title' => $title,
+ 'pageObjects' => array($badge->getPHID()),
+ ));
+ }
+
+ private function buildPropertyListView(PhabricatorBadgesBadge $badge) {
+ $viewer = $this->getViewer();
+
+ $view = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setObject($badge);
+
+ $quality = idx($badge->getQualityNameMap(), $badge->getQuality());
+ $icon = idx($badge->getIconNameMap(), $badge->getIcon());
+
+ $view->addProperty(
+ pht('Quality'),
+ $quality);
+
+ $view->addProperty(
+ pht('Icon'),
+ $icon);
+
+ $view->addProperty(
+ pht('Flavor'),
+ $badge->getFlavor());
+
+ $view->invokeWillRenderEvent();
+
+ $description = $badge->getDescription();
+ if (strlen($description)) {
+ $description = PhabricatorMarkupEngine::renderOneObject(
+ id(new PhabricatorMarkupOneOff())->setContent($description),
+ 'default',
+ $viewer);
+
+ $view->addSectionHeader(pht('Description'));
+ $view->addTextContent($description);
+ }
+
+ $badge = id(new PHUIBadgeView())
+ ->setIcon($badge->getIcon())
+ ->setHeader($badge->getName())
+ ->setSubhead($badge->getFlavor())
+ ->setQuality($badge->getQuality());
+
+ $view->addTextContent($badge);
+
+ return $view;
+ }
+
+ private function buildActionListView(PhabricatorBadgesBadge $badge) {
+ $viewer = $this->getViewer();
+ $id = $badge->getID();
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $badge,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $view = id(new PhabricatorActionListView())
+ ->setUser($viewer)
+ ->setObject($badge);
+
+ $view->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('Edit Badge'))
+ ->setIcon('fa-pencil')
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(!$can_edit)
+ ->setHref($this->getApplicationURI("/edit/{$id}/")));
+
+ $view->addAction(
+ id(new PhabricatorActionView())
+ ->setName('Manage Recipients')
+ ->setIcon('fa-users')
+ ->setDisabled(!$can_edit)
+ ->setHref($this->getApplicationURI("/recipients/{$id}/")));
+
+ return $view;
+ }
+
+}
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,194 @@
+<?php
+
+final class PhabricatorBadgesEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ public function getEditorApplicationClass() {
+ return 'PhabricatorBadgesApplication';
+ }
+
+ public function getEditorObjectsDescription() {
+ return pht('Badges');
+ }
+
+ public function getTransactionTypes() {
+ $types = parent::getTransactionTypes();
+
+ $types[] = PhabricatorBadgesTransaction::TYPE_NAME;
+ $types[] = PhabricatorBadgesTransaction::TYPE_FLAVOR;
+ $types[] = PhabricatorBadgesTransaction::TYPE_DESCRIPTION;
+ $types[] = PhabricatorBadgesTransaction::TYPE_ICON;
+ $types[] = PhabricatorBadgesTransaction::TYPE_STATUS;
+ $types[] = PhabricatorBadgesTransaction::TYPE_QUALITY;
+
+ $types[] = PhabricatorTransactions::TYPE_EDGE;
+ $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
+ $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
+
+ return $types;
+ }
+
+ protected function getCustomTransactionOldValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorBadgesTransaction::TYPE_NAME:
+ return $object->getName();
+ case PhabricatorBadgesTransaction::TYPE_FLAVOR:
+ return $object->getFlavor();
+ case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
+ return $object->getDescription();
+ case PhabricatorBadgesTransaction::TYPE_ICON:
+ return $object->getIcon();
+ case PhabricatorBadgesTransaction::TYPE_QUALITY:
+ return $object->getQuality();
+ case PhabricatorBadgesTransaction::TYPE_STATUS:
+ return $object->getStatus();
+ }
+
+ return parent::getCustomTransactionOldValue($object, $xaction);
+ }
+
+ protected function getCustomTransactionNewValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorBadgesTransaction::TYPE_NAME:
+ case PhabricatorBadgesTransaction::TYPE_FLAVOR:
+ case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
+ case PhabricatorBadgesTransaction::TYPE_ICON:
+ case PhabricatorBadgesTransaction::TYPE_STATUS:
+ case PhabricatorBadgesTransaction::TYPE_QUALITY:
+ return $xaction->getNewValue();
+ }
+
+ return parent::getCustomTransactionNewValue($object, $xaction);
+ }
+
+ protected function applyCustomInternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ $type = $xaction->getTransactionType();
+ switch ($type) {
+ case PhabricatorBadgesTransaction::TYPE_NAME:
+ $object->setName($xaction->getNewValue());
+ return;
+ case PhabricatorBadgesTransaction::TYPE_FLAVOR:
+ $object->setFlavor($xaction->getNewValue());
+ return;
+ case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
+ $object->setDescription($xaction->getNewValue());
+ return;
+ case PhabricatorBadgesTransaction::TYPE_ICON:
+ $object->setIcon($xaction->getNewValue());
+ return;
+ case PhabricatorBadgesTransaction::TYPE_QUALITY:
+ $object->setQuality($xaction->getNewValue());
+ return;
+ case PhabricatorBadgesTransaction::TYPE_STATUS:
+ $object->setStatus($xaction->getNewValue());
+ return;
+ }
+
+ return parent::applyCustomInternalTransaction($object, $xaction);
+ }
+
+ protected function applyCustomExternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ $type = $xaction->getTransactionType();
+ switch ($type) {
+ case PhabricatorBadgesTransaction::TYPE_NAME:
+ case PhabricatorBadgesTransaction::TYPE_FLAVOR:
+ case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
+ case PhabricatorBadgesTransaction::TYPE_ICON:
+ case PhabricatorBadgesTransaction::TYPE_STATUS:
+ case PhabricatorBadgesTransaction::TYPE_QUALITY:
+ return;
+ }
+
+ return parent::applyCustomExternalTransaction($object, $xaction);
+ }
+
+ protected function validateTransaction(
+ PhabricatorLiskDAO $object,
+ $type,
+ array $xactions) {
+
+ $errors = parent::validateTransaction($object, $type, $xactions);
+
+ switch ($type) {
+ case PhabricatorBadgesTransaction::TYPE_NAME:
+ $missing = $this->validateIsEmptyTextField(
+ $object->getName(),
+ $xactions);
+
+ if ($missing) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Required'),
+ pht('Badge name is required.'),
+ nonempty(last($xactions), null));
+
+ $error->setIsMissingFieldError(true);
+ $errors[] = $error;
+ }
+ break;
+ }
+
+ return $errors;
+ }
+
+ protected function shouldPublishFeedStory(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return true;
+ }
+
+ protected function buildReplyHandler(PhabricatorLiskDAO $object) {
+ return id(new PhabricatorMacroReplyHandler())
+ ->setMailReceiver($object);
+ }
+
+ protected function buildMailTemplate(PhabricatorLiskDAO $object) {
+ $name = $object->getName();
+ $id = $object->getID();
+ $name = pht('Badge %d', $id);
+ return id(new PhabricatorMetaMTAMail())
+ ->setSubject($name)
+ ->addHeader('Thread-Topic', $name);
+ }
+
+ protected function getMailTo(PhabricatorLiskDAO $object) {
+ return array(
+ $this->requireActor()->getPHID(),
+ );
+ }
+
+ protected function buildMailBody(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $description = $object->getDescription();
+ $body = parent::buildMailBody($object, $xactions);
+
+ if (strlen($description)) {
+ $body->addTextSection(
+ pht('BADGE DESCRIPTION'),
+ $object->getDescription());
+ }
+
+ $body->addLinkSection(
+ pht('BADGE DETAIL'),
+ PhabricatorEnv::getProductionURI('/badge/view/'.$object->getID().'/'));
+ return $body;
+ }
+
+ protected function getMailSubjectPrefix() {
+ return pht('[Badge]');
+ }
+
+}
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/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,47 @@
+<?php
+
+final class PhabricatorBadgesPHIDType extends PhabricatorPHIDType {
+
+ const TYPECONST = 'BDGE';
+
+ public function getTypeName() {
+ return pht('Badge');
+ }
+
+ public function newObject() {
+ return new PhabricatorBadgesBadge();
+ }
+
+ public function getPHIDTypeApplicationClass() {
+ return 'PhabricatorBadgesApplication';
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new PhabricatorBadgesQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $badge = $objects[$phid];
+
+ $id = $badge->getID();
+ $name = $badge->getName();
+
+ if ($badge->isClosed()) {
+ $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
+ }
+
+ $handle->setName($name);
+ $handle->setURI("/badges/view/{$id}/");
+ }
+ }
+
+}
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,113 @@
+<?php
+
+final class PhabricatorBadgesQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $qualities;
+ private $statuses;
+ private $recipientPHIDs;
+
+ private $needRecipients;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withQualities(array $qualities) {
+ $this->qualities = $qualities;
+ return $this;
+ }
+
+ public function withStatuses(array $statuses) {
+ $this->statuses = $statuses;
+ return $this;
+ }
+
+ public function withRecipientPHIDs(array $recipient_phids) {
+ $this->recipientPHIDs = $recipient_phids;
+ return $this;
+ }
+
+ public function needRecipients($need_recipients) {
+ $this->needRecipients = $need_recipients;
+ return $this;
+ }
+
+ protected function loadPage() {
+ return $this->loadStandardPage($this->newResultObject());
+ }
+
+ public function newResultObject() {
+ return new PhabricatorBadgesBadge();
+ }
+
+ protected function didFilterPage(array $badges) {
+
+ if ($this->needRecipients) {
+ $edge_query = id(new PhabricatorEdgeQuery())
+ ->withSourcePHIDs(mpull($badges, 'getPHID'))
+ ->withEdgeTypes(
+ array(
+ PhabricatorBadgeHasRecipientEdgeType::EDGECONST,
+ ));
+ $edge_query->execute();
+
+ foreach ($badges as $badge) {
+ $phids = $edge_query->getDestinationPHIDs(
+ array(
+ $badge->getPHID(),
+ ));
+ $badge->attachRecipientPHIDs($phids);
+ }
+ }
+
+ return $badges;
+ }
+
+ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+ $where = parent::buildWhereClauseParts($conn);
+
+ if ($this->ids !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->qualities !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'quality IN (%Ls)',
+ $this->qualities);
+ }
+
+ if ($this->statuses !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'status IN (%Ls)',
+ $this->statuses);
+ }
+
+ return $where;
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorBadgesApplication';
+ }
+
+}
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,159 @@
+<?php
+
+final class PhabricatorBadgesSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function getResultTypeDescription() {
+ return pht('Badge');
+ }
+
+ public function getApplicationClassName() {
+ return 'PhabricatorBadgesApplication';
+ }
+
+ public function newQuery() {
+ return new PhabricatorBadgesQuery();
+ }
+
+ public function buildSavedQueryFromRequest(AphrontRequest $request) {
+ $saved = new PhabricatorSavedQuery();
+
+ $saved->setParameter(
+ 'statuses',
+ $this->readListFromRequest($request, 'statuses'));
+
+ $saved->setParameter(
+ 'qualities',
+ $this->readListFromRequest($request, 'qualities'));
+
+ return $saved;
+ }
+
+ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ $query = new PhabricatorBadgesQuery();
+
+ $statuses = $saved->getParameter('statuses');
+ if ($statuses) {
+ $query->withStatuses($statuses);
+ }
+
+ $qualities = $saved->getParameter('qualities');
+ if ($qualities) {
+ $query->withQualities($qualities);
+ }
+
+ return $query;
+ }
+
+ protected function buildCustomSearchFields() {
+ return array(
+ id(new PhabricatorSearchCheckboxesField())
+ ->setKey('qualities')
+ ->setLabel(pht('Quality'))
+ ->setOptions(
+ id(new PhabricatorBadgesBadge())
+ ->getQualityNameMap()),
+ id(new PhabricatorSearchCheckboxesField())
+ ->setKey('statuses')
+ ->setLabel(pht('Status'))
+ ->setOptions(
+ id(new PhabricatorBadgesBadge())
+ ->getStatusNameMap()),
+ );
+ }
+
+ protected function buildQueryFromParameters(array $map) {
+ $query = $this->newQuery();
+
+ if ($map['statuses']) {
+ $query->withStatuses($map['statuses']);
+ }
+
+ if ($map['qualities']) {
+ $query->withQualities($map['qualities']);
+ }
+
+ return $query;
+ }
+
+ protected function getURI($path) {
+ return '/badges/'.$path;
+ }
+
+ protected function getBuiltinQueryNames() {
+ $names = array();
+
+ $names['open'] = pht('Active Badges');
+ $names['all'] = pht('All Badges');
+
+ return $names;
+ }
+
+ public function buildSavedQueryFromBuiltin($query_key) {
+ $query = $this->newSavedQuery();
+ $query->setQueryKey($query_key);
+
+ switch ($query_key) {
+ case 'all':
+ return $query;
+ case 'open':
+ return $query->setParameter(
+ 'statuses',
+ array(
+ PhabricatorBadgesBadge::STATUS_OPEN,
+ ));
+ }
+
+ return parent::buildSavedQueryFromBuiltin($query_key);
+ }
+
+ protected function getRequiredHandlePHIDsForResultList(
+ array $badges,
+ PhabricatorSavedQuery $query) {
+
+ $phids = array();
+
+ return $phids;
+ }
+
+ protected function renderResultList(
+ array $badges,
+ PhabricatorSavedQuery $query,
+ array $handles) {
+ assert_instances_of($badges, 'PhabricatorBadgesBadge');
+
+ $viewer = $this->requireViewer();
+
+ $list = id(new PHUIObjectItemListView());
+ foreach ($badges as $badge) {
+
+ $quality = idx($badge->getQualityNameMap(), $badge->getQuality());
+ $mini_badge = id(new PHUIBadgeMiniView())
+ ->setHeader($badge->getName())
+ ->setIcon($badge->getIcon())
+ ->setQuality($badge->getQuality());
+
+ $item = id(new PHUIObjectItemView())
+ ->setHeader($badge->getName())
+ ->setBadge($mini_badge)
+ ->setHref('/badges/view/'.$badge->getID().'/')
+ ->addAttribute($quality)
+ ->addAttribute($badge->getFlavor());
+
+ if ($badge->isClosed()) {
+ $item->setDisabled(true);
+ $item->addIcon('fa-ban', pht('Archived'));
+ }
+
+ $list->addItem($item);
+ }
+
+ $result = new PhabricatorApplicationSearchResultView();
+ $result->setObjectList($list);
+ $result->setNoDataString(pht('No badges found.'));
+
+ return $result;
+
+ }
+
+}
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/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php
new file mode 100644
--- /dev/null
+++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php
@@ -0,0 +1,196 @@
+<?php
+
+final class PhabricatorBadgesBadge 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 PhabricatorBadgesBadge())
+ ->setIcon(self::DEFAULT_ICON)
+ ->setQuality(self::DEFAULT_QUALITY)
+ ->setCreatorPHID($actor->getPHID())
+ ->setViewPolicy($view_policy)
+ ->setEditPolicy($edit_policy)
+ ->setStatus(self::STATUS_OPEN);
+ }
+
+ protected function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'name' => 'text255',
+ 'flavor' => 'text255',
+ 'description' => 'text',
+ 'icon' => 'text255',
+ 'quality' => 'text255',
+ 'status' => 'text32',
+ ),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_creator' => array(
+ 'columns' => array('creatorPHID', 'dateModified'),
+ ),
+ ),
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return
+ PhabricatorPHID::generateNewPHID(PhabricatorBadgesPHIDType::TYPECONST);
+ }
+
+ public function isClosed() {
+ return ($this->getStatus() == self::STATUS_CLOSED);
+ }
+
+ public function attachRecipientPHIDs(array $phids) {
+ $this->recipientPHIDs = $phids;
+ return $this;
+ }
+
+ public function getRecipientPHIDs() {
+ return $this->assertAttached($this->recipientPHIDs);
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ );
+ }
+
+ public function getPolicy($capability) {
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return $this->getViewPolicy();
+ case PhabricatorPolicyCapability::CAN_EDIT:
+ return $this->getEditPolicy();
+ }
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return null;
+ }
+
+
+/* -( PhabricatorApplicationTransactionInterface )------------------------- */
+
+
+ public function getApplicationTransactionEditor() {
+ return new PhabricatorBadgesEditor();
+ }
+
+ public function getApplicationTransactionObject() {
+ return $this;
+ }
+
+ public function getApplicationTransactionTemplate() {
+ return new PhabricatorBadgesTransaction();
+ }
+
+ public function willRenderTimeline(
+ PhabricatorApplicationTransactionView $timeline,
+ AphrontRequest $request) {
+
+ return $timeline;
+ }
+
+
+/* -( PhabricatorSubscribableInterface )----------------------------------- */
+
+
+ public function isAutomaticallySubscribed($phid) {
+ return ($this->creatorPHID == $phid);
+ }
+
+ public function shouldShowSubscribersProperty() {
+ return true;
+ }
+
+ public function shouldAllowSubscription($phid) {
+ return true;
+ }
+
+
+/* -( PhabricatorDestructibleInterface )----------------------------------- */
+
+
+ public function destroyObjectPermanently(
+ PhabricatorDestructionEngine $engine) {
+
+ $this->openTransaction();
+ $this->delete();
+ $this->saveTransaction();
+ }
+
+}
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 PhabricatorBadgesBadge());
+ }
+
+}
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 = PhabricatorBadgesBadge::getIconNameMap();
+ $icon_new = idx($icon_map, $new, $new);
+ $icon_old = idx($icon_map, $old, $old);
+ return pht(
+ '%s updated the icon for this badge from "%s" to "%s".',
+ $this->renderHandleLink($author_phid),
+ $icon_old,
+ $icon_new);
+ }
+ break;
+ case self::TYPE_QUALITY:
+ if ($old === null) {
+ return pht(
+ '%s set the quality for this badge as "%s".',
+ $this->renderHandleLink($author_phid),
+ $new);
+ } else {
+ $qual_map = PhabricatorBadgesBadge::getQualityNameMap();
+ $qual_new = idx($qual_map, $new, $new);
+ $qual_old = idx($qual_map, $old, $old);
+ return pht(
+ '%s updated the quality for this badge from "%s" to "%s".',
+ $this->renderHandleLink($author_phid),
+ $qual_old,
+ $qual_new);
+ }
+ break;
+ }
+
+ return parent::getTitle();
+ }
+
+ public function getTitleForFeed() {
+ $author_phid = $this->getAuthorPHID();
+ $object_phid = $this->getObjectPHID();
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $type = $this->getTransactionType();
+ switch ($type) {
+ case self::TYPE_NAME:
+ if ($old === null) {
+ return pht(
+ '%s created %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+
+ } else {
+ return pht(
+ '%s renamed %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ }
+ break;
+ case self::TYPE_FLAVOR:
+ return pht(
+ '%s updated the flavor text for %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ case self::TYPE_ICON:
+ return pht(
+ '%s updated the icon for %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ case self::TYPE_QUALITY:
+ return pht(
+ '%s updated the quality level for %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ case self::TYPE_DESCRIPTION:
+ return pht(
+ '%s updated the description for %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ case self::TYPE_STATUS:
+ switch ($new) {
+ case PhabricatorBadgesBadge::STATUS_OPEN:
+ return pht(
+ '%s activated %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ case PhabricatorBadgesBadge::STATUS_CLOSED:
+ return pht(
+ '%s archived %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ }
+ break;
+ }
+
+ return parent::getTitleForFeed();
+ }
+
+
+ public function shouldHide() {
+ $old = $this->getOldValue();
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DESCRIPTION:
+ return ($old === null);
+ }
+ return parent::shouldHide();
+ }
+
+ public function hasChangeDetails() {
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DESCRIPTION:
+ return ($this->getOldValue() !== null);
+ }
+
+ return parent::hasChangeDetails();
+ }
+
+ public function renderChangeDetails(PhabricatorUser $viewer) {
+ return $this->renderTextCorpusChangeDetails(
+ $viewer,
+ $this->getOldValue(),
+ $this->getNewValue());
+ }
+}
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(PhabricatorBadgesBadge $badge) {
+ $this->badge = $badge;
+ return $this;
+ }
+
+ public function setHandles(array $handles) {
+ $this->handles = $handles;
+ return $this;
+ }
+
+ protected function getTagContent() {
+
+ $viewer = $this->user;
+
+ $badge = $this->badge;
+ $handles = $this->handles;
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $badge,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $list = id(new PHUIObjectItemListView())
+ ->setNoDataString(pht('This badge does not have any recipients.'));
+
+ foreach ($handles as $handle) {
+ $remove_uri = '/badges/recipients/'.
+ $badge->getID().'/remove/?phid='.$handle->getPHID();
+
+ $item = id(new PHUIObjectItemView())
+ ->setHeader($handle->getFullName())
+ ->setHref($handle->getURI())
+ ->setImageURI($handle->getImageURI());
+
+ if ($can_edit) {
+ $item->addAction(
+ id(new PHUIListItemView())
+ ->setIcon('fa-times')
+ ->setName(pht('Remove'))
+ ->setHref($remove_uri)
+ ->setWorkflow(true));
+ }
+
+ $list->addItem($item);
+ }
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Recipients'))
+ ->setObjectList($list);
+
+ return $box;
+ }
+
+}
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-thumbs-up';
}
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,15 @@
$status);
}
+ if ($this->badge) {
+ $column0 = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-object-item-col0 phui-object-item-badge',
+ ),
+ $this->badge);
+ }
+
$column1 = phutil_tag(
'div',
array(
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, 1:25 AM (19 h, 21 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6784556
Default Alt Text
D13626.id32984.diff (79 KB)
Attached To
Mode
D13626: Badges v0.1
Attached
Detach File
Event Timeline
Log In to Comment