1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 12:30:56 +01:00

Convert Countdown to EditEngine

Summary: Fixes T10684. Fixes T10520. This primarily implements a date/epoch field, and then does a bunch of standard plumbing.

Test Plan:
  - Created countdowns.
  - Edited countdowns.
  - Used HTTP prefilling.
  - Created a countdown ending on "Christmas Morning", etc.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10520, T10684

Differential Revision: https://secure.phabricator.com/D15655
This commit is contained in:
epriestley 2016-04-07 11:06:26 -07:00
parent 1f423c3bd1
commit cdec319143
15 changed files with 366 additions and 456 deletions

View file

@ -139,6 +139,7 @@ phutil_register_library_map(array(
'AphrontDefaultApplicationConfiguration' => 'aphront/configuration/AphrontDefaultApplicationConfiguration.php',
'AphrontDialogResponse' => 'aphront/response/AphrontDialogResponse.php',
'AphrontDialogView' => 'view/AphrontDialogView.php',
'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php',
'AphrontException' => 'aphront/exception/AphrontException.php',
'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php',
'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php',
@ -2100,7 +2101,6 @@ phutil_register_library_map(array(
'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php',
'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php',
'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php',
'PhabricatorCountdownCommentController' => 'applications/countdown/controller/PhabricatorCountdownCommentController.php',
'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
'PhabricatorCountdownCountdownPHIDType' => 'applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php',
'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php',
@ -2108,6 +2108,7 @@ phutil_register_library_map(array(
'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php',
'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php',
'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php',
'PhabricatorCountdownEditEngine' => 'applications/countdown/editor/PhabricatorCountdownEditEngine.php',
'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php',
'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php',
'PhabricatorCountdownMailReceiver' => 'applications/countdown/mail/PhabricatorCountdownMailReceiver.php',
@ -2325,6 +2326,7 @@ phutil_register_library_map(array(
'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php',
'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php',
'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php',
'PhabricatorEpochEditField' => 'applications/transactions/editfield/PhabricatorEpochEditField.php',
'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php',
'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php',
'PhabricatorEventListener' => 'infrastructure/events/PhabricatorEventListener.php',
@ -4261,6 +4263,7 @@ phutil_register_library_map(array(
'AphrontView',
'AphrontResponseProducerInterface',
),
'AphrontEpochHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontException' => 'Exception',
'AphrontFileResponse' => 'AphrontResponse',
'AphrontFormCheckboxControl' => 'AphrontFormControl',
@ -6525,7 +6528,6 @@ phutil_register_library_map(array(
'PhabricatorProjectInterface',
),
'PhabricatorCountdownApplication' => 'PhabricatorApplication',
'PhabricatorCountdownCommentController' => 'PhabricatorCountdownController',
'PhabricatorCountdownController' => 'PhabricatorController',
'PhabricatorCountdownCountdownPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO',
@ -6533,6 +6535,7 @@ phutil_register_library_map(array(
'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController',
'PhabricatorCountdownEditController' => 'PhabricatorCountdownController',
'PhabricatorCountdownEditEngine' => 'PhabricatorEditEngine',
'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorCountdownListController' => 'PhabricatorCountdownController',
'PhabricatorCountdownMailReceiver' => 'PhabricatorObjectMailReceiver',
@ -6776,6 +6779,7 @@ phutil_register_library_map(array(
'PhabricatorEmptyQueryException' => 'Exception',
'PhabricatorEnv' => 'Phobject',
'PhabricatorEnvTestCase' => 'PhabricatorTestCase',
'PhabricatorEpochEditField' => 'PhabricatorEditField',
'PhabricatorEvent' => 'PhutilEvent',
'PhabricatorEventEngine' => 'Phobject',
'PhabricatorEventListener' => 'PhutilEventListener',

View file

@ -0,0 +1,37 @@
<?php
final class AphrontEpochHTTPParameterType
extends AphrontHTTPParameterType {
protected function getParameterExists(AphrontRequest $request, $key) {
return $request->getExists($key) ||
$request->getExists($key.'_d');
}
protected function getParameterValue(AphrontRequest $request, $key) {
return AphrontFormDateControlValue::newFromRequest($request, $key);
}
protected function getParameterTypeName() {
return 'epoch';
}
protected function getParameterFormatDescriptions() {
return array(
pht('An epoch timestamp, as an integer.'),
pht('An absolute date, as a string.'),
pht('A relative date, as a string.'),
pht('Separate date and time inputs, as strings.'),
);
}
protected function getParameterExamples() {
return array(
'v=1460050737',
'v=2022-01-01',
'v=yesterday',
'v_d=2022-01-01&v_t=12:34',
);
}
}

View file

@ -46,9 +46,7 @@ final class PhabricatorCountdownApplication extends PhabricatorApplication {
=> 'PhabricatorCountdownViewController',
'comment/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCountdownCommentController',
'edit/(?:(?P<id>[1-9]\d*)/)?'
=> 'PhabricatorCountdownEditController',
'create/'
$this->getEditRoutePattern('edit/')
=> 'PhabricatorCountdownEditController',
'delete/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCountdownDeleteController',

View file

@ -1,63 +0,0 @@
<?php
final class PhabricatorCountdownCommentController
extends PhabricatorCountdownController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if (!$request->isFormPost()) {
return new Aphront400Response();
}
$countdown = id(new PhabricatorCountdownQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$countdown) {
return new Aphront404Response();
}
$is_preview = $request->isPreviewRequest();
$draft = PhabricatorDraft::buildFromRequest($request);
$view_uri = '/'.$countdown->getMonogram();
$xactions = array();
$xactions[] = id(new PhabricatorCountdownTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new PhabricatorCountdownTransactionComment())
->setContent($request->getStr('comment')));
$editor = id(new PhabricatorCountdownEditor())
->setActor($viewer)
->setContinueOnNoEffect($request->isContinueRequest())
->setContentSourceFromRequest($request)
->setIsPreview($is_preview);
try {
$xactions = $editor->applyTransactions($countdown, $xactions);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($view_uri)
->setException($ex);
}
if ($draft) {
$draft->replaceOrDelete();
}
if ($request->isAjax() && $is_preview) {
return id(new PhabricatorApplicationTransactionResponse())
->setViewer($viewer)
->setTransactions($xactions)
->setIsPreview($is_preview);
} else {
return id(new AphrontRedirectResponse())
->setURI($view_uri);
}
}
}

View file

@ -7,16 +7,5 @@ abstract class PhabricatorCountdownController extends PhabricatorController {
->setSearchEngine(new PhabricatorCountdownSearchEngine());
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Countdown'))
->setHref($this->getApplicationURI('create/'))
->setIcon('fa-plus-square'));
return $crumbs;
}
}

View file

@ -4,205 +4,9 @@ final class PhabricatorCountdownEditController
extends PhabricatorCountdownController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if ($id) {
$countdown = id(new PhabricatorCountdownQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$countdown) {
return new Aphront404Response();
}
$date_value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
$countdown->getEpoch());
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
$countdown->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
$v_projects = array_reverse($v_projects);
$title = pht('Edit Countdown: %s', $countdown->getTitle());
} else {
$title = pht('Create Countdown');
$countdown = PhabricatorCountdown::initializeNewCountdown($viewer);
$date_value = AphrontFormDateControlValue::newFromEpoch(
$viewer, PhabricatorTime::getNow());
$v_projects = array();
}
$errors = array();
$e_text = true;
$e_epoch = null;
$v_text = $countdown->getTitle();
$v_desc = $countdown->getDescription();
$v_space = $countdown->getSpacePHID();
$v_view = $countdown->getViewPolicy();
$v_edit = $countdown->getEditPolicy();
if ($request->isFormPost()) {
$v_text = $request->getStr('title');
$v_desc = $request->getStr('description');
$v_space = $request->getStr('spacePHID');
$date_value = AphrontFormDateControlValue::newFromRequest(
$request,
'epoch');
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$v_projects = $request->getArr('projects');
$type_title = PhabricatorCountdownTransaction::TYPE_TITLE;
$type_epoch = PhabricatorCountdownTransaction::TYPE_EPOCH;
$type_description = PhabricatorCountdownTransaction::TYPE_DESCRIPTION;
$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_description)
->setNewValue($v_desc);
$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);
return id(new AphrontRedirectResponse())
->setURI('/'.$countdown->getMonogram());
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_title = $ex->getShortMessage($type_title);
$e_epoch = $ex->getShortMessage($type_epoch);
}
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
$cancel_uri = '/countdown/';
if ($countdown->getID()) {
$cancel_uri = '/countdown/'.$countdown->getID().'/';
$crumbs->addTextCrumb('C'.$countdown->getID(), $cancel_uri);
$crumbs->addTextCrumb(pht('Edit'));
$submit_label = pht('Save Changes');
$header_icon = 'fa-pencil';
} else {
$crumbs->addTextCrumb(pht('Create Countdown'));
$submit_label = pht('Create Countdown');
$header_icon = 'fa-plus-square';
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($countdown)
->execute();
$form = id(new AphrontFormView())
->setUser($viewer)
->setAction($request->getRequestURI()->getPath())
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Title'))
->setValue($v_text)
->setName('title')
->setError($e_text))
->appendControl(
id(new AphrontFormDateControl())
->setName('epoch')
->setLabel(pht('End Date'))
->setError($e_epoch)
->setValue($date_value))
->appendControl(
id(new PhabricatorRemarkupControl())
->setName('description')
->setLabel(pht('Description'))
->setValue($v_desc))
->appendControl(
id(new AphrontFormPolicyControl())
->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)
->setValue($submit_label));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Countdown'))
->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon($header_icon);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($form_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$view,
));
return id(new PhabricatorCountdownEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -13,4 +13,14 @@ final class PhabricatorCountdownListController
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
id(new PhabricatorCountdownEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}
}

View file

@ -55,12 +55,15 @@ final class PhabricatorCountdownViewController
$timeline = $this->buildTransactionTimeline(
$countdown,
new PhabricatorCountdownTransactionQuery());
$add_comment = $this->buildCommentForm($countdown);
$comment_view = id(new PhabricatorCountdownEditEngine())
->setViewer($viewer)
->buildEditEngineCommentView($countdown);
$content = array(
$countdown_view,
$timeline,
$add_comment,
$comment_view,
);
$view = id(new PHUITwoColumnView())
@ -135,25 +138,4 @@ final class PhabricatorCountdownViewController
->setContent($content);
}
private function buildCommentForm(PhabricatorCountdown $countdown) {
$viewer = $this->getViewer();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$add_comment_header = $is_serious
? pht('Add Comment')
: pht('Last Words');
$draft = PhabricatorDraft::newFromUserAndKey(
$viewer, $countdown->getPHID());
return id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($countdown->getPHID())
->setDraft($draft)
->setHeaderText($add_comment_header)
->setAction($this->getApplicationURI('/comment/'.$countdown->getID().'/'))
->setSubmitButtonName(pht('Add Comment'));
}
}

View file

@ -0,0 +1,108 @@
<?php
final class PhabricatorCountdownEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'countdown.countdown';
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Countdowns');
}
public function getSummaryHeader() {
return pht('Edit Countdowns');
}
public function getSummaryText() {
return pht('Creates and edits countdowns.');
}
public function getEngineApplicationClass() {
return 'PhabricatorCountdownApplication';
}
protected function newEditableObject() {
return PhabricatorCountdown::initializeNewCountdown(
$this->getViewer());
}
protected function newObjectQuery() {
return id(new PhabricatorCountdownQuery());
}
protected function getObjectCreateTitleText($object) {
return pht('Create Countdown');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Countdown');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Countdown: %s', $object->getTitle());
}
protected function getObjectEditShortText($object) {
return pht('Edit Countdown');
}
protected function getObjectCreateShortText() {
return pht('Create Countdown');
}
protected function getObjectName() {
return pht('Countdown');
}
protected function getCommentViewHeaderText($object) {
return pht('Last Words');
}
protected function getCommentViewButtonText($object) {
return pht('Contemplate Infinity');
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function buildCustomEditFields($object) {
$epoch_value = $object->getEpoch();
if ($epoch_value === null) {
$epoch_value = PhabricatorTime::getNow();
}
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setIsRequired(true)
->setTransactionType(PhabricatorCountdownTransaction::TYPE_TITLE)
->setDescription(pht('The countdown name.'))
->setConduitDescription(pht('Rename the countdown.'))
->setConduitTypeDescription(pht('New countdown name.'))
->setValue($object->getTitle()),
id(new PhabricatorEpochEditField())
->setKey('epoch')
->setLabel(pht('End Date'))
->setTransactionType(PhabricatorCountdownTransaction::TYPE_EPOCH)
->setDescription(pht('Date when the countdown ends.'))
->setConduitDescription(pht('Change the end date of the countdown.'))
->setConduitTypeDescription(pht('New countdown end date.'))
->setValue($epoch_value),
id(new PhabricatorRemarkupEditField())
->setKey('description')
->setLabel(pht('Description'))
->setTransactionType(PhabricatorCountdownTransaction::TYPE_DESCRIPTION)
->setDescription(pht('Description of the countdown.'))
->setConduitDescription(pht('Change the countdown description.'))
->setConduitTypeDescription(pht('New description.'))
->setValue($object->getDescription()),
);
}
}

View file

@ -120,19 +120,27 @@ final class PhabricatorCountdownEditor
}
break;
case PhabricatorCountdownTransaction::TYPE_EPOCH:
$date_value = AphrontFormDateControlValue::newFromEpoch(
$this->requireActor(),
$object->getEpoch());
if (!$date_value->isValid()) {
if (!$object->getEpoch() && !$xactions) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('You must give the countdown a valid end date.'),
nonempty(last($xactions), null));
pht('Required'),
pht('You must give the countdown an end date.'),
null);
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if (!$value->isValid()) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('You must give the countdown a valid end date.'),
$xaction);
$errors[] = $error;
}
}
break;
}

View file

@ -28,10 +28,13 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO
$view_policy = $app->getPolicy(
PhabricatorCountdownDefaultViewCapability::CAPABILITY);
$edit_policy = $app->getPolicy(
PhabricatorCountdownDefaultEditCapability::CAPABILITY);
return id(new PhabricatorCountdown())
->setAuthorPHID($actor->getPHID())
->setViewPolicy($view_policy)
->setEpoch(PhabricatorTime::getNow())
->setEditPolicy($edit_policy)
->setSpacePHID($actor->getDefaultSpacePHID());
}
@ -55,6 +58,10 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO
return 'C'.$this->getID();
}
public function getURI() {
return '/'.$this->getMonogram();
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));

View file

@ -33,42 +33,20 @@ final class PhabricatorCountdownTransaction
$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);
}
break;
return pht(
'%s renamed this countdown from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
case self::TYPE_DESCRIPTION:
if ($old === null) {
return pht(
'%s set the description of this countdown.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s edited the description of this countdown.',
$this->renderHandleLink($author_phid));
}
break;
return pht(
'%s edited the description of this countdown.',
$this->renderHandleLink($author_phid));
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 pht(
'%s updated this countdown to end on %s.',
$this->renderHandleLink($author_phid),
phabricator_datetime($new, $this->getViewer()));
}
return parent::getTitle();
@ -84,47 +62,20 @@ final class PhabricatorCountdownTransaction
$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 pht(
'%s renamed %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_DESCRIPTION:
if ($old === null) {
return pht(
'%s set the description of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s edited the description of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
return pht(
'%s edited the description of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_EPOCH:
if ($old === null) {
return pht(
'%s set the end date of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s edited the end date of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
return pht(
'%s edited the end date of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
return parent::getTitleForFeed();
@ -150,15 +101,6 @@ final class PhabricatorCountdownTransaction
return $tags;
}
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:

View file

@ -0,0 +1,21 @@
<?php
final class PhabricatorEpochEditField
extends PhabricatorEditField {
protected function newControl() {
return id(new AphrontFormDateControl())
->setViewer($this->getViewer());
}
protected function newHTTPParameterType() {
return new AphrontEpochHTTPParameterType();
}
protected function newConduitParameterType() {
// TODO: This isn't correct, but we don't have any methods which use this
// yet.
return new ConduitIntParameterType();
}
}

View file

@ -130,10 +130,13 @@ final class AphrontFormDateControl extends AphrontFormControl {
$date_format = $this->getDateFormat();
$timezone = $this->getTimezone();
$datetime = new DateTime($this->valueDate, $timezone);
$date = $datetime->format($date_format);
try {
$datetime = new DateTime($this->valueDate, $timezone);
} catch (Exception $ex) {
return $this->valueDate;
}
return $date;
return $datetime->format($date_format);
}
private function getTimeFormat() {

View file

@ -84,10 +84,33 @@ final class AphrontFormDateControlValue extends Phobject {
$value = new AphrontFormDateControlValue();
$value->viewer = $request->getViewer();
list($value->valueDate, $value->valueTime) =
$value->getFormattedDateFromDate(
$request->getStr($key.'_d'),
$request->getStr($key.'_t'));
$datetime = $request->getStr($key);
if (strlen($datetime)) {
$date = $datetime;
$time = null;
} else {
$date = $request->getStr($key.'_d');
$time = $request->getStr($key.'_t');
}
// If this looks like an epoch timestamp, prefix it with "@" so that
// DateTime() reads it as one. Assume small numbers are a "Ymd" digit
// string instead of an epoch timestamp for a time in 1970.
if (ctype_digit($date) && ($date > 30000000)) {
$date = '@'.$date;
$time = null;
}
$value->valueDate = $date;
$value->valueTime = $time;
$formatted = $value->getFormattedDateFromDate(
$value->valueDate,
$value->valueTime);
if ($formatted) {
list($value->valueDate, $value->valueTime) = $formatted;
}
$value->valueEnabled = $request->getStr($key.'_e');
return $value;
@ -96,6 +119,11 @@ final class AphrontFormDateControlValue extends Phobject {
public static function newFromEpoch(PhabricatorUser $viewer, $epoch) {
$value = new AphrontFormDateControlValue();
$value->viewer = $viewer;
if (!$epoch) {
return $value;
}
$readable = $value->formatTime($epoch, 'Y!m!d!g:i A');
$readable = explode('!', $readable, 4);
@ -120,10 +148,16 @@ final class AphrontFormDateControlValue extends Phobject {
$value = new AphrontFormDateControlValue();
$value->viewer = $viewer;
list($value->valueDate, $value->valueTime) =
$value->getFormattedDateFromDate(
idx($dictionary, 'd'),
idx($dictionary, 't'));
$value->valueDate = idx($dictionary, 'd');
$value->valueTime = idx($dictionary, 't');
$formatted = $value->getFormattedDateFromDate(
$value->valueDate,
$value->valueTime);
if ($formatted) {
list($value->valueDate, $value->valueTime) = $formatted;
}
$value->valueEnabled = idx($dictionary, 'e');
@ -170,37 +204,12 @@ final class AphrontFormDateControlValue extends Phobject {
return null;
}
$date = $this->valueDate;
$time = $this->valueTime;
$zone = $this->getTimezone();
if (!strlen($time)) {
$datetime = $this->newDateTime($this->valueDate, $this->valueTime);
if (!$datetime) {
return null;
}
$colloquial = array(
'elevenses' => '11:00 AM',
'morning tea' => '11:00 AM',
'noon' => '12:00 PM',
'high noon' => '12:00 PM',
'lunch' => '12:00 PM',
'tea time' => '3:00 PM',
'witching hour' => '12:00 AM',
'midnight' => '12:00 AM',
);
$normalized = phutil_utf8_strtolower($time);
if (isset($colloquial[$normalized])) {
$time = $colloquial[$normalized];
}
try {
$datetime = new DateTime("{$date} {$time}", $zone);
$value = $datetime->format('U');
} catch (Exception $ex) {
$value = null;
}
return $value;
return $datetime->format('U');
}
private function getTimeFormat() {
@ -214,25 +223,34 @@ final class AphrontFormDateControlValue extends Phobject {
}
private function getFormattedDateFromDate($date, $time) {
$original_input = $date;
$zone = $this->getTimezone();
$separator = $this->getFormatSeparator();
$parts = preg_split('@[,./:-]@', $date);
$date = implode($separator, $parts);
$date = id(new DateTime($date, $zone));
if ($date) {
$date = $date->format($this->getDateFormat());
} else {
$date = $original_input;
$datetime = $this->newDateTime($date, $time);
if (!$datetime) {
return null;
}
$date = id(new DateTime("{$date} {$time}", $zone));
return array(
$date->format($this->getDateFormat()),
$date->format($this->getTimeFormat()),
$datetime->format($this->getDateFormat()),
$datetime->format($this->getTimeFormat()),
);
return array($date, $time);
}
private function newDateTime($date, $time) {
$date = $this->getStandardDateFormat($date);
$time = $this->getStandardTimeFormat($time);
try {
$datetime = new DateTime("{$date} {$time}");
} catch (Exception $ex) {
return null;
}
// Set the timezone explicitly because it is ignored in the constructor
// if the date is an epoch timestamp.
$zone = $this->getTimezone();
$datetime->setTimezone($zone);
return $datetime;
}
private function getFormattedDateFromParts(
@ -261,16 +279,7 @@ final class AphrontFormDateControlValue extends Phobject {
}
public function getDateTime() {
$epoch = $this->getEpoch();
$date = null;
if ($epoch) {
$zone = $this->getTimezone();
$date = new DateTime('@'.$epoch);
$date->setTimeZone($zone);
}
return $date;
return $this->newDateTime();
}
private function getTimezone() {
@ -283,5 +292,56 @@ final class AphrontFormDateControlValue extends Phobject {
return $this->zone;
}
private function getStandardDateFormat($date) {
$colloquial = array(
'newyear' => 'January 1',
'valentine' => 'February 14',
'pi' => 'March 14',
'christma' => 'December 25',
);
// Lowercase the input, then remove punctuation, a "day" suffix, and an
// "s" if one is present. This allows all of these to match. This allows
// variations like "New Year's Day" and "New Year" to both match.
$normalized = phutil_utf8_strtolower($date);
$normalized = preg_replace('/[^a-z]/', '', $normalized);
$normalized = preg_replace('/day\z/', '', $normalized);
$normalized = preg_replace('/s\z/', '', $normalized);
if (isset($colloquial[$normalized])) {
return $colloquial[$normalized];
}
$separator = $this->getFormatSeparator();
$parts = preg_split('@[,./:-]@', $date);
return implode($separator, $parts);
}
private function getStandardTimeFormat($time) {
$colloquial = array(
'crack of dawn' => '5:00 AM',
'dawn' => '6:00 AM',
'early' => '7:00 AM',
'morning' => '8:00 AM',
'elevenses' => '11:00 AM',
'morning tea' => '11:00 AM',
'noon' => '12:00 PM',
'high noon' => '12:00 PM',
'lunch' => '12:00 PM',
'afternoon' => '2:00 PM',
'tea time' => '3:00 PM',
'evening' => '7:00 PM',
'late' => '11:00 PM',
'witching hour' => '12:00 AM',
'midnight' => '12:00 AM',
);
$normalized = phutil_utf8_strtolower($time);
if (isset($colloquial[$normalized])) {
$time = $colloquial[$normalized];
}
return $time;
}
}