diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '1716a671', + 'core.pkg.css' => '0730cb6d', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '9451634c', @@ -142,10 +142,10 @@ 'rsrc/css/phui/phui-info-view.css' => '5b16bac6', 'rsrc/css/phui/phui-list.css' => '125599df', 'rsrc/css/phui/phui-object-box.css' => '3db9f358', - 'rsrc/css/phui/phui-object-item-list-view.css' => 'fc19bfc1', + 'rsrc/css/phui/phui-object-item-list-view.css' => 'a1b990b7', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', - 'rsrc/css/phui/phui-property-list-view.css' => '1baf23eb', + 'rsrc/css/phui/phui-property-list-view.css' => 'aeb09581', 'rsrc/css/phui/phui-remarkup-preview.css' => '867f85b3', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '888cedb8', @@ -797,10 +797,10 @@ 'phui-inline-comment-view-css' => '9fadd6b8', 'phui-list-view-css' => '125599df', 'phui-object-box-css' => '3db9f358', - 'phui-object-item-list-view-css' => 'fc19bfc1', + 'phui-object-item-list-view-css' => 'a1b990b7', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', - 'phui-property-list-view-css' => '1baf23eb', + 'phui-property-list-view-css' => 'aeb09581', 'phui-remarkup-preview-css' => '867f85b3', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '888cedb8', diff --git a/resources/sql/autopatches/20150712.badges.1.sql b/resources/sql/autopatches/20150712.badges.1.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150712.badges.1.sql @@ -0,0 +1,54 @@ +CREATE TABLE {$NAMESPACE}_badges.badges_badge ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + flavor VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + description LONGTEXT NOT NULL, + icon VARCHAR(255) NOT NULL, + quality VARCHAR(255) NOT NULL, + status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + creatorPHID varbinary(64) NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_creator` (creatorPHID, dateModified) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_badges.badges_transaction ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64), + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + oldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + newValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + contentSource LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + metadata LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_object` (objectPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_badges.edge ( + src VARBINARY(64) NOT NULL, + type INT UNSIGNED NOT NULL, + dst VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + seq INT UNSIGNED NOT NULL, + dataID INT UNSIGNED, + PRIMARY KEY (src, type, dst), + KEY `src` (src, type, dateCreated, seq), + UNIQUE KEY `key_dst` (dst, type, src) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_badges.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1600,6 +1600,30 @@ 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', 'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php', 'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php', + 'PhabricatorBadge' => 'applications/badges/storage/PhabricatorBadge.php', + 'PhabricatorBadgeHasRecipientEdgeType' => 'applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php', + 'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.php', + 'PhabricatorBadgesController' => 'applications/badges/controller/PhabricatorBadgesController.php', + 'PhabricatorBadgesCreateCapability' => 'applications/badges/capability/PhabricatorBadgesCreateCapability.php', + 'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php', + 'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php', + 'PhabricatorBadgesDefaultViewCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultViewCapability.php', + 'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php', + 'PhabricatorBadgesEditIconController' => 'applications/badges/controller/PhabricatorBadgesEditIconController.php', + 'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php', + 'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php', + 'PhabricatorBadgesIcon' => 'applications/badges/icon/PhabricatorBadgesIcon.php', + 'PhabricatorBadgesInterface' => 'applications/badges/interface/PhabricatorBadgesInterface.php', + 'PhabricatorBadgesListController' => 'applications/badges/controller/PhabricatorBadgesListController.php', + 'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php', + 'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.php', + 'PhabricatorBadgesRecipientsListView' => 'applications/badges/view/PhabricatorBadgesRecipientsListView.php', + 'PhabricatorBadgesRemoveRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php', + 'PhabricatorBadgesSchemaSpec' => 'applications/badges/storage/PhabricatorBadgesSchemaSpec.php', + 'PhabricatorBadgesSearchEngine' => 'applications/badges/query/PhabricatorBadgesSearchEngine.php', + 'PhabricatorBadgesTransaction' => 'applications/badges/storage/PhabricatorBadgesTransaction.php', + 'PhabricatorBadgesTransactionQuery' => 'applications/badges/query/PhabricatorBadgesTransactionQuery.php', + 'PhabricatorBadgesViewController' => 'applications/badges/controller/PhabricatorBadgesViewController.php', 'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php', 'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php', 'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php', @@ -2533,6 +2557,7 @@ 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', + 'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', 'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php', @@ -5286,6 +5311,36 @@ 'PhabricatorAuthValidateController' => 'PhabricatorAuthController', 'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAutoEventListener' => 'PhabricatorEventListener', + 'PhabricatorBadge' => array( + 'PhabricatorBadgesDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorSubscribableInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorBadgeHasRecipientEdgeType' => 'PhabricatorEdgeType', + 'PhabricatorBadgesApplication' => 'PhabricatorApplication', + 'PhabricatorBadgesController' => 'PhabricatorController', + 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO', + 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorBadgesDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesEditIconController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorBadgesIcon' => 'Phobject', + 'PhabricatorBadgesInterface' => 'PhabricatorPHIDInterface', + 'PhabricatorBadgesListController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorBadgesRecipientsListView' => 'AphrontTagView', + 'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec', + 'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorBadgesViewController' => 'PhabricatorBadgesController', 'PhabricatorBarePageUIExample' => 'PhabricatorUIExample', 'PhabricatorBarePageView' => 'AphrontPageView', 'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck', @@ -6375,6 +6430,7 @@ 'Iterator', ), 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', 'PhabricatorRegistrationProfile' => 'Phobject', diff --git a/src/applications/badges/application/PhabricatorBadgesApplication.php b/src/applications/badges/application/PhabricatorBadgesApplication.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/application/PhabricatorBadgesApplication.php @@ -0,0 +1,77 @@ + array( + '(?:query/(?P[^/]+)/)?' + => 'PhabricatorBadgesListController', + 'create/' + => 'PhabricatorBadgesEditController', + 'edit/(?:(?P\d+)/)?' + => 'PhabricatorBadgesEditController', + 'view/(?:(?P\d+)/)?' + => 'PhabricatorBadgesViewController', + 'icon/(?P[1-9]\d*)/' + => 'PhabricatorBadgesEditIconController', + 'icon/' + => 'PhabricatorBadgesEditIconController', + 'recipients/(?P[1-9]\d*)/' + => 'PhabricatorBadgesEditRecipientsController', + 'recipients/(?P[1-9]\d*)/remove/' + => 'PhabricatorBadgesRemoveRecipientsController', + + ), + ); + } + + protected function getCustomCapabilities() { + return array( + PhabricatorBadgesCreateCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + 'caption' => pht('Default create policy for badges.'), + ), + PhabricatorBadgesDefaultEditCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + 'caption' => pht('Default edit policy for badges.'), + ), + PhabricatorBadgesDefaultViewCapability::CAPABILITY => array( + 'caption' => pht('Default view policy for badges.'), + ), + ); + } + +} diff --git a/src/applications/badges/capability/PhabricatorBadgesCreateCapability.php b/src/applications/badges/capability/PhabricatorBadgesCreateCapability.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/capability/PhabricatorBadgesCreateCapability.php @@ -0,0 +1,16 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + if ($this->id) { + $badge = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$badge) { + return new Aphront404Response(); + } + $is_new = false; + } else { + $badge = PhabricatorBadge::initializeNewBadge($viewer); + $is_new = true; + } + + if ($is_new) { + $title = pht('Create Badge'); + $button_text = pht('Create Badge'); + $cancel_uri = $this->getApplicationURI(); + } else { + $title = pht( + 'Edit %s', + $badge->getName()); + $button_text = pht('Save Changes'); + $cancel_uri = $this->getApplicationURI().'view/'.$this->id.'/'; + } + + $e_name = true; + $v_name = $badge->getName(); + + $e_icon = true; + $v_icon = $badge->getIcon(); + + $v_flav = $badge->getFlavor(); + $v_desc = $badge->getDescription(); + $v_qual = $badge->getQuality(); + $v_stat = $badge->getStatus(); + + $validation_exception = null; + if ($request->isFormPost()) { + $v_name = $request->getStr('name'); + $v_flav = $request->getStr('flavor'); + $v_desc = $request->getStr('description'); + $v_icon = $request->getStr('icon'); + $v_stat = $request->getStr('status'); + $v_qual = $request->getStr('quality'); + + $v_view = $request->getStr('viewPolicy'); + $v_edit = $request->getStr('editPolicy'); + + $type_name = PhabricatorBadgesTransaction::TYPE_NAME; + $type_flav = PhabricatorBadgesTransaction::TYPE_FLAVOR; + $type_desc = PhabricatorBadgesTransaction::TYPE_DESCRIPTION; + $type_icon = PhabricatorBadgesTransaction::TYPE_ICON; + $type_qual = PhabricatorBadgesTransaction::TYPE_QUALITY; + $type_stat = PhabricatorBadgesTransaction::TYPE_STATUS; + + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $xactions = array(); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType($type_flav) + ->setNewValue($v_flav); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType($type_desc) + ->setNewValue($v_desc); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType($type_icon) + ->setNewValue($v_icon); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType($type_qual) + ->setNewValue($v_qual); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType($type_stat) + ->setNewValue($v_stat); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType($type_view) + ->setNewValue($v_view); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType($type_edit) + ->setNewValue($v_edit); + + $editor = id(new PhabricatorBadgesEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($badge, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI('/badges/'); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + + $e_name = $ex->getShortMessage($type_name); + $e_desc = $ex->getShortMessage($type_desc); + + $badge->setViewPolicy($v_view); + $badge->setEditPolicy($v_edit); + } + } + + if ($is_new) { + $icon_uri = $this->getApplicationURI('icon/'); + } else { + $icon_uri = $this->getApplicationURI('icon/'.$badge->getID().'/'); + } + $icon_display = PhabricatorBadgesIcon::renderIconForChooser($v_icon); + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($badge) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('name') + ->setLabel(pht('Name')) + ->setValue($v_name) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('flavor') + ->setLabel(pht('Flavor Text')) + ->setValue($v_flav)) + ->appendChild( + id(new AphrontFormChooseButtonControl()) + ->setLabel(pht('Icon')) + ->setName('icon') + ->setDisplayValue($icon_display) + ->setButtonText(pht('Choose Icon...')) + ->setChooseURI($icon_uri) + ->setValue($v_icon)) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setName('quality') + ->setLabel(pht('Quality')) + ->setValue($v_qual) + ->setOptions($badge->getQualityNameMap())) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Status')) + ->setName('status') + ->setValue($v_stat) + ->setOptions($badge->getStatusNameMap())) + ->appendChild( + id(new PhabricatorRemarkupControl()) + ->setUser($viewer) + ->setName('description') + ->setLabel(pht('Description')) + ->setValue($v_desc)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('viewPolicy') + ->setPolicyObject($badge) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('editPolicy') + ->setPolicyObject($badge) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue($button_text) + ->addCancelButton($cancel_uri)); + + $crumbs = $this->buildApplicationCrumbs(); + if ($is_new) { + $crumbs->addTextCrumb(pht('Create Badge')); + } else { + $crumbs->addTextCrumb( + $badge->getName(), + '/badges/view/'.$badge->getID().'/'); + $crumbs->addTextCrumb(pht('Edit')); + } + + $box = id(new PHUIObjectBoxView()) + ->setValidationException($validation_exception) + ->setHeaderText($title) + ->appendChild($form); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/badges/controller/PhabricatorBadgesEditIconController.php b/src/applications/badges/controller/PhabricatorBadgesEditIconController.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/controller/PhabricatorBadgesEditIconController.php @@ -0,0 +1,107 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + if ($this->id) { + $badge = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$badge) { + return new Aphront404Response(); + } + $cancel_uri = + $this->getApplicationURI('view/'.$badge->getID().'/'); + $badge_icon = $badge->getIcon(); + } else { + $this->requireApplicationCapability( + PhabricatorBadgesCreateCapability::CAPABILITY); + + $cancel_uri = '/badges/'; + $badge_icon = $request->getStr('value'); + } + + require_celerity_resource('project-icon-css'); + Javelin::initBehavior('phabricator-tooltips'); + + $badge_icons = PhabricatorBadgesIcon::getIconMap(); + + if ($request->isFormPost()) { + $v_icon = $request->getStr('icon'); + + return id(new AphrontAjaxResponse())->setContent( + array( + 'value' => $v_icon, + 'display' => PhabricatorBadgesIcon::renderIconForChooser($v_icon), + )); + } + + $ii = 0; + $buttons = array(); + foreach ($badge_icons as $icon => $label) { + $view = id(new PHUIIconView()) + ->setIconFont($icon); + + $aural = javelin_tag( + 'span', + array( + 'aural' => true, + ), + pht('Choose "%s" Icon', $label)); + + if ($icon == $badge_icon) { + $class_extra = ' selected'; + } else { + $class_extra = null; + } + + $buttons[] = javelin_tag( + 'button', + array( + 'class' => 'icon-button'.$class_extra, + 'name' => 'icon', + 'value' => $icon, + 'type' => 'submit', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $label, + ), + ), + array( + $aural, + $view, + )); + if ((++$ii % 4) == 0) { + $buttons[] = phutil_tag('br'); + } + } + + $buttons = phutil_tag( + 'div', + array( + 'class' => 'icon-grid', + ), + $buttons); + + return $this->newDialog() + ->setTitle(pht('Choose Badge Icon')) + ->appendChild($buttons) + ->addCancelButton($cancel_uri); + } +} diff --git a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php @@ -0,0 +1,126 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + $id = $request->getURIData('id'); + + $badge = id(new PhabricatorBadgesQuery()) + ->setViewer($user) + ->withIDs(array($this->id)) + ->needRecipients(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->executeOne(); + if (!$badge) { + return new Aphront404Response(); + } + + $recipient_phids = $badge->getRecipientPHIDs(); + + if ($request->isFormPost()) { + $recipient_spec = array(); + + $remove = $request->getStr('remove'); + if ($remove) { + $recipient_spec['-'] = array_fuse(array($remove)); + } + + $add_recipients = $request->getArr('phids'); + if ($add_recipients) { + $recipient_spec['+'] = array_fuse($add_recipients); + } + + $type_recipient = PhabricatorBadgeHasRecipientEdgeType::EDGECONST; + + $xactions = array(); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $type_recipient) + ->setNewValue($recipient_spec); + + $editor = id(new PhabricatorBadgesEditor($badge)) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($badge, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI($request->getRequestURI()); + } + + $recipient_phids = array_reverse($recipient_phids); + $handles = $this->loadViewerHandles($recipient_phids); + + $state = array(); + foreach ($handles as $handle) { + $state[] = array( + 'phid' => $handle->getPHID(), + 'name' => $handle->getFullName(), + ); + } + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $user, + $badge, + PhabricatorPolicyCapability::CAN_EDIT); + + $form_box = null; + $title = pht('Add Recipient'); + if ($can_edit) { + $header_name = pht('Edit Recipients'); + $view_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); + + $form = new AphrontFormView(); + $form + ->setUser($user) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('phids') + ->setLabel(pht('Add Recipients')) + ->setDatasource(new PhabricatorPeopleDatasource())) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($view_uri) + ->setValue(pht('Add Recipients'))); + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setForm($form); + } + + $recipient_list = id(new PhabricatorBadgesRecipientsListView()) + ->setBadge($badge) + ->setHandles($handles) + ->setUser($user); + + $badge_url = $this->getApplicationURI().'view/'.$this->id.'/'; + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($badge->getName(), $badge_url); + $crumbs->addTextCrumb(pht('Recipients')); + + return $this->buildApplicationPage( + array( + $crumbs, + $form_box, + $recipient_list, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/badges/controller/PhabricatorBadgesListController.php b/src/applications/badges/controller/PhabricatorBadgesListController.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/controller/PhabricatorBadgesListController.php @@ -0,0 +1,57 @@ +queryKey = idx($data, 'queryKey'); + } + + public function processRequest() { + $controller = id(new PhabricatorApplicationSearchController()) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new PhabricatorBadgesSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildSideNavView() { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new PhabricatorBadgesSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + PhabricatorBadgesCreateCapability::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Badge')) + ->setHref($this->getApplicationURI('create/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + +} diff --git a/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php @@ -0,0 +1,83 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $badge = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->needRecipients(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$badge) { + return new Aphront404Response(); + } + + $recipient_phids = $badge->getRecipientPHIDs(); + $remove_phid = $request->getStr('phid'); + + if (!in_array($remove_phid, $recipient_phids)) { + return new Aphront404Response(); + } + + $recipients_uri = + $this->getApplicationURI('recipients/'.$badge->getID().'/'); + + if ($request->isFormPost()) { + $recipient_spec = array(); + $recipient_spec['-'] = array($remove_phid => $remove_phid); + + $type_recipient = PhabricatorBadgeHasRecipientEdgeType::EDGECONST; + + $xactions = array(); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $type_recipient) + ->setNewValue($recipient_spec); + + $editor = id(new PhabricatorBadgesEditor($badge)) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($badge, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI($recipients_uri); + } + + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($remove_phid)) + ->executeOne(); + + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('Really Revoke Badge?')) + ->appendParagraph( + pht( + 'Really revoke the badge "%s" from %s?', + phutil_tag('strong', array(), $badge->getName()), + phutil_tag('strong', array(), $handle->getName()))) + ->addCancelButton($recipients_uri) + ->addSubmitButton(pht('Revoke Badge')); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php @@ -0,0 +1,163 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $badge = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->needRecipients(true) + ->executeOne(); + if (!$badge) { + return new Aphront404Response(); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($badge->getName()); + $title = $badge->getName(); + + if ($badge->isClosed()) { + $status_icon = 'fa-ban'; + $status_color = 'dark'; + } else { + $status_icon = 'fa-check'; + $status_color = 'bluegrey'; + } + $status_name = idx( + PhabricatorBadge::getStatusNameMap(), + $badge->getStatus()); + + $header = id(new PHUIHeaderView()) + ->setHeader($badge->getName()) + ->setUser($viewer) + ->setPolicyObject($badge) + ->setStatus($status_icon, $status_color, $status_name); + + $properties = $this->buildPropertyListView($badge); + $actions = $this->buildActionListView($badge); + $properties->setActionList($actions); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($properties); + + $timeline = $this->buildTransactionTimeline( + $badge, + new PhabricatorBadgesTransactionQuery()); + $timeline + ->setShouldTerminate(true); + + $recipient_phids = $badge->getRecipientPHIDs(); + $recipient_phids = array_reverse($recipient_phids); + $handles = $this->loadViewerHandles($recipient_phids); + + $recipient_list = id(new PhabricatorBadgesRecipientsListView()) + ->setBadge($badge) + ->setHandles($handles) + ->setUser($viewer); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $recipient_list, + $timeline, + ), + array( + 'title' => $title, + 'pageObjects' => array($badge->getPHID()), + )); + } + + private function buildPropertyListView(PhabricatorBadge $badge) { + $viewer = $this->getRequest()->getUser(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($badge); + + $quality = idx($badge->getQualityNameMap(), $badge->getQuality()); + $icon = idx($badge->getIconNameMap(), $badge->getIcon()); + + $view->addProperty( + pht('Quality'), + $quality); + + $view->addProperty( + pht('Icon'), + $icon); + + $view->addProperty( + pht('Flavor'), + $badge->getFlavor()); + + $view->invokeWillRenderEvent(); + + $description = $badge->getDescription(); + if (strlen($description)) { + $description = PhabricatorMarkupEngine::renderOneObject( + id(new PhabricatorMarkupOneOff())->setContent($description), + 'default', + $viewer); + + $view->addSectionHeader(pht('Description')); + $view->addTextContent($description); + } + + $badge = id(new PHUIBadgeView()) + ->setIcon($badge->getIcon()) + ->setHeader($badge->getName()) + ->setSubhead($badge->getFlavor()) + ->setQuality($badge->getQuality()); + + $view->addTextContent($badge); + + return $view; + } + + private function buildActionListView(PhabricatorBadge $badge) { + $viewer = $this->getRequest()->getUser(); + $id = $badge->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $badge, + PhabricatorPolicyCapability::CAN_EDIT); + + $view = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($badge); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Badge')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($this->getApplicationURI("/edit/{$id}/"))); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName('Manage Recipients') + ->setIcon('fa-users') + ->setDisabled(!$can_edit) + ->setHref($this->getApplicationURI("/recipients/{$id}/"))); + + return $view; + } + +} diff --git a/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php b/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php @@ -0,0 +1,103 @@ +getTransactionType()) { + case PhabricatorBadgesTransaction::TYPE_NAME: + return $object->getName(); + case PhabricatorBadgesTransaction::TYPE_FLAVOR: + return $object->getFlavor(); + case PhabricatorBadgesTransaction::TYPE_DESCRIPTION: + return $object->getDescription(); + case PhabricatorBadgesTransaction::TYPE_ICON: + return $object->getIcon(); + case PhabricatorBadgesTransaction::TYPE_QUALITY: + return $object->getQuality(); + case PhabricatorBadgesTransaction::TYPE_STATUS: + return $object->getStatus(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorBadgesTransaction::TYPE_NAME: + case PhabricatorBadgesTransaction::TYPE_FLAVOR: + case PhabricatorBadgesTransaction::TYPE_DESCRIPTION: + case PhabricatorBadgesTransaction::TYPE_ICON: + case PhabricatorBadgesTransaction::TYPE_STATUS: + case PhabricatorBadgesTransaction::TYPE_QUALITY: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + $type = $xaction->getTransactionType(); + switch ($type) { + case PhabricatorBadgesTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + case PhabricatorBadgesTransaction::TYPE_FLAVOR: + $object->setFlavor($xaction->getNewValue()); + return; + case PhabricatorBadgesTransaction::TYPE_DESCRIPTION: + $object->setDescription($xaction->getNewValue()); + return; + case PhabricatorBadgesTransaction::TYPE_ICON: + $object->setIcon($xaction->getNewValue()); + return; + case PhabricatorBadgesTransaction::TYPE_QUALITY: + $object->setQuality($xaction->getNewValue()); + return; + case PhabricatorBadgesTransaction::TYPE_STATUS: + $object->setStatus($xaction->getNewValue()); + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + $type = $xaction->getTransactionType(); + switch ($type) { + case PhabricatorBadgesTransaction::TYPE_NAME: + case PhabricatorBadgesTransaction::TYPE_FLAVOR: + case PhabricatorBadgesTransaction::TYPE_DESCRIPTION: + case PhabricatorBadgesTransaction::TYPE_ICON: + case PhabricatorBadgesTransaction::TYPE_STATUS: + case PhabricatorBadgesTransaction::TYPE_QUALITY: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case PhabricatorBadgesTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Badge name is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + +} diff --git a/src/applications/badges/icon/PhabricatorBadgesIcon.php b/src/applications/badges/icon/PhabricatorBadgesIcon.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/icon/PhabricatorBadgesIcon.php @@ -0,0 +1,53 @@ + pht('Superstar'), + 'fa-user' => pht('Average Person'), + 'fa-bug' => pht('Ladybug'), + 'fa-users' => pht('Triplets'), + + 'fa-book' => pht('Nominomicon'), + 'fa-rocket' => pht('Escape Route'), + 'fa-life-ring' => pht('Foam Circle'), + 'fa-birthday-cake' => pht('Cake Day'), + + 'fa-camera-retro' => pht('Leica Enthusiast'), + 'fa-beer' => pht('Liquid Lunch'), + 'fa-gift' => pht('Free Stuff'), + 'fa-eye' => pht('Eye See You'), + + 'fa-heart' => pht('Love is Love'), + 'fa-trophy' => pht('Winner at Things'), + 'fa-umbrella' => pht('Rain Defender'), + 'fa-graduation-cap' => pht('In Debt'), + + ); + } + + public static function getLabel($key) { + $map = self::getIconMap(); + return $map[$key]; + } + + public static function getAPIName($key) { + return substr($key, 3); + } + + public static function renderIconForChooser($icon) { + $badge_icons = self::getIconMap(); + + return phutil_tag( + 'span', + array(), + array( + id(new PHUIIconView())->setIconFont($icon), + ' ', + idx($badge_icons, $icon, pht('Unknown Icon')), + )); + } + +} diff --git a/src/applications/badges/interface/PhabricatorBadgesInterface.php b/src/applications/badges/interface/PhabricatorBadgesInterface.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/interface/PhabricatorBadgesInterface.php @@ -0,0 +1,3 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $badge = $objects[$phid]; + + $id = $badge->getID(); + $name = $badge->getName(); + + if ($badge->isClosed()) { + $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); + } + + $handle->setName($name); + $handle->setFullName("{$name}"); + $handle->setURI("/badges/view/{$id}/"); + } + } + + public function loadNamedObjects( + PhabricatorObjectQuery $query, + array $names) { + + $id_map = array(); + foreach ($names as $name) { + $id = (int)substr($name, 1); + $id_map[$id][] = $name; + } + + $objects = id(new PhabricatorBadgesQuery()) + ->setViewer($query->getViewer()) + ->withIDs(array_keys($id_map)) + ->execute(); + + $results = array(); + foreach ($objects as $id => $object) { + foreach (idx($id_map, $id, array()) as $name) { + $results[$name] = $object; + } + } + + return $results; + } + +} diff --git a/src/applications/badges/query/PhabricatorBadgesQuery.php b/src/applications/badges/query/PhabricatorBadgesQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/query/PhabricatorBadgesQuery.php @@ -0,0 +1,124 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withQualities(array $qualities) { + $this->qualities = $qualities; + return $this; + } + + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + + public function withRecipientPHIDs(array $recipient_phids) { + $this->recipientPHIDs = $recipient_phids; + return $this; + } + + public function needRecipients($need_recipients) { + $this->needRecipients = $need_recipients; + return $this; + } + + protected function loadPage() { + $table = new PhabricatorBadge(); + $conn_r = $table->establishConnection('r'); + + $rows = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + $badges = $table->loadAllFromArray($rows); + $viewer_phid = $this->getViewer()->getPHID(); + $badge_phids = mpull($badges, 'getPHID'); + + $recipient_type = PhabricatorBadgeHasRecipientEdgeType::EDGECONST; + $need_edge_types = array(); + $need_edge_types[] = $recipient_type; + + if ($badge_phids) { + $edges = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs($badge_phids) + ->withEdgeTypes($need_edge_types) + ->execute(); + + foreach ($badges as $badge) { + $phid = $badge->getPHID(); + $badge->attachRecipientPHIDs( + array_keys($edges[$phid][$recipient_type])); + } + } + + return $badges; + } + + protected function didFilterPage(array $badges) { + return $badges; + } + + protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + $where[] = $this->buildPagingClause($conn_r); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->qualities !== null) { + $where[] = qsprintf( + $conn_r, + 'quality IN (%Ls)', + $this->qualities); + } + + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn_r, + 'status IN (%Ls)', + $this->statuses); + } + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorBadgesApplication'; + } + +} diff --git a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php @@ -0,0 +1,140 @@ +setParameter( + 'statuses', + $this->readListFromRequest($request, 'statuses')); + + return $saved; + } + + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $query = new PhabricatorBadgesQuery(); + + $statuses = $saved->getParameter('statuses'); + if ($statuses) { + $query->withStatuses($statuses); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') + ->setLabel(pht('Status')) + ->setOptions( + id(new PhabricatorBadge()) + ->getStatusNameMap()), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } + + return $query; + } + + protected function getURI($path) { + return '/badges/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array(); + + $names['open'] = pht('Active Badges'); + $names['all'] = pht('All Badges'); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + case 'open': + return $query->setParameter( + 'statuses', + array( + PhabricatorBadge::STATUS_OPEN, + )); + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function getRequiredHandlePHIDsForResultList( + array $badges, + PhabricatorSavedQuery $query) { + + $phids = array(); + + return $phids; + } + + protected function renderResultList( + array $badges, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($badges, 'PhabricatorBadge'); + + $viewer = $this->requireViewer(); + + $list = id(new PHUIObjectItemListView()); + foreach ($badges as $badge) { + + $quality = idx($badge->getQualityNameMap(), $badge->getQuality()); + $mini_badge = id(new PHUIBadgeMiniView()) + ->setHeader($badge->getName()) + ->setIcon($badge->getIcon()) + ->setQuality($badge->getQuality()); + + $item = id(new PHUIObjectItemView()) + ->setHeader($badge->getName()) + ->setBadge($mini_badge) + ->setHref('/badges/view/'.$badge->getID().'/') + ->addAttribute($quality) + ->addAttribute($badge->getFlavor()); + + if ($badge->isClosed()) { + $item->setDisabled(true); + $item->addIcon('fa-ban', pht('Archived')); + } + + $list->addItem($item); + } + + $result = new PhabricatorApplicationSearchResultView(); + $result->setObjectList($list); + $result->setNoDataString(pht('No badges found.')); + + return $result; + + } + +} diff --git a/src/applications/badges/query/PhabricatorBadgesTransactionQuery.php b/src/applications/badges/query/PhabricatorBadgesTransactionQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/query/PhabricatorBadgesTransactionQuery.php @@ -0,0 +1,10 @@ + pht('Active'), + self::STATUS_CLOSED => pht('Archived'), + ); + } + + public static function getQualityNameMap() { + return array( + self::POOR => pht('Poor'), + self::COMMON => pht('Common'), + self::UNCOMMON => pht('Uncommon'), + self::RARE => pht('Rare'), + self::EPIC => pht('Epic'), + self::LEGENDARY => pht('Legendary'), + self::HEIRLOOM => pht('Heirloom'), + ); + } + + public static function getIconNameMap() { + return PhabricatorBadgesIcon::getIconMap(); + } + + public static function initializeNewBadge(PhabricatorUser $actor) { + $app = id(new PhabricatorApplicationQuery()) + ->setViewer($actor) + ->withClasses(array('PhabricatorBadgesApplication')) + ->executeOne(); + + $view_policy = + $app->getPolicy(PhabricatorBadgesDefaultViewCapability::CAPABILITY); + + $edit_policy = + $app->getPolicy(PhabricatorBadgesDefaultEditCapability::CAPABILITY); + + return id(new PhabricatorBadge()) + ->setIcon(self::DEFAULT_ICON) + ->setQuality(self::DEFAULT_QUALITY) + ->setCreatorPHID($actor->getPHID()) + ->setViewPolicy($view_policy) + ->setEditPolicy($edit_policy) + ->setStatus(self::STATUS_OPEN); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text255', + 'flavor' => 'text255', + 'description' => 'text', + 'icon' => 'text255', + 'quality' => 'text255', + 'status' => 'text32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_creator' => array( + 'columns' => array('creatorPHID', 'dateModified'), + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return + PhabricatorPHID::generateNewPHID(PhabricatorBadgesPHIDType::TYPECONST); + } + + public function isClosed() { + return ($this->getStatus() == self::STATUS_CLOSED); + } + + public function attachRecipientPHIDs(array $phids) { + $this->recipientPHIDs = $phids; + return $this; + } + + public function getRecipientPHIDs() { + return $this->assertAttached($this->recipientPHIDs); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorBadgesEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorBadgesTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + + return $timeline; + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return ($this->creatorPHID == $phid); + } + + public function shouldShowSubscribersProperty() { + return true; + } + + public function shouldAllowSubscription($phid) { + return true; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $this->openTransaction(); + $this->delete(); + $this->saveTransaction(); + } + +} diff --git a/src/applications/badges/storage/PhabricatorBadgesDAO.php b/src/applications/badges/storage/PhabricatorBadgesDAO.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/storage/PhabricatorBadgesDAO.php @@ -0,0 +1,9 @@ +buildEdgeSchemata(new PhabricatorBadge()); + } + +} diff --git a/src/applications/badges/storage/PhabricatorBadgesTransaction.php b/src/applications/badges/storage/PhabricatorBadgesTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/storage/PhabricatorBadgesTransaction.php @@ -0,0 +1,195 @@ +getAuthorPHID(); + $object_phid = $this->getObjectPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $type = $this->getTransactionType(); + switch ($type) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this badge.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this badge from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + break; + case self::TYPE_FLAVOR: + if ($old === null) { + return pht( + '%s set the flavor text for this badge.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s updated the flavor text for this badge.', + $this->renderHandleLink($author_phid)); + } + break; + case self::TYPE_DESCRIPTION: + if ($old === null) { + return pht( + '%s set the description for this badge.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s updated the description for this badge.', + $this->renderHandleLink($author_phid)); + } + break; + case self::TYPE_ICON: + if ($old === null) { + return pht( + '%s set the icon for this badge as "%s".', + $this->renderHandleLink($author_phid), + $new); + } else { + $icon_map = PhabricatorBadge::getIconNameMap(); + $icon_new = idx($icon_map, $new, $new); + $icon_old = idx($icon_map, $old, $old); + return pht( + '%s updated the icon for this badge from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $icon_old, + $icon_new); + } + break; + case self::TYPE_QUALITY: + if ($old === null) { + return pht( + '%s set the quality for this badge as "%s".', + $this->renderHandleLink($author_phid), + $new); + } else { + $qual_map = PhabricatorBadge::getQualityNameMap(); + $qual_new = idx($qual_map, $new, $new); + $qual_old = idx($qual_map, $old, $old); + return pht( + '%s updated the quality for this badge from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $qual_old, + $qual_new); + } + break; + } + + return parent::getTitle(); + } + + public function getTitleForFeed() { + $author_phid = $this->getAuthorPHID(); + $object_phid = $this->getObjectPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $type = $this->getTransactionType(); + switch ($type) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + + } else { + return pht( + '%s renamed %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + break; + case self::TYPE_FLAVOR: + return pht( + '%s updated the flavor text for %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + case self::TYPE_ICON: + return pht( + '%s updated the icon for %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + case self::TYPE_QUALITY: + return pht( + '%s updated the quality level for %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + case self::TYPE_DESCRIPTION: + return pht( + '%s updated the description for %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + case self::TYPE_STATUS: + switch ($new) { + case PhabricatorBadge::STATUS_OPEN: + return pht( + '%s activated %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + case PhabricatorBadge::STATUS_CLOSED: + return pht( + '%s archived %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + break; + } + + return parent::getTitleForFeed(); + } + + + public function shouldHide() { + $old = $this->getOldValue(); + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($old === null); + } + return parent::shouldHide(); + } + + public function hasChangeDetails() { + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($this->getOldValue() !== null); + } + + return parent::hasChangeDetails(); + } + + public function renderChangeDetails(PhabricatorUser $viewer) { + return $this->renderTextCorpusChangeDetails( + $viewer, + $this->getOldValue(), + $this->getNewValue()); + } +} diff --git a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php @@ -0,0 +1,61 @@ +badge = $badge; + return $this; + } + + public function setHandles(array $handles) { + $this->handles = $handles; + return $this; + } + + protected function getTagContent() { + + $viewer = $this->user; + + $badge = $this->badge; + $handles = $this->handles; + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $badge, + PhabricatorPolicyCapability::CAN_EDIT); + + $list = id(new PHUIObjectItemListView()) + ->setNoDataString(pht('This badge does not have any recipients.')); + + foreach ($handles as $handle) { + $remove_uri = '/badges/recipients/'. + $badge->getID().'/remove/?phid='.$handle->getPHID(); + + $item = id(new PHUIObjectItemView()) + ->setHeader($handle->getFullName()) + ->setHref($handle->getURI()) + ->setImageURI($handle->getImageURI()); + + if ($can_edit) { + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-times') + ->setName(pht('Remove')) + ->setHref($remove_uri) + ->setWorkflow(true)); + } + + $list->addItem($item); + } + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recipients')) + ->setObjectList($list); + + return $box; + } + +} diff --git a/src/applications/tokens/application/PhabricatorTokensApplication.php b/src/applications/tokens/application/PhabricatorTokensApplication.php --- a/src/applications/tokens/application/PhabricatorTokensApplication.php +++ b/src/applications/tokens/application/PhabricatorTokensApplication.php @@ -11,7 +11,7 @@ } public function getFontIcon() { - return 'fa-trophy'; + return 'fa-heart'; } public function getTitleGlyph() { diff --git a/src/applications/uiexample/examples/PHUIBadgeExample.php b/src/applications/uiexample/examples/PHUIBadgeExample.php --- a/src/applications/uiexample/examples/PHUIBadgeExample.php +++ b/src/applications/uiexample/examples/PHUIBadgeExample.php @@ -89,7 +89,7 @@ $badges2[] = id(new PHUIBadgeView()) ->setIcon('fa-users') ->setHeader(pht('Security Team')) - ->setSubhead(pht('alert(1);')) + ->setSubhead(pht('')) ->setQuality(PHUIBadgeView::EPIC) ->setSource('Projects (automatic)') ->addByline(pht('Dec 31, 1969')) @@ -152,7 +152,7 @@ $flex2->addItems($badges2); $box2 = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Acheivements')) + ->setHeaderText(pht('Achievements')) ->appendChild($flex2); $flex3 = new PHUIBadgeBoxView(); diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -106,6 +106,7 @@ 'db.almanac' => array(), 'db.multimeter' => array(), 'db.spaces' => array(), + 'db.badges' => array(), '0000.legacy.sql' => array( 'legacy' => 0, ), diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -23,6 +23,7 @@ private $fontIcon; private $imageIcon; private $titleText; + private $badge; const AGE_FRESH = 'fresh'; const AGE_STALE = 'stale'; @@ -99,6 +100,11 @@ return $this; } + public function setBadge(PHUIBadgeMiniView $badge) { + $this->badge = $badge; + return $this; + } + public function setTitleText($title_text) { $this->titleText = $title_text; return $this; @@ -588,6 +594,16 @@ $status); } + /* Can't set a Badge and a Status Icon */ + if ($this->badge) { + $column0 = phutil_tag( + 'div', + array( + 'class' => 'phui-object-item-col0 phui-object-item-badge', + ), + $this->badge); + } + $column1 = phutil_tag( 'div', array( diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css --- a/webroot/rsrc/css/phui/phui-object-item-list-view.css +++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css @@ -617,6 +617,17 @@ border: none; } +/* - Badges ---------------------------------------------------------------- */ + +.phui-object-item-col0.phui-object-item-badge { + width: 28px; +} + +.phui-object-item-col0.phui-object-item-badge .phui-icon-view { + left: 0; +} + + /* - Dashboards ------------------------------------------------------------ */ .phui-object-box .phui-object-item-list-view .phui-object-item-frame { diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css --- a/webroot/rsrc/css/phui/phui-property-list-view.css +++ b/webroot/rsrc/css/phui/phui-property-list-view.css @@ -98,6 +98,12 @@ border-width: 1px 0 0; } +.phui-property-list-container + .phui-property-list-text-content { + border-color: {$thinblueborder}; + border-style: solid; + border-width: 1px 0 0; +} + .phui-property-list-section-noninitial .phui-property-list-section-header { border-top: none; }