Badges v0.1

Summary:
Basic plumbing for Badges application.

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

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

Reviewers: lpriestley, epriestley

Reviewed By: epriestley

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

Maniphest Tasks: T6526

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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