diff --git a/resources/sql/autopatches/20150712.badges.1.sql b/resources/sql/autopatches/20150712.badges.1.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150712.badges.1.sql @@ -0,0 +1,52 @@ +CREATE TABLE {$NAMESPACE}_badges.badges_badge ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + flavor VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + description LONGTEXT NOT NULL, + icon VARCHAR(255) NOT NULL, + quality VARCHAR(255) NOT NULL, + status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + UNIQUE KEY `key_phid` (phid) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_badges.badges_transaction ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64), + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + oldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + newValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + contentSource LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + metadata LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_object` (objectPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_badges.edge ( + src VARBINARY(64) NOT NULL, + type INT UNSIGNED NOT NULL, + dst VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + seq INT UNSIGNED NOT NULL, + dataID INT UNSIGNED, + PRIMARY KEY (src, type, dst), + KEY `src` (src, type, dateCreated, seq), + UNIQUE KEY `key_dst` (dst, type, src) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_badges.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -176,6 +176,10 @@ 'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php', 'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php', 'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php', + 'BadgesController' => 'applications/badges/controller/BadgesController.php', + 'BadgesEditController' => 'applications/badges/controller/BadgesEditController.php', + 'BadgesListController' => 'applications/badges/controller/BadgesListController.php', + 'BadgesViewController' => 'applications/badges/controller/BadgesViewController.php', 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', @@ -1600,6 +1604,20 @@ 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', 'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php', 'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php', + 'PhabricatorBadge' => 'applications/badges/storage/PhabricatorBadge.php', + 'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.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', + 'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php', + 'PhabricatorBadgesInterface' => 'applications/badges/interface/PhabricatorBadgesInterface.php', + 'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php', + 'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.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', 'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php', 'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php', 'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php', @@ -3653,6 +3671,10 @@ 'AuditConduitAPIMethod' => 'ConduitAPIMethod', 'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod', 'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability', + 'BadgesController' => 'PhabricatorController', + 'BadgesEditController' => 'BadgesController', + 'BadgesListController' => 'BadgesController', + 'BadgesViewController' => 'BadgesController', 'CalendarTimeUtil' => 'Phobject', 'CalendarTimeUtilTestCase' => 'PhabricatorTestCase', 'CelerityAPI' => 'Phobject', @@ -5286,6 +5308,25 @@ 'PhabricatorAuthValidateController' => 'PhabricatorAuthController', 'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAutoEventListener' => 'PhabricatorEventListener', + 'PhabricatorBadge' => array( + 'PhabricatorBadgesDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorBadgesApplication' => 'PhabricatorApplication', + 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO', + 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorBadgesDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorBadgesInterface' => 'PhabricatorPHIDInterface', + 'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec', + 'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorBarePageUIExample' => 'PhabricatorUIExample', 'PhabricatorBarePageView' => 'AphrontPageView', 'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck', 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,66 @@ + array( + '(?:query/(?P[^/]+)/)?' => 'BadgesListController', + 'create/' => 'BadgesEditController', + 'edit/(?:(?P\d+)/)?' => 'BadgesEditController', + 'view/(?:(?P\d+)/)?' => 'BadgesViewController', + 'members/(?:(?P\d+)/)?(?:query/(?P[^/]+)/)?' + => 'BadgesMemberListController', + ), + ); + } + + 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); + } + } + + $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 AphrontFormSelectControl()) + ->setUser($viewer) + ->setName('icon') + ->setLabel(pht('Icon')) + ->setValue($v_icon) + ->setOptions($badge->getIconNameMap())) + ->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/BadgesListController.php b/src/applications/badges/controller/BadgesListController.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/controller/BadgesListController.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/BadgesViewController.php b/src/applications/badges/controller/BadgesViewController.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/controller/BadgesViewController.php @@ -0,0 +1,152 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $badge = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->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); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $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()); + + $view->addProperty( + pht('Quality'), + $quality); + + $view->addProperty( + pht('Icon'), + $badge->getIcon()); + + $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); + } + + 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}/"))); + + if ($badge->isClosed()) { + $close_name = pht('Activate Badge'); + $close_icon = 'fa-check'; + } else { + $close_name = pht('Archive Badge'); + $close_icon = 'fa-times'; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($close_name) + ->setIcon($close_icon) + ->setDisabled(!$can_edit) + ->setWorkflow(true) + ->setHref($this->getApplicationURI("/close/{$id}/"))); + + return $view; + } + +} diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php @@ -0,0 +1,150 @@ +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->getIcon(); + 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; + } + +} 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,90 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withQualiyies(array $qualities) { + $this->qualities = $qualities; + return $this; + } + + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + 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)); + + return $table->loadAllFromArray($rows); + } + + 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,129 @@ +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; + } + + public function buildSearchForm( + AphrontFormView $form, + PhabricatorSavedQuery $saved) { + + $statuses = $saved->getParameter('statuses', array()); + $statuses = array_fuse($statuses); + + $status_map = PhabricatorBadge::getStatusNameMap(); + $status_control = id(new AphrontFormCheckboxControl()) + ->setLabel(pht('Statuses')); + foreach ($status_map as $status => $name) { + $status_control->addCheckbox( + 'statuses[]', + $status, + $name, + isset($statuses[$status])); + } + } + + 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()); + + $item = id(new PHUIObjectItemView()) + ->setHeader($badge->getName()) + ->setStatusIcon($badge->getIcon().' '.$badge->getQuality()) + ->setHref('/badges/view/'.$badge->getID().'/') + ->addAttribute($quality) + ->addAttribute($badge->getFlavor()); + + if ($badge->isClosed()) { + $item->setDisabled(true); + } + + $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 array( + self::ICON_STAR => pht('Star'), + self::ICON_PERSON => pht('Person'), + self::ICON_BUG => pht('Bug'), + self::ICON_BOOK => pht('Book'), + self::ICON_ROCKET => pht('Rocket'), + self::ICON_HELP => pht('Bouy'), + ); + } + + 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()) + ->setName('') + ->setViewPolicy($view_policy) + ->setEditPolicy($edit_policy) + ->setStatus(self::STATUS_OPEN); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text255', + 'flavor' => 'text255', + 'description' => 'text', + 'icon' => 'text255', + 'quality' => 'text255', + 'status' => 'text32', + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return + PhabricatorPHID::generateNewPHID(PhabricatorBadgesPHIDType::TYPECONST); + } + + public function isClosed() { + return ($this->getStatus() == self::STATUS_CLOSED); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorBadgesEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorBadgesTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + + return $timeline; + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + public function shouldShowSubscribersProperty() { + return false; + } + + public function shouldAllowSubscription($phid) { + return false; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $this->openTransaction(); + $this->delete(); + $this->saveTransaction(); + } + +} diff --git a/src/applications/badges/storage/PhabricatorBadgesDAO.php b/src/applications/badges/storage/PhabricatorBadgesDAO.php new file mode 100644 --- /dev/null +++ b/src/applications/badges/storage/PhabricatorBadgesDAO.php @@ -0,0 +1,9 @@ +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,139 @@ +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; + } + + 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/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/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, ),