Tokens v1

Summary:
Features!

  - Giving tokens.
  - Taking tokens back.
  - Not giving tokens.

Test Plan: See screenshots.

Reviewers: chad, vrana

Reviewed By: chad

CC: aran, btrahan

Maniphest Tasks: T2541

Differential Revision: https://secure.phabricator.com/D4964
This commit is contained in:
epriestley
2013-02-15 07:47:14 -08:00
parent f9f29253e4
commit 49c40d209d
60 changed files with 1758 additions and 452 deletions

View File

@@ -532,6 +532,13 @@ celerity_register_resource_map(array(
'disk' => '/rsrc/image/sprite-menu.png',
'type' => 'png',
),
'/rsrc/image/sprite-tokens.png' =>
array(
'hash' => '67c46fd75c885b76ecbfe46e71a476cc',
'uri' => '/res/67c46fd7/rsrc/image/sprite-tokens.png',
'disk' => '/rsrc/image/sprite-tokens.png',
'type' => 'png',
),
'/rsrc/image/texture/dark-menu-hover.png' =>
array(
'hash' => 'a214a732644be34872e895b338b5d639',
@@ -2861,7 +2868,7 @@ celerity_register_resource_map(array(
),
'phabricator-pinboard-view-css' =>
array(
'uri' => '/res/fdb2470f/rsrc/css/layout/phabricator-pinboard-view.css',
'uri' => '/res/b954ccbf/rsrc/css/layout/phabricator-pinboard-view.css',
'type' => 'css',
'requires' =>
array(
@@ -3336,7 +3343,7 @@ celerity_register_resource_map(array(
),
'sprite-conpher-css' =>
array(
'uri' => '/res/89821322/rsrc/css/sprite-conph.css',
'uri' => '/res/f640f0c5/rsrc/css/sprite-conph.css',
'type' => 'css',
'requires' =>
array(
@@ -3370,6 +3377,15 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/css/sprite-menu.css',
),
'sprite-tokens-css' =>
array(
'uri' => '/res/9ae0de5b/rsrc/css/sprite-tokens.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/sprite-tokens.css',
),
'stripe-core' =>
array(
'uri' => '/res/3b0f0ad4/rsrc/js/stripe/stripe_core.js',
@@ -3397,6 +3413,15 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/css/core/syntax.css',
),
'tokens-css' =>
array(
'uri' => '/res/c1f6113c/rsrc/css/application/tokens/tokens.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/tokens/tokens.css',
),
), array(
'packages' =>
array(

View File

@@ -182,6 +182,10 @@ phutil_register_library_map(array(
'ConduitAPI_repository_create_Method' => 'applications/repository/conduit/ConduitAPI_repository_create_Method.php',
'ConduitAPI_repository_query_Method' => 'applications/repository/conduit/ConduitAPI_repository_query_Method.php',
'ConduitAPI_slowvote_info_Method' => 'applications/slowvote/conduit/ConduitAPI_slowvote_info_Method.php',
'ConduitAPI_token_Method' => 'applications/tokens/conduit/ConduitAPI_token_Method.php',
'ConduitAPI_token_give_Method' => 'applications/tokens/conduit/ConduitAPI_token_give_Method.php',
'ConduitAPI_token_given_Method' => 'applications/tokens/conduit/ConduitAPI_token_given_Method.php',
'ConduitAPI_token_query_Method' => 'applications/tokens/conduit/ConduitAPI_token_query_Method.php',
'ConduitAPI_user_Method' => 'applications/people/conduit/ConduitAPI_user_Method.php',
'ConduitAPI_user_addstatus_Method' => 'applications/people/conduit/ConduitAPI_user_addstatus_Method.php',
'ConduitAPI_user_disable_Method' => 'applications/people/conduit/ConduitAPI_user_disable_Method.php',
@@ -664,6 +668,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationSlowvote' => 'applications/slowvote/application/PhabricatorApplicationSlowvote.php',
'PhabricatorApplicationStatusView' => 'applications/meta/view/PhabricatorApplicationStatusView.php',
'PhabricatorApplicationSubscriptions' => 'applications/subscriptions/application/PhabricatorApplicationSubscriptions.php',
'PhabricatorApplicationTokens' => 'applications/tokens/application/PhabricatorApplicationTokens.php',
'PhabricatorApplicationTransaction' => 'applications/transactions/storage/PhabricatorApplicationTransaction.php',
'PhabricatorApplicationTransactionComment' => 'applications/transactions/storage/PhabricatorApplicationTransactionComment.php',
'PhabricatorApplicationTransactionCommentEditController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php',
@@ -1323,6 +1328,18 @@ phutil_register_library_map(array(
'PhabricatorTimelineIterator' => 'infrastructure/daemon/timeline/cursor/PhabricatorTimelineIterator.php',
'PhabricatorTimelineView' => 'view/layout/PhabricatorTimelineView.php',
'PhabricatorTimer' => 'applications/countdown/storage/PhabricatorTimer.php',
'PhabricatorToken' => 'applications/tokens/storage/PhabricatorToken.php',
'PhabricatorTokenController' => 'applications/tokens/controller/PhabricatorTokenController.php',
'PhabricatorTokenCount' => 'applications/tokens/storage/PhabricatorTokenCount.php',
'PhabricatorTokenDAO' => 'applications/tokens/storage/PhabricatorTokenDAO.php',
'PhabricatorTokenGiveController' => 'applications/tokens/controller/PhabricatorTokenGiveController.php',
'PhabricatorTokenGiven' => 'applications/tokens/storage/PhabricatorTokenGiven.php',
'PhabricatorTokenGivenController' => 'applications/tokens/controller/PhabricatorTokenGivenController.php',
'PhabricatorTokenGivenEditor' => 'applications/tokens/editor/PhabricatorTokenGivenEditor.php',
'PhabricatorTokenGivenQuery' => 'applications/tokens/query/PhabricatorTokenGivenQuery.php',
'PhabricatorTokenQuery' => 'applications/tokens/query/PhabricatorTokenQuery.php',
'PhabricatorTokenReceiverInterface' => 'applications/tokens/interface/PhabricatorTokenReceiverInterface.php',
'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php',
'PhabricatorTransactionView' => 'view/layout/PhabricatorTransactionView.php',
'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php',
'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php',
@@ -1683,6 +1700,10 @@ phutil_register_library_map(array(
'ConduitAPI_repository_create_Method' => 'ConduitAPI_repository_Method',
'ConduitAPI_repository_query_Method' => 'ConduitAPI_repository_Method',
'ConduitAPI_slowvote_info_Method' => 'ConduitAPIMethod',
'ConduitAPI_token_Method' => 'ConduitAPIMethod',
'ConduitAPI_token_give_Method' => 'ConduitAPI_token_Method',
'ConduitAPI_token_given_Method' => 'ConduitAPI_token_Method',
'ConduitAPI_token_query_Method' => 'ConduitAPI_token_Method',
'ConduitAPI_user_Method' => 'ConduitAPIMethod',
'ConduitAPI_user_addstatus_Method' => 'ConduitAPI_user_Method',
'ConduitAPI_user_disable_Method' => 'ConduitAPI_user_Method',
@@ -1817,7 +1838,12 @@ phutil_register_library_map(array(
'DifferentialReviewedByFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialReviewerStatsTestCase' => 'PhabricatorTestCase',
'DifferentialReviewersFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialRevision' => 'DifferentialDAO',
'DifferentialRevision' =>
array(
0 => 'DifferentialDAO',
1 => 'PhabricatorTokenReceiverInterface',
2 => 'PhabricatorPolicyInterface',
),
'DifferentialRevisionCommentListView' => 'AphrontView',
'DifferentialRevisionCommentView' => 'AphrontView',
'DifferentialRevisionDetailView' => 'AphrontView',
@@ -2112,6 +2138,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationSlowvote' => 'PhabricatorApplication',
'PhabricatorApplicationStatusView' => 'AphrontView',
'PhabricatorApplicationSubscriptions' => 'PhabricatorApplication',
'PhabricatorApplicationTokens' => 'PhabricatorApplication',
'PhabricatorApplicationTransaction' =>
array(
0 => 'PhabricatorLiskDAO',
@@ -2741,6 +2768,25 @@ phutil_register_library_map(array(
'PhabricatorTimelineIterator' => 'Iterator',
'PhabricatorTimelineView' => 'AphrontView',
'PhabricatorTimer' => 'PhabricatorCountdownDAO',
'PhabricatorToken' =>
array(
0 => 'PhabricatorTokenDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhabricatorTokenController' => 'PhabricatorController',
'PhabricatorTokenCount' => 'PhabricatorTokenDAO',
'PhabricatorTokenDAO' => 'PhabricatorLiskDAO',
'PhabricatorTokenGiveController' => 'PhabricatorTokenController',
'PhabricatorTokenGiven' =>
array(
0 => 'PhabricatorTokenDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhabricatorTokenGivenController' => 'PhabricatorTokenController',
'PhabricatorTokenGivenEditor' => 'PhabricatorEditor',
'PhabricatorTokenGivenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorTokenUIEventListener' => 'PhutilEventListener',
'PhabricatorTransactionView' => 'AphrontView',
'PhabricatorTransformedFile' => 'PhabricatorFileDAO',
'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions',
@@ -2853,6 +2899,7 @@ phutil_register_library_map(array(
1 => 'PhabricatorMarkupInterface',
2 => 'PhabricatorPolicyInterface',
3 => 'PhabricatorSubscribableInterface',
4 => 'PhabricatorTokenReceiverInterface',
),
'PholioMockCommentController' => 'PholioController',
'PholioMockEditController' => 'PholioController',

View File

@@ -1,6 +1,7 @@
<?php
final class DifferentialRevision extends DifferentialDAO {
final class DifferentialRevision extends DifferentialDAO
implements PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface {
protected $title;
protected $originalTitle;
@@ -311,4 +312,19 @@ final class DifferentialRevision extends DifferentialDAO {
return $this;
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_USER;
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
return false;
}
}

View File

@@ -58,7 +58,10 @@ final class DifferentialRevisionDetailView extends AphrontView {
$actions->addAction($obj);
}
$properties = new PhabricatorPropertyListView();
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($revision);
$status = $revision->getStatus();
$local_vcs = $this->getDiff()->getSourceControlSystem();

View File

@@ -424,7 +424,9 @@ final class ManiphestTaskDetailController extends ManiphestController {
$viewer = $this->getRequest()->getUser();
$view = new PhabricatorPropertyListView();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($task);
$view->addProperty(
pht('Assigned To'),
@@ -511,6 +513,8 @@ final class ManiphestTaskDetailController extends ManiphestController {
$file_view->render());
}
$view->invokeWillRenderEvent();
if (strlen($task->getDescription())) {
$view->addSectionHeader(pht('Description'));
$view->addTextContent(

View File

@@ -4,7 +4,10 @@
* @group maniphest
*/
final class ManiphestTask extends ManiphestDAO
implements PhabricatorMarkupInterface {
implements
PhabricatorMarkupInterface,
PhabricatorPolicyInterface,
PhabricatorTokenReceiverInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
@@ -261,4 +264,23 @@ final class ManiphestTask extends ManiphestDAO
return (bool)$this->getID();
}
/* -( Policy Interface )--------------------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_USER;
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
return false;
}
}

View File

@@ -122,7 +122,9 @@ final class PholioMockViewController extends PholioController {
$user = $this->getRequest()->getUser();
$properties = new PhabricatorPropertyListView();
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($mock);
$properties->addProperty(
pht('Author'),
@@ -154,6 +156,8 @@ final class PholioMockViewController extends PholioController {
pht('Subscribers'),
$sub_view);
$properties->invokeWillRenderEvent();
$properties->addTextContent(
$engine->getOutput($mock, PholioMock::MARKUP_FIELD_DESCRIPTION));

View File

@@ -7,7 +7,8 @@ final class PholioMock extends PholioDAO
implements
PhabricatorMarkupInterface,
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface {
PhabricatorSubscribableInterface,
PhabricatorTokenReceiverInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:description';

View File

@@ -0,0 +1,45 @@
<?php
final class PhabricatorApplicationTokens extends PhabricatorApplication {
public function getName() {
return pht('Tokens');
}
public function isBeta() {
return true;
}
public function getBaseURI() {
return '/token/';
}
public function getTitleGlyph() {
return "\xE2\x99\xA6";
}
public function getShortDescription() {
return pht('Acquire Trinkets');
}
public function getApplicationGroup() {
return self::GROUP_UTILITIES;
}
public function getRoutes() {
return array(
'/token/' => array(
'' => 'PhabricatorTokenGivenController',
'given/' => 'PhabricatorTokenGivenController',
'give/(?<phid>[^/]+)/' => 'PhabricatorTokenGiveController',
),
);
}
public function getEventListeners() {
return array(
new PhabricatorTokenUIEventListener(),
);
}
}

View File

@@ -0,0 +1,41 @@
<?php
abstract class ConduitAPI_token_Method extends ConduitAPIMethod {
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function buildTokenDicts(array $tokens) {
assert_instances_of($tokens, 'PhabricatorToken');
$list = array();
foreach ($tokens as $token) {
$list[] = array(
'id' => $token->getID(),
'name' => $token->getName(),
'phid' => $token->getPHID(),
);
}
return $list;
}
public function buildTokenGivenDicts(array $tokens_given) {
assert_instances_of($tokens_given, 'PhabricatorTokenGiven');
$list = array();
foreach ($tokens_given as $given) {
$list[] = array(
'authorPHID' => $given->getAuthorPHID(),
'objectPHID' => $given->getObjectPHID(),
'tokenPHID' => $given->getTokenPHID(),
'dateCreated' => $given->getDateCreated(),
);
}
return $list;
}
}

View File

@@ -0,0 +1,37 @@
<?php
final class ConduitAPI_token_give_Method extends ConduitAPI_token_Method {
public function getMethodDescription() {
return pht('Give or change a token.');
}
public function defineParamTypes() {
return array(
'tokenPHID' => 'phid|null',
'objectPHID' => 'phid',
);
}
public function defineErrorTypes() {
return array();
}
public function defineReturnType() {
return 'void';
}
public function execute(ConduitAPIRequest $request) {
$editor = id(new PhabricatorTokenGivenEditor())
->setActor($request->getUser());
if ($request->getValue('tokenPHID')) {
$editor->addToken(
$request->getValue('objectPHID'),
$request->getValue('tokenPHID'));
} else {
$editor->deleteToken($request->getValue('objectPHID'));
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
final class ConduitAPI_token_given_Method extends ConduitAPI_token_Method {
public function getMethodDescription() {
return pht('Query tokens given to objects.');
}
public function defineParamTypes() {
return array(
'authorPHIDs' => 'list<phid>',
'objectPHIDs' => 'list<phid>',
'tokenPHIDs' => 'list<phid>',
);
}
public function defineErrorTypes() {
return array();
}
public function defineReturnType() {
return 'list<dict>';
}
public function execute(ConduitAPIRequest $request) {
$query = id(new PhabricatorTokenGivenQuery())
->setViewer($request->getUser());
$author_phids = $request->getValue('authorPHIDs');
if ($author_phids) {
$query->withAuthorPHIDs($author_phids);
}
$object_phids = $request->getValue('objectPHIDs');
if ($object_phids) {
$query->withObjectPHIDs($object_phids);
}
$token_phids = $request->getValue('tokenPHIDs');
if ($token_phids) {
$query->withTokenPHIDs($token_phids);
}
$given = $query->execute();
return $this->buildTokenGivenDicts($given);
}
}

View File

@@ -0,0 +1,30 @@
<?php
final class ConduitAPI_token_query_Method extends ConduitAPI_token_Method {
public function getMethodDescription() {
return pht('Query tokens.');
}
public function defineParamTypes() {
return array();
}
public function defineReturnType() {
return 'list<dict>';
}
public function defineErrorTypes() {
return array();
}
public function execute(ConduitAPIRequest $request) {
$query = id(new PhabricatorTokenQuery())
->setViewer($request->getUser());
$tokens = $query->execute();
return $this->buildTokenDicts($tokens);
}
}

View File

@@ -0,0 +1,16 @@
<?php
abstract class PhabricatorTokenController extends PhabricatorController {
protected function buildSideNav() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$nav->addFilter('given/', pht('Tokens Given'));
return $nav;
}
}

View File

@@ -0,0 +1,116 @@
<?php
final class PhabricatorTokenGiveController extends PhabricatorTokenController {
private $phid;
public function willProcessRequest(array $data) {
$this->phid = $data['phid'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$handle = PhabricatorObjectHandleData::loadOneHandle($this->phid, $user);
if (!$handle->isComplete()) {
return new Aphront404Response();
}
$current = id(new PhabricatorTokenGivenQuery())
->setViewer($user)
->withAuthorPHIDs(array($user->getPHID()))
->withObjectPHIDs(array($handle->getPHID()))
->execute();
if ($current) {
$is_give = false;
$title = pht('Rescind Token');
} else {
$is_give = true;
$title = pht('Give Token');
}
$done_uri = $handle->getURI();
if ($request->isDialogFormPost()) {
if ($is_give) {
$token_phid = $request->getStr('tokenPHID');
$editor = id(new PhabricatorTokenGivenEditor())
->setActor($user)
->addToken($handle->getPHID(), $token_phid);
} else {
$editor = id(new PhabricatorTokenGivenEditor())
->setActor($user)
->deleteToken($handle->getPHID());
}
return id(new AphrontReloadResponse())->setURI($done_uri);
}
if ($is_give) {
$dialog = $this->buildGiveTokenDialog();
} else {
$dialog = $this->buildRescindTokenDialog(head($current));
}
$dialog->setUser($user);
$dialog->addCancelButton($done_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function buildGiveTokenDialog() {
$user = $this->getRequest()->getUser();
$tokens = id(new PhabricatorTokenQuery())
->setViewer($user)
->execute();
$buttons = array();
$ii = 0;
foreach ($tokens as $token) {
$buttons[] = javelin_tag(
'button',
array(
'class' => 'token-button',
'name' => 'tokenPHID',
'value' => $token->getPHID(),
'type' => 'submit',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $token->getName(),
)
),
$token->renderIcon());
if ((++$ii % 4) == 0) {
$buttons[] = phutil_tag('br');
}
}
$buttons = phutil_tag(
'div',
array(
'class' => 'token-grid',
),
$buttons);
$dialog = new AphrontDialogView();
$dialog->setTitle(pht('Give Token'));
$dialog->appendChild($buttons);
return $dialog;
}
private function buildRescindTokenDialog(PhabricatorTokenGiven $token_given) {
$dialog = new AphrontDialogView();
$dialog->setTitle(pht('Rescind Token'));
$dialog->appendChild(
pht('Really rescind this lovely token?'));
$dialog->addSubmitButton(pht('Rescind Token'));
return $dialog;
}
}

View File

@@ -0,0 +1,79 @@
<?php
final class PhabricatorTokenGivenController extends PhabricatorTokenController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$pager = id(new AphrontCursorPagerView())
->setURI(new PhutilURI($this->getApplicationURI('/given/')));
$tokens_given = id(new PhabricatorTokenGivenQuery())
->setViewer($user)
->setLimit(100)
->executeWithCursorPager($pager);
$handles = array();
if ($tokens_given) {
$object_phids = mpull($tokens_given, 'getObjectPHID');
$user_phids = mpull($tokens_given, 'getAuthorPHID');
$handle_phids = array_merge($object_phids, $user_phids);
$handles = id(new PhabricatorObjectHandleData($handle_phids))
->setViewer($user)
->loadHandles();
}
$tokens = array();
if ($tokens_given) {
$token_phids = mpull($tokens_given, 'getTokenPHID');
$tokens = id(new PhabricatorTokenQuery())
->setViewer($user)
->withPHIDs($token_phids)
->execute();
$tokens = mpull($tokens, null, 'getPHID');
}
$list = new PhabricatorObjectItemListView();
foreach ($tokens_given as $token_given) {
$handle = $handles[$token_given->getObjectPHID()];
$token = idx($tokens, $token_given->getTokenPHID());
$item = id(new PhabricatorObjectItemView());
$item->setHeader($handle->getFullName());
$item->setHref($handle->getURI());
$item->addAttribute($token->renderIcon());
$item->addAttribute(
pht(
'Given by %s on %s',
$handles[$token_given->getAuthorPHID()]->renderLink(),
phabricator_date($token_given->getDateCreated(), $user)));
$list->addItem($item);
}
$title = pht('Tokens Given');
$nav = $this->buildSideNav();
$nav->setCrumbs(
$this->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)));
$nav->selectFilter('given/');
$nav->appendChild($list);
$nav->appendChild($pager);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
));
}
}

View File

@@ -0,0 +1,91 @@
<?php
final class PhabricatorTokenGivenEditor
extends PhabricatorEditor {
public function addToken($object_phid, $token_phid) {
$token = $this->validateToken($token_phid);
$handle = $this->validateObject($object_phid);
$actor = $this->requireActor();
$token_given = id(new PhabricatorTokenGiven())
->setAuthorPHID($actor->getPHID())
->setObjectPHID($handle->getPHID())
->setTokenPHID($token->getPHID());
$token_given->openTransaction();
$this->executeDeleteToken($handle);
$token_given->save();
queryfx(
$token_given->establishConnection('w'),
'INSERT INTO %T (objectPHID, tokenCount) VALUES (%s, 1)
ON DUPLICATE KEY UPDATE tokenCount = tokenCount + 1',
id(new PhabricatorTokenCount())->getTableName(),
$handle->getPHID());
$token_given->saveTransaction();
return $token_given;
}
public function deleteToken($object_phid) {
$handle = $this->validateObject($object_phid);
return $this->executeDeleteToken($handle);
}
private function executeDeleteToken(PhabricatorObjectHandle $handle) {
$actor = $this->requireActor();
$token_given = id(new PhabricatorTokenGiven())->loadOneWhere(
'authorPHID = %s AND objectPHID = %s',
$actor->getPHID(),
$handle->getPHID());
if (!$token_given) {
return;
}
$token_given->openTransaction();
$token_given->delete();
queryfx(
$token_given->establishConnection('w'),
'INSERT INTO %T (objectPHID, tokenCount) VALUES (%s, 0)
ON DUPLICATE KEY UPDATE tokenCount = tokenCount - 1',
id(new PhabricatorTokenCount())->getTableName(),
$handle->getPHID());
$token_given->saveTransaction();
}
private function validateToken($token_phid) {
$tokens = id(new PhabricatorTokenQuery())
->setViewer($this->requireActor())
->withPHIDs(array($token_phid))
->execute();
if (empty($tokens)) {
throw new Exception("No such token!");
}
return head($tokens);
}
private function validateObject($object_phid) {
$handle = PhabricatorObjectHandleData::loadOneHandle(
$object_phid,
$this->requireActor());
if (!$handle->isComplete()) {
throw new Exception("No such object!");
}
return $handle;
}
}

View File

@@ -0,0 +1,121 @@
<?php
final class PhabricatorTokenUIEventListener
extends PhutilEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
$this->listen(PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionEvent($event);
break;
case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES:
$this->handlePropertyEvent($event);
break;
}
}
private function handleActionEvent($event) {
$user = $event->getUser();
$object = $event->getValue('object');
if (!$object || !$object->getPHID()) {
// No object, or the object has no PHID yet..
return;
}
if (!($object instanceof PhabricatorTokenReceiverInterface)) {
// This object isn't a token receiver.
return;
}
$current = id(new PhabricatorTokenGivenQuery())
->setViewer($user)
->withAuthorPHIDs(array($user->getPHID()))
->withObjectPHIDs(array($object->getPHID()))
->execute();
if (!$current) {
$token_action = id(new PhabricatorActionView())
->setUser($user)
->setWorkflow(true)
->setHref('/token/give/'.$object->getPHID().'/')
->setName(pht('Give Token'))
->setIcon('like');
} else {
$token_action = id(new PhabricatorActionView())
->setUser($user)
->setWorkflow(true)
->setHref('/token/give/'.$object->getPHID().'/')
->setName(pht('Rescind Token'))
->setIcon('dislike');
}
$actions = $event->getValue('actions');
$actions[] = $token_action;
$event->setValue('actions', $actions);
}
private function handlePropertyEvent($event) {
$user = $event->getUser();
$object = $event->getValue('object');
if (!$object || !$object->getPHID()) {
// No object, or the object has no PHID yet..
return;
}
if (!($object instanceof PhabricatorTokenReceiverInterface)) {
// This object isn't a token receiver.
return;
}
$limit = 1;
$tokens_given = id(new PhabricatorTokenGivenQuery())
->setViewer($user)
->withObjectPHIDs(array($object->getPHID()))
->execute();
if (!$tokens_given) {
return;
}
$tokens = id(new PhabricatorTokenQuery())
->setViewer($user)
->withPHIDs(mpull($tokens_given, 'getTokenPHID'))
->execute();
$tokens = mpull($tokens, null, 'getPHID');
$author_phids = mpull($tokens_given, 'getAuthorPHID');
$handles = id(new PhabricatorObjectHandleData($author_phids))
->loadHandles();
$list = array();
foreach ($tokens_given as $token_given) {
if (!idx($tokens, $token_given->getTokenPHID())) {
continue;
}
$token = $tokens[$token_given->getTokenPHID()];
$list[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $handles[$token_given->getAuthorPHID()]->getName(),
),
),
$token->renderIcon());
}
$view = $event->getValue('view');
$view->addProperty(pht('Tokens'), $list);
}
}

View File

@@ -0,0 +1,5 @@
<?php
interface PhabricatorTokenReceiverInterface {
}

View File

@@ -0,0 +1,89 @@
<?php
final class PhabricatorTokenGivenQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $authorPHIDs;
private $objectPHIDs;
private $tokenPHIDs;
public function withTokenPHIDs(array $token_phids) {
$this->tokenPHIDs = $token_phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function loadPage() {
$table = new PhabricatorTokenGiven();
$conn_r = $table->establishConnection('r');
$rows = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($rows);
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
if ($this->authorPHIDs) {
$where[] = qsprintf(
$conn_r,
'authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->objectPHIDs) {
$where[] = qsprintf(
$conn_r,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->tokenPHIDs) {
$where[] = qsprintf(
$conn_r,
'tokenPHID IN (%Ls)',
$this->tokenPHIDs);
}
return $this->formatWhereClause($where);
}
public function willFilterPage(array $results) {
$object_phids = array_filter(mpull($results, 'getObjectPHID'));
if (!$object_phids) {
return array();
}
$objects = id(new PhabricatorObjectHandleData($object_phids))
->setViewer($this->getViewer())
->loadObjects();
foreach ($results as $key => $result) {
$phid = $result->getObjectPHID();
if (empty($objects[$phid])) {
unset($results[$key]);
} else {
$result->attachObject($objects[$phid]);
}
}
return $results;
}
}

View File

@@ -0,0 +1,62 @@
<?php
final class PhabricatorTokenQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $phids;
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function loadPage() {
$tokens = $this->getBuiltinTokens();
if ($this->phids) {
$map = array_fill_keys($this->phids, true);
foreach ($tokens as $key => $token) {
if (empty($map[$token->getPHID()])) {
unset($tokens[$key]);
}
}
}
return $tokens;
}
private function getBuiltinTokens() {
$specs = array(
array('like-1', pht('Like')),
array('like-2', pht('Dislike')),
array('heart-1', pht('Love')),
array('heart-2', pht('Heartbreak')),
array('medal-1', pht('Orange Medal')),
array('medal-2', pht('Grey Medal')),
array('medal-3', pht('Yellow Medal')),
array('medal-4', pht('Manufacturing Defect?')),
array('coin-1', pht('Haypence')),
array('coin-2', pht('Piece of Eight')),
array('coin-3', pht('Doubloon')),
array('coin-4', pht('Mountain of Wealth')),
array('misc-1', pht('Pterodactyl')),
array('misc-2', pht('Evil Spooky Haunted Tree')),
array('misc-3', pht('Baby Tequila')),
array('misc-4', pht('The World Burns')),
);
$tokens = array();
foreach ($specs as $id => $spec) {
list($image, $name) = $spec;
$token = id(new PhabricatorToken())
->setID($id)
->setName($name)
->setPHID('PHID-TOKN-'.$image);
$tokens[] = $token;
}
return $tokens;
}
}

View File

@@ -0,0 +1,46 @@
<?php
final class PhabricatorToken extends PhabricatorTokenDAO
implements PhabricatorPolicyInterface {
protected $phid;
protected $name;
protected $filePHID;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_USER;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function renderIcon() {
// TODO: Maybe move to a View class?
require_celerity_resource('sprite-tokens-css');
require_celerity_resource('tokens-css');
$sprite = substr($this->getPHID(), 10);
return phutil_tag(
'div',
array(
'class' => 'sprite-tokens token-icon token-'.$sprite,
),
'');
}
}

View File

@@ -0,0 +1,15 @@
<?php
final class PhabricatorTokenCount extends PhabricatorTokenDAO {
protected $objectPHID;
protected $tokenCount;
public function getConfiguration() {
return array(
self::CONFIG_IDS => self::IDS_MANUAL,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}

View File

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

View File

@@ -0,0 +1,54 @@
<?php
final class PhabricatorTokenGiven extends PhabricatorTokenDAO
implements PhabricatorPolicyInterface {
protected $authorPHID;
protected $objectPHID;
protected $tokenPHID;
private $object;
public function attachObject(PhabricatorTokenReceiverInterface $object) {
$this->object = $object;
return $this;
}
public function getObject() {
if ($this->object === null) {
throw new Exception("Call attachObject() before getObject()!");
}
return $this->object;
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getObject()->getPolicy($capability);
default:
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getObject()->hasAutomaticCapability(
$capability,
$user);
default:
if ($user->getPHID() == $this->authorPHID) {
return true;
}
return false;
}
}
}

View File

@@ -176,6 +176,33 @@ final class CeleritySpriteGenerator {
return $sheet;
}
public function buildTokenSheet() {
$tokens = $this->getDirectoryList('tokens_1x');
$template = id(new PhutilSprite())
->setSourceSize(16, 16);
$sprites = array();
foreach ($tokens as $token) {
$path = $this->getPath('tokens_1x/'.$token.'.png');
$sprite = id(clone $template)
->setName('token-'.$token)
->setTargetCSS('.token-'.$token)
->setSourceFile($path, 1);
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('tokens', false);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildConpherenceSheet() {
$icons = $this->getDirectoryList('conpher_1x');
$scales = array(
@@ -186,7 +213,7 @@ final class CeleritySpriteGenerator {
->setSourceSize(32, 32);
$sprites = array();
foreach ($icons as $icon) {
foreach ($icons as $icon) {
$color = preg_match('/_on/', $icon) ? 'on' : 'off';
$prefix = 'conpher_';

View File

@@ -29,6 +29,7 @@ final class PhabricatorEventType extends PhutilEventType {
const TYPE_UI_WILLRENDEROBJECTS = 'ui.willRenderObjects';
const TYPE_UI_DDIDRENDEROBJECT = 'ui.didRenderObject';
const TYPE_UI_DIDRENDEROBJECTS = 'ui.didRenderObjects';
const TYPE_UI_WILLRENDERPROPERTIES = 'ui.willRenderProperties';
const TYPE_PEOPLE_DIDRENDERMENU = 'people.didRenderMenu';
}

View File

@@ -163,6 +163,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'db',
'name' => 'conpherence',
),
'db.config' => array(
'type' => 'db',
'name' => 'config',
),
'db.token' => array(
'type' => 'db',
'name' => 'token',
),
'0000.legacy.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('0000.legacy.sql'),
@@ -1068,10 +1076,6 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20121220.generalcache.sql'),
),
'db.config' => array(
'type' => 'db',
'name' => 'config',
),
'20121226.config.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20121226.config.sql'),
@@ -1117,6 +1121,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20130214.chatlogchannelid.sql'),
),
'20130214.token.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130214.token.sql'),
),
);
}

View File

@@ -4,11 +4,18 @@ final class PhabricatorPropertyListView extends AphrontView {
private $parts = array();
private $hasKeyboardShortcuts;
private $object;
private $invokedWillRenderEvent;
protected function canAppendChild() {
return false;
}
public function setObject($object) {
$this->object = $object;
return $this;
}
public function setHasKeyboardShortcuts($has_keyboard_shortcuts) {
$this->hasKeyboardShortcuts = $has_keyboard_shortcuts;
return $this;
@@ -52,7 +59,25 @@ final class PhabricatorPropertyListView extends AphrontView {
return $this;
}
public function invokeWillRenderEvent() {
if ($this->object && $this->getUser() && !$this->invokedWillRenderEvent) {
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES,
array(
'object' => $this->object,
'view' => $this,
));
$event->setUser($this->getUser());
PhutilEventEngine::dispatchEvent($event);
}
$this->invokedWillRenderEvent = true;
}
public function render() {
$this->invokeWillRenderEvent();
require_celerity_resource('phabricator-property-list-view-css');
$items = array();