Page MenuHomePhabricator

D13660.id33028.diff
No OneTemporary

D13660.id33028.diff

diff --git a/resources/sql/autopatches/20150719.countdown.1.sql b/resources/sql/autopatches/20150719.countdown.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150719.countdown.1.sql
@@ -0,0 +1,36 @@
+CREATE TABLE {$NAMESPACE}_countdown.countdown_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}_countdown.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}_countdown.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/resources/sql/autopatches/20150719.countdown.2.sql b/resources/sql/autopatches/20150719.countdown.2.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150719.countdown.2.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_countdown.countdown
+ADD editPolicy VARBINARY(64) NOT NULL;
diff --git a/resources/sql/autopatches/20150719.countdown.3.sql b/resources/sql/autopatches/20150719.countdown.3.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150719.countdown.3.sql
@@ -0,0 +1,2 @@
+UPDATE {$NAMESPACE}_countdown.countdown
+ SET editPolicy = authorPHID WHERE editPolicy = '';
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
@@ -1795,13 +1795,19 @@
'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
'PhabricatorCountdownCountdownPHIDType' => 'applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php',
'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php',
+ 'PhabricatorCountdownDefaultEditCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php',
'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php',
'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php',
'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php',
+ 'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php',
'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php',
'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php',
'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php',
+ 'PhabricatorCountdownReplyHandler' => 'applications/countdown/mail/PhabricatorCountdownReplyHandler.php',
+ 'PhabricatorCountdownSchemaSpec' => 'applications/countdown/storage/PhabricatorCountdownSchemaSpec.php',
'PhabricatorCountdownSearchEngine' => 'applications/countdown/query/PhabricatorCountdownSearchEngine.php',
+ 'PhabricatorCountdownTransaction' => 'applications/countdown/storage/PhabricatorCountdownTransaction.php',
+ 'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php',
'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php',
'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
'PhabricatorCredentialsUsedByObjectEdgeType' => 'applications/passphrase/edge/PhabricatorCredentialsUsedByObjectEdgeType.php',
@@ -5538,19 +5544,28 @@
'PhabricatorCountdownDAO',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
+ 'PhabricatorSubscribableInterface',
+ 'PhabricatorApplicationTransactionInterface',
'PhabricatorSpacesInterface',
+ 'PhabricatorProjectInterface',
),
'PhabricatorCountdownApplication' => 'PhabricatorApplication',
'PhabricatorCountdownController' => 'PhabricatorController',
'PhabricatorCountdownCountdownPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO',
+ 'PhabricatorCountdownDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController',
'PhabricatorCountdownEditController' => 'PhabricatorCountdownController',
+ 'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorCountdownListController' => 'PhabricatorCountdownController',
'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCountdownRemarkupRule' => 'PhabricatorObjectRemarkupRule',
+ 'PhabricatorCountdownReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
+ 'PhabricatorCountdownSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorCountdownSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'PhabricatorCountdownTransaction' => 'PhabricatorApplicationTransaction',
+ 'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCountdownView' => 'AphrontTagView',
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
'PhabricatorCredentialsUsedByObjectEdgeType' => 'PhabricatorEdgeType',
diff --git a/src/applications/countdown/application/PhabricatorCountdownApplication.php b/src/applications/countdown/application/PhabricatorCountdownApplication.php
--- a/src/applications/countdown/application/PhabricatorCountdownApplication.php
+++ b/src/applications/countdown/application/PhabricatorCountdownApplication.php
@@ -38,6 +38,7 @@
public function getRoutes() {
return array(
+ '/C(?P<id>[1-9]\d*)' => 'PhabricatorCountdownViewController',
'/countdown/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorCountdownListController',
@@ -55,6 +56,11 @@
'template' => PhabricatorCountdownCountdownPHIDType::TYPECONST,
'capability' => PhabricatorPolicyCapability::CAN_VIEW,
),
+ PhabricatorCountdownDefaultEditCapability::CAPABILITY => array(
+ 'caption' => pht('Default edit policy for new countdowns.'),
+ 'template' => PhabricatorCountdownCountdownPHIDType::TYPECONST,
+ 'capability' => PhabricatorPolicyCapability::CAN_EDIT,
+ ),
);
}
diff --git a/src/applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php b/src/applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php
new file mode 100644
--- /dev/null
+++ b/src/applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php
@@ -0,0 +1,12 @@
+<?php
+
+final class PhabricatorCountdownDefaultEditCapability
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'countdown.default.edit';
+
+ public function getCapabilityName() {
+ return pht('Default Edit Policy');
+ }
+
+}
diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php
--- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php
@@ -3,21 +3,15 @@
final class PhabricatorCountdownEditController
extends PhabricatorCountdownController {
- private $id;
- public function willProcessRequest(array $data) {
- $this->id = idx($data, 'id');
- }
-
- public function processRequest() {
-
- $request = $this->getRequest();
- $user = $request->getUser();
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+ $id = $request->getURIData('id');
- if ($this->id) {
+ if ($id) {
$page_title = pht('Edit Countdown');
$countdown = id(new PhabricatorCountdownQuery())
- ->setViewer($user)
- ->withIDs(array($this->id))
+ ->setViewer($viewer)
+ ->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
@@ -28,12 +22,18 @@
return new Aphront404Response();
}
$date_value = AphrontFormDateControlValue::newFromEpoch(
- $user,
+ $viewer,
$countdown->getEpoch());
+ $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $countdown->getPHID(),
+ PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
+ $v_projects = array_reverse($v_projects);
} else {
$page_title = pht('Create Countdown');
- $countdown = PhabricatorCountdown::initializeNewCountdown($user);
- $date_value = AphrontFormDateControlValue::newFromEpoch($user, time());
+ $countdown = PhabricatorCountdown::initializeNewCountdown($viewer);
+ $date_value = AphrontFormDateControlValue::newFromEpoch(
+ $viewer, PhabricatorTime::getNow());
+ $v_projects = array();
}
$errors = array();
@@ -42,6 +42,8 @@
$v_text = $countdown->getTitle();
$v_space = $countdown->getSpacePHID();
+ $v_view = $countdown->getViewPolicy();
+ $v_edit = $countdown->getEditPolicy();
if ($request->isFormPost()) {
$v_text = $request->getStr('title');
@@ -49,27 +51,61 @@
$date_value = AphrontFormDateControlValue::newFromRequest(
$request,
'epoch');
- $view_policy = $request->getStr('viewPolicy');
+ $v_view = $request->getStr('viewPolicy');
+ $v_edit = $request->getStr('editPolicy');
+ $v_projects = $request->getArr('projects');
- $e_text = null;
- if (!strlen($v_text)) {
- $e_text = pht('Required');
- $errors[] = pht('You must give the countdown a name.');
- }
- if (!$date_value->isValid()) {
- $e_epoch = pht('Invalid');
- $errors[] = pht('You must give the countdown a valid end date.');
- }
+ $type_title = PhabricatorCountdownTransaction::TYPE_TITLE;
+ $type_epoch = PhabricatorCountdownTransaction::TYPE_EPOCH;
+ $type_space = PhabricatorTransactions::TYPE_SPACE;
+ $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
+ $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
+
+ $xactions = array();
+
+ $xactions[] = id(new PhabricatorCountdownTransaction())
+ ->setTransactionType($type_title)
+ ->setNewValue($v_text);
+
+ $xactions[] = id(new PhabricatorCountdownTransaction())
+ ->setTransactionType($type_epoch)
+ ->setNewValue($date_value);
+
+ $xactions[] = id(new PhabricatorCountdownTransaction())
+ ->setTransactionType($type_space)
+ ->setNewValue($v_space);
+
+ $xactions[] = id(new PhabricatorCountdownTransaction())
+ ->setTransactionType($type_view)
+ ->setNewValue($v_view);
+
+ $xactions[] = id(new PhabricatorCountdownTransaction())
+ ->setTransactionType($type_edit)
+ ->setNewValue($v_edit);
+
+ $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
+ $xactions[] = id(new PhabricatorCountdownTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
+ ->setMetadataValue('edge:type', $proj_edge_type)
+ ->setNewValue(array('=' => array_fuse($v_projects)));
+
+ $editor = id(new PhabricatorCountdownEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true);
+
+ try {
+ $editor->applyTransactions($countdown, $xactions);
- if (!count($errors)) {
- $countdown->setTitle($v_text);
- $countdown->setEpoch($date_value->getEpoch());
- $countdown->setViewPolicy($view_policy);
- $countdown->setSpacePHID($v_space);
- $countdown->save();
return id(new AphrontRedirectResponse())
- ->setURI('/countdown/'.$countdown->getID().'/');
+ ->setURI('/'.$countdown->getMonogram());
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $validation_exception = $ex;
+
+ $e_title = $ex->getShortMessage($type_title);
+ $e_epoch = $ex->getShortMessage($type_epoch);
}
+
}
$crumbs = $this->buildApplicationCrumbs();
@@ -86,12 +122,12 @@
}
$policies = id(new PhabricatorPolicyQuery())
- ->setViewer($user)
+ ->setViewer($viewer)
->setObject($countdown)
->execute();
$form = id(new AphrontFormView())
- ->setUser($user)
+ ->setUser($viewer)
->setAction($request->getRequestURI()->getPath())
->appendChild(
id(new AphrontFormTextControl())
@@ -99,21 +135,33 @@
->setValue($v_text)
->setName('title')
->setError($e_text))
- ->appendChild(
+ ->appendControl(
id(new AphrontFormDateControl())
- ->setUser($user)
->setName('epoch')
->setLabel(pht('End Date'))
->setError($e_epoch)
->setValue($date_value))
- ->appendChild(
+ ->appendControl(
id(new AphrontFormPolicyControl())
- ->setUser($user)
->setName('viewPolicy')
->setPolicyObject($countdown)
->setPolicies($policies)
->setSpacePHID($v_space)
+ ->setValue($v_view)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
+ ->appendControl(
+ id(new AphrontFormPolicyControl())
+ ->setName('editPolicy')
+ ->setPolicyObject($countdown)
+ ->setPolicies($policies)
+ ->setValue($v_edit)
+ ->setCapability(PhabricatorPolicyCapability::CAN_EDIT))
+ ->appendControl(
+ id(new AphrontFormTokenizerControl())
+ ->setLabel(pht('Projects'))
+ ->setName('projects')
+ ->setValue($v_projects)
+ ->setDatasource(new PhabricatorProjectDatasource()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php
--- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php
@@ -3,31 +3,24 @@
final class PhabricatorCountdownViewController
extends PhabricatorCountdownController {
- private $id;
-
public function shouldAllowPublic() {
return true;
}
- public function willProcessRequest(array $data) {
- $this->id = $data['id'];
- }
-
- public function processRequest() {
-
- $request = $this->getRequest();
- $user = $request->getUser();
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+ $id = $request->getURIData('id');
$countdown = id(new PhabricatorCountdownQuery())
- ->setViewer($user)
- ->withIDs(array($this->id))
+ ->setViewer($viewer)
+ ->withIDs(array($id))
->executeOne();
if (!$countdown) {
return new Aphront404Response();
}
$countdown_view = id(new PhabricatorCountdownView())
- ->setUser($user)
+ ->setUser($viewer)
->setCountdown($countdown)
->setHeadless(true);
@@ -38,10 +31,22 @@
->buildApplicationCrumbs()
->addTextCrumb("C{$id}");
+ $epoch = $countdown->getEpoch();
+ if ($epoch >= PhabricatorTime::getNow()) {
+ $icon = 'fa-clock-o';
+ $color = '';
+ $status = pht('Running');
+ } else {
+ $icon = 'fa-check-square-o';
+ $color = 'dark';
+ $status = pht('Launched');
+ }
+
$header = id(new PHUIHeaderView())
->setHeader($title)
- ->setUser($user)
- ->setPolicyObject($countdown);
+ ->setUser($viewer)
+ ->setPolicyObject($countdown)
+ ->setStatus($icon, $color, $status);
$actions = $this->buildActionListView($countdown);
$properties = $this->buildPropertyListView($countdown, $actions);
@@ -50,10 +55,16 @@
->setHeader($header)
->addPropertyList($properties);
+ $timeline = $this->buildTransactionTimeline(
+ $countdown,
+ new PhabricatorCountdownTransactionQuery());
+ $timeline->setShouldTerminate(true);
+
$content = array(
$crumbs,
$object_box,
$countdown_view,
+ $timeline,
);
return $this->buildApplicationPage(
@@ -105,6 +116,7 @@
$view = id(new PHUIPropertyListView())
->setUser($viewer)
+ ->setObject($countdown)
->setActionList($actions);
$view->addProperty(
diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php
@@ -0,0 +1,193 @@
+<?php
+
+final class PhabricatorCountdownEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ public function getEditorApplicationClass() {
+ return 'PhabricatorCountdownApplication';
+ }
+
+ public function getEditorObjectsDescription() {
+ return pht('Countdown');
+ }
+
+ public function getTransactionTypes() {
+ $types = parent::getTransactionTypes();
+
+ $types[] = PhabricatorCountdownTransaction::TYPE_TITLE;
+ $types[] = PhabricatorCountdownTransaction::TYPE_EPOCH;
+
+ $types[] = PhabricatorTransactions::TYPE_EDGE;
+ $types[] = PhabricatorTransactions::TYPE_SPACE;
+ $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
+ $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
+
+ return $types;
+ }
+
+ protected function getCustomTransactionOldValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorCountdownTransaction::TYPE_TITLE:
+ return $object->getTitle();
+ case PhabricatorCountdownTransaction::TYPE_EPOCH:
+ return $object->getEpoch();
+ }
+
+ return parent::getCustomTransactionOldValue($object, $xaction);
+ }
+
+ protected function getCustomTransactionNewValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorCountdownTransaction::TYPE_TITLE:
+ return $xaction->getNewValue();
+ case PhabricatorCountdownTransaction::TYPE_EPOCH:
+ return $xaction->getNewValue()->getEpoch();
+ }
+
+ return parent::getCustomTransactionNewValue($object, $xaction);
+ }
+
+ protected function applyCustomInternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ $type = $xaction->getTransactionType();
+ switch ($type) {
+ case PhabricatorCountdownTransaction::TYPE_TITLE:
+ $object->setTitle($xaction->getNewValue());
+ return;
+ case PhabricatorCountdownTransaction::TYPE_EPOCH:
+ $object->setEpoch($xaction->getNewValue());
+ return;
+ }
+
+ return parent::applyCustomInternalTransaction($object, $xaction);
+ }
+
+ protected function applyCustomExternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ $type = $xaction->getTransactionType();
+ switch ($type) {
+ case PhabricatorCountdownTransaction::TYPE_TITLE:
+ return;
+ case PhabricatorCountdownTransaction::TYPE_EPOCH:
+ return;
+ }
+
+ return parent::applyCustomExternalTransaction($object, $xaction);
+ }
+
+ protected function validateTransaction(
+ PhabricatorLiskDAO $object,
+ $type,
+ array $xactions) {
+
+ $errors = parent::validateTransaction($object, $type, $xactions);
+
+ switch ($type) {
+ case PhabricatorCountdownTransaction::TYPE_TITLE:
+ $missing = $this->validateIsEmptyTextField(
+ $object->getTitle(),
+ $xactions);
+
+ if ($missing) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Required'),
+ pht('You must give the countdown a name.'),
+ nonempty(last($xactions), null));
+
+ $error->setIsMissingFieldError(true);
+ $errors[] = $error;
+ }
+ break;
+ case PhabricatorCountdownTransaction::TYPE_EPOCH:
+ $date_value = AphrontFormDateControlValue::newFromEpoch(
+ $this->requireActor(),
+ $object->getEpoch());
+ if (!$date_value->isValid()) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Invalid'),
+ pht('You must give the countdown a valid end date.'),
+ nonempty(last($xactions), null));
+
+ $error->setIsMissingFieldError(true);
+ $errors[] = $error;
+ }
+ break;
+ }
+
+ return $errors;
+ }
+
+ protected function shouldSendMail(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return true;
+ }
+
+ public function getMailTagsMap() {
+ return array(
+ PhabricatorCountdownTransaction::MAILTAG_TITLE =>
+ pht('Someone changes the countdown title.'),
+ PhabricatorCountdownTransaction::MAILTAG_EPOCH =>
+ pht('Someone changes the countdown end date.'),
+ PhabricatorCountdownTransaction::MAILTAG_OTHER =>
+ pht('Other countdown activity not listed above occurs.'),
+ );
+ }
+
+ protected function buildMailTemplate(PhabricatorLiskDAO $object) {
+ $monogram = $object->getMonogram();
+ $name = $object->getName();
+
+ return id(new PhabricatorMetaMTAMail())
+ ->setSubject("{$monogram}: {$name}")
+ ->addHeader('Thread-Topic', $monogram);
+ }
+
+ protected function buildMailBody(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $body = parent::buildMailBody($object, $xactions);
+
+ $body->addLinkSection(
+ pht('COUNTDOWN DETAIL'),
+ PhabricatorEnv::getProductionURI('/'.$object->getMonogram()));
+
+ return $body;
+ }
+
+ protected function getMailTo(PhabricatorLiskDAO $object) {
+ return array($object->getAuthorPHID());
+ }
+
+ protected function getMailSubjectPrefix() {
+ return 'Countdown';
+ }
+
+ protected function buildReplyHandler(PhabricatorLiskDAO $object) {
+ return id(new PhabricatorCountdownReplyHandler())
+ ->setMailReceiver($object);
+ }
+
+ protected function shouldPublishFeedStory(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return true;
+ }
+
+ protected function supportsSearch() {
+ return true;
+ }
+
+}
diff --git a/src/applications/countdown/mail/PhabricatorCountdownReplyHandler.php b/src/applications/countdown/mail/PhabricatorCountdownReplyHandler.php
new file mode 100644
--- /dev/null
+++ b/src/applications/countdown/mail/PhabricatorCountdownReplyHandler.php
@@ -0,0 +1,16 @@
+<?php
+
+final class PhabricatorCountdownReplyHandler
+ extends PhabricatorApplicationTransactionReplyHandler {
+
+ public function validateMailReceiver($mail_receiver) {
+ if (!($mail_receiver instanceof PhabricatorCountdown)) {
+ throw new Exception(pht('Mail receiver is not a %s!', 'Countdown'));
+ }
+ }
+
+ public function getObjectPrefix() {
+ return 'C';
+ }
+
+}
diff --git a/src/applications/countdown/query/PhabricatorCountdownQuery.php b/src/applications/countdown/query/PhabricatorCountdownQuery.php
--- a/src/applications/countdown/query/PhabricatorCountdownQuery.php
+++ b/src/applications/countdown/query/PhabricatorCountdownQuery.php
@@ -6,7 +6,8 @@
private $ids;
private $phids;
private $authorPHIDs;
- private $upcoming;
+ private $rangeBegin;
+ private $rangeEnd;
public function withIDs(array $ids) {
$this->ids = $ids;
@@ -23,62 +24,59 @@
return $this;
}
- public function withUpcoming($upcoming) {
- $this->upcoming = true;
+ public function withDateRange($begin, $end) {
+ $this->rangeBegin = $begin;
+ $this->rangeEnd = $end;
return $this;
}
protected function loadPage() {
- $table = new PhabricatorCountdown();
- $conn_r = $table->establishConnection('r');
-
- $data = queryfx_all(
- $conn_r,
- 'SELECT * FROM %T %Q %Q %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- $countdowns = $table->loadAllFromArray($data);
-
- return $countdowns;
+ return $this->loadStandardPage($this->newResultObject());
}
- protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
- $where = array();
+ public function newResultObject() {
+ return new PhabricatorCountdown();
+ }
- $where[] = $this->buildPagingClause($conn_r);
+ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+ $where = parent::buildWhereClauseParts($conn);
- if ($this->ids) {
+ if ($this->ids !== null) {
$where[] = qsprintf(
- $conn_r,
+ $conn,
'id IN (%Ld)',
$this->ids);
}
- if ($this->phids) {
+ if ($this->phids !== null) {
$where[] = qsprintf(
- $conn_r,
+ $conn,
'phid IN (%Ls)',
$this->phids);
}
- if ($this->authorPHIDs) {
+ if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
- $conn_r,
+ $conn,
'authorPHID in (%Ls)',
$this->authorPHIDs);
}
- if ($this->upcoming) {
+ if ($this->rangeEnd !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'epoch <= %d',
+ $this->rangeEnd);
+ }
+
+ if ($this->rangeBegin !== null) {
$where[] = qsprintf(
- $conn_r,
+ $conn,
'epoch >= %d',
- time());
+ $this->rangeBegin);
}
- return $this->formatWhereClause($where);
+ return $where;
}
public function getQueryApplicationClass() {
diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
--- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
+++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
@@ -11,53 +11,107 @@
return 'PhabricatorCountdownApplication';
}
- public function buildSavedQueryFromRequest(AphrontRequest $request) {
- $saved = new PhabricatorSavedQuery();
- $saved->setParameter(
- 'authorPHIDs',
- $this->readUsersFromRequest($request, 'authors'));
-
- $saved->setParameter('upcoming', $request->getBool('upcoming'));
-
- return $saved;
+ public function newQuery() {
+ return new PhabricatorCountdownQuery();
}
- public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
- $query = id(new PhabricatorCountdownQuery());
+ protected function buildQueryFromParameters(array $map) {
+ $query = $this->newQuery();
- $author_phids = $saved->getParameter('authorPHIDs', array());
- if ($author_phids) {
- $query->withAuthorPHIDs($author_phids);
+ if ($map['authorPHIDs']) {
+ $query->withAuthorPHIDs($map['authorPHIDs']);
}
- if ($saved->getParameter('upcoming')) {
- $query->withUpcoming(true);
+ $range_start = $map['rangeStart'];
+ $range_end = $map['rangeEnd'];
+
+ if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') {
+ $upcoming = true;
+ } else {
+ $upcoming = false;
}
+ list($range_start, $range_end) = $this->getQueryDateRange(
+ $range_start,
+ $range_end,
+ $upcoming);
+
+ $query->withDateRange($range_start, $range_end);
+
return $query;
}
- public function buildSearchForm(
- AphrontFormView $form,
- PhabricatorSavedQuery $saved_query) {
+ private function getQueryDateRange(
+ $start_date_wild,
+ $end_date_wild,
+ $upcoming) {
+
+ $start_date_value = $this->getSafeDate($start_date_wild);
+ $end_date_value = $this->getSafeDate($end_date_wild);
+
+ $viewer = $this->requireViewer();
+ $timezone = new DateTimeZone($viewer->getTimezoneIdentifier());
+ $min_range = null;
+ $max_range = null;
+
+ $min_range = $start_date_value->getEpoch();
+ $max_range = $end_date_value->getEpoch();
+
+ if ($upcoming) {
+ if ($min_range) {
+ $min_range = max(time(), $min_range);
+ } else {
+ $min_range = time();
+ }
+ }
+
+ return array($min_range, $max_range);
+ }
+
+ private function getSafeDate($value) {
+ $viewer = $this->requireViewer();
+ if ($value) {
+ // ideally this would be consistent and always pass in the same type
+ if ($value instanceof AphrontFormDateControlValue) {
+ return $value;
+ } else {
+ $value = AphrontFormDateControlValue::newFromWild($viewer, $value);
+ }
+ } else {
+ $value = AphrontFormDateControlValue::newFromEpoch(
+ $viewer,
+ PhabricatorTime::getTodayMidnightDateTime($viewer)->format('U'));
+ $value->setEnabled(false);
+ }
- $author_phids = $saved_query->getParameter('authorPHIDs', array());
- $upcoming = $saved_query->getParameter('upcoming');
+ $value->setOptional(true);
- $form
- ->appendControl(
- id(new AphrontFormTokenizerControl())
- ->setDatasource(new PhabricatorPeopleDatasource())
- ->setName('authors')
+ return $value;
+ }
+
+ protected function buildCustomSearchFields() {
+
+ return array(
+ id(new PhabricatorUsersSearchField())
->setLabel(pht('Authors'))
- ->setValue($author_phids))
- ->appendChild(
- id(new AphrontFormCheckboxControl())
- ->addCheckbox(
- 'upcoming',
- 1,
- pht('Show only countdowns that are still counting down.'),
- $upcoming));
+ ->setKey('authorPHIDs')
+ ->setAliases(array('author', 'authors')),
+
+ id(new PhabricatorSearchDateControlField())
+ ->setLabel(pht('Start Date Range'))
+ ->setKey('rangeStart'),
+
+ id(new PhabricatorSearchDateControlField())
+ ->setLabel(pht('End Date Range'))
+ ->setKey('rangeEnd'),
+
+ id(new PhabricatorSearchCheckboxesField())
+ ->setKey('upcoming')
+ ->setOptions(array(
+ 'upcoming' => pht('Show only upcoming countdowns.'),
+ )),
+ );
+
}
protected function getURI($path) {
@@ -89,7 +143,7 @@
'authorPHIDs',
array($this->requireViewer()->getPHID()));
case 'upcoming':
- return $query->setParameter('upcoming', true);
+ return $query->setParameter('upcoming', array('upcoming'));
}
return parent::buildSavedQueryFromBuiltin($query_key);
@@ -115,28 +169,35 @@
$list->setUser($viewer);
foreach ($countdowns as $countdown) {
$id = $countdown->getID();
+ $ended = false;
+ $icon = 'fa-clock-o';
+ $color = 'green';
+ $epoch = $countdown->getEpoch();
+ if ($epoch <= PhabricatorTime::getNow()) {
+ $ended = true;
+ $icon = 'fa-check-square-o';
+ $color = 'grey';
+ }
$item = id(new PHUIObjectItemView())
->setUser($viewer)
->setObject($countdown)
->setObjectName("C{$id}")
->setHeader($countdown->getTitle())
+ ->setStatusIcon($icon.' '.$color)
->setHref($this->getApplicationURI("{$id}/"))
->addByline(
pht(
'Created by %s',
$handles[$countdown->getAuthorPHID()]->renderLink()));
- $epoch = $countdown->getEpoch();
- if ($epoch >= time()) {
- $item->addIcon(
- 'none',
- pht('Ends %s', phabricator_datetime($epoch, $viewer)));
- } else {
- $item->addIcon(
- 'delete',
- pht('Ended %s', phabricator_datetime($epoch, $viewer)));
+ if ($ended) {
+ $item->addAttribute(
+ pht('Launched on %s', phabricator_datetime($epoch, $viewer)));
$item->setDisabled(true);
+ } else {
+ $item->addAttribute(
+ phabricator_datetime($epoch, $viewer));
}
$list->addItem($item);
diff --git a/src/applications/countdown/query/PhabricatorCountdownTransactionQuery.php b/src/applications/countdown/query/PhabricatorCountdownTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/countdown/query/PhabricatorCountdownTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorCountdownTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new PhabricatorCountdownTransaction();
+ }
+
+}
diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php
--- a/src/applications/countdown/storage/PhabricatorCountdown.php
+++ b/src/applications/countdown/storage/PhabricatorCountdown.php
@@ -4,12 +4,16 @@
implements
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
- PhabricatorSpacesInterface {
+ PhabricatorSubscribableInterface,
+ PhabricatorApplicationTransactionInterface,
+ PhabricatorSpacesInterface,
+ PhabricatorProjectInterface {
protected $title;
protected $authorPHID;
protected $epoch;
protected $viewPolicy;
+ protected $editPolicy;
protected $spacePHID;
@@ -43,6 +47,48 @@
PhabricatorCountdownCountdownPHIDType::TYPECONST);
}
+ public function getMonogram() {
+ return 'C'.$this->getID();
+ }
+
+
+/* -( PhabricatorSubscribableInterface )----------------------------------- */
+
+
+ public function isAutomaticallySubscribed($phid) {
+ return ($phid == $this->getAuthorPHID());
+ }
+
+ public function shouldShowSubscribersProperty() {
+ return true;
+ }
+
+ public function shouldAllowSubscription($phid) {
+ return true;
+ }
+
+/* -( PhabricatorApplicationTransactionInterface )------------------------- */
+
+
+ public function getApplicationTransactionEditor() {
+ return new PhabricatorCountdownEditor();
+ }
+
+ public function getApplicationTransactionObject() {
+ return $this;
+ }
+
+ public function getApplicationTransactionTemplate() {
+ return new PhabricatorCountdownTransaction();
+ }
+
+ public function willRenderTimeline(
+ PhabricatorApplicationTransactionView $timeline,
+ AphrontRequest $request) {
+
+ return $timeline;
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@@ -59,16 +105,16 @@
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
- return PhabricatorPolicies::POLICY_NOONE;
+ return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return ($viewer->getPHID() == $this->getAuthorPHID());
+ return false;
}
public function describeAutomaticCapability($capability) {
- return pht('The author of a countdown can always view and edit it.');
+ return false;
}
/* -( PhabricatorSpacesInterface )------------------------------------------- */
diff --git a/src/applications/countdown/storage/PhabricatorCountdownSchemaSpec.php b/src/applications/countdown/storage/PhabricatorCountdownSchemaSpec.php
new file mode 100644
--- /dev/null
+++ b/src/applications/countdown/storage/PhabricatorCountdownSchemaSpec.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorCountdownSchemaSpec
+ extends PhabricatorConfigSchemaSpec {
+
+ public function buildSchemata() {
+ $this->buildEdgeSchemata(new PhabricatorCountdown());
+ }
+
+}
diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php
@@ -0,0 +1,110 @@
+<?php
+
+final class PhabricatorCountdownTransaction
+ extends PhabricatorApplicationTransaction {
+
+ const TYPE_TITLE = 'countdown:title';
+ const TYPE_EPOCH = 'countdown:epoch';
+
+ const MAILTAG_TITLE = 'countdown:title';
+ const MAILTAG_EPOCH = 'countdown:epoch';
+ const MAILTAG_OTHER = 'countdown:other';
+
+ public function getApplicationName() {
+ return 'countdown';
+ }
+
+ public function getApplicationTransactionType() {
+ return PhabricatorCountdownCountdownPHIDType::TYPECONST;
+ }
+
+ public function getApplicationTransactionCommentObject() {
+ return null;
+ }
+
+ public function getTitle() {
+ $author_phid = $this->getAuthorPHID();
+ $object_phid = $this->getObjectPHID();
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $type = $this->getTransactionType();
+ switch ($type) {
+ case self::TYPE_TITLE:
+ if ($old === null) {
+ return pht(
+ '%s created this countdown.',
+ $this->renderHandleLink($author_phid));
+ } else {
+ return pht(
+ '%s renamed this countdown from "%s" to "%s".',
+ $this->renderHandleLink($author_phid),
+ $old,
+ $new);
+ }
+ case self::TYPE_EPOCH:
+ if ($old === null) {
+ return pht(
+ '%s set this countdown to end on %s.',
+ $this->renderHandleLink($author_phid),
+ phabricator_datetime($new, $this->getViewer()));
+ } else if ($old != $new) {
+ return pht(
+ '%s updated this countdown to end on %s.',
+ $this->renderHandleLink($author_phid),
+ phabricator_datetime($new, $this->getViewer()));
+ }
+ 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_TITLE:
+ 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;
+ }
+
+ return parent::getTitleForFeed();
+ }
+
+ public function getMailTags() {
+ $tags = parent::getMailTags();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_TITLE:
+ $tags[] = self::MAILTAG_TITLE;
+ break;
+ case self::TYPE_EPOCH:
+ $tags[] = self::MAILTAG_EPOCH;
+ break;
+ default:
+ $tags[] = self::MAILTAG_OTHER;
+ break;
+ }
+
+ return $tags;
+ }
+
+}

File Metadata

Mime Type
text/plain
Expires
Wed, Oct 23, 4:32 PM (2 w, 6 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/vw/2c/cqwd3whawxqly7fb
Default Alt Text
D13660.id33028.diff (39 KB)

Event Timeline