@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Moderize Countdown

Summary:
[DRAFT] Ref T8895. Makes a reasonable attempt at:

- Project Support
- Timeline / History
- Better Search
- Better ObjectItemLists

Test Plan: Needs better testing (I'm sleepy)

Reviewers: btrahan, epriestley

Reviewed By: epriestley

Subscribers: eadler, epriestley, Korvin

Maniphest Tasks: T8895

Differential Revision: https://secure.phabricator.com/D13660

+630 -130
+36
resources/sql/autopatches/20150719.countdown.1.sql
··· 1 + CREATE TABLE {$NAMESPACE}_countdown.countdown_transaction ( 2 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + objectPHID VARBINARY(64) NOT NULL, 6 + viewPolicy VARBINARY(64) NOT NULL, 7 + editPolicy VARBINARY(64) NOT NULL, 8 + commentPHID VARBINARY(64), 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, 11 + oldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 12 + newValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 13 + contentSource LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 14 + metadata LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (phid), 18 + KEY `key_object` (objectPHID) 19 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; 20 + 21 + CREATE TABLE {$NAMESPACE}_countdown.edge ( 22 + src VARBINARY(64) NOT NULL, 23 + type INT UNSIGNED NOT NULL, 24 + dst VARBINARY(64) NOT NULL, 25 + dateCreated INT UNSIGNED NOT NULL, 26 + seq INT UNSIGNED NOT NULL, 27 + dataID INT UNSIGNED, 28 + PRIMARY KEY (src, type, dst), 29 + KEY `src` (src, type, dateCreated, seq), 30 + UNIQUE KEY `key_dst` (dst, type, src) 31 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; 32 + 33 + CREATE TABLE {$NAMESPACE}_countdown.edgedata ( 34 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 35 + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} 36 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+2
resources/sql/autopatches/20150719.countdown.2.sql
··· 1 + ALTER TABLE {$NAMESPACE}_countdown.countdown 2 + ADD editPolicy VARBINARY(64) NOT NULL;
+2
resources/sql/autopatches/20150719.countdown.3.sql
··· 1 + UPDATE {$NAMESPACE}_countdown.countdown 2 + SET editPolicy = authorPHID WHERE editPolicy = '';
+15
src/__phutil_library_map__.php
··· 1799 1799 'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php', 1800 1800 'PhabricatorCountdownCountdownPHIDType' => 'applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php', 1801 1801 'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php', 1802 + 'PhabricatorCountdownDefaultEditCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php', 1802 1803 'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php', 1803 1804 'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php', 1804 1805 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', 1806 + 'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php', 1805 1807 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 1806 1808 'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php', 1807 1809 'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php', 1810 + 'PhabricatorCountdownReplyHandler' => 'applications/countdown/mail/PhabricatorCountdownReplyHandler.php', 1811 + 'PhabricatorCountdownSchemaSpec' => 'applications/countdown/storage/PhabricatorCountdownSchemaSpec.php', 1808 1812 'PhabricatorCountdownSearchEngine' => 'applications/countdown/query/PhabricatorCountdownSearchEngine.php', 1813 + 'PhabricatorCountdownTransaction' => 'applications/countdown/storage/PhabricatorCountdownTransaction.php', 1814 + 'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php', 1809 1815 'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php', 1810 1816 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', 1811 1817 'PhabricatorCredentialsUsedByObjectEdgeType' => 'applications/passphrase/edge/PhabricatorCredentialsUsedByObjectEdgeType.php', ··· 5548 5554 'PhabricatorCountdownDAO', 5549 5555 'PhabricatorPolicyInterface', 5550 5556 'PhabricatorFlaggableInterface', 5557 + 'PhabricatorSubscribableInterface', 5558 + 'PhabricatorApplicationTransactionInterface', 5551 5559 'PhabricatorSpacesInterface', 5560 + 'PhabricatorProjectInterface', 5552 5561 ), 5553 5562 'PhabricatorCountdownApplication' => 'PhabricatorApplication', 5554 5563 'PhabricatorCountdownController' => 'PhabricatorController', 5555 5564 'PhabricatorCountdownCountdownPHIDType' => 'PhabricatorPHIDType', 5556 5565 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', 5566 + 'PhabricatorCountdownDefaultEditCapability' => 'PhabricatorPolicyCapability', 5557 5567 'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability', 5558 5568 'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController', 5559 5569 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 5570 + 'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor', 5560 5571 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 5561 5572 'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5562 5573 'PhabricatorCountdownRemarkupRule' => 'PhabricatorObjectRemarkupRule', 5574 + 'PhabricatorCountdownReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 5575 + 'PhabricatorCountdownSchemaSpec' => 'PhabricatorConfigSchemaSpec', 5563 5576 'PhabricatorCountdownSearchEngine' => 'PhabricatorApplicationSearchEngine', 5577 + 'PhabricatorCountdownTransaction' => 'PhabricatorApplicationTransaction', 5578 + 'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 5564 5579 'PhabricatorCountdownView' => 'AphrontTagView', 5565 5580 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 5566 5581 'PhabricatorCredentialsUsedByObjectEdgeType' => 'PhabricatorEdgeType',
+6
src/applications/countdown/application/PhabricatorCountdownApplication.php
··· 38 38 39 39 public function getRoutes() { 40 40 return array( 41 + '/C(?P<id>[1-9]\d*)' => 'PhabricatorCountdownViewController', 41 42 '/countdown/' => array( 42 43 '(?:query/(?P<queryKey>[^/]+)/)?' 43 44 => 'PhabricatorCountdownListController', ··· 54 55 'caption' => pht('Default view policy for new countdowns.'), 55 56 'template' => PhabricatorCountdownCountdownPHIDType::TYPECONST, 56 57 'capability' => PhabricatorPolicyCapability::CAN_VIEW, 58 + ), 59 + PhabricatorCountdownDefaultEditCapability::CAPABILITY => array( 60 + 'caption' => pht('Default edit policy for new countdowns.'), 61 + 'template' => PhabricatorCountdownCountdownPHIDType::TYPECONST, 62 + 'capability' => PhabricatorPolicyCapability::CAN_EDIT, 57 63 ), 58 64 ); 59 65 }
+12
src/applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php
··· 1 + <?php 2 + 3 + final class PhabricatorCountdownDefaultEditCapability 4 + extends PhabricatorPolicyCapability { 5 + 6 + const CAPABILITY = 'countdown.default.edit'; 7 + 8 + public function getCapabilityName() { 9 + return pht('Default Edit Policy'); 10 + } 11 + 12 + }
+86 -38
src/applications/countdown/controller/PhabricatorCountdownEditController.php
··· 3 3 final class PhabricatorCountdownEditController 4 4 extends PhabricatorCountdownController { 5 5 6 - private $id; 7 - public function willProcessRequest(array $data) { 8 - $this->id = idx($data, 'id'); 9 - } 10 - 11 - public function processRequest() { 12 - 13 - $request = $this->getRequest(); 14 - $user = $request->getUser(); 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + $id = $request->getURIData('id'); 15 9 16 - if ($this->id) { 10 + if ($id) { 17 11 $page_title = pht('Edit Countdown'); 18 12 $countdown = id(new PhabricatorCountdownQuery()) 19 - ->setViewer($user) 20 - ->withIDs(array($this->id)) 13 + ->setViewer($viewer) 14 + ->withIDs(array($id)) 21 15 ->requireCapabilities( 22 16 array( 23 17 PhabricatorPolicyCapability::CAN_VIEW, ··· 28 22 return new Aphront404Response(); 29 23 } 30 24 $date_value = AphrontFormDateControlValue::newFromEpoch( 31 - $user, 25 + $viewer, 32 26 $countdown->getEpoch()); 27 + $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( 28 + $countdown->getPHID(), 29 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 30 + $v_projects = array_reverse($v_projects); 33 31 } else { 34 32 $page_title = pht('Create Countdown'); 35 - $countdown = PhabricatorCountdown::initializeNewCountdown($user); 36 - $date_value = AphrontFormDateControlValue::newFromEpoch($user, time()); 33 + $countdown = PhabricatorCountdown::initializeNewCountdown($viewer); 34 + $date_value = AphrontFormDateControlValue::newFromEpoch( 35 + $viewer, PhabricatorTime::getNow()); 36 + $v_projects = array(); 37 37 } 38 38 39 39 $errors = array(); ··· 42 42 43 43 $v_text = $countdown->getTitle(); 44 44 $v_space = $countdown->getSpacePHID(); 45 + $v_view = $countdown->getViewPolicy(); 46 + $v_edit = $countdown->getEditPolicy(); 45 47 46 48 if ($request->isFormPost()) { 47 49 $v_text = $request->getStr('title'); ··· 49 51 $date_value = AphrontFormDateControlValue::newFromRequest( 50 52 $request, 51 53 'epoch'); 52 - $view_policy = $request->getStr('viewPolicy'); 54 + $v_view = $request->getStr('viewPolicy'); 55 + $v_edit = $request->getStr('editPolicy'); 56 + $v_projects = $request->getArr('projects'); 53 57 54 - $e_text = null; 55 - if (!strlen($v_text)) { 56 - $e_text = pht('Required'); 57 - $errors[] = pht('You must give the countdown a name.'); 58 - } 59 - if (!$date_value->isValid()) { 60 - $e_epoch = pht('Invalid'); 61 - $errors[] = pht('You must give the countdown a valid end date.'); 62 - } 58 + $type_title = PhabricatorCountdownTransaction::TYPE_TITLE; 59 + $type_epoch = PhabricatorCountdownTransaction::TYPE_EPOCH; 60 + $type_space = PhabricatorTransactions::TYPE_SPACE; 61 + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; 62 + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; 63 + 64 + $xactions = array(); 65 + 66 + $xactions[] = id(new PhabricatorCountdownTransaction()) 67 + ->setTransactionType($type_title) 68 + ->setNewValue($v_text); 63 69 64 - if (!count($errors)) { 65 - $countdown->setTitle($v_text); 66 - $countdown->setEpoch($date_value->getEpoch()); 67 - $countdown->setViewPolicy($view_policy); 68 - $countdown->setSpacePHID($v_space); 69 - $countdown->save(); 70 + $xactions[] = id(new PhabricatorCountdownTransaction()) 71 + ->setTransactionType($type_epoch) 72 + ->setNewValue($date_value); 73 + 74 + $xactions[] = id(new PhabricatorCountdownTransaction()) 75 + ->setTransactionType($type_space) 76 + ->setNewValue($v_space); 77 + 78 + $xactions[] = id(new PhabricatorCountdownTransaction()) 79 + ->setTransactionType($type_view) 80 + ->setNewValue($v_view); 81 + 82 + $xactions[] = id(new PhabricatorCountdownTransaction()) 83 + ->setTransactionType($type_edit) 84 + ->setNewValue($v_edit); 85 + 86 + $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 87 + $xactions[] = id(new PhabricatorCountdownTransaction()) 88 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 89 + ->setMetadataValue('edge:type', $proj_edge_type) 90 + ->setNewValue(array('=' => array_fuse($v_projects))); 91 + 92 + $editor = id(new PhabricatorCountdownEditor()) 93 + ->setActor($viewer) 94 + ->setContentSourceFromRequest($request) 95 + ->setContinueOnNoEffect(true); 96 + 97 + try { 98 + $editor->applyTransactions($countdown, $xactions); 99 + 70 100 return id(new AphrontRedirectResponse()) 71 - ->setURI('/countdown/'.$countdown->getID().'/'); 101 + ->setURI('/'.$countdown->getMonogram()); 102 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 103 + $validation_exception = $ex; 104 + 105 + $e_title = $ex->getShortMessage($type_title); 106 + $e_epoch = $ex->getShortMessage($type_epoch); 72 107 } 108 + 73 109 } 74 110 75 111 $crumbs = $this->buildApplicationCrumbs(); ··· 86 122 } 87 123 88 124 $policies = id(new PhabricatorPolicyQuery()) 89 - ->setViewer($user) 125 + ->setViewer($viewer) 90 126 ->setObject($countdown) 91 127 ->execute(); 92 128 93 129 $form = id(new AphrontFormView()) 94 - ->setUser($user) 130 + ->setUser($viewer) 95 131 ->setAction($request->getRequestURI()->getPath()) 96 132 ->appendChild( 97 133 id(new AphrontFormTextControl()) ··· 99 135 ->setValue($v_text) 100 136 ->setName('title') 101 137 ->setError($e_text)) 102 - ->appendChild( 138 + ->appendControl( 103 139 id(new AphrontFormDateControl()) 104 - ->setUser($user) 105 140 ->setName('epoch') 106 141 ->setLabel(pht('End Date')) 107 142 ->setError($e_epoch) 108 143 ->setValue($date_value)) 109 - ->appendChild( 144 + ->appendControl( 110 145 id(new AphrontFormPolicyControl()) 111 - ->setUser($user) 112 146 ->setName('viewPolicy') 113 147 ->setPolicyObject($countdown) 114 148 ->setPolicies($policies) 115 149 ->setSpacePHID($v_space) 150 + ->setValue($v_view) 116 151 ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) 152 + ->appendControl( 153 + id(new AphrontFormPolicyControl()) 154 + ->setName('editPolicy') 155 + ->setPolicyObject($countdown) 156 + ->setPolicies($policies) 157 + ->setValue($v_edit) 158 + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)) 159 + ->appendControl( 160 + id(new AphrontFormTokenizerControl()) 161 + ->setLabel(pht('Projects')) 162 + ->setName('projects') 163 + ->setValue($v_projects) 164 + ->setDatasource(new PhabricatorProjectDatasource())) 117 165 ->appendChild( 118 166 id(new AphrontFormSubmitControl()) 119 167 ->addCancelButton($cancel_uri)
+27 -15
src/applications/countdown/controller/PhabricatorCountdownViewController.php
··· 3 3 final class PhabricatorCountdownViewController 4 4 extends PhabricatorCountdownController { 5 5 6 - private $id; 7 - 8 6 public function shouldAllowPublic() { 9 7 return true; 10 8 } 11 9 12 - public function willProcessRequest(array $data) { 13 - $this->id = $data['id']; 14 - } 15 - 16 - public function processRequest() { 17 - 18 - $request = $this->getRequest(); 19 - $user = $request->getUser(); 10 + public function handleRequest(AphrontRequest $request) { 11 + $viewer = $request->getViewer(); 12 + $id = $request->getURIData('id'); 20 13 21 14 $countdown = id(new PhabricatorCountdownQuery()) 22 - ->setViewer($user) 23 - ->withIDs(array($this->id)) 15 + ->setViewer($viewer) 16 + ->withIDs(array($id)) 24 17 ->executeOne(); 25 18 if (!$countdown) { 26 19 return new Aphront404Response(); 27 20 } 28 21 29 22 $countdown_view = id(new PhabricatorCountdownView()) 30 - ->setUser($user) 23 + ->setUser($viewer) 31 24 ->setCountdown($countdown) 32 25 ->setHeadless(true); 33 26 ··· 38 31 ->buildApplicationCrumbs() 39 32 ->addTextCrumb("C{$id}"); 40 33 34 + $epoch = $countdown->getEpoch(); 35 + if ($epoch >= PhabricatorTime::getNow()) { 36 + $icon = 'fa-clock-o'; 37 + $color = ''; 38 + $status = pht('Running'); 39 + } else { 40 + $icon = 'fa-check-square-o'; 41 + $color = 'dark'; 42 + $status = pht('Launched'); 43 + } 44 + 41 45 $header = id(new PHUIHeaderView()) 42 46 ->setHeader($title) 43 - ->setUser($user) 44 - ->setPolicyObject($countdown); 47 + ->setUser($viewer) 48 + ->setPolicyObject($countdown) 49 + ->setStatus($icon, $color, $status); 45 50 46 51 $actions = $this->buildActionListView($countdown); 47 52 $properties = $this->buildPropertyListView($countdown, $actions); ··· 50 55 ->setHeader($header) 51 56 ->addPropertyList($properties); 52 57 58 + $timeline = $this->buildTransactionTimeline( 59 + $countdown, 60 + new PhabricatorCountdownTransactionQuery()); 61 + $timeline->setShouldTerminate(true); 62 + 53 63 $content = array( 54 64 $crumbs, 55 65 $object_box, 56 66 $countdown_view, 67 + $timeline, 57 68 ); 58 69 59 70 return $this->buildApplicationPage( ··· 105 116 106 117 $view = id(new PHUIPropertyListView()) 107 118 ->setUser($viewer) 119 + ->setObject($countdown) 108 120 ->setActionList($actions); 109 121 110 122 $view->addProperty(
+193
src/applications/countdown/editor/PhabricatorCountdownEditor.php
··· 1 + <?php 2 + 3 + final class PhabricatorCountdownEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorCountdownApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Countdown'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = PhabricatorCountdownTransaction::TYPE_TITLE; 18 + $types[] = PhabricatorCountdownTransaction::TYPE_EPOCH; 19 + 20 + $types[] = PhabricatorTransactions::TYPE_EDGE; 21 + $types[] = PhabricatorTransactions::TYPE_SPACE; 22 + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 23 + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 24 + 25 + return $types; 26 + } 27 + 28 + protected function getCustomTransactionOldValue( 29 + PhabricatorLiskDAO $object, 30 + PhabricatorApplicationTransaction $xaction) { 31 + switch ($xaction->getTransactionType()) { 32 + case PhabricatorCountdownTransaction::TYPE_TITLE: 33 + return $object->getTitle(); 34 + case PhabricatorCountdownTransaction::TYPE_EPOCH: 35 + return $object->getEpoch(); 36 + } 37 + 38 + return parent::getCustomTransactionOldValue($object, $xaction); 39 + } 40 + 41 + protected function getCustomTransactionNewValue( 42 + PhabricatorLiskDAO $object, 43 + PhabricatorApplicationTransaction $xaction) { 44 + 45 + switch ($xaction->getTransactionType()) { 46 + case PhabricatorCountdownTransaction::TYPE_TITLE: 47 + return $xaction->getNewValue(); 48 + case PhabricatorCountdownTransaction::TYPE_EPOCH: 49 + return $xaction->getNewValue()->getEpoch(); 50 + } 51 + 52 + return parent::getCustomTransactionNewValue($object, $xaction); 53 + } 54 + 55 + protected function applyCustomInternalTransaction( 56 + PhabricatorLiskDAO $object, 57 + PhabricatorApplicationTransaction $xaction) { 58 + 59 + $type = $xaction->getTransactionType(); 60 + switch ($type) { 61 + case PhabricatorCountdownTransaction::TYPE_TITLE: 62 + $object->setTitle($xaction->getNewValue()); 63 + return; 64 + case PhabricatorCountdownTransaction::TYPE_EPOCH: 65 + $object->setEpoch($xaction->getNewValue()); 66 + return; 67 + } 68 + 69 + return parent::applyCustomInternalTransaction($object, $xaction); 70 + } 71 + 72 + protected function applyCustomExternalTransaction( 73 + PhabricatorLiskDAO $object, 74 + PhabricatorApplicationTransaction $xaction) { 75 + 76 + $type = $xaction->getTransactionType(); 77 + switch ($type) { 78 + case PhabricatorCountdownTransaction::TYPE_TITLE: 79 + return; 80 + case PhabricatorCountdownTransaction::TYPE_EPOCH: 81 + return; 82 + } 83 + 84 + return parent::applyCustomExternalTransaction($object, $xaction); 85 + } 86 + 87 + protected function validateTransaction( 88 + PhabricatorLiskDAO $object, 89 + $type, 90 + array $xactions) { 91 + 92 + $errors = parent::validateTransaction($object, $type, $xactions); 93 + 94 + switch ($type) { 95 + case PhabricatorCountdownTransaction::TYPE_TITLE: 96 + $missing = $this->validateIsEmptyTextField( 97 + $object->getTitle(), 98 + $xactions); 99 + 100 + if ($missing) { 101 + $error = new PhabricatorApplicationTransactionValidationError( 102 + $type, 103 + pht('Required'), 104 + pht('You must give the countdown a name.'), 105 + nonempty(last($xactions), null)); 106 + 107 + $error->setIsMissingFieldError(true); 108 + $errors[] = $error; 109 + } 110 + break; 111 + case PhabricatorCountdownTransaction::TYPE_EPOCH: 112 + $date_value = AphrontFormDateControlValue::newFromEpoch( 113 + $this->requireActor(), 114 + $object->getEpoch()); 115 + if (!$date_value->isValid()) { 116 + $error = new PhabricatorApplicationTransactionValidationError( 117 + $type, 118 + pht('Invalid'), 119 + pht('You must give the countdown a valid end date.'), 120 + nonempty(last($xactions), null)); 121 + 122 + $error->setIsMissingFieldError(true); 123 + $errors[] = $error; 124 + } 125 + break; 126 + } 127 + 128 + return $errors; 129 + } 130 + 131 + protected function shouldSendMail( 132 + PhabricatorLiskDAO $object, 133 + array $xactions) { 134 + return true; 135 + } 136 + 137 + public function getMailTagsMap() { 138 + return array( 139 + PhabricatorCountdownTransaction::MAILTAG_TITLE => 140 + pht('Someone changes the countdown title.'), 141 + PhabricatorCountdownTransaction::MAILTAG_EPOCH => 142 + pht('Someone changes the countdown end date.'), 143 + PhabricatorCountdownTransaction::MAILTAG_OTHER => 144 + pht('Other countdown activity not listed above occurs.'), 145 + ); 146 + } 147 + 148 + protected function buildMailTemplate(PhabricatorLiskDAO $object) { 149 + $monogram = $object->getMonogram(); 150 + $name = $object->getName(); 151 + 152 + return id(new PhabricatorMetaMTAMail()) 153 + ->setSubject("{$monogram}: {$name}") 154 + ->addHeader('Thread-Topic', $monogram); 155 + } 156 + 157 + protected function buildMailBody( 158 + PhabricatorLiskDAO $object, 159 + array $xactions) { 160 + 161 + $body = parent::buildMailBody($object, $xactions); 162 + 163 + $body->addLinkSection( 164 + pht('COUNTDOWN DETAIL'), 165 + PhabricatorEnv::getProductionURI('/'.$object->getMonogram())); 166 + 167 + return $body; 168 + } 169 + 170 + protected function getMailTo(PhabricatorLiskDAO $object) { 171 + return array($object->getAuthorPHID()); 172 + } 173 + 174 + protected function getMailSubjectPrefix() { 175 + return 'Countdown'; 176 + } 177 + 178 + protected function buildReplyHandler(PhabricatorLiskDAO $object) { 179 + return id(new PhabricatorCountdownReplyHandler()) 180 + ->setMailReceiver($object); 181 + } 182 + 183 + protected function shouldPublishFeedStory( 184 + PhabricatorLiskDAO $object, 185 + array $xactions) { 186 + return true; 187 + } 188 + 189 + protected function supportsSearch() { 190 + return true; 191 + } 192 + 193 + }
+16
src/applications/countdown/mail/PhabricatorCountdownReplyHandler.php
··· 1 + <?php 2 + 3 + final class PhabricatorCountdownReplyHandler 4 + extends PhabricatorApplicationTransactionReplyHandler { 5 + 6 + public function validateMailReceiver($mail_receiver) { 7 + if (!($mail_receiver instanceof PhabricatorCountdown)) { 8 + throw new Exception(pht('Mail receiver is not a %s!', 'Countdown')); 9 + } 10 + } 11 + 12 + public function getObjectPrefix() { 13 + return 'C'; 14 + } 15 + 16 + }
+17 -28
src/applications/countdown/query/PhabricatorCountdownQuery.php
··· 23 23 return $this; 24 24 } 25 25 26 - public function withUpcoming($upcoming) { 26 + public function withUpcoming() { 27 27 $this->upcoming = true; 28 28 return $this; 29 29 } 30 30 31 31 protected function loadPage() { 32 - $table = new PhabricatorCountdown(); 33 - $conn_r = $table->establishConnection('r'); 32 + return $this->loadStandardPage($this->newResultObject()); 33 + } 34 34 35 - $data = queryfx_all( 36 - $conn_r, 37 - 'SELECT * FROM %T %Q %Q %Q', 38 - $table->getTableName(), 39 - $this->buildWhereClause($conn_r), 40 - $this->buildOrderClause($conn_r), 41 - $this->buildLimitClause($conn_r)); 42 - 43 - $countdowns = $table->loadAllFromArray($data); 44 - 45 - return $countdowns; 35 + public function newResultObject() { 36 + return new PhabricatorCountdown(); 46 37 } 47 38 48 - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { 49 - $where = array(); 39 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 40 + $where = parent::buildWhereClauseParts($conn); 50 41 51 - $where[] = $this->buildPagingClause($conn_r); 52 - 53 - if ($this->ids) { 42 + if ($this->ids !== null) { 54 43 $where[] = qsprintf( 55 - $conn_r, 44 + $conn, 56 45 'id IN (%Ld)', 57 46 $this->ids); 58 47 } 59 48 60 - if ($this->phids) { 49 + if ($this->phids !== null) { 61 50 $where[] = qsprintf( 62 - $conn_r, 51 + $conn, 63 52 'phid IN (%Ls)', 64 53 $this->phids); 65 54 } 66 55 67 - if ($this->authorPHIDs) { 56 + if ($this->authorPHIDs !== null) { 68 57 $where[] = qsprintf( 69 - $conn_r, 58 + $conn, 70 59 'authorPHID in (%Ls)', 71 60 $this->authorPHIDs); 72 61 } 73 62 74 - if ($this->upcoming) { 63 + if ($this->upcoming !== null) { 75 64 $where[] = qsprintf( 76 - $conn_r, 65 + $conn, 77 66 'epoch >= %d', 78 - time()); 67 + PhabricatorTime::getNow()); 79 68 } 80 69 81 - return $this->formatWhereClause($where); 70 + return $where; 82 71 } 83 72 84 73 public function getQueryApplicationClass() {
+38 -45
src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
··· 11 11 return 'PhabricatorCountdownApplication'; 12 12 } 13 13 14 - public function buildSavedQueryFromRequest(AphrontRequest $request) { 15 - $saved = new PhabricatorSavedQuery(); 16 - $saved->setParameter( 17 - 'authorPHIDs', 18 - $this->readUsersFromRequest($request, 'authors')); 19 - 20 - $saved->setParameter('upcoming', $request->getBool('upcoming')); 21 - 22 - return $saved; 14 + public function newQuery() { 15 + return new PhabricatorCountdownQuery(); 23 16 } 24 17 25 - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 26 - $query = id(new PhabricatorCountdownQuery()); 18 + protected function buildQueryFromParameters(array $map) { 19 + $query = $this->newQuery(); 27 20 28 - $author_phids = $saved->getParameter('authorPHIDs', array()); 29 - if ($author_phids) { 30 - $query->withAuthorPHIDs($author_phids); 21 + if ($map['authorPHIDs']) { 22 + $query->withAuthorPHIDs($map['authorPHIDs']); 31 23 } 32 24 33 - if ($saved->getParameter('upcoming')) { 34 - $query->withUpcoming(true); 25 + if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') { 26 + $query->withUpcoming(); 35 27 } 36 28 37 29 return $query; 38 30 } 39 31 40 - public function buildSearchForm( 41 - AphrontFormView $form, 42 - PhabricatorSavedQuery $saved_query) { 32 + protected function buildCustomSearchFields() { 33 + 34 + return array( 35 + id(new PhabricatorUsersSearchField()) 36 + ->setLabel(pht('Authors')) 37 + ->setKey('authorPHIDs') 38 + ->setAliases(array('author', 'authors')), 43 39 44 - $author_phids = $saved_query->getParameter('authorPHIDs', array()); 45 - $upcoming = $saved_query->getParameter('upcoming'); 40 + id(new PhabricatorSearchCheckboxesField()) 41 + ->setKey('upcoming') 42 + ->setOptions(array( 43 + 'upcoming' => pht('Show only upcoming countdowns.'), 44 + )), 45 + ); 46 46 47 - $form 48 - ->appendControl( 49 - id(new AphrontFormTokenizerControl()) 50 - ->setDatasource(new PhabricatorPeopleDatasource()) 51 - ->setName('authors') 52 - ->setLabel(pht('Authors')) 53 - ->setValue($author_phids)) 54 - ->appendChild( 55 - id(new AphrontFormCheckboxControl()) 56 - ->addCheckbox( 57 - 'upcoming', 58 - 1, 59 - pht('Show only countdowns that are still counting down.'), 60 - $upcoming)); 61 47 } 62 48 63 49 protected function getURI($path) { ··· 89 75 'authorPHIDs', 90 76 array($this->requireViewer()->getPHID())); 91 77 case 'upcoming': 92 - return $query->setParameter('upcoming', true); 78 + return $query->setParameter('upcoming', array('upcoming')); 93 79 } 94 80 95 81 return parent::buildSavedQueryFromBuiltin($query_key); ··· 115 101 $list->setUser($viewer); 116 102 foreach ($countdowns as $countdown) { 117 103 $id = $countdown->getID(); 104 + $ended = false; 105 + $icon = 'fa-clock-o'; 106 + $color = 'green'; 107 + $epoch = $countdown->getEpoch(); 108 + if ($epoch <= PhabricatorTime::getNow()) { 109 + $ended = true; 110 + $icon = 'fa-check-square-o'; 111 + $color = 'grey'; 112 + } 118 113 119 114 $item = id(new PHUIObjectItemView()) 120 115 ->setUser($viewer) 121 116 ->setObject($countdown) 122 117 ->setObjectName("C{$id}") 123 118 ->setHeader($countdown->getTitle()) 119 + ->setStatusIcon($icon.' '.$color) 124 120 ->setHref($this->getApplicationURI("{$id}/")) 125 121 ->addByline( 126 122 pht( 127 123 'Created by %s', 128 124 $handles[$countdown->getAuthorPHID()]->renderLink())); 129 125 130 - $epoch = $countdown->getEpoch(); 131 - if ($epoch >= time()) { 132 - $item->addIcon( 133 - 'none', 134 - pht('Ends %s', phabricator_datetime($epoch, $viewer))); 135 - } else { 136 - $item->addIcon( 137 - 'delete', 138 - pht('Ended %s', phabricator_datetime($epoch, $viewer))); 126 + if ($ended) { 127 + $item->addAttribute( 128 + pht('Launched on %s', phabricator_datetime($epoch, $viewer))); 139 129 $item->setDisabled(true); 130 + } else { 131 + $item->addAttribute( 132 + phabricator_datetime($epoch, $viewer)); 140 133 } 141 134 142 135 $list->addItem($item);
+10
src/applications/countdown/query/PhabricatorCountdownTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorCountdownTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhabricatorCountdownTransaction(); 8 + } 9 + 10 + }
+50 -4
src/applications/countdown/storage/PhabricatorCountdown.php
··· 4 4 implements 5 5 PhabricatorPolicyInterface, 6 6 PhabricatorFlaggableInterface, 7 - PhabricatorSpacesInterface { 7 + PhabricatorSubscribableInterface, 8 + PhabricatorApplicationTransactionInterface, 9 + PhabricatorSpacesInterface, 10 + PhabricatorProjectInterface { 8 11 9 12 protected $title; 10 13 protected $authorPHID; 11 14 protected $epoch; 12 15 protected $viewPolicy; 16 + protected $editPolicy; 13 17 14 18 protected $spacePHID; 15 19 ··· 43 47 PhabricatorCountdownCountdownPHIDType::TYPECONST); 44 48 } 45 49 50 + public function getMonogram() { 51 + return 'C'.$this->getID(); 52 + } 53 + 54 + 55 + /* -( PhabricatorSubscribableInterface )----------------------------------- */ 56 + 57 + 58 + public function isAutomaticallySubscribed($phid) { 59 + return ($phid == $this->getAuthorPHID()); 60 + } 61 + 62 + public function shouldShowSubscribersProperty() { 63 + return true; 64 + } 65 + 66 + public function shouldAllowSubscription($phid) { 67 + return true; 68 + } 69 + 70 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 71 + 72 + 73 + public function getApplicationTransactionEditor() { 74 + return new PhabricatorCountdownEditor(); 75 + } 76 + 77 + public function getApplicationTransactionObject() { 78 + return $this; 79 + } 80 + 81 + public function getApplicationTransactionTemplate() { 82 + return new PhabricatorCountdownTransaction(); 83 + } 84 + 85 + public function willRenderTimeline( 86 + PhabricatorApplicationTransactionView $timeline, 87 + AphrontRequest $request) { 88 + 89 + return $timeline; 90 + } 91 + 46 92 47 93 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 48 94 ··· 59 105 case PhabricatorPolicyCapability::CAN_VIEW: 60 106 return $this->getViewPolicy(); 61 107 case PhabricatorPolicyCapability::CAN_EDIT: 62 - return PhabricatorPolicies::POLICY_NOONE; 108 + return $this->getEditPolicy(); 63 109 } 64 110 } 65 111 66 112 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 67 - return ($viewer->getPHID() == $this->getAuthorPHID()); 113 + return false; 68 114 } 69 115 70 116 public function describeAutomaticCapability($capability) { 71 - return pht('The author of a countdown can always view and edit it.'); 117 + return false; 72 118 } 73 119 74 120 /* -( PhabricatorSpacesInterface )------------------------------------------- */
+10
src/applications/countdown/storage/PhabricatorCountdownSchemaSpec.php
··· 1 + <?php 2 + 3 + final class PhabricatorCountdownSchemaSpec 4 + extends PhabricatorConfigSchemaSpec { 5 + 6 + public function buildSchemata() { 7 + $this->buildEdgeSchemata(new PhabricatorCountdown()); 8 + } 9 + 10 + }
+110
src/applications/countdown/storage/PhabricatorCountdownTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorCountdownTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_TITLE = 'countdown:title'; 7 + const TYPE_EPOCH = 'countdown:epoch'; 8 + 9 + const MAILTAG_TITLE = 'countdown:title'; 10 + const MAILTAG_EPOCH = 'countdown:epoch'; 11 + const MAILTAG_OTHER = 'countdown:other'; 12 + 13 + public function getApplicationName() { 14 + return 'countdown'; 15 + } 16 + 17 + public function getApplicationTransactionType() { 18 + return PhabricatorCountdownCountdownPHIDType::TYPECONST; 19 + } 20 + 21 + public function getApplicationTransactionCommentObject() { 22 + return null; 23 + } 24 + 25 + public function getTitle() { 26 + $author_phid = $this->getAuthorPHID(); 27 + $object_phid = $this->getObjectPHID(); 28 + 29 + $old = $this->getOldValue(); 30 + $new = $this->getNewValue(); 31 + 32 + $type = $this->getTransactionType(); 33 + switch ($type) { 34 + case self::TYPE_TITLE: 35 + if ($old === null) { 36 + return pht( 37 + '%s created this countdown.', 38 + $this->renderHandleLink($author_phid)); 39 + } else { 40 + return pht( 41 + '%s renamed this countdown from "%s" to "%s".', 42 + $this->renderHandleLink($author_phid), 43 + $old, 44 + $new); 45 + } 46 + case self::TYPE_EPOCH: 47 + if ($old === null) { 48 + return pht( 49 + '%s set this countdown to end on %s.', 50 + $this->renderHandleLink($author_phid), 51 + phabricator_datetime($new, $this->getViewer())); 52 + } else if ($old != $new) { 53 + return pht( 54 + '%s updated this countdown to end on %s.', 55 + $this->renderHandleLink($author_phid), 56 + phabricator_datetime($new, $this->getViewer())); 57 + } 58 + break; 59 + } 60 + 61 + return parent::getTitle(); 62 + } 63 + 64 + public function getTitleForFeed() { 65 + $author_phid = $this->getAuthorPHID(); 66 + $object_phid = $this->getObjectPHID(); 67 + 68 + $old = $this->getOldValue(); 69 + $new = $this->getNewValue(); 70 + 71 + $type = $this->getTransactionType(); 72 + switch ($type) { 73 + case self::TYPE_TITLE: 74 + if ($old === null) { 75 + return pht( 76 + '%s created %s.', 77 + $this->renderHandleLink($author_phid), 78 + $this->renderHandleLink($object_phid)); 79 + 80 + } else { 81 + return pht( 82 + '%s renamed %s.', 83 + $this->renderHandleLink($author_phid), 84 + $this->renderHandleLink($object_phid)); 85 + } 86 + break; 87 + } 88 + 89 + return parent::getTitleForFeed(); 90 + } 91 + 92 + public function getMailTags() { 93 + $tags = parent::getMailTags(); 94 + 95 + switch ($this->getTransactionType()) { 96 + case self::TYPE_TITLE: 97 + $tags[] = self::MAILTAG_TITLE; 98 + break; 99 + case self::TYPE_EPOCH: 100 + $tags[] = self::MAILTAG_EPOCH; 101 + break; 102 + default: 103 + $tags[] = self::MAILTAG_OTHER; 104 + break; 105 + } 106 + 107 + return $tags; 108 + } 109 + 110 + }