From cdec31914390852c46ccc411fc64ec29a71589c4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Apr 2016 11:06:26 -0700 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 8 +- .../AphrontEpochHTTPParameterType.php | 37 ++++ .../PhabricatorCountdownApplication.php | 4 +- .../PhabricatorCountdownCommentController.php | 63 ------ .../PhabricatorCountdownController.php | 11 - .../PhabricatorCountdownEditController.php | 202 +----------------- .../PhabricatorCountdownListController.php | 10 + .../PhabricatorCountdownViewController.php | 28 +-- .../editor/PhabricatorCountdownEditEngine.php | 108 ++++++++++ .../editor/PhabricatorCountdownEditor.php | 24 ++- .../storage/PhabricatorCountdown.php | 9 +- .../PhabricatorCountdownTransaction.php | 106 +++------ .../editfield/PhabricatorEpochEditField.php | 21 ++ .../form/control/AphrontFormDateControl.php | 9 +- .../control/AphrontFormDateControlValue.php | 182 ++++++++++------ 15 files changed, 366 insertions(+), 456 deletions(-) create mode 100644 src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php delete mode 100644 src/applications/countdown/controller/PhabricatorCountdownCommentController.php create mode 100644 src/applications/countdown/editor/PhabricatorCountdownEditEngine.php create mode 100644 src/applications/transactions/editfield/PhabricatorEpochEditField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f6f7cf1865..596901a360 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php new file mode 100644 index 0000000000..f1932bd872 --- /dev/null +++ b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php @@ -0,0 +1,37 @@ +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', + ); + } + +} diff --git a/src/applications/countdown/application/PhabricatorCountdownApplication.php b/src/applications/countdown/application/PhabricatorCountdownApplication.php index d6c62d7e1c..a446c88a88 100644 --- a/src/applications/countdown/application/PhabricatorCountdownApplication.php +++ b/src/applications/countdown/application/PhabricatorCountdownApplication.php @@ -46,9 +46,7 @@ final class PhabricatorCountdownApplication extends PhabricatorApplication { => 'PhabricatorCountdownViewController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorCountdownCommentController', - 'edit/(?:(?P[1-9]\d*)/)?' - => 'PhabricatorCountdownEditController', - 'create/' + $this->getEditRoutePattern('edit/') => 'PhabricatorCountdownEditController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorCountdownDeleteController', diff --git a/src/applications/countdown/controller/PhabricatorCountdownCommentController.php b/src/applications/countdown/controller/PhabricatorCountdownCommentController.php deleted file mode 100644 index 03b2001289..0000000000 --- a/src/applications/countdown/controller/PhabricatorCountdownCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -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); - } - } - -} diff --git a/src/applications/countdown/controller/PhabricatorCountdownController.php b/src/applications/countdown/controller/PhabricatorCountdownController.php index 37b0e49a68..4d09d7ed5b 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownController.php @@ -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; - } } diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php index ea89c1591b..7bdd4236b7 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php @@ -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(); } } diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php index 382f1a0306..b06cfd0e7f 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownListController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php @@ -13,4 +13,14 @@ final class PhabricatorCountdownListController ->buildResponse(); } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorCountdownEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + } diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 6e259df555..56911bf43b 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -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')); - } - } diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php new file mode 100644 index 0000000000..c1d5f6753a --- /dev/null +++ b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php @@ -0,0 +1,108 @@ +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()), + ); + } + +} diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index e1eddf2270..37f23f6bd6 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -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; } diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php index 52a395f0c2..1a985b0b83 100644 --- a/src/applications/countdown/storage/PhabricatorCountdown.php +++ b/src/applications/countdown/storage/PhabricatorCountdown.php @@ -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)); diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php index 72303a369f..247466ffe1 100644 --- a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php +++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php @@ -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: diff --git a/src/applications/transactions/editfield/PhabricatorEpochEditField.php b/src/applications/transactions/editfield/PhabricatorEpochEditField.php new file mode 100644 index 0000000000..c5dabe6171 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorEpochEditField.php @@ -0,0 +1,21 @@ +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(); + } + +} diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index 25dec8eef0..38420748d8 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -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() { diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index f533bcaddb..114340006e 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -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; + } }