From dea952aa48fc14de2a14345a781b697993da137e Mon Sep 17 00:00:00 2001 From: Ben Schmidt Date: Fri, 8 Jul 2016 23:08:51 +1000 Subject: [PATCH 01/76] Make links to hidden comments work (Fixes T11117) --- resources/celerity/map.php | 18 +++++++++--------- .../behavior-show-older-transactions.js | 2 ++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5763349c0e..6dcf3f3c9a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '4e7e9bde', - 'core.pkg.js' => '1bcca0f3', + 'core.pkg.js' => '13c7e56a', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3e81ae60', 'differential.pkg.js' => '634399e9', @@ -439,7 +439,7 @@ return array( 'rsrc/js/application/transactions/behavior-comment-actions.js' => '06460e71', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', - 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6', + 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '94c65b72', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '13c739ea', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', @@ -663,7 +663,7 @@ return array( 'javelin-behavior-phabricator-remarkup-assist' => '116cf19b', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '06c32383', - 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', + 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', 'javelin-behavior-phabricator-tooltips' => '42fcb747', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', 'javelin-behavior-phabricator-transaction-list' => '13c739ea', @@ -1655,6 +1655,12 @@ return array( 'javelin-resource', 'javelin-routable', ), + '94c65b72' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-busy', + ), '988040b4' => array( 'javelin-install', 'javelin-dom', @@ -2014,12 +2020,6 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), - 'dbbf48b6' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-busy', - ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js index 5fe1caabea..b4dcd2975b 100644 --- a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js +++ b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js @@ -97,6 +97,8 @@ JX.behavior('phabricator-show-older-transactions', function(config) { JX.Router.getInstance().queue(routable); }); + JX.Stratcom.listen('hashchange', null, check_hash); + check_hash(); new JX.KeyboardShortcut(['@'], 'Show all older changes in the timeline.') From a5b26104f6ebd31fa4fd9965a6fde01093766edb Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 Jul 2016 05:55:45 -0700 Subject: [PATCH 02/76] Fix an issue with creating new Repository URIs via the Web UI Summary I broke this in D16237: that made the CLI workflow work, but we attach the repository earlier in the web workflow and won't have one when we arrive here. Test Plan: Created a new repository URI from the web UI. Auditors: chad --- .../diffusion/editor/DiffusionURIEditor.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 2ff0854a89..0b7096db3c 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -78,10 +78,12 @@ final class DiffusionURIEditor } else { $old_uri = null; - // When creating a URI, we may not have processed the repository - // transaction yet. Attach the repository here to make sure we - // have it for the calls below. - $object->attachRepository($this->repository); + // When creating a URI via the API, we may not have processed the + // repository transaction yet. Attach the repository here to make + // sure we have it for the calls below. + if ($this->repository) { + $object->attachRepository($this->repository); + } } $object->setURI($xaction->getNewValue()); From e2d195e03ab074844a499a448946ebf79e37476f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 9 Jul 2016 13:53:57 -0700 Subject: [PATCH 03/76] Fix mobile menu for files in Differential Changesets Summary: Fixes T11305, Ref T7754. Makes this menu dropdown act like actions and collapse to a fa-bars menu. Test Plan: View on mobile, desktop, browser. Click an action, spawn new page. {F1717953} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T7754, T11305 Differential Revision: https://secure.phabricator.com/D16265 --- resources/celerity/map.php | 12 +++++------ .../view/DifferentialChangesetListView.php | 20 +++++++++---------- .../differential/changeset-view.css | 9 +++------ 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6dcf3f3c9a..0aa02f6894 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'core.pkg.css' => '4e7e9bde', 'core.pkg.js' => '13c7e56a', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => '3e81ae60', + 'differential.pkg.css' => '3fb7f532', 'differential.pkg.js' => '634399e9', 'diffusion.pkg.css' => '91c5d3a6', 'diffusion.pkg.js' => '84c8f8fd', @@ -57,7 +57,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '37792573', + 'rsrc/css/application/differential/changeset-view.css' => '9ef7d354', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -555,7 +555,7 @@ return array( 'conpherence-update-css' => 'faf6be09', 'conpherence-widget-pane-css' => '775eaaba', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '37792573', + 'differential-changeset-view-css' => '9ef7d354', 'differential-core-view-css' => '5b7b8ff4', 'differential-inline-comment-editor' => '64a5550f', 'differential-revision-add-comment-css' => 'c47f8c40', @@ -1143,9 +1143,6 @@ return array( 'javelin-dom', 'javelin-workflow', ), - 37792573 => array( - 'phui-inline-comment-view-css', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1678,6 +1675,9 @@ return array( 'phabricator-phtize', 'changeset-view-manager', ), + '9ef7d354' => array( + 'phui-inline-comment-view-css', + ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index f0c611c609..533f579b0a 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -342,18 +342,16 @@ final class DifferentialChangesetListView extends AphrontView { } $meta['containerID'] = $detail->getID(); - $caret = phutil_tag('span', array('class' => 'caret'), ''); - return javelin_tag( - 'a', - array( - 'class' => 'button grey dropdown', - 'meta' => $meta, - 'href' => idx($meta, 'detailURI', '#'), - 'target' => '_blank', - 'sigil' => 'differential-view-options', - ), - array(pht('View Options'), $caret)); + return id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View Options')) + ->setIcon('fa-bars') + ->setColor(PHUIButtonView::GREY) + ->setHref(idx($meta, 'detailURI', '#')) + ->setMetadata($meta) + ->addSigil('differential-view-options'); + } } diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 2cedb39db5..9474553f28 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -348,12 +348,9 @@ td.cov-I { margin-right: 12px; } -.device-phone .differential-changeset-buttons { - float: none; - margin: 0 0 8px 4px; -} - -.differential-changeset-buttons a.button { +.device-phone .differential-changeset-buttons .button .phui-button-text { + visibility: hidden; + width: 0; margin-left: 8px; } From d44a5fa93374d6ed0590d819fb355fd0ecef2531 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 10 Jul 2016 07:19:47 -0700 Subject: [PATCH 04/76] In Git, only use "--find-copies-harder" on small diffs Summary: Ref T10423. This flag can cause `git diff` to take an enormously long time (the problem case was a 5M line, 20K file commit). Instead: - Run without the flag first. - If that shows that the diff is definitely small, try again with the flag. - If that works, return the slower, better output. - If the fast diff affects too many paths or generating the slow diff takes too long, return the faster, slightly worse output. The quality of the output differs in how well Git is able to detect "M" and "C" (moves and copies of files). For example, if you copy `src/` to `srcpro/`, the fast output may not show that you copied files. The slow output will. I think this is rarely useful for large copies anyway: it's interesting if a 1-2 file diff is a copy, but usually obvious/uninteresting if a 500-file diff is a copy. Test Plan: - Ran `bin/repository reparse --change rXnnn` on Git changes. - Saw fast and slow commands execute normally. - Tried on a large diff, saw only the fast command execute. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10423 Differential Revision: https://secure.phabricator.com/D16266 --- ...nternalGitRawDiffQueryConduitAPIMethod.php | 71 ++++++++++++++++--- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php index 1e4c906322..ac241a282f 100644 --- a/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php @@ -28,6 +28,7 @@ final class DiffusionInternalGitRawDiffQueryConduitAPIMethod protected function getResult(ConduitAPIRequest $request) { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + $commit = $request->getValue('commit'); if (!$repository->isGit()) { throw new Exception( @@ -44,30 +45,78 @@ final class DiffusionInternalGitRawDiffQueryConduitAPIMethod list($parents) = $repository->execxLocalCommand( 'log -n1 --format=%s %s', '%P', - $request->getValue('commit')); - + $commit); $use_log = !strlen(trim($parents)); + + // First, get a fast raw diff without "--find-copies-harder". This flag + // produces better results for moves and copies, but is explosively slow + // for large changes to large repositories. See T10423. + $raw = $this->getRawDiff($repository, $commit, $use_log, false); + + // If we got a normal-sized diff (no more than 100 modified files), we'll + // try using "--find-copies-harder" to improve the output. This improved + // output is mostly useful for small changes anyway. + $try_harder = (substr_count($raw, "\n") <= 100); + if ($try_harder) { + try { + $raw = $this->getRawDiff($repository, $commit, $use_log, true); + } catch (Exception $ex) { + // Just ignore any exception we hit, we'll use the fast output + // instead. + } + } + + return $raw; + } + + private function getRawDiff( + PhabricatorRepository $repository, + $commit, + $use_log, + $try_harder) { + + $flags = array( + '-n1', + '-M', + '-C', + '-B', + '--raw', + '-t', + '--abbrev=40', + ); + + if ($try_harder) { + $flags[] = '--find-copies-harder'; + } + if ($use_log) { // This is the first commit so we need to use "log". We know it's not a // merge commit because it couldn't be merging anything, so this is safe. // NOTE: "--pretty=format: " is to disable diff output, we only want the // part we get from "--raw". - list($raw) = $repository->execxLocalCommand( - 'log -n1 -M -C -B --find-copies-harder --raw -t '. - '--pretty=format: --abbrev=40 %s', - $request->getValue('commit')); + $future = $repository->getLocalCommandFuture( + 'log %Ls --pretty=format: %s', + $flags, + $commit); } else { // Otherwise, we can use "diff", which will give us output for merges. // We diff against the first parent, as this is generally the expectation // and results in sensible behavior. - list($raw) = $repository->execxLocalCommand( - 'diff -n1 -M -C -B --find-copies-harder --raw -t '. - '--abbrev=40 %s^1 %s', - $request->getValue('commit'), - $request->getValue('commit')); + $future = $repository->getLocalCommandFuture( + 'diff %Ls %s^1 %s', + $flags, + $commit, + $commit); } + // Don't spend more than 30 seconds generating the slower output. + if ($try_harder) { + $future->setTimeout(30); + } + + list($raw) = $future->resolvex(); + return $raw; } From c21be4849f8d79e5957ce59ea90a915fb8e15a11 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 Jul 2016 06:47:32 -0700 Subject: [PATCH 05/76] By default, do not save queries when executing Conduit "*.search" calls Summary: Fixes T11304. Prior to this change, we did an unnecessary write on every "*.search" call (this write didn't always actually write a row, since we only save //unique// saved queries, but still doesn't do anything useful ever, currently). Instead, change this to not-write by default. We could add an "oh, and also I want you to do a write" option later, which would let us implement something like `arc query-stuff` which says "To see more results, view this URI in your browser: ...". (It's possible to run one of these methods with an existing SavedQuery by using the key, so we still sometimes have a queryKey to return.) Test Plan: Ran `almanac.service.search`, used DarkConsole to verify that no serachengine writes occurred. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11304 Differential Revision: https://secure.phabricator.com/D16263 --- .../search/engine/PhabricatorApplicationSearchEngine.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 96363ed947..d7268facb3 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1160,7 +1160,11 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $saved_query->setParameter($field->getKey(), $value); } - $this->saveQuery($saved_query); + // NOTE: Currently, when running an ad-hoc query we never persist it into + // a saved query. We might want to add an option to do this in the future + // (for example, to enable a CLI-to-Web workflow where user can view more + // details about results by following a link), but have no use cases for + // it today. If we do identify a use case, we could save the query here. $query = $this->buildQueryFromSavedQuery($saved_query); $pager = $this->newPagerForSavedQuery($saved_query); @@ -1234,6 +1238,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { 'data' => $data, 'maps' => $method->getQueryMaps($query), 'query' => array( + // This may be `null` if we have not saved the query. 'queryKey' => $saved_query->getQueryKey(), ), 'cursor' => array( From c510c925cf879259c1c8163c76a102396e08e9c9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Jul 2016 07:18:41 -0700 Subject: [PATCH 06/76] Allow worker tasks to be cancelled by classname Summary: Ref T3554. Makes `bin/worker cancel --class ` work (cancel all tasks with that type). This is useful in development if your queue is full of a bunch of gunk, and a need has occasionally arisen in production environments (usually "one option is cancel everything and move on"). Test Plan: Ran `bin/worker cancel` to cancel blocks of tasks by class name. Reviewers: chad Reviewed By: chad Maniphest Tasks: T3554 Differential Revision: https://secure.phabricator.com/D16267 --- .../PhabricatorWorkerManagementWorkflow.php | 48 ++++++++++++++----- .../PhabricatorWorkerArchiveTaskQuery.php | 17 ++++++- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php index be2408e54a..01fa18b188 100644 --- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php @@ -11,31 +11,57 @@ abstract class PhabricatorWorkerManagementWorkflow 'repeat' => true, 'help' => pht('Select one or more tasks by ID.'), ), + array( + 'name' => 'class', + 'param' => 'name', + 'help' => pht('Select all tasks of a given class.'), + ), ); } protected function loadTasks(PhutilArgumentParser $args) { $ids = $args->getArg('id'); - if (!$ids) { + $class = $args->getArg('class'); + + if (!$ids && !$class) { throw new PhutilArgumentUsageException( - pht('Use --id to select tasks by ID.')); + pht('Use --id or --class to select tasks.')); + } if ($ids && $class) { + throw new PhutilArgumentUsageException( + pht('Use one of --id or --class to select tasks, but not both.')); } - $active_tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( - 'id IN (%Ls)', - $ids); - $archive_tasks = id(new PhabricatorWorkerArchiveTaskQuery()) - ->withIDs($ids) - ->execute(); + if ($ids) { + $active_tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( + 'id IN (%Ls)', + $ids); + $archive_tasks = id(new PhabricatorWorkerArchiveTaskQuery()) + ->withIDs($ids) + ->execute(); + } else { + $active_tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( + 'taskClass IN (%Ls)', + array($class)); + $archive_tasks = id(new PhabricatorWorkerArchiveTaskQuery()) + ->withClassNames(array($class)) + ->execute(); + } $tasks = mpull($active_tasks, null, 'getID') + mpull($archive_tasks, null, 'getID'); - foreach ($ids as $id) { - if (empty($tasks[$id])) { + if ($ids) { + foreach ($ids as $id) { + if (empty($tasks[$id])) { + throw new PhutilArgumentUsageException( + pht('No task exists with id "%s"!', $id)); + } + } + } else { + if (!$tasks) { throw new PhutilArgumentUsageException( - pht('No task exists with id "%s"!', $id)); + pht('No task exists with class "%s"!', $class)); } } diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php index b0aa6addd3..69abac27b0 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php @@ -7,6 +7,7 @@ final class PhabricatorWorkerArchiveTaskQuery private $dateModifiedSince; private $dateCreatedBefore; private $objectPHIDs; + private $classNames; private $limit; public function withIDs(array $ids) { @@ -29,6 +30,11 @@ final class PhabricatorWorkerArchiveTaskQuery return $this; } + public function withClassNames(array $names) { + $this->classNames = $names; + return $this; + } + public function setLimit($limit) { $this->limit = $limit; return $this; @@ -67,20 +73,27 @@ final class PhabricatorWorkerArchiveTaskQuery $this->objectPHIDs); } - if ($this->dateModifiedSince) { + if ($this->dateModifiedSince !== null) { $where[] = qsprintf( $conn_r, 'dateModified > %d', $this->dateModifiedSince); } - if ($this->dateCreatedBefore) { + if ($this->dateCreatedBefore !== null) { $where[] = qsprintf( $conn_r, 'dateCreated < %d', $this->dateCreatedBefore); } + if ($this->classNames !== null) { + $where[] = qsprintf( + $conn_r, + 'taskClass IN (%Ls)', + $this->classNames); + } + return $this->formatWhereClause($where); } From 4068ee2a752d95becfd783710040cc82a84963c3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Jul 2016 07:04:25 -0700 Subject: [PATCH 07/76] Make permanent worker failures more user-friendly Summary: Ref T11309. In that task, a user misunderstood two parts of this error: - They took "exception" to mean "unexpected failure", when it was intended to mean "rare circumstance". - They intereted the internal ID number of a commit to mean that Phabricator was malfunctioning. Make the language of this condition more direct, explaining what the situation means in greater detail. Additionally, we would previously re-throw this exception, which would make the daemon exit, wait a moment, and restart. This was normal and expected. When //unexpected// failures occur, it's important do to this: it prevents a daemon failing in a loop from causing too many side effects (e.g., limit of 1 email per 5 seconds instead of thousands per second). When expected, permanent failures occur, we do not need to do this: the task will not be retried. I just did it because it was slightly more consistent ("failures restart daemons") and we had few permanent failure types at the time. We have more now, and restarting the daemons generates some additional logs which have the potential to confuse. Cycling the daemon also (intentionally) reduces the rate at which we process tasks, which can be bad for permanent failures like "deleted commit" because users can delete a huge number of commits and possibly clog up the queue with cycle-after-failure actions. Test Plan: Tried to process a deleted commit, saw a new message: ``` 2016-07-11 9:30:22 AM [STDE] PhabricatorTaskmasterDaemon Task 1428658 was cancelled: Commit "R55:6c46b7d0fb82a859ca3f87a95dc8dcceef8088c9" (with internal ID "282161") is no longer reachable from any branch, tag, or ref in this repository, so it will not be imported. This usually means that the branch the commit was on was deleted or overwritten. ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T11309 Differential Revision: https://secure.phabricator.com/D16268 --- .../worker/PhabricatorRepositoryCommitParserWorker.php | 7 +++++-- .../daemon/workers/PhabricatorTaskmasterDaemon.php | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php index f6e2661b87..b565d30c55 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php @@ -29,8 +29,11 @@ abstract class PhabricatorRepositoryCommitParserWorker if ($commit->isUnreachable()) { throw new PhabricatorWorkerPermanentFailureException( pht( - 'Commit "%s" has been deleted: it is no longer reachable from '. - 'any ref.', + 'Commit "%s" (with internal ID "%s") is no longer reachable from '. + 'any branch, tag, or ref in this repository, so it will not be '. + 'imported. This usually means that the branch the commit was on '. + 'was deleted or overwritten.', + $commit->getMonogram(), $commit_id)); } diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php index 2b54b9ab51..49e283c946 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php @@ -23,9 +23,11 @@ final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon { $ex = $task->getExecutionException(); if ($ex) { if ($ex instanceof PhabricatorWorkerPermanentFailureException) { - throw new PhutilProxyException( - pht('Permanent failure while executing Task ID %d.', $id), - $ex); + $this->log( + pht( + 'Task %d was cancelled: %s', + $id, + $ex->getMessage())); } else if ($ex instanceof PhabricatorWorkerYieldException) { $this->log(pht('Task %s yielded.', $id)); } else { From 553c335fbdf85120b9fdce3a8449fe4ca416073e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Jul 2016 08:05:57 -0700 Subject: [PATCH 08/76] Ignore unreachable commits when testing if a repository has imported Summary: Fixes T11309. When checking if a repository was fully imported, we incorrectly allow unreachable, un-imported commits to prevent the repository from moving to "Imported". This can happen if you delete branches from a repository while it is importing. Instead, ignore unreachable commits when checking for remaining imports, and when reporting status via `bin/repository importing`. Test Plan: - Stopped daemons. - Created a new repository and activated it. - Ran `bin/repository update Rxx`. - Deleted a branch in the repository. - Ran `bin/repository update Rxx`. - Ran daemons to flush queue. Now: - Ran `bin/repository importing`. Old behavior: showed unreachable commits as importing. New behavior: does not show unreachable commits. - Ran `bin/repository update`. Old behavior: failed to move repository to "imported" status. New behavior: correctly moves repository to "imported" status. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11309 Differential Revision: https://secure.phabricator.com/D16269 --- ...habricatorRepositoryManagementImportingWorkflow.php | 8 ++++++-- .../PhabricatorRepositoryManagementUpdateWorkflow.php | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php index acb533999b..7e814fc723 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php @@ -40,11 +40,15 @@ final class PhabricatorRepositoryManagementImportingWorkflow $rows = queryfx_all( $conn_r, 'SELECT repositoryID, commitIdentifier, importStatus FROM %T - WHERE repositoryID IN (%Ld) AND (importStatus & %d) != %d', + WHERE repositoryID IN (%Ld) + AND (importStatus & %d) != %d + AND (importStatus & %d) != %d', $table->getTableName(), array_keys($repos), PhabricatorRepositoryCommit::IMPORTED_ALL, - PhabricatorRepositoryCommit::IMPORTED_ALL); + PhabricatorRepositoryCommit::IMPORTED_ALL, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE); $console = PhutilConsole::getConsole(); if ($rows) { diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php index f435861c60..1b52a7c0e4 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php @@ -152,15 +152,19 @@ final class PhabricatorRepositoryManagementUpdateWorkflow return; } - // Look for any commit which hasn't imported. + // Look for any commit which is reachable and hasn't imported. $unparsed_commit = queryfx_one( $repository->establishConnection('r'), - 'SELECT * FROM %T WHERE repositoryID = %d AND (importStatus & %d) != %d + 'SELECT * FROM %T WHERE repositoryID = %d + AND (importStatus & %d) != %d + AND (importStatus & %d) != %d LIMIT 1', id(new PhabricatorRepositoryCommit())->getTableName(), $repository->getID(), PhabricatorRepositoryCommit::IMPORTED_ALL, - PhabricatorRepositoryCommit::IMPORTED_ALL); + PhabricatorRepositoryCommit::IMPORTED_ALL, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE); if ($unparsed_commit) { // We found a commit which still needs to import, so we can't clear the // flag. From 830f3eb8f803bda281f5b98e0519d39bed519e4c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Jul 2016 08:20:42 -0700 Subject: [PATCH 09/76] When users choose a default project icon, make a permanent file Summary: Fixes T10907. As written, this workflow will incorrectly reuse a temporary file if one exists. Instead, make a new permanent file. (Storage is still shared, so this usually will not actually create a copy of the file's data.) Test Plan: - Set a project's icon by clicking first button in "Use Picture" row. - Before patch: temporary image was reused. - After patch: new permanent file is generated. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10907 Differential Revision: https://secure.phabricator.com/D16270 --- .../files/controller/PhabricatorFileComposeController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index 6a4536d94a..930ec61483 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -35,7 +35,7 @@ final class PhabricatorFileComposeController $data = $composer->loadBuiltinFileData(); - $file = PhabricatorFile::buildFromFileDataOrHash( + $file = PhabricatorFile::newFromFileData( $data, array( 'name' => $composer->getBuiltinDisplayName(), From 8ad61d01502d5b95e2a131476d7071f3ff112a13 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Jul 2016 09:16:03 -0700 Subject: [PATCH 10/76] Simplify "builtin file" management and recover from races Summary: Fixes T11307. Fixes T8124. Currently, builtin files are tracked by using a special transform with an invalid source ID. Just use a dedicated column instead. The transform thing is too clever/weird/hacky and exposes us to issues with the "file" and "transform" tables getting out of sync (possibly the issue in T11307?) and with race conditions. Test Plan: - Loaded profile "edit picture" page, saw builtins. - Deleted all builtin files, put 3 second sleep in the storage engine write, loaded profile page in two windows. - Before patch: one of them failed with a race. - After patch: both of them loaded. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8124, T11307 Differential Revision: https://secure.phabricator.com/D16271 --- .../autopatches/20160711.files.01.builtin.sql | 2 + .../20160711.files.02.builtinkey.sql | 2 + .../files/query/PhabricatorFileQuery.php | 13 +++++++ .../files/storage/PhabricatorFile.php | 38 +++++++++++-------- 4 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 resources/sql/autopatches/20160711.files.01.builtin.sql create mode 100644 resources/sql/autopatches/20160711.files.02.builtinkey.sql diff --git a/resources/sql/autopatches/20160711.files.01.builtin.sql b/resources/sql/autopatches/20160711.files.01.builtin.sql new file mode 100644 index 0000000000..d8849ec053 --- /dev/null +++ b/resources/sql/autopatches/20160711.files.01.builtin.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_file.file + ADD builtinKey VARCHAR(64) COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160711.files.02.builtinkey.sql b/resources/sql/autopatches/20160711.files.02.builtinkey.sql new file mode 100644 index 0000000000..3551f6c3cd --- /dev/null +++ b/resources/sql/autopatches/20160711.files.02.builtinkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_file.file + ADD UNIQUE KEY `key_builtin` (builtinKey); diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index 5e1876acd6..c2ac083fea 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -16,6 +16,7 @@ final class PhabricatorFileQuery private $names; private $isPartial; private $needTransforms; + private $builtinKeys; public function withIDs(array $ids) { $this->ids = $ids; @@ -47,6 +48,11 @@ final class PhabricatorFileQuery return $this; } + public function withBuiltinKeys(array $keys) { + $this->builtinKeys = $keys; + return $this; + } + /** * Select files which are transformations of some other file. For example, * you can use this query to find previously generated thumbnails of an image @@ -384,6 +390,13 @@ final class PhabricatorFileQuery (int)$this->isPartial); } + if ($this->builtinKeys !== null) { + $where[] = qsprintf( + $conn, + 'builtinKey IN (%Ls)', + $this->builtinKeys); + } + return $where; } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 7e8f4b18fc..8d81c3ae98 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -42,6 +42,7 @@ final class PhabricatorFile extends PhabricatorFileDAO protected $contentHash; protected $metadata = array(); protected $mailKey; + protected $builtinKey; protected $storageEngine; protected $storageFormat; @@ -94,6 +95,7 @@ final class PhabricatorFile extends PhabricatorFileDAO 'isExplicitUpload' => 'bool?', 'mailKey' => 'bytes20', 'isPartial' => 'bool', + 'builtinKey' => 'text64?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -116,6 +118,10 @@ final class PhabricatorFile extends PhabricatorFileDAO 'key_partial' => array( 'columns' => array('authorPHID', 'isPartial'), ), + 'key_builtin' => array( + 'columns' => array('builtinKey'), + 'unique' => true, + ), ), ) + parent::getConfiguration(); } @@ -1070,19 +1076,11 @@ final class PhabricatorFile extends PhabricatorFileDAO public static function loadBuiltins(PhabricatorUser $user, array $builtins) { $builtins = mpull($builtins, null, 'getBuiltinFileKey'); - $specs = array(); - foreach ($builtins as $key => $buitin) { - $specs[] = array( - 'originalPHID' => PhabricatorPHIDConstants::PHID_VOID, - 'transform' => $key, - ); - } - // NOTE: Anyone is allowed to access builtin files. $files = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withTransforms($specs) + ->withBuiltinKeys(array_keys($builtins)) ->execute(); $results = array(); @@ -1109,12 +1107,21 @@ final class PhabricatorFile extends PhabricatorFileDAO ); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $file = self::newFromFileData($data, $params); - $xform = id(new PhabricatorTransformedFile()) - ->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID) - ->setTransform($key) - ->setTransformedPHID($file->getPHID()) - ->save(); + try { + $file = self::newFromFileData($data, $params); + } catch (AphrontDuplicateKeyQueryException $ex) { + $file = id(new PhabricatorFileQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withBuiltinKeys(array($key)) + ->executeOne(); + if (!$file) { + throw new Exception( + pht( + 'Collided mid-air when generating builtin file "%s", but '. + 'then failed to load the object we collided with.', + $key)); + } + } unset($unguarded); $file->attachObjectPHIDs(array()); @@ -1289,6 +1296,7 @@ final class PhabricatorFile extends PhabricatorFileDAO $builtin = idx($params, 'builtin'); if ($builtin) { $this->setBuiltinName($builtin); + $this->setBuiltinKey($builtin); } $profile = idx($params, 'profile'); From 91a8a6d618cb9a9d3d5207cfe0b70449f010a18e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jul 2016 09:27:45 -0700 Subject: [PATCH 11/76] Initial cut of CalendarEditEngine Summary: Ref T9275. This builds a Calendar EditEngine which only edits "name". I'll add more fields, Conduit, etc., and move to modular transactions in future changes. Test Plan: Used `editpro/` URI manually to edit the name of an event. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16235 --- src/__phutil_library_map__.php | 4 + .../PhabricatorCalendarApplication.php | 2 + ...bricatorCalendarEventEditProController.php | 12 +++ .../editor/PhabricatorCalendarEditEngine.php | 74 +++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php create mode 100644 src/applications/calendar/editor/PhabricatorCalendarEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 791c4f8e5e..e947d60526 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2019,11 +2019,13 @@ phutil_register_library_map(array( 'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php', 'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php', 'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php', + 'PhabricatorCalendarEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEditEngine.php', 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', 'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php', 'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php', 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', + 'PhabricatorCalendarEventEditProController' => 'applications/calendar/controller/PhabricatorCalendarEventEditProController.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', 'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php', 'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php', @@ -6615,6 +6617,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarApplication' => 'PhabricatorApplication', 'PhabricatorCalendarController' => 'PhabricatorController', 'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO', + 'PhabricatorCalendarEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCalendarEvent' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', @@ -6633,6 +6636,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventEditProController' => 'ManiphestController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 71358366df..1d74664c5c 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -47,6 +47,8 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { '(?P\d+)/)?(?:(?P\d+)/)?)?' => 'PhabricatorCalendarEventListController', 'event/' => array( + $this->getEditRoutePattern('editpro/') + => 'PhabricatorCalendarEventEditProController', 'create/' => 'PhabricatorCalendarEventEditController', 'edit/(?P[1-9]\d*)/(?:(?P\d+)/)?' diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php new file mode 100644 index 0000000000..6cd4c4bb35 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php new file mode 100644 index 0000000000..0bdb840175 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php @@ -0,0 +1,74 @@ +getViewer(), + $mode = null); + } + + protected function newObjectQuery() { + return new PhabricatorCalendarEventQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Event'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Event: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getMonogram(); + } + + protected function getObjectCreateShortText() { + return pht('Create Event'); + } + + protected function getObjectName() { + return pht('Event'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $fields = array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the event.')) + ->setConduitDescription(pht('Rename the event.')) + ->setConduitTypeDescription(pht('New event name.')) + ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + ); + + return $fields; + } + +} From 3ab6a7e19f468c3d5ed1f120234421c1e1b46f59 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Jul 2016 07:19:58 -0700 Subject: [PATCH 12/76] Generate "stub" events earlier, so more infrastructure works with Calendar Summary: Ref T9275. When you create a recurring event which recurs forever, we want to avoid writing an infinite number of rows to the database. Currently, we write a row to the database right before you edit the event. Until then, we refer to it as `E123/999` or whatever ("instance 999 of event 123"). This creates a big mess with trying to make recurring events work with EditEngine, Subscriptions, Projects, Flags, Tokens, etc -- all of this stuff assumes that whatever you're working with has a PHID. I poked at letting this stuff work without a PHID a little bit, but that looked like a gigantic mess. Instead, generate an event "stub" a little sooner (when you look at the event detail page). This is basically just an ID/PHID to refer to the instance. Then, when you edit the stub, "materialize" it into a real event. This still has some issues, but I think it's more promising than the other approach was. Also: - Removes dead user profile calendar controller. - Replaces comments with EditEngine comments. Test Plan: - Commented on a recurring event. - Awarded tokens to a recurring event. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16248 --- .../autopatches/20160707.calendar.01.stub.sql | 2 + src/__phutil_library_map__.php | 4 - .../PhabricatorCalendarApplication.php | 8 +- .../PhabricatorCalendarController.php | 45 ---- ...abricatorCalendarEventCancelController.php | 81 +++----- ...bricatorCalendarEventCommentController.php | 81 -------- ...PhabricatorCalendarEventEditController.php | 43 +--- ...PhabricatorCalendarEventJoinController.php | 3 +- ...PhabricatorCalendarEventViewController.php | 195 +++++++++--------- .../editor/PhabricatorCalendarEditEngine.php | 4 + .../editor/PhabricatorCalendarEventEditor.php | 50 ++++- .../query/PhabricatorCalendarEventQuery.php | 95 ++++++--- .../PhabricatorCalendarEventSearchEngine.php | 10 +- .../storage/PhabricatorCalendarEvent.php | 188 +++++++++++------ .../query/ConpherenceThreadQuery.php | 2 + .../PhabricatorPeopleApplication.php | 1 - .../PhabricatorPeopleCalendarController.php | 97 --------- 17 files changed, 404 insertions(+), 505 deletions(-) create mode 100644 resources/sql/autopatches/20160707.calendar.01.stub.sql delete mode 100644 src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php delete mode 100644 src/applications/people/controller/PhabricatorPeopleCalendarController.php diff --git a/resources/sql/autopatches/20160707.calendar.01.stub.sql b/resources/sql/autopatches/20160707.calendar.01.stub.sql new file mode 100644 index 0000000000..b872f17eeb --- /dev/null +++ b/resources/sql/autopatches/20160707.calendar.01.stub.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD isStub BOOL NOT NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e947d60526..d23cd3b2d8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2022,7 +2022,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEditEngine.php', 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', 'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php', - 'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php', 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 'PhabricatorCalendarEventEditProController' => 'applications/calendar/controller/PhabricatorCalendarEventEditProController.php', @@ -2994,7 +2993,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php', 'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php', 'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php', - 'PhabricatorPeopleCalendarController' => 'applications/people/controller/PhabricatorPeopleCalendarController.php', 'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php', 'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php', 'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php', @@ -6633,7 +6631,6 @@ phutil_register_library_map(array( 'PhabricatorFulltextInterface', ), 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', - 'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditProController' => 'ManiphestController', @@ -7741,7 +7738,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPeopleApplication' => 'PhabricatorApplication', 'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController', - 'PhabricatorPeopleCalendarController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController', 'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 1d74664c5c..4f0f26d2e0 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -40,7 +40,7 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { public function getRoutes() { return array( - '/E(?P[1-9]\d*)(?:/(?P\d+))?' + '/E(?P[1-9]\d*)(?:/(?P\d+)/)?' => 'PhabricatorCalendarEventViewController', '/calendar/' => array( '(?:query/(?P[^/]+)/(?:(?P\d+)/'. @@ -51,15 +51,15 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { => 'PhabricatorCalendarEventEditProController', 'create/' => 'PhabricatorCalendarEventEditController', - 'edit/(?P[1-9]\d*)/(?:(?P\d+)/)?' + 'edit/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventEditController', 'drag/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventDragController', - 'cancel/(?P[1-9]\d*)/(?:(?P\d+)/)?' + 'cancel/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventCancelController', '(?Pjoin|decline|accept)/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventJoinController', - 'comment/(?P[1-9]\d*)/(?:(?P\d+)/)?' + 'comment/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventCommentController', ), ), diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php index 9df28c8ddc..60beb853d0 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarController.php @@ -30,49 +30,4 @@ abstract class PhabricatorCalendarController extends PhabricatorController { return $crumbs; } - protected function getEventAtIndexForGhostPHID($viewer, $phid, $index) { - $result = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->withInstanceSequencePairs( - array( - array( - $phid, - $index, - ), - )) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - - return $result; - } - - protected function createEventFromGhost($viewer, $event, $index) { - $invitees = $event->getInvitees(); - - $new_ghost = $event->generateNthGhost($index, $viewer); - $new_ghost->attachParentEvent($event); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $new_ghost - ->setID(null) - ->setPHID(null) - ->removeViewerTimezone($viewer) - ->setViewPolicy($event->getViewPolicy()) - ->setEditPolicy($event->getEditPolicy()) - ->save(); - $ghost_invitees = array(); - foreach ($invitees as $invitee) { - $ghost_invitee = clone $invitee; - $ghost_invitee - ->setID(null) - ->setEventPHID($new_ghost->getPHID()) - ->save(); - } - unset($unguarded); - return $new_ghost; - } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php index 81736436a0..49bb0c3948 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php @@ -6,7 +6,6 @@ final class PhabricatorCalendarEventCancelController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); - $sequence = $request->getURIData('sequence'); $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) @@ -17,40 +16,24 @@ final class PhabricatorCalendarEventCancelController PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); - - if ($sequence) { - $parent_event = $event; - $event = $parent_event->generateNthGhost($sequence, $viewer); - $event->attachParentEvent($parent_event); - } - if (!$event) { return new Aphront404Response(); } - if (!$sequence) { - $cancel_uri = '/E'.$event->getID(); + $cancel_uri = $event->getURI(); + + $is_parent = $event->isParentEvent(); + $is_child = $event->isChildEvent(); + $is_cancelled = $event->getIsCancelled(); + + if ($is_child) { + $is_parent_cancelled = $event->getParentEvent()->getIsCancelled(); } else { - $cancel_uri = '/E'.$event->getID().'/'.$sequence; + $is_parent_cancelled = false; } - $is_cancelled = $event->getIsCancelled(); - $is_parent_cancelled = $event->getIsParentCancelled(); - $is_parent = $event->getIsRecurrenceParent(); - $validation_exception = null; - if ($request->isFormPost()) { - if ($is_cancelled && $sequence) { - return id(new AphrontRedirectResponse())->setURI($cancel_uri); - } else if ($sequence) { - $event = $this->createEventFromGhost( - $viewer, - $event, - $sequence); - $event->applyViewerTimezone($viewer); - } - $xactions = array(); $xaction = id(new PhabricatorCalendarEventTransaction()) @@ -73,43 +56,47 @@ final class PhabricatorCalendarEventCancelController } if ($is_cancelled) { - if ($sequence || $is_parent_cancelled) { + if ($is_parent_cancelled) { $title = pht('Cannot Reinstate Instance'); $paragraph = pht( - 'Cannot reinstate an instance of a cancelled recurring event.'); - $cancel = pht('Cancel'); + 'You cannot reinstate an instance of a cancelled recurring event.'); + $cancel = pht('Back'); $submit = null; - } else if ($is_parent) { - $title = pht('Reinstate Recurrence'); + } else if ($is_child) { + $title = pht('Reinstate Instance'); $paragraph = pht( - 'Reinstate all instances of this recurrence - that have not been individually cancelled?'); - $cancel = pht("Don't Reinstate Recurrence"); - $submit = pht('Reinstate Recurrence'); + 'Reinstate this instance of this recurring event?'); + $cancel = pht('Back'); + $submit = pht('Reinstate Instance'); + } else if ($is_parent) { + $title = pht('Reinstate Recurring Event'); + $paragraph = pht( + 'Reinstate all instances of this recurring event which have not '. + 'been individually cancelled?'); + $cancel = pht('Back'); + $submit = pht('Reinstate Recurring Event'); } else { $title = pht('Reinstate Event'); $paragraph = pht('Reinstate this event?'); - $cancel = pht("Don't Reinstate Event"); + $cancel = pht('Back'); $submit = pht('Reinstate Event'); } } else { - if ($sequence) { + if ($is_child) { $title = pht('Cancel Instance'); - $paragraph = pht( - 'Cancel just this instance of a recurring event.'); - $cancel = pht("Don't Cancel Instance"); + $paragraph = pht('Cancel this instance of this recurring event?'); + $cancel = pht('Back'); $submit = pht('Cancel Instance'); } else if ($is_parent) { - $title = pht('Cancel Recurrence'); - $paragraph = pht( - 'Cancel the entire series of recurring events?'); - $cancel = pht("Don't Cancel Recurrence"); - $submit = pht('Cancel Recurrence'); + $title = pht('Cancel Recurrin Event'); + $paragraph = pht('Cancel this entire series of recurring events?'); + $cancel = pht('Back'); + $submit = pht('Cancel Recurring Event'); } else { $title = pht('Cancel Event'); $paragraph = pht( - 'You can always reinstate the event later.'); - $cancel = pht("Don't Cancel Event"); + 'Cancel this event? You can always reinstate the event later.'); + $cancel = pht('Back'); $submit = pht('Cancel Event'); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php deleted file mode 100644 index 876712a455..0000000000 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php +++ /dev/null @@ -1,81 +0,0 @@ -isFormPost()) { - return new Aphront400Response(); - } - - $viewer = $request->getViewer(); - $id = $request->getURIData('id'); - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$event) { - return new Aphront404Response(); - } - - $index = $request->getURIData('sequence'); - if ($index && !$is_preview) { - $result = $this->getEventAtIndexForGhostPHID( - $viewer, - $event->getPHID(), - $index); - - if ($result) { - $event = $result; - } else { - $event = $this->createEventFromGhost( - $viewer, - $event, - $index); - $event->applyViewerTimezone($viewer); - } - } - - $view_uri = '/'.$event->getMonogram(); - - $xactions = array(); - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new PhabricatorCalendarEventTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new PhabricatorCalendarEventEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($event, $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/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 421084ceaf..0f960ba1ac 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -77,41 +77,18 @@ final class PhabricatorCalendarEventEditController $cancel_uri = $this->getApplicationURI(); } else { $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->withIDs(array($this->id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); if (!$event) { return new Aphront404Response(); } - if ($request->getURIData('sequence')) { - $index = $request->getURIData('sequence'); - - $result = $this->getEventAtIndexForGhostPHID( - $viewer, - $event->getPHID(), - $index); - - if ($result) { - return id(new AphrontRedirectResponse()) - ->setURI('/calendar/event/edit/'.$result->getID().'/'); - } - - $event = $this->createEventFromGhost( - $viewer, - $event, - $index); - - return id(new AphrontRedirectResponse()) - ->setURI('/calendar/event/edit/'.$event->getID().'/'); - } - $end_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $event->getDateTo()); @@ -137,7 +114,7 @@ final class PhabricatorCalendarEventEditController } } - $cancel_uri = '/'.$event->getMonogram(); + $cancel_uri = $event->getURI(); } if ($this->isCreate()) { @@ -153,7 +130,7 @@ final class PhabricatorCalendarEventEditController $description = $event->getDescription(); $is_all_day = $event->getIsAllDay(); $is_recurring = $event->getIsRecurring(); - $is_parent = $event->getIsRecurrenceParent(); + $is_parent = $event->isParentEvent(); $frequency = idx($event->getRecurrenceFrequency(), 'rule'); $icon = $event->getIcon(); $edit_policy = $event->getEditPolicy(); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php index a7f41c4a0c..d69a065f9b 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php @@ -20,12 +20,11 @@ final class PhabricatorCalendarEventJoinController ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); - if (!$event) { return new Aphront404Response(); } - $cancel_uri = '/E'.$event->getID(); + $cancel_uri = $event->getURI(); $validation_exception = null; $is_attending = $event->getIsUserAttending($viewer->getPHID()); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 98d3a2fe74..933b258e3b 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -9,89 +9,48 @@ final class PhabricatorCalendarEventViewController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $id = $request->getURIData('id'); - $sequence = $request->getURIData('sequence'); - $timeline = null; - - $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); + $event = $this->loadEvent(); if (!$event) { return new Aphront404Response(); } - if ($sequence) { - $result = $this->getEventAtIndexForGhostPHID( - $viewer, - $event->getPHID(), - $sequence); - - if ($result) { - $parent_event = $event; - $event = $result; - $event->attachParentEvent($parent_event); - return id(new AphrontRedirectResponse()) - ->setURI('/E'.$result->getID()); - } else if ($sequence && $event->getIsRecurring()) { - $parent_event = $event; - $event = $event->generateNthGhost($sequence, $viewer); - $event->attachParentEvent($parent_event); - } else if ($sequence) { - return new Aphront404Response(); - } - - $title = $event->getMonogram().' ('.$sequence.')'; - $page_title = $title.' '.$event->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title, '/'.$event->getMonogram().'/'.$sequence); - - - } else { - $title = 'E'.$event->getID(); - $page_title = $title.' '.$event->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title); - $crumbs->setBorder(true); + // If we looked up or generated a stub event, redirect to that event's + // canonical URI. + $id = $request->getURIData('id'); + if ($event->getID() != $id) { + $uri = $event->getURI(); + return id(new AphrontRedirectResponse())->setURI($uri); } - if (!$event->getIsGhostEvent()) { - $timeline = $this->buildTransactionTimeline( - $event, - new PhabricatorCalendarEventTransactionQuery()); - } + $monogram = $event->getMonogram(); + $page_title = $monogram.' '.$event->getName(); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($monogram); + $crumbs->setBorder(true); + + $timeline = $this->buildTransactionTimeline( + $event, + new PhabricatorCalendarEventTransactionQuery()); $header = $this->buildHeaderView($event); $curtain = $this->buildCurtain($event); $details = $this->buildPropertySection($event); $description = $this->buildDescriptionView($event); - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - $add_comment_header = $is_serious - ? pht('Add Comment') - : pht('Add To Plate'); - $draft = PhabricatorDraft::newFromUserAndKey($viewer, $event->getPHID()); - if ($sequence) { - $comment_uri = $this->getApplicationURI( - '/event/comment/'.$event->getID().'/'.$sequence.'/'); - } else { - $comment_uri = $this->getApplicationURI( - '/event/comment/'.$event->getID().'/'); - } - $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($event->getPHID()) - ->setDraft($draft) - ->setHeaderText($add_comment_header) - ->setAction($comment_uri) - ->setSubmitButtonName(pht('Add Comment')); + $comment_view = id(new PhabricatorCalendarEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($event); + + $timeline->setQuoteRef($monogram); + $comment_view->setTransactionTimeline($timeline); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setMainColumn(array( + ->setMainColumn( + array( $timeline, - $add_comment_form, + $comment_view, )) ->setCurtain($curtain) ->addPropertySection(pht('Details'), $details) @@ -101,10 +60,7 @@ final class PhabricatorCalendarEventViewController ->setTitle($page_title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($event->getPHID())) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } private function buildHeaderView( @@ -152,7 +108,7 @@ final class PhabricatorCalendarEventViewController private function buildCurtain(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); $id = $event->getID(); - $is_cancelled = $event->getIsCancelled(); + $is_cancelled = $event->isCancelledEvent(); $is_attending = $event->getIsUserAttending($viewer->getPHID()); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -160,19 +116,11 @@ final class PhabricatorCalendarEventViewController $event, PhabricatorPolicyCapability::CAN_EDIT); - $edit_label = false; - $edit_uri = false; - - if ($event->getIsGhostEvent()) { - $index = $event->getSequenceIndex(); + $edit_uri = "event/edit/{$id}/"; + if ($event->isChildEvent()) { $edit_label = pht('Edit This Instance'); - $edit_uri = "event/edit/{$id}/{$index}/"; - } else if ($event->getIsRecurrenceException()) { - $edit_label = pht('Edit This Instance'); - $edit_uri = "event/edit/{$id}/"; } else { $edit_label = pht('Edit'); - $edit_uri = "event/edit/{$id}/"; } $curtain = $this->newCurtainView($event); @@ -204,28 +152,21 @@ final class PhabricatorCalendarEventViewController } $cancel_uri = $this->getApplicationURI("event/cancel/{$id}/"); + $cancel_disabled = !$can_edit; - if ($event->getIsGhostEvent()) { - $index = $event->getSequenceIndex(); - $can_reinstate = $event->getIsParentCancelled(); - + if ($event->isChildEvent()) { $cancel_label = pht('Cancel This Instance'); $reinstate_label = pht('Reinstate This Instance'); - $cancel_disabled = (!$can_edit || $can_reinstate); - $cancel_uri = $this->getApplicationURI("event/cancel/{$id}/{$index}/"); - } else if ($event->getIsRecurrenceException()) { - $can_reinstate = $event->getIsParentCancelled(); - $cancel_label = pht('Cancel This Instance'); - $reinstate_label = pht('Reinstate This Instance'); - $cancel_disabled = (!$can_edit || $can_reinstate); - } else if ($event->getIsRecurrenceParent()) { + + if ($event->getParentEvent()->getIsCancelled()) { + $cancel_disabled = true; + } + } else if ($event->isParentEvent()) { $cancel_label = pht('Cancel All'); $reinstate_label = pht('Reinstate All'); - $cancel_disabled = !$can_edit; } else { $cancel_label = pht('Cancel Event'); $reinstate_label = pht('Reinstate Event'); - $cancel_disabled = !$can_edit; } if ($is_cancelled) { @@ -385,4 +326,68 @@ final class PhabricatorCalendarEventViewController return null; } + + private function loadEvent() { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $id = $request->getURIData('id'); + $sequence = $request->getURIData('sequence'); + + // We're going to figure out which event you're trying to look at. Most of + // the time this is simple, but you may be looking at an instance of a + // recurring event which we haven't generated an object for. + + // If you are, we're going to generate a "stub" event so we have a real + // ID and PHID to work with, since the rest of the infrastructure relies + // on these identifiers existing. + + // Load the event identified by ID first. + $event = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$event) { + return null; + } + + // If we aren't looking at an instance of this event, this is a completely + // normal request and we can just return this event. + if (!$sequence) { + return $event; + } + + // When you view "E123/999", E123 is normally the parent event. However, + // you might visit a different instance first instead and then fiddle + // with the URI. If the event we're looking at is a child, we are going + // to act on the parent instead. + if ($event->isChildEvent()) { + $event = $event->getParentEvent(); + } + + // Try to load the instance. If it already exists, we're all done and + // can just return it. + $instance = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withInstanceSequencePairs( + array( + array($event->getPHID(), $sequence), + )) + ->executeOne(); + if ($instance) { + return $instance; + } + + if (!$viewer->isLoggedIn()) { + throw new Exception( + pht( + 'This event instance has not been created yet. Log in to create '. + 'it.')); + } + + $instance = $event->newStub($viewer, $sequence); + + return $instance; + } + } diff --git a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php index 0bdb840175..33a8462075 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php @@ -55,6 +55,10 @@ final class PhabricatorCalendarEditEngine return $object->getURI(); } + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('event/editpro/'); + } + protected function buildCustomEditFields($object) { $fields = array( id(new PhabricatorTextEditField()) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index dd5b361f71..e8ef5528e8 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -11,6 +11,47 @@ final class PhabricatorCalendarEventEditor return pht('Calendar'); } + protected function shouldApplyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function applyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + $actor = $this->requireActor(); + $object->removeViewerTimezone($actor); + + if ($object->getIsStub()) { + $this->materializeStub($object); + } + } + + private function materializeStub(PhabricatorCalendarEvent $event) { + if (!$event->getIsStub()) { + throw new Exception( + pht('Can not materialize an event stub: this event is not a stub.')); + } + + $actor = $this->getActor(); + $event->copyFromParent($actor); + $event->setIsStub(0); + + $invitees = $event->getParentEvent()->getInvitees(); + foreach ($invitees as $invitee) { + $invitee = id(new PhabricatorCalendarEventInvitee()) + ->setEventPHID($event->getPHID()) + ->setInviteePHID($invitee->getInviteePHID()) + ->setInviterPHID($invitee->getInviterPHID()) + ->setStatus($invitee->getStatus()) + ->save(); + } + + $event->save(); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); @@ -196,15 +237,6 @@ final class PhabricatorCalendarEventEditor return parent::applyCustomExternalTransaction($object, $xaction); } - protected function didApplyInternalEffects( - PhabricatorLiskDAO $object, - array $xactions) { - - $object->removeViewerTimezone($this->requireActor()); - - return $xactions; - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 51cd0ea25e..2771fc0497 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -12,6 +12,7 @@ final class PhabricatorCalendarEventQuery private $isCancelled; private $eventsWithNoParent; private $instanceSequencePairs; + private $isStub; private $generateGhosts = false; @@ -55,6 +56,11 @@ final class PhabricatorCalendarEventQuery return $this; } + public function withIsStub($is_stub) { + $this->isStub = $is_stub; + return $this; + } + public function withEventsWithNoParent($events_with_no_parent) { $this->eventsWithNoParent = $events_with_no_parent; return $this; @@ -183,7 +189,7 @@ final class PhabricatorCalendarEventQuery $sequence_start = max(1, $sequence_start); for ($index = $sequence_start; $index < $sequence_end; $index++) { - $events[] = $event->generateNthGhost($index, $viewer); + $events[] = $event->newGhost($viewer, $index); } // NOTE: We're slicing results every time because this makes it cheaper @@ -201,40 +207,66 @@ final class PhabricatorCalendarEventQuery } } - $map = array(); - $instance_sequence_pairs = array(); + // Now that we're done generating ghost events, we're going to remove any + // ghosts that we have concrete events for (or which we can load the + // concrete events for). These concrete events are generated when users + // edit a ghost, and replace the ghost events. - foreach ($events as $key => $event) { + // First, generate a map of all concrete events we + // already loaded. We don't need to load these again. + $have_pairs = array(); + foreach ($events as $event) { if ($event->getIsGhostEvent()) { - $index = $event->getSequenceIndex(); - $instance_sequence_pairs[] = array($event->getPHID(), $index); - $map[$event->getPHID()][$index] = $key; + continue; } + + $parent_phid = $event->getInstanceOfEventPHID(); + $sequence = $event->getSequenceIndex(); + + $have_pairs[$parent_phid][$sequence] = true; } - if (count($instance_sequence_pairs) > 0) { - $sub_query = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->setParentQuery($this) - ->withInstanceSequencePairs($instance_sequence_pairs) - ->execute(); - - foreach ($sub_query as $edited_ghost) { - $indexes = idx($map, $edited_ghost->getInstanceOfEventPHID()); - $key = idx($indexes, $edited_ghost->getSequenceIndex()); - $events[$key] = $edited_ghost; + // Now, generate a map of all events we generated + // ghosts for. We need to try to load these if we don't already have them. + $map = array(); + $parent_pairs = array(); + foreach ($events as $key => $event) { + if (!$event->getIsGhostEvent()) { + continue; } - $id_map = array(); - foreach ($events as $key => $event) { - if ($event->getIsGhostEvent()) { - continue; - } - if (isset($id_map[$event->getID()])) { - unset($events[$key]); - } else { - $id_map[$event->getID()] = true; - } + $parent_phid = $event->getInstanceOfEventPHID(); + $sequence = $event->getSequenceIndex(); + + // We already loaded the concrete version of this event, so we can just + // throw out the ghost and move on. + if (isset($have_pairs[$parent_phid][$sequence])) { + unset($events[$key]); + continue; + } + + // We didn't load the concrete version of this event, so we need to + // try to load it if it exists. + $parent_pairs[] = array($parent_phid, $sequence); + $map[$parent_phid][$sequence] = $key; + } + + if ($parent_pairs) { + $instances = id(new self()) + ->setViewer($viewer) + ->setParentQuery($this) + ->withInstanceSequencePairs($parent_pairs) + ->execute(); + + foreach ($instances as $instance) { + $parent_phid = $instance->getInstanceOfEventPHID(); + $sequence = $instance->getSequenceIndex(); + + $indexes = idx($map, $parent_phid); + $key = idx($indexes, $sequence); + + // Replace the ghost with the corresponding concrete event. + $events[$key] = $instance; } } @@ -329,6 +361,13 @@ final class PhabricatorCalendarEventQuery implode(' OR ', $sql)); } + if ($this->isStub !== null) { + $where[] = qsprintf( + $conn, + 'event.isStub = %d', + (int)$this->isStub); + } + return $where; } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 9eb50048fb..8258475158 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -113,7 +113,15 @@ final class PhabricatorCalendarEventSearchEngine break; } - return $query->setGenerateGhosts(true); + // Generate ghosts (and ignore stub events) if we aren't querying for + // specific events. + if (!$map['ids'] && !$map['phids']) { + $query + ->withIsStub(false) + ->setGenerateGhosts(true); + } + + return $query; } private function getQueryDateRange( diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 8432a157e2..25734f92b0 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -22,6 +22,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $isAllDay; protected $icon; protected $mailKey; + protected $isStub; protected $isRecurring = 0; protected $recurrenceFrequency = array(); @@ -71,6 +72,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setUserPHID($actor->getPHID()) ->setIsCancelled(0) ->setIsAllDay(0) + ->setIsStub(0) ->setIsRecurring($is_recurring) ->setIcon(self::DEFAULT_ICON) ->setViewPolicy($view_policy) @@ -80,6 +82,116 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->applyViewerTimezone($actor); } + private function newChild(PhabricatorUser $actor, $sequence) { + if (!$this->isParentEvent()) { + throw new Exception( + pht( + 'Unable to generate a new child event for an event which is not '. + 'a recurring parent event!')); + } + + $child = id(new self()) + ->setIsCancelled(0) + ->setIsStub(0) + ->setInstanceOfEventPHID($this->getPHID()) + ->setSequenceIndex($sequence) + ->setIsRecurring(true) + ->setRecurrenceFrequency($this->getRecurrenceFrequency()) + ->attachParentEvent($this); + + return $child->copyFromParent($actor); + } + + protected function readField($field) { + static $inherit = array( + 'userPHID' => true, + 'isAllDay' => true, + 'icon' => true, + 'spacePHID' => true, + 'viewPolicy' => true, + 'editPolicy' => true, + 'name' => true, + 'description' => true, + ); + + // Read these fields from the parent event instead of this event. For + // example, we want any changes to the parent event's name to + if (isset($inherit[$field])) { + if ($this->getIsStub()) { + // TODO: This should be unconditional, but the execution order of + // CalendarEventQuery and applyViewerTimezone() are currently odd. + if ($this->parentEvent !== self::ATTACHABLE) { + return $this->getParentEvent()->readField($field); + } + } + } + + return parent::readField($field); + } + + + public function copyFromParent(PhabricatorUser $actor) { + if (!$this->isChildEvent()) { + throw new Exception( + pht( + 'Unable to copy from parent event: this is not a child event.')); + } + + $parent = $this->getParentEvent(); + + $this + ->setUserPHID($parent->getUserPHID()) + ->setIsAllDay($parent->getIsAllDay()) + ->setIcon($parent->getIcon()) + ->setSpacePHID($parent->getSpacePHID()) + ->setViewPolicy($parent->getViewPolicy()) + ->setEditPolicy($parent->getEditPolicy()) + ->setName($parent->getName()) + ->setDescription($parent->getDescription()); + + $frequency = $parent->getFrequencyUnit(); + $modify_key = '+'.$this->getSequenceIndex().' '.$frequency; + + $date = $parent->getDateFrom(); + $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor); + $date_time->modify($modify_key); + $date = $date_time->format('U'); + + $duration = $parent->getDateTo() - $parent->getDateFrom(); + + $this + ->setDateFrom($date) + ->setDateTo($date + $duration); + + return $this; + } + + public function newStub(PhabricatorUser $actor, $sequence) { + $stub = $this->newChild($actor, $sequence); + + $stub->setIsStub(1); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $stub->save(); + unset($unguarded); + + $stub->applyViewerTimezone($actor); + + return $stub; + } + + public function newGhost(PhabricatorUser $actor, $sequence) { + $ghost = $this->newChild($actor, $sequence); + + $ghost + ->setIsGhostEvent(true) + ->makeEphemeral(); + + $ghost->applyViewerTimezone($actor); + + return $ghost; + } + public function applyViewerTimezone(PhabricatorUser $viewer) { if ($this->appliedViewer) { throw new Exception(pht('Viewer timezone is already applied!')); @@ -211,6 +323,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 'recurrenceEndDate' => 'epoch?', 'instanceOfEventPHID' => 'phid?', 'sequenceIndex' => 'uint32?', + 'isStub' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'userPHID_dateFrom' => array( @@ -285,38 +398,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this; } - public function generateNthGhost( - $sequence_index, - PhabricatorUser $actor) { - - $frequency = $this->getFrequencyUnit(); - $modify_key = '+'.$sequence_index.' '.$frequency; - - $instance_of = ($this->getPHID()) ? - $this->getPHID() : $this->instanceOfEventPHID; - - $date = $this->dateFrom; - $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor); - $date_time->modify($modify_key); - $date = $date_time->format('U'); - - $duration = $this->dateTo - $this->dateFrom; - - $edit_policy = PhabricatorPolicies::POLICY_NOONE; - - $ghost_event = id(clone $this) - ->setIsGhostEvent(true) - ->setDateFrom($date) - ->setDateTo($date + $duration) - ->setIsRecurring(true) - ->setRecurrenceFrequency($this->recurrenceFrequency) - ->setInstanceOfEventPHID($instance_of) - ->setSequenceIndex($sequence_index) - ->setEditPolicy($edit_policy); - - return $ghost_event; - } - public function getFrequencyUnit() { $frequency = idx($this->recurrenceFrequency, 'rule'); @@ -335,11 +416,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function getURI() { - $uri = '/'.$this->getMonogram(); - if ($this->isGhostEvent) { - $uri = $uri.'/'.$this->sequenceIndex; + if ($this->getIsGhostEvent()) { + $base = $this->getParentEvent()->getURI(); + $sequence = $this->getSequenceIndex(); + return "{$base}/{$sequence}/"; } - return $uri; + + return '/'.$this->getMonogram(); } public function getParentEvent() { @@ -351,37 +434,25 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this; } - public function getIsCancelled() { - $instance_of = $this->instanceOfEventPHID; - if ($instance_of != null && $this->getIsParentCancelled()) { - return true; - } - return $this->isCancelled; + public function isParentEvent() { + return ($this->isRecurring && !$this->instanceOfEventPHID); } - public function getIsRecurrenceParent() { - if ($this->isRecurring && !$this->instanceOfEventPHID) { - return true; - } - return false; + public function isChildEvent() { + return ($this->instanceOfEventPHID !== null); } - public function getIsRecurrenceException() { - if ($this->instanceOfEventPHID && !$this->isGhostEvent) { + public function isCancelledEvent() { + if ($this->getIsCancelled()) { return true; } - return false; - } - public function getIsParentCancelled() { - if ($this->instanceOfEventPHID == null) { - return false; + if ($this->isChildEvent()) { + if ($this->getParentEvent()->getIsCancelled()) { + return true; + } } - $recurring_event = $this->getParentEvent(); - if ($recurring_event->getIsCancelled()) { - return true; - } return false; } @@ -408,6 +479,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } } + /* -( Markup Interface )--------------------------------------------------- */ diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 7ad653f486..6a70faf29f 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -314,6 +314,8 @@ final class ConpherenceThreadQuery $events = array(); if ($participant_phids) { + // TODO: All of this Calendar code is probably extra-broken, but none + // of it is currently reachable in the UI. $events = id(new PhabricatorCalendarEventQuery()) ->setViewer($this->getViewer()) ->withInvitedPHIDs($participant_phids) diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index 5ea4e6aa1c..adc3d87560 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -69,7 +69,6 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication { '' => 'PhabricatorPeopleProfileViewController', 'panel/' => $this->getPanelRouting('PhabricatorPeopleProfilePanelController'), - 'calendar/' => 'PhabricatorPeopleCalendarController', ), ); } diff --git a/src/applications/people/controller/PhabricatorPeopleCalendarController.php b/src/applications/people/controller/PhabricatorPeopleCalendarController.php deleted file mode 100644 index ed3d557fb8..0000000000 --- a/src/applications/people/controller/PhabricatorPeopleCalendarController.php +++ /dev/null @@ -1,97 +0,0 @@ -getViewer(); - $username = $request->getURIData('username'); - - $user = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withUsernames(array($username)) - ->needProfileImage(true) - ->executeOne(); - if (!$user) { - return new Aphront404Response(); - } - - $this->setUser($user); - - $picture = $user->getProfileImageURI(); - - $now = time(); - $request = $this->getRequest(); - $year_d = phabricator_format_local_time($now, $user, 'Y'); - $year = $request->getInt('year', $year_d); - $month_d = phabricator_format_local_time($now, $user, 'm'); - $month = $request->getInt('month', $month_d); - $day = phabricator_format_local_time($now, $user, 'j'); - - $start_epoch = strtotime("{$year}-{$month}-01"); - $end_epoch = strtotime("{$year}-{$month}-01 next month"); - - $statuses = id(new PhabricatorCalendarEventQuery()) - ->setViewer($user) - ->withInvitedPHIDs(array($user->getPHID())) - ->withDateRange( - $start_epoch, - $end_epoch) - ->execute(); - - $start_range_value = AphrontFormDateControlValue::newFromEpoch( - $user, - $start_epoch); - $end_range_value = AphrontFormDateControlValue::newFromEpoch( - $user, - $end_epoch); - - if ($month == $month_d && $year == $year_d) { - $month_view = new PHUICalendarMonthView( - $start_range_value, - $end_range_value, - $month, - $year, - $day); - } else { - $month_view = new PHUICalendarMonthView( - $start_range_value, - $end_range_value, - $month, - $year); - } - - $month_view->setBrowseURI($request->getRequestURI()); - $month_view->setUser($user); - $month_view->setImage($picture); - - $phids = mpull($statuses, 'getUserPHID'); - $handles = $this->loadViewerHandles($phids); - - foreach ($statuses as $status) { - $event = new AphrontCalendarEventView(); - $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); - $event->setUserPHID($status->getUserPHID()); - $event->setName($status->getName()); - $event->setDescription($status->getDescription()); - $event->setEventID($status->getID()); - $month_view->addEvent($event); - } - - $nav = $this->getProfileMenu(); - $nav->selectFilter('calendar'); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Calendar')); - - return $this->newPage() - ->setTitle(pht('Calendar')) - ->setNavigation($nav) - ->setCrumbs($crumbs) - ->appendChild($month_view); - } -} From ffdb9f06f859585ab6d65ff85dfcd6f5a085fc00 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 Jul 2016 09:12:03 -0700 Subject: [PATCH 13/76] Move more event fields to EditEngine Summary: Ref T9275. This moves description, icon, and cancel/uncancel to EditEngine. It removes TYPE_SEQUENCE_INDEX and TYPE_INSTANCE_OF_EVENT. These are currently never generated and I do not expect to genereate them (instead, these changes happen automatically when you edit a stub). Test Plan: Edited an event with normal and pro edit forms. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16264 --- .../editor/PhabricatorCalendarEditEngine.php | 33 +++++++++++++++++-- .../editor/PhabricatorCalendarEventEditor.php | 21 +++--------- .../PhabricatorCalendarEventTransaction.php | 15 --------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php index 33a8462075..4aaab4634d 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php @@ -65,11 +65,40 @@ final class PhabricatorCalendarEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the event.')) + ->setIsRequired(true) + ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_NAME) ->setConduitDescription(pht('Rename the event.')) ->setConduitTypeDescription(pht('New event name.')) - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_NAME) - ->setIsRequired(true) ->setValue($object->getName()), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Description')) + ->setDescription(pht('Description of the event.')) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION) + ->setConduitDescription(pht('Update the event description.')) + ->setConduitTypeDescription(pht('New event description.')) + ->setValue($object->getDescription()), + id(new PhabricatorBoolEditField()) + ->setKey('cancelled') + ->setOptions(pht('Active'), pht('Cancelled')) + ->setLabel(pht('Cancelled')) + ->setDescription(pht('Cancel the event.')) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_CANCEL) + ->setIsConduitOnly(true) + ->setConduitDescription(pht('Cancel or restore the event.')) + ->setConduitTypeDescription(pht('True to cancel the event.')) + ->setValue($object->getIsCancelled()), + id(new PhabricatorIconSetEditField()) + ->setKey('icon') + ->setLabel(pht('Icon')) + ->setIconSet(new PhabricatorCalendarIconSet()) + ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_ICON) + ->setDescription(pht('Event icon.')) + ->setConduitDescription(pht('Change the event icon.')) + ->setConduitTypeDescription(pht('New event icon.')) + ->setValue($object->getIcon()), ); return $fields; diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index e8ef5528e8..5b46a42e7c 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -40,6 +40,8 @@ final class PhabricatorCalendarEventEditor $event->setIsStub(0); $invitees = $event->getParentEvent()->getInvitees(); + + $new_invitees = array(); foreach ($invitees as $invitee) { $invitee = id(new PhabricatorCalendarEventInvitee()) ->setEventPHID($event->getPHID()) @@ -47,9 +49,12 @@ final class PhabricatorCalendarEventEditor ->setInviterPHID($invitee->getInviterPHID()) ->setStatus($invitee->getStatus()) ->save(); + + $new_invitees[] = $invitee; } $event->save(); + $event->attachInvitees($new_invitees); } public function getTransactionTypes() { @@ -67,8 +72,6 @@ final class PhabricatorCalendarEventEditor $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRING; $types[] = PhabricatorCalendarEventTransaction::TYPE_FREQUENCY; $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; - $types[] = PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT; - $types[] = PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX; $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -87,10 +90,6 @@ final class PhabricatorCalendarEventEditor return $object->getRecurrenceFrequency(); case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: return $object->getRecurrenceEndDate(); - case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: - return $object->getInstanceOfEventPHID(); - case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: - return $object->getSequenceIndex(); case PhabricatorCalendarEventTransaction::TYPE_NAME: return $object->getName(); case PhabricatorCalendarEventTransaction::TYPE_START_DATE: @@ -131,8 +130,6 @@ final class PhabricatorCalendarEventEditor switch ($xaction->getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_RECURRING: case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: - case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: - case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: @@ -159,10 +156,6 @@ final class PhabricatorCalendarEventEditor return $object->setIsRecurring($xaction->getNewValue()); case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: return $object->setRecurrenceFrequency($xaction->getNewValue()); - case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: - return $object->setInstanceOfEventPHID($xaction->getNewValue()); - case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: - return $object->setSequenceIndex($xaction->getNewValue()); case PhabricatorCalendarEventTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; @@ -202,8 +195,6 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_RECURRING: case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: - case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: @@ -253,8 +244,6 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_RECURRING: case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: - case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index f9c9e79409..573428d8bc 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -16,9 +16,6 @@ final class PhabricatorCalendarEventTransaction const TYPE_FREQUENCY = 'calendar.frequency'; const TYPE_RECURRENCE_END_DATE = 'calendar.recurrenceenddate'; - const TYPE_INSTANCE_OF_EVENT = 'calendar.instanceofevent'; - const TYPE_SEQUENCE_INDEX = 'calendar.sequenceindex'; - const MAILTAG_RESCHEDULE = 'calendar-reschedule'; const MAILTAG_CONTENT = 'calendar-content'; const MAILTAG_OTHER = 'calendar-other'; @@ -48,8 +45,6 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_RECURRING: case self::TYPE_FREQUENCY: case self::TYPE_RECURRENCE_END_DATE: - case self::TYPE_INSTANCE_OF_EVENT: - case self::TYPE_SEQUENCE_INDEX: $phids[] = $this->getObjectPHID(); break; case self::TYPE_INVITE: @@ -75,8 +70,6 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_RECURRING: case self::TYPE_FREQUENCY: case self::TYPE_RECURRENCE_END_DATE: - case self::TYPE_INSTANCE_OF_EVENT: - case self::TYPE_SEQUENCE_INDEX: return ($old === null); } return parent::shouldHide(); @@ -95,8 +88,6 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_RECURRING: case self::TYPE_FREQUENCY: case self::TYPE_RECURRENCE_END_DATE: - case self::TYPE_INSTANCE_OF_EVENT: - case self::TYPE_SEQUENCE_INDEX: return 'fa-pencil'; break; case self::TYPE_INVITE: @@ -283,9 +274,6 @@ final class PhabricatorCalendarEventTransaction $text = pht('%s has changed the recurrence end date of this event.', $this->renderHandleLink($author_phid)); return $text; - case self::TYPE_INSTANCE_OF_EVENT: - case self::TYPE_SEQUENCE_INDEX: - return pht('Recurring event has been updated.'); } return parent::getTitle(); } @@ -501,9 +489,6 @@ final class PhabricatorCalendarEventTransaction $this->renderHandleLink($object_phid), $new); return $text; - case self::TYPE_INSTANCE_OF_EVENT: - case self::TYPE_SEQUENCE_INDEX: - return pht('Recurring event has been updated.'); } return parent::getTitleForFeed(); From 3a09bb577e0a6f610c7f96e6501e7b350a3ed713 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Jul 2016 15:08:53 -0700 Subject: [PATCH 14/76] Create separate "Accept" and "Decline" transactions for Calendar Summary: Ref T9275. Currently, there's a single "invite" transaction type for managing Calendar invites, and it takes a map of invitees to status. This isn't great for EditEngine or API access, since it lets you set anyone else to any status and we can't reuse as much code as we can with a simpler API. Make "Accept" and "Decline" separate actions which affect the actor's invite, so "invite" can be a simpler transaction which just invites or uninvites people. Test Plan: - Joined/accepted/declined an event invitation. - Edited event invitees. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16272 --- ...PhabricatorCalendarEventJoinController.php | 57 +++++++------------ .../editor/PhabricatorCalendarEventEditor.php | 40 +++++++++++++ .../PhabricatorCalendarEventTransaction.php | 21 +++++++ 3 files changed, 83 insertions(+), 35 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php index d69a065f9b..b69dbe1bfd 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php @@ -3,18 +3,9 @@ final class PhabricatorCalendarEventJoinController extends PhabricatorCalendarController { - const ACTION_ACCEPT = 'accept'; - const ACTION_DECLINE = 'decline'; - const ACTION_JOIN = 'join'; - public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $id = $request->getURIData('id'); - $action = $request->getURIData('action'); - - $request = $this->getRequest(); - $viewer = $request->getViewer(); - $declined_status = PhabricatorCalendarEventInvitee::STATUS_DECLINED; - $attending_status = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) @@ -25,34 +16,31 @@ final class PhabricatorCalendarEventJoinController } $cancel_uri = $event->getURI(); + + $action = $request->getURIData('action'); + switch ($action) { + case 'accept': + $is_join = true; + break; + case 'decline': + $is_join = false; + break; + default: + $is_join = !$event->getIsUserAttending($viewer->getPHID()); + break; + } + $validation_exception = null; - - $is_attending = $event->getIsUserAttending($viewer->getPHID()); - if ($request->isFormPost()) { - $new_status = null; - - switch ($action) { - case self::ACTION_ACCEPT: - $new_status = $attending_status; - break; - case self::ACTION_JOIN: - if ($is_attending) { - $new_status = $declined_status; - } else { - $new_status = $attending_status; - } - break; - case self::ACTION_DECLINE: - $new_status = $declined_status; - break; + if ($is_join) { + $xaction_type = PhabricatorCalendarEventTransaction::TYPE_ACCEPT; + } else { + $xaction_type = PhabricatorCalendarEventTransaction::TYPE_DECLINE; } - $new_status = array($viewer->getPHID() => $new_status); - $xaction = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_INVITE) - ->setNewValue($new_status); + ->setTransactionType($xaction_type) + ->setNewValue(true); $editor = id(new PhabricatorCalendarEventEditor()) ->setActor($viewer) @@ -68,8 +56,7 @@ final class PhabricatorCalendarEventJoinController } } - if (($action == self::ACTION_JOIN && !$is_attending) - || $action == self::ACTION_ACCEPT) { + if ($is_join) { $title = pht('Join Event'); $paragraph = pht('Would you like to join this event?'); $submit = pht('Join'); diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index 5b46a42e7c..f632abca02 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -68,6 +68,8 @@ final class PhabricatorCalendarEventEditor $types[] = PhabricatorCalendarEventTransaction::TYPE_INVITE; $types[] = PhabricatorCalendarEventTransaction::TYPE_ALL_DAY; $types[] = PhabricatorCalendarEventTransaction::TYPE_ICON; + $types[] = PhabricatorCalendarEventTransaction::TYPE_ACCEPT; + $types[] = PhabricatorCalendarEventTransaction::TYPE_DECLINE; $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRING; $types[] = PhabricatorCalendarEventTransaction::TYPE_FREQUENCY; @@ -104,6 +106,10 @@ final class PhabricatorCalendarEventEditor return (int)$object->getIsAllDay(); case PhabricatorCalendarEventTransaction::TYPE_ICON: return $object->getIcon(); + case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: + case PhabricatorCalendarEventTransaction::TYPE_DECLINE: + $actor_phid = $this->getActingAsPHID(); + return $object->getUserInviteStatus($actor_phid); case PhabricatorCalendarEventTransaction::TYPE_INVITE: $map = $xaction->getNewValue(); $phids = array_keys($map); @@ -136,6 +142,10 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_INVITE: case PhabricatorCalendarEventTransaction::TYPE_ICON: return $xaction->getNewValue(); + case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: + return PhabricatorCalendarEventInvitee::STATUS_ATTENDING; + case PhabricatorCalendarEventTransaction::TYPE_DECLINE: + return PhabricatorCalendarEventInvitee::STATUS_DECLINED; case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: return (int)$xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: @@ -181,6 +191,8 @@ final class PhabricatorCalendarEventEditor $object->setIcon($xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_INVITE: + case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: + case PhabricatorCalendarEventTransaction::TYPE_DECLINE: return; } @@ -223,6 +235,28 @@ final class PhabricatorCalendarEventEditor } $object->attachInvitees($invitees); return; + case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: + case PhabricatorCalendarEventTransaction::TYPE_DECLINE: + $acting_phid = $this->getActingAsPHID(); + + $invitees = $object->getInvitees(); + $invitees = mpull($invitees, null, 'getInviteePHID'); + + $invitee = idx($invitees, $acting_phid); + if (!$invitee) { + $invitee = id(new PhabricatorCalendarEventInvitee()) + ->setEventPHID($object->getPHID()) + ->setInviteePHID($acting_phid) + ->setInviterPHID($acting_phid); + $invitees[$acting_phid] = $invitee; + } + + $invitee + ->setStatus($xaction->getNewValue()) + ->save(); + + $object->attachInvitees($invitees); + return; } return parent::applyCustomExternalTransaction($object, $xaction); @@ -252,6 +286,12 @@ final class PhabricatorCalendarEventEditor // caches for all attendees. $invalidate_all = true; break; + + case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: + case PhabricatorCalendarEventTransaction::TYPE_DECLINE: + $acting_phid = $this->getActingAsPHID(); + $invalidate_phids[$acting_phid] = $acting_phid; + break; case PhabricatorCalendarEventTransaction::TYPE_INVITE: foreach ($xaction->getNewValue() as $phid => $ignored) { $invalidate_phids[$phid] = $phid; diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index 573428d8bc..584c41e569 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -11,11 +11,14 @@ final class PhabricatorCalendarEventTransaction const TYPE_ALL_DAY = 'calendar.allday'; const TYPE_ICON = 'calendar.icon'; const TYPE_INVITE = 'calendar.invite'; + const TYPE_ACCEPT = 'calendar.accept'; + const TYPE_DECLINE = 'calendar.decline'; const TYPE_RECURRING = 'calendar.recurring'; const TYPE_FREQUENCY = 'calendar.frequency'; const TYPE_RECURRENCE_END_DATE = 'calendar.recurrenceenddate'; + const MAILTAG_RESCHEDULE = 'calendar-reschedule'; const MAILTAG_CONTENT = 'calendar-content'; const MAILTAG_OTHER = 'calendar-other'; @@ -163,6 +166,14 @@ final class PhabricatorCalendarEventTransaction '%s reinstated this event.', $this->renderHandleLink($author_phid)); } + case self::TYPE_ACCEPT: + return pht( + '%s is attending this event.', + $this->renderHandleLink($author_phid)); + case self::TYPE_DECLINE: + return pht( + '%s declined this event.', + $this->renderHandleLink($author_phid)); case self::TYPE_INVITE: $text = null; @@ -363,6 +374,16 @@ final class PhabricatorCalendarEventTransaction $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } + case self::TYPE_ACCEPT: + return pht( + '%s is attending %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + case self::TYPE_DECLINE: + return pht( + '%s declined %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); case self::TYPE_INVITE: $text = null; From c09e870733112e5eb98e1153551cf3e5894c27d8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Jul 2016 15:29:11 -0700 Subject: [PATCH 15/76] Prepare event dates for EditEngine/API Summary: Ref T9275. Currently, the "Start Date", "End Date", and "Recurrence End Date" transcations take a complex value (AphrontFormDateControlValue) and reduce it to an epoch. Do this a little earlier, since the API will be much more usable if it just passes in epoch timestamps. Events also have some logic where they rewrite the from date and to date on the actual object for all day events, then undo the changes later. Specifically, if you have an all-day event on "July 24th", the exact start and end times vary based on who is looking at it. Instead of overwriting the persistent `dateFrom` and `dateTo` properties, add separate `viewer` properties to make it easier to keep this stuff straight. Since this means all-day events get stored in UTC, we need to query/fetch (and then discard) slightly more events. This is perfectly and much simpler to do. The one weird "UTC" hack in here will get nuked when this moves to EditEngine properly. Test Plan: Edited times for normal events and all-day events. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16274 --- ...PhabricatorCalendarEventDragController.php | 3 +- ...PhabricatorCalendarEventEditController.php | 21 +++- ...PhabricatorCalendarEventViewController.php | 14 +-- .../editor/PhabricatorCalendarEventEditor.php | 25 +--- .../query/PhabricatorCalendarEventQuery.php | 29 +++-- .../PhabricatorCalendarEventSearchEngine.php | 31 ++--- .../storage/PhabricatorCalendarEvent.php | 113 +++++++----------- ...PhabricatorPeopleProfileViewController.php | 41 ++++--- .../people/query/PhabricatorPeopleQuery.php | 9 +- 9 files changed, 135 insertions(+), 151 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php index 6f88aae0c7..7fc3ce8e78 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php @@ -29,7 +29,7 @@ final class PhabricatorCalendarEventDragController $xactions = array(); - $duration = $event->getDateTo() - $event->getDateFrom(); + $duration = $event->getDuration(); $start = $request->getInt('start'); $start_value = id(AphrontFormDateControlValue::newFromEpoch( @@ -50,7 +50,6 @@ final class PhabricatorCalendarEventDragController ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_END_DATE) ->setNewValue($end_value); - $editor = id(new PhabricatorCalendarEventEditor()) ->setActor($viewer) ->setContinueOnMissingFields(true) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 0f960ba1ac..888f52c572 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -91,10 +91,10 @@ final class PhabricatorCalendarEventEditController $end_value = AphrontFormDateControlValue::newFromEpoch( $viewer, - $event->getDateTo()); + $event->getViewerDateTo()); $start_value = AphrontFormDateControlValue::newFromEpoch( $viewer, - $event->getDateFrom()); + $event->getViewerDateFrom()); $recurrence_end_date_value = id(clone $end_value) ->setOptional(true); @@ -137,7 +137,17 @@ final class PhabricatorCalendarEventEditController $view_policy = $event->getViewPolicy(); $space = $event->getSpacePHID(); + if ($request->isFormPost()) { + $is_all_day = $request->getStr('isAllDay'); + + if ($is_all_day) { + // TODO: This is a very gross temporary hack to get this working + // reasonably: if this is an all day event, force the viewer's + // timezone to UTC so the date controls get interpreted as UTC. + $viewer->overrideTimezoneIdentifier('UTC'); + } + $xactions = array(); $name = $request->getStr('name'); @@ -159,7 +169,6 @@ final class PhabricatorCalendarEventEditController $space = $request->getStr('spacePHID'); $is_recurring = $request->getStr('isRecurring') ? 1 : 0; $frequency = $request->getStr('frequency'); - $is_all_day = $request->getStr('isAllDay'); $icon = $request->getStr('icon'); $invitees = $request->getArr('invitees'); @@ -192,7 +201,7 @@ final class PhabricatorCalendarEventEditController $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE) - ->setNewValue($recurrence_end_date_value); + ->setNewValue($recurrence_end_date_value->getEpoch()); } } @@ -210,12 +219,12 @@ final class PhabricatorCalendarEventEditController $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_START_DATE) - ->setNewValue($start_value); + ->setNewValue($start_value->getEpoch()); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_END_DATE) - ->setNewValue($end_value); + ->setNewValue($end_value->getEpoch()); } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 933b258e3b..691fd579c9 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -198,29 +198,29 @@ final class PhabricatorCalendarEventViewController ->setUser($viewer); if ($event->getIsAllDay()) { - $date_start = phabricator_date($event->getDateFrom(), $viewer); - $date_end = phabricator_date($event->getDateTo(), $viewer); + $date_start = phabricator_date($event->getViewerDateFrom(), $viewer); + $date_end = phabricator_date($event->getViewerDateTo(), $viewer); if ($date_start == $date_end) { $properties->addProperty( pht('Time'), - phabricator_date($event->getDateFrom(), $viewer)); + phabricator_date($event->getViewerDateFrom(), $viewer)); } else { $properties->addProperty( pht('Starts'), - phabricator_date($event->getDateFrom(), $viewer)); + phabricator_date($event->getViewerDateFrom(), $viewer)); $properties->addProperty( pht('Ends'), - phabricator_date($event->getDateTo(), $viewer)); + phabricator_date($event->getViewerDateTo(), $viewer)); } } else { $properties->addProperty( pht('Starts'), - phabricator_datetime($event->getDateFrom(), $viewer)); + phabricator_datetime($event->getViewerDateFrom(), $viewer)); $properties->addProperty( pht('Ends'), - phabricator_datetime($event->getDateTo(), $viewer)); + phabricator_datetime($event->getViewerDateTo(), $viewer)); } if ($event->getIsRecurring()) { diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index f632abca02..ce886d2ed6 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -22,8 +22,6 @@ final class PhabricatorCalendarEventEditor array $xactions) { $actor = $this->requireActor(); - $object->removeViewerTimezone($actor); - if ($object->getIsStub()) { $this->materializeStub($object); } @@ -151,7 +149,7 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - return $xaction->getNewValue()->getEpoch(); + return $xaction->getNewValue(); } return parent::getCustomTransactionNewValue($object, $xaction); @@ -308,6 +306,8 @@ final class PhabricatorCalendarEventEditor } if ($phids) { + $object->applyViewerTimezone($this->getActor()); + $user = new PhabricatorUser(); $conn_w = $user->establishConnection('w'); queryfx( @@ -344,15 +344,16 @@ final class PhabricatorCalendarEventEditor foreach ($xactions as $xaction) { if ($xaction->getTransactionType() == $start_date_xaction) { - $start_date = $xaction->getNewValue()->getEpoch(); + $start_date = $xaction->getNewValue(); } else if ($xaction->getTransactionType() == $end_date_xaction) { - $end_date = $xaction->getNewValue()->getEpoch(); + $end_date = $xaction->getNewValue(); } else if ($xaction->getTransactionType() == $recurrence_end_xaction) { $recurrence_end = $xaction->getNewValue(); } else if ($xaction->getTransactionType() == $is_recurrence_xaction) { $is_recurring = $xaction->getNewValue(); } } + if ($start_date > $end_date) { $type = PhabricatorCalendarEventTransaction::TYPE_END_DATE; $errors[] = new PhabricatorApplicationTransactionValidationError( @@ -399,20 +400,6 @@ final class PhabricatorCalendarEventEditor $errors[] = $error; } break; - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - foreach ($xactions as $xaction) { - $date_value = $xaction->getNewValue(); - if (!$date_value->isValid()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Invalid date.'), - $xaction); - } - } - break; } return $errors; diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 2771fc0497..3eac223018 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -90,7 +90,7 @@ final class PhabricatorCalendarEventQuery protected function getPagingValueMap($cursor, array $keys) { $event = $this->loadCursorObject($cursor); return array( - 'start' => $event->getDateFrom(), + 'start' => $event->getViewerDateFrom(), 'id' => $event->getID(), ); } @@ -121,7 +121,7 @@ final class PhabricatorCalendarEventQuery foreach ($events as $key => $event) { $sequence_start = 0; $sequence_end = null; - $duration = $event->getDateTo() - $event->getDateFrom(); + $duration = $event->getDuration(); $end = null; $instance_of = $event->getInstanceOfEventPHID(); @@ -137,9 +137,10 @@ final class PhabricatorCalendarEventQuery $frequency = $event->getFrequencyUnit(); $modify_key = '+1 '.$frequency; - if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) { + if (($this->rangeBegin !== null) && + ($this->rangeBegin > $event->getViewerDateFrom())) { $max_date = $this->rangeBegin - $duration; - $date = $event->getDateFrom(); + $date = $event->getViewerDateFrom(); $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); while ($date < $max_date) { @@ -151,7 +152,7 @@ final class PhabricatorCalendarEventQuery $start = $this->rangeBegin; } else { - $start = $event->getDateFrom() - $duration; + $start = $event->getViewerDateFrom() - $duration; } $date = $start; @@ -199,9 +200,9 @@ final class PhabricatorCalendarEventQuery if ($raw_limit) { if (count($events) >= $raw_limit) { - $events = msort($events, 'getDateFrom'); + $events = msort($events, 'getViewerDateFrom'); $events = array_slice($events, 0, $raw_limit, true); - $enforced_end = last($events)->getDateFrom(); + $enforced_end = last($events)->getViewerDateFrom(); } } } @@ -303,18 +304,22 @@ final class PhabricatorCalendarEventQuery $this->phids); } + // NOTE: The date ranges we query for are larger than the requested ranges + // because we need to catch all-day events. We'll refine this range later + // after adjusting the visible range of events we load. + if ($this->rangeBegin) { $where[] = qsprintf( $conn, 'event.dateTo >= %d OR event.isRecurring = 1', - $this->rangeBegin); + $this->rangeBegin - phutil_units('16 hours in seconds')); } if ($this->rangeEnd) { $where[] = qsprintf( $conn, 'event.dateFrom <= %d', - $this->rangeEnd); + $this->rangeEnd + phutil_units('16 hours in seconds')); } if ($this->inviteePHIDs !== null) { @@ -399,8 +404,8 @@ final class PhabricatorCalendarEventQuery $viewer = $this->getViewer(); foreach ($events as $key => $event) { - $event_start = $event->getDateFrom(); - $event_end = $event->getDateTo(); + $event_start = $event->getViewerDateFrom(); + $event_end = $event->getViewerDateTo(); if ($range_start && $event_end < $range_start) { unset($events[$key]); @@ -466,7 +471,7 @@ final class PhabricatorCalendarEventQuery } } - $events = msort($events, 'getDateFrom'); + $events = msort($events, 'getViewerDateFrom'); return $events; } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 8258475158..3465d3ee71 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -311,11 +311,10 @@ final class PhabricatorCalendarEventSearchEngine $item->addAttribute($attending); } - if (strlen($event->getDuration()) > 0) { + if ($event->getDuration()) { $duration = pht( 'Duration: %s', - $event->getDuration()); - + $event->getDisplayDuration()); $item->addIcon('none', $duration); } @@ -370,7 +369,9 @@ final class PhabricatorCalendarEventSearchEngine $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); $event = new AphrontCalendarEventView(); - $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); + $event->setEpochRange( + $status->getViewerDateFrom(), + $status->getViewerDateTo()); $event->setIsAllDay($status->getIsAllDay()); $event->setIcon($status->getIcon()); @@ -434,7 +435,9 @@ final class PhabricatorCalendarEventSearchEngine $event = new AphrontCalendarEventView(); $event->setCanEdit($can_edit); $event->setEventID($status->getID()); - $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); + $event->setEpochRange( + $status->getViewerDateFrom(), + $status->getViewerDateTo()); $event->setIsAllDay($status->getIsAllDay()); $event->setIcon($status->getIcon()); $event->setViewerIsInvited($viewer_is_invited); @@ -553,10 +556,10 @@ final class PhabricatorCalendarEventSearchEngine $viewer = $this->requireViewer(); $from_datetime = PhabricatorTime::getDateTimeFromEpoch( - $event->getDateFrom(), + $event->getViewerDateFrom(), $viewer); $to_datetime = PhabricatorTime::getDateTimeFromEpoch( - $event->getDateTo(), + $event->getViewerDateTo(), $viewer); $from_date_formatted = $from_datetime->format('Y m d'); @@ -566,23 +569,23 @@ final class PhabricatorCalendarEventSearchEngine if ($from_date_formatted == $to_date_formatted) { return pht( '%s, All Day', - phabricator_date($event->getDateFrom(), $viewer)); + phabricator_date($event->getViewerDateFrom(), $viewer)); } else { return pht( '%s - %s, All Day', - phabricator_date($event->getDateFrom(), $viewer), - phabricator_date($event->getDateTo(), $viewer)); + phabricator_date($event->getViewerDateFrom(), $viewer), + phabricator_date($event->getViewerDateTo(), $viewer)); } } else if ($from_date_formatted == $to_date_formatted) { return pht( '%s - %s', - phabricator_datetime($event->getDateFrom(), $viewer), - phabricator_time($event->getDateTo(), $viewer)); + phabricator_datetime($event->getViewerDateFrom(), $viewer), + phabricator_time($event->getViewerDateTo(), $viewer)); } else { return pht( '%s - %s', - phabricator_datetime($event->getDateFrom(), $viewer), - phabricator_datetime($event->getDateTo(), $viewer)); + phabricator_datetime($event->getViewerDateFrom(), $viewer), + phabricator_datetime($event->getViewerDateTo(), $viewer)); } } } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 25734f92b0..991bfbf521 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -41,7 +41,9 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO private $parentEvent = self::ATTACHABLE; private $invitees = self::ATTACHABLE; - private $appliedViewer; + + private $viewerDateFrom; + private $viewerDateTo; // Frequency Constants const FREQUENCY_DAILY = 'daily'; @@ -157,7 +159,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $date_time->modify($modify_key); $date = $date_time->format('U'); - $duration = $parent->getDateTo() - $parent->getDateFrom(); + $duration = $this->getDuration(); $this ->setDateFrom($date) @@ -192,74 +194,49 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $ghost; } + public function getViewerDateFrom() { + if ($this->viewerDateFrom === null) { + throw new PhutilInvalidStateException('applyViewerTimezone'); + } + + return $this->viewerDateFrom; + } + + public function getViewerDateTo() { + if ($this->viewerDateTo === null) { + throw new PhutilInvalidStateException('applyViewerTimezone'); + } + + return $this->viewerDateTo; + } + public function applyViewerTimezone(PhabricatorUser $viewer) { - if ($this->appliedViewer) { - throw new Exception(pht('Viewer timezone is already applied!')); - } - - $this->appliedViewer = $viewer; - if (!$this->getIsAllDay()) { - return $this; - } + $this->viewerDateFrom = $this->getDateFrom(); + $this->viewerDateTo = $this->getDateTo(); + } else { + $zone = $viewer->getTimeZone(); - $zone = $viewer->getTimeZone(); - - - $this->setDateFrom( - $this->getDateEpochForTimeZone( + $this->viewerDateFrom = $this->getDateEpochForTimeZone( $this->getDateFrom(), - new DateTimeZone('Pacific/Kiritimati'), + new DateTimeZone('UTC'), 'Y-m-d', null, - $zone)); + $zone); - $this->setDateTo( - $this->getDateEpochForTimeZone( + $this->viewerDateTo = $this->getDateEpochForTimeZone( $this->getDateTo(), - new DateTimeZone('Pacific/Midway'), + new DateTimeZone('UTC'), 'Y-m-d 23:59:00', - '-1 day', - $zone)); + null, + $zone); + } return $this; } - - public function removeViewerTimezone(PhabricatorUser $viewer) { - if (!$this->appliedViewer) { - throw new Exception(pht('Viewer timezone is not applied!')); - } - - if ($viewer->getPHID() != $this->appliedViewer->getPHID()) { - throw new Exception(pht('Removed viewer must match applied viewer!')); - } - - $this->appliedViewer = null; - - if (!$this->getIsAllDay()) { - return $this; - } - - $zone = $viewer->getTimeZone(); - - $this->setDateFrom( - $this->getDateEpochForTimeZone( - $this->getDateFrom(), - $zone, - 'Y-m-d', - null, - new DateTimeZone('Pacific/Kiritimati'))); - - $this->setDateTo( - $this->getDateEpochForTimeZone( - $this->getDateTo(), - $zone, - 'Y-m-d', - '+1 day', - new DateTimeZone('Pacific/Midway'))); - - return $this; + public function getDuration() { + return $this->getDateTo() - $this->getDateFrom(); } private function getDateEpochForTimeZone( @@ -281,12 +258,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function save() { - if ($this->appliedViewer) { - throw new Exception( - pht( - 'Can not save event with viewer timezone still applied!')); - } - if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } @@ -298,13 +269,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO * Get the event start epoch for evaluating invitee availability. * * When assessing availability, we pretend events start earlier than they - * really. This allows us to mark users away for the entire duration of a + * really do. This allows us to mark users away for the entire duration of a * series of back-to-back meetings, even if they don't strictly overlap. * * @return int Event start date for availability caches. */ public function getDateFromForCache() { - return ($this->getDateFrom() - phutil_units('15 minutes in seconds')); + return ($this->getViewerDateFrom() - phutil_units('15 minutes in seconds')); } protected function getConfiguration() { @@ -456,8 +427,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return false; } - public function getDuration() { - $seconds = $this->dateTo - $this->dateFrom; + public function getDisplayDuration() { + $seconds = $this->getDuration(); $minutes = round($seconds / 60, 1); $hours = round($minutes / 60, 3); $days = round($hours / 24, 2); @@ -470,12 +441,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO round($days, 1)); } else if ($hours >= 1) { return pht( - '%s hour(s)', - round($hours, 1)); + '%s hour(s)', + round($hours, 1)); } else if ($minutes >= 1) { return pht( - '%s minute(s)', - round($minutes, 0)); + '%s minute(s)', + round($minutes, 0)); } } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index e5073eff94..66d5e91af6 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -189,41 +189,44 @@ final class PhabricatorPeopleProfileViewController $range_start = $midnight->format('U'); $range_end = $week_end->format('U'); - $query = id(new PhabricatorCalendarEventQuery()) + $events = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) ->withDateRange($range_start, $range_end) ->withInvitedPHIDs(array($user->getPHID())) - ->withIsCancelled(false); + ->withIsCancelled(false) + ->execute(); - $statuses = $query->execute(); - $phids = mpull($statuses, 'getUserPHID'); - $events = array(); - - foreach ($statuses as $status) { - $viewer_is_invited = $status->getIsUserInvited($user->getPHID()); + $event_views = array(); + foreach ($events as $event) { + $viewer_is_invited = $event->getIsUserInvited($viewer->getPHID()); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, - $status, + $event, PhabricatorPolicyCapability::CAN_EDIT); - $event = id(new AphrontCalendarEventView()) + $epoch_min = $event->getViewerDateFrom(); + $epoch_max = $event->getViewerDateTo(); + + $event_view = id(new AphrontCalendarEventView()) ->setCanEdit($can_edit) - ->setEventID($status->getID()) - ->setEpochRange($status->getDateFrom(), $status->getDateTo()) - ->setIsAllDay($status->getIsAllDay()) - ->setIcon($status->getIcon()) + ->setEventID($event->getID()) + ->setEpochRange($epoch_min, $epoch_max) + ->setIsAllDay($event->getIsAllDay()) + ->setIcon($event->getIcon()) ->setViewerIsInvited($viewer_is_invited) - ->setName($status->getName()) - ->setURI($status->getURI()); - $events[] = $event; + ->setName($event->getName()) + ->setURI($event->getURI()); + + $event_views[] = $event_view; } - $events = msort($events, 'getEpochStart'); + $event_views = msort($event_views, 'getEpochStart'); + $day_view = id(new PHUICalendarWeekView()) ->setViewer($viewer) ->setView('week') - ->setEvents($events) + ->setEvents($event_views) ->setWeekLength(3) ->render(); diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 6c1fa7b68d..3dcb156295 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -413,6 +413,13 @@ final class PhabricatorPeopleQuery foreach ($rebuild as $phid => $user) { $events = idx($map, $phid, array()); + // We loaded events with the omnipotent user, but want to shift them + // into the user's timezone before building the cache because they will + // be unavailable during their own local day. + foreach ($events as $event) { + $event->applyViewerTimezone($user); + } + $cursor = $min_range; if ($events) { // Find the next time when the user has no meetings. If we move forward @@ -420,7 +427,7 @@ final class PhabricatorPeopleQuery while (true) { foreach ($events as $event) { $from = $event->getDateFromForCache(); - $to = $event->getDateTo(); + $to = $event->getViewerDateTo(); if (($from <= $cursor) && ($to > $cursor)) { $cursor = $to; continue 2; From eebaf583421b67f8cb4a615a4ed4299d9bb9bc65 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Jul 2016 09:05:12 -0700 Subject: [PATCH 16/76] Simplify the TYPE_INVITE Calendar Event transaction for EditEngine Summary: Ref T9275. Now that TYPE_ACCEPT and TYPE_DECLINE have been separated out, we can simplify TYPE_INVITE. This now just takes a list of invited PHIDs, uninvites ones that were removed and invites ones that were added. This is simpler, lets more logic live in the Editor, and makes EditEngine/API access easier. Test Plan: Created events, added and removed invitees. Used comment stacked action and "pro" editor to adjust invitees. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16280 --- ...PhabricatorCalendarEventEditController.php | 47 +--------- .../editor/PhabricatorCalendarEditEngine.php | 19 ++++ .../editor/PhabricatorCalendarEventEditor.php | 90 +++++++++++++++---- .../storage/PhabricatorCalendarEvent.php | 13 +++ .../PhabricatorCalendarEventTransaction.php | 9 +- 5 files changed, 117 insertions(+), 61 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 888f52c572..4f92fa5b0c 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -105,15 +105,7 @@ final class PhabricatorCalendarEventEditController $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $event->getPHID()); - $invitees = array(); - foreach ($event->getInvitees() as $invitee) { - if ($invitee->isUninvited()) { - continue; - } else { - $invitees[] = $invitee->getInviteePHID(); - } - } - + $invitees = $event->getInviteePHIDsForEdit(); $cancel_uri = $event->getURI(); } @@ -172,14 +164,6 @@ final class PhabricatorCalendarEventEditController $icon = $request->getStr('icon'); $invitees = $request->getArr('invitees'); - $new_invitees = $this->getNewInviteeList($invitees, $event); - $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; - if ($this->isCreate()) { - $status = idx($new_invitees, $viewer->getPHID()); - if ($status) { - $new_invitees[$viewer->getPHID()] = $status_attending; - } - } $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( @@ -236,7 +220,7 @@ final class PhabricatorCalendarEventEditController $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_INVITE) - ->setNewValue($new_invitees); + ->setNewValue($invitees); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( @@ -487,7 +471,6 @@ final class PhabricatorCalendarEventEditController ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); - $icon = id(new PHUIFormIconSetControl()) ->setLabel(pht('Icon')) ->setName('icon') @@ -574,32 +557,6 @@ final class PhabricatorCalendarEventEditController } - public function getNewInviteeList(array $phids, $event) { - $invitees = $event->getInvitees(); - $invitees = mpull($invitees, null, 'getInviteePHID'); - $invited_status = PhabricatorCalendarEventInvitee::STATUS_INVITED; - $uninvited_status = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; - $phids = array_fuse($phids); - - $new = array(); - foreach ($phids as $phid) { - $old_status = $event->getUserInviteStatus($phid); - if ($old_status != $uninvited_status) { - continue; - } - $new[$phid] = $invited_status; - } - - foreach ($invitees as $invitee) { - $deleted_invitee = !idx($phids, $invitee->getInviteePHID()); - if ($deleted_invitee) { - $new[$invitee->getInviteePHID()] = $uninvited_status; - } - } - - return $new; - } - private function getDefaultTimeValues($viewer) { $start = new DateTime('@'.time()); $start->setTimeZone($viewer->getTimeZone()); diff --git a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php index 4aaab4634d..ee36c7e82e 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php @@ -60,6 +60,14 @@ final class PhabricatorCalendarEditEngine } protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + if ($this->getIsCreate()) { + $invitee_phids = array($viewer->getPHID()); + } else { + $invitee_phids = $object->getInviteePHIDsForEdit(); + } + $fields = array( id(new PhabricatorTextEditField()) ->setKey('name') @@ -90,6 +98,17 @@ final class PhabricatorCalendarEditEngine ->setConduitDescription(pht('Cancel or restore the event.')) ->setConduitTypeDescription(pht('True to cancel the event.')) ->setValue($object->getIsCancelled()), + id(new PhabricatorDatasourceEditField()) + ->setKey('inviteePHIDs') + ->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID')) + ->setLabel(pht('Invitees')) + ->setDatasource(new PhabricatorMetaMTAMailableDatasource()) + ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_INVITE) + ->setDescription(pht('Users invited to the event.')) + ->setConduitDescription(pht('Change invited users.')) + ->setConduitTypeDescription(pht('New event invitees.')) + ->setValue($invitee_phids) + ->setCommentActionLabel(pht('Change Invitees')), id(new PhabricatorIconSetEditField()) ->setKey('icon') ->setLabel(pht('Icon')) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index ce886d2ed6..16e00a2873 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -109,20 +109,8 @@ final class PhabricatorCalendarEventEditor $actor_phid = $this->getActingAsPHID(); return $object->getUserInviteStatus($actor_phid); case PhabricatorCalendarEventTransaction::TYPE_INVITE: - $map = $xaction->getNewValue(); - $phids = array_keys($map); - $invitees = mpull($object->getInvitees(), null, 'getInviteePHID'); - - $old = array(); - foreach ($phids as $phid) { - $invitee = idx($invitees, $phid); - if ($invitee) { - $old[$phid] = $invitee->getStatus(); - } else { - $old[$phid] = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; - } - } - return $old; + $invitees = $object->getInvitees(); + return mpull($invitees, 'getStatus', 'getInviteePHID'); } return parent::getCustomTransactionOldValue($object, $xaction); @@ -137,7 +125,6 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: - case PhabricatorCalendarEventTransaction::TYPE_INVITE: case PhabricatorCalendarEventTransaction::TYPE_ICON: return $xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: @@ -150,6 +137,45 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: return $xaction->getNewValue(); + case PhabricatorCalendarEventTransaction::TYPE_INVITE: + $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; + $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; + $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; + + $invitees = $object->getInvitees(); + foreach ($invitees as $key => $invitee) { + if ($invitee->getStatus() == $status_uninvited) { + unset($invitees[$key]); + } + } + $invitees = mpull($invitees, null, 'getInviteePHID'); + + $new = $xaction->getNewValue(); + $new = array_fuse($new); + + $all = array_keys($invitees + $new); + $map = array(); + foreach ($all as $phid) { + $is_old = isset($invitees[$phid]); + $is_new = isset($new[$phid]); + + if ($is_old && !$is_new) { + $map[$phid] = $status_uninvited; + } else if (!$is_old && $is_new) { + $map[$phid] = $status_invited; + } + } + + // If we're creating this event and the actor is inviting themselves, + // mark them as attending. + if ($this->getIsNewObject()) { + $acting_phid = $this->getActingAsPHID(); + if (isset($map[$acting_phid])) { + $map[$acting_phid] = $status_attending; + } + } + + return $map; } return parent::getCustomTransactionNewValue($object, $xaction); @@ -400,6 +426,40 @@ final class PhabricatorCalendarEventEditor $errors[] = $error; } break; + case PhabricatorCalendarEventTransaction::TYPE_INVITE: + $old = $object->getInvitees(); + $old = mpull($old, null, 'getInviteePHID'); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + $new = array_fuse($new); + $add = array_diff_key($new, $old); + if (!$add) { + continue; + } + + // In the UI, we only allow you to invite mailable objects, but there + // is no definitive marker for "invitable object" today. Just allow + // any valid object to be invited. + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($add) + ->execute(); + $objects = mpull($objects, null, 'getPHID'); + foreach ($add as $phid) { + if (isset($objects[$phid])) { + continue; + } + + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Invitee "%s" identifies an object that does not exist or '. + 'which you do not have permission to view.', + $phid)); + } + } + break; } return $errors; diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 991bfbf521..3fba3f2e66 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -329,6 +329,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this; } + public function getInviteePHIDsForEdit() { + $invitees = array(); + + foreach ($this->getInvitees() as $invitee) { + if ($invitee->isUninvited()) { + continue; + } + $invitees[] = $invitee->getInviteePHID(); + } + + return $invitees; + } + public function getUserInviteStatus($phid) { $invitees = $this->getInvitees(); $invitees = mpull($invitees, null, 'getInviteePHID'); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index 584c41e569..0040ff8817 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -18,7 +18,6 @@ final class PhabricatorCalendarEventTransaction const TYPE_FREQUENCY = 'calendar.frequency'; const TYPE_RECURRENCE_END_DATE = 'calendar.recurrenceenddate'; - const MAILTAG_RESCHEDULE = 'calendar-reschedule'; const MAILTAG_CONTENT = 'calendar-content'; const MAILTAG_OTHER = 'calendar-other'; @@ -177,6 +176,11 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_INVITE: $text = null; + // Fill in any new invitees as "uninvited" in the old data, to make + // some of the rendering logic a little easier. + $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; + $old = $old + array_fill_keys(array_keys($new), $status_uninvited); + if (count($old) === 1 && count($new) === 1 && isset($old[$author_phid])) { @@ -387,6 +391,9 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_INVITE: $text = null; + $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; + $old = $old + array_fill_keys(array_keys($new), $status_uninvited); + if (count($old) === 1 && count($new) === 1 && isset($old[$author_phid])) { From bac6acb3d1199454b5516ccec4261224f93215e1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Jul 2016 10:04:05 -0700 Subject: [PATCH 17/76] Make EditEngine form for Calendar Events almost fully-functional Summary: Ref T9275. This still has a number of rough edges and other minor problems (no JS on the controls, some date handling control bugs) but I'll smooth those over in future changes. It does make all the editable transaction types available from EditEngine, technically speaking. Test Plan: Created and edited events with the "pro" controller, which mostly worked. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16281 --- .../editor/PhabricatorCalendarEditEngine.php | 81 ++++++++++++++++++- .../storage/PhabricatorCalendarEvent.php | 12 +++ .../editfield/PhabricatorEpochEditField.php | 12 +++ 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php index ee36c7e82e..2e1f0dc7ca 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php @@ -68,6 +68,13 @@ final class PhabricatorCalendarEditEngine $invitee_phids = $object->getInviteePHIDsForEdit(); } + $frequency_options = array( + PhabricatorCalendarEvent::FREQUENCY_DAILY => pht('Daily'), + PhabricatorCalendarEvent::FREQUENCY_WEEKLY => pht('Weekly'), + PhabricatorCalendarEvent::FREQUENCY_MONTHLY => pht('Monthly'), + PhabricatorCalendarEvent::FREQUENCY_YEARLY => pht('Yearly'), + ); + $fields = array( id(new PhabricatorTextEditField()) ->setKey('name') @@ -109,7 +116,76 @@ final class PhabricatorCalendarEditEngine ->setConduitTypeDescription(pht('New event invitees.')) ->setValue($invitee_phids) ->setCommentActionLabel(pht('Change Invitees')), - id(new PhabricatorIconSetEditField()) + ); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorBoolEditField()) + ->setKey('isRecurring') + ->setLabel(pht('Recurring')) + ->setOptions(pht('One-Time Event'), pht('Recurring Event')) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_RECURRING) + ->setDescription(pht('One time or recurring event.')) + ->setConduitDescription(pht('Make the event recurring.')) + ->setConduitTypeDescription(pht('Mark the event as a recurring event.')) + ->setValue($object->getIsRecurring()); + + $fields[] = id(new PhabricatorSelectEditField()) + ->setKey('frequency') + ->setLabel(pht('Frequency')) + ->setOptions($frequency_options) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_FREQUENCY) + ->setDescription(pht('Recurring event frequency.')) + ->setConduitDescription(pht('Change the event frequency.')) + ->setConduitTypeDescription(pht('New event frequency.')) + ->setValue($object->getFrequencyUnit()); + } + + if ($this->getIsCreate() || $object->getIsRecurring()) { + $fields[] = id(new PhabricatorEpochEditField()) + ->setAllowNull(true) + ->setKey('until') + ->setLabel(pht('Repeat Until')) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE) + ->setDescription(pht('Last instance of the event.')) + ->setConduitDescription(pht('Change when the event repeats until.')) + ->setConduitTypeDescription(pht('New final event time.')) + ->setValue($object->getRecurrenceEndDate()); + } + + $fields[] = id(new PhabricatorBoolEditField()) + ->setKey('isAllDay') + ->setLabel(pht('All Day')) + ->setOptions(pht('Normal Event'), pht('All Day Event')) + ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_ALL_DAY) + ->setDescription(pht('Marks this as an all day event.')) + ->setConduitDescription(pht('Make the event an all day event.')) + ->setConduitTypeDescription(pht('Mark the event as an all day event.')) + ->setValue($object->getIsAllDay()); + + $fields[] = id(new PhabricatorEpochEditField()) + ->setKey('start') + ->setLabel(pht('Start')) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_START_DATE) + ->setDescription(pht('Start time of the event.')) + ->setConduitDescription(pht('Change the start time of the event.')) + ->setConduitTypeDescription(pht('New event start time.')) + ->setValue($object->getViewerDateFrom()); + + $fields[] = id(new PhabricatorEpochEditField()) + ->setKey('end') + ->setLabel(pht('End')) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_END_DATE) + ->setDescription(pht('End time of the event.')) + ->setConduitDescription(pht('Change the end time of the event.')) + ->setConduitTypeDescription(pht('New event end time.')) + ->setValue($object->getViewerDateTo()); + + $fields[] = id(new PhabricatorIconSetEditField()) ->setKey('icon') ->setLabel(pht('Icon')) ->setIconSet(new PhabricatorCalendarIconSet()) @@ -117,8 +193,7 @@ final class PhabricatorCalendarEditEngine ->setDescription(pht('Event icon.')) ->setConduitDescription(pht('Change the event icon.')) ->setConduitTypeDescription(pht('New event icon.')) - ->setValue($object->getIcon()), - ); + ->setValue($object->getIcon()); return $fields; } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 3fba3f2e66..ec4c8f7404 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -70,6 +70,16 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $is_recurring = true; } + $start = new DateTime('@'.PhabricatorTime::getNow()); + $start->setTimeZone($actor->getTimeZone()); + + $start->setTime($start->format('H'), 0, 0); + $start->modify('+1 hour'); + $end = id(clone $start)->modify('+1 hour'); + + $epoch_min = $start->format('U'); + $epoch_max = $end->format('U'); + return id(new PhabricatorCalendarEvent()) ->setUserPHID($actor->getPHID()) ->setIsCancelled(0) @@ -81,6 +91,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setEditPolicy($actor->getPHID()) ->setSpacePHID($actor->getDefaultSpacePHID()) ->attachInvitees(array()) + ->setDateFrom($epoch_min) + ->setDateTo($epoch_max) ->applyViewerTimezone($actor); } diff --git a/src/applications/transactions/editfield/PhabricatorEpochEditField.php b/src/applications/transactions/editfield/PhabricatorEpochEditField.php index c5dabe6171..ba708c5ce3 100644 --- a/src/applications/transactions/editfield/PhabricatorEpochEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEpochEditField.php @@ -3,8 +3,20 @@ final class PhabricatorEpochEditField extends PhabricatorEditField { + private $allowNull; + + public function setAllowNull($allow_null) { + $this->allowNull = $allow_null; + return $this; + } + + public function getAllowNull() { + return $this->allowNull; + } + protected function newControl() { return id(new AphrontFormDateControl()) + ->setAllowNull($this->getAllowNull()) ->setViewer($this->getViewer()); } From a46a4362dbfb428579b0be2d73d4ffe52962a028 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Jul 2016 11:58:56 -0700 Subject: [PATCH 18/76] Smooth over a few more transaction compatibility/structure issues with Calendar events Summary: Ref T9275. This gets things roughly into shape for a cutover to EditEngine, mostly by fixing some problems with "recurrence end date" not being nullable while editing events. Test Plan: Edited events with EditPro controller, nothing was obviously broken. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16282 --- src/__phutil_library_map__.php | 2 +- .../AphrontEpochHTTPParameterType.php | 19 ++++++++++++++++- ...PhabricatorCalendarEventEditController.php | 6 +++--- ...bricatorCalendarEventEditProController.php | 2 +- .../editor/PhabricatorCalendarEventEditor.php | 21 +++++++++++-------- .../PhabricatorCalendarEventTransaction.php | 14 +++++++++++-- .../editfield/PhabricatorEpochEditField.php | 3 ++- 7 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d23cd3b2d8..3307219e6a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -6633,7 +6633,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', - 'PhabricatorCalendarEventEditProController' => 'ManiphestController', + 'PhabricatorCalendarEventEditProController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', diff --git a/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php index f1932bd872..1b74d8f736 100644 --- a/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php +++ b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php @@ -3,13 +3,30 @@ final class AphrontEpochHTTPParameterType extends AphrontHTTPParameterType { + private $allowNull; + + public function setAllowNull($allow_null) { + $this->allowNull = $allow_null; + return $this; + } + + public function getAllowNull() { + return $this->allowNull; + } + 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); + $value = AphrontFormDateControlValue::newFromRequest($request, $key); + + if ($this->getAllowNull()) { + $value->setOptional(true); + } + + return $value; } protected function getParameterTypeName() { diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 4f92fa5b0c..2ac88c77d7 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -185,7 +185,7 @@ final class PhabricatorCalendarEventEditController $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE) - ->setNewValue($recurrence_end_date_value->getEpoch()); + ->setNewValue($recurrence_end_date_value); } } @@ -203,12 +203,12 @@ final class PhabricatorCalendarEventEditController $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_START_DATE) - ->setNewValue($start_value->getEpoch()); + ->setNewValue($start_value); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_END_DATE) - ->setNewValue($end_value->getEpoch()); + ->setNewValue($end_value); } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php index 6cd4c4bb35..d59a1ff3ad 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php @@ -1,7 +1,7 @@ getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_RECURRING: - return $object->getIsRecurring(); + return (int)$object->getIsRecurring(); case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: - return $object->getRecurrenceFrequency(); + return $object->getFrequencyUnit(); case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: return $object->getRecurrenceEndDate(); case PhabricatorCalendarEventTransaction::TYPE_NAME: @@ -120,7 +120,6 @@ final class PhabricatorCalendarEventEditor PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorCalendarEventTransaction::TYPE_RECURRING: case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: @@ -132,11 +131,12 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_DECLINE: return PhabricatorCalendarEventInvitee::STATUS_DECLINED; case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: + case PhabricatorCalendarEventTransaction::TYPE_RECURRING: return (int)$xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - return $xaction->getNewValue(); + return $xaction->getNewValue()->getEpoch(); case PhabricatorCalendarEventTransaction::TYPE_INVITE: $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; @@ -187,9 +187,12 @@ final class PhabricatorCalendarEventEditor switch ($xaction->getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_RECURRING: - return $object->setIsRecurring($xaction->getNewValue()); + return $object->setIsRecurring((int)$xaction->getNewValue()); case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: - return $object->setRecurrenceFrequency($xaction->getNewValue()); + return $object->setRecurrenceFrequency( + array( + 'rule' => $xaction->getNewValue(), + )); case PhabricatorCalendarEventTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; @@ -370,11 +373,11 @@ final class PhabricatorCalendarEventEditor foreach ($xactions as $xaction) { if ($xaction->getTransactionType() == $start_date_xaction) { - $start_date = $xaction->getNewValue(); + $start_date = $xaction->getNewValue()->getEpoch(); } else if ($xaction->getTransactionType() == $end_date_xaction) { - $end_date = $xaction->getNewValue(); + $end_date = $xaction->getNewValue()->getEpoch(); } else if ($xaction->getTransactionType() == $recurrence_end_xaction) { - $recurrence_end = $xaction->getNewValue(); + $recurrence_end = $xaction->getNewValue()->getEpoch(); } else if ($xaction->getTransactionType() == $is_recurrence_xaction) { $is_recurring = $xaction->getNewValue(); } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index 0040ff8817..e7c7205024 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -265,8 +265,13 @@ final class PhabricatorCalendarEventTransaction $this->renderHandleLink($author_phid)); return $text; case self::TYPE_FREQUENCY: + $rule = $new; + if (is_array($rule)) { + $rule = idx($rule, 'rule'); + } + $text = ''; - switch ($new['rule']) { + switch ($rule) { case PhabricatorCalendarEvent::FREQUENCY_DAILY: $text = pht('%s set this event to repeat daily.', $this->renderHandleLink($author_phid)); @@ -487,8 +492,13 @@ final class PhabricatorCalendarEventTransaction $this->renderHandleLink($object_phid)); return $text; case self::TYPE_FREQUENCY: + $rule = $new; + if (is_array($rule)) { + $rule = idx($rule, 'rule'); + } + $text = ''; - switch ($new['rule']) { + switch ($rule) { case PhabricatorCalendarEvent::FREQUENCY_DAILY: $text = pht('%s set %s to repeat daily.', $this->renderHandleLink($author_phid), diff --git a/src/applications/transactions/editfield/PhabricatorEpochEditField.php b/src/applications/transactions/editfield/PhabricatorEpochEditField.php index ba708c5ce3..36c7be861c 100644 --- a/src/applications/transactions/editfield/PhabricatorEpochEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEpochEditField.php @@ -21,7 +21,8 @@ final class PhabricatorEpochEditField } protected function newHTTPParameterType() { - return new AphrontEpochHTTPParameterType(); + return id(new AphrontEpochHTTPParameterType()) + ->setAllowNull($this->getAllowNull()); } protected function newConduitParameterType() { From ea813985a2845929b3c6637bc467cd2757c79d76 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Jul 2016 13:05:45 -0700 Subject: [PATCH 19/76] Switch Calendar to EditEngine Summary: Ref T9275. This throws away the old EditController and switches fully to EditEngine. There's still some sketchy behavior (particularly, no JS stuff yet) but I think all the basics work properly. Test Plan: Created and edited events via EditEngine, everything seemed to work alright. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16283 --- src/__phutil_library_map__.php | 2 - .../PhabricatorCalendarApplication.php | 8 +- .../PhabricatorCalendarController.php | 28 - ...PhabricatorCalendarEventEditController.php | 572 +----------------- ...bricatorCalendarEventEditProController.php | 12 - ...PhabricatorCalendarEventListController.php | 21 +- .../editor/PhabricatorCalendarEditEngine.php | 2 +- .../people/query/PhabricatorPeopleQuery.php | 5 + 8 files changed, 18 insertions(+), 632 deletions(-) delete mode 100644 src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3307219e6a..7392b040aa 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2024,7 +2024,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php', 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', - 'PhabricatorCalendarEventEditProController' => 'applications/calendar/controller/PhabricatorCalendarEventEditProController.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', 'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php', 'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php', @@ -6633,7 +6632,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', - 'PhabricatorCalendarEventEditProController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 4f0f26d2e0..5e52cdc4e0 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -47,11 +47,7 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { '(?P\d+)/)?(?:(?P\d+)/)?)?' => 'PhabricatorCalendarEventListController', 'event/' => array( - $this->getEditRoutePattern('editpro/') - => 'PhabricatorCalendarEventEditProController', - 'create/' - => 'PhabricatorCalendarEventEditController', - 'edit/(?P[1-9]\d*)/' + $this->getEditRoutePattern('edit/') => 'PhabricatorCalendarEventEditController', 'drag/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventDragController', @@ -59,8 +55,6 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { => 'PhabricatorCalendarEventCancelController', '(?Pjoin|decline|accept)/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventJoinController', - 'comment/(?P[1-9]\d*)/' - => 'PhabricatorCalendarEventCommentController', ), ), ); diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php index 60beb853d0..01ea367aeb 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarController.php @@ -2,32 +2,4 @@ abstract class PhabricatorCalendarController extends PhabricatorController { - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($this->getViewer()) - ->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Create Event')) - ->setHref('/calendar/event/create/')) - ->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Create Public Event')) - ->setHref('/calendar/event/create/?mode=public')) - ->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Create Recurring Event')) - ->setHref('/calendar/event/create/?mode=recurring')); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Event')) - ->setHref($this->getApplicationURI().'event/create/') - ->setIcon('fa-plus-square') - ->setDropdownMenu($actions)); - - return $crumbs; - } - } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 2ac88c77d7..1c0bdd0739 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -3,576 +3,10 @@ final class PhabricatorCalendarEventEditController extends PhabricatorCalendarController { - private $id; - - public function isCreate() { - return !$this->id; - } - public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - $user_phid = $viewer->getPHID(); - $this->id = $request->getURIData('id'); - - $error_name = true; - $error_recurrence_end_date = null; - $error_start_date = true; - $error_end_date = true; - $validation_exception = null; - - $is_recurring_id = celerity_generate_unique_node_id(); - $recurrence_end_date_id = celerity_generate_unique_node_id(); - $frequency_id = celerity_generate_unique_node_id(); - $all_day_id = celerity_generate_unique_node_id(); - $start_date_id = celerity_generate_unique_node_id(); - $end_date_id = celerity_generate_unique_node_id(); - - $next_workflow = $request->getStr('next'); - $uri_query = $request->getStr('query'); - - if ($this->isCreate()) { - $mode = $request->getStr('mode'); - $event = PhabricatorCalendarEvent::initializeNewCalendarEvent( - $viewer, - $mode); - - $create_start_year = $request->getInt('year'); - $create_start_month = $request->getInt('month'); - $create_start_day = $request->getInt('day'); - $create_start_time = $request->getStr('time'); - - if ($create_start_year) { - $start = AphrontFormDateControlValue::newFromParts( - $viewer, - $create_start_year, - $create_start_month, - $create_start_day, - $create_start_time); - if (!$start->isValid()) { - return new Aphront400Response(); - } - $start_value = AphrontFormDateControlValue::newFromEpoch( - $viewer, - $start->getEpoch()); - - $end = clone $start_value->getDateTime(); - $end->modify('+1 hour'); - $end_value = AphrontFormDateControlValue::newFromEpoch( - $viewer, - $end->format('U')); - - } else { - list($start_value, $end_value) = $this->getDefaultTimeValues($viewer); - } - - $recurrence_end_date_value = clone $end_value; - $recurrence_end_date_value->setOptional(true); - - $submit_label = pht('Create'); - $title = pht('Create Event'); - $header_icon = 'fa-plus-square'; - $redirect = 'created'; - $subscribers = array(); - $invitees = array($user_phid); - $cancel_uri = $this->getApplicationURI(); - } else { - $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->withIDs(array($this->id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$event) { - return new Aphront404Response(); - } - - $end_value = AphrontFormDateControlValue::newFromEpoch( - $viewer, - $event->getViewerDateTo()); - $start_value = AphrontFormDateControlValue::newFromEpoch( - $viewer, - $event->getViewerDateFrom()); - $recurrence_end_date_value = id(clone $end_value) - ->setOptional(true); - - $submit_label = pht('Update'); - $title = pht('Edit Event: %s', $event->getName()); - $header_icon = 'fa-pencil'; - - $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( - $event->getPHID()); - - $invitees = $event->getInviteePHIDsForEdit(); - $cancel_uri = $event->getURI(); - } - - if ($this->isCreate()) { - $projects = array(); - } else { - $projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $event->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $projects = array_reverse($projects); - } - - $name = $event->getName(); - $description = $event->getDescription(); - $is_all_day = $event->getIsAllDay(); - $is_recurring = $event->getIsRecurring(); - $is_parent = $event->isParentEvent(); - $frequency = idx($event->getRecurrenceFrequency(), 'rule'); - $icon = $event->getIcon(); - $edit_policy = $event->getEditPolicy(); - $view_policy = $event->getViewPolicy(); - $space = $event->getSpacePHID(); - - - if ($request->isFormPost()) { - $is_all_day = $request->getStr('isAllDay'); - - if ($is_all_day) { - // TODO: This is a very gross temporary hack to get this working - // reasonably: if this is an all day event, force the viewer's - // timezone to UTC so the date controls get interpreted as UTC. - $viewer->overrideTimezoneIdentifier('UTC'); - } - - $xactions = array(); - $name = $request->getStr('name'); - - $start_value = AphrontFormDateControlValue::newFromRequest( - $request, - 'start'); - $end_value = AphrontFormDateControlValue::newFromRequest( - $request, - 'end'); - $recurrence_end_date_value = AphrontFormDateControlValue::newFromRequest( - $request, - 'recurrenceEndDate'); - $recurrence_end_date_value->setOptional(true); - $projects = $request->getArr('projects'); - $description = $request->getStr('description'); - $subscribers = $request->getArr('subscribers'); - $edit_policy = $request->getStr('editPolicy'); - $view_policy = $request->getStr('viewPolicy'); - $space = $request->getStr('spacePHID'); - $is_recurring = $request->getStr('isRecurring') ? 1 : 0; - $frequency = $request->getStr('frequency'); - $icon = $request->getStr('icon'); - - $invitees = $request->getArr('invitees'); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_NAME) - ->setNewValue($name); - - if ($is_recurring && $this->isCreate()) { - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_RECURRING) - ->setNewValue($is_recurring); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_FREQUENCY) - ->setNewValue(array('rule' => $frequency)); - - if (!$recurrence_end_date_value->isDisabled()) { - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE) - ->setNewValue($recurrence_end_date_value); - } - } - - if (($is_recurring && $this->isCreate()) || !$is_parent) { - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_ALL_DAY) - ->setNewValue($is_all_day); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_ICON) - ->setNewValue($icon); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_START_DATE) - ->setNewValue($start_value); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_END_DATE) - ->setNewValue($end_value); - } - - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue(array('=' => array_fuse($subscribers))); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_INVITE) - ->setNewValue($invitees); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION) - ->setNewValue($description); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($request->getStr('viewPolicy')); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($request->getStr('editPolicy')); - - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) - ->setNewValue($space); - - $editor = id(new PhabricatorCalendarEventEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($projects))); - - $xactions = $editor->applyTransactions($event, $xactions); - $response = id(new AphrontRedirectResponse()); - switch ($next_workflow) { - case 'day': - if (!$uri_query) { - $uri_query = 'month'; - } - $year = $start_value->getDateTime()->format('Y'); - $month = $start_value->getDateTime()->format('m'); - $day = $start_value->getDateTime()->format('d'); - $response->setURI( - '/calendar/query/'.$uri_query.'/'.$year.'/'.$month.'/'.$day.'/'); - break; - default: - $response->setURI('/E'.$event->getID()); - break; - } - return $response; - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - $error_name = $ex->getShortMessage( - PhabricatorCalendarEventTransaction::TYPE_NAME); - $error_start_date = $ex->getShortMessage( - PhabricatorCalendarEventTransaction::TYPE_START_DATE); - $error_end_date = $ex->getShortMessage( - PhabricatorCalendarEventTransaction::TYPE_END_DATE); - $error_recurrence_end_date = $ex->getShortMessage( - PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE); - } - } - - $is_recurring_checkbox = null; - $recurrence_end_date_control = null; - $recurrence_frequency_select = null; - - $all_day_checkbox = null; - $start_control = null; - $end_control = null; - - $recurring_date_edit_label = null; - - $current_policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($event) - ->execute(); - - $name = id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($name) - ->setError($error_name); - - if ($this->isCreate()) { - Javelin::initBehavior('recurring-edit', array( - 'isRecurring' => $is_recurring_id, - 'frequency' => $frequency_id, - 'recurrenceEndDate' => $recurrence_end_date_id, - )); - - $is_recurring_checkbox = id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'isRecurring', - 1, - pht('Recurring Event'), - $is_recurring, - $is_recurring_id); - - $recurrence_end_date_control = id(new AphrontFormDateControl()) - ->setUser($viewer) - ->setName('recurrenceEndDate') - ->setLabel(pht('Recurrence End Date')) - ->setError($error_recurrence_end_date) - ->setValue($recurrence_end_date_value) - ->setID($recurrence_end_date_id) - ->setIsTimeDisabled(true) - ->setIsDisabled($recurrence_end_date_value->isDisabled()) - ->setAllowNull(true); - - $recurrence_frequency_select = id(new AphrontFormSelectControl()) - ->setName('frequency') - ->setOptions(array( - PhabricatorCalendarEvent::FREQUENCY_DAILY => pht('Daily'), - PhabricatorCalendarEvent::FREQUENCY_WEEKLY => pht('Weekly'), - PhabricatorCalendarEvent::FREQUENCY_MONTHLY => pht('Monthly'), - PhabricatorCalendarEvent::FREQUENCY_YEARLY => pht('Yearly'), - )) - ->setValue($frequency) - ->setLabel(pht('Recurring Event Frequency')) - ->setID($frequency_id) - ->setDisabled(!$is_recurring); - } - - if ($this->isCreate() || (!$is_parent && !$this->isCreate())) { - Javelin::initBehavior('event-all-day', array( - 'allDayID' => $all_day_id, - 'startDateID' => $start_date_id, - 'endDateID' => $end_date_id, - )); - - $all_day_checkbox = id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'isAllDay', - 1, - pht('All Day Event'), - $is_all_day, - $all_day_id); - - $start_control = id(new AphrontFormDateControl()) - ->setUser($viewer) - ->setName('start') - ->setLabel(pht('Start')) - ->setError($error_start_date) - ->setValue($start_value) - ->setID($start_date_id) - ->setIsTimeDisabled($is_all_day) - ->setEndDateID($end_date_id); - - $end_control = id(new AphrontFormDateControl()) - ->setUser($viewer) - ->setName('end') - ->setLabel(pht('End')) - ->setError($error_end_date) - ->setValue($end_value) - ->setID($end_date_id) - ->setIsTimeDisabled($is_all_day); - } else if ($is_parent) { - $recurring_date_edit_label = id(new AphrontFormStaticControl()) - ->setUser($viewer) - ->setValue(pht('Date and time of recurring event cannot be edited.')); - - if (!$recurrence_end_date_value->isDisabled()) { - $disabled_recurrence_end_date_value = - $recurrence_end_date_value->getValueAsFormat('M d, Y'); - $recurrence_end_date_control = id(new AphrontFormStaticControl()) - ->setUser($viewer) - ->setLabel(pht('Recurrence End Date')) - ->setValue($disabled_recurrence_end_date_value) - ->setDisabled(true); - } - - $recurrence_frequency_select = id(new AphrontFormSelectControl()) - ->setName('frequency') - ->setOptions(array( - 'daily' => pht('Daily'), - 'weekly' => pht('Weekly'), - 'monthly' => pht('Monthly'), - 'yearly' => pht('Yearly'), - )) - ->setValue($frequency) - ->setLabel(pht('Recurring Event Frequency')) - ->setID($frequency_id) - ->setDisabled(true); - - $all_day_checkbox = id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'isAllDay', - 1, - pht('All Day Event'), - $is_all_day, - $all_day_id) - ->setDisabled(true); - - $start_disabled = $start_value->getValueAsFormat('M d, Y, g:i A'); - $end_disabled = $end_value->getValueAsFormat('M d, Y, g:i A'); - - $start_control = id(new AphrontFormStaticControl()) - ->setUser($viewer) - ->setLabel(pht('Start')) - ->setValue($start_disabled) - ->setDisabled(true); - - $end_control = id(new AphrontFormStaticControl()) - ->setUser($viewer) - ->setLabel(pht('End')) - ->setValue($end_disabled); - } - - $projects = id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setValue($projects) - ->setUser($viewer) - ->setDatasource(new PhabricatorProjectDatasource()); - - $description = id(new PhabricatorRemarkupControl()) - ->setLabel(pht('Description')) - ->setName('description') - ->setValue($description) - ->setUser($viewer); - - $view_policies = id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setValue($view_policy) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($event) - ->setPolicies($current_policies) - ->setSpacePHID($space) - ->setName('viewPolicy'); - $edit_policies = id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setValue($edit_policy) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($event) - ->setPolicies($current_policies) - ->setName('editPolicy'); - - $subscribers = id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Subscribers')) - ->setName('subscribers') - ->setValue($subscribers) - ->setUser($viewer) - ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); - - $invitees = id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Invitees')) - ->setName('invitees') - ->setValue($invitees) - ->setUser($viewer) - ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); - - $icon = id(new PHUIFormIconSetControl()) - ->setLabel(pht('Icon')) - ->setName('icon') - ->setIconSet(new PhabricatorCalendarIconSet()) - ->setValue($icon); - - $form = id(new AphrontFormView()) - ->addHiddenInput('next', $next_workflow) - ->addHiddenInput('query', $uri_query) - ->setUser($viewer) - ->appendChild($name); - - if ($recurring_date_edit_label) { - $form->appendControl($recurring_date_edit_label); - } - if ($is_recurring_checkbox) { - $form->appendChild($is_recurring_checkbox); - } - if ($recurrence_end_date_control) { - $form->appendChild($recurrence_end_date_control); - } - if ($recurrence_frequency_select) { - $form->appendControl($recurrence_frequency_select); - } - - $form - ->appendChild($all_day_checkbox) - ->appendChild($start_control) - ->appendChild($end_control) - ->appendControl($view_policies) - ->appendControl($edit_policies) - ->appendControl($subscribers) - ->appendControl($invitees) - ->appendChild($projects) - ->appendChild($description) - ->appendChild($icon); - - - if ($request->isAjax()) { - return $this->newDialog() - ->setTitle($title) - ->setWidth(AphrontDialogView::WIDTH_FULL) - ->appendForm($form) - ->addCancelButton($cancel_uri) - ->addSubmitButton($submit_label); - } - - $submit = id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($submit_label); - - $form->appendChild($submit); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Event')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setValidationException($validation_exception) - ->setForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - - if (!$this->isCreate()) { - $crumbs->addTextCrumb('E'.$event->getId(), '/E'.$event->getId()); - $crumb_title = pht('Edit Event'); - } else { - $crumb_title = pht('Create Event'); - } - - $crumbs->addTextCrumb($crumb_title); - $crumbs->setBorder(true); - - $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($view); - } - - - private function getDefaultTimeValues($viewer) { - $start = new DateTime('@'.time()); - $start->setTimeZone($viewer->getTimeZone()); - - $start->setTime($start->format('H'), 0, 0); - $start->modify('+1 hour'); - $end = id(clone $start)->modify('+1 hour'); - - $start_value = AphrontFormDateControlValue::newFromEpoch( - $viewer, - $start->format('U')); - $end_value = AphrontFormDateControlValue::newFromEpoch( - $viewer, - $end->format('U')); - - return array($start_value, $end_value); + return id(new PhabricatorCalendarEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php deleted file mode 100644 index d59a1ff3ad..0000000000 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditProController.php +++ /dev/null @@ -1,12 +0,0 @@ -setController($this) - ->buildResponse(); - } - -} diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php index 519841d1f3..529c206e7b 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -19,24 +19,19 @@ final class PhabricatorCalendarEventListController $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine($engine) - ->setNavigation($this->buildSideNav()); + ->setSearchEngine($engine); + return $this->delegateToController($controller); } - public function buildSideNav() { - $user = $this->getRequest()->getUser(); + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + id(new PhabricatorCalendarEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); - id(new PhabricatorCalendarEventSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + return $crumbs; } } diff --git a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php index 2e1f0dc7ca..43745b4f86 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php @@ -56,7 +56,7 @@ final class PhabricatorCalendarEditEngine } protected function getEditorURI() { - return $this->getApplication()->getApplicationURI('event/editpro/'); + return $this->getApplication()->getApplicationURI('event/edit/'); } protected function buildCustomEditFields($object) { diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 3dcb156295..69ce099111 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -410,6 +410,11 @@ final class PhabricatorPeopleQuery } } + // We need to load these users' timezone settings to figure out their + // availability if they're attending all-day events. + $this->needUserSettings(true); + $this->fillUserCaches($rebuild); + foreach ($rebuild as $phid => $user) { $events = idx($map, $phid, array()); From 46cf1894134f669be63adc852192f5cf99737bda Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Jul 2016 13:22:28 -0700 Subject: [PATCH 20/76] Fix some EditEngine issues with rendering "invite" transactions Summary: Ref T9275. We were rendering too many transactions and/or over-rendering invitees. Clean this logic up a bit: - List all before/after invitees. - Simplify the lists before rendering. Test Plan: Viewed an event, edited invitees, got sensible human-readable transactions. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16284 --- .../editor/PhabricatorCalendarEventEditor.php | 2 ++ .../PhabricatorCalendarEventTransaction.php | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index eb9533b03d..e12023db52 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -163,6 +163,8 @@ final class PhabricatorCalendarEventEditor $map[$phid] = $status_uninvited; } else if (!$is_old && $is_new) { $map[$phid] = $status_invited; + } else { + $map[$phid] = $invitees[$phid]->getStatus(); } } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index e7c7205024..eb76eb29b5 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -176,10 +176,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_INVITE: $text = null; - // Fill in any new invitees as "uninvited" in the old data, to make - // some of the rendering logic a little easier. - $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; - $old = $old + array_fill_keys(array_keys($new), $status_uninvited); + list($old, $new) = $this->getSimpleInvites($old, $new); if (count($old) === 1 && count($new) === 1 @@ -396,8 +393,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_INVITE: $text = null; - $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; - $old = $old + array_fill_keys(array_keys($new), $status_uninvited); + list($old, $new) = $this->getSimpleInvites($old, $new); if (count($old) === 1 && count($new) === 1 @@ -592,4 +588,26 @@ final class PhabricatorCalendarEventTransaction return $tags; } + private function getSimpleInvites(array $old, array $new) { + // Fill in any new invitees as "uninvited" in the old data, to make + // some of the rendering logic a little easier. + $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; + $old = $old + array_fill_keys(array_keys($new), $status_uninvited); + + $all = $old + $new; + foreach (array_keys($all) as $key) { + // If the invitee exists in both the old and new lists with the same + // value, remove it from both. + if (isset($old[$key]) && isset($new[$key])) { + if ($old[$key] == $new[$key]) { + unset($old[$key]); + unset($new[$key]); + } + } + } + + return array($old, $new); + } + + } From 63fec9b97d6fd2e2dccbc9ef00e4be710d2e0e9d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Jul 2016 14:39:24 -0700 Subject: [PATCH 21/76] Restore date validation errors to Calendar Summary: Ref T9275. I waffled back and forth on these transactions a bit, but put these back here in better working order. Test Plan: Tried to schedule an event on "taco". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16285 --- .../editor/PhabricatorCalendarEventEditor.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index e12023db52..33cbdc46e6 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -465,6 +465,34 @@ final class PhabricatorCalendarEventEditor } } break; + case PhabricatorCalendarEventTransaction::TYPE_START_DATE: + case PhabricatorCalendarEventTransaction::TYPE_END_DATE: + case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: + foreach ($xactions as $xaction) { + if ($xaction->getNewValue()->isValid()) { + continue; + } + + switch ($type) { + case PhabricatorCalendarEventTransaction::TYPE_START_DATE: + $message = pht('Start date is invalid.'); + break; + case PhabricatorCalendarEventTransaction::TYPE_END_DATE: + $message = pht('End date is invalid.'); + break; + case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: + $message = pht('Repeat until date is invalid.'); + break; + } + + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + $message, + $xaction); + } + break; + } return $errors; From 7b09f5698fe09fc88b4c10a97c776c00caf7a18d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Jul 2016 15:44:11 -0700 Subject: [PATCH 22/76] Convert Calendar to Modular Transactions Summary: Ref T9275. Swaps Calendar over to modular transactions. Theoretically, this has almost no effect on anything. Ref T10633. I didn't actually do anything here yet, but this gets us ready to put timestamps in email. Test Plan: Created and edited a bunch of events, nothing seemed catastrophically broken. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275, T10633 Differential Revision: https://secure.phabricator.com/D16286 --- src/__phutil_library_map__.php | 34 +- ...abricatorCalendarEventRSVPEmailCommand.php | 18 +- ...abricatorCalendarEventCancelController.php | 2 +- ...PhabricatorCalendarEventDragController.php | 7 +- ...PhabricatorCalendarEventJoinController.php | 6 +- ...PhabricatorCalendarEventViewController.php | 2 +- .../editor/PhabricatorCalendarEditEngine.php | 26 +- .../editor/PhabricatorCalendarEventEditor.php | 354 +---------- .../PhabricatorCalendarEventTransaction.php | 588 +----------------- ...bricatorCalendarEventAcceptTransaction.php | 25 + ...bricatorCalendarEventAllDayTransaction.php | 46 ++ ...bricatorCalendarEventCancelTransaction.php | 46 ++ ...habricatorCalendarEventDateTransaction.php | 27 + ...ricatorCalendarEventDeclineTransaction.php | 25 + ...torCalendarEventDescriptionTransaction.php | 42 ++ ...ricatorCalendarEventEndDateTransaction.php | 37 ++ ...catorCalendarEventFrequencyTransaction.php | 75 +++ ...habricatorCalendarEventIconTransaction.php | 44 ++ ...bricatorCalendarEventInviteTransaction.php | 191 ++++++ ...habricatorCalendarEventNameTransaction.php | 44 ++ ...catorCalendarEventRecurringTransaction.php | 44 ++ ...abricatorCalendarEventReplyTransaction.php | 33 + ...catorCalendarEventStartDateTransaction.php | 37 ++ ...habricatorCalendarEventTransactionType.php | 4 + ...catorCalendarEventUntilDateTransaction.php | 35 ++ .../phid/view/PHUIHandleListView.php | 16 +- .../PhabricatorModularTransactionType.php | 69 ++ .../PhabricatorUSEnglishTranslation.php | 18 + 28 files changed, 942 insertions(+), 953 deletions(-) create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7392b040aa..08c682de12 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2021,24 +2021,40 @@ phutil_register_library_map(array( 'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php', 'PhabricatorCalendarEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEditEngine.php', 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', + 'PhabricatorCalendarEventAcceptTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php', + 'PhabricatorCalendarEventAllDayTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php', 'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php', + 'PhabricatorCalendarEventCancelTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php', + 'PhabricatorCalendarEventDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php', + 'PhabricatorCalendarEventDeclineTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php', + 'PhabricatorCalendarEventDescriptionTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php', 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', 'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php', + 'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php', + 'PhabricatorCalendarEventFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php', 'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php', + 'PhabricatorCalendarEventIconTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php', + 'PhabricatorCalendarEventInviteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php', 'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php', 'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php', 'PhabricatorCalendarEventJoinController' => 'applications/calendar/controller/PhabricatorCalendarEventJoinController.php', 'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php', 'PhabricatorCalendarEventMailReceiver' => 'applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php', + 'PhabricatorCalendarEventNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php', 'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php', 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', 'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php', + 'PhabricatorCalendarEventRecurringTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php', + 'PhabricatorCalendarEventReplyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php', 'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php', + 'PhabricatorCalendarEventStartDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php', 'PhabricatorCalendarEventTransaction' => 'applications/calendar/storage/PhabricatorCalendarEventTransaction.php', 'PhabricatorCalendarEventTransactionComment' => 'applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php', 'PhabricatorCalendarEventTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php', + 'PhabricatorCalendarEventTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php', + 'PhabricatorCalendarEventUntilDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php', 'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', @@ -6629,12 +6645,22 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', ), + 'PhabricatorCalendarEventAcceptTransaction' => 'PhabricatorCalendarEventReplyTransaction', + 'PhabricatorCalendarEventAllDayTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventCancelTransaction' => 'PhabricatorCalendarEventTransactionType', + 'PhabricatorCalendarEventDateTransaction' => 'PhabricatorCalendarEventTransactionType', + 'PhabricatorCalendarEventDeclineTransaction' => 'PhabricatorCalendarEventReplyTransaction', + 'PhabricatorCalendarEventDescriptionTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', + 'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction', + 'PhabricatorCalendarEventFrequencyTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhabricatorCalendarEventIconTransaction' => 'PhabricatorCalendarEventTransactionType', + 'PhabricatorCalendarEventInviteTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventInvitee' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', @@ -6643,13 +6669,19 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventJoinController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventMailReceiver' => 'PhabricatorObjectMailReceiver', + 'PhabricatorCalendarEventNameTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand', + 'PhabricatorCalendarEventRecurringTransaction' => 'PhabricatorCalendarEventTransactionType', + 'PhabricatorCalendarEventReplyTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorCalendarEventTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorCalendarEventStartDateTransaction' => 'PhabricatorCalendarEventDateTransaction', + 'PhabricatorCalendarEventTransaction' => 'PhabricatorModularTransaction', 'PhabricatorCalendarEventTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorCalendarEventTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorCalendarEventTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorCalendarEventUntilDateTransaction' => 'PhabricatorCalendarEventDateTransaction', 'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php b/src/applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php index c4f73aaeaf..9c4fad57df 100644 --- a/src/applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php +++ b/src/applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php @@ -44,32 +44,24 @@ final class PhabricatorCalendarEventRSVPEmailCommand PhabricatorMetaMTAReceivedMail $mail, $command, array $argv) { - $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; - $status_declined = PhabricatorCalendarEventInvitee::STATUS_DECLINED; - $xactions = array(); $target = phutil_utf8_strtolower(implode(' ', $argv)); - $rsvp = null; $yes_values = $this->getYesValues(); $no_values = $this->getNoValues(); if (in_array($target, $yes_values)) { - $rsvp = $status_attending; + $rsvp = PhabricatorCalendarEventAcceptTransaction::TRANSACTIONTYPE; } else if (in_array($target, $no_values)) { - $rsvp = $status_declined; + $rsvp = PhabricatorCalendarEventDeclineTransaction::TRANSACTIONTYPE; } else { - $rsvp = null; - } - - if ($rsvp === null) { return array(); } + $xactions = array(); $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_INVITE) - ->setNewValue(array($viewer->getPHID() => $rsvp)); - + ->setTransactionType($rsvp) + ->setNewValue(true); return $xactions; } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php index 49bb0c3948..63ce289970 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php @@ -38,7 +38,7 @@ final class PhabricatorCalendarEventCancelController $xaction = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_CANCEL) + PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE) ->setNewValue(!$is_cancelled); $editor = id(new PhabricatorCalendarEventEditor()) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php index 7fc3ce8e78..ddb11dee5b 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php @@ -41,13 +41,14 @@ final class PhabricatorCalendarEventDragController $viewer, $end)); - $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_START_DATE) + ->setTransactionType( + PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE) ->setNewValue($start_value); $xactions[] = id(new PhabricatorCalendarEventTransaction()) - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_END_DATE) + ->setTransactionType( + PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE) ->setNewValue($end_value); $editor = id(new PhabricatorCalendarEventEditor()) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php index b69dbe1bfd..9b58d39dec 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php @@ -33,9 +33,11 @@ final class PhabricatorCalendarEventJoinController $validation_exception = null; if ($request->isFormPost()) { if ($is_join) { - $xaction_type = PhabricatorCalendarEventTransaction::TYPE_ACCEPT; + $xaction_type = + PhabricatorCalendarEventAcceptTransaction::TRANSACTIONTYPE; } else { - $xaction_type = PhabricatorCalendarEventTransaction::TYPE_DECLINE; + $xaction_type = + PhabricatorCalendarEventDeclineTransaction::TRANSACTIONTYPE; } $xaction = id(new PhabricatorCalendarEventTransaction()) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 691fd579c9..6c771cda31 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -120,7 +120,7 @@ final class PhabricatorCalendarEventViewController if ($event->isChildEvent()) { $edit_label = pht('Edit This Instance'); } else { - $edit_label = pht('Edit'); + $edit_label = pht('Edit Event'); } $curtain = $this->newCurtainView($event); diff --git a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php index 43745b4f86..75772d5141 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php @@ -81,7 +81,8 @@ final class PhabricatorCalendarEditEngine ->setLabel(pht('Name')) ->setDescription(pht('Name of the event.')) ->setIsRequired(true) - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_NAME) + ->setTransactionType( + PhabricatorCalendarEventNameTransaction::TRANSACTIONTYPE) ->setConduitDescription(pht('Rename the event.')) ->setConduitTypeDescription(pht('New event name.')) ->setValue($object->getName()), @@ -90,7 +91,7 @@ final class PhabricatorCalendarEditEngine ->setLabel(pht('Description')) ->setDescription(pht('Description of the event.')) ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION) + PhabricatorCalendarEventDescriptionTransaction::TRANSACTIONTYPE) ->setConduitDescription(pht('Update the event description.')) ->setConduitTypeDescription(pht('New event description.')) ->setValue($object->getDescription()), @@ -100,7 +101,7 @@ final class PhabricatorCalendarEditEngine ->setLabel(pht('Cancelled')) ->setDescription(pht('Cancel the event.')) ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_CANCEL) + PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE) ->setIsConduitOnly(true) ->setConduitDescription(pht('Cancel or restore the event.')) ->setConduitTypeDescription(pht('True to cancel the event.')) @@ -110,7 +111,8 @@ final class PhabricatorCalendarEditEngine ->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID')) ->setLabel(pht('Invitees')) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()) - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_INVITE) + ->setTransactionType( + PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE) ->setDescription(pht('Users invited to the event.')) ->setConduitDescription(pht('Change invited users.')) ->setConduitTypeDescription(pht('New event invitees.')) @@ -124,7 +126,7 @@ final class PhabricatorCalendarEditEngine ->setLabel(pht('Recurring')) ->setOptions(pht('One-Time Event'), pht('Recurring Event')) ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_RECURRING) + PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE) ->setDescription(pht('One time or recurring event.')) ->setConduitDescription(pht('Make the event recurring.')) ->setConduitTypeDescription(pht('Mark the event as a recurring event.')) @@ -135,7 +137,7 @@ final class PhabricatorCalendarEditEngine ->setLabel(pht('Frequency')) ->setOptions($frequency_options) ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_FREQUENCY) + PhabricatorCalendarEventFrequencyTransaction::TRANSACTIONTYPE) ->setDescription(pht('Recurring event frequency.')) ->setConduitDescription(pht('Change the event frequency.')) ->setConduitTypeDescription(pht('New event frequency.')) @@ -148,7 +150,7 @@ final class PhabricatorCalendarEditEngine ->setKey('until') ->setLabel(pht('Repeat Until')) ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE) + PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE) ->setDescription(pht('Last instance of the event.')) ->setConduitDescription(pht('Change when the event repeats until.')) ->setConduitTypeDescription(pht('New final event time.')) @@ -159,7 +161,8 @@ final class PhabricatorCalendarEditEngine ->setKey('isAllDay') ->setLabel(pht('All Day')) ->setOptions(pht('Normal Event'), pht('All Day Event')) - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_ALL_DAY) + ->setTransactionType( + PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE) ->setDescription(pht('Marks this as an all day event.')) ->setConduitDescription(pht('Make the event an all day event.')) ->setConduitTypeDescription(pht('Mark the event as an all day event.')) @@ -169,7 +172,7 @@ final class PhabricatorCalendarEditEngine ->setKey('start') ->setLabel(pht('Start')) ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_START_DATE) + PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE) ->setDescription(pht('Start time of the event.')) ->setConduitDescription(pht('Change the start time of the event.')) ->setConduitTypeDescription(pht('New event start time.')) @@ -179,7 +182,7 @@ final class PhabricatorCalendarEditEngine ->setKey('end') ->setLabel(pht('End')) ->setTransactionType( - PhabricatorCalendarEventTransaction::TYPE_END_DATE) + PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE) ->setDescription(pht('End time of the event.')) ->setConduitDescription(pht('Change the end time of the event.')) ->setConduitTypeDescription(pht('New event end time.')) @@ -189,7 +192,8 @@ final class PhabricatorCalendarEditEngine ->setKey('icon') ->setLabel(pht('Icon')) ->setIconSet(new PhabricatorCalendarIconSet()) - ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_ICON) + ->setTransactionType( + PhabricatorCalendarEventIconTransaction::TRANSACTIONTYPE) ->setDescription(pht('Event icon.')) ->setConduitDescription(pht('Change the event icon.')) ->setConduitTypeDescription(pht('New event icon.')) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index 33cbdc46e6..efa66e2b09 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -58,21 +58,6 @@ final class PhabricatorCalendarEventEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorCalendarEventTransaction::TYPE_NAME; - $types[] = PhabricatorCalendarEventTransaction::TYPE_START_DATE; - $types[] = PhabricatorCalendarEventTransaction::TYPE_END_DATE; - $types[] = PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorCalendarEventTransaction::TYPE_CANCEL; - $types[] = PhabricatorCalendarEventTransaction::TYPE_INVITE; - $types[] = PhabricatorCalendarEventTransaction::TYPE_ALL_DAY; - $types[] = PhabricatorCalendarEventTransaction::TYPE_ICON; - $types[] = PhabricatorCalendarEventTransaction::TYPE_ACCEPT; - $types[] = PhabricatorCalendarEventTransaction::TYPE_DECLINE; - - $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRING; - $types[] = PhabricatorCalendarEventTransaction::TYPE_FREQUENCY; - $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -80,217 +65,6 @@ final class PhabricatorCalendarEventEditor return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorCalendarEventTransaction::TYPE_RECURRING: - return (int)$object->getIsRecurring(); - case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: - return $object->getFrequencyUnit(); - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - return $object->getRecurrenceEndDate(); - case PhabricatorCalendarEventTransaction::TYPE_NAME: - return $object->getName(); - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: - return $object->getDateFrom(); - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - return $object->getDateTo(); - case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhabricatorCalendarEventTransaction::TYPE_CANCEL: - return $object->getIsCancelled(); - case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: - return (int)$object->getIsAllDay(); - case PhabricatorCalendarEventTransaction::TYPE_ICON: - return $object->getIcon(); - case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: - case PhabricatorCalendarEventTransaction::TYPE_DECLINE: - $actor_phid = $this->getActingAsPHID(); - return $object->getUserInviteStatus($actor_phid); - case PhabricatorCalendarEventTransaction::TYPE_INVITE: - $invitees = $object->getInvitees(); - return mpull($invitees, 'getStatus', 'getInviteePHID'); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: - case PhabricatorCalendarEventTransaction::TYPE_NAME: - case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: - case PhabricatorCalendarEventTransaction::TYPE_CANCEL: - case PhabricatorCalendarEventTransaction::TYPE_ICON: - return $xaction->getNewValue(); - case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: - return PhabricatorCalendarEventInvitee::STATUS_ATTENDING; - case PhabricatorCalendarEventTransaction::TYPE_DECLINE: - return PhabricatorCalendarEventInvitee::STATUS_DECLINED; - case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: - case PhabricatorCalendarEventTransaction::TYPE_RECURRING: - return (int)$xaction->getNewValue(); - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - return $xaction->getNewValue()->getEpoch(); - case PhabricatorCalendarEventTransaction::TYPE_INVITE: - $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; - $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; - $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; - - $invitees = $object->getInvitees(); - foreach ($invitees as $key => $invitee) { - if ($invitee->getStatus() == $status_uninvited) { - unset($invitees[$key]); - } - } - $invitees = mpull($invitees, null, 'getInviteePHID'); - - $new = $xaction->getNewValue(); - $new = array_fuse($new); - - $all = array_keys($invitees + $new); - $map = array(); - foreach ($all as $phid) { - $is_old = isset($invitees[$phid]); - $is_new = isset($new[$phid]); - - if ($is_old && !$is_new) { - $map[$phid] = $status_uninvited; - } else if (!$is_old && $is_new) { - $map[$phid] = $status_invited; - } else { - $map[$phid] = $invitees[$phid]->getStatus(); - } - } - - // If we're creating this event and the actor is inviting themselves, - // mark them as attending. - if ($this->getIsNewObject()) { - $acting_phid = $this->getActingAsPHID(); - if (isset($map[$acting_phid])) { - $map[$acting_phid] = $status_attending; - } - } - - return $map; - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorCalendarEventTransaction::TYPE_RECURRING: - return $object->setIsRecurring((int)$xaction->getNewValue()); - case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: - return $object->setRecurrenceFrequency( - array( - 'rule' => $xaction->getNewValue(), - )); - case PhabricatorCalendarEventTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: - $object->setDateFrom($xaction->getNewValue()); - return; - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - $object->setDateTo($xaction->getNewValue()); - return; - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - $object->setRecurrenceEndDate($xaction->getNewValue()); - return; - case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case PhabricatorCalendarEventTransaction::TYPE_CANCEL: - $object->setIsCancelled((int)$xaction->getNewValue()); - return; - case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: - $object->setIsAllDay((int)$xaction->getNewValue()); - return; - case PhabricatorCalendarEventTransaction::TYPE_ICON: - $object->setIcon($xaction->getNewValue()); - return; - case PhabricatorCalendarEventTransaction::TYPE_INVITE: - case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: - case PhabricatorCalendarEventTransaction::TYPE_DECLINE: - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorCalendarEventTransaction::TYPE_RECURRING: - case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_NAME: - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: - case PhabricatorCalendarEventTransaction::TYPE_CANCEL: - case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: - case PhabricatorCalendarEventTransaction::TYPE_ICON: - return; - case PhabricatorCalendarEventTransaction::TYPE_INVITE: - $map = $xaction->getNewValue(); - $phids = array_keys($map); - $invitees = $object->getInvitees(); - $invitees = mpull($invitees, null, 'getInviteePHID'); - - foreach ($phids as $phid) { - $invitee = idx($invitees, $phid); - if (!$invitee) { - $invitee = id(new PhabricatorCalendarEventInvitee()) - ->setEventPHID($object->getPHID()) - ->setInviteePHID($phid) - ->setInviterPHID($this->getActingAsPHID()); - $invitees[] = $invitee; - } - $invitee->setStatus($map[$phid]) - ->save(); - } - $object->attachInvitees($invitees); - return; - case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: - case PhabricatorCalendarEventTransaction::TYPE_DECLINE: - $acting_phid = $this->getActingAsPHID(); - - $invitees = $object->getInvitees(); - $invitees = mpull($invitees, null, 'getInviteePHID'); - - $invitee = idx($invitees, $acting_phid); - if (!$invitee) { - $invitee = id(new PhabricatorCalendarEventInvitee()) - ->setEventPHID($object->getPHID()) - ->setInviteePHID($acting_phid) - ->setInviterPHID($acting_phid); - $invitees[$acting_phid] = $invitee; - } - - $invitee - ->setStatus($xaction->getNewValue()) - ->save(); - - $object->attachInvitees($invitees); - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -302,26 +76,21 @@ final class PhabricatorCalendarEventEditor $invalidate_phids = array(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorCalendarEventTransaction::TYPE_ICON: - break; - case PhabricatorCalendarEventTransaction::TYPE_RECURRING: - case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_CANCEL: - case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: + case PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE: // For these kinds of changes, we need to invalidate the availabilty // caches for all attendees. $invalidate_all = true; break; - - case PhabricatorCalendarEventTransaction::TYPE_ACCEPT: - case PhabricatorCalendarEventTransaction::TYPE_DECLINE: + case PhabricatorCalendarEventAcceptTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventDeclineTransaction::TRANSACTIONTYPE: $acting_phid = $this->getActingAsPHID(); $invalidate_phids[$acting_phid] = $acting_phid; break; - case PhabricatorCalendarEventTransaction::TYPE_INVITE: + case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE: foreach ($xaction->getNewValue() as $phid => $ignored) { $invalidate_phids[$phid] = $phid; } @@ -357,14 +126,15 @@ final class PhabricatorCalendarEventEditor protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { + $start_date_xaction = - PhabricatorCalendarEventTransaction::TYPE_START_DATE; + PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE; $end_date_xaction = - PhabricatorCalendarEventTransaction::TYPE_END_DATE; + PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE; $is_recurrence_xaction = - PhabricatorCalendarEventTransaction::TYPE_RECURRING; + PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE; $recurrence_end_xaction = - PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; + PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE; $start_date = $object->getDateFrom(); $end_date = $object->getDateTo(); @@ -386,19 +156,16 @@ final class PhabricatorCalendarEventEditor } if ($start_date > $end_date) { - $type = PhabricatorCalendarEventTransaction::TYPE_END_DATE; $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, + $end_date_xaction, pht('Invalid'), pht('End date must be after start date.'), null); } if ($recurrence_end && !$is_recurring) { - $type = - PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, + $recurrence_end_xaction, pht('Invalid'), pht('Event must be recurring to have a recurrence end date.'). null); @@ -407,97 +174,6 @@ final class PhabricatorCalendarEventEditor return $errors; } - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorCalendarEventTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Event name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhabricatorCalendarEventTransaction::TYPE_INVITE: - $old = $object->getInvitees(); - $old = mpull($old, null, 'getInviteePHID'); - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - $new = array_fuse($new); - $add = array_diff_key($new, $old); - if (!$add) { - continue; - } - - // In the UI, we only allow you to invite mailable objects, but there - // is no definitive marker for "invitable object" today. Just allow - // any valid object to be invited. - $objects = id(new PhabricatorObjectQuery()) - ->setViewer($this->getActor()) - ->withPHIDs($add) - ->execute(); - $objects = mpull($objects, null, 'getPHID'); - foreach ($add as $phid) { - if (isset($objects[$phid])) { - continue; - } - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Invitee "%s" identifies an object that does not exist or '. - 'which you do not have permission to view.', - $phid)); - } - } - break; - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - foreach ($xactions as $xaction) { - if ($xaction->getNewValue()->isValid()) { - continue; - } - - switch ($type) { - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: - $message = pht('Start date is invalid.'); - break; - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: - $message = pht('End date is invalid.'); - break; - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: - $message = pht('Repeat until date is invalid.'); - break; - } - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $message, - $xaction); - } - break; - - } - - return $errors; - } - protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index eb76eb29b5..cd4d788c1a 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -1,22 +1,7 @@ getTransactionType()) { - case self::TYPE_NAME: - case self::TYPE_START_DATE: - case self::TYPE_END_DATE: - case self::TYPE_DESCRIPTION: - case self::TYPE_CANCEL: - case self::TYPE_ALL_DAY: - case self::TYPE_RECURRING: - case self::TYPE_FREQUENCY: - case self::TYPE_RECURRENCE_END_DATE: - $phids[] = $this->getObjectPHID(); - break; - case self::TYPE_INVITE: - $new = $this->getNewValue(); - foreach ($new as $phid => $status) { - $phids[] = $phid; - } - break; - } - - return $phids; - } - - public function shouldHide() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_START_DATE: - case self::TYPE_END_DATE: - case self::TYPE_DESCRIPTION: - case self::TYPE_CANCEL: - case self::TYPE_ALL_DAY: - case self::TYPE_INVITE: - case self::TYPE_RECURRING: - case self::TYPE_FREQUENCY: - case self::TYPE_RECURRENCE_END_DATE: - return ($old === null); - } - return parent::shouldHide(); - } - - public function getIcon() { - switch ($this->getTransactionType()) { - case self::TYPE_ICON: - return $this->getNewValue(); - case self::TYPE_NAME: - case self::TYPE_START_DATE: - case self::TYPE_END_DATE: - case self::TYPE_DESCRIPTION: - case self::TYPE_ALL_DAY: - case self::TYPE_CANCEL: - case self::TYPE_RECURRING: - case self::TYPE_FREQUENCY: - case self::TYPE_RECURRENCE_END_DATE: - return 'fa-pencil'; - break; - case self::TYPE_INVITE: - return 'fa-user-plus'; - break; - } - return parent::getIcon(); - } - - 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_NAME: - if ($old === null) { - return pht( - '%s created this event.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the name of this event from %s to %s.', - $this->renderHandleLink($author_phid), - $old, - $new); - } - case self::TYPE_START_DATE: - if ($old) { - return pht( - '%s edited the start date of this event.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_END_DATE: - if ($old) { - return pht( - '%s edited the end date of this event.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - "%s updated the event's description.", - $this->renderHandleLink($author_phid)); - case self::TYPE_ALL_DAY: - if ($new) { - return pht( - '%s made this an all day event.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s converted this from an all day event.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_ICON: - $set = new PhabricatorCalendarIconSet(); - return pht( - '%s set this event\'s icon to %s.', - $this->renderHandleLink($author_phid), - $set->getIconLabel($new)); - break; - case self::TYPE_CANCEL: - if ($new) { - return pht( - '%s cancelled this event.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s reinstated this event.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_ACCEPT: - return pht( - '%s is attending this event.', - $this->renderHandleLink($author_phid)); - case self::TYPE_DECLINE: - return pht( - '%s declined this event.', - $this->renderHandleLink($author_phid)); - case self::TYPE_INVITE: - $text = null; - - list($old, $new) = $this->getSimpleInvites($old, $new); - - if (count($old) === 1 - && count($new) === 1 - && isset($old[$author_phid])) { - // user joined/declined/accepted event themself - $old_status = $old[$author_phid]; - $new_status = $new[$author_phid]; - - if ($old_status !== $new_status) { - switch ($new_status) { - case PhabricatorCalendarEventInvitee::STATUS_INVITED: - $text = pht( - '%s has joined this event.', - $this->renderHandleLink($author_phid)); - break; - case PhabricatorCalendarEventInvitee::STATUS_ATTENDING: - $text = pht( - '%s is attending this event.', - $this->renderHandleLink($author_phid)); - break; - case PhabricatorCalendarEventInvitee::STATUS_DECLINED: - case PhabricatorCalendarEventInvitee::STATUS_UNINVITED: - $text = pht( - '%s has declined this event.', - $this->renderHandleLink($author_phid)); - break; - default: - $text = pht( - '%s has changed their status for this event.', - $this->renderHandleLink($author_phid)); - break; - } - } - } else { - // user changed status for many users - $self_joined = null; - $self_declined = null; - $added = array(); - $uninvited = array(); - - foreach ($new as $phid => $status) { - if ($status == PhabricatorCalendarEventInvitee::STATUS_INVITED - || $status == PhabricatorCalendarEventInvitee::STATUS_ATTENDING) { - // added users - $added[] = $phid; - } else if ( - $status == PhabricatorCalendarEventInvitee::STATUS_DECLINED - || $status == PhabricatorCalendarEventInvitee::STATUS_UNINVITED) { - $uninvited[] = $phid; - } - } - - $count_added = count($added); - $count_uninvited = count($uninvited); - $added_text = null; - $uninvited_text = null; - - if ($count_added > 0 && $count_uninvited == 0) { - $added_text = $this->renderHandleList($added); - $text = pht('%s invited %s.', - $this->renderHandleLink($author_phid), - $added_text); - } else if ($count_added > 0 && $count_uninvited > 0) { - $added_text = $this->renderHandleList($added); - $uninvited_text = $this->renderHandleList($uninvited); - $text = pht('%s invited %s and uninvited %s.', - $this->renderHandleLink($author_phid), - $added_text, - $uninvited_text); - } else if ($count_added == 0 && $count_uninvited > 0) { - $uninvited_text = $this->renderHandleList($uninvited); - $text = pht('%s uninvited %s.', - $this->renderHandleLink($author_phid), - $uninvited_text); - } else { - $text = pht('%s updated the invitee list.', - $this->renderHandleLink($author_phid)); - } - } - return $text; - case self::TYPE_RECURRING: - $text = pht('%s made this event recurring.', - $this->renderHandleLink($author_phid)); - return $text; - case self::TYPE_FREQUENCY: - $rule = $new; - if (is_array($rule)) { - $rule = idx($rule, 'rule'); - } - - $text = ''; - switch ($rule) { - case PhabricatorCalendarEvent::FREQUENCY_DAILY: - $text = pht('%s set this event to repeat daily.', - $this->renderHandleLink($author_phid)); - break; - case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: - $text = pht('%s set this event to repeat weekly.', - $this->renderHandleLink($author_phid)); - break; - case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: - $text = pht('%s set this event to repeat monthly.', - $this->renderHandleLink($author_phid)); - break; - case PhabricatorCalendarEvent::FREQUENCY_YEARLY: - $text = pht('%s set this event to repeat yearly.', - $this->renderHandleLink($author_phid)); - break; - } - return $text; - case self::TYPE_RECURRENCE_END_DATE: - $text = pht('%s has changed the recurrence end date of this event.', - $this->renderHandleLink($author_phid)); - return $text; - } - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $viewer = $this->getViewer(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s changed the name of %s from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - } - case self::TYPE_START_DATE: - if ($old) { - $old = phabricator_datetime($old, $viewer); - $new = phabricator_datetime($new, $viewer); - return pht( - '%s changed the start date of %s from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - } - break; - case self::TYPE_END_DATE: - if ($old) { - $old = phabricator_datetime($old, $viewer); - $new = phabricator_datetime($new, $viewer); - return pht( - '%s edited the end date of %s from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_ALL_DAY: - if ($new) { - return pht( - '%s made %s an all day event.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s converted %s from an all day event.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - case self::TYPE_ICON: - $set = new PhabricatorCalendarIconSet(); - return pht( - '%s set the icon for %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $set->getIconLabel($new)); - case self::TYPE_CANCEL: - if ($new) { - return pht( - '%s cancelled %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s reinstated %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - case self::TYPE_ACCEPT: - return pht( - '%s is attending %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_DECLINE: - return pht( - '%s declined %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_INVITE: - $text = null; - - list($old, $new) = $this->getSimpleInvites($old, $new); - - if (count($old) === 1 - && count($new) === 1 - && isset($old[$author_phid])) { - // user joined/declined/accepted event themself - $old_status = $old[$author_phid]; - $new_status = $new[$author_phid]; - - if ($old_status !== $new_status) { - switch ($new_status) { - case PhabricatorCalendarEventInvitee::STATUS_INVITED: - $text = pht( - '%s has joined %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case PhabricatorCalendarEventInvitee::STATUS_ATTENDING: - $text = pht( - '%s is attending %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case PhabricatorCalendarEventInvitee::STATUS_DECLINED: - case PhabricatorCalendarEventInvitee::STATUS_UNINVITED: - $text = pht( - '%s has declined %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - default: - $text = pht( - '%s has changed their status of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - } - } - } else { - // user changed status for many users - $self_joined = null; - $self_declined = null; - $added = array(); - $uninvited = array(); - - foreach ($new as $phid => $status) { - if ($status == PhabricatorCalendarEventInvitee::STATUS_INVITED - || $status == PhabricatorCalendarEventInvitee::STATUS_ATTENDING) { - // added users - $added[] = $phid; - } else if ( - $status == PhabricatorCalendarEventInvitee::STATUS_DECLINED - || $status == PhabricatorCalendarEventInvitee::STATUS_UNINVITED) { - $uninvited[] = $phid; - } - } - - $count_added = count($added); - $count_uninvited = count($uninvited); - $added_text = null; - $uninvited_text = null; - - if ($count_added > 0 && $count_uninvited == 0) { - $added_text = $this->renderHandleList($added); - $text = pht('%s invited %s to %s.', - $this->renderHandleLink($author_phid), - $added_text, - $this->renderHandleLink($object_phid)); - } else if ($count_added > 0 && $count_uninvited > 0) { - $added_text = $this->renderHandleList($added); - $uninvited_text = $this->renderHandleList($uninvited); - $text = pht('%s invited %s and uninvited %s to %s.', - $this->renderHandleLink($author_phid), - $added_text, - $uninvited_text, - $this->renderHandleLink($object_phid)); - } else if ($count_added == 0 && $count_uninvited > 0) { - $uninvited_text = $this->renderHandleList($uninvited); - $text = pht('%s uninvited %s to %s.', - $this->renderHandleLink($author_phid), - $uninvited_text, - $this->renderHandleLink($object_phid)); - } else { - $text = pht('%s updated the invitee list of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - } - return $text; - case self::TYPE_RECURRING: - $text = pht('%s made %s a recurring event.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - return $text; - case self::TYPE_FREQUENCY: - $rule = $new; - if (is_array($rule)) { - $rule = idx($rule, 'rule'); - } - - $text = ''; - switch ($rule) { - case PhabricatorCalendarEvent::FREQUENCY_DAILY: - $text = pht('%s set %s to repeat daily.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: - $text = pht('%s set %s to repeat weekly.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: - $text = pht('%s set %s to repeat monthly.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case PhabricatorCalendarEvent::FREQUENCY_YEARLY: - $text = pht('%s set %s to repeat yearly.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - } - return $text; - case self::TYPE_RECURRENCE_END_DATE: - $text = pht('%s set the recurrence end date of %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new); - return $text; - } - - return parent::getTitleForFeed(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - case self::TYPE_START_DATE: - case self::TYPE_END_DATE: - case self::TYPE_DESCRIPTION: - case self::TYPE_CANCEL: - case self::TYPE_INVITE: - return PhabricatorTransactions::COLOR_GREEN; - } - - return parent::getColor(); - } - - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - return $this->renderTextCorpusChangeDetails( - $viewer, - $old, - $new); - } - - return parent::renderChangeDetails($viewer); + public function getBaseTransactionClass() { + return 'PhabricatorCalendarEventTransactionType'; } public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_NAME: - case self::TYPE_DESCRIPTION: - case self::TYPE_INVITE: - case self::TYPE_ICON: + case PhabricatorCalendarEventNameTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventDescriptionTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_CONTENT; break; - case self::TYPE_START_DATE: - case self::TYPE_END_DATE: - case self::TYPE_CANCEL: + case PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE: + case PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_RESCHEDULE; break; } return $tags; } - private function getSimpleInvites(array $old, array $new) { - // Fill in any new invitees as "uninvited" in the old data, to make - // some of the rendering logic a little easier. - $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; - $old = $old + array_fill_keys(array_keys($new), $status_uninvited); - - $all = $old + $new; - foreach (array_keys($all) as $key) { - // If the invitee exists in both the old and new lists with the same - // value, remove it from both. - if (isset($old[$key]) && isset($new[$key])) { - if ($old[$key] == $new[$key]) { - unset($old[$key]); - unset($new[$key]); - } - } - } - - return array($old, $new); - } - - } diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php new file mode 100644 index 0000000000..dc0e4ac1ea --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php @@ -0,0 +1,25 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s is attending %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php new file mode 100644 index 0000000000..1fe9320d6f --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php @@ -0,0 +1,46 @@ +getIsAllDay(); + } + + public function generateNewValue($object, $value) { + return (int)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsAllDay($value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s changed this as an all day event.', + $this->renderAuthor()); + } else { + return pht( + '%s conveted this from an all day event.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue()) { + return pht( + '%s changed %s to an all day event.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s converted %s from an all day event.', + $this->renderAuthor(), + $this->renderObject()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php new file mode 100644 index 0000000000..10a1fc9531 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php @@ -0,0 +1,46 @@ +getIsCancelled(); + } + + public function generateNewValue($object, $value) { + return (int)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsCancelled($value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s cancelled this event.', + $this->renderAuthor()); + } else { + return pht( + '%s reinstated this event.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue()) { + return pht( + '%s cancelled %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s reinstated %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php new file mode 100644 index 0000000000..5a4958ba4a --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php @@ -0,0 +1,27 @@ +getEpoch(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + if ($xaction->getNewValue()->isValid()) { + continue; + } + + $message = $this->getInvalidDateMessage(); + $errors[] = $this->newInvalidError($message, $xaction); + } + + return $errors; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php new file mode 100644 index 0000000000..89579a7144 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php @@ -0,0 +1,25 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s declined %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php new file mode 100644 index 0000000000..9c14f1ae80 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php @@ -0,0 +1,42 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the event description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the event description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php new file mode 100644 index 0000000000..124787305d --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php @@ -0,0 +1,37 @@ +getDateTo(); + } + + public function applyInternalEffects($object, $value) { + $object->setDateTo($value); + } + + public function getTitle() { + return pht( + '%s changed the end date for this event from %s to %s.', + $this->renderAuthor(), + $this->renderOldDate(), + $this->renderNewDate()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the end date for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldDate(), + $this->renderNewDate()); + } + + protected function getInvalidDateMessage() { + return pht('End date is invalid.'); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php new file mode 100644 index 0000000000..c97901250e --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php @@ -0,0 +1,75 @@ +getFrequencyUnit(); + } + + public function applyInternalEffects($object, $value) { + $object->setRecurrenceFrequency( + array( + 'rule' => $value, + )); + } + + public function getTitle() { + $frequency = $this->getFrequencyUnit($this->getNewValue()); + switch ($frequency) { + case PhabricatorCalendarEvent::FREQUENCY_DAILY: + return pht( + '%s set this event to repeat daily.', + $this->renderAuthor()); + case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + return pht( + '%s set this event to repeat weekly.', + $this->renderAuthor()); + case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + return pht( + '%s set this event to repeat monthly.', + $this->renderAuthor()); + case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + return pht( + '%s set this event to repeat yearly.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $frequency = $this->getFrequencyUnit($this->getNewValue()); + switch ($frequency) { + case PhabricatorCalendarEvent::FREQUENCY_DAILY: + return pht( + '%s set %s to repeat daily.', + $this->renderAuthor(), + $this->renderObject()); + case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + return pht( + '%s set %s to repeat weekly.', + $this->renderAuthor(), + $this->renderObject()); + case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + return pht( + '%s set %s to repeat monthly.', + $this->renderAuthor(), + $this->renderObject()); + case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + return pht( + '%s set %s to repeat yearly.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + private function getFrequencyUnit($value) { + if (is_array($value)) { + $value = idx($value, 'rule'); + } else { + return $value; + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php new file mode 100644 index 0000000000..d69cf20859 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php @@ -0,0 +1,44 @@ +getIcon(); + } + + public function applyInternalEffects($object, $value) { + $object->setIcon($value); + } + + public function getTitle() { + $old = $this->getIconLabel($this->getOldValue()); + $new = $this->getIconLabel($this->getNewValue()); + + return pht( + '%s changed the event icon from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old), + $this->renderValue($new)); + } + + public function getTitleForFeed() { + $old = $this->getIconLabel($this->getOldValue()); + $new = $this->getIconLabel($this->getNewValue()); + + return pht( + '%s changed the icon for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old), + $this->renderValue($new)); + } + + private function getIconLabel($icon) { + $set = new PhabricatorCalendarIconSet(); + return $set->getIconLabel($icon); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php new file mode 100644 index 0000000000..69575121af --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php @@ -0,0 +1,191 @@ +getInvitees(); + foreach ($invitees as $key => $invitee) { + if ($invitee->getStatus() == $status_uninvited) { + unset($invitees[$key]); + } + } + + return mpull($invitees, 'getStatus', 'getInviteePHID'); + } + + public function generateNewValue($object, $value) { + $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; + $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; + $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; + + $invitees = $this->generateOldValue($object); + + $new = array_fuse($value); + + $all = array_keys($invitees + $new); + $map = array(); + foreach ($all as $phid) { + $is_old = isset($invitees[$phid]); + $is_new = isset($new[$phid]); + + if ($is_old && !$is_new) { + $map[$phid] = $status_uninvited; + } else if (!$is_old && $is_new) { + $map[$phid] = $status_invited; + } else { + $map[$phid] = $invitees[$phid]; + } + } + + // If we're creating this event and the actor is inviting themselves, + // mark them as attending. + if ($this->isNewObject()) { + $acting_phid = $this->getActingAsPHID(); + if (isset($map[$acting_phid])) { + $map[$acting_phid] = $status_attending; + } + } + + return $map; + } + + public function applyExternalEffects($object, $value) { + $phids = array_keys($value); + $invitees = $object->getInvitees(); + $invitees = mpull($invitees, null, 'getInviteePHID'); + + foreach ($phids as $phid) { + $invitee = idx($invitees, $phid); + if (!$invitee) { + $invitee = id(new PhabricatorCalendarEventInvitee()) + ->setEventPHID($object->getPHID()) + ->setInviteePHID($phid) + ->setInviterPHID($this->getActingAsPHID()); + $invitees[] = $invitee; + } + $invitee->setStatus($value[$phid]) + ->save(); + } + + $object->attachInvitees($invitees); + } + + public function validateTransactions($object, array $xactions) { + $actor = $this->getActor(); + + $errors = array(); + + $old = $object->getInvitees(); + $old = mpull($old, null, 'getInviteePHID'); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + $new = array_fuse($new); + $add = array_diff_key($new, $old); + if (!$add) { + continue; + } + + // In the UI, we only allow you to invite mailable objects, but there + // is no definitive marker for "invitable object" today. Just allow + // any valid object to be invited. + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($actor) + ->withPHIDs($add) + ->execute(); + $objects = mpull($objects, null, 'getPHID'); + foreach ($add as $phid) { + if (isset($objects[$phid])) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'Invitee "%s" identifies an object that does not exist or '. + 'which you do not have permission to view.', + $phid), + $xaction); + } + } + + return $errors; + } + + public function getIcon() { + return 'fa-user-plus'; + } + + public function getTitle() { + list($add, $rem) = $this->getChanges(); + + if ($add && !$rem) { + return pht( + '%s invited %s attendee(s): %s.', + $this->renderAuthor(), + phutil_count($add), + $this->renderHandleList($add)); + } else if (!$add && $rem) { + return pht( + '%s uninvited %s attendee(s): %s.', + $this->renderAuthor(), + phutil_count($rem), + $this->renderHandleList($rem)); + } else { + return pht( + '%s invited %s attendee(s): %s; uninvinted %s attendee(s): %s.', + $this->renderAuthor(), + phutil_count($add), + $this->renderHandleList($add), + phutil_count($rem), + $this->renderHandleList($rem)); + } + } + + public function getTitleForFeed() { + list($add, $rem) = $this->getChanges(); + + if ($add && !$rem) { + return pht( + '%s invited %s attendee(s) to %s: %s.', + $this->renderAuthor(), + phutil_count($add), + $this->renderObject(), + $this->renderHandleList($add)); + } else if (!$add && $rem) { + return pht( + '%s uninvited %s attendee(s) to %s: %s.', + $this->renderAuthor(), + phutil_count($rem), + $this->renderObject(), + $this->renderHandleList($rem)); + } else { + return pht( + '%s updated the invite list for %s, invited %s: %s; '. + 'uninvinted %s: %s.', + $this->renderAuthor(), + $this->renderObject(), + phutil_count($add), + $this->renderHandleList($add), + phutil_count($rem), + $this->renderHandleList($rem)); + } + } + + private function getChanges() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff_key($new, $old); + $rem = array_diff_key($old, $new); + + $add = array_keys($add); + $rem = array_keys($rem); + + return array(array_fuse($add), array_fuse($rem)); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php new file mode 100644 index 0000000000..64fbd84010 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php @@ -0,0 +1,44 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this event from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Events must have a name.')); + } + + return $errors; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php new file mode 100644 index 0000000000..04062953b1 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php @@ -0,0 +1,44 @@ +getIsRecurring(); + } + + public function generateNewValue($object, $value) { + return (int)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsRecurring($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $old = $object->getIsRecurring(); + foreach ($xactions as $xaction) { + if ($this->getIsNewObject()) { + continue; + } + + if ($xaction->getNewValue() == $old) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'An event can only be made recurring when it is created. '. + 'You can not convert an existing event into a recurring '. + 'event or vice versa.'), + $xaction); + } + + return $errors; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php new file mode 100644 index 0000000000..e2ad9ea4be --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php @@ -0,0 +1,33 @@ +getActingAsPHID(); + return $object->getUserInviteStatus($actor_phid); + } + + public function applyExternalEffects($object, $value) { + $acting_phid = $this->getActingAsPHID(); + + $invitees = $object->getInvitees(); + $invitees = mpull($invitees, null, 'getInviteePHID'); + + $invitee = idx($invitees, $acting_phid); + if (!$invitee) { + $invitee = id(new PhabricatorCalendarEventInvitee()) + ->setEventPHID($object->getPHID()) + ->setInviteePHID($acting_phid) + ->setInviterPHID($acting_phid); + $invitees[$acting_phid] = $invitee; + } + + $invitee + ->setStatus($value) + ->save(); + + $object->attachInvitees($invitees); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php new file mode 100644 index 0000000000..31a41a3967 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php @@ -0,0 +1,37 @@ +getDateFrom(); + } + + public function applyInternalEffects($object, $value) { + $object->setDateFrom($value); + } + + public function getTitle() { + return pht( + '%s changed the start date for this event from %s to %s.', + $this->renderAuthor(), + $this->renderOldDate(), + $this->renderNewDate()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the start date for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldDate(), + $this->renderNewDate()); + } + + protected function getInvalidDateMessage() { + return pht('Start date is invalid.'); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php b/src/applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php new file mode 100644 index 0000000000..53c00969e9 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php @@ -0,0 +1,4 @@ +getRecurrenceEndDate(); + } + + public function applyInternalEffects($object, $value) { + $object->setRecurrenceEndDate($value); + } + + public function getTitle() { + return pht( + '%s changed this event to repeat until %s.', + $this->renderAuthor(), + $this->renderNewDate()); + } + + public function getTitleForFeed() { + return pht( + '%s changed %s to repeat until %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewDate()); + } + + protected function getInvalidDateMessage() { + return pht('Repeat until date is invalid.'); + } + +} diff --git a/src/applications/phid/view/PHUIHandleListView.php b/src/applications/phid/view/PHUIHandleListView.php index cc9f3d00bd..f7f02a391b 100644 --- a/src/applications/phid/view/PHUIHandleListView.php +++ b/src/applications/phid/view/PHUIHandleListView.php @@ -12,6 +12,7 @@ final class PHUIHandleListView private $handleList; private $asInline; + private $asText; public function setHandleList(PhabricatorHandleList $list) { $this->handleList = $list; @@ -27,6 +28,15 @@ final class PHUIHandleListView return $this->asInline; } + public function setAsText($as_text) { + $this->asText = $as_text; + return $this; + } + + public function getAsText() { + return $this->asText; + } + protected function getTagName() { // TODO: It would be nice to render this with a proper
    , at least in // block mode, but don't stir the waters up too much for now. @@ -37,9 +47,9 @@ final class PHUIHandleListView $list = $this->handleList; $items = array(); foreach ($list as $handle) { - $view = $list->renderHandle($handle->getPHID()); - - $view->setShowHovercard(true); + $view = $list->renderHandle($handle->getPHID()) + ->setShowHovercard(true) + ->setAsText($this->getAsText()); $items[] = $view; } diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index a76f734c14..c98f914559 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -86,6 +86,14 @@ abstract class PhabricatorModularTransactionType return $this->viewer; } + final public function getActor() { + return $this->getEditor()->getActor(); + } + + final public function getActingAsPHID() { + return $this->getEditor()->getActingAsPHID(); + } + final public function setEditor( PhabricatorApplicationTransactionEditor $editor) { $this->editor = $editor; @@ -141,6 +149,19 @@ abstract class PhabricatorModularTransactionType return $display; } + final protected function renderHandleList(array $phids) { + $viewer = $this->getViewer(); + $display = $viewer->renderHandleList($phids) + ->setAsInline(true); + + $rendering_target = $this->getStorage()->getRenderingTarget(); + if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { + $display->setAsText(true); + } + + return $display; + } + final protected function renderValue($value) { $rendering_target = $this->getStorage()->getRenderingTarget(); if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { @@ -155,6 +176,32 @@ abstract class PhabricatorModularTransactionType $value); } + final protected function renderOldValue() { + return $this->renderValue($this->getOldValue()); + } + + final protected function renderNewValue() { + return $this->renderValue($this->getNewValue()); + } + + final protected function renderDate($epoch) { + $viewer = $this->getViewer(); + + $display = phabricator_datetime($epoch, $viewer); + + // TODO: When rendering for email, include the UTC offset. See T10633. + + return $this->renderValue($display); + } + + final protected function renderOldDate() { + return $this->renderDate($this->getOldValue()); + } + + final protected function renderNewDate() { + return $this->renderDate($this->getNewValue()); + } + final protected function newError($title, $message, $xaction = null) { return new PhabricatorApplicationTransactionValidationError( $this->getTransactionTypeConstant(), @@ -163,4 +210,26 @@ abstract class PhabricatorModularTransactionType $xaction); } + final protected function newRequiredError($message, $xaction = null) { + return $this->newError(pht('Required'), $message, $xaction) + ->setIsMissingFieldError(true); + } + + final protected function newInvalidError($message, $xaction = null) { + return $this->newError(pht('Invalid'), $message, $xaction); + } + + final protected function isNewObject() { + return $this->getEditor()->getIsNewObject(); + } + + final protected function isEmptyTextTransaction($value, array $xactions) { + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + } + + return !strlen($value); + } + + } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 23cb079297..3b4cf6abc7 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1560,6 +1560,24 @@ final class PhabricatorUSEnglishTranslation ), ), ), + + '%s invited %s attendee(s): %s.' => + '%s invited: %3$s.', + + '%s uninvited %s attendee(s): %s.' => + '%s uninvited: %3$s.', + + '%s invited %s attendee(s): %s; uninvinted %s attendee(s): %s.' => + '%s invited: %3$s; uninvited: %5$s.', + + '%s invited %s attendee(s) to %s: %s.' => + '%s added invites for %3$s: %4$s.', + + '%s uninvited %s attendee(s) to %s: %s.' => + '%s removed invites for %3$s: %4$s.', + + '%s updated the invite list for %s, invited %s: %s; uninvinted %s: %s.' => + '%s updated the invite list for %s, invited: %4$s; uninvited: %6$s.', ); } From 26c6f64fd469e9673c58821f279f472729ad59ca Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 Jul 2016 08:06:54 -0700 Subject: [PATCH 23/76] In email, render dates with an explicit timezone offset Summary: Fixes T10633. When generating email about a transaction which adjusts a date, render the offset explicitly (like "UTC-7"). This makes it more clear in cases like this: - mail is being sent to multiple users, and not necessarily using the viewer's settings; - you get some mail while travelling and aren't sure which timezone setting it generated under. Test Plan: Rendered in text mode, saw UTC offset. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10633 Differential Revision: https://secure.phabricator.com/D16287 --- .../people/storage/PhabricatorUser.php | 8 ++++++ .../PhabricatorModularTransactionType.php | 27 ++++++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 81a5126474..acbf477eb4 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -832,6 +832,14 @@ final class PhabricatorUser return $offset; } + public function getTimeZoneOffsetInHours() { + $offset = $this->getTimeZoneOffset(); + $offset = (int)round($offset / 60); + $offset = -$offset; + + return $offset; + } + public function formatShortDateTime($when, $now = null) { if ($now === null) { $now = PhabricatorTime::getNow(); diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index c98f914559..66cdf5a570 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -141,8 +141,7 @@ abstract class PhabricatorModularTransactionType $viewer = $this->getViewer(); $display = $viewer->renderHandle($phid); - $rendering_target = $this->getStorage()->getRenderingTarget(); - if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { + if ($this->isTextMode()) { $display->setAsText(true); } @@ -154,8 +153,7 @@ abstract class PhabricatorModularTransactionType $display = $viewer->renderHandleList($phids) ->setAsInline(true); - $rendering_target = $this->getStorage()->getRenderingTarget(); - if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { + if ($this->isTextMode()) { $display->setAsText(true); } @@ -163,8 +161,7 @@ abstract class PhabricatorModularTransactionType } final protected function renderValue($value) { - $rendering_target = $this->getStorage()->getRenderingTarget(); - if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { + if ($this->isTextMode()) { return sprintf('"%s"', $value); } @@ -189,7 +186,19 @@ abstract class PhabricatorModularTransactionType $display = phabricator_datetime($epoch, $viewer); - // TODO: When rendering for email, include the UTC offset. See T10633. + // When rendering to text, we explicitly render the offset from UTC to + // provide context to the date: the mail may be generating with the + // server's settings, or the user may later refer back to it after changing + // timezones. + + if ($this->isTextMode()) { + $offset = $viewer->getTimeZoneOffsetInHours(); + if ($offset >= 0) { + $display = pht('%s (UTC+%d)', $display, $offset); + } else { + $display = pht('%s (UTC-%d)', $display, abs($offset)); + } + } return $this->renderValue($display); } @@ -231,5 +240,9 @@ abstract class PhabricatorModularTransactionType return !strlen($value); } + private function isTextMode() { + $target = $this->getStorage()->getRenderingTarget(); + return ($target == PhabricatorApplicationTransaction::TARGET_TEXT); + } } From 8ade91486cfc643e0373fd31f6f32227fe592423 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Jul 2016 08:39:42 -0700 Subject: [PATCH 24/76] Add `calendar.event.search` and `calendar.event.edit` Summary: Ref T7944. The search method is a bit bare-bones for now, but these substantially work. Test Plan: Edited events via API; queried events via API. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7944 Differential Revision: https://secure.phabricator.com/D16288 --- src/__phutil_library_map__.php | 9 +++- ...catorCalendarEventEditConduitAPIMethod.php | 19 +++++++ ...torCalendarEventSearchConduitAPIMethod.php | 18 +++++++ ...PhabricatorCalendarEventEditController.php | 2 +- ...PhabricatorCalendarEventListController.php | 2 +- ...PhabricatorCalendarEventViewController.php | 2 +- ...=> PhabricatorCalendarEventEditEngine.php} | 2 +- .../storage/PhabricatorCalendarEvent.php | 52 +++++++++++++++---- 8 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 src/applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php create mode 100644 src/applications/calendar/conduit/PhabricatorCalendarEventSearchConduitAPIMethod.php rename src/applications/calendar/editor/{PhabricatorCalendarEditEngine.php => PhabricatorCalendarEventEditEngine.php} (99%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 08c682de12..8397f72e1d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2019,7 +2019,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php', 'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php', 'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php', - 'PhabricatorCalendarEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEditEngine.php', 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', 'PhabricatorCalendarEventAcceptTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php', 'PhabricatorCalendarEventAllDayTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php', @@ -2029,7 +2028,9 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventDeclineTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php', 'PhabricatorCalendarEventDescriptionTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php', 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', + 'PhabricatorCalendarEventEditConduitAPIMethod' => 'applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', + 'PhabricatorCalendarEventEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEventEditEngine.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', 'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php', 'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php', @@ -2048,6 +2049,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php', 'PhabricatorCalendarEventRecurringTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php', 'PhabricatorCalendarEventReplyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php', + 'PhabricatorCalendarEventSearchConduitAPIMethod' => 'applications/calendar/conduit/PhabricatorCalendarEventSearchConduitAPIMethod.php', 'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php', 'PhabricatorCalendarEventStartDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php', 'PhabricatorCalendarEventTransaction' => 'applications/calendar/storage/PhabricatorCalendarEventTransaction.php', @@ -6630,7 +6632,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarApplication' => 'PhabricatorApplication', 'PhabricatorCalendarController' => 'PhabricatorController', 'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO', - 'PhabricatorCalendarEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCalendarEvent' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', @@ -6644,6 +6645,7 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', + 'PhabricatorConduitResultInterface', ), 'PhabricatorCalendarEventAcceptTransaction' => 'PhabricatorCalendarEventReplyTransaction', 'PhabricatorCalendarEventAllDayTransaction' => 'PhabricatorCalendarEventTransactionType', @@ -6653,7 +6655,9 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventDeclineTransaction' => 'PhabricatorCalendarEventReplyTransaction', 'PhabricatorCalendarEventDescriptionTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction', @@ -6675,6 +6679,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand', 'PhabricatorCalendarEventRecurringTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventReplyTransaction' => 'PhabricatorCalendarEventTransactionType', + 'PhabricatorCalendarEventSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCalendarEventStartDateTransaction' => 'PhabricatorCalendarEventDateTransaction', 'PhabricatorCalendarEventTransaction' => 'PhabricatorModularTransaction', diff --git a/src/applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php b/src/applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php new file mode 100644 index 0000000000..4f9e50f19a --- /dev/null +++ b/src/applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setController($this) ->buildResponse(); } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php index 529c206e7b..a2915b0be9 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -27,7 +27,7 @@ final class PhabricatorCalendarEventListController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - id(new PhabricatorCalendarEditEngine()) + id(new PhabricatorCalendarEventEditEngine()) ->setViewer($this->getViewer()) ->addActionToCrumbs($crumbs); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 6c771cda31..b66943db33 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -38,7 +38,7 @@ final class PhabricatorCalendarEventViewController $details = $this->buildPropertySection($event); $description = $this->buildDescriptionView($event); - $comment_view = id(new PhabricatorCalendarEditEngine()) + $comment_view = id(new PhabricatorCalendarEventEditEngine()) ->setViewer($viewer) ->buildEditEngineCommentView($event); diff --git a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php similarity index 99% rename from src/applications/calendar/editor/PhabricatorCalendarEditEngine.php rename to src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php index 75772d5141..c53bb823ab 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -1,6 +1,6 @@ setKey('name') + ->setType('string') + ->setDescription(pht('The name of the event.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('description') + ->setType('string') + ->setDescription(pht('The event description.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'description' => $this->getDescription(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } From 872bcd4487e608513deba9c5b379e49c91e37795 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Jul 2016 09:29:02 -0700 Subject: [PATCH 25/76] Make limits and ranges work better with Calendar event queries Summary: Fixes T8911. This corrects several issues which could crop up if a calendar event query matched more results than the query limit: - The desired order was not applied by the SearchEngine -- it applies the first builtin order instead. Provide a proper builtin order. - When we generate ghosts, we can't do limiting in the database because we may select and then immediately discard a large number of parent events which are outside of the query range. - For now, just don't limit results to get the behavior correct. - This may need to be refined eventually to improve performance. - When trimming events, we could trim parents and fail to generate ghosts from them. Separate parent events out first. - Try to simplify some logic. Test Plan: An "Upcoming" dashboard panel with limit 10 and the main Calendar "Upcoming Events" UI now show the same results. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8911 Differential Revision: https://secure.phabricator.com/D16289 --- .../query/PhabricatorCalendarEventQuery.php | 195 +++++++++++------- .../PhabricatorCalendarEventSearchEngine.php | 5 +- ...PhabricatorCursorPagedPolicyAwareQuery.php | 15 +- 3 files changed, 136 insertions(+), 79 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 3eac223018..e735e8220f 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -75,6 +75,15 @@ final class PhabricatorCalendarEventQuery return array('start', 'id'); } + public function getBuiltinOrders() { + return array( + 'start' => array( + 'vector' => array('start', 'id'), + 'name' => pht('Event Start'), + ), + ) + parent::getBuiltinOrders(); + } + public function getOrderableColumns() { return array( 'start' => array( @@ -95,6 +104,17 @@ final class PhabricatorCalendarEventQuery ); } + protected function shouldLimitResults() { + // When generating ghosts, we can't rely on database ordering because + // MySQL can't predict the ghost start times. We'll just load all matching + // events, then generate results from there. + if ($this->generateGhosts) { + return false; + } + + return true; + } + protected function loadPage() { $events = $this->loadStandardPage($this->newResultObject()); @@ -107,7 +127,6 @@ final class PhabricatorCalendarEventQuery return $events; } - $enforced_end = null; $raw_limit = $this->getRawResultLimit(); if (!$raw_limit && !$this->rangeEnd) { @@ -121,7 +140,6 @@ final class PhabricatorCalendarEventQuery foreach ($events as $key => $event) { $sequence_start = 0; $sequence_end = null; - $duration = $event->getDuration(); $end = null; $instance_of = $event->getInstanceOfEventPHID(); @@ -132,78 +150,97 @@ final class PhabricatorCalendarEventQuery continue; } } + } - if ($event->getIsRecurring() && $instance_of == null) { - $frequency = $event->getFrequencyUnit(); - $modify_key = '+1 '.$frequency; + // Pull out all of the parents first. We may discard them as we begin + // generating ghost events, but we still want to process all of them. + $parents = array(); + foreach ($events as $key => $event) { + if ($event->isParentEvent()) { + $parents[$key] = $event; + } + } - if (($this->rangeBegin !== null) && - ($this->rangeBegin > $event->getViewerDateFrom())) { - $max_date = $this->rangeBegin - $duration; - $date = $event->getViewerDateFrom(); - $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); + // Now that we've picked out all the parent events, we can immediately + // discard anything outside of the time window. + $events = $this->getEventsInRange($events); - while ($date < $max_date) { - // TODO: optimize this to not loop through all off-screen events - $sequence_start++; - $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); - $date = $datetime->modify($modify_key)->format('U'); - } + $enforced_end = null; + foreach ($parents as $key => $event) { + $sequence_start = 0; + $sequence_end = null; + $start = null; - $start = $this->rangeBegin; - } else { - $start = $event->getViewerDateFrom() - $duration; - } + $duration = $event->getDuration(); - $date = $start; + $frequency = $event->getFrequencyUnit(); + $modify_key = '+1 '.$frequency; + + if (($this->rangeBegin !== null) && + ($this->rangeBegin > $event->getViewerDateFrom())) { + $max_date = $this->rangeBegin - $duration; + $date = $event->getViewerDateFrom(); $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); - if (($this->rangeEnd && $event->getRecurrenceEndDate()) && - $this->rangeEnd < $event->getRecurrenceEndDate()) { - $end = $this->rangeEnd; - } else if ($event->getRecurrenceEndDate()) { - $end = $event->getRecurrenceEndDate(); - } else if ($this->rangeEnd) { - $end = $this->rangeEnd; - } else if ($enforced_end) { - if ($end) { - $end = min($end, $enforced_end); - } else { - $end = $enforced_end; - } + while ($date < $max_date) { + // TODO: optimize this to not loop through all off-screen events + $sequence_start++; + $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); + $date = $datetime->modify($modify_key)->format('U'); } - if ($end) { - $sequence_end = $sequence_start; - while ($date < $end) { - $sequence_end++; - $datetime->modify($modify_key); - $date = $datetime->format('U'); - if ($sequence_end > $raw_limit + $sequence_start) { - break; - } + $start = $this->rangeBegin; + } else { + $start = $event->getViewerDateFrom() - $duration; + } + + $date = $start; + $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); + + // Select the minimum end time we need to generate events until. + $end_times = array(); + if ($this->rangeEnd) { + $end_times[] = $this->rangeEnd; + } + + if ($event->getRecurrenceEndDate()) { + $end_times[] = $event->getRecurrenceEndDate(); + } + + if ($enforced_end) { + $end_times[] = $enforced_end; + } + + if ($end_times) { + $end = min($end_times); + $sequence_end = $sequence_start; + while ($date < $end) { + $sequence_end++; + $datetime->modify($modify_key); + $date = $datetime->format('U'); + if ($sequence_end > $raw_limit + $sequence_start) { + break; } - } else { - $sequence_end = $raw_limit + $sequence_start; } + } else { + $sequence_end = $raw_limit + $sequence_start; + } - $sequence_start = max(1, $sequence_start); + $sequence_start = max(1, $sequence_start); + for ($index = $sequence_start; $index < $sequence_end; $index++) { + $events[] = $event->newGhost($viewer, $index); + } - for ($index = $sequence_start; $index < $sequence_end; $index++) { - $events[] = $event->newGhost($viewer, $index); - } + // NOTE: We're slicing results every time because this makes it cheaper + // to generate future ghosts. If we already have 100 events that occur + // before July 1, we know we never need to generate ghosts after that + // because they couldn't possibly ever appear in the result set. - // NOTE: We're slicing results every time because this makes it cheaper - // to generate future ghosts. If we already have 100 events that occur - // before July 1, we know we never need to generate ghosts after that - // because they couldn't possibly ever appear in the result set. - - if ($raw_limit) { - if (count($events) >= $raw_limit) { - $events = msort($events, 'getViewerDateFrom'); - $events = array_slice($events, 0, $raw_limit, true); - $enforced_end = last($events)->getViewerDateFrom(); - } + if ($raw_limit) { + if (count($events) > $raw_limit) { + $events = msort($events, 'getViewerDateFrom'); + $events = array_slice($events, 0, $raw_limit, true); + $enforced_end = last($events)->getViewerDateFrom(); } } } @@ -271,11 +308,14 @@ final class PhabricatorCalendarEventQuery } } + $events = msort($events, 'getViewerDateFrom'); + return $events; } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) { $parts = parent::buildJoinClauseParts($conn_r); + if ($this->inviteePHIDs !== null) { $parts[] = qsprintf( $conn_r, @@ -284,6 +324,7 @@ final class PhabricatorCalendarEventQuery id(new PhabricatorCalendarEventInvitee())->getTableName(), PhabricatorCalendarEventInvitee::STATUS_UNINVITED); } + return $parts; } @@ -397,23 +438,11 @@ final class PhabricatorCalendarEventQuery protected function willFilterPage(array $events) { - $range_start = $this->rangeBegin; - $range_end = $this->rangeEnd; $instance_of_event_phids = array(); $recurring_events = array(); $viewer = $this->getViewer(); - foreach ($events as $key => $event) { - $event_start = $event->getViewerDateFrom(); - $event_end = $event->getViewerDateTo(); - - if ($range_start && $event_end < $range_start) { - unset($events[$key]); - } - if ($range_end && $event_start > $range_end) { - unset($events[$key]); - } - } + $events = $this->getEventsInRange($events); $phids = array(); @@ -476,4 +505,24 @@ final class PhabricatorCalendarEventQuery return $events; } + private function getEventsInRange(array $events) { + $range_start = $this->rangeBegin; + $range_end = $this->rangeEnd; + + foreach ($events as $key => $event) { + $event_start = $event->getViewerDateFrom(); + $event_end = $event->getViewerDateTo(); + + if ($range_start && $event_end < $range_start) { + unset($events[$key]); + } + + if ($range_end && $event_start > $range_end) { + unset($events[$key]); + } + } + + return $events; + } + } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 3465d3ee71..bdff84fc1d 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -192,10 +192,11 @@ final class PhabricatorCalendarEventSearchEngine } if ($upcoming) { + $now = PhabricatorTime::getNow(); if ($min_range) { - $min_range = max(time(), $min_range); + $min_range = max($now, $min_range); } else { - $min_range = time(); + $min_range = $now; } } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 0efca33b6a..8d43980122 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -142,11 +142,18 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } final protected function buildLimitClause(AphrontDatabaseConnection $conn_r) { - if ($this->getRawResultLimit()) { - return qsprintf($conn_r, 'LIMIT %d', $this->getRawResultLimit()); - } else { - return ''; + if ($this->shouldLimitResults()) { + $limit = $this->getRawResultLimit(); + if ($limit) { + return qsprintf($conn_r, 'LIMIT %d', $limit); + } } + + return ''; + } + + protected function shouldLimitResults() { + return true; } final protected function didLoadResults(array $results) { From 8062423271c76d3482d7e7e51b62286f246fe71c Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Jul 2016 10:26:18 -0700 Subject: [PATCH 26/76] Respect 12 hour vs 24 hour time formats in Calendar day views Summary: Fixes T9202. Test Plan: - Viewed day in 12-hour, saw "8:00 PM". - Viewed day in 24-hour, saw "16:00". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9202, T10932 Differential Revision: https://secure.phabricator.com/D16290 --- resources/celerity/map.php | 4 ++-- src/view/phui/calendar/PHUICalendarDayView.php | 2 +- webroot/rsrc/js/application/calendar/behavior-day-view.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0aa02f6894..31a56d7e70 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -362,7 +362,7 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2', + 'rsrc/js/application/calendar/behavior-day-view.js' => '1a5bb063', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/calendar/behavior-recurring-edit.js' => '5f1c4d5f', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', @@ -603,7 +603,7 @@ return array( 'javelin-behavior-dashboard-move-panels' => '019f36c4', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', - 'javelin-behavior-day-view' => '5c46cff2', + 'javelin-behavior-day-view' => '1a5bb063', 'javelin-behavior-desktop-notifications-control' => 'edd1ba66', 'javelin-behavior-detect-timezone' => '4c193c96', 'javelin-behavior-device' => 'bb1dd507', diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index d7b5f9bda2..ee9e8b5cf4 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -61,7 +61,7 @@ final class PHUICalendarDayView extends AphrontView { foreach ($hours as $hour) { $js_hours[] = array( 'hour' => $hour->format('G'), - 'hour_meridian' => $hour->format('g A'), + 'displayTime' => phabricator_time($hour->format('U'), $viewer), ); } diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index 5be51ce99c..651698b026 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -169,13 +169,13 @@ JX.behavior('day-view', function(config) { var cell_time = JX.$N( 'td', {className: 'phui-calendar-day-hour'}, - hours[i]['hour_meridian']); + hours[i].displayTime); var cell_event = JX.$N( 'td', { meta: { - time: hours[i]['hour_meridian'] + time: hours[i].displayTime }, className: 'phui-calendar-day-events', sigil: 'phui-calendar-day-event-cell' From b6daa049de73ee2f04140cd7e0cad75088c2f779 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Jul 2016 11:33:43 -0700 Subject: [PATCH 27/76] Rename Event "userPHID" to "hostPHID" Summary: Ref T10909. Ref T9224. We label this field "Host" in the UI; make the storage format consistent. Test Plan: - Viewed month view, day view, detail view of an event. - Created a new event, saw myself as the host. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9224, T10909 Differential Revision: https://secure.phabricator.com/D16291 --- .../20150506.calendarunnamedevents.1.php | 2 +- .../autopatches/20160713.event.01.host.sql | 2 ++ ...PhabricatorCalendarEventViewController.php | 2 +- .../editor/PhabricatorCalendarEventEditor.php | 4 +-- .../PhabricatorCalendarEventSearchEngine.php | 20 ++++---------- ...PhabricatorCalendarEventFulltextEngine.php | 4 +-- .../storage/PhabricatorCalendarEvent.php | 27 ++++++++++--------- .../view/AphrontCalendarEventView.php | 10 +++---- ...catorCalendarEventRecurringTransaction.php | 2 +- ...ifferentialInlineCommentEditController.php | 2 +- 10 files changed, 34 insertions(+), 41 deletions(-) create mode 100644 resources/sql/autopatches/20160713.event.01.host.sql diff --git a/resources/sql/autopatches/20150506.calendarunnamedevents.1.php b/resources/sql/autopatches/20150506.calendarunnamedevents.1.php index 00512de9ed..89a2650c73 100644 --- a/resources/sql/autopatches/20150506.calendarunnamedevents.1.php +++ b/resources/sql/autopatches/20150506.calendarunnamedevents.1.php @@ -17,7 +17,7 @@ foreach ($iterator as $event) { // later patch. See T8209. $user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) - ->withPHIDs(array($event->getUserPHID())) + ->withPHIDs(array($event->getHostPHID())) ->executeOne(); if ($user) { diff --git a/resources/sql/autopatches/20160713.event.01.host.sql b/resources/sql/autopatches/20160713.event.01.host.sql new file mode 100644 index 0000000000..d1a6dd643b --- /dev/null +++ b/resources/sql/autopatches/20160713.event.01.host.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + CHANGE userPHID hostPHID VARBINARY(64) NOT NULL; diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index b66943db33..b964aacfbf 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -245,7 +245,7 @@ final class PhabricatorCalendarEventViewController $properties->addProperty( pht('Host'), - $viewer->renderHandle($event->getUserPHID())); + $viewer->renderHandle($event->getHostPHID())); $invitees = $event->getInvitees(); foreach ($invitees as $key => $invitee) { diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index efa66e2b09..05e2449dbb 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -197,8 +197,8 @@ final class PhabricatorCalendarEventEditor protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); - if ($object->getUserPHID()) { - $phids[] = $object->getUserPHID(); + if ($object->getHostPHID()) { + $phids[] = $object->getHostPHID(); } $phids[] = $this->getActingAsPHID(); diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index bdff84fc1d..f3489c5cee 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -248,16 +248,6 @@ final class PhabricatorCalendarEventSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $objects, - PhabricatorSavedQuery $query) { - $phids = array(); - foreach ($objects as $event) { - $phids[$event->getUserPHID()] = 1; - } - return array_keys($phids); - } - protected function renderResultList( array $events, PhabricatorSavedQuery $query, @@ -275,7 +265,6 @@ final class PhabricatorCalendarEventSearchEngine foreach ($events as $event) { $event_date_info = $this->getEventDateLabel($event); - $creator_handle = $handles[$event->getUserPHID()]; $attendees = array(); foreach ($event->getInvitees() as $invitee) { @@ -364,7 +353,8 @@ final class PhabricatorCalendarEventSearchEngine $month_view->setUser($viewer); - $phids = mpull($statuses, 'getUserPHID'); + $phids = mpull($statuses, 'getHostPHID'); + $handles = $viewer->loadHandles($phids); foreach ($statuses as $status) { $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); @@ -376,9 +366,9 @@ final class PhabricatorCalendarEventSearchEngine $event->setIsAllDay($status->getIsAllDay()); $event->setIcon($status->getIcon()); - $name_text = $handles[$status->getUserPHID()]->getName(); + $name_text = $handles[$status->getHostPHID()]->getName(); $status_text = $status->getName(); - $event->setUserPHID($status->getUserPHID()); + $event->setHostPHID($status->getHostPHID()); $event->setDescription(pht('%s (%s)', $name_text, $status_text)); $event->setName($status_text); $event->setURI($status->getURI()); @@ -419,7 +409,7 @@ final class PhabricatorCalendarEventSearchEngine $day_view->setUser($viewer); - $phids = mpull($statuses, 'getUserPHID'); + $phids = mpull($statuses, 'getHostPHID'); foreach ($statuses as $status) { if ($status->getIsCancelled()) { diff --git a/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php b/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php index 8447a6a30d..d50a86915e 100644 --- a/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php +++ b/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php @@ -17,13 +17,13 @@ final class PhabricatorCalendarEventFulltextEngine $document->addRelationship( PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, - $event->getUserPHID(), + $event->getHostPHID(), PhabricatorPeopleUserPHIDType::TYPECONST, $event->getDateCreated()); $document->addRelationship( PhabricatorSearchRelationship::RELATIONSHIP_OWNER, - $event->getUserPHID(), + $event->getHostPHID(), PhabricatorPeopleUserPHIDType::TYPECONST, $event->getDateCreated()); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 208cb34d52..fdd0f4c18e 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -16,7 +16,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO PhabricatorConduitResultInterface { protected $name; - protected $userPHID; + protected $hostPHID; protected $dateFrom; protected $dateTo; protected $description; @@ -83,7 +83,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $epoch_max = $end->format('U'); return id(new PhabricatorCalendarEvent()) - ->setUserPHID($actor->getPHID()) + ->setHostPHID($actor->getPHID()) ->setIsCancelled(0) ->setIsAllDay(0) ->setIsStub(0) @@ -120,7 +120,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected function readField($field) { static $inherit = array( - 'userPHID' => true, + 'hostPHID' => true, 'isAllDay' => true, 'icon' => true, 'spacePHID' => true, @@ -156,7 +156,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $parent = $this->getParentEvent(); $this - ->setUserPHID($parent->getUserPHID()) + ->setHostPHID($parent->getHostPHID()) ->setIsAllDay($parent->getIsAllDay()) ->setIcon($parent->getIcon()) ->setSpacePHID($parent->getSpacePHID()) @@ -311,8 +311,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 'isStub' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( - 'userPHID_dateFrom' => array( - 'columns' => array('userPHID', 'dateTo'), + 'key_date' => array( + 'columns' => array('dateFrom', 'dateTo'), ), 'key_instance' => array( 'columns' => array('instanceOfEventPHID', 'sequenceIndex'), @@ -545,8 +545,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - // The owner of a task can always view and edit it. - $user_phid = $this->getUserPHID(); + // The host of an event can always view and edit it. + $user_phid = $this->getHostPHID(); if ($user_phid) { $viewer_phid = $viewer->getPHID(); if ($viewer_phid == $user_phid) { @@ -567,11 +567,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function describeAutomaticCapability($capability) { - return pht('The owner of an event can always view and edit it, - and invitees can always view it, except if the event is an - instance of a recurring event.'); + return pht( + 'The host of an event can always view and edit it. Users who are '. + 'invited to an event can always view it.'); } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -598,14 +599,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO public function isAutomaticallySubscribed($phid) { - return ($phid == $this->getUserPHID()); + return ($phid == $this->getHostPHID()); } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { - return array($this->getUserPHID()); + return array($this->getHostPHID()); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/calendar/view/AphrontCalendarEventView.php b/src/applications/calendar/view/AphrontCalendarEventView.php index 5bc0b71e31..40317047a3 100644 --- a/src/applications/calendar/view/AphrontCalendarEventView.php +++ b/src/applications/calendar/view/AphrontCalendarEventView.php @@ -2,7 +2,7 @@ final class AphrontCalendarEventView extends AphrontView { - private $userPHID; + private $hostPHID; private $name; private $epochStart; private $epochEnd; @@ -39,13 +39,13 @@ final class AphrontCalendarEventView extends AphrontView { return $this->viewerIsInvited; } - public function setUserPHID($user_phid) { - $this->userPHID = $user_phid; + public function setHostPHID($host_phid) { + $this->hostPHID = $host_phid; return $this; } - public function getUserPHID() { - return $this->userPHID; + public function getHostPHID() { + return $this->hostPHID; } public function setName($name) { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php index 04062953b1..07aa26fa94 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php @@ -22,7 +22,7 @@ final class PhabricatorCalendarEventRecurringTransaction $old = $object->getIsRecurring(); foreach ($xactions as $xaction) { - if ($this->getIsNewObject()) { + if ($this->isNewObject()) { continue; } diff --git a/src/applications/differential/controller/DifferentialInlineCommentEditController.php b/src/applications/differential/controller/DifferentialInlineCommentEditController.php index 11c91fde10..42b96920eb 100644 --- a/src/applications/differential/controller/DifferentialInlineCommentEditController.php +++ b/src/applications/differential/controller/DifferentialInlineCommentEditController.php @@ -46,7 +46,7 @@ final class DifferentialInlineCommentEditController throw new Exception( pht( 'Changeset ID "%s" is part of diff ID "%s", but that diff '. - 'is attached to reivsion "%s", not revision "%s".', + 'is attached to revision "%s", not revision "%s".', $changeset_id, $diff->getID(), $diff->getRevisionID(), From 439af11e70aa1c4767e1ccc2baefa2658a7e2873 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Jul 2016 12:36:49 -0700 Subject: [PATCH 28/76] Make event hosts editable Summary: Fixes T10909. I think this is a generally reasonable sort of capability to expose, although I've made it edit-only for now (when creating an event, you're always the host). Also clean up some minor leftovers in the code, and a couple of little bugs with recurrence frequencies. Test Plan: Created an event, edited the host of an event. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10909 Differential Revision: https://secure.phabricator.com/D16292 --- src/__phutil_library_map__.php | 2 + .../PhabricatorCalendarEventEditEngine.php | 16 ++++- .../storage/PhabricatorCalendarEvent.php | 33 +++++------ ...catorCalendarEventFrequencyTransaction.php | 8 +-- ...habricatorCalendarEventHostTransaction.php | 59 +++++++++++++++++++ .../PhabricatorModularTransactionType.php | 8 +++ 6 files changed, 101 insertions(+), 25 deletions(-) create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8397f72e1d..da6f91f900 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2036,6 +2036,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php', 'PhabricatorCalendarEventFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php', 'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php', + 'PhabricatorCalendarEventHostTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php', 'PhabricatorCalendarEventIconTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php', 'PhabricatorCalendarEventInviteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php', 'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php', @@ -6663,6 +6664,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction', 'PhabricatorCalendarEventFrequencyTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhabricatorCalendarEventHostTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventIconTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventInviteTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventInvitee' => array( diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php index c53bb823ab..3e97cb2f5e 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -23,8 +23,7 @@ final class PhabricatorCalendarEventEditEngine protected function newEditableObject() { return PhabricatorCalendarEvent::initializeNewCalendarEvent( - $this->getViewer(), - $mode = null); + $this->getViewer()); } protected function newObjectQuery() { @@ -106,6 +105,17 @@ final class PhabricatorCalendarEventEditEngine ->setConduitDescription(pht('Cancel or restore the event.')) ->setConduitTypeDescription(pht('True to cancel the event.')) ->setValue($object->getIsCancelled()), + id(new PhabricatorUsersEditField()) + ->setKey('hostPHID') + ->setAliases(array('host')) + ->setLabel(pht('Host')) + ->setDescription(pht('Host of the event.')) + ->setTransactionType( + PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE) + ->setIsConduitOnly($this->getIsCreate()) + ->setConduitDescription(pht('Change the host of the event.')) + ->setConduitTypeDescription(pht('New event host.')) + ->setSingleValue($object->getHostPHID()), id(new PhabricatorDatasourceEditField()) ->setKey('inviteePHIDs') ->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID')) @@ -141,7 +151,7 @@ final class PhabricatorCalendarEventEditEngine ->setDescription(pht('Recurring event frequency.')) ->setConduitDescription(pht('Change the event frequency.')) ->setConduitTypeDescription(pht('New event frequency.')) - ->setValue($object->getFrequencyUnit()); + ->setValue($object->getFrequencyRule()); } if ($this->getIsCreate() || $object->getIsRecurring()) { diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index fdd0f4c18e..a0122a4b3a 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -39,8 +39,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $spacePHID; - const DEFAULT_ICON = 'fa-calendar'; - private $parentEvent = self::ATTACHABLE; private $invitees = self::ATTACHABLE; @@ -53,24 +51,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO const FREQUENCY_MONTHLY = 'monthly'; const FREQUENCY_YEARLY = 'yearly'; - public static function initializeNewCalendarEvent( - PhabricatorUser $actor, - $mode) { + public static function initializeNewCalendarEvent(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorCalendarApplication')) ->executeOne(); - $view_policy = null; - $is_recurring = 0; - - if ($mode == 'public') { - $view_policy = PhabricatorPolicies::getMostOpenPolicy(); - } - - if ($mode == 'recurring') { - $is_recurring = true; - } + $view_policy = PhabricatorPolicies::getMostOpenPolicy(); $start = new DateTime('@'.PhabricatorTime::getNow()); $start->setTimeZone($actor->getTimeZone()); @@ -82,13 +69,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $epoch_min = $start->format('U'); $epoch_max = $end->format('U'); + $default_icon = 'fa-calendar'; + return id(new PhabricatorCalendarEvent()) ->setHostPHID($actor->getPHID()) ->setIsCancelled(0) ->setIsAllDay(0) ->setIsStub(0) - ->setIsRecurring($is_recurring) - ->setIcon(self::DEFAULT_ICON) + ->setIsRecurring(0) + ->setRecurrenceFrequency( + array( + 'rule' => self::FREQUENCY_WEEKLY, + )) + ->setIcon($default_icon) ->setViewPolicy($view_policy) ->setEditPolicy($actor->getPHID()) ->setSpacePHID($actor->getDefaultSpacePHID()) @@ -396,8 +389,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this; } + public function getFrequencyRule() { + return idx($this->recurrenceFrequency, 'rule'); + } + public function getFrequencyUnit() { - $frequency = idx($this->recurrenceFrequency, 'rule'); + $frequency = $this->getFrequencyRule(); switch ($frequency) { case 'daily': diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php index c97901250e..9690a97dce 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php @@ -6,7 +6,7 @@ final class PhabricatorCalendarEventFrequencyTransaction const TRANSACTIONTYPE = 'calendar.frequency'; public function generateOldValue($object) { - return $object->getFrequencyUnit(); + return $object->getFrequencyRule(); } public function applyInternalEffects($object, $value) { @@ -17,7 +17,7 @@ final class PhabricatorCalendarEventFrequencyTransaction } public function getTitle() { - $frequency = $this->getFrequencyUnit($this->getNewValue()); + $frequency = $this->getFrequencyRule($this->getNewValue()); switch ($frequency) { case PhabricatorCalendarEvent::FREQUENCY_DAILY: return pht( @@ -39,7 +39,7 @@ final class PhabricatorCalendarEventFrequencyTransaction } public function getTitleForFeed() { - $frequency = $this->getFrequencyUnit($this->getNewValue()); + $frequency = $this->getFrequencyRule($this->getNewValue()); switch ($frequency) { case PhabricatorCalendarEvent::FREQUENCY_DAILY: return pht( @@ -64,7 +64,7 @@ final class PhabricatorCalendarEventFrequencyTransaction } } - private function getFrequencyUnit($value) { + private function getFrequencyRule($value) { if (is_array($value)) { $value = idx($value, 'rule'); } else { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php new file mode 100644 index 0000000000..febdbbd1a1 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php @@ -0,0 +1,59 @@ +getHostPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setHostPHID($value); + } + + public function getTitle() { + return pht( + '%s changed the host of this event from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the host of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $host_phid = $xaction->getNewValue(); + if (!$host_phid) { + $errors[] = $this->newRequiredError( + pht('Event host is required.')); + continue; + } + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($host_phid)) + ->executeOne(); + if (!$user) { + $errors[] = $this->newInvalidError( + pht( + 'Host PHID "%s" is not a valid user PHID.', + $host_phid)); + } + } + + return $errors; + } + +} diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 66cdf5a570..a35eb02cd3 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -148,6 +148,14 @@ abstract class PhabricatorModularTransactionType return $display; } + final protected function renderOldHandle() { + return $this->renderHandle($this->getOldValue()); + } + + final protected function renderNewHandle() { + return $this->renderHandle($this->getNewValue()); + } + final protected function renderHandleList(array $phids) { $viewer = $this->getViewer(); $display = $viewer->renderHandleList($phids) From 32272920734b4f1e2f4b636bbf91cb023b85bf8b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Jul 2016 12:40:19 -0700 Subject: [PATCH 29/76] Slightly simplify some Paste modular transactions Summary: Modular transactions have slightly more modern ways to express values now. Test Plan: Looked at transaction record of a paste. Reviewers: chad, avivey Reviewed By: avivey Differential Revision: https://secure.phabricator.com/D16293 --- .../xaction/PhabricatorPasteLanguageTransaction.php | 8 ++++---- .../xaction/PhabricatorPasteTitleTransaction.php | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php b/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php index 75cf8559d1..3497288b74 100644 --- a/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php +++ b/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php @@ -17,8 +17,8 @@ final class PhabricatorPasteLanguageTransaction return pht( "%s updated the paste's language from %s to %s.", $this->renderAuthor(), - $this->renderValue($this->getOldValue()), - $this->renderValue($this->getNewValue())); + $this->renderOldValue(), + $this->renderNewValue()); } public function getTitleForFeed() { @@ -26,8 +26,8 @@ final class PhabricatorPasteLanguageTransaction '%s updated the language for %s from %s to %s.', $this->renderAuthor(), $this->renderObject(), - $this->renderValue($this->getOldValue()), - $this->renderValue($this->getNewValue())); + $this->renderOldValue(), + $this->renderNewValue()); } } diff --git a/src/applications/paste/xaction/PhabricatorPasteTitleTransaction.php b/src/applications/paste/xaction/PhabricatorPasteTitleTransaction.php index 0fc86b3d96..f9d24a0a93 100644 --- a/src/applications/paste/xaction/PhabricatorPasteTitleTransaction.php +++ b/src/applications/paste/xaction/PhabricatorPasteTitleTransaction.php @@ -15,19 +15,19 @@ final class PhabricatorPasteTitleTransaction public function getTitle() { return pht( - '%s updated the paste\'s title from "%s" to "%s".', + '%s changed the title of this paste from %s to %s.', $this->renderAuthor(), - $this->getOldValue(), - $this->getNewValue()); + $this->renderOldValue(), + $this->renderNewValue()); } public function getTitleForFeed() { return pht( - '%s updated the title for %s from "%s" to "%s".', + '%s updated the title for %s from %s to %s.', $this->renderAuthor(), $this->renderObject(), - $this->getOldValue(), - $this->getNewValue()); + $this->renderOldValue(), + $this->renderNewValue()); } } From 6eaa9faec77bba12d6da73d3ef7e880452625318 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Jul 2016 12:55:22 -0700 Subject: [PATCH 30/76] Provide default view and edit policies in Calendar, plus "Event Host" and "Event Invitees" Summary: Fixes T9224. This adds: - A "Default Edit Policy" and "Default View Policy" to Calendar, similar to other applications. - "Event Host" and "Event Invitees" objects policies. These policies often end up being redundant (the host can always view/edit, the invitees can always view), but they can be more clear than setting "No One", and "Editable By: Event Invitees" is a legitimately useful policy. Test Plan: - Created and edited events. - Fiddled with defaults. - Tried to remove myself as the event host for an "Editable By: Host" event, got an error ("you wouldn't be able to edit"). - Tried to remove myself as host/invitee for an "Editable By: Invitees" event, got an error. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9224 Differential Revision: https://secure.phabricator.com/D16294 --- src/__phutil_library_map__.php | 8 ++ .../PhabricatorCalendarApplication.php | 15 +++ ...atorCalendarEventDefaultEditCapability.php | 12 ++ ...atorCalendarEventDefaultViewCapability.php | 16 +++ .../editor/PhabricatorCalendarEventEditor.php | 23 ++++ ...PhabricatorCalendarEventHostPolicyRule.php | 43 ++++++++ ...ricatorCalendarEventInviteesPolicyRule.php | 104 ++++++++++++++++++ .../storage/PhabricatorCalendarEvent.php | 7 +- 8 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 src/applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php create mode 100644 src/applications/calendar/capability/PhabricatorCalendarEventDefaultViewCapability.php create mode 100644 src/applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php create mode 100644 src/applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index da6f91f900..fd02d46c58 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2026,6 +2026,8 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventCancelTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php', 'PhabricatorCalendarEventDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php', 'PhabricatorCalendarEventDeclineTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php', + 'PhabricatorCalendarEventDefaultEditCapability' => 'applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php', + 'PhabricatorCalendarEventDefaultViewCapability' => 'applications/calendar/capability/PhabricatorCalendarEventDefaultViewCapability.php', 'PhabricatorCalendarEventDescriptionTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php', 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 'PhabricatorCalendarEventEditConduitAPIMethod' => 'applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php', @@ -2036,11 +2038,13 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php', 'PhabricatorCalendarEventFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php', 'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php', + 'PhabricatorCalendarEventHostPolicyRule' => 'applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php', 'PhabricatorCalendarEventHostTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php', 'PhabricatorCalendarEventIconTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php', 'PhabricatorCalendarEventInviteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php', 'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php', 'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php', + 'PhabricatorCalendarEventInviteesPolicyRule' => 'applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php', 'PhabricatorCalendarEventJoinController' => 'applications/calendar/controller/PhabricatorCalendarEventJoinController.php', 'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php', 'PhabricatorCalendarEventMailReceiver' => 'applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php', @@ -6654,6 +6658,8 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventCancelTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventDateTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventDeclineTransaction' => 'PhabricatorCalendarEventReplyTransaction', + 'PhabricatorCalendarEventDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorCalendarEventDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PhabricatorCalendarEventDescriptionTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', @@ -6664,6 +6670,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction', 'PhabricatorCalendarEventFrequencyTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhabricatorCalendarEventHostPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorCalendarEventHostTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventIconTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventInviteTransaction' => 'PhabricatorCalendarEventTransactionType', @@ -6672,6 +6679,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhabricatorCalendarEventInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorCalendarEventInviteesPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorCalendarEventJoinController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventMailReceiver' => 'PhabricatorObjectMailReceiver', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 5e52cdc4e0..16e6daf3bf 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -83,4 +83,19 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { ); } + protected function getCustomCapabilities() { + return array( + PhabricatorCalendarEventDefaultViewCapability::CAPABILITY => array( + 'caption' => pht('Default view policy for newly created events.'), + 'template' => PhabricatorCalendarEventPHIDType::TYPECONST, + 'capability' => PhabricatorPolicyCapability::CAN_VIEW, + ), + PhabricatorCalendarEventDefaultEditCapability::CAPABILITY => array( + 'caption' => pht('Default edit policy for newly created events.'), + 'template' => PhabricatorCalendarEventPHIDType::TYPECONST, + 'capability' => PhabricatorPolicyCapability::CAN_EDIT, + ), + ); + } + } diff --git a/src/applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php b/src/applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php new file mode 100644 index 0000000000..ae8e4f8980 --- /dev/null +++ b/src/applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php @@ -0,0 +1,12 @@ +getTransactionType()) { + case PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE: + $copy->setHostPHID($xaction->getNewValue()); + break; + case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE: + PhabricatorPolicyRule::passTransactionHintToRule( + $copy, + new PhabricatorCalendarEventInviteesPolicyRule(), + array_fuse($xaction->getNewValue())); + break; + } + } + + return $copy; + } + + protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php b/src/applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php new file mode 100644 index 0000000000..98906b8ce2 --- /dev/null +++ b/src/applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php @@ -0,0 +1,43 @@ +getPHID(); + if (!$viewer_phid) { + return false; + } + + return ($object->getHostPHID() == $viewer_phid); + } + + public function getValueControlType() { + return self::CONTROL_TYPE_NONE; + } + +} diff --git a/src/applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php b/src/applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php new file mode 100644 index 0000000000..4e7b928901 --- /dev/null +++ b/src/applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php @@ -0,0 +1,104 @@ +getPHID(); + if (!$viewer_phid) { + return; + } + + if (empty($this->invited[$viewer_phid])) { + $this->invited[$viewer_phid] = array(); + } + + if (!isset($this->sourcePHIDs[$viewer_phid])) { + $source_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $viewer_phid, + PhabricatorProjectMemberOfProjectEdgeType::EDGECONST); + $source_phids[] = $viewer_phid; + $this->sourcePHIDs[$viewer_phid] = $source_phids; + } + + foreach ($objects as $key => $object) { + $cache = $this->getTransactionHint($object); + if ($cache === null) { + // We don't have a hint for this object, so we'll deal with it below. + continue; + } + + // We have a hint, so use that as the source of truth. + unset($objects[$key]); + + foreach ($this->sourcePHIDs[$viewer_phid] as $source_phid) { + if (isset($cache[$source_phid])) { + $this->invited[$viewer_phid][$object->getPHID()] = true; + break; + } + } + } + + $phids = mpull($objects, 'getPHID'); + if (!$phids) { + return; + } + + $invited = id(new PhabricatorCalendarEventInvitee())->loadAllWhere( + 'eventPHID IN (%Ls) + AND inviteePHID IN (%Ls) + AND status != %s', + $phids, + $this->sourcePHIDs[$viewer_phid], + PhabricatorCalendarEventInvitee::STATUS_UNINVITED); + $invited = mpull($invited, 'getEventPHID'); + + $this->invited[$viewer_phid] += array_fill_keys($invited, true); + } + + public function applyRule( + PhabricatorUser $viewer, + $value, + PhabricatorPolicyInterface $object) { + + $viewer_phid = $viewer->getPHID(); + if (!$viewer_phid) { + return false; + } + + $invited = idx($this->invited, $viewer_phid); + return isset($invited[$object->getPHID()]); + } + + public function getValueControlType() { + return self::CONTROL_TYPE_NONE; + } + +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index a0122a4b3a..8afac00247 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -57,7 +57,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->withClasses(array('PhabricatorCalendarApplication')) ->executeOne(); - $view_policy = PhabricatorPolicies::getMostOpenPolicy(); + $view_default = PhabricatorCalendarEventDefaultViewCapability::CAPABILITY; + $edit_default = PhabricatorCalendarEventDefaultEditCapability::CAPABILITY; + $view_policy = $app->getPolicy($view_default); + $edit_policy = $app->getPolicy($edit_default); $start = new DateTime('@'.PhabricatorTime::getNow()); $start->setTimeZone($actor->getTimeZone()); @@ -83,7 +86,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO )) ->setIcon($default_icon) ->setViewPolicy($view_policy) - ->setEditPolicy($actor->getPHID()) + ->setEditPolicy($edit_policy) ->setSpacePHID($actor->getDefaultSpacePHID()) ->attachInvitees(array()) ->setDateFrom($epoch_min) From 2a1b8ce85bef36e45555a98b83d79c74080317fd Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Jul 2016 21:07:34 -0700 Subject: [PATCH 31/76] For now, hard limit task graph at 100 nodes Summary: Ref T4788. One install has some particularly impressive task graphs which are thousands of nodes large. The current graph is pretty broken in these cases. For now, just render a "too big to show" message. In the future, I'd plan to finesse this (e.g., show parents/children, show links to parents/children, etc). Test Plan: - Viewed a normal task. - Set limit to 3, viewed a task with graph size 6, saw an error message. - Viewed a revision stack graph (unaffected). Reviewers: chad Reviewed By: chad Maniphest Tasks: T4788 Differential Revision: https://secure.phabricator.com/D16295 --- .../ManiphestTaskDetailController.php | 15 +++++++++++- .../graph/PhabricatorObjectGraph.php | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index f544fa6b2c..34c7f54c11 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -87,12 +87,25 @@ final class ManiphestTaskDetailController extends ManiphestController { ->addPropertySection(pht('Description'), $description) ->addPropertySection(pht('Details'), $details); + $graph_limit = 100; $task_graph = id(new ManiphestTaskGraph()) ->setViewer($viewer) ->setSeedPHID($task->getPHID()) + ->setLimit($graph_limit) ->loadGraph(); if (!$task_graph->isEmpty()) { - $graph_table = $task_graph->newGraphTable(); + if ($task_graph->isOverLimit()) { + $message = pht( + 'Task graph too large to display (this task is connected to '. + 'more than %s other tasks).', + $graph_limit); + $message = phutil_tag('em', array(), $message); + $graph_table = id(new PHUIPropertyListView()) + ->addTextContent($message); + } else { + $graph_table = $task_graph->newGraphTable(); + } + $view->addPropertySection(pht('Task Graph'), $graph_table); } diff --git a/src/infrastructure/graph/PhabricatorObjectGraph.php b/src/infrastructure/graph/PhabricatorObjectGraph.php index 7a3790dea4..5e25cd8e33 100644 --- a/src/infrastructure/graph/PhabricatorObjectGraph.php +++ b/src/infrastructure/graph/PhabricatorObjectGraph.php @@ -9,6 +9,7 @@ abstract class PhabricatorObjectGraph private $seedPHID; private $objects; private $loadEntireGraph = false; + private $limit; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -23,6 +24,15 @@ abstract class PhabricatorObjectGraph return $this->viewer; } + public function setLimit($limit) { + $this->limit = $limit; + return $this; + } + + public function getLimit() { + return $this->limit; + } + abstract protected function getEdgeTypes(); abstract protected function getParentEdgeType(); abstract protected function newQuery(); @@ -44,6 +54,16 @@ abstract class PhabricatorObjectGraph return (count($this->getNodes()) <= 2); } + final public function isOverLimit() { + $limit = $this->getLimit(); + + if (!$limit) { + return false; + } + + return (count($this->edgeReach) > $limit); + } + final public function getEdges($type) { $edges = idx($this->edges, $type, array()); @@ -72,6 +92,10 @@ abstract class PhabricatorObjectGraph } final protected function loadEdges(array $nodes) { + if ($this->isOverLimit()) { + return array_fill_keys($nodes, array()); + } + $edge_types = $this->getEdgeTypes(); $query = id(new PhabricatorEdgeQuery()) From 7c9a74ce04d862f4acdfa863ab1bd0623e5fb7ee Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 04:44:13 -0700 Subject: [PATCH 32/76] Make ghost event instances render better on the event list Summary: Ref T11326. Currently, we render "E (99)" for ghost instances, which is meaningless and inconsistent. Render these more sensibly and consistently. Test Plan: Viewed event list, saw reasonable monograms / object names. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16296 --- .../query/PhabricatorCalendarEventSearchEngine.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index f3489c5cee..b88b99dd39 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -275,19 +275,18 @@ final class PhabricatorCalendarEventSearchEngine } if ($event->getIsGhostEvent()) { - $title_text = $event->getMonogram() - .' (' - .$event->getSequenceIndex() - .'): ' - .$event->getName(); + $monogram = $event->getParentEvent()->getMonogram(); + $index = $event->getSequenceIndex(); + $monogram = "{$monogram}/{$index}"; } else { - $title_text = $event->getMonogram().': '.$event->getName(); + $monogram = $event->getMonogram(); } $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($event) - ->setHeader($title_text) + ->setObjectName($monogram) + ->setHeader($event->getName()) ->setHref($event->getURI()) ->addAttribute($event_date_info); From 04a69fa31343453caaad17c350b56ab86bac9515 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 04:50:38 -0700 Subject: [PATCH 33/76] Show event icons in header instead of property list Summary: Ref T11326. Currently, events show the icon as a property, like this: > Icon: Default This is boring and terrible. Show the icon in the header instead: {F1723530} Also minor cleanup on active/cancel states. Test Plan: Viewed an event, saw icon. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16297 --- ...PhabricatorCalendarEventViewController.php | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index b964aacfbf..390736dc35 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -68,10 +68,15 @@ final class PhabricatorCalendarEventViewController $viewer = $this->getViewer(); $id = $event->getID(); - $is_cancelled = $event->getIsCancelled(); - $icon = $is_cancelled ? ('fa-ban') : ('fa-check'); - $color = $is_cancelled ? ('red') : ('bluegrey'); - $status = $is_cancelled ? pht('Cancelled') : pht('Active'); + if ($event->isCancelledEvent()) { + $icon = 'fa-ban'; + $color = 'red'; + $status = pht('Cancelled'); + } else { + $icon = 'fa-check'; + $color = 'bluegrey'; + $status = pht('Active'); + } $invite_status = $event->getUserInviteStatus($viewer->getPHID()); $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; @@ -82,7 +87,7 @@ final class PhabricatorCalendarEventViewController ->setHeader($event->getName()) ->setStatus($icon, $color, $status) ->setPolicyObject($event) - ->setHeaderIcon('fa-calendar'); + ->setHeaderIcon($event->getIcon()); if ($is_invite_pending) { $decline_button = id(new PHUIButtonView()) @@ -108,7 +113,6 @@ final class PhabricatorCalendarEventViewController private function buildCurtain(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); $id = $event->getID(); - $is_cancelled = $event->isCancelledEvent(); $is_attending = $event->getIsUserAttending($viewer->getPHID()); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -169,7 +173,7 @@ final class PhabricatorCalendarEventViewController $reinstate_label = pht('Reinstate Event'); } - if ($is_cancelled) { + if ($event->isCancelledEvent()) { $curtain->addAction( id(new PhabricatorActionView()) ->setName($reinstate_label) @@ -302,11 +306,6 @@ final class PhabricatorCalendarEventViewController $properties->invokeWillRenderEvent(); - $properties->addProperty( - pht('Icon'), - id(new PhabricatorCalendarIconSet()) - ->getIconLabel($event->getIcon())); - return $properties; } From 2ce37034ad62dbc0ed4c11289e7a6befa62d82d4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 05:07:06 -0700 Subject: [PATCH 34/76] Move event host and times into event subheader Summary: Ref T11326. Show this information with a subheader instead of in properties. Also, slightly simplify the list view. Test Plan: {F1723539} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16298 --- ...PhabricatorCalendarEventViewController.php | 57 +++++++++--------- .../PhabricatorCalendarEventSearchEngine.php | 46 +------------- .../storage/PhabricatorCalendarEvent.php | 60 ++++++++++++++----- 3 files changed, 73 insertions(+), 90 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 390736dc35..16e057ebcc 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -34,6 +34,7 @@ final class PhabricatorCalendarEventViewController new PhabricatorCalendarEventTransactionQuery()); $header = $this->buildHeaderView($event); + $subheader = $this->buildSubheaderView($event); $curtain = $this->buildCurtain($event); $details = $this->buildPropertySection($event); $description = $this->buildDescriptionView($event); @@ -47,6 +48,7 @@ final class PhabricatorCalendarEventViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setSubheader($subheader) ->setMainColumn( array( $timeline, @@ -201,32 +203,6 @@ final class PhabricatorCalendarEventViewController $properties = id(new PHUIPropertyListView()) ->setUser($viewer); - if ($event->getIsAllDay()) { - $date_start = phabricator_date($event->getViewerDateFrom(), $viewer); - $date_end = phabricator_date($event->getViewerDateTo(), $viewer); - - if ($date_start == $date_end) { - $properties->addProperty( - pht('Time'), - phabricator_date($event->getViewerDateFrom(), $viewer)); - } else { - $properties->addProperty( - pht('Starts'), - phabricator_date($event->getViewerDateFrom(), $viewer)); - $properties->addProperty( - pht('Ends'), - phabricator_date($event->getViewerDateTo(), $viewer)); - } - } else { - $properties->addProperty( - pht('Starts'), - phabricator_datetime($event->getViewerDateFrom(), $viewer)); - - $properties->addProperty( - pht('Ends'), - phabricator_datetime($event->getViewerDateTo(), $viewer)); - } - if ($event->getIsRecurring()) { $properties->addProperty( pht('Recurs'), @@ -247,10 +223,6 @@ final class PhabricatorCalendarEventViewController } } - $properties->addProperty( - pht('Host'), - $viewer->renderHandle($event->getHostPHID())); - $invitees = $event->getInvitees(); foreach ($invitees as $key => $invitee) { if ($invitee->isUninvited()) { @@ -389,4 +361,29 @@ final class PhabricatorCalendarEventViewController return $instance; } + private function buildSubheaderView(PhabricatorCalendarEvent $event) { + $viewer = $this->getViewer(); + + $host_phid = $event->getHostPHID(); + + $handles = $viewer->loadHandles(array($host_phid)); + $handle = $handles[$host_phid]; + + $host = $viewer->renderHandle($host_phid); + $host = phutil_tag('strong', array(), $host); + + $image_uri = $handles[$host_phid]->getImageURI(); + $image_href = $handles[$host_phid]->getURI(); + + $date = $event->renderEventDate($viewer, true); + + $content = pht('Hosted by %s on %s.', $host, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + + } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index b88b99dd39..d0c4559902 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -264,7 +264,6 @@ final class PhabricatorCalendarEventSearchEngine $list = new PHUIObjectItemListView(); foreach ($events as $event) { - $event_date_info = $this->getEventDateLabel($event); $attendees = array(); foreach ($event->getInvitees() as $invitee) { @@ -288,7 +287,7 @@ final class PhabricatorCalendarEventSearchEngine ->setObjectName($monogram) ->setHeader($event->getName()) ->setHref($event->getURI()) - ->addAttribute($event_date_info); + ->addAttribute($event->renderEventDate($viewer, false)); if ($attendees) { $attending = pht( @@ -300,13 +299,6 @@ final class PhabricatorCalendarEventSearchEngine $item->addAttribute($attending); } - if ($event->getDuration()) { - $duration = pht( - 'Duration: %s', - $event->getDisplayDuration()); - $item->addIcon('none', $duration); - } - $list->addItem($item); } @@ -542,40 +534,4 @@ final class PhabricatorCalendarEventSearchEngine return false; } - private function getEventDateLabel($event) { - $viewer = $this->requireViewer(); - - $from_datetime = PhabricatorTime::getDateTimeFromEpoch( - $event->getViewerDateFrom(), - $viewer); - $to_datetime = PhabricatorTime::getDateTimeFromEpoch( - $event->getViewerDateTo(), - $viewer); - - $from_date_formatted = $from_datetime->format('Y m d'); - $to_date_formatted = $to_datetime->format('Y m d'); - - if ($event->getIsAllDay()) { - if ($from_date_formatted == $to_date_formatted) { - return pht( - '%s, All Day', - phabricator_date($event->getViewerDateFrom(), $viewer)); - } else { - return pht( - '%s - %s, All Day', - phabricator_date($event->getViewerDateFrom(), $viewer), - phabricator_date($event->getViewerDateTo(), $viewer)); - } - } else if ($from_date_formatted == $to_date_formatted) { - return pht( - '%s - %s', - phabricator_datetime($event->getViewerDateFrom(), $viewer), - phabricator_time($event->getViewerDateTo(), $viewer)); - } else { - return pht( - '%s - %s', - phabricator_datetime($event->getViewerDateFrom(), $viewer), - phabricator_datetime($event->getViewerDateTo(), $viewer)); - } - } } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 8afac00247..53261ed49b 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -454,30 +454,60 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return false; } - public function getDisplayDuration() { - $seconds = $this->getDuration(); - $minutes = round($seconds / 60, 1); - $hours = round($minutes / 60, 3); - $days = round($hours / 24, 2); + public function renderEventDate( + PhabricatorUser $viewer, + $show_end) { - $duration = ''; + if ($show_end) { + $min_date = PhabricatorTime::getDateTimeFromEpoch( + $this->getViewerDateFrom(), + $viewer); - if ($days >= 1) { + $max_date = PhabricatorTime::getDateTimeFromEpoch( + $this->getViewerDateTo(), + $viewer); + + $min_day = $min_date->format('Y m d'); + $max_day = $max_date->format('Y m d'); + + $show_end_date = ($min_day != $max_day); + } else { + $show_end_date = false; + } + + $min_epoch = $this->getViewerDateFrom(); + $max_epoch = $this->getViewerDateTo(); + + if ($this->getIsAllDay()) { + if ($show_end_date) { + return pht( + '%s - %s, All Day', + phabricator_date($min_epoch, $viewer), + phabricator_date($max_epoch, $viewer)); + } else { + return pht( + '%s, All Day', + phabricator_date($min_epoch, $viewer)); + } + } else if ($show_end_date) { return pht( - '%s day(s)', - round($days, 1)); - } else if ($hours >= 1) { + '%s - %s', + phabricator_datetime($min_epoch, $viewer), + phabricator_datetime($max_epoch, $viewer)); + } else if ($show_end) { return pht( - '%s hour(s)', - round($hours, 1)); - } else if ($minutes >= 1) { + '%s - %s', + phabricator_datetime($min_epoch, $viewer), + phabricator_time($max_epoch, $viewer)); + } else { return pht( - '%s minute(s)', - round($minutes, 0)); + '%s', + phabricator_datetime($min_epoch, $viewer)); } } + /* -( Markup Interface )--------------------------------------------------- */ From 3085e52843420a17cc7783840e1c7230d17b79a1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 05:26:44 -0700 Subject: [PATCH 35/76] Make upcoming events view more viewer-oriented Summary: Ref T11326. Try to make this a little more useful: - Don't show entire attendee list (not useful?) - Show host (useful?) - Show your own status prominently (attending vs declined vs invited). - Show cancelled events prominently. Test Plan: {F1723550} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16299 --- .../PhabricatorCalendarEventSearchEngine.php | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index d0c4559902..44e19ec3e7 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -264,15 +264,6 @@ final class PhabricatorCalendarEventSearchEngine $list = new PHUIObjectItemListView(); foreach ($events as $event) { - $attendees = array(); - - foreach ($event->getInvitees() as $invitee) { - $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; - if ($invitee->getStatus() === $status_attending) { - $attendees[] = $invitee->getInviteePHID(); - } - } - if ($event->getIsGhostEvent()) { $monogram = $event->getParentEvent()->getMonogram(); $index = $event->getSequenceIndex(); @@ -286,19 +277,43 @@ final class PhabricatorCalendarEventSearchEngine ->setObject($event) ->setObjectName($monogram) ->setHeader($event->getName()) - ->setHref($event->getURI()) - ->addAttribute($event->renderEventDate($viewer, false)); + ->setHref($event->getURI()); - if ($attendees) { - $attending = pht( - 'Attending: %s', - $viewer->renderHandleList($attendees) - ->setAsInline(1) - ->render()); + $item->addAttribute($event->renderEventDate($viewer, false)); - $item->addAttribute($attending); + if ($event->isCancelledEvent()) { + $status_icon = 'fa-times red'; + $status_label = pht('Cancelled'); + $item->setDisabled(true); + } else if ($viewer->isLoggedIn()) { + $status = $event->getUserInviteStatus($viewer->getPHID()); + switch ($status) { + case PhabricatorCalendarEventInvitee::STATUS_ATTENDING: + $status_icon = 'fa-check-circle green'; + $status_label = pht('Attending'); + break; + case PhabricatorCalendarEventInvitee::STATUS_INVITED: + $status_icon = 'fa-user-plus green'; + $status_label = pht('Invited'); + break; + case PhabricatorCalendarEventInvitee::STATUS_DECLINED: + $status_icon = 'fa-times grey'; + $status_label = pht('Declined'); + break; + default: + $status_icon = $event->getIcon().' grey'; + $status_label = null; + break; + } } + $item->setStatusIcon($status_icon, $status_label); + + $host = pht( + 'Hosted by %s', + $viewer->renderHandle($event->getHostPHID())); + $item->addByline($host); + $list->addItem($item); } From 893edf9d9558526cbcd1dadb005b3c8dc4ff38c0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 09:56:42 -0700 Subject: [PATCH 36/76] Make Calendar Event handles slightly more modern Summary: Ref T11326. Use modern methods instead of building this stuff separately. Test Plan: Used `E123`, `{E123}`, saw references render normally. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16300 --- .../calendar/phid/PhabricatorCalendarEventPHIDType.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/applications/calendar/phid/PhabricatorCalendarEventPHIDType.php b/src/applications/calendar/phid/PhabricatorCalendarEventPHIDType.php index d37be2ef32..90beff28f9 100644 --- a/src/applications/calendar/phid/PhabricatorCalendarEventPHIDType.php +++ b/src/applications/calendar/phid/PhabricatorCalendarEventPHIDType.php @@ -32,16 +32,16 @@ final class PhabricatorCalendarEventPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $event = $objects[$phid]; - $id = $event->getID(); + $monogram = $event->getMonogram(); $name = $event->getName(); - $is_cancelled = $event->getIsCancelled(); + $uri = $event->getURI(); $handle ->setName($name) - ->setFullName(pht('E%d: %s', $id, $name)) - ->setURI('/E'.$id); + ->setFullName(pht('%s: %s', $monogram, $name)) + ->setURI($uri); - if ($is_cancelled) { + if ($event->isCancelledEvent()) { $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); } } From 1c33b70c663ab78600e66b93e605de447a8c4bc5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 10:08:15 -0700 Subject: [PATCH 37/76] Remove two unused SearchEngine methods Summary: Ref T11326. These are last-generation and neither of these have callsites anymore. (I nuked these since I'm trying to simplify date handling.) Test Plan: Grepped for callsites. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16301 --- .../PhabricatorApplicationSearchEngine.php | 57 ------------------- 1 file changed, 57 deletions(-) diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index d7268facb3..3d6b4d9cff 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -712,50 +712,6 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } - /** - * Read a list of project PHIDs from a request in a flexible way. - * - * @param AphrontRequest Request to read user PHIDs from. - * @param string Key to read in the request. - * @return list List of projet PHIDs and selector functions. - * @task read - */ - protected function readProjectsFromRequest(AphrontRequest $request, $key) { - $list = $this->readListFromRequest($request, $key); - - $phids = array(); - $slugs = array(); - $project_type = PhabricatorProjectProjectPHIDType::TYPECONST; - foreach ($list as $item) { - $type = phid_get_type($item); - if ($type == $project_type) { - $phids[] = $item; - } else { - if (PhabricatorTypeaheadDatasource::isFunctionToken($item)) { - // If this is a function, pass it through unchanged; we'll evaluate - // it later. - $phids[] = $item; - } else { - $slugs[] = $item; - } - } - } - - if ($slugs) { - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($this->requireViewer()) - ->withSlugs($slugs) - ->execute(); - foreach ($projects as $project) { - $phids[] = $project->getPHID(); - } - $phids = array_unique($phids); - } - - return $phids; - } - - /** * Read a list of subscribers from a request in a flexible way. * @@ -849,19 +805,6 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $list; } - protected function readDateFromRequest( - AphrontRequest $request, - $key) { - - $value = AphrontFormDateControlValue::newFromRequest($request, $key); - - if ($value->isEmpty()) { - return null; - } - - return $value->getDictionary(); - } - protected function readBoolFromRequest( AphrontRequest $request, $key) { From 08ac49e15a4ab53676cf6de52f9a3471f036505a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 10:21:10 -0700 Subject: [PATCH 38/76] Remove old paged forms Summary: Ref T11326. This gets rid of the old multi-paged form stuff used in the last version of Diffusion. This incidentally removes a callsite for a date control to make it a little easier to simplify them. Test Plan: Grepped for all removed classes, no more callsites. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16302 --- src/__phutil_library_map__.php | 8 - .../PhabricatorPagedFormUIExample.php | 71 ----- src/view/form/PHUIFormPageView.php | 221 -------------- src/view/form/PHUIPagedFormView.php | 279 ------------------ src/view/form/control/AphrontFormControl.php | 16 - .../control/PHUIFormMultiSubmitControl.php | 61 ---- 6 files changed, 656 deletions(-) delete mode 100644 src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php delete mode 100644 src/view/form/PHUIFormPageView.php delete mode 100644 src/view/form/PHUIPagedFormView.php delete mode 100644 src/view/form/control/PHUIFormMultiSubmitControl.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fd02d46c58..3700788483 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1626,8 +1626,6 @@ phutil_register_library_map(array( 'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php', 'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php', 'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php', - 'PHUIFormMultiSubmitControl' => 'view/form/control/PHUIFormMultiSubmitControl.php', - 'PHUIFormPageView' => 'view/form/PHUIFormPageView.php', 'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php', 'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php', 'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php', @@ -1653,7 +1651,6 @@ phutil_register_library_map(array( 'PHUIObjectItemListExample' => 'applications/uiexample/examples/PHUIObjectItemListExample.php', 'PHUIObjectItemListView' => 'view/phui/PHUIObjectItemListView.php', 'PHUIObjectItemView' => 'view/phui/PHUIObjectItemView.php', - 'PHUIPagedFormView' => 'view/form/PHUIPagedFormView.php', 'PHUIPagerView' => 'view/phui/PHUIPagerView.php', 'PHUIPinboardItemView' => 'view/phui/PHUIPinboardItemView.php', 'PHUIPinboardView' => 'view/phui/PHUIPinboardView.php', @@ -2974,7 +2971,6 @@ phutil_register_library_map(array( 'PhabricatorPHPASTApplication' => 'applications/phpast/application/PhabricatorPHPASTApplication.php', 'PhabricatorPHPConfigSetupCheck' => 'applications/config/check/PhabricatorPHPConfigSetupCheck.php', 'PhabricatorPHPMailerConfigOptions' => 'applications/config/option/PhabricatorPHPMailerConfigOptions.php', - 'PhabricatorPagedFormUIExample' => 'applications/uiexample/examples/PhabricatorPagedFormUIExample.php', 'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php', 'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php', 'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php', @@ -6189,8 +6185,6 @@ phutil_register_library_map(array( 'PHUIFormIconSetControl' => 'AphrontFormControl', 'PHUIFormInsetView' => 'AphrontView', 'PHUIFormLayoutView' => 'AphrontView', - 'PHUIFormMultiSubmitControl' => 'AphrontFormControl', - 'PHUIFormPageView' => 'AphrontView', 'PHUIHandleListView' => 'AphrontTagView', 'PHUIHandleTagListView' => 'AphrontTagView', 'PHUIHandleView' => 'AphrontView', @@ -6216,7 +6210,6 @@ phutil_register_library_map(array( 'PHUIObjectItemListExample' => 'PhabricatorUIExample', 'PHUIObjectItemListView' => 'AphrontTagView', 'PHUIObjectItemView' => 'AphrontTagView', - 'PHUIPagedFormView' => 'AphrontView', 'PHUIPagerView' => 'AphrontView', 'PHUIPinboardItemView' => 'AphrontView', 'PHUIPinboardView' => 'AphrontView', @@ -7730,7 +7723,6 @@ phutil_register_library_map(array( 'PhabricatorPHPASTApplication' => 'PhabricatorApplication', 'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorPHPMailerConfigOptions' => 'PhabricatorApplicationConfigOptions', - 'PhabricatorPagedFormUIExample' => 'PhabricatorUIExample', 'PhabricatorPagerUIExample' => 'PhabricatorUIExample', 'PhabricatorPassphraseApplication' => 'PhabricatorApplication', 'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider', diff --git a/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php b/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php deleted file mode 100644 index 3f42be9ce1..0000000000 --- a/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php +++ /dev/null @@ -1,71 +0,0 @@ -PHUIPagedFormView')); - } - - public function renderExample() { - $request = $this->getRequest(); - $user = $request->getUser(); - - - $page1 = id(new PHUIFormPageView()) - ->setPageName(pht('Page 1')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('page1') - ->setLabel(pht('Page 1'))); - - $page2 = id(new PHUIFormPageView()) - ->setPageName(pht('Page 2')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('page2') - ->setLabel(pht('Page 2'))); - - $page3 = id(new PHUIFormPageView()) - ->setPageName(pht('Page 3')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('page3') - ->setLabel(pht('Page 3'))); - - $page4 = id(new PHUIFormPageView()) - ->setPageName(pht('Page 4')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('page4') - ->setLabel(pht('Page 4'))); - - $form = new PHUIPagedFormView(); - $form->setUser($user); - - $form->addPage('page1', $page1); - $form->addPage('page2', $page2); - $form->addPage('page3', $page3); - $form->addPage('page4', $page4); - - if ($request->isFormPost()) { - $form->readFromRequest($request); - if ($form->isComplete()) { - return id(new AphrontDialogView()) - ->setUser($user) - ->setTitle(pht('Form Complete')) - ->appendChild(pht('You submitted the form. Well done!')) - ->addCancelButton($request->getRequestURI(), pht('Again!')); - } - } else { - $form->readFromObject(null); - } - - return $form; - } -} diff --git a/src/view/form/PHUIFormPageView.php b/src/view/form/PHUIFormPageView.php deleted file mode 100644 index aeff9e38f5..0000000000 --- a/src/view/form/PHUIFormPageView.php +++ /dev/null @@ -1,221 +0,0 @@ -pageName = $page_name; - return $this; - } - - public function getPageName() { - return $this->pageName; - } - - public function addPageError($page_error) { - $this->pageErrors[] = $page_error; - return $this; - } - - public function getPageErrors() { - return $this->pageErrors; - } - - public function setAdjustFormPageCallback($adjust_form_page_callback) { - $this->adjustFormPageCallback = $adjust_form_page_callback; - return $this; - } - - public function setValidateFormPageCallback($validate_form_page_callback) { - $this->validateFormPageCallback = $validate_form_page_callback; - return $this; - } - - public function addInstructions($text, $before = null) { - $tag = phutil_tag( - 'div', - array( - 'class' => 'aphront-form-instructions', - ), - $text); - - $append = true; - if ($before !== null) { - for ($ii = 0; $ii < count($this->content); $ii++) { - if ($this->content[$ii] instanceof AphrontFormControl) { - if ($this->content[$ii]->getName() == $before) { - array_splice($this->content, $ii, 0, array($tag)); - $append = false; - break; - } - } - } - } - - if ($append) { - $this->content[] = $tag; - } - - return $this; - } - - public function addRemarkupInstructions($remarkup, $before = null) { - $remarkup = new PHUIRemarkupView($this->getUser(), $remarkup); - return $this->addInstructions($remarkup, $before); - } - - public function addControl(AphrontFormControl $control) { - $name = $control->getName(); - - if (!strlen($name)) { - throw new Exception(pht('Form control has no name!')); - } - - if (isset($this->controls[$name])) { - throw new Exception( - pht("Form page contains duplicate control with name '%s'!", $name)); - } - - $this->controls[$name] = $control; - $this->content[] = $control; - $control->setFormPage($this); - - return $this; - } - - public function getControls() { - return $this->controls; - } - - public function getControl($name) { - if (empty($this->controls[$name])) { - throw new Exception(pht("No page control '%s'!", $name)); - } - return $this->controls[$name]; - } - - protected function canAppendChild() { - return false; - } - - public function setPagedFormView(PHUIPagedFormView $view, $key) { - if ($this->key) { - throw new Exception(pht('This page is already part of a form!')); - } - $this->form = $view; - $this->key = $key; - return $this; - } - - public function adjustFormPage() { - if ($this->adjustFormPageCallback) { - call_user_func($this->adjustFormPageCallback, $this); - } - return $this; - } - - protected function validateFormPage() { - if ($this->validateFormPageCallback) { - return call_user_func($this->validateFormPageCallback, $this); - } - return true; - } - - public function getKey() { - return $this->key; - } - - public function render() { - return $this->content; - } - - public function getForm() { - return $this->form; - } - - public function getRequestKey($key) { - return $this->getForm()->getRequestKey('p:'.$this->key.':'.$key); - } - - public function validateObjectType($object) { - return true; - } - - public function validateResponseType($response) { - return true; - } - - protected function validateControls() { - $result = true; - foreach ($this->getControls() as $name => $control) { - if (!$control->isValid()) { - $result = false; - break; - } - } - - return $result; - } - - public function isValid() { - if ($this->isValid === null) { - $this->isValid = $this->validateControls() && $this->validateFormPage(); - } - return $this->isValid; - } - - public function readFromRequest(AphrontRequest $request) { - foreach ($this->getControls() as $name => $control) { - $control->readValueFromRequest($request); - } - - return $this; - } - - public function readFromObject($object) { - foreach ($this->getControls() as $name => $control) { - if (is_array($object)) { - $control->readValueFromDictionary($object); - } - } - - return $this; - } - - public function writeToResponse($response) { - return $this; - } - - public function readSerializedValues(AphrontRequest $request) { - foreach ($this->getControls() as $name => $control) { - $key = $this->getRequestKey($name); - $control->readSerializedValue($request->getStr($key)); - } - - return $this; - } - - public function getSerializedValues() { - $dict = array(); - foreach ($this->getControls() as $name => $control) { - $key = $this->getRequestKey($name); - $dict[$key] = $control->getSerializedValue(); - } - return $dict; - } - -} diff --git a/src/view/form/PHUIPagedFormView.php b/src/view/form/PHUIPagedFormView.php deleted file mode 100644 index cfd18add58..0000000000 --- a/src/view/form/PHUIPagedFormView.php +++ /dev/null @@ -1,279 +0,0 @@ -pages[$key])) { - throw new Exception(pht("Duplicate page with key '%s'!", $key)); - } - $this->pages[$key] = $page; - $page->setPagedFormView($this, $key); - - $this->selectedPage = null; - $this->complete = null; - - return $this; - } - - - /** - * @task page - */ - public function getPage($key) { - if (!$this->pageExists($key)) { - throw new Exception(pht("No page '%s' exists!", $key)); - } - return $this->pages[$key]; - } - - - /** - * @task page - */ - public function pageExists($key) { - return isset($this->pages[$key]); - } - - - /** - * @task page - */ - protected function getPageIndex($key) { - $page = $this->getPage($key); - - $index = 0; - foreach ($this->pages as $target_page) { - if ($page === $target_page) { - break; - } - $index++; - } - - return $index; - } - - - /** - * @task page - */ - protected function getPageByIndex($index) { - foreach ($this->pages as $page) { - if (!$index) { - return $page; - } - $index--; - } - - throw new Exception(pht("Requesting out-of-bounds page '%s'.", $index)); - } - - protected function getLastIndex() { - return count($this->pages) - 1; - } - - protected function isFirstPage(PHUIFormPageView $page) { - return ($this->getPageIndex($page->getKey()) === 0); - - } - - protected function isLastPage(PHUIFormPageView $page) { - return ($this->getPageIndex($page->getKey()) === (count($this->pages) - 1)); - } - - public function getSelectedPage() { - return $this->selectedPage; - } - - public function readFromObject($object) { - return $this->processForm($is_request = false, $object); - } - - public function writeToResponse($response) { - foreach ($this->pages as $page) { - $page->validateResponseType($response); - $response = $page->writeToResponse($page); - } - - return $response; - } - - public function readFromRequest(AphrontRequest $request) { - $this->choosePage = $request->getStr($this->getRequestKey('page')); - $this->nextPage = $request->getStr('__submit__'); - $this->prevPage = $request->getStr('__back__'); - - return $this->processForm($is_request = true, $request); - } - - public function setName($name) { - $this->name = $name; - return $this; - } - - - public function getValue($page, $key, $default = null) { - return $this->getPage($page)->getValue($key, $default); - } - - public function setValue($page, $key, $value) { - $this->getPage($page)->setValue($key, $value); - return $this; - } - - private function processForm($is_request, $source) { - if ($this->pageExists($this->choosePage)) { - $selected = $this->getPage($this->choosePage); - } else { - $selected = $this->getPageByIndex(0); - } - - $is_attempt_complete = false; - if ($this->prevPage) { - $prev_index = $this->getPageIndex($selected->getKey()) - 1; - $index = max(0, $prev_index); - $selected = $this->getPageByIndex($index); - } else if ($this->nextPage) { - $next_index = $this->getPageIndex($selected->getKey()) + 1; - if ($next_index > $this->getLastIndex()) { - $is_attempt_complete = true; - } - $index = min($this->getLastIndex(), $next_index); - $selected = $this->getPageByIndex($index); - } - - $validation_error = false; - $found_selected = false; - foreach ($this->pages as $key => $page) { - if ($is_request) { - if ($key === $this->choosePage) { - $page->readFromRequest($source); - } else { - $page->readSerializedValues($source); - } - } else { - $page->readFromObject($source); - } - - if (!$found_selected) { - $page->adjustFormPage(); - } - - if ($page === $selected) { - $found_selected = true; - } - - if (!$found_selected || $is_attempt_complete) { - if (!$page->isValid()) { - $selected = $page; - $validation_error = true; - break; - } - } - } - - if ($is_attempt_complete && !$validation_error) { - $this->complete = true; - } else { - $this->selectedPage = $selected; - } - - return $this; - } - - public function isComplete() { - return $this->complete; - } - - public function getRequestKey($key) { - return $this->name.':'.$key; - } - - public function setCancelURI($cancel_uri) { - $this->cancelURI = $cancel_uri; - return $this; - } - - public function getCancelURI() { - return $this->cancelURI; - } - - public function render() { - $form = id(new AphrontFormView()) - ->setUser($this->getUser()); - - $selected_page = $this->getSelectedPage(); - if (!$selected_page) { - throw new Exception(pht('No selected page!')); - } - - $form->addHiddenInput( - $this->getRequestKey('page'), - $selected_page->getKey()); - - $errors = array(); - - foreach ($this->pages as $page) { - if ($page == $selected_page) { - $errors = $page->getPageErrors(); - continue; - } - foreach ($page->getSerializedValues() as $key => $value) { - $form->addHiddenInput($key, $value); - } - } - - $submit = id(new PHUIFormMultiSubmitControl()); - - if (!$this->isFirstPage($selected_page)) { - $submit->addBackButton(); - } else if ($this->getCancelURI()) { - $submit->addCancelButton($this->getCancelURI()); - } - - if ($this->isLastPage($selected_page)) { - $submit->addSubmitButton(pht('Save')); - } else { - $submit->addSubmitButton(pht('Continue')." \xC2\xBB"); - } - - $form->appendChild($selected_page); - $form->appendChild($submit); - - $box = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setFormErrors($errors) - ->setForm($form); - - if ($selected_page->getPageName()) { - $header = id(new PHUIHeaderView()) - ->setHeader($selected_page->getPageName()); - $box->setHeader($header); - } - - return $box; - } - -} diff --git a/src/view/form/control/AphrontFormControl.php b/src/view/form/control/AphrontFormControl.php index 16e36be663..0125fa87b3 100644 --- a/src/view/form/control/AphrontFormControl.php +++ b/src/view/form/control/AphrontFormControl.php @@ -11,7 +11,6 @@ abstract class AphrontFormControl extends AphrontView { private $id; private $controlID; private $controlStyle; - private $formPage; private $required; private $hidden; private $classes; @@ -132,21 +131,6 @@ abstract class AphrontFormControl extends AphrontView { return $this; } - public function setFormPage(PHUIFormPageView $page) { - if ($this->formPage) { - throw new Exception(pht('This control is already a member of a page!')); - } - $this->formPage = $page; - return $this; - } - - public function getFormPage() { - if ($this->formPage === null) { - throw new Exception(pht('This control does not have a page!')); - } - return $this->formPage; - } - public function setDisabled($disabled) { $this->disabled = $disabled; return $this; diff --git a/src/view/form/control/PHUIFormMultiSubmitControl.php b/src/view/form/control/PHUIFormMultiSubmitControl.php deleted file mode 100644 index 4c7454bd6d..0000000000 --- a/src/view/form/control/PHUIFormMultiSubmitControl.php +++ /dev/null @@ -1,61 +0,0 @@ -addButton('__back__', $label, 'grey'); - } - - public function addSubmitButton($label) { - return $this->addButton('__submit__', $label); - } - - public function addCancelButton($uri, $label = null) { - if ($label === null) { - $label = pht('Cancel'); - } - - $this->buttons[] = phutil_tag( - 'a', - array( - 'class' => 'grey button', - 'href' => $uri, - ), - $label); - - return $this; - } - - public function addButtonView(PHUIButtonView $button) { - $this->buttons[] = $button; - return $this; - } - - public function addButton($name, $label, $class = null) { - $this->buttons[] = javelin_tag( - 'input', - array( - 'type' => 'submit', - 'name' => $name, - 'value' => $label, - 'class' => $class, - 'sigil' => 'alternate-submit-button', - 'disabled' => $this->getDisabled() ? 'disabled' : null, - )); - return $this; - } - - protected function getCustomControlClass() { - return 'phui-form-control-multi-submit'; - } - - protected function renderInput() { - return array_reverse($this->buttons); - } - -} From cf57f6385ba11f4650eb46e50400802da97c495b Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 12:54:36 -0700 Subject: [PATCH 39/76] Fix some Calendar Event userPHID/hostPHID/"Creator" confusion in searching Summary: Ref T11326. Align this stuff with "Host" and "hostPHID". Test Plan: Searched for events by host. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16303 --- .../calendar/query/PhabricatorCalendarEventQuery.php | 12 ++++++------ .../query/PhabricatorCalendarEventSearchEngine.php | 9 +++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index e735e8220f..d0006ba3fe 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -8,7 +8,7 @@ final class PhabricatorCalendarEventQuery private $rangeBegin; private $rangeEnd; private $inviteePHIDs; - private $creatorPHIDs; + private $hostPHIDs; private $isCancelled; private $eventsWithNoParent; private $instanceSequencePairs; @@ -46,8 +46,8 @@ final class PhabricatorCalendarEventQuery return $this; } - public function withCreatorPHIDs(array $phids) { - $this->creatorPHIDs = $phids; + public function withHostPHIDs(array $phids) { + $this->hostPHIDs = $phids; return $this; } @@ -370,11 +370,11 @@ final class PhabricatorCalendarEventQuery $this->inviteePHIDs); } - if ($this->creatorPHIDs) { + if ($this->hostPHIDs) { $where[] = qsprintf( $conn, - 'event.userPHID IN (%Ls)', - $this->creatorPHIDs); + 'event.hostPHID IN (%Ls)', + $this->hostPHIDs); } if ($this->isCancelled !== null) { diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 44e19ec3e7..f9083f9021 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -26,8 +26,9 @@ final class PhabricatorCalendarEventSearchEngine protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchDatasourceField()) - ->setLabel(pht('Created By')) - ->setKey('creatorPHIDs') + ->setLabel(pht('Hosts')) + ->setKey('hostPHIDs') + ->setAliases(array('host', 'hostPHID', 'hosts')) ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Invited')) @@ -78,8 +79,8 @@ final class PhabricatorCalendarEventSearchEngine $query = $this->newQuery(); $viewer = $this->requireViewer(); - if ($map['creatorPHIDs']) { - $query->withCreatorPHIDs($map['creatorPHIDs']); + if ($map['hostPHIDs']) { + $query->withHostPHIDs($map['hostPHIDs']); } if ($map['invitedPHIDs']) { From e2b6912b9dc7eda5c9c8a4b45bbc0112dcc82bcd Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Jul 2016 10:10:09 -0700 Subject: [PATCH 40/76] Store "All Day" events in a way that is compatible with EditEngine Summary: Ref T11326. Normally, events occur at a specific epoch, independent of the viewer. For example, if we're having a meeting in 35 hours, every user who looks at the event will see that it starts 35 hours from now. But when an event is "All Day", the start time and end time depend on the //viewer//. A day like "Christmas" does not start at the same time for everyone: it starts sooner if you're in a more-eastern timezone. Baiscally, an event on "July 15th" starts whenever "July 15th" starts for whoever is looking at it. Previously, we stored these events by using the western-most and eastern-most timezones as the start and end times (the earliest possible start and latest possible end). This worked OK, but we get into a bunch of trouble with EditEngine, mostly because each field can be updated individually now. We can't easily tell if an event is all-day or not when reading or updating the start time and end time, and making that easier would introduce a huge amount of complexity. Instead, when we update the start or end time, we write //two// times: - The epoch timestamp of the time the user entered, which is the start time we will use if the event is a normal event. - The epoch timestamp of 12:00 AM in UTC on the same date as the //local// date the user entered. This is pretty much like just storing the date the user actually typed. This is what w'ell use if the event is an all-day event. Then, no matter whether the event is later made all-day or not, we have all the information we need to display it correctly. Test Plan: - Created and edited all-day events. - Migrated existing all-day events, which appeared to survive without problems. (Note that events all-day which were created or edited in the last couple of days `master` won't survive this mutation correctly and will need to be fixed.) - Created and edited normal, recurring, and recurring all-day events. - Swapped back to `stable`, created an event, specifically migrated it forward, made sure it survived with times intact. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16305 --- .../20160715.event.01.alldayfrom.sql | 2 + .../20160715.event.02.alldayto.sql | 2 + .../autopatches/20160715.event.03.allday.php | 52 +++++++++++++++++++ .../storage/PhabricatorCalendarEvent.php | 38 +++++++++++--- ...ricatorCalendarEventEndDateTransaction.php | 10 ++++ ...catorCalendarEventStartDateTransaction.php | 10 ++++ 6 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 resources/sql/autopatches/20160715.event.01.alldayfrom.sql create mode 100644 resources/sql/autopatches/20160715.event.02.alldayto.sql create mode 100644 resources/sql/autopatches/20160715.event.03.allday.php diff --git a/resources/sql/autopatches/20160715.event.01.alldayfrom.sql b/resources/sql/autopatches/20160715.event.01.alldayfrom.sql new file mode 100644 index 0000000000..269345b3d9 --- /dev/null +++ b/resources/sql/autopatches/20160715.event.01.alldayfrom.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD allDayDateFrom INT UNSIGNED NOT NULL; diff --git a/resources/sql/autopatches/20160715.event.02.alldayto.sql b/resources/sql/autopatches/20160715.event.02.alldayto.sql new file mode 100644 index 0000000000..7038274487 --- /dev/null +++ b/resources/sql/autopatches/20160715.event.02.alldayto.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD allDayDateTo INT UNSIGNED NOT NULL; diff --git a/resources/sql/autopatches/20160715.event.03.allday.php b/resources/sql/autopatches/20160715.event.03.allday.php new file mode 100644 index 0000000000..8bc3ffe568 --- /dev/null +++ b/resources/sql/autopatches/20160715.event.03.allday.php @@ -0,0 +1,52 @@ +establishConnection('w'); + +// Previously, "All Day" events were stored with a start and end date set to +// the earliest possible start and end seconds for the corresponding days. We +// now store all day events with their "date" epochs as UTC, separate from +// individual event times. +$zone_min = new DateTimeZone('Pacific/Midway'); +$zone_max = new DateTimeZone('Pacific/Kiritimati'); +$zone_utc = new DateTimeZone('UTC'); + +foreach (new LiskMigrationIterator($table) as $event) { + // If this event has already migrated, skip it. + if ($event->getAllDayDateFrom()) { + continue; + } + + $is_all_day = $event->getIsAllDay(); + + $epoch_min = $event->getDateFrom(); + $epoch_max = $event->getDateTo(); + + $date_min = new DateTime('@'.$epoch_min); + $date_max = new DateTime('@'.$epoch_max); + + if ($is_all_day) { + $date_min->setTimeZone($zone_min); + $date_min->modify('+2 days'); + $date_max->setTimeZone($zone_max); + $date_max->modify('-2 days'); + } else { + $date_min->setTimeZone($zone_utc); + $date_max->setTimeZone($zone_utc); + } + + $string_min = $date_min->format('Y-m-d'); + $string_max = $date_max->format('Y-m-d 23:59:00'); + + $allday_min = id(new DateTime($string_min, $zone_utc))->format('U'); + $allday_max = id(new DateTime($string_max, $zone_utc))->format('U'); + + queryfx( + $conn, + 'UPDATE %T SET allDayDateFrom = %d, allDayDateTo = %d + WHERE id = %d', + $table->getTableName(), + $allday_min, + $allday_max, + $event->getID()); +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 53261ed49b..f0f9598b98 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -19,6 +19,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $hostPHID; protected $dateFrom; protected $dateTo; + protected $allDayDateFrom; + protected $allDayDateTo; protected $description; protected $isCancelled; protected $isAllDay; @@ -62,7 +64,9 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $view_policy = $app->getPolicy($view_default); $edit_policy = $app->getPolicy($edit_default); - $start = new DateTime('@'.PhabricatorTime::getNow()); + $now = PhabricatorTime::getNow(); + + $start = new DateTime('@'.$now); $start->setTimeZone($actor->getTimeZone()); $start->setTime($start->format('H'), 0, 0); @@ -72,6 +76,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $epoch_min = $start->format('U'); $epoch_max = $end->format('U'); + $now_date = new DateTime('@'.$now); + $now_min = id(clone $now_date)->setTime(0, 0)->format('U'); + $now_max = id(clone $now_date)->setTime(23, 59)->format('U'); + $default_icon = 'fa-calendar'; return id(new PhabricatorCalendarEvent()) @@ -91,6 +99,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->attachInvitees(array()) ->setDateFrom($epoch_min) ->setDateTo($epoch_max) + ->setAllDayDateFrom($now_min) + ->setAllDayDateTo($now_max) ->applyViewerTimezone($actor); } @@ -171,9 +181,21 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $duration = $this->getDuration(); + $utc = new DateTimeZone('UTC'); + + $allday_from = $parent->getAllDayDateFrom(); + $allday_date = new DateTime('@'.$allday_from, $utc); + $allday_date->setTimeZone($utc); + $allday_date->modify($modify_key); + + $allday_min = $allday_date->format('U'); + $allday_duration = ($parent->getAllDayDateTo() - $allday_from); + $this ->setDateFrom($date) - ->setDateTo($date + $duration); + ->setDateTo($date + $duration) + ->setAllDayDateFrom($allday_min) + ->setAllDayDateTo($allday_min + $allday_duration); return $this; } @@ -227,15 +249,15 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } else { $zone = $viewer->getTimeZone(); - $this->viewerDateFrom = $this->getDateEpochForTimeZone( - $this->getDateFrom(), + $this->viewerDateFrom = $this->getDateEpochForTimezone( + $this->getAllDayDateFrom(), new DateTimeZone('UTC'), 'Y-m-d', null, $zone); - $this->viewerDateTo = $this->getDateEpochForTimeZone( - $this->getDateTo(), + $this->viewerDateTo = $this->getDateEpochForTimezone( + $this->getAllDayDateTo(), new DateTimeZone('UTC'), 'Y-m-d 23:59:00', null, @@ -249,7 +271,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this->getDateTo() - $this->getDateFrom(); } - private function getDateEpochForTimeZone( + public function getDateEpochForTimezone( $epoch, $src_zone, $format, @@ -295,6 +317,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 'name' => 'text', 'dateFrom' => 'epoch', 'dateTo' => 'epoch', + 'allDayDateFrom' => 'epoch', + 'allDayDateTo' => 'epoch', 'description' => 'text', 'isCancelled' => 'bool', 'isAllDay' => 'bool', diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php index 124787305d..fc7f9859ba 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php @@ -10,7 +10,17 @@ final class PhabricatorCalendarEventEndDateTransaction } public function applyInternalEffects($object, $value) { + $actor = $this->getActor(); + $object->setDateTo($value); + + $object->setAllDayDateTo( + $object->getDateEpochForTimezone( + $value, + $actor->getTimeZone(), + 'Y-m-d 23:59:00', + null, + new DateTimeZone('UTC'))); } public function getTitle() { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php index 31a41a3967..9823ec336f 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php @@ -10,7 +10,17 @@ final class PhabricatorCalendarEventStartDateTransaction } public function applyInternalEffects($object, $value) { + $actor = $this->getActor(); + $object->setDateFrom($value); + + $object->setAllDayDateFrom( + $object->getDateEpochForTimezone( + $value, + $actor->getTimeZone(), + 'Y-m-d', + null, + new DateTimeZone('UTC'))); } public function getTitle() { From b6c3d184d200e6bc43ba8f15a069c3a8620779cc Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Jul 2016 09:47:46 -0700 Subject: [PATCH 41/76] Make Calendar month view events render a little more consistently Summary: Ref T11326. This just inches things forward a little bit: - Make it easier to see current day. - Line-through cancelled events. - Don't colorize the whole event title, just use an Attending/Invited/Custom icon. - Slightly subtler treatment for all-day events. Test Plan: See screenshot in T11326. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16306 --- resources/celerity/map.php | 8 +- .../PhabricatorCalendarEventSearchEngine.php | 74 +++++++------------ .../storage/PhabricatorCalendarEvent.php | 60 +++++++++++++++ .../view/AphrontCalendarEventView.php | 20 +++++ .../phui/calendar/PHUICalendarListView.php | 24 +++--- .../phui/calendar/PHUICalendarMonthView.php | 28 ++++--- .../css/phui/calendar/phui-calendar-month.css | 17 +++-- .../rsrc/css/phui/calendar/phui-calendar.css | 10 ++- 8 files changed, 156 insertions(+), 85 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 31a56d7e70..276223ec85 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -117,8 +117,8 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '56e6381a', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', - 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'f3bb2030', + 'rsrc/css/phui/calendar/phui-calendar.css' => '3354bbd6', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => '3baef8db', @@ -825,10 +825,10 @@ return array( 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '5c8387cf', 'phui-button-css' => '4a5fbe3d', - 'phui-calendar-css' => 'ccabe893', + 'phui-calendar-css' => '3354bbd6', 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => '56e6381a', - 'phui-calendar-month-css' => '476be7e0', + 'phui-calendar-month-css' => 'f3bb2030', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => '6b813619', 'phui-curtain-view-css' => '7148ae25', diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index f9083f9021..9cbb1985a6 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -255,7 +255,7 @@ final class PhabricatorCalendarEventSearchEngine array $handles) { if ($this->isMonthView($query)) { - return $this->buildCalendarView($events, $query, $handles); + return $this->buildCalendarView($events, $query); } else if ($this->isDayView($query)) { return $this->buildCalendarDayView($events, $query, $handles); } @@ -283,32 +283,14 @@ final class PhabricatorCalendarEventSearchEngine $item->addAttribute($event->renderEventDate($viewer, false)); if ($event->isCancelledEvent()) { - $status_icon = 'fa-times red'; - $status_label = pht('Cancelled'); $item->setDisabled(true); - } else if ($viewer->isLoggedIn()) { - $status = $event->getUserInviteStatus($viewer->getPHID()); - switch ($status) { - case PhabricatorCalendarEventInvitee::STATUS_ATTENDING: - $status_icon = 'fa-check-circle green'; - $status_label = pht('Attending'); - break; - case PhabricatorCalendarEventInvitee::STATUS_INVITED: - $status_icon = 'fa-user-plus green'; - $status_label = pht('Invited'); - break; - case PhabricatorCalendarEventInvitee::STATUS_DECLINED: - $status_icon = 'fa-times grey'; - $status_label = pht('Declined'); - break; - default: - $status_icon = $event->getIcon().' grey'; - $status_label = null; - break; - } } - $item->setStatusIcon($status_icon, $status_label); + $status_icon = $event->getDisplayIcon($viewer); + $status_color = $event->getDisplayIconColor($viewer); + $status_label = $event->getDisplayIconLabel($viewer); + + $item->setStatusIcon("{$status_icon} {$status_color}", $status_label); $host = pht( 'Hosted by %s', @@ -326,12 +308,12 @@ final class PhabricatorCalendarEventSearchEngine } private function buildCalendarView( - array $statuses, - PhabricatorSavedQuery $query, - array $handles) { + array $events, + PhabricatorSavedQuery $query) { + assert_instances_of($events, 'PhabricatorCalendarEvent'); $viewer = $this->requireViewer(); - $now = time(); + $now = PhabricatorTime::getNow(); list($start_year, $start_month) = $this->getDisplayYearAndMonthAndDay( @@ -339,9 +321,9 @@ final class PhabricatorCalendarEventSearchEngine $this->getQueryDateTo($query)->getEpoch(), $query->getParameter('display')); - $now_year = phabricator_format_local_time($now, $viewer, 'Y'); + $now_year = phabricator_format_local_time($now, $viewer, 'Y'); $now_month = phabricator_format_local_time($now, $viewer, 'm'); - $now_day = phabricator_format_local_time($now, $viewer, 'j'); + $now_day = phabricator_format_local_time($now, $viewer, 'j'); if ($start_month == $now_month && $start_year == $now_year) { $month_view = new PHUICalendarMonthView( @@ -360,27 +342,21 @@ final class PhabricatorCalendarEventSearchEngine $month_view->setUser($viewer); - $phids = mpull($statuses, 'getHostPHID'); - $handles = $viewer->loadHandles($phids); + foreach ($events as $event) { + $epoch_min = $event->getViewerDateFrom(); + $epoch_max = $event->getViewerDateTo(); - foreach ($statuses as $status) { - $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); + $event_view = id(new AphrontCalendarEventView()) + ->setHostPHID($event->getHostPHID()) + ->setEpochRange($epoch_min, $epoch_max) + ->setIsCancelled($event->isCancelledEvent()) + ->setName($event->getName()) + ->setURI($event->getURI()) + ->setIsAllDay($event->getIsAllDay()) + ->setIcon($event->getDisplayIcon($viewer)) + ->setIconColor($event->getDisplayIconColor($viewer)); - $event = new AphrontCalendarEventView(); - $event->setEpochRange( - $status->getViewerDateFrom(), - $status->getViewerDateTo()); - $event->setIsAllDay($status->getIsAllDay()); - $event->setIcon($status->getIcon()); - - $name_text = $handles[$status->getHostPHID()]->getName(); - $status_text = $status->getName(); - $event->setHostPHID($status->getHostPHID()); - $event->setDescription(pht('%s (%s)', $name_text, $status_text)); - $event->setName($status_text); - $event->setURI($status->getURI()); - $event->setViewerIsInvited($viewer_is_invited); - $month_view->addEvent($event); + $month_view->addEvent($event_view); } $month_view->setBrowseURI( diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index f0f9598b98..ca42f872f7 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -531,6 +531,66 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } + public function getDisplayIcon(PhabricatorUser $viewer) { + if ($this->isCancelledEvent()) { + return 'fa-times'; + } + + if ($viewer->isLoggedIn()) { + $status = $this->getUserInviteStatus($viewer->getPHID()); + switch ($status) { + case PhabricatorCalendarEventInvitee::STATUS_ATTENDING: + return 'fa-check-circle'; + case PhabricatorCalendarEventInvitee::STATUS_INVITED: + return 'fa-user-plus'; + case PhabricatorCalendarEventInvitee::STATUS_DECLINED: + return 'fa-times'; + } + } + + return $this->getIcon(); + } + + public function getDisplayIconColor(PhabricatorUser $viewer) { + if ($this->isCancelledEvent()) { + return 'red'; + } + + if ($viewer->isLoggedIn()) { + $status = $this->getUserInviteStatus($viewer->getPHID()); + switch ($status) { + case PhabricatorCalendarEventInvitee::STATUS_ATTENDING: + return 'green'; + case PhabricatorCalendarEventInvitee::STATUS_INVITED: + return 'green'; + case PhabricatorCalendarEventInvitee::STATUS_DECLINED: + return 'grey'; + } + } + + return 'bluegrey'; + } + + public function getDisplayIconLabel(PhabricatorUser $viewer) { + if ($this->isCancelledEvent()) { + return pht('Cancelled'); + } + + if ($viewer->isLoggedIn()) { + $status = $this->getUserInviteStatus($viewer->getPHID()); + switch ($status) { + case PhabricatorCalendarEventInvitee::STATUS_ATTENDING: + return pht('Attending'); + case PhabricatorCalendarEventInvitee::STATUS_INVITED: + return pht('Invited'); + case PhabricatorCalendarEventInvitee::STATUS_DECLINED: + return pht('Declined'); + } + } + + return null; + } + /* -( Markup Interface )--------------------------------------------------- */ diff --git a/src/applications/calendar/view/AphrontCalendarEventView.php b/src/applications/calendar/view/AphrontCalendarEventView.php index 40317047a3..0dc9231e6f 100644 --- a/src/applications/calendar/view/AphrontCalendarEventView.php +++ b/src/applications/calendar/view/AphrontCalendarEventView.php @@ -12,7 +12,27 @@ final class AphrontCalendarEventView extends AphrontView { private $uri; private $isAllDay; private $icon; + private $iconColor; private $canEdit; + private $isCancelled; + + public function setIconColor($icon_color) { + $this->iconColor = $icon_color; + return $this; + } + + public function getIconColor() { + return $this->iconColor; + } + + public function setIsCancelled($is_cancelled) { + $this->isCancelled = $is_cancelled; + return $this; + } + + public function getIsCancelled() { + return $this->isCancelled; + } public function setURI($uri) { $this->uri = $uri; diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index 7dd921d769..cff2dcf8a5 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -53,14 +53,11 @@ final class PHUICalendarListView extends AphrontTagView { $this->getUser()); } - if ($event->getViewerIsInvited()) { - $icon_color = 'green'; - } else { - $icon_color = null; - } + $icon_icon = $event->getIcon(); + $icon_color = $event->getIconColor(); $dot = id(new PHUIIconView()) - ->setIcon($event->getIcon(), $icon_color) + ->setIcon($icon_icon, $icon_color) ->addClass('phui-calendar-list-item-icon'); $title = phutil_tag( @@ -76,12 +73,14 @@ final class PHUICalendarListView extends AphrontTagView { ), $timelabel); - $class = 'phui-calendar-list-item'; - if ($event->getViewerIsInvited()) { - $class = $class.' phui-calendar-viewer-invited'; - } + $event_classes = array(); + $event_classes[] = 'phui-calendar-list-item'; if ($event->getIsAllDay()) { - $class = $class.' all-day'; + $event_classes[] = 'all-day'; + } + + if ($event->getIsCancelled()) { + $event_classes[] = 'event-cancelled'; } $tip = $this->getEventTooltip($event); @@ -92,6 +91,7 @@ final class PHUICalendarListView extends AphrontTagView { } else { $tip_align = 'W'; } + $content = javelin_tag( 'a', array( @@ -112,7 +112,7 @@ final class PHUICalendarListView extends AphrontTagView { $singletons[] = phutil_tag( 'li', array( - 'class' => $class, + 'class' => implode(' ', $event_classes), ), $content); } diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index da7220f015..f3f90c5a14 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -236,16 +236,7 @@ final class PHUICalendarMonthView extends AphrontView { $cell_day = null; } - if ($date && $date->format('j') == $this->day && - $date->format('m') == $this->month) { - $today_class = 'phui-calendar-today-slot phui-calendar-today'; - } else { - $today_class = 'phui-calendar-today-slot'; - } - - if ($this->isDateInCurrentWeek($date)) { - $today_class .= ' phui-calendar-this-week'; - } + $today_class = 'phui-calendar-today-slot'; $last_week_day = 6; if ($date->format('w') == $last_week_day) { @@ -271,10 +262,25 @@ final class PHUICalendarMonthView extends AphrontView { $today_slot, )); + $classes = array(); + $classes[] = 'phui-calendar-date-number-container'; + + if ($date) { + if ($this->isDateInCurrentWeek($date)) { + $classes[] = 'phui-calendar-this-week'; + } + + if ($date->format('j') == $this->day) { + if ($date->format('m') == $this->month) { + $classes[] = 'phui-calendar-today'; + } + } + } + return phutil_tag( 'td', array( - 'class' => 'phui-calendar-date-number-container '.$class, + 'class' => implode(' ', $classes), ), $cell_div); } diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index a219e49cf7..adaeabadd0 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -133,12 +133,17 @@ table.phui-calendar-view td.phui-calendar-date-number-container { right: 0px; } -.phui-calendar-today-slot.phui-calendar-this-week { - background-color: {$blueborder}; +.phui-calendar-this-week .phui-calendar-today-slot { + background-color: {$blue}; } -.phui-calendar-today-slot.phui-calendar-today { - background-color: {$lightblueborder}; +.phui-calendar-today .phui-calendar-today-slot, +.phui-calendar-today .phui-calendar-date-number { + background-color: {$sky}; +} + +table.phui-calendar-view .phui-calendar-today a.phui-calendar-date-number { + color: #ffffff; } .phui-calendar-event-empty { @@ -159,7 +164,7 @@ table.phui-calendar-view td.phui-calendar-date-number-container { .phui-calendar-view .phui-calendar-list li.phui-calendar-list-item.all-day { height: 20px; - background-color: {$darkgreybackground}; + background-color: {$bluebackground}; display: block; float: none; } @@ -194,7 +199,7 @@ li.phui-calendar-viewer-invited.all-day { .phui-calendar-view li.phui-calendar-list-item .phui-calendar-list-title { width: auto; position: absolute; - left: 20px; + left: 16px; right: 60px; text-overflow: ellipsis; overflow: hidden; diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar.css b/webroot/rsrc/css/phui/calendar/phui-calendar.css index d2a094c65a..637a479aa8 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar.css @@ -2,9 +2,13 @@ * @provides phui-calendar-css */ - .phui-calendar-list a { - color: {$lightgreytext}; - } +.phui-calendar-list a { + color: {$greytext}; +} + +.phui-calendar-list .event-cancelled .phui-calendar-list-title { + text-decoration: line-through; +} .phui-calendar-viewer-invited a { color: {$green}; From 7e49479ab0ffe7749e22dee3e9ac8f18c96ae0ac Mon Sep 17 00:00:00 2001 From: Sbastien Santoro Date: Fri, 15 Jul 2016 21:06:47 +0000 Subject: [PATCH 42/76] =?UTF-8?q?discouarges=20=E2=86=92=20discourages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test Plan: Read again the sentence. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D16307 --- src/docs/flavor/so_many_databases.diviner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/flavor/so_many_databases.diviner b/src/docs/flavor/so_many_databases.diviner index 8fa41e6c97..74fb21332f 100644 --- a/src/docs/flavor/so_many_databases.diviner +++ b/src/docs/flavor/so_many_databases.diviner @@ -16,7 +16,7 @@ on shared hosts which require a lot of work to create or authorize access to each database. However, Phabricator does a lot of advanced or complex things which are difficult to configure or manage on shared hosts, and we don't recommend installing it on a shared host. The install documentation explicitly -discouarges installing on shared hosts. +discourages installing on shared hosts. Broadly, in cases where we must choose between operating well at scale for growing organizations and installing easily on shared hosts, we prioritize From 4859a3373921bac761c725d87a1e10df519cb35c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Jul 2016 14:02:24 -0700 Subject: [PATCH 43/76] Make Calendar day view a little more consistent Summary: Ref T11326. This just cleans things up a little and removes some of the obvious layout/CSS issues. Test Plan: - Viewed day view before/after. Also viewed profile panel. Before: {F1725547} After: {F1725548} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16308 --- resources/celerity/map.php | 15 +++--- .../PhabricatorCalendarEventSearchEngine.php | 48 +++++++++--------- ...PhabricatorPeopleProfileViewController.php | 6 ++- .../phui/calendar/PHUICalendarDayView.php | 16 +++--- .../css/phui/calendar/phui-calendar-day.css | 37 +++++++------- .../css/phui/calendar/phui-calendar-list.css | 2 +- .../application/calendar/behavior-day-view.js | 50 ++++++++++--------- 7 files changed, 90 insertions(+), 84 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 276223ec85..0a3bdd965f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -115,8 +115,8 @@ return array( 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'dd849797', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', - 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', - 'rsrc/css/phui/calendar/phui-calendar-list.css' => '56e6381a', + 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'f15bb6d6', + 'rsrc/css/phui/calendar/phui-calendar-list.css' => '5d89cd71', 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'f3bb2030', 'rsrc/css/phui/calendar/phui-calendar.css' => '3354bbd6', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', @@ -362,7 +362,7 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/behavior-day-view.js' => '1a5bb063', + 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/calendar/behavior-recurring-edit.js' => '5f1c4d5f', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', @@ -603,7 +603,7 @@ return array( 'javelin-behavior-dashboard-move-panels' => '019f36c4', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', - 'javelin-behavior-day-view' => '1a5bb063', + 'javelin-behavior-day-view' => '4b3c4443', 'javelin-behavior-desktop-notifications-control' => 'edd1ba66', 'javelin-behavior-detect-timezone' => '4c193c96', 'javelin-behavior-device' => 'bb1dd507', @@ -826,8 +826,8 @@ return array( 'phui-box-css' => '5c8387cf', 'phui-button-css' => '4a5fbe3d', 'phui-calendar-css' => '3354bbd6', - 'phui-calendar-day-css' => 'd1cf6f93', - 'phui-calendar-list-css' => '56e6381a', + 'phui-calendar-day-css' => 'f15bb6d6', + 'phui-calendar-list-css' => '5d89cd71', 'phui-calendar-month-css' => 'f3bb2030', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => '6b813619', @@ -1233,6 +1233,9 @@ return array( 'javelin-dom', 'javelin-vector', ), + '4b3c4443' => array( + 'phuix-icon-view', + ), '4b700e9e' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 9cbb1985a6..86d298d069 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -257,7 +257,7 @@ final class PhabricatorCalendarEventSearchEngine if ($this->isMonthView($query)) { return $this->buildCalendarView($events, $query); } else if ($this->isDayView($query)) { - return $this->buildCalendarDayView($events, $query, $handles); + return $this->buildCalendarDayView($events, $query); } assert_instances_of($events, 'PhabricatorCalendarEvent'); @@ -370,9 +370,8 @@ final class PhabricatorCalendarEventSearchEngine } private function buildCalendarDayView( - array $statuses, - PhabricatorSavedQuery $query, - array $handles) { + array $events, + PhabricatorSavedQuery $query) { $viewer = $this->requireViewer(); @@ -392,33 +391,32 @@ final class PhabricatorCalendarEventSearchEngine $day_view->setUser($viewer); - $phids = mpull($statuses, 'getHostPHID'); - - foreach ($statuses as $status) { - if ($status->getIsCancelled()) { - continue; - } - - $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); + $phids = mpull($events, 'getHostPHID'); + foreach ($events as $event) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, - $status, + $event, PhabricatorPolicyCapability::CAN_EDIT); - $event = new AphrontCalendarEventView(); - $event->setCanEdit($can_edit); - $event->setEventID($status->getID()); - $event->setEpochRange( - $status->getViewerDateFrom(), - $status->getViewerDateTo()); - $event->setIsAllDay($status->getIsAllDay()); - $event->setIcon($status->getIcon()); - $event->setViewerIsInvited($viewer_is_invited); + $epoch_min = $event->getViewerDateFrom(); + $epoch_max = $event->getViewerDateTo(); - $event->setName($status->getName()); - $event->setURI($status->getURI()); - $day_view->addEvent($event); + $status_icon = $event->getDisplayIcon($viewer); + $status_color = $event->getDisplayIconColor($viewer); + + $event_view = id(new AphrontCalendarEventView()) + ->setCanEdit($can_edit) + ->setEventID($event->getID()) + ->setEpochRange($epoch_min, $epoch_max) + ->setIsAllDay($event->getIsAllDay()) + ->setIcon($status_icon) + ->setIconColor($status_color) + ->setName($event->getName()) + ->setURI($event->getURI()) + ->setIsCancelled($event->isCancelledEvent()); + + $day_view->addEvent($event_view); } $day_view->setBrowseURI( diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 66d5e91af6..4621aba3c5 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -234,11 +234,13 @@ final class PhabricatorPeopleProfileViewController ->setHeader(pht('Calendar')) ->setHref( urisprintf( - '/calendar/?invitedPHIDs=%s#R', - $user->getPHID())); + '/calendar/?invited=%s#R', + $user->getUsername())); + $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($day_view) + ->addClass('calendar-profile-box') ->setBackground(PHUIObjectBoxView::GREY); return $box; diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index ee9e8b5cf4..a2cab191a5 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -85,6 +85,8 @@ final class PHUICalendarDayView extends AphrontView { 'id' => $all_day_event->getEventID(), 'viewerIsInvited' => $all_day_event->getViewerIsInvited(), 'uri' => $all_day_event->getURI(), + 'displayIcon' => $all_day_event->getIcon(), + 'displayIconColor' => $all_day_event->getIconColor(), ); } } @@ -140,6 +142,8 @@ final class PHUICalendarDayView extends AphrontView { 'top' => $top.'px', 'height' => $height.'px', 'canEdit' => $event->getCanEdit(), + 'displayIcon' => $event->getIcon(), + 'displayIconColor' => $event->getIconColor(), ); } } @@ -183,17 +187,11 @@ final class PHUICalendarDayView extends AphrontView { ->setFlush(true); $layout = id(new AphrontMultiColumnView()) - ->addColumn($sidebar, 'third') + ->addColumn($sidebar, 'third phui-day-view-upcoming') ->addColumn($table_box, 'thirds phui-day-view-column') - ->setFluidLayout(true) - ->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM); + ->setFluidLayout(true); - return phutil_tag( - 'div', - array( - 'class' => 'ml', - ), - $layout); + return $layout; } private function getAllDayEvents() { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css index e012be0afa..acb577675a 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -10,6 +10,15 @@ width: 100%; } +.aphront-multi-column-column-outer.phui-day-view-upcoming, +.aphront-multi-column-column-outer.phui-day-view-column{ + padding: 0; +} + +.aphront-multi-column-column-outer.phui-day-view-upcoming .phui-header-shell { + margin: 0; +} + .phui-calendar-day-view { overflow: scroll; width: 100%; @@ -61,17 +70,13 @@ div.phui-calendar-day-event.all-day { } .phui-calendar-day-event-link { - padding: 8px; - border: 1px solid {$greyborder}; - background-color: {$darkgreybackground}; - margin: 0 1px; + margin: 0 0 -1px -1px; + box-sizing: border-box; position: absolute; left: 0; right: 0; top: 0; bottom: 0; - text-decoration: none; - color: {$greytext}; } .phui-calendar-day-event-link.viewer-invited-day-event { @@ -80,21 +85,19 @@ div.phui-calendar-day-event.all-day { color: {$green}; } -.day-view-all-day { - border: 1px solid {$greyborder}; - height: 12px; - margin: 0; - display: block; +.day-view-all-day, +.phui-calendar-day-event-link { padding: 8px; - background-color: {$darkgreybackground}; - text-decoration: none; + border: 1px solid {$lightblueborder}; + background-color: {$bluebackground}; color: {$greytext}; + text-decoration: none; + text-decoration: none; } -.day-view-all-day.viewer-invited-day-event { - border-color: {$green}; - background-color: {$lightgreen}; - color: {$green}; +.day-view-all-day { + margin: 3px 0 3px 4px; + display: block; } .phui-calendar-day-event + .phui-calendar-day-event .day-view-all-day { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css index 9580343415..9cf5e3bcd8 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css @@ -6,7 +6,7 @@ width: auto; } -.phui-calendar-list-container.calendar-day-view-sidebar .phui-object-box { +.calendar-profile-box .calendar-day-view-sidebar .phui-object-box { border-bottom: none; margin: 0; padding: 0 12px 12px; diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index 651698b026..7f821ffe83 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -1,8 +1,7 @@ /** * @provides javelin-behavior-day-view + * @requires phuix-icon-view */ - - JX.behavior('day-view', function(config) { function findTodayClusters() { @@ -92,13 +91,20 @@ JX.behavior('day-view', function(config) { link_class = link_class + ' viewer-invited-day-event'; } + var icon = new JX.PHUIXIconView() + .setIcon(e.displayIcon) + .setColor(e.displayIconColor) + .getNode(); + + var content = [icon, ' ', name]; + var name_link = JX.$N( 'a', { className : link_class, href: uri }, - name); + content); var class_name = 'phui-calendar-day-event'; if (e.canEdit) { @@ -123,22 +129,22 @@ JX.behavior('day-view', function(config) { return div; } - function drawAllDayEvent( - viewerIsInvited, - uri, - name) { + function drawAllDayEvent(e) { var class_name = 'day-view-all-day'; - if (viewerIsInvited) { - class_name = class_name + ' viewer-invited-day-event'; - } - name = JX.$N( + var icon = new JX.PHUIXIconView() + .setIcon(e.displayIcon) + .setColor(e.displayIconColor) + .getNode(); + var content = [icon, ' ', e.name]; + + var name = JX.$N( 'a', { className: class_name, - href: uri + href: e.uri }, - name); + content); var all_day_label = JX.$N( 'span', @@ -220,10 +226,7 @@ JX.behavior('day-view', function(config) { var all_day_events = []; for(i=0; i < today_all_day_events.length; i++) { var all_day_event = today_all_day_events[i]; - all_day_events.push(drawAllDayEvent( - all_day_event['viewerIsInvited'], - all_day_event['uri'], - all_day_event['name'])); + all_day_events.push(drawAllDayEvent(all_day_event)); } var table = JX.$N( @@ -329,15 +332,14 @@ JX.behavior('day-view', function(config) { } var data = e.getNodeData('phui-calendar-day-event-cell'); var time = data.time; + new JX.Workflow( - '/calendar/event/create/', + '/calendar/event/edit/', { - year: year, - month: month, - day: day, - time: time, - next: 'day', - query: query + start_d: year + '-' + month + '-' + day, + start_t: time, + end_d: year + '-' + month + '-' + day, + end_t: time + ' +1 hour' }) .start(); }); From 959337ec62f5f72dacd6541715106c2a54d19449 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Jul 2016 14:17:58 -0700 Subject: [PATCH 44/76] Add crumbs to Calendar events to return to the month/day view Summary: Ref T11326. This makes it a little easier to jump back up to check out your day. Test Plan: {F1725575} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16309 --- .../PhabricatorCalendarEventViewController.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 16e057ebcc..08c7d91869 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -26,6 +26,18 @@ final class PhabricatorCalendarEventViewController $monogram = $event->getMonogram(); $page_title = $monogram.' '.$event->getName(); $crumbs = $this->buildApplicationCrumbs(); + + $start = new DateTime('@'.$event->getViewerDateFrom()); + $start->setTimeZone($viewer->getTimeZone()); + + $crumbs->addTextCrumb( + $start->format('F Y'), + '/calendar/query/month/'.$start->format('Y/m/')); + + $crumbs->addTextCrumb( + $start->format('D jS'), + '/calendar/query/month/'.$start->format('Y/m/d/')); + $crumbs->addTextCrumb($monogram); $crumbs->setBorder(true); From 56bd762dd3ed5ced1914c6f36fd58dab589ccb31 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Jul 2016 13:06:41 -0700 Subject: [PATCH 45/76] Allow file comments to be edited Summary: Fixes T10750. Files have some outdated cache/key code which prevents recording an edit history on file comments. Remove this ancient cruft. (Users must `bin/storage adjust` after upgrading to this patch to reap the benefits.) Test Plan: - Ran `bin/storage adjust`. - Edited a comment in Files. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10750 Differential Revision: https://secure.phabricator.com/D16312 --- .../PhabricatorFileTransactionComment.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/applications/files/storage/PhabricatorFileTransactionComment.php b/src/applications/files/storage/PhabricatorFileTransactionComment.php index ee668a49fb..6bada88eac 100644 --- a/src/applications/files/storage/PhabricatorFileTransactionComment.php +++ b/src/applications/files/storage/PhabricatorFileTransactionComment.php @@ -7,20 +7,4 @@ final class PhabricatorFileTransactionComment return new PhabricatorFileTransaction(); } - public function shouldUseMarkupCache($field) { - // Only cache submitted comments. - return ($this->getTransactionPHID() != null); - } - - protected function getConfiguration() { - $config = parent::getConfiguration(); - $config[self::CONFIG_KEY_SCHEMA] = array( - 'key_draft' => array( - 'columns' => array('authorPHID', 'transactionPHID'), - 'unique' => true, - ), - ) + $config[self::CONFIG_KEY_SCHEMA]; - return $config; - } - } From c27ba19da3b70381bef83ea978036f42e527af50 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 21 Jul 2016 07:13:20 -0700 Subject: [PATCH 46/76] Raise human-readable error messages for overlong Phame blog titles and subtitles Summary: Fixes T11358. Entering a too-long title/subtitle currently raises an unfriendly (database-level) error. Raise a friendlier error. Test Plan: {F1731533} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11358 Differential Revision: https://secure.phabricator.com/D16313 --- .../phame/editor/PhameBlogEditor.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index ea1c132f64..14f63f51ac 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -160,6 +160,33 @@ final class PhameBlogEditor $error->setIsMissingFieldError(true); $errors[] = $error; } + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (phutil_utf8_strlen($new) > 64) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'The selected blog title is too long. The maximum length '. + 'of a blog title is 64 characters.'), + $xaction); + } + } + break; + case PhameBlogTransaction::TYPE_SUBTITLE: + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (phutil_utf8_strlen($new) > 64) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'The selected blog subtitle is too long. The maximum length '. + 'of a blog subtitle is 64 characters.'), + $xaction); + } + } break; case PhameBlogTransaction::TYPE_PARENTDOMAIN: if (!$xactions) { From 68904d941c5481f0b0237706366ce4d790deddd5 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Thu, 21 Jul 2016 23:42:27 +0000 Subject: [PATCH 47/76] bin/storage shell: force TCP Summary: `mysql` has the magic feature of ignoring port arguments and using the socket when connecting to localhost. This flag makes it not do that. Test Plan: `./bin/storage shell`, execute `status`, see `Connection: localhost via TCP/IP`. Reviewers: joshuaspence, #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16317 --- .../workflow/PhabricatorStorageManagementShellWorkflow.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php index 3cf85e5c35..45f4d2d8e8 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php @@ -31,7 +31,7 @@ final class PhabricatorStorageManagementShellWorkflow } return phutil_passthru( - 'mysql --default-character-set=utf8 '. + 'mysql --protocol=TCP --default-character-set=utf8 '. '-u %s %C -h %s %C', $api->getUser(), $flag_password, From fc950140b4c1cec0538573f4fc832d514347566c Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 21 Jul 2016 17:22:35 -0700 Subject: [PATCH 48/76] Blanket reject request which may have been poisoned by a "Proxy" header to mitigate the httpoxy vulnerability Summary: See accompanying discussion in T11359. As far as I can tell we aren't vulnerable, but subprocesses could be (now, or in the future). Reject any request which may have a `Proxy:` header. This will also do a false-positive reject if `HTTP_PROXY` is defined in the environment, but this is likely a misconfiguration (cURL does not read it). I'll provide guidance on this. Test Plan: - Made requests using `curl -H Proxy:...`, got rejected. - Made normal requests, got normal pages. Reviewers: chad, avivey Reviewed By: avivey Differential Revision: https://secure.phabricator.com/D16318 --- support/PhabricatorStartup.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 6c50ed603e..0a27a616e3 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -374,7 +374,7 @@ final class PhabricatorStartup { $http_error = 500); error_log($log_message); - echo $message; + echo $message."\n"; exit(1); } @@ -529,6 +529,13 @@ final class PhabricatorStartup { "Downgrade to version 3.1.13."); } } + + if (isset($_SERVER['HTTP_PROXY'])) { + self::didFatal( + 'This HTTP request included a "Proxy:" header, poisoning the '. + 'environment (CVE-2016-5385 / httpoxy). Declining to process this '. + 'request. For details, see: https://phurl.io/u/httpoxy'); + } } From b6bf0f6a3b4971db173e782577a70c6cd5ddfbb0 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 22 Jul 2016 18:03:28 +0000 Subject: [PATCH 49/76] Re-implement calendar.invite transactions Summary: Fix T11339. Now, old and new are both simple lists of phids, and the rendering should make sense. Test Plan: Viewed existing transaction with all 3 states. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T11339 Differential Revision: https://secure.phabricator.com/D16311 --- .../20160720.calendar.invitetxn.php | 37 ++++++++++++++++++ ...bricatorCalendarEventInviteTransaction.php | 38 ++++++++----------- 2 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 resources/sql/autopatches/20160720.calendar.invitetxn.php diff --git a/resources/sql/autopatches/20160720.calendar.invitetxn.php b/resources/sql/autopatches/20160720.calendar.invitetxn.php new file mode 100644 index 0000000000..1d9ade6e67 --- /dev/null +++ b/resources/sql/autopatches/20160720.calendar.invitetxn.php @@ -0,0 +1,37 @@ +establishConnection('w'); + +echo pht( + "Restructuring calendar invite transactions...\n"); + +foreach (new LiskMigrationIterator($table) as $txn) { + $type = PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE; + if ($txn->getTransactionType() != $type) { + continue; + } + + $old_value = array_keys($txn->getOldValue()); + + $orig_new = $txn->getNewValue(); + $status_uninvited = 'uninvited'; + foreach ($orig_new as $key => $status) { + if ($status == $status_uninvited) { + unset($orig_new[$key]); + } + } + $new_value = array_keys($orig_new); + + queryfx( + $conn_w, + 'UPDATE %T SET '. + 'oldValue = %s, newValue = %s'. + 'WHERE id = %d', + $table->getTableName(), + phutil_json_encode($old_value), + phutil_json_encode($new_value), + $txn->getID()); +} + +echo pht('Done.')."\n"; diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php index 69575121af..ea9355eb25 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php @@ -15,31 +15,25 @@ final class PhabricatorCalendarEventInviteTransaction } } - return mpull($invitees, 'getStatus', 'getInviteePHID'); + return array_values(mpull($invitees, 'getInviteePHID')); } - public function generateNewValue($object, $value) { + private function generateChangeMap($object, $new_value) { $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; $status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; - $invitees = $this->generateOldValue($object); + $old = $this->generateOldValue($object); - $new = array_fuse($value); + $add = array_diff($new_value, $old); + $rem = array_diff($old, $new_value); - $all = array_keys($invitees + $new); $map = array(); - foreach ($all as $phid) { - $is_old = isset($invitees[$phid]); - $is_new = isset($new[$phid]); - - if ($is_old && !$is_new) { - $map[$phid] = $status_uninvited; - } else if (!$is_old && $is_new) { + foreach ($add as $phid) { $map[$phid] = $status_invited; - } else { - $map[$phid] = $invitees[$phid]; - } + } + foreach ($rem as $phid) { + $map[$phid] = $status_uninvited; } // If we're creating this event and the actor is inviting themselves, @@ -55,11 +49,12 @@ final class PhabricatorCalendarEventInviteTransaction } public function applyExternalEffects($object, $value) { - $phids = array_keys($value); + $map = $this->generateChangeMap($object, $value); + $invitees = $object->getInvitees(); $invitees = mpull($invitees, null, 'getInviteePHID'); - foreach ($phids as $phid) { + foreach ($map as $phid => $status) { $invitee = idx($invitees, $phid); if (!$invitee) { $invitee = id(new PhabricatorCalendarEventInvitee()) @@ -68,7 +63,7 @@ final class PhabricatorCalendarEventInviteTransaction ->setInviterPHID($this->getActingAsPHID()); $invitees[] = $invitee; } - $invitee->setStatus($value[$phid]) + $invitee->setStatus($status) ->save(); } @@ -179,11 +174,8 @@ final class PhabricatorCalendarEventInviteTransaction $old = $this->getOldValue(); $new = $this->getNewValue(); - $add = array_diff_key($new, $old); - $rem = array_diff_key($old, $new); - - $add = array_keys($add); - $rem = array_keys($rem); + $add = array_diff($new, $old); + $rem = array_diff($old, $new); return array(array_fuse($add), array_fuse($rem)); } From 020df6f5cb8077133b8705a6efb97805444a9b2c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 23 Jul 2016 13:28:02 -0700 Subject: [PATCH 50/76] Add a numeric input control for TOTP codes Summary: Fixes T11365. I tested these variants: - `` - `` Of these, this one (using `pattern`) appears to have the best behavior: it shows the correct keyboard on iOS mobile and does nothing on desktops. Using `type="number"` causes unwanted sub-controls to appear in desktop Safari, and a numbers + symbols keyboard to appear on iOS (presumably so users can type "." and "-" and maybe ","). Test Plan: Tested variants in desktop browsers and iOS simulator, see here and T11365 for discussion. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11365 Differential Revision: https://secure.phabricator.com/D16323 --- src/__phutil_library_map__.php | 2 ++ .../auth/factor/PhabricatorTOTPAuthFactor.php | 2 +- .../form/control/PHUIFormNumberControl.php | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/view/form/control/PHUIFormNumberControl.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3700788483..2c72ce85da 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1626,6 +1626,7 @@ phutil_register_library_map(array( 'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php', 'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php', 'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php', + 'PHUIFormNumberControl' => 'view/form/control/PHUIFormNumberControl.php', 'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php', 'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php', 'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php', @@ -6185,6 +6186,7 @@ phutil_register_library_map(array( 'PHUIFormIconSetControl' => 'AphrontFormControl', 'PHUIFormInsetView' => 'AphrontView', 'PHUIFormLayoutView' => 'AphrontView', + 'PHUIFormNumberControl' => 'AphrontFormControl', 'PHUIHandleListView' => 'AphrontTagView', 'PHUIHandleTagListView' => 'AphrontTagView', 'PHUIHandleView' => 'AphrontView', diff --git a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php index b5124ab3ec..2910dc0610 100644 --- a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php +++ b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php @@ -132,7 +132,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { 'the authenticator correctly:')); $form->appendChild( - id(new AphrontFormTextControl()) + id(new PHUIFormNumberControl()) ->setLabel(pht('TOTP Code')) ->setName('totpcode') ->setValue($code) diff --git a/src/view/form/control/PHUIFormNumberControl.php b/src/view/form/control/PHUIFormNumberControl.php new file mode 100644 index 0000000000..d65e590746 --- /dev/null +++ b/src/view/form/control/PHUIFormNumberControl.php @@ -0,0 +1,22 @@ + 'text', + 'pattern' => '\d*', + 'name' => $this->getName(), + 'value' => $this->getValue(), + 'disabled' => $this->getDisabled() ? 'disabled' : null, + 'id' => $this->getID(), + )); + } + +} From 29d6e5fd4bae08b4015799bd5a3041b8843900ef Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 26 Jul 2016 04:47:02 -0700 Subject: [PATCH 51/76] Use numeric input control for TOTP factor entry Summary: Finishes fixing T11365. rP28199bcb48 added the new numeric entry control and used it for TOTP setup, but missed the case of entering a factor when TOTP was already set up. Test Plan: Observe behaviour of TOTP setup and subsequent factor entry in iOS browser, make sure they're consistent. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Maniphest Tasks: T11365 Differential Revision: https://secure.phabricator.com/D16325 --- src/applications/auth/factor/PhabricatorTOTPAuthFactor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php index 2910dc0610..8875b960e2 100644 --- a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php +++ b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php @@ -151,7 +151,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { } $form->appendChild( - id(new AphrontFormTextControl()) + id(new PHUIFormNumberControl()) ->setName($this->getParameterName($config, 'totpcode')) ->setLabel(pht('App Code')) ->setCaption(pht('Factor Name: %s', $config->getFactorName())) From 637b58c7c83af4753e373d2e8bff23a155c69032 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 26 Jul 2016 07:06:25 -0700 Subject: [PATCH 52/76] Correct an issue with epoch timestamps in Conduit Summary: Fixes T11375. Some validation code was mishandling raw epoch timestamps. For numeric values larger than 29999999 (e.g., 2999-12-25, christmas 2999), assume the value is a timestamp. Test Plan: Used `maniphest.search` to query for `modifiedStart`, got a better result set and saw the `dateModified` constraint in the query. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11375 Differential Revision: https://secure.phabricator.com/D16326 --- .../search/field/PhabricatorSearchDateField.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/search/field/PhabricatorSearchDateField.php b/src/applications/search/field/PhabricatorSearchDateField.php index 84b7e2580a..21b1627de7 100644 --- a/src/applications/search/field/PhabricatorSearchDateField.php +++ b/src/applications/search/field/PhabricatorSearchDateField.php @@ -35,6 +35,14 @@ final class PhabricatorSearchDateField return null; } + // If this appears to be an epoch timestamp, just return it unmodified. + // This assumes values like "2016" or "20160101" are "Ymd". + if (is_int($value) || ctype_digit($value)) { + if ((int)$value > 30000000) { + return (int)$value; + } + } + return PhabricatorTime::parseLocalTime($value, $this->getViewer()); } From 6a5cfe3b025a705d14723cfc8e04137e180face9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 26 Jul 2016 12:17:45 -0700 Subject: [PATCH 53/76] Roomier forms on mobile Summary: Mobile forms are super tight, this opens them up a little bit. Test Plan: Review editing a document, task, mobile, tablet. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16329 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-form-view.css | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0a3bdd965f..60abd2874f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '4e7e9bde', + 'core.pkg.css' => '91e1d466', 'core.pkg.js' => '13c7e56a', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3fb7f532', @@ -133,7 +133,7 @@ return array( 'rsrc/css/phui/phui-document.css' => '715aedfb', 'rsrc/css/phui/phui-feed-story.css' => 'aa49845d', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', - 'rsrc/css/phui/phui-form-view.css' => '6a51768e', + 'rsrc/css/phui/phui-form-view.css' => '1b04a437', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => '4c7dd8f5', @@ -839,7 +839,7 @@ return array( 'phui-font-icon-base-css' => '6449bce8', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', - 'phui-form-view-css' => '6a51768e', + 'phui-form-view-css' => '1b04a437', 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => '4c7dd8f5', 'phui-hovercard' => '1bd28176', diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 6f88db302f..5d36add4e0 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -128,6 +128,10 @@ padding: 4px; } +.device-phone .aphront-form-control { + padding: 4px 8px 8px; +} + .phui-form-full-width .aphront-form-control { padding: 4px 0; } From cfb6d5a70c9fa519732ffb0c070902d34ad0953a Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 26 Jul 2016 12:08:32 -0700 Subject: [PATCH 54/76] Clean up mobile crumb padding Summary: These feel mis-aligned now. Equalizes paddding left and right. Test Plan: Review a task create, workboards. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16328 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-crumbs-view.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 60abd2874f..13ad834762 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '91e1d466', + 'core.pkg.css' => '95b16818', 'core.pkg.js' => '13c7e56a', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3fb7f532', @@ -126,7 +126,7 @@ return array( 'rsrc/css/phui/phui-box.css' => '5c8387cf', 'rsrc/css/phui/phui-button.css' => '4a5fbe3d', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', - 'rsrc/css/phui/phui-crumbs-view.css' => '6b813619', + 'rsrc/css/phui/phui-crumbs-view.css' => 'b4fa5755', 'rsrc/css/phui/phui-curtain-view.css' => '7148ae25', 'rsrc/css/phui/phui-document-pro.css' => 'a3730b94', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', @@ -830,7 +830,7 @@ return array( 'phui-calendar-list-css' => '5d89cd71', 'phui-calendar-month-css' => 'f3bb2030', 'phui-chart-css' => '6bf6f78e', - 'phui-crumbs-view-css' => '6b813619', + 'phui-crumbs-view-css' => 'b4fa5755', 'phui-curtain-view-css' => '7148ae25', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '715aedfb', diff --git a/webroot/rsrc/css/phui/phui-crumbs-view.css b/webroot/rsrc/css/phui/phui-crumbs-view.css index b47418e240..dd070647fb 100644 --- a/webroot/rsrc/css/phui/phui-crumbs-view.css +++ b/webroot/rsrc/css/phui/phui-crumbs-view.css @@ -24,7 +24,7 @@ .device-phone .phui-crumbs-view, .project-board-nav .phui-crumbs-view { padding-left: 8px; - padding-right: 0; + padding-right: 8px; } .phui-crumbs-view a.phui-crumbs-action-disabled, From f5e801f358afdc7bf7591221ab6edf2d64612cd2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 26 Jul 2016 12:32:56 -0700 Subject: [PATCH 55/76] Clean up spacing on mobile form instructions Summary: This padding is a little off / custom. Normalizes it to the form on mobile. Test Plan: Review some settings forms, save form changes. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16330 --- resources/celerity/map.php | 10 +++++----- webroot/rsrc/css/phui/phui-form-view.css | 6 ++++-- webroot/rsrc/css/phui/phui-two-column-view.css | 5 +++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 13ad834762..ab8a0a6782 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '95b16818', + 'core.pkg.css' => 'efc0f11c', 'core.pkg.js' => '13c7e56a', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3fb7f532', @@ -133,7 +133,7 @@ return array( 'rsrc/css/phui/phui-document.css' => '715aedfb', 'rsrc/css/phui/phui-feed-story.css' => 'aa49845d', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', - 'rsrc/css/phui/phui-form-view.css' => '1b04a437', + 'rsrc/css/phui/phui-form-view.css' => 'fab0a10f', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => '4c7dd8f5', @@ -156,7 +156,7 @@ return array( 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'bc523970', - 'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85', + 'rsrc/css/phui/phui-two-column-view.css' => '11c9ab96', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5', @@ -839,7 +839,7 @@ return array( 'phui-font-icon-base-css' => '6449bce8', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', - 'phui-form-view-css' => '1b04a437', + 'phui-form-view-css' => 'fab0a10f', 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => '4c7dd8f5', 'phui-hovercard' => '1bd28176', @@ -864,7 +864,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => 'bc523970', - 'phui-two-column-view-css' => '9fb86c85', + 'phui-two-column-view-css' => '11c9ab96', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '0c62d7c5', diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 5d36add4e0..a3fdac4574 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -180,13 +180,15 @@ .aphront-form-instructions { width: 60%; margin-left: 20%; - padding: 10px 4px; + padding: 12px 4px; + color: {$darkbluetext}; } .device .aphront-form-instructions, .phui-form-full-width .aphront-form-instructions { - width: 100%; + width: auto; margin: 0; + padding: 12px 8px 8px; } .aphront-form-important { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 3fc3ba6836..be066b788d 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -214,6 +214,11 @@ margin: 16px; } +.device .phui-two-column-view .phui-box-blue-property + .phui-header-shell + .phui-info-view { + margin: 8px; +} + /* Navigation */ .phui-two-column-view .side-has-nav .phabricator-nav-local { From c6ba4272d7f840661f51683bdb201b1a881b2c4f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 26 Jul 2016 12:49:12 -0700 Subject: [PATCH 56/76] Clean up mobile spacing on info views in a column Summary: When we have an info view in a column, the css isn't specific enough to override the core info-view css. Test Plan: Review an importing repository, see info view properly spaced. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16331 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/phui/phui-two-column-view.css | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ab8a0a6782..426fd8ed9d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -156,7 +156,7 @@ return array( 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'bc523970', - 'rsrc/css/phui/phui-two-column-view.css' => '11c9ab96', + 'rsrc/css/phui/phui-two-column-view.css' => '5afdf637', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5', @@ -864,7 +864,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => 'bc523970', - 'phui-two-column-view-css' => '11c9ab96', + 'phui-two-column-view-css' => '5afdf637', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '0c62d7c5', diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index be066b788d..41cfd2db21 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -203,6 +203,11 @@ padding: 16px; } +.device .phui-two-column-view .phui-info-view { + margin: 0 0 20px 0; + padding: 12px; +} + .phui-two-column-view .phui-side-column .phui-object-item-empty .phui-info-view { margin-bottom: 0; From e5256bd815844d34eb0bd7df0c964b0992f0e526 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 26 Jul 2016 11:20:09 -0700 Subject: [PATCH 57/76] Hide time controls when editing all-day Calendar events Summary: Ref T11326. When an event is all-day, hide the time controls for the start/end dates. These aren't used and aren't helpful/useful. This got a little more complicated than it used to be because EditEngine forms may have only some of these controls present. Test Plan: Edited an all-day event; edited a normal event; swapped an event between normal and all-day. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16327 --- .../PhabricatorCalendarEventEditEngine.php | 41 +++++++++++++++++++ .../editengine/PhabricatorEditEngine.php | 6 +++ .../editfield/PhabricatorEpochEditField.php | 15 +++++-- .../calendar/behavior-event-all-day.js | 15 +++---- 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php index 3e97cb2f5e..5fa6e95732 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -212,4 +212,45 @@ final class PhabricatorCalendarEventEditEngine return $fields; } + protected function willBuildEditForm($object, array $fields) { + $all_day_field = idx($fields, 'isAllDay'); + $start_field = idx($fields, 'start'); + $end_field = idx($fields, 'end'); + + if ($all_day_field) { + $is_all_day = $all_day_field->getValueForTransaction(); + + $control_ids = array(); + if ($start_field) { + $control_ids[] = $start_field->getControlID(); + } + if ($end_field) { + $control_ids[] = $end_field->getControlID(); + } + + Javelin::initBehavior( + 'event-all-day', + array( + 'allDayID' => $all_day_field->getControlID(), + 'controlIDs' => $control_ids, + )); + + } else { + $is_all_day = $object->getIsAllDay(); + } + + if ($is_all_day) { + if ($start_field) { + $start_field->setHideTime(true); + } + + if ($end_field) { + $end_field->setHideTime(true); + } + } + + + + return $fields; + } } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 181369a58f..2805162336 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1176,6 +1176,8 @@ abstract class PhabricatorEditEngine $controller = $this->getController(); $request = $controller->getRequest(); + $fields = $this->willBuildEditForm($object, $fields); + $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('editEngine', 'true'); @@ -1210,6 +1212,10 @@ abstract class PhabricatorEditEngine return $form; } + protected function willBuildEditForm($object, array $fields) { + return $fields; + } + private function buildEditFormActionButton($object) { if (!$this->isEngineConfigurable()) { return null; diff --git a/src/applications/transactions/editfield/PhabricatorEpochEditField.php b/src/applications/transactions/editfield/PhabricatorEpochEditField.php index 36c7be861c..9ac9726593 100644 --- a/src/applications/transactions/editfield/PhabricatorEpochEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEpochEditField.php @@ -4,6 +4,7 @@ final class PhabricatorEpochEditField extends PhabricatorEditField { private $allowNull; + private $hideTime; public function setAllowNull($allow_null) { $this->allowNull = $allow_null; @@ -14,9 +15,19 @@ final class PhabricatorEpochEditField return $this->allowNull; } + public function setHideTime($hide_time) { + $this->hideTime = $hide_time; + return $this; + } + + public function getHideTime() { + return $this->hideTime; + } + protected function newControl() { return id(new AphrontFormDateControl()) ->setAllowNull($this->getAllowNull()) + ->setIsTimeDisabled($this->getHideTime()) ->setViewer($this->getViewer()); } @@ -26,9 +37,7 @@ final class PhabricatorEpochEditField } protected function newConduitParameterType() { - // TODO: This isn't correct, but we don't have any methods which use this - // yet. - return new ConduitIntParameterType(); + return new ConduitEpochParameterType(); } } diff --git a/webroot/rsrc/js/application/calendar/behavior-event-all-day.js b/webroot/rsrc/js/application/calendar/behavior-event-all-day.js index a8bd7da7a8..1121759e3c 100644 --- a/webroot/rsrc/js/application/calendar/behavior-event-all-day.js +++ b/webroot/rsrc/js/application/calendar/behavior-event-all-day.js @@ -2,15 +2,16 @@ * @provides javelin-behavior-event-all-day */ - JX.behavior('event-all-day', function(config) { - var checkbox = JX.$(config.allDayID); - JX.DOM.listen(checkbox, 'change', null, function() { - var start = JX.$(config.startDateID); - var end = JX.$(config.endDateID); + var all_day = JX.$(config.allDayID); - JX.DOM.alterClass(start, 'no-time', checkbox.checked); - JX.DOM.alterClass(end, 'no-time', checkbox.checked); + JX.DOM.listen(all_day, 'change', null, function() { + var is_all_day = !!parseInt(all_day.value, 10); + + for (var ii = 0; ii < config.controlIDs.length; ii++) { + var control = JX.$(config.controlIDs[ii]); + JX.DOM.alterClass(control, 'no-time', is_all_day); + } }); }); From cd8eccde8a89e469a9671167e0b682263b1094ba Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 Jul 2016 05:07:38 -0700 Subject: [PATCH 58/76] Cruncy legumes. Auditors: chad --- resources/celerity/map.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 426fd8ed9d..e0f7c26960 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -363,7 +363,7 @@ return array( 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', - 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', + 'rsrc/js/application/calendar/behavior-event-all-day.js' => '937bb700', 'rsrc/js/application/calendar/behavior-recurring-edit.js' => '5f1c4d5f', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', @@ -629,7 +629,7 @@ return array( 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', - 'javelin-behavior-event-all-day' => '38dcf3c8', + 'javelin-behavior-event-all-day' => '937bb700', 'javelin-behavior-fancy-datepicker' => '568931f3', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', From aee9d88c17e25b924ecd5293691cc6fe38d6c7d9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 Jul 2016 06:44:42 -0700 Subject: [PATCH 59/76] Give the Calendar month view a nice hover effect Summary: Ref T11326. Currently, we link Calendar days using hidden DOM nodes. This is nice because it's simple, and right-clicking a day works properly. However, it's a bit ugly/unintuitive, messy, and unclear. It's especially messy because days are really two different rows, one for events and one for day/week numbers. Instead, use JS to highlight day cells. You can still right-click by clicking the actual day number, which seems like a reasonable compromise. Test Plan: {F1738941} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16334 --- resources/celerity/map.php | 10 +- .../phui/calendar/PHUICalendarListView.php | 6 +- .../phui/calendar/PHUICalendarMonthView.php | 99 ++++++++++--------- .../css/phui/calendar/phui-calendar-month.css | 18 ++-- .../rsrc/css/phui/calendar/phui-calendar.css | 6 ++ .../calendar/behavior-month-view.js | 89 +++++++++++++++++ 6 files changed, 172 insertions(+), 56 deletions(-) create mode 100644 webroot/rsrc/js/application/calendar/behavior-month-view.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e0f7c26960..7905323a81 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -117,8 +117,8 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'f15bb6d6', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '5d89cd71', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'f3bb2030', - 'rsrc/css/phui/calendar/phui-calendar.css' => '3354bbd6', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '29a5ef75', + 'rsrc/css/phui/calendar/phui-calendar.css' => 'daadaf39', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => '3baef8db', @@ -364,6 +364,7 @@ return array( 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '937bb700', + 'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256', 'rsrc/js/application/calendar/behavior-recurring-edit.js' => '5f1c4d5f', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', @@ -590,6 +591,7 @@ return array( 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-badge-view' => '8ff5e24c', 'javelin-behavior-bulk-job-reload' => 'edf8a145', + 'javelin-behavior-calendar-month-view' => 'fe33e256', 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => '06460e71', 'javelin-behavior-config-reorder-fields' => 'b6993408', @@ -825,10 +827,10 @@ return array( 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '5c8387cf', 'phui-button-css' => '4a5fbe3d', - 'phui-calendar-css' => '3354bbd6', + 'phui-calendar-css' => 'daadaf39', 'phui-calendar-day-css' => 'f15bb6d6', 'phui-calendar-list-css' => '5d89cd71', - 'phui-calendar-month-css' => 'f3bb2030', + 'phui-calendar-month-css' => '29a5ef75', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => 'b4fa5755', 'phui-curtain-view-css' => '7148ae25', diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index cff2dcf8a5..d84c0b0bc3 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -32,7 +32,11 @@ final class PHUICalendarListView extends AphrontTagView { protected function getTagAttributes() { require_celerity_resource('phui-calendar-css'); require_celerity_resource('phui-calendar-list-css'); - return array('class' => 'phui-calendar-event-list'); + + return array( + 'sigil' => 'calendar-event-list', + 'class' => 'phui-calendar-event-list', + ); } protected function getTagContent() { diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index f3f90c5a14..7f60ffd740 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -53,6 +53,8 @@ final class PHUICalendarMonthView extends AphrontView { public function render() { $viewer = $this->getViewer(); + Javelin::initBehavior('calendar-month-view'); + $events = msort($this->events, 'getEpochStart'); $days = $this->getDatesInMonth(); @@ -111,32 +113,50 @@ final class PHUICalendarMonthView extends AphrontView { $day->format('m').'/'. $day->format('d').'/'; - $cell_lists[] = array( + $day_id = $day->format('Ymd'); + + $cell_lists[$day_id] = array( + 'dayID' => $day_id, 'list' => $list, 'date' => $day, - 'uri' => $uri, + 'dayURI' => $uri, 'count' => count($all_day_events) + count($list_events), 'class' => $class, - ); + ); } $rows = array(); - $cell_lists_by_week = array_chunk($cell_lists, 7); - + $cell_lists_by_week = array_chunk($cell_lists, 7, true); foreach ($cell_lists_by_week as $week_of_cell_lists) { $cells = array(); - $max_count = $this->getMaxDailyEventsForWeek($week_of_cell_lists); + $action_map = array(); + foreach ($week_of_cell_lists as $day_id => $cell_list) { + $cells[] = $this->getEventListCell($cell_list); - foreach ($week_of_cell_lists as $cell_list) { - $cells[] = $this->getEventListCell($cell_list, $max_count); + $action_map[$day_id] = array( + 'dayURI' => $cell_list['dayURI'], + ); } - $rows[] = phutil_tag('tr', array(), $cells); + $rows[] = javelin_tag( + 'tr', + array( + 'sigil' => 'calendar-week calendar-week-body', + 'meta' => array( + 'actionMap' => $action_map, + ), + ), + $cells); $cells = array(); - foreach ($week_of_cell_lists as $cell_list) { + foreach ($week_of_cell_lists as $day_id => $cell_list) { $cells[] = $this->getDayNumberCell($cell_list); } - $rows[] = phutil_tag('tr', array(), $cells); + $rows[] = javelin_tag( + 'tr', + array( + 'sigil' => 'calendar-week calendar-week-foot', + ), + $cells); } $header = $this->getDayNamesHeader(); @@ -175,51 +195,53 @@ final class PHUICalendarMonthView extends AphrontView { return $max_count; } - private function getEventListCell($event_list, $max_count = 0) { + private function getEventListCell($event_list) { $list = $event_list['list']; $class = $event_list['class']; - $uri = $event_list['uri']; $count = $event_list['count']; $viewer_is_invited = $list->getIsViewerInvitedOnList(); - $event_count_badge = $this->getEventCountBadge($count, $viewer_is_invited); - $cell_day_secret_link = $this->getHiddenDayLink($uri, $max_count, 125); - $cell_data_div = phutil_tag( + $cell_content = phutil_tag( 'div', array( 'class' => 'phui-calendar-month-cell-div', ), array( - $cell_day_secret_link, $event_count_badge, $list, )); - return phutil_tag( + $cell_meta = array( + 'dayID' => $event_list['dayID'], + ); + + $classes = array(); + $classes[] = 'phui-calendar-month-event-list'; + $classes[] = $event_list['class']; + $classes = implode(' ', $classes); + + return javelin_tag( 'td', array( - 'class' => 'phui-calendar-month-event-list '.$class, + 'class' => $classes, + 'meta' => $cell_meta, ), - $cell_data_div); + $cell_content); } private function getDayNumberCell($event_list) { $class = $event_list['class']; $date = $event_list['date']; - $cell_day_secret_link = null; $week_number = null; if ($date) { - $uri = $event_list['uri']; - $cell_day_secret_link = $this->getHiddenDayLink($uri, 0, 25); - $cell_day = phutil_tag( 'a', array( 'class' => 'phui-calendar-date-number', - 'href' => $uri, + 'href' => $event_list['dayURI'], ), $date->format('j')); @@ -228,7 +250,7 @@ final class PHUICalendarMonthView extends AphrontView { 'a', array( 'class' => 'phui-calendar-week-number', - 'href' => $uri, + 'href' => $event_list['dayURI'], ), $date->format('W')); } @@ -256,14 +278,13 @@ final class PHUICalendarMonthView extends AphrontView { 'class' => 'phui-calendar-month-cell-div', ), array( - $cell_day_secret_link, $week_number, $cell_day, $today_slot, )); $classes = array(); - $classes[] = 'phui-calendar-date-number-container'; + $classes[] = 'phui-calendar-month-number'; if ($date) { if ($this->isDateInCurrentWeek($date)) { @@ -277,10 +298,15 @@ final class PHUICalendarMonthView extends AphrontView { } } - return phutil_tag( + $cell_meta = array( + 'dayID' => $event_list['dayID'], + ); + + return javelin_tag( 'td', array( 'class' => implode(' ', $classes), + 'meta' => $cell_meta, ), $cell_div); } @@ -320,21 +346,6 @@ final class PHUICalendarMonthView extends AphrontView { $event_count); } - private function getHiddenDayLink($uri, $count, $max_height) { - // approximately the height of the tallest cell - $height = 18 * $count + 5; - $height = ($height > $max_height) ? $height : $max_height; - $height_style = 'height: '.$height.'px'; - return phutil_tag( - 'a', - array( - 'class' => 'phui-calendar-month-secret-link', - 'style' => $height_style, - 'href' => $uri, - ), - null); - } - private function getDayNamesHeader() { list($week_start, $week_end) = $this->getWeekStartAndEnd(); diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index adaeabadd0..60166a4731 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -50,12 +50,6 @@ table.phui-calendar-view td { min-height: 32px; } -a.phui-calendar-month-secret-link { - position: absolute; - left: 0; - right: 0; -} - table.phui-calendar-view tr td:first-child { border-left-width: 0px; } @@ -113,7 +107,7 @@ table.phui-calendar-view a.phui-calendar-date-number { width: 12px; } -table.phui-calendar-view td.phui-calendar-date-number-container { +table.phui-calendar-view td.phui-calendar-month-number { font-weight: normal; color: {$lightgreytext}; border-width: 0 1px 0 1px; @@ -215,3 +209,13 @@ li.phui-calendar-viewer-invited.all-day { color: {$lightgreytext}; text-align: right; } + +td.phui-calendar-month-day, +td.phui-calendar-month-number { + cursor: pointer; +} + +.device-desktop td.phui-calendar-month-day.calendar-hover, +.device-desktop td.phui-calendar-month-number.calendar-hover { + background: {$lightblue}; +} diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar.css b/webroot/rsrc/css/phui/calendar/phui-calendar.css index 637a479aa8..6c369b21df 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar.css @@ -2,6 +2,12 @@ * @provides phui-calendar-css */ +.phui-calendar-list { + /* When hovering over a day, this allows the hover color to peek through + the event name, but for event names to mostly remain readable. */ + background: rgba(255, 255, 255, 0.75); +} + .phui-calendar-list a { color: {$greytext}; } diff --git a/webroot/rsrc/js/application/calendar/behavior-month-view.js b/webroot/rsrc/js/application/calendar/behavior-month-view.js new file mode 100644 index 0000000000..914b3b929f --- /dev/null +++ b/webroot/rsrc/js/application/calendar/behavior-month-view.js @@ -0,0 +1,89 @@ +/** + * @provides javelin-behavior-calendar-month-view + */ +JX.behavior('calendar-month-view', function() { + + var hover_nodes = []; + + function get_info(e) { + var week_body = e.getNode('calendar-week-body'); + if (!week_body) { + week_body = e.getNode('calendar-week-foot').previousSibling; + } + + var week_foot = week_body.nextSibling; + var day_id = JX.Stratcom.getData(e.getNode('tag:td')).dayID; + + var day_body; + var day_foot; + var body_nodes = JX.DOM.scry(week_body, 'td'); + var foot_nodes = JX.DOM.scry(week_foot, 'td'); + for (var ii = 0; ii < body_nodes.length; ii++) { + if (JX.Stratcom.getData(body_nodes[ii]).dayID == day_id) { + day_body = body_nodes[ii]; + day_foot = foot_nodes[ii]; + break; + } + } + + return { + data: JX.Stratcom.getData(week_body), + dayID: day_id, + nodes: { + week: { + body: week_body, + foot: week_foot + }, + day: { + body: day_body, + foot: day_foot + } + } + }; + } + + function alter_hover(enable) { + for (var ii = 0; ii < hover_nodes.length; ii++) { + JX.DOM.alterClass(hover_nodes[ii], 'calendar-hover', enable); + } + } + + JX.enableDispatch(document.body, 'mouseover'); + JX.enableDispatch(document.body, 'mouseout'); + + JX.Stratcom.listen('mouseover', ['calendar-week', 'tag:td'], function(e) { + if (e.getNode('calendar-event-list')) { + alter_hover(false); + hover_nodes = []; + return; + } + + var info = get_info(e); + hover_nodes = [ + info.nodes.day.body, + info.nodes.day.foot + ]; + + alter_hover(true); + }); + + JX.Stratcom.listen('mouseout', ['calendar-week', 'tag:td'], function() { + alter_hover(false); + }); + + JX.Stratcom.listen('click', ['calendar-week', 'tag:td'], function(e) { + if (!e.isNormalClick()) { + return; + } + + // If this is a click in the event list or on a link, ignore it. This + // allows users to follow links to events and select text. + if (e.getNode('calendar-event-list') || e.getNode('tag:a')) { + return; + } + + var info = get_info(e); + JX.$U(info.data.actionMap[info.dayID].dayURI).go(); + }); + +}); From ba00022730c79a7a0b1c35cceb2775ea854187d4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 Jul 2016 06:58:28 -0700 Subject: [PATCH 60/76] Remove extra margins on Calendar month view Summary: Ref T11326. This doesn't go quite as far as the mock in T11326#185932, but gets rid of the easy margins. Also cleans up some of the border rules so they're simpler and more consistent (no weird ragged edges on the far right). Test Plan: {F1738951} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16335 --- resources/celerity/map.php | 4 ++-- .../PhabricatorCalendarEventSearchEngine.php | 19 ++++++++----------- ...PhabricatorApplicationSearchResultView.php | 4 +--- .../css/phui/calendar/phui-calendar-month.css | 15 +++++++++------ 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 7905323a81..f6e2c41457 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -117,7 +117,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'f15bb6d6', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '5d89cd71', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => '29a5ef75', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '31cec731', 'rsrc/css/phui/calendar/phui-calendar.css' => 'daadaf39', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -830,7 +830,7 @@ return array( 'phui-calendar-css' => 'daadaf39', 'phui-calendar-day-css' => 'f15bb6d6', 'phui-calendar-list-css' => '5d89cd71', - 'phui-calendar-month-css' => '29a5ef75', + 'phui-calendar-month-css' => '31cec731', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => 'b4fa5755', 'phui-curtain-view-css' => '7148ae25', diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 86d298d069..d82e78d5b3 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -255,7 +255,7 @@ final class PhabricatorCalendarEventSearchEngine array $handles) { if ($this->isMonthView($query)) { - return $this->buildCalendarView($events, $query); + return $this->buildCalendarMonthView($events, $query); } else if ($this->isDayView($query)) { return $this->buildCalendarDayView($events, $query); } @@ -307,7 +307,7 @@ final class PhabricatorCalendarEventSearchEngine return $result; } - private function buildCalendarView( + private function buildCalendarMonthView( array $events, PhabricatorSavedQuery $query) { assert_instances_of($events, 'PhabricatorCalendarEvent'); @@ -362,11 +362,9 @@ final class PhabricatorCalendarEventSearchEngine $month_view->setBrowseURI( $this->getURI('query/'.$query->getQueryKey().'/')); - // TODO redesign-2015 : Move buttons out of PHUICalendarView? - $result = new PhabricatorApplicationSearchResultView(); - $result->setContent($month_view); - - return $result; + return id(new PhabricatorApplicationSearchResultView()) + ->setContent($month_view) + ->setCollapsed(true); } private function buildCalendarDayView( @@ -422,10 +420,9 @@ final class PhabricatorCalendarEventSearchEngine $day_view->setBrowseURI( $this->getURI('query/'.$query->getQueryKey().'/')); - $result = new PhabricatorApplicationSearchResultView(); - $result->setContent($day_view); - - return $result; + return id(new PhabricatorApplicationSearchResultView()) + ->setContent($day_view) + ->setCollapsed(true); } private function getDisplayYearAndMonthAndDay( diff --git a/src/applications/search/view/PhabricatorApplicationSearchResultView.php b/src/applications/search/view/PhabricatorApplicationSearchResultView.php index a6b8aa3b8b..45a396898b 100644 --- a/src/applications/search/view/PhabricatorApplicationSearchResultView.php +++ b/src/applications/search/view/PhabricatorApplicationSearchResultView.php @@ -1,13 +1,11 @@ Date: Wed, 27 Jul 2016 07:23:07 -0700 Subject: [PATCH 61/76] Give Calendar days from adjacent months a background color hint Summary: Ref T11326. When viewing "February", add a class to dates in January and March to let them be styled a little differently as a UI hint. For now, I've given them a grey background. (Calendar.app changes the date number color instead.) Test Plan: {F1738990} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16336 --- resources/celerity/map.php | 4 ++-- src/view/phui/calendar/PHUICalendarMonthView.php | 12 ++++++++++-- .../rsrc/css/phui/calendar/phui-calendar-month.css | 4 ++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f6e2c41457..3acfc7fd19 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -117,7 +117,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'f15bb6d6', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '5d89cd71', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => '31cec731', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c', 'rsrc/css/phui/calendar/phui-calendar.css' => 'daadaf39', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -830,7 +830,7 @@ return array( 'phui-calendar-css' => 'daadaf39', 'phui-calendar-day-css' => 'f15bb6d6', 'phui-calendar-list-css' => '5d89cd71', - 'phui-calendar-month-css' => '31cec731', + 'phui-calendar-month-css' => '8e10e92c', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => 'b4fa5755', 'phui-curtain-view-css' => '7148ae25', diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 7f60ffd740..d7efeac97f 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -65,7 +65,6 @@ final class PHUICalendarMonthView extends AphrontView { foreach ($days as $day) { $day_number = $day->format('j'); - $class = 'phui-calendar-month-day'; $weekday = $day->format('w'); $day->setTime(0, 0, 0); @@ -115,13 +114,20 @@ final class PHUICalendarMonthView extends AphrontView { $day_id = $day->format('Ymd'); + + $classes = array(); + if ($day->format('m') != $this->month) { + $classes[] = 'phui-calendar-month-adjacent'; + } + $classes = implode(' ', $classes); + $cell_lists[$day_id] = array( 'dayID' => $day_id, 'list' => $list, 'date' => $day, 'dayURI' => $uri, 'count' => count($all_day_events) + count($list_events), - 'class' => $class, + 'class' => $classes, ); } @@ -219,6 +225,7 @@ final class PHUICalendarMonthView extends AphrontView { $classes = array(); $classes[] = 'phui-calendar-month-event-list'; + $classes[] = 'phui-calendar-month-day'; $classes[] = $event_list['class']; $classes = implode(' ', $classes); @@ -285,6 +292,7 @@ final class PHUICalendarMonthView extends AphrontView { $classes = array(); $classes[] = 'phui-calendar-month-number'; + $classes[] = $event_list['class']; if ($date) { if ($this->isDateInCurrentWeek($date)) { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index abc4146e01..c649021f0d 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -222,3 +222,7 @@ td.phui-calendar-month-number { .device-desktop td.phui-calendar-month-number.calendar-hover { background: {$lightblue}; } + +.phui-calendar-month-adjacent { + background: {$greybackground}; +} From ef6c689e87624d74ebf1c0cb77fc69604440af79 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 Jul 2016 08:23:27 -0700 Subject: [PATCH 62/76] When a Calendar day has too many events, show a "More..." link Summary: Fixes T8361. Test Plan: {F1739073} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8361 Differential Revision: https://secure.phabricator.com/D16337 --- .../phui/calendar/PHUICalendarListView.php | 37 ++++++++++++++++++- .../phui/calendar/PHUICalendarMonthView.php | 9 +++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index d84c0b0bc3..88bbde351a 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -5,6 +5,16 @@ final class PHUICalendarListView extends AphrontTagView { private $events = array(); private $blankState; private $view; + private $moreLink; + + public function setMoreLink($more_link) { + $this->moreLink = $more_link; + return $this; + } + + public function getMoreLink() { + return $this->moreLink; + } private function getView() { return $this->view; @@ -60,7 +70,7 @@ final class PHUICalendarListView extends AphrontTagView { $icon_icon = $event->getIcon(); $icon_color = $event->getIconColor(); - $dot = id(new PHUIIconView()) + $icon = id(new PHUIIconView()) ->setIcon($icon_icon, $icon_color) ->addClass('phui-calendar-list-item-icon'); @@ -108,7 +118,7 @@ final class PHUICalendarListView extends AphrontTagView { ), ), array( - $dot, + $icon, $time, $title, )); @@ -121,6 +131,29 @@ final class PHUICalendarListView extends AphrontTagView { $content); } + if ($this->moreLink) { + $singletons[] = phutil_tag( + 'li', + array( + 'class' => 'phui-calendar-list-item', + ), + phutil_tag( + 'a', + array( + 'href' => $this->moreLink, + 'class' => 'phui-calendar-list-more', + ), + array( + id(new PHUIIconView())->setIcon('fa-ellipsis-h grey'), + phutil_tag( + 'span', + array( + 'class' => 'phui-calendar-list-title', + ), + pht('View More...')), + ))); + } + if (empty($singletons)) { $singletons[] = phutil_tag( 'li', diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index d7efeac97f..0cc3d9b815 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -95,13 +95,13 @@ final class PHUICalendarMonthView extends AphrontView { ->setViewer($viewer) ->setView('month'); foreach ($all_day_events as $item) { - if ($counter <= $max_daily) { + if ($counter < $max_daily) { $list->addEvent($item); } $counter++; } foreach ($list_events as $item) { - if ($counter <= $max_daily) { + if ($counter < $max_daily) { $list->addEvent($item); } $counter++; @@ -112,8 +112,11 @@ final class PHUICalendarMonthView extends AphrontView { $day->format('m').'/'. $day->format('d').'/'; - $day_id = $day->format('Ymd'); + if ($counter > $max_daily) { + $list->setMoreLink($uri); + } + $day_id = $day->format('Ymd'); $classes = array(); if ($day->format('m') != $this->month) { From eab74a9d7cd79e1c723472f27d8b2ef31b4928c6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 Jul 2016 09:07:29 -0700 Subject: [PATCH 63/76] Provide better headers and crumbs for Calendar result views Summary: Ref T11326. This isn't perfect, but should be a little easier to use and less weird/confusing. Generally, provide a "Query > Month > Day" crumb on day views, and a "Wed, July 3" header. Generally, provide a "Query > Month" crumb on month views, and a "July 2019" header. Also try to fix a bit of padding/spacing on the day view. Test Plan: {F1739128} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16338 --- resources/celerity/map.php | 8 +-- .../PhabricatorCalendarEventSearchEngine.php | 49 +++++++++++++++++-- ...PhabricatorApplicationSearchController.php | 23 +++++++-- ...PhabricatorApplicationSearchResultView.php | 24 +++++++++ .../control/AphrontFormDateControlValue.php | 21 -------- .../phui/calendar/PHUICalendarMonthView.php | 3 +- .../css/phui/calendar/phui-calendar-day.css | 1 - .../css/phui/calendar/phui-calendar-list.css | 18 +++++++ 8 files changed, 112 insertions(+), 35 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3acfc7fd19..b9eac87871 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -115,8 +115,8 @@ return array( 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'dd849797', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', - 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'f15bb6d6', - 'rsrc/css/phui/calendar/phui-calendar-list.css' => '5d89cd71', + 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', + 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'fcc9fb41', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c', 'rsrc/css/phui/calendar/phui-calendar.css' => 'daadaf39', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', @@ -828,8 +828,8 @@ return array( 'phui-box-css' => '5c8387cf', 'phui-button-css' => '4a5fbe3d', 'phui-calendar-css' => 'daadaf39', - 'phui-calendar-day-css' => 'f15bb6d6', - 'phui-calendar-list-css' => '5d89cd71', + 'phui-calendar-day-css' => '572b1893', + 'phui-calendar-list-css' => 'fcc9fb41', 'phui-calendar-month-css' => '8e10e92c', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => 'b4fa5755', diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index d82e78d5b3..f093130bb4 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -362,7 +362,18 @@ final class PhabricatorCalendarEventSearchEngine $month_view->setBrowseURI( $this->getURI('query/'.$query->getQueryKey().'/')); + $from = $this->getQueryDateFrom($query)->getDateTime(); + + $crumbs = array(); + $crumbs[] = id(new PHUICrumbView()) + ->setName($from->format('F Y')); + + $header = id(new PHUIHeaderView()) + ->setHeader($from->format('F Y')); + return id(new PhabricatorApplicationSearchResultView()) + ->setCrumbs($crumbs) + ->setHeader($header) ->setContent($month_view) ->setCollapsed(true); } @@ -380,8 +391,8 @@ final class PhabricatorCalendarEventSearchEngine $query->getParameter('display')); $day_view = id(new PHUICalendarDayView( - $this->getQueryDateFrom($query)->getEpoch(), - $this->getQueryDateTo($query)->getEpoch(), + $this->getQueryDateFrom($query), + $this->getQueryDateTo($query), $start_year, $start_month, $start_day)) @@ -417,10 +428,26 @@ final class PhabricatorCalendarEventSearchEngine $day_view->addEvent($event_view); } - $day_view->setBrowseURI( - $this->getURI('query/'.$query->getQueryKey().'/')); + $browse_uri = $this->getURI('query/'.$query->getQueryKey().'/'); + $day_view->setBrowseURI($browse_uri); + + $from = $this->getQueryDateFrom($query)->getDateTime(); + $month_uri = $browse_uri.$from->format('Y/m/'); + + $crumbs = array( + id(new PHUICrumbView()) + ->setName($from->format('F Y')) + ->setHref($month_uri), + id(new PHUICrumbView()) + ->setName($from->format('D jS')), + ); + + $header = id(new PHUIHeaderView()) + ->setHeader($from->format('D, F jS')); return id(new PhabricatorApplicationSearchResultView()) + ->setCrumbs($crumbs) + ->setHeader($header) ->setContent($day_view) ->setCollapsed(true); } @@ -466,6 +493,20 @@ final class PhabricatorCalendarEventSearchEngine } private function getQueryDateFrom(PhabricatorSavedQuery $saved) { + if ($this->calendarYear && $this->calendarMonth) { + $viewer = $this->requireViewer(); + + $start_year = $this->calendarYear; + $start_month = $this->calendarMonth; + $start_day = $this->calendarDay ? $this->calendarDay : 1; + + return AphrontFormDateControlValue::newFromDictionary( + $viewer, + array( + 'd' => "{$start_year}-{$start_month}-{$start_day}", + )); + } + return $this->getQueryDate($saved, 'rangeStart'); } diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 688a7db527..b9ed90e80e 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -210,7 +210,7 @@ final class PhabricatorApplicationSearchController } $body[] = $box; - + $more_crumbs = null; if ($run_query) { $exec_errors = array(); @@ -272,6 +272,13 @@ final class PhabricatorApplicationSearchController $box->setCollapsed(true); } + $result_header = $list->getHeader(); + if ($result_header) { + $box->setHeader($result_header); + } + + $more_crumbs = $list->getCrumbs(); + if ($pager->willShowPagingControls()) { $pager_box = id(new PHUIBoxView()) ->addPadding(PHUI::PADDING_MEDIUM) @@ -301,8 +308,18 @@ final class PhabricatorApplicationSearchController } $crumbs = $parent - ->buildApplicationCrumbs() - ->addTextCrumb($title); + ->buildApplicationCrumbs(); + + if ($more_crumbs) { + $query_uri = $engine->getQueryResultsPageURI($saved_query->getQueryKey()); + $crumbs->addTextCrumb($title, $query_uri); + + foreach ($more_crumbs as $crumb) { + $crumbs->addCrumb($crumb); + } + } else { + $crumbs->addTextCrumb($title); + } return $this->newPage() ->setApplicationMenu($this->buildApplicationMenu()) diff --git a/src/applications/search/view/PhabricatorApplicationSearchResultView.php b/src/applications/search/view/PhabricatorApplicationSearchResultView.php index 45a396898b..5bcfdcf675 100644 --- a/src/applications/search/view/PhabricatorApplicationSearchResultView.php +++ b/src/applications/search/view/PhabricatorApplicationSearchResultView.php @@ -14,6 +14,8 @@ final class PhabricatorApplicationSearchResultView extends Phobject { private $actions = array(); private $collapsed = null; private $noDataString; + private $crumbs = array(); + private $header; public function setObjectList(PHUIObjectItemListView $list) { $this->objectList = $list; @@ -82,4 +84,26 @@ final class PhabricatorApplicationSearchResultView extends Phobject { return $this; } + public function setCrumbs(array $crumbs) { + assert_instances_of($crumbs, 'PHUICrumbView'); + + $this->crumbs = $crumbs; + return $this; + } + + public function getCrumbs() { + return $this->crumbs; + } + + public function setHeader(PHUIHeaderView $header) { + $this->header = $header; + return $this; + } + + public function getHeader() { + return $this->header; + } + + + } diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index aeb35cdb15..f9dc14b238 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -59,27 +59,6 @@ final class AphrontFormDateControlValue extends Phobject { return $this->viewer; } - public static function newFromParts( - PhabricatorUser $viewer, - $year, - $month, - $day, - $time = null, - $enabled = true) { - - $value = new AphrontFormDateControlValue(); - $value->viewer = $viewer; - list($value->valueDate, $value->valueTime) = - $value->getFormattedDateFromParts( - $year, - $month, - $day, - coalesce($time, '12:00 AM')); - $value->valueEnabled = $enabled; - - return $value; - } - public static function newFromRequest(AphrontRequest $request, $key) { $value = new AphrontFormDateControlValue(); $value->viewer = $request->getViewer(); diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 0cc3d9b815..61fd8b1e99 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -445,8 +445,7 @@ final class PHUICalendarMonthView extends AphrontView { } - $header = id(new PHUIHeaderView()) - ->setHeader($date->format('F Y')); + $header = id(new PHUIHeaderView()); if ($button_bar) { $header->setButtonBar($button_bar); diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css index acb577675a..1c97c42265 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -92,7 +92,6 @@ div.phui-calendar-day-event.all-day { background-color: {$bluebackground}; color: {$greytext}; text-decoration: none; - text-decoration: none; } .day-view-all-day { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css index 9cf5e3bcd8..d5a4a89dc9 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css @@ -66,4 +66,22 @@ .phui-calendar-list-item-empty { color: {$lightgreytext}; + padding: 0 12px; + font-style: italic; +} + +.phui-calendar-list-item.all-day { + background: {$bluebackground}; +} + +.calendar-day-view-sidebar .phui-calendar-list { + padding: 12px 0; +} + +.calendar-day-view-sidebar .phui-calendar-list-item { + padding: 0 12px; +} + +.calendar-day-view-sidebar .phui-calendar-list-item a { + position: relative; } From 9160da1afb62d3506add835a9170736ce50cc70e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 21 Jul 2016 08:14:34 -0700 Subject: [PATCH 64/76] Add a Packages application and PackagePublisher Summary: Ref T8116. Partially scavenged from D14152. This roughs in a new Packages application for Arcanist extensions and third-party applications, and adds a "Publisher" object. A "Publisher" represents an individual or entity who is publishing a package, like "Phacility". It's explicitly //not// necessarily the original author -- just the primary entity vouching for the safety of the code. A publisher just has a name and a unique key for now. For example, Phacility might have "Phacility" and "phacility", respectively. Unique keys are immutable, e.g., the package "phacility/arcanist" will always be exactly the same package by exactly the same publisher. Test Plan: {F1731621} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8116 Differential Revision: https://secure.phabricator.com/D16314 --- .../sql/autopatches/20160721.pack.01.pub.sql | 11 + .../20160721.pack.02.pubxaction.sql | 19 ++ .../sql/autopatches/20160721.pack.03.edge.sql | 16 ++ src/__phutil_library_map__.php | 56 +++++ .../PhabricatorPackagesApplication.php | 47 +++++ ...rPackagesPublisherEditConduitAPIMethod.php | 19 ++ ...ackagesPublisherSearchConduitAPIMethod.php | 18 ++ .../PhabricatorPackagesController.php | 3 + ...PhabricatorPackagesPublisherController.php | 4 + ...ricatorPackagesPublisherEditController.php | 12 ++ ...ricatorPackagesPublisherListController.php | 26 +++ ...ricatorPackagesPublisherViewController.php | 84 ++++++++ .../editor/PhabricatorPackagesEditEngine.php | 14 ++ .../editor/PhabricatorPackagesEditor.php | 10 + ...PhabricatorPackagesPublisherEditEngine.php | 91 +++++++++ .../PhabricatorPackagesPublisherEditor.php | 51 +++++ .../PhabricatorPackagesPublisherPHIDType.php | 45 +++++ .../PhabricatorPackagesPublisherQuery.php | 64 ++++++ ...abricatorPackagesPublisherSearchEngine.php | 77 +++++++ ...catorPackagesPublisherTransactionQuery.php | 10 + .../storage/PhabricatorPackagesDAO.php | 9 + .../storage/PhabricatorPackagesPublisher.php | 191 ++++++++++++++++++ ...habricatorPackagesPublisherTransaction.php | 18 ++ .../storage/PhabricatorPackagesSchemaSpec.php | 10 + .../PhabricatorPackagesTransactionType.php | 4 + ...ricatorPackagesPublisherKeyTransaction.php | 44 ++++ ...icatorPackagesPublisherNameTransaction.php | 53 +++++ ...icatorPackagesPublisherTransactionType.php | 4 + .../patch/PhabricatorBuiltinPatchList.php | 1 + 29 files changed, 1011 insertions(+) create mode 100644 resources/sql/autopatches/20160721.pack.01.pub.sql create mode 100644 resources/sql/autopatches/20160721.pack.02.pubxaction.sql create mode 100644 resources/sql/autopatches/20160721.pack.03.edge.sql create mode 100644 src/applications/packages/application/PhabricatorPackagesApplication.php create mode 100644 src/applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php create mode 100644 src/applications/packages/conduit/PhabricatorPackagesPublisherSearchConduitAPIMethod.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPublisherController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPublisherEditController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPublisherListController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesEditEngine.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesEditor.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php create mode 100644 src/applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPublisherQuery.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesDAO.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesPublisher.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesPublisherTransaction.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesSchemaSpec.php create mode 100644 src/applications/packages/xaction/PhabricatorPackagesTransactionType.php create mode 100644 src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherKeyTransaction.php create mode 100644 src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php create mode 100644 src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php diff --git a/resources/sql/autopatches/20160721.pack.01.pub.sql b/resources/sql/autopatches/20160721.pack.01.pub.sql new file mode 100644 index 0000000000..b123740920 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.01.pub.sql @@ -0,0 +1,11 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_publisher ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, + publisherKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_SORT}, + editPolicy VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_publisher` (publisherKey) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160721.pack.02.pubxaction.sql b/resources/sql/autopatches/20160721.pack.02.pubxaction.sql new file mode 100644 index 0000000000..f42f5ba742 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.02.pubxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_publishertransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + 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) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160721.pack.03.edge.sql b/resources/sql/autopatches/20160721.pack.03.edge.sql new file mode 100644 index 0000000000..d735df50a3 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.03.edge.sql @@ -0,0 +1,16 @@ +CREATE TABLE {$NAMESPACE}_packages.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}_packages.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2c72ce85da..fed5a1c75f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2972,6 +2972,30 @@ phutil_register_library_map(array( 'PhabricatorPHPASTApplication' => 'applications/phpast/application/PhabricatorPHPASTApplication.php', 'PhabricatorPHPConfigSetupCheck' => 'applications/config/check/PhabricatorPHPConfigSetupCheck.php', 'PhabricatorPHPMailerConfigOptions' => 'applications/config/option/PhabricatorPHPMailerConfigOptions.php', + 'PhabricatorPackagesApplication' => 'applications/packages/application/PhabricatorPackagesApplication.php', + 'PhabricatorPackagesController' => 'applications/packages/controller/PhabricatorPackagesController.php', + 'PhabricatorPackagesDAO' => 'applications/packages/storage/PhabricatorPackagesDAO.php', + 'PhabricatorPackagesEditEngine' => 'applications/packages/editor/PhabricatorPackagesEditEngine.php', + 'PhabricatorPackagesEditor' => 'applications/packages/editor/PhabricatorPackagesEditor.php', + 'PhabricatorPackagesPublisher' => 'applications/packages/storage/PhabricatorPackagesPublisher.php', + 'PhabricatorPackagesPublisherController' => 'applications/packages/controller/PhabricatorPackagesPublisherController.php', + 'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php', + 'PhabricatorPackagesPublisherEditController' => 'applications/packages/controller/PhabricatorPackagesPublisherEditController.php', + 'PhabricatorPackagesPublisherEditEngine' => 'applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php', + 'PhabricatorPackagesPublisherEditor' => 'applications/packages/editor/PhabricatorPackagesPublisherEditor.php', + 'PhabricatorPackagesPublisherKeyTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherKeyTransaction.php', + 'PhabricatorPackagesPublisherListController' => 'applications/packages/controller/PhabricatorPackagesPublisherListController.php', + 'PhabricatorPackagesPublisherNameTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php', + 'PhabricatorPackagesPublisherPHIDType' => 'applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php', + 'PhabricatorPackagesPublisherQuery' => 'applications/packages/query/PhabricatorPackagesPublisherQuery.php', + 'PhabricatorPackagesPublisherSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherSearchConduitAPIMethod.php', + 'PhabricatorPackagesPublisherSearchEngine' => 'applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php', + 'PhabricatorPackagesPublisherTransaction' => 'applications/packages/storage/PhabricatorPackagesPublisherTransaction.php', + 'PhabricatorPackagesPublisherTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php', + 'PhabricatorPackagesPublisherTransactionType' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php', + 'PhabricatorPackagesPublisherViewController' => 'applications/packages/controller/PhabricatorPackagesPublisherViewController.php', + 'PhabricatorPackagesSchemaSpec' => 'applications/packages/storage/PhabricatorPackagesSchemaSpec.php', + 'PhabricatorPackagesTransactionType' => 'applications/packages/xaction/PhabricatorPackagesTransactionType.php', 'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php', 'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php', 'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php', @@ -7725,6 +7749,38 @@ phutil_register_library_map(array( 'PhabricatorPHPASTApplication' => 'PhabricatorApplication', 'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorPHPMailerConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorPackagesApplication' => 'PhabricatorApplication', + 'PhabricatorPackagesController' => 'PhabricatorController', + 'PhabricatorPackagesDAO' => 'PhabricatorLiskDAO', + 'PhabricatorPackagesEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorPackagesEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorPackagesPublisher' => array( + 'PhabricatorPackagesDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + 'PhabricatorSubscribableInterface', + 'PhabricatorProjectInterface', + 'PhabricatorConduitResultInterface', + ), + 'PhabricatorPackagesPublisherController' => 'PhabricatorPackagesController', + 'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'PhabricatorPackagesPublisherEditController' => 'PhabricatorPackagesPublisherController', + 'PhabricatorPackagesPublisherEditEngine' => 'PhabricatorPackagesEditEngine', + 'PhabricatorPackagesPublisherEditor' => 'PhabricatorPackagesEditor', + 'PhabricatorPackagesPublisherKeyTransaction' => 'PhabricatorPackagesPublisherTransactionType', + 'PhabricatorPackagesPublisherListController' => 'PhabricatorPackagesPublisherController', + 'PhabricatorPackagesPublisherNameTransaction' => 'PhabricatorPackagesPublisherTransactionType', + 'PhabricatorPackagesPublisherPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorPackagesPublisherQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPackagesPublisherSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhabricatorPackagesPublisherSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorPackagesPublisherTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorPackagesPublisherTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorPackagesPublisherTransactionType' => 'PhabricatorPackagesTransactionType', + 'PhabricatorPackagesPublisherViewController' => 'PhabricatorPackagesPublisherController', + 'PhabricatorPackagesSchemaSpec' => 'PhabricatorConfigSchemaSpec', + 'PhabricatorPackagesTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorPagerUIExample' => 'PhabricatorUIExample', 'PhabricatorPassphraseApplication' => 'PhabricatorApplication', 'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider', diff --git a/src/applications/packages/application/PhabricatorPackagesApplication.php b/src/applications/packages/application/PhabricatorPackagesApplication.php new file mode 100644 index 0000000000..0f6b47cbf9 --- /dev/null +++ b/src/applications/packages/application/PhabricatorPackagesApplication.php @@ -0,0 +1,47 @@ + array( + '(?P[^/]+)/' => array( + '' => 'PhabricatorPackagesPublisherViewController', + ), + ), + '/packages/' => array( + 'publisher/' => array( + $this->getQueryRoutePattern() => + 'PhabricatorPackagesPublisherListController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorPackagesPublisherEditController', + ), + ), + ); + } + +} diff --git a/src/applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php b/src/applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php new file mode 100644 index 0000000000..7718363878 --- /dev/null +++ b/src/applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPublisherListController.php b/src/applications/packages/controller/PhabricatorPackagesPublisherListController.php new file mode 100644 index 0000000000..74f05962ef --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPublisherListController.php @@ -0,0 +1,26 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorPackagesPublisherEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php b/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php new file mode 100644 index 0000000000..fad00ad665 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php @@ -0,0 +1,84 @@ +getViewer(); + $publisher_key = $request->getURIData('publisherKey'); + + $publisher = id(new PhabricatorPackagesPublisherQuery()) + ->setViewer($viewer) + ->withPublisherKeys(array($publisher_key)) + ->executeOne(); + if (!$publisher) { + return new Aphront404Response(); + } + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb( + pht('Publishers'), + $this->getApplicationURI('publisher/')) + ->addTextCrumb($publisher->getName()) + ->setBorder(true); + + $header = $this->buildHeaderView($publisher); + $curtain = $this->buildCurtain($publisher); + + $timeline = $this->buildTransactionTimeline( + $publisher, + new PhabricatorPackagesPublisherTransactionQuery()); + + $publisher_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn($timeline); + + return $this->newPage() + ->setCrumbs($crumbs) + ->setPageObjectPHIDs( + array( + $publisher->getPHID(), + )) + ->appendChild($publisher_view); + } + + + private function buildHeaderView(PhabricatorPackagesPublisher $publisher) { + $viewer = $this->getViewer(); + $name = $publisher->getName(); + + return id(new PHUIHeaderView()) + ->setViewer($viewer) + ->setHeader($name) + ->setPolicyObject($publisher) + ->setHeaderIcon('fa-paw'); + } + + private function buildCurtain(PhabricatorPackagesPublisher $publisher) { + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($publisher); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $publisher, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $publisher->getID(); + $edit_uri = $this->getApplicationURI("publisher/edit/{$id}/"); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Publisher')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setHref($edit_uri)); + + return $curtain; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesEditEngine.php new file mode 100644 index 0000000000..1ba71d7274 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesEditEngine.php @@ -0,0 +1,14 @@ +getViewer(); + return PhabricatorPackagesPublisher::initializeNewPublisher($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorPackagesPublisherQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Publisher'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Publisher'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Publisher: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Publisher'); + } + + protected function getObjectCreateShortText() { + return pht('Create Publisher'); + } + + protected function getObjectName() { + return pht('Publisher'); + } + + protected function getEditorURI() { + return '/packages/publisher/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/packages/publisher/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $fields = array(); + + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the publisher.')) + ->setTransactionType( + PhabricatorPackagesPublisherNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('publisherKey') + ->setLabel(pht('Publisher Key')) + ->setDescription(pht('Unique key to identify the publisher.')) + ->setTransactionType( + PhabricatorPackagesPublisherKeyTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getPublisherKey()); + } + + return $fields; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php b/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php new file mode 100644 index 0000000000..6e8fb7caf3 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php @@ -0,0 +1,51 @@ +getPublisherKey()), + null); + + throw new PhabricatorApplicationTransactionValidationException($errors); + } + +} diff --git a/src/applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php b/src/applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php new file mode 100644 index 0000000000..f746ddce22 --- /dev/null +++ b/src/applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php @@ -0,0 +1,45 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $publisher = $objects[$phid]; + + $name = $publisher->getName(); + $uri = $publisher->getURI(); + + $handle + ->setName($name) + ->setURI($uri); + } + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php b/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php new file mode 100644 index 0000000000..1c034951c2 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php @@ -0,0 +1,64 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withPublisherKeys(array $keys) { + $this->publisherKeys = $keys; + return $this; + } + + public function newResultObject() { + return new PhabricatorPackagesPublisher(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->publisherKeys !== null) { + $where[] = qsprintf( + $conn, + 'publisherKey IN (%Ls)', + $this->publisherKeys); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorPackagesApplication'; + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php new file mode 100644 index 0000000000..8d44c45ac2 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php @@ -0,0 +1,77 @@ +newQuery(); + + return $query; + } + + protected function buildCustomSearchFields() { + return array(); + } + + protected function getURI($path) { + return '/packages/publisher/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Publishers'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $publishers, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($publishers, 'PhabricatorPackagesPublisher'); + + $viewer = $this->requireViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + foreach ($publishers as $publisher) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($publisher->getPublisherKey()) + ->setHeader($publisher->getName()) + ->setHref($publisher->getURI()); + + $list->addItem($item); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No publishers found.')); + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php b/src/applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php new file mode 100644 index 0000000000..e7af82cfa8 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php @@ -0,0 +1,10 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text64', + 'publisherKey' => 'sort64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_publisher' => array( + 'columns' => array('publisherKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPackagesPublisherPHIDType::TYPECONST); + } + + public function getURI() { + $publisher_key = $this->getPublisherKey(); + return "/package/{$publisher_key}/"; + } + + public static function assertValidPublisherName($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Publisher name "%s" is not valid: publisher names are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Publisher name "%s" is not valid: publisher names must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + } + + public static function assertValidPublisherKey($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Publisher key "%s" is not valid: publisher keys are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Publisher key "%s" is not valid: publisher keys must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + + if (!preg_match('/^[a-z]+\z/', $value)) { + throw new Exception( + pht( + 'Publisher key "%s" is not valid: publisher keys may only contain '. + 'lowercase latin letters.', + $value)); + } + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( Policy Interface )--------------------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $user) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorPackagesPublisherEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorPackagesPublisherTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the publisher.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('publisherKey') + ->setType('string') + ->setDescription(pht('The unique key of the publisher.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'publisherKey' => $this->getPublisherKey(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + +} diff --git a/src/applications/packages/storage/PhabricatorPackagesPublisherTransaction.php b/src/applications/packages/storage/PhabricatorPackagesPublisherTransaction.php new file mode 100644 index 0000000000..4aa0892ea5 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesPublisherTransaction.php @@ -0,0 +1,18 @@ +buildEdgeSchemata(new PhabricatorPackagesPublisher()); + } + +} diff --git a/src/applications/packages/xaction/PhabricatorPackagesTransactionType.php b/src/applications/packages/xaction/PhabricatorPackagesTransactionType.php new file mode 100644 index 0000000000..a4c55a4221 --- /dev/null +++ b/src/applications/packages/xaction/PhabricatorPackagesTransactionType.php @@ -0,0 +1,4 @@ +getPublisherKey(); + } + + public function applyInternalEffects($object, $value) { + $object->setPublisherKey($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Publishers must have a unique publisher key.')); + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a publisher is created, its key can not be changed.'), + $xaction); + } + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPublisher::assertValidPublisherKey($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php b/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php new file mode 100644 index 0000000000..fbc85c9733 --- /dev/null +++ b/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php @@ -0,0 +1,53 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s changed the name of this publisher from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the name for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Publishers must have a name.')); + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPublisher::assertValidPublisherName($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php b/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php new file mode 100644 index 0000000000..3d39f707dc --- /dev/null +++ b/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php @@ -0,0 +1,4 @@ + array(), 'db.phurl' => array(), 'db.badges' => array(), + 'db.packages' => array(), '0000.legacy.sql' => array( 'legacy' => 0, ), From 704afea281b1c3127c182968778efdb04832bef3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 21 Jul 2016 10:39:59 -0700 Subject: [PATCH 65/76] Add PackagesPackage Summary: Ref T8116. A package has: - a publisher (like "Phacility"), from the previous revision; - a name (like "Arcanist"); - a package key (like "arcanist"). The package key is immutable, like the publisher key. This gives a package a full key like "phacility/arcanist". Policy stuff: - You must be able to view a publisher to view a package (currently, everyone can always see all publishers). - You must be able to edit a publisher to create a new package inside it. - Packages have separate view/edit permissions. This still does nothing interesting. Test Plan: {F1731663} Reviewers: chad Reviewed By: chad Subscribers: eadler Maniphest Tasks: T8116 Differential Revision: https://secure.phabricator.com/D16315 --- .../sql/autopatches/20160721.pack.04.pkg.sql | 13 + .../20160721.pack.05.pkgxaction.sql | 19 ++ src/__phutil_library_map__.php | 50 +++- .../PhabricatorPackagesApplication.php | 9 + ...torPackagesPackageEditConduitAPIMethod.php | 19 ++ ...rPackagesPackageSearchConduitAPIMethod.php | 18 ++ .../PhabricatorPackagesPackageController.php | 4 + ...abricatorPackagesPackageEditController.php | 12 + ...abricatorPackagesPackageListController.php | 26 ++ ...abricatorPackagesPackageViewController.php | 87 +++++++ .../PhabricatorPackagesPackageEditEngine.php | 104 ++++++++ .../PhabricatorPackagesPackageEditor.php | 55 +++++ ...PhabricatorPackagesPublisherEditEngine.php | 14 +- .../PhabricatorPackagesPackagePHIDType.php | 45 ++++ .../query/PhabricatorPackagesPackageQuery.php | 152 ++++++++++++ ...PhabricatorPackagesPackageSearchEngine.php | 77 ++++++ ...ricatorPackagesPackageTransactionQuery.php | 10 + .../PhabricatorPackagesPublisherQuery.php | 6 +- .../query/PhabricatorPackagesQuery.php | 10 + .../storage/PhabricatorPackagesPackage.php | 222 ++++++++++++++++++ .../PhabricatorPackagesPackageTransaction.php | 18 ++ .../storage/PhabricatorPackagesPublisher.php | 16 +- ...PhabricatorPackagesPublisherDatasource.php | 35 +++ ...abricatorPackagesPackageKeyTransaction.php | 47 ++++ ...bricatorPackagesPackageNameTransaction.php | 54 +++++ ...torPackagesPackagePublisherTransaction.php | 66 ++++++ ...bricatorPackagesPackageTransactionType.php | 4 + 27 files changed, 1178 insertions(+), 14 deletions(-) create mode 100644 resources/sql/autopatches/20160721.pack.04.pkg.sql create mode 100644 resources/sql/autopatches/20160721.pack.05.pkgxaction.sql create mode 100644 src/applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php create mode 100644 src/applications/packages/conduit/PhabricatorPackagesPackageSearchConduitAPIMethod.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPackageController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPackageEditController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPackageListController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPackageViewController.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesPackageEditEngine.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesPackageEditor.php create mode 100644 src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPackageQuery.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php create mode 100644 src/applications/packages/query/PhabricatorPackagesQuery.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesPackage.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesPackageTransaction.php create mode 100644 src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php create mode 100644 src/applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php create mode 100644 src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php create mode 100644 src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php create mode 100644 src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php diff --git a/resources/sql/autopatches/20160721.pack.04.pkg.sql b/resources/sql/autopatches/20160721.pack.04.pkg.sql new file mode 100644 index 0000000000..c5f427f1c0 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.04.pkg.sql @@ -0,0 +1,13 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_package ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, + publisherPHID VARBINARY(64) NOT NULL, + packageKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_SORT}, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_package` (publisherPHID, packageKey) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql b/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql new file mode 100644 index 0000000000..7fd82569de --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_packagetransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + 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) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fed5a1c75f..750c465efe 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2977,8 +2977,27 @@ phutil_register_library_map(array( 'PhabricatorPackagesDAO' => 'applications/packages/storage/PhabricatorPackagesDAO.php', 'PhabricatorPackagesEditEngine' => 'applications/packages/editor/PhabricatorPackagesEditEngine.php', 'PhabricatorPackagesEditor' => 'applications/packages/editor/PhabricatorPackagesEditor.php', + 'PhabricatorPackagesPackage' => 'applications/packages/storage/PhabricatorPackagesPackage.php', + 'PhabricatorPackagesPackageController' => 'applications/packages/controller/PhabricatorPackagesPackageController.php', + 'PhabricatorPackagesPackageEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php', + 'PhabricatorPackagesPackageEditController' => 'applications/packages/controller/PhabricatorPackagesPackageEditController.php', + 'PhabricatorPackagesPackageEditEngine' => 'applications/packages/editor/PhabricatorPackagesPackageEditEngine.php', + 'PhabricatorPackagesPackageEditor' => 'applications/packages/editor/PhabricatorPackagesPackageEditor.php', + 'PhabricatorPackagesPackageKeyTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php', + 'PhabricatorPackagesPackageListController' => 'applications/packages/controller/PhabricatorPackagesPackageListController.php', + 'PhabricatorPackagesPackageNameTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php', + 'PhabricatorPackagesPackagePHIDType' => 'applications/packages/phid/PhabricatorPackagesPackagePHIDType.php', + 'PhabricatorPackagesPackagePublisherTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php', + 'PhabricatorPackagesPackageQuery' => 'applications/packages/query/PhabricatorPackagesPackageQuery.php', + 'PhabricatorPackagesPackageSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageSearchConduitAPIMethod.php', + 'PhabricatorPackagesPackageSearchEngine' => 'applications/packages/query/PhabricatorPackagesPackageSearchEngine.php', + 'PhabricatorPackagesPackageTransaction' => 'applications/packages/storage/PhabricatorPackagesPackageTransaction.php', + 'PhabricatorPackagesPackageTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php', + 'PhabricatorPackagesPackageTransactionType' => 'applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php', + 'PhabricatorPackagesPackageViewController' => 'applications/packages/controller/PhabricatorPackagesPackageViewController.php', 'PhabricatorPackagesPublisher' => 'applications/packages/storage/PhabricatorPackagesPublisher.php', 'PhabricatorPackagesPublisherController' => 'applications/packages/controller/PhabricatorPackagesPublisherController.php', + 'PhabricatorPackagesPublisherDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php', 'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php', 'PhabricatorPackagesPublisherEditController' => 'applications/packages/controller/PhabricatorPackagesPublisherEditController.php', 'PhabricatorPackagesPublisherEditEngine' => 'applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php', @@ -2994,6 +3013,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisherTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php', 'PhabricatorPackagesPublisherTransactionType' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php', 'PhabricatorPackagesPublisherViewController' => 'applications/packages/controller/PhabricatorPackagesPublisherViewController.php', + 'PhabricatorPackagesQuery' => 'applications/packages/query/PhabricatorPackagesQuery.php', 'PhabricatorPackagesSchemaSpec' => 'applications/packages/storage/PhabricatorPackagesSchemaSpec.php', 'PhabricatorPackagesTransactionType' => 'applications/packages/xaction/PhabricatorPackagesTransactionType.php', 'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php', @@ -7754,6 +7774,32 @@ phutil_register_library_map(array( 'PhabricatorPackagesDAO' => 'PhabricatorLiskDAO', 'PhabricatorPackagesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorPackagesEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorPackagesPackage' => array( + 'PhabricatorPackagesDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + 'PhabricatorSubscribableInterface', + 'PhabricatorProjectInterface', + 'PhabricatorConduitResultInterface', + ), + 'PhabricatorPackagesPackageController' => 'PhabricatorPackagesController', + 'PhabricatorPackagesPackageEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'PhabricatorPackagesPackageEditController' => 'PhabricatorPackagesPackageController', + 'PhabricatorPackagesPackageEditEngine' => 'PhabricatorPackagesEditEngine', + 'PhabricatorPackagesPackageEditor' => 'PhabricatorPackagesEditor', + 'PhabricatorPackagesPackageKeyTransaction' => 'PhabricatorPackagesPackageTransactionType', + 'PhabricatorPackagesPackageListController' => 'PhabricatorPackagesPackageController', + 'PhabricatorPackagesPackageNameTransaction' => 'PhabricatorPackagesPackageTransactionType', + 'PhabricatorPackagesPackagePHIDType' => 'PhabricatorPHIDType', + 'PhabricatorPackagesPackagePublisherTransaction' => 'PhabricatorPackagesPackageTransactionType', + 'PhabricatorPackagesPackageQuery' => 'PhabricatorPackagesQuery', + 'PhabricatorPackagesPackageSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhabricatorPackagesPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorPackagesPackageTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorPackagesPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorPackagesPackageTransactionType' => 'PhabricatorPackagesTransactionType', + 'PhabricatorPackagesPackageViewController' => 'PhabricatorPackagesPackageController', 'PhabricatorPackagesPublisher' => array( 'PhabricatorPackagesDAO', 'PhabricatorPolicyInterface', @@ -7764,6 +7810,7 @@ phutil_register_library_map(array( 'PhabricatorConduitResultInterface', ), 'PhabricatorPackagesPublisherController' => 'PhabricatorPackagesController', + 'PhabricatorPackagesPublisherDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorPackagesPublisherEditController' => 'PhabricatorPackagesPublisherController', 'PhabricatorPackagesPublisherEditEngine' => 'PhabricatorPackagesEditEngine', @@ -7772,13 +7819,14 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisherListController' => 'PhabricatorPackagesPublisherController', 'PhabricatorPackagesPublisherNameTransaction' => 'PhabricatorPackagesPublisherTransactionType', 'PhabricatorPackagesPublisherPHIDType' => 'PhabricatorPHIDType', - 'PhabricatorPackagesPublisherQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPackagesPublisherQuery' => 'PhabricatorPackagesQuery', 'PhabricatorPackagesPublisherSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhabricatorPackagesPublisherSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPackagesPublisherTransaction' => 'PhabricatorModularTransaction', 'PhabricatorPackagesPublisherTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPackagesPublisherTransactionType' => 'PhabricatorPackagesTransactionType', 'PhabricatorPackagesPublisherViewController' => 'PhabricatorPackagesPublisherController', + 'PhabricatorPackagesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPackagesSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorPackagesTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorPagerUIExample' => 'PhabricatorUIExample', diff --git a/src/applications/packages/application/PhabricatorPackagesApplication.php b/src/applications/packages/application/PhabricatorPackagesApplication.php index 0f6b47cbf9..818dc31bf0 100644 --- a/src/applications/packages/application/PhabricatorPackagesApplication.php +++ b/src/applications/packages/application/PhabricatorPackagesApplication.php @@ -31,6 +31,9 @@ final class PhabricatorPackagesApplication extends PhabricatorApplication { '/package/' => array( '(?P[^/]+)/' => array( '' => 'PhabricatorPackagesPublisherViewController', + '(?P[^/]+)/' => array( + '' => 'PhabricatorPackagesPackageViewController', + ), ), ), '/packages/' => array( @@ -40,6 +43,12 @@ final class PhabricatorPackagesApplication extends PhabricatorApplication { $this->getEditRoutePattern('edit/') => 'PhabricatorPackagesPublisherEditController', ), + 'package/' => array( + $this->getQueryRoutePattern() => + 'PhabricatorPackagesPackageListController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorPackagesPackageEditController', + ), ), ); } diff --git a/src/applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php b/src/applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php new file mode 100644 index 0000000000..53b9709d36 --- /dev/null +++ b/src/applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPackageListController.php b/src/applications/packages/controller/PhabricatorPackagesPackageListController.php new file mode 100644 index 0000000000..5b29ab83f6 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPackageListController.php @@ -0,0 +1,26 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorPackagesPackageEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php b/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php new file mode 100644 index 0000000000..f0a9483a28 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php @@ -0,0 +1,87 @@ +getViewer(); + + $publisher_key = $request->getURIData('publisherKey'); + $package_key = $request->getURIData('packageKey'); + $full_key = $publisher_key.'/'.$package_key; + + $package = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($viewer) + ->withFullKeys(array($full_key)) + ->executeOne(); + if (!$package) { + return new Aphront404Response(); + } + + $publisher = $package->getPublisher(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($publisher->getName(), $publisher->getURI()) + ->addTextCrumb($package->getName()) + ->setBorder(true); + + $header = $this->buildHeaderView($package); + $curtain = $this->buildCurtain($package); + + $timeline = $this->buildTransactionTimeline( + $package, + new PhabricatorPackagesPackageTransactionQuery()); + + $package_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn($timeline); + + return $this->newPage() + ->setCrumbs($crumbs) + ->setPageObjectPHIDs( + array( + $package->getPHID(), + )) + ->appendChild($package_view); + } + + + private function buildHeaderView(PhabricatorPackagesPackage $package) { + $viewer = $this->getViewer(); + $name = $package->getName(); + + return id(new PHUIHeaderView()) + ->setViewer($viewer) + ->setHeader($name) + ->setPolicyObject($package) + ->setHeaderIcon('fa-gift'); + } + + private function buildCurtain(PhabricatorPackagesPackage $package) { + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($package); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $package, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $package->getID(); + $edit_uri = $this->getApplicationURI("package/edit/{$id}/"); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Package')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setHref($edit_uri)); + + return $curtain; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPackageEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesPackageEditEngine.php new file mode 100644 index 0000000000..5d151e6bf4 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesPackageEditEngine.php @@ -0,0 +1,104 @@ +getViewer(); + return PhabricatorPackagesPackage::initializeNewPackage($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorPackagesPackageQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Package'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Package'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Package: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Package'); + } + + protected function getObjectCreateShortText() { + return pht('Create Package'); + } + + protected function getObjectName() { + return pht('Package'); + } + + protected function getEditorURI() { + return '/packages/package/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/packages/package/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $fields = array(); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorDatasourceEditField()) + ->setKey('publisher') + ->setAliases(array('publisherPHID')) + ->setLabel(pht('Publisher')) + ->setDescription(pht('Publisher for this package.')) + ->setTransactionType( + PhabricatorPackagesPackagePublisherTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setDatasource(new PhabricatorPackagesPublisherDatasource()) + ->setSingleValue($object->getPublisherPHID()); + } + + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the package.')) + ->setTransactionType( + PhabricatorPackagesPackageNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('packageKey') + ->setLabel(pht('Package Key')) + ->setDescription(pht('Unique key to identify the package.')) + ->setTransactionType( + PhabricatorPackagesPackageKeyTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getPackageKey()); + } + + return $fields; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php b/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php new file mode 100644 index 0000000000..1446fa3cac --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php @@ -0,0 +1,55 @@ +getPackageKey()), + null); + + throw new PhabricatorApplicationTransactionValidationException($errors); + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php index e4f027a49e..521aec7ee0 100644 --- a/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php +++ b/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php @@ -76,13 +76,13 @@ final class PhabricatorPackagesPublisherEditEngine if ($this->getIsCreate()) { $fields[] = id(new PhabricatorTextEditField()) - ->setKey('publisherKey') - ->setLabel(pht('Publisher Key')) - ->setDescription(pht('Unique key to identify the publisher.')) - ->setTransactionType( - PhabricatorPackagesPublisherKeyTransaction::TRANSACTIONTYPE) - ->setIsRequired(true) - ->setValue($object->getPublisherKey()); + ->setKey('publisherKey') + ->setLabel(pht('Publisher Key')) + ->setDescription(pht('Unique key to identify the publisher.')) + ->setTransactionType( + PhabricatorPackagesPublisherKeyTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getPublisherKey()); } return $fields; diff --git a/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php b/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php new file mode 100644 index 0000000000..6f8e982c81 --- /dev/null +++ b/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php @@ -0,0 +1,45 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $package = $objects[$phid]; + + $name = $package->getName(); + $uri = $package->getURI(); + + $handle + ->setName($name) + ->setURI($uri); + } + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php new file mode 100644 index 0000000000..66f1d99840 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php @@ -0,0 +1,152 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withPublisherPHIDs(array $phids) { + $this->publisherPHIDs = $phids; + return $this; + } + + public function withPackageKeys(array $keys) { + $this->packageKeys = $keys; + return $this; + } + + public function withFullKeys(array $keys) { + $this->fullKeys = $keys; + return $this; + } + + public function newResultObject() { + return new PhabricatorPackagesPackage(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'p.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'p.phid IN (%Ls)', + $this->phids); + } + + if ($this->publisherPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'p.phid IN (%Ls)', + $this->publisherPHIDs); + } + + if ($this->packageKeys !== null) { + $where[] = qsprintf( + $conn, + 'p.packageKey IN (%Ls)', + $this->packageKeys); + } + + if ($this->fullKeys !== null) { + $parts = array(); + foreach ($this->fullKeys as $full_key) { + $key_parts = explode('/', $full_key, 2); + + if (count($key_parts) != 2) { + continue; + } + + $parts[] = qsprintf( + $conn, + '(u.publisherKey = %s AND p.packageKey = %s)', + $key_parts[0], + $key_parts[1]); + } + + // If none of the full keys we were provided were valid, we don't + // match any results. + if (!$parts) { + throw new PhabricatorEmptyQueryException(); + } + + $where[] = qsprintf( + $conn, + '%Q', + implode(' OR ', $parts)); + } + + return $where; + } + + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + $join_publisher = ($this->fullKeys !== null); + if ($join_publisher) { + $publisher_table = new PhabricatorPackagesPublisher(); + + $joins[] = qsprintf( + $conn, + 'JOIN %T u ON u.phid = p.publisherPHID', + $publisher_table->getTableName()); + } + + return $joins; + } + + protected function willFilterPage(array $packages) { + $publisher_phids = mpull($packages, 'getPublisherPHID'); + + $publishers = id(new PhabricatorPackagesPublisherQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs($publisher_phids) + ->execute(); + $publishers = mpull($publishers, null, 'getPHID'); + + foreach ($packages as $key => $package) { + $publisher = idx($publishers, $package->getPublisherPHID()); + + if (!$publisher) { + unset($packages[$key]); + $this->didRejectResult($package); + continue; + } + + $package->attachPublisher($publisher); + } + + return $packages; + } + + protected function getPrimaryTableAlias() { + return 'p'; + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php new file mode 100644 index 0000000000..38fd264276 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php @@ -0,0 +1,77 @@ +newQuery(); + + return $query; + } + + protected function buildCustomSearchFields() { + return array(); + } + + protected function getURI($path) { + return '/packages/package/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Packages'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $packages, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($packages, 'PhabricatorPackagesPackage'); + + $viewer = $this->requireViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + foreach ($packages as $package) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($package->getFullKey()) + ->setHeader($package->getName()) + ->setHref($package->getURI()); + + $list->addItem($item); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No packages found.')); + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php new file mode 100644 index 0000000000..146f138119 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php @@ -0,0 +1,10 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text64', + 'packageKey' => 'sort64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_package' => array( + 'columns' => array('publisherPHID', 'packageKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPackagesPackagePHIDType::TYPECONST); + } + + public function getURI() { + $full_key = $this->getFullKey(); + return "/package/{$full_key}/"; + } + + public function getFullKey() { + $publisher = $this->getPublisher(); + $publisher_key = $publisher->getPublisherKey(); + $package_key = $this->getPackageKey(); + return "{$publisher_key}/{$package_key}"; + } + + public function attachPublisher(PhabricatorPackagesPublisher $publisher) { + $this->publisher = $publisher; + return $this; + } + + public function getPublisher() { + return $this->assertAttached($this->publisher); + } + + public static function assertValidPackageName($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Package name "%s" is not valid: package names are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Package name "%s" is not valid: package names must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + } + + public static function assertValidPackageKey($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + + if (!preg_match('/^[a-z]+\z/', $value)) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys may only contain '. + 'lowercase latin letters.', + $value)); + } + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( Policy Interface )--------------------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $user) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorPackagesPackageEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorPackagesPackageTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the package.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('packageKey') + ->setType('string') + ->setDescription(pht('The unique key of the package.')), + ); + } + + public function getFieldValuesForConduit() { + $publisher = $this->getPublisher(); + + $publisher_map = array( + 'id' => (int)$publisher->getID(), + 'phid' => $publisher->getPHID(), + 'name' => $publisher->getName(), + 'publisherKey' => $publisher->getPublisherKey(), + ); + + return array( + 'name' => $this->getName(), + 'packageKey' => $this->getPackageKey(), + 'fullKey' => $this->getFullKey(), + 'publisher' => $publisher_map, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + +} diff --git a/src/applications/packages/storage/PhabricatorPackagesPackageTransaction.php b/src/applications/packages/storage/PhabricatorPackagesPackageTransaction.php new file mode 100644 index 0000000000..29a6b74051 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesPackageTransaction.php @@ -0,0 +1,18 @@ +delete(); + $viewer = $engine->getViewer(); + + $this->openTransaction(); + + $packages = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($viewer) + ->withPublisherPHIDs(array($this->getPHID())) + ->execute(); + foreach ($packages as $package) { + $engine->destroyObject($package); + } + + $this->delete(); + + $this->saveTransaction(); } diff --git a/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php b/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php new file mode 100644 index 0000000000..e8ca7d4c7f --- /dev/null +++ b/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php @@ -0,0 +1,35 @@ +getViewer(); + $raw_query = $this->getRawQuery(); + + $publisher_query = id(new PhabricatorPackagesPublisherQuery()); + $publishers = $this->executeQuery($publisher_query); + + $results = array(); + foreach ($publishers as $publisher) { + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($publisher->getName()) + ->setPHID($publisher->getPHID()); + } + + return $this->filterResultsAgainstTokens($results); + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php new file mode 100644 index 0000000000..6029b8f4af --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php @@ -0,0 +1,47 @@ +getPackageKey(); + } + + public function applyInternalEffects($object, $value) { + $object->setPackageKey($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht( + 'Each package provided by a publisher must have a '. + 'unique package key.')); + return $errors; + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a package is created, its key can not be changed.'), + $xaction); + } + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPackage::assertValidPackageKey($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php new file mode 100644 index 0000000000..0496911e57 --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php @@ -0,0 +1,54 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s changed the name of this package from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the name for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Packages must have a name.')); + return $errors; + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPackage::assertValidPackageName($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php new file mode 100644 index 0000000000..a3be870fdd --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php @@ -0,0 +1,66 @@ +getPublisherPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setPublisherPHID($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht( + 'You must select a publisher when creating a package.')); + return $errors; + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a package is created, its publisher can not be changed.'), + $xaction); + } + } + + $viewer = $this->getActor(); + foreach ($xactions as $xaction) { + $publisher_phid = $xaction->getNewValue(); + + $publisher = id(new PhabricatorPackagesPublisherQuery()) + ->setViewer($viewer) + ->withPHIDs(array($publisher_phid)) + ->setRaisePolicyExceptions(false) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if (!$publisher) { + $errors[] = $this->newInvalidError( + pht( + 'Publisher "%s" is invalid: the publisher must exist and you '. + 'must have permission to edit it in order to create a new '. + 'package.', + $publisher_phid), + $xaction); + continue; + } + + $object->attachPublisher($publisher); + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php new file mode 100644 index 0000000000..08cbb113b4 --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php @@ -0,0 +1,4 @@ + Date: Thu, 21 Jul 2016 12:11:11 -0700 Subject: [PATCH 66/76] Add PackagesVersion Summary: Ref T8116. A version has: - a package (like "Arcanist") which it belongs to; - a name (like "v3.1.5"). The name is immutable and unique, like the package key and publisher key. Policy stuff: - Versions have the exact same policies as their packages. - You must be able to edit a package to create new versions of it. This is still entirely uninteresting. Test Plan: {F1731703} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8116 Differential Revision: https://secure.phabricator.com/D16316 --- .../autopatches/20160721.pack.06.version.sql | 10 + .../20160721.pack.07.versionxaction.sql | 19 ++ src/__phutil_library_map__.php | 45 ++++ .../PhabricatorPackagesApplication.php | 8 + ...torPackagesVersionEditConduitAPIMethod.php | 19 ++ ...rPackagesVersionSearchConduitAPIMethod.php | 18 ++ .../PhabricatorPackagesVersionController.php | 4 + ...abricatorPackagesVersionEditController.php | 12 ++ ...abricatorPackagesVersionListController.php | 26 +++ ...abricatorPackagesVersionViewController.php | 91 ++++++++ .../PhabricatorPackagesVersionEditEngine.php | 93 ++++++++ .../PhabricatorPackagesVersionEditor.php | 46 ++++ .../PhabricatorPackagesVersionPHIDType.php | 45 ++++ .../query/PhabricatorPackagesPackageQuery.php | 27 +-- .../query/PhabricatorPackagesQuery.php | 28 +++ .../query/PhabricatorPackagesVersionQuery.php | 141 ++++++++++++ ...PhabricatorPackagesVersionSearchEngine.php | 76 +++++++ ...ricatorPackagesVersionTransactionQuery.php | 10 + .../storage/PhabricatorPackagesPackage.php | 16 +- .../storage/PhabricatorPackagesVersion.php | 200 ++++++++++++++++++ .../PhabricatorPackagesVersionTransaction.php | 18 ++ .../PhabricatorPackagesPackageDatasource.php | 35 +++ ...torPackagesPackagePublisherTransaction.php | 3 +- ...bricatorPackagesVersionNameTransaction.php | 62 ++++++ ...catorPackagesVersionPackageTransaction.php | 66 ++++++ ...bricatorPackagesVersionTransactionType.php | 4 + 26 files changed, 1095 insertions(+), 27 deletions(-) create mode 100644 resources/sql/autopatches/20160721.pack.06.version.sql create mode 100644 resources/sql/autopatches/20160721.pack.07.versionxaction.sql create mode 100644 src/applications/packages/conduit/PhabricatorPackagesVersionEditConduitAPIMethod.php create mode 100644 src/applications/packages/conduit/PhabricatorPackagesVersionSearchConduitAPIMethod.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesVersionController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesVersionEditController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesVersionListController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesVersionViewController.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesVersionEditEngine.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesVersionEditor.php create mode 100644 src/applications/packages/phid/PhabricatorPackagesVersionPHIDType.php create mode 100644 src/applications/packages/query/PhabricatorPackagesVersionQuery.php create mode 100644 src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php create mode 100644 src/applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesVersion.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesVersionTransaction.php create mode 100644 src/applications/packages/typeahead/PhabricatorPackagesPackageDatasource.php create mode 100644 src/applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php create mode 100644 src/applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php create mode 100644 src/applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php diff --git a/resources/sql/autopatches/20160721.pack.06.version.sql b/resources/sql/autopatches/20160721.pack.06.version.sql new file mode 100644 index 0000000000..8ca2870f78 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.06.version.sql @@ -0,0 +1,10 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_version ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(64) NOT NULL COLLATE {$COLLATE_SORT}, + packagePHID VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_package` (packagePHID, name) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160721.pack.07.versionxaction.sql b/resources/sql/autopatches/20160721.pack.07.versionxaction.sql new file mode 100644 index 0000000000..706460b025 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.07.versionxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_versiontransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + 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) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 750c465efe..b85da8bfac 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2979,6 +2979,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesEditor' => 'applications/packages/editor/PhabricatorPackagesEditor.php', 'PhabricatorPackagesPackage' => 'applications/packages/storage/PhabricatorPackagesPackage.php', 'PhabricatorPackagesPackageController' => 'applications/packages/controller/PhabricatorPackagesPackageController.php', + 'PhabricatorPackagesPackageDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPackageDatasource.php', 'PhabricatorPackagesPackageEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php', 'PhabricatorPackagesPackageEditController' => 'applications/packages/controller/PhabricatorPackagesPackageEditController.php', 'PhabricatorPackagesPackageEditEngine' => 'applications/packages/editor/PhabricatorPackagesPackageEditEngine.php', @@ -3016,6 +3017,23 @@ phutil_register_library_map(array( 'PhabricatorPackagesQuery' => 'applications/packages/query/PhabricatorPackagesQuery.php', 'PhabricatorPackagesSchemaSpec' => 'applications/packages/storage/PhabricatorPackagesSchemaSpec.php', 'PhabricatorPackagesTransactionType' => 'applications/packages/xaction/PhabricatorPackagesTransactionType.php', + 'PhabricatorPackagesVersion' => 'applications/packages/storage/PhabricatorPackagesVersion.php', + 'PhabricatorPackagesVersionController' => 'applications/packages/controller/PhabricatorPackagesVersionController.php', + 'PhabricatorPackagesVersionEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesVersionEditConduitAPIMethod.php', + 'PhabricatorPackagesVersionEditController' => 'applications/packages/controller/PhabricatorPackagesVersionEditController.php', + 'PhabricatorPackagesVersionEditEngine' => 'applications/packages/editor/PhabricatorPackagesVersionEditEngine.php', + 'PhabricatorPackagesVersionEditor' => 'applications/packages/editor/PhabricatorPackagesVersionEditor.php', + 'PhabricatorPackagesVersionListController' => 'applications/packages/controller/PhabricatorPackagesVersionListController.php', + 'PhabricatorPackagesVersionNameTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php', + 'PhabricatorPackagesVersionPHIDType' => 'applications/packages/phid/PhabricatorPackagesVersionPHIDType.php', + 'PhabricatorPackagesVersionPackageTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php', + 'PhabricatorPackagesVersionQuery' => 'applications/packages/query/PhabricatorPackagesVersionQuery.php', + 'PhabricatorPackagesVersionSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesVersionSearchConduitAPIMethod.php', + 'PhabricatorPackagesVersionSearchEngine' => 'applications/packages/query/PhabricatorPackagesVersionSearchEngine.php', + 'PhabricatorPackagesVersionTransaction' => 'applications/packages/storage/PhabricatorPackagesVersionTransaction.php', + 'PhabricatorPackagesVersionTransactionQuery' => 'applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php', + 'PhabricatorPackagesVersionTransactionType' => 'applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php', + 'PhabricatorPackagesVersionViewController' => 'applications/packages/controller/PhabricatorPackagesVersionViewController.php', 'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php', 'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php', 'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php', @@ -7784,6 +7802,7 @@ phutil_register_library_map(array( 'PhabricatorConduitResultInterface', ), 'PhabricatorPackagesPackageController' => 'PhabricatorPackagesController', + 'PhabricatorPackagesPackageDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPackagesPackageEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorPackagesPackageEditController' => 'PhabricatorPackagesPackageController', 'PhabricatorPackagesPackageEditEngine' => 'PhabricatorPackagesEditEngine', @@ -7829,6 +7848,32 @@ phutil_register_library_map(array( 'PhabricatorPackagesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPackagesSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorPackagesTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorPackagesVersion' => array( + 'PhabricatorPackagesDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + 'PhabricatorSubscribableInterface', + 'PhabricatorProjectInterface', + 'PhabricatorConduitResultInterface', + ), + 'PhabricatorPackagesVersionController' => 'PhabricatorPackagesController', + 'PhabricatorPackagesVersionEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'PhabricatorPackagesVersionEditController' => 'PhabricatorPackagesVersionController', + 'PhabricatorPackagesVersionEditEngine' => 'PhabricatorPackagesEditEngine', + 'PhabricatorPackagesVersionEditor' => 'PhabricatorPackagesEditor', + 'PhabricatorPackagesVersionListController' => 'PhabricatorPackagesVersionController', + 'PhabricatorPackagesVersionNameTransaction' => 'PhabricatorPackagesVersionTransactionType', + 'PhabricatorPackagesVersionPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorPackagesVersionPackageTransaction' => 'PhabricatorPackagesVersionTransactionType', + 'PhabricatorPackagesVersionQuery' => 'PhabricatorPackagesQuery', + 'PhabricatorPackagesVersionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhabricatorPackagesVersionSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorPackagesVersionTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorPackagesVersionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorPackagesVersionTransactionType' => 'PhabricatorPackagesTransactionType', + 'PhabricatorPackagesVersionViewController' => 'PhabricatorPackagesVersionController', 'PhabricatorPagerUIExample' => 'PhabricatorUIExample', 'PhabricatorPassphraseApplication' => 'PhabricatorApplication', 'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider', diff --git a/src/applications/packages/application/PhabricatorPackagesApplication.php b/src/applications/packages/application/PhabricatorPackagesApplication.php index 818dc31bf0..e52a26310d 100644 --- a/src/applications/packages/application/PhabricatorPackagesApplication.php +++ b/src/applications/packages/application/PhabricatorPackagesApplication.php @@ -33,6 +33,8 @@ final class PhabricatorPackagesApplication extends PhabricatorApplication { '' => 'PhabricatorPackagesPublisherViewController', '(?P[^/]+)/' => array( '' => 'PhabricatorPackagesPackageViewController', + '(?P[^/]+)/' => + 'PhabricatorPackagesVersionViewController', ), ), ), @@ -49,6 +51,12 @@ final class PhabricatorPackagesApplication extends PhabricatorApplication { $this->getEditRoutePattern('edit/') => 'PhabricatorPackagesPackageEditController', ), + 'version/' => array( + $this->getQueryRoutePattern() => + 'PhabricatorPackagesVersionListController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorPackagesVersionEditController', + ), ), ); } diff --git a/src/applications/packages/conduit/PhabricatorPackagesVersionEditConduitAPIMethod.php b/src/applications/packages/conduit/PhabricatorPackagesVersionEditConduitAPIMethod.php new file mode 100644 index 0000000000..b0bbef10ce --- /dev/null +++ b/src/applications/packages/conduit/PhabricatorPackagesVersionEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesVersionListController.php b/src/applications/packages/controller/PhabricatorPackagesVersionListController.php new file mode 100644 index 0000000000..b3c71708a9 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesVersionListController.php @@ -0,0 +1,26 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorPackagesVersionEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php b/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php new file mode 100644 index 0000000000..bf2abd1920 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php @@ -0,0 +1,91 @@ +getViewer(); + + $publisher_key = $request->getURIData('publisherKey'); + $package_key = $request->getURIData('packageKey'); + $full_key = $publisher_key.'/'.$package_key; + $version_key = $request->getURIData('versionKey'); + + $version = id(new PhabricatorPackagesVersionQuery()) + ->setViewer($viewer) + ->withFullKeys(array($full_key)) + ->withNames(array($version_key)) + ->executeOne(); + if (!$version) { + return new Aphront404Response(); + } + + $package = $version->getPackage(); + $publisher = $package->getPublisher(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($publisher->getName(), $publisher->getURI()) + ->addTextCrumb($package->getName(), $package->getURI()) + ->addTextCrumb($version->getName()) + ->setBorder(true); + + $header = $this->buildHeaderView($version); + $curtain = $this->buildCurtain($version); + + $timeline = $this->buildTransactionTimeline( + $version, + new PhabricatorPackagesVersionTransactionQuery()); + + $version_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn($timeline); + + return $this->newPage() + ->setCrumbs($crumbs) + ->setPageObjectPHIDs( + array( + $version->getPHID(), + )) + ->appendChild($version_view); + } + + + private function buildHeaderView(PhabricatorPackagesVersion $version) { + $viewer = $this->getViewer(); + $name = $version->getName(); + + return id(new PHUIHeaderView()) + ->setViewer($viewer) + ->setHeader($name) + ->setPolicyObject($version) + ->setHeaderIcon('fa-tag'); + } + + private function buildCurtain(PhabricatorPackagesVersion $version) { + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($version); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $version, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $version->getID(); + $edit_uri = $this->getApplicationURI("version/edit/{$id}/"); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Version')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setHref($edit_uri)); + + return $curtain; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesVersionEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesVersionEditEngine.php new file mode 100644 index 0000000000..a3c50a886f --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesVersionEditEngine.php @@ -0,0 +1,93 @@ +getViewer(); + return PhabricatorPackagesVersion::initializeNewVersion($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorPackagesVersionQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Version'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Version'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Version: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Version'); + } + + protected function getObjectCreateShortText() { + return pht('Create Version'); + } + + protected function getObjectName() { + return pht('Version'); + } + + protected function getEditorURI() { + return '/packages/version/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/packages/version/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $fields = array(); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorDatasourceEditField()) + ->setKey('package') + ->setAliases(array('packagePHID')) + ->setLabel(pht('Package')) + ->setDescription(pht('Package for this version.')) + ->setTransactionType( + PhabricatorPackagesVersionPackageTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setDatasource(new PhabricatorPackagesPackageDatasource()) + ->setSingleValue($object->getPackagePHID()); + + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the version.')) + ->setTransactionType( + PhabricatorPackagesVersionNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()); + } + + return $fields; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php b/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php new file mode 100644 index 0000000000..34ab962fb5 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php @@ -0,0 +1,46 @@ +getName()), + null); + + throw new PhabricatorApplicationTransactionValidationException($errors); + } + +} diff --git a/src/applications/packages/phid/PhabricatorPackagesVersionPHIDType.php b/src/applications/packages/phid/PhabricatorPackagesVersionPHIDType.php new file mode 100644 index 0000000000..203cca20e0 --- /dev/null +++ b/src/applications/packages/phid/PhabricatorPackagesVersionPHIDType.php @@ -0,0 +1,45 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $version = $objects[$phid]; + + $name = $version->getName(); + $uri = $version->getURI(); + + $handle + ->setName($name) + ->setURI($uri); + } + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php index 66f1d99840..a0e325c453 100644 --- a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php +++ b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php @@ -74,31 +74,8 @@ final class PhabricatorPackagesPackageQuery } if ($this->fullKeys !== null) { - $parts = array(); - foreach ($this->fullKeys as $full_key) { - $key_parts = explode('/', $full_key, 2); - - if (count($key_parts) != 2) { - continue; - } - - $parts[] = qsprintf( - $conn, - '(u.publisherKey = %s AND p.packageKey = %s)', - $key_parts[0], - $key_parts[1]); - } - - // If none of the full keys we were provided were valid, we don't - // match any results. - if (!$parts) { - throw new PhabricatorEmptyQueryException(); - } - - $where[] = qsprintf( - $conn, - '%Q', - implode(' OR ', $parts)); + $parts = $this->buildFullKeyClauseParts($conn, $this->fullKeys); + $where[] = qsprintf($conn, '%Q', $parts); } return $where; diff --git a/src/applications/packages/query/PhabricatorPackagesQuery.php b/src/applications/packages/query/PhabricatorPackagesQuery.php index 086a9e63f8..58b90599eb 100644 --- a/src/applications/packages/query/PhabricatorPackagesQuery.php +++ b/src/applications/packages/query/PhabricatorPackagesQuery.php @@ -7,4 +7,32 @@ abstract class PhabricatorPackagesQuery return 'PhabricatorPackagesApplication'; } + protected function buildFullKeyClauseParts( + AphrontDatabaseConnection $conn, + array $full_keys) { + + $parts = array(); + foreach ($full_keys as $full_key) { + $key_parts = explode('/', $full_key, 2); + + if (count($key_parts) != 2) { + continue; + } + + $parts[] = qsprintf( + $conn, + '(u.publisherKey = %s AND p.packageKey = %s)', + $key_parts[0], + $key_parts[1]); + } + + // If none of the full keys we were provided were valid, we don't + // match any results. + if (!$parts) { + throw new PhabricatorEmptyQueryException(); + } + + return implode(' OR ', $parts); + } + } diff --git a/src/applications/packages/query/PhabricatorPackagesVersionQuery.php b/src/applications/packages/query/PhabricatorPackagesVersionQuery.php new file mode 100644 index 0000000000..5dad6878b9 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesVersionQuery.php @@ -0,0 +1,141 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withPackagePHIDs(array $phids) { + $this->packagePHIDs = $phids; + return $this; + } + + public function withFullKeys(array $keys) { + $this->fullKeys = $keys; + return $this; + } + + public function withNames(array $names) { + $this->names = $names; + return $this; + } + + public function newResultObject() { + return new PhabricatorPackagesVersion(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'v.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'v.phid IN (%Ls)', + $this->phids); + } + + if ($this->packagePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'v.packagePHID IN (%Ls)', + $this->packagePHIDs); + } + + if ($this->names !== null) { + $where[] = qsprintf( + $conn, + 'v.name IN (%Ls)', + $this->names); + } + + if ($this->fullKeys !== null) { + $parts = $this->buildFullKeyClauseParts($conn, $this->fullKeys); + $where[] = qsprintf($conn, '%Q', $parts); + } + + return $where; + } + + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + $join_publisher = ($this->fullKeys !== null); + $join_package = ($this->fullKeys !== null) || $join_publisher; + + if ($join_package) { + $package_table = new PhabricatorPackagesPackage(); + + $joins[] = qsprintf( + $conn, + 'JOIN %T p ON v.packagePHID = p.phid', + $package_table->getTableName()); + } + + if ($join_publisher) { + $publisher_table = new PhabricatorPackagesPublisher(); + + $joins[] = qsprintf( + $conn, + 'JOIN %T u ON u.phid = p.publisherPHID', + $publisher_table->getTableName()); + } + + return $joins; + } + + protected function willFilterPage(array $versions) { + $package_phids = mpull($versions, 'getPackagePHID'); + + $packages = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs($package_phids) + ->execute(); + $packages = mpull($packages, null, 'getPHID'); + + foreach ($versions as $key => $version) { + $package = idx($packages, $version->getPackagePHID()); + + if (!$package) { + unset($versions[$key]); + $this->didRejectResult($version); + continue; + } + + $version->attachPackage($package); + } + + return $versions; + } + + protected function getPrimaryTableAlias() { + return 'v'; + } + + +} diff --git a/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php new file mode 100644 index 0000000000..40cc42f920 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php @@ -0,0 +1,76 @@ +newQuery(); + + return $query; + } + + protected function buildCustomSearchFields() { + return array(); + } + + protected function getURI($path) { + return '/packages/version/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Versions'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $versions, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($versions, 'PhabricatorPackagesVersion'); + + $viewer = $this->requireViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + foreach ($versions as $version) { + $item = id(new PHUIObjectItemView()) + ->setHeader($version->getName()) + ->setHref($version->getURI()); + + $list->addItem($item); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No versions found.')); + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php b/src/applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php new file mode 100644 index 0000000000..7ab10e2490 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php @@ -0,0 +1,10 @@ +delete(); + $viewer = $engine->getViewer(); + + $this->openTransaction(); + + $versions = id(new PhabricatorPackagesVersionQuery()) + ->setViewer($viewer) + ->withPackagePHIDs(array($this->getPHID())) + ->execute(); + foreach ($versions as $version) { + $engine->destroyObject($version); + } + + $this->delete(); + + $this->saveTransaction(); } diff --git a/src/applications/packages/storage/PhabricatorPackagesVersion.php b/src/applications/packages/storage/PhabricatorPackagesVersion.php new file mode 100644 index 0000000000..557d768ccb --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesVersion.php @@ -0,0 +1,200 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'sort64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_package' => array( + 'columns' => array('packagePHID', 'name'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPackagesVersionPHIDType::TYPECONST); + } + + public function getURI() { + $package = $this->getPackage(); + $full_key = $package->getFullKey(); + $name = $this->getName(); + + return "/package/{$full_key}/{$name}/"; + } + + public function attachPackage(PhabricatorPackagesPackage $package) { + $this->package = $package; + return $this; + } + + public function getPackage() { + return $this->assertAttached($this->package); + } + + public static function assertValidVersionName($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Version name "%s" is not valid: version names are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Version name "%s" is not valid: version names must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + + if (!preg_match('/^[A-Za-z0-9.-]+\z/', $value)) { + throw new Exception( + pht( + 'Version name "%s" is not valid: version names may only contain '. + 'latin letters, digits, periods, and hyphens.', + $value)); + } + + if (preg_match('/^[.-]|[.-]$/', $value)) { + throw new Exception( + pht( + 'Version name "%s" is not valid: version names may not start or '. + 'end with a period or hyphen.', + $value)); + } + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( Policy Interface )--------------------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return PhabricatorPolicies::POLICY_USER; + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $user) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + return array( + array( + $this->getPackage(), + $capability, + ), + ); + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorPackagesVersionEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorPackagesVersionTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the version.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + +} diff --git a/src/applications/packages/storage/PhabricatorPackagesVersionTransaction.php b/src/applications/packages/storage/PhabricatorPackagesVersionTransaction.php new file mode 100644 index 0000000000..9ae1622d70 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesVersionTransaction.php @@ -0,0 +1,18 @@ +getViewer(); + $raw_query = $this->getRawQuery(); + + $package_query = id(new PhabricatorPackagesPackageQuery()); + $packages = $this->executeQuery($package_query); + + $results = array(); + foreach ($packages as $package) { + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($package->getName()) + ->setPHID($package->getPHID()); + } + + return $this->filterResultsAgainstTokens($results); + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php index a3be870fdd..77c8fa8301 100644 --- a/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php @@ -16,7 +16,8 @@ final class PhabricatorPackagesPackagePublisherTransaction public function validateTransactions($object, array $xactions) { $errors = array(); - if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $current_value = $object->getPublisherPHID(); + if ($this->isEmptyTextTransaction($current_value, $xactions)) { $errors[] = $this->newRequiredError( pht( 'You must select a publisher when creating a package.')); diff --git a/src/applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php b/src/applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php new file mode 100644 index 0000000000..ffe14eabd0 --- /dev/null +++ b/src/applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php @@ -0,0 +1,62 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s changed the name of this version from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the name for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Versions must have a name.')); + return $errors; + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesVersion::assertValidVersionName($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a version is created, its name can not be changed.'), + $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php b/src/applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php new file mode 100644 index 0000000000..733427c925 --- /dev/null +++ b/src/applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php @@ -0,0 +1,66 @@ +getPackagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setPackagePHID($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getPackagePHID(), $xactions)) { + $errors[] = $this->newRequiredError( + pht( + 'You must select a package when creating a version')); + return $errors; + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a version is created, its package can not be changed.'), + $xaction); + } + } + + $viewer = $this->getActor(); + foreach ($xactions as $xaction) { + $package_phid = $xaction->getNewValue(); + + $package = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($viewer) + ->withPHIDs(array($package_phid)) + ->setRaisePolicyExceptions(false) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if (!$package) { + $errors[] = $this->newInvalidError( + pht( + 'Package "%s" is invalid: the package must exist and you '. + 'must have permission to edit it in order to create a new '. + 'package.', + $package_phid), + $xaction); + continue; + } + + $object->attachPackage($package); + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php b/src/applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php new file mode 100644 index 0000000000..f25034097c --- /dev/null +++ b/src/applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php @@ -0,0 +1,4 @@ + Date: Fri, 22 Jul 2016 06:03:31 -0700 Subject: [PATCH 67/76] Add default create, view, edit capabilities to Packages Summary: Ref T8116. This adds a control for creating publishers (default: administrators) and default publisher/package edit controls. I've left the edit defaults at "no one" for now to force you to select a policy. This might be something to look at later. Test Plan: Created publishers, packages. Tried to create publishers with "can create" policy set restrictively. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8116 Differential Revision: https://secure.phabricator.com/D16319 --- src/__phutil_library_map__.php | 8 +++++++ .../PhabricatorPackagesApplication.php | 21 +++++++++++++++++++ ...catorPackagesCreatePublisherCapability.php | 16 ++++++++++++++ ...orPackagesPackageDefaultEditCapability.php | 12 +++++++++++ ...orPackagesPackageDefaultViewCapability.php | 16 ++++++++++++++ ...PackagesPublisherDefaultEditCapability.php | 12 +++++++++++ ...PhabricatorPackagesPublisherEditEngine.php | 5 +++++ .../storage/PhabricatorPackagesPackage.php | 15 ++++++++++++- .../storage/PhabricatorPackagesPublisher.php | 11 +++++++++- 9 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 src/applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php create mode 100644 src/applications/packages/capability/PhabricatorPackagesPackageDefaultEditCapability.php create mode 100644 src/applications/packages/capability/PhabricatorPackagesPackageDefaultViewCapability.php create mode 100644 src/applications/packages/capability/PhabricatorPackagesPublisherDefaultEditCapability.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b85da8bfac..6190d055c9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2974,12 +2974,15 @@ phutil_register_library_map(array( 'PhabricatorPHPMailerConfigOptions' => 'applications/config/option/PhabricatorPHPMailerConfigOptions.php', 'PhabricatorPackagesApplication' => 'applications/packages/application/PhabricatorPackagesApplication.php', 'PhabricatorPackagesController' => 'applications/packages/controller/PhabricatorPackagesController.php', + 'PhabricatorPackagesCreatePublisherCapability' => 'applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php', 'PhabricatorPackagesDAO' => 'applications/packages/storage/PhabricatorPackagesDAO.php', 'PhabricatorPackagesEditEngine' => 'applications/packages/editor/PhabricatorPackagesEditEngine.php', 'PhabricatorPackagesEditor' => 'applications/packages/editor/PhabricatorPackagesEditor.php', 'PhabricatorPackagesPackage' => 'applications/packages/storage/PhabricatorPackagesPackage.php', 'PhabricatorPackagesPackageController' => 'applications/packages/controller/PhabricatorPackagesPackageController.php', 'PhabricatorPackagesPackageDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPackageDatasource.php', + 'PhabricatorPackagesPackageDefaultEditCapability' => 'applications/packages/capability/PhabricatorPackagesPackageDefaultEditCapability.php', + 'PhabricatorPackagesPackageDefaultViewCapability' => 'applications/packages/capability/PhabricatorPackagesPackageDefaultViewCapability.php', 'PhabricatorPackagesPackageEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php', 'PhabricatorPackagesPackageEditController' => 'applications/packages/controller/PhabricatorPackagesPackageEditController.php', 'PhabricatorPackagesPackageEditEngine' => 'applications/packages/editor/PhabricatorPackagesPackageEditEngine.php', @@ -2999,6 +3002,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisher' => 'applications/packages/storage/PhabricatorPackagesPublisher.php', 'PhabricatorPackagesPublisherController' => 'applications/packages/controller/PhabricatorPackagesPublisherController.php', 'PhabricatorPackagesPublisherDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php', + 'PhabricatorPackagesPublisherDefaultEditCapability' => 'applications/packages/capability/PhabricatorPackagesPublisherDefaultEditCapability.php', 'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php', 'PhabricatorPackagesPublisherEditController' => 'applications/packages/controller/PhabricatorPackagesPublisherEditController.php', 'PhabricatorPackagesPublisherEditEngine' => 'applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php', @@ -7789,6 +7793,7 @@ phutil_register_library_map(array( 'PhabricatorPHPMailerConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPackagesApplication' => 'PhabricatorApplication', 'PhabricatorPackagesController' => 'PhabricatorController', + 'PhabricatorPackagesCreatePublisherCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPackagesDAO' => 'PhabricatorLiskDAO', 'PhabricatorPackagesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorPackagesEditor' => 'PhabricatorApplicationTransactionEditor', @@ -7803,6 +7808,8 @@ phutil_register_library_map(array( ), 'PhabricatorPackagesPackageController' => 'PhabricatorPackagesController', 'PhabricatorPackagesPackageDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorPackagesPackageDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorPackagesPackageDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPackagesPackageEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorPackagesPackageEditController' => 'PhabricatorPackagesPackageController', 'PhabricatorPackagesPackageEditEngine' => 'PhabricatorPackagesEditEngine', @@ -7830,6 +7837,7 @@ phutil_register_library_map(array( ), 'PhabricatorPackagesPublisherController' => 'PhabricatorPackagesController', 'PhabricatorPackagesPublisherDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorPackagesPublisherDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorPackagesPublisherEditController' => 'PhabricatorPackagesPublisherController', 'PhabricatorPackagesPublisherEditEngine' => 'PhabricatorPackagesEditEngine', diff --git a/src/applications/packages/application/PhabricatorPackagesApplication.php b/src/applications/packages/application/PhabricatorPackagesApplication.php index e52a26310d..7a100e82b8 100644 --- a/src/applications/packages/application/PhabricatorPackagesApplication.php +++ b/src/applications/packages/application/PhabricatorPackagesApplication.php @@ -26,6 +26,27 @@ final class PhabricatorPackagesApplication extends PhabricatorApplication { return true; } + protected function getCustomCapabilities() { + return array( + PhabricatorPackagesCreatePublisherCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + PhabricatorPackagesPublisherDefaultEditCapability::CAPABILITY => array( + 'caption' => pht('Default edit policy for newly created publishers.'), + 'template' => PhabricatorPackagesPublisherPHIDType::TYPECONST, + 'default' => PhabricatorPolicies::POLICY_NOONE, + ), + PhabricatorPackagesPackageDefaultViewCapability::CAPABILITY => array( + 'caption' => pht('Default edit policy for newly created packages.'), + 'template' => PhabricatorPackagesPackagePHIDType::TYPECONST, + ), + PhabricatorPackagesPackageDefaultEditCapability::CAPABILITY => array( + 'caption' => pht('Default view policy for newly created packages.'), + 'template' => PhabricatorPackagesPackagePHIDType::TYPECONST, + 'default' => PhabricatorPolicies::POLICY_NOONE, + ), + ); + } public function getRoutes() { return array( '/package/' => array( diff --git a/src/applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php b/src/applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php new file mode 100644 index 0000000000..d744c35438 --- /dev/null +++ b/src/applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php @@ -0,0 +1,16 @@ +getURI(); } + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhabricatorPackagesCreatePublisherCapability::CAPABILITY); + } + protected function buildCustomEditFields($object) { $fields = array(); diff --git a/src/applications/packages/storage/PhabricatorPackagesPackage.php b/src/applications/packages/storage/PhabricatorPackagesPackage.php index 472e145900..57fe32e1af 100644 --- a/src/applications/packages/storage/PhabricatorPackagesPackage.php +++ b/src/applications/packages/storage/PhabricatorPackagesPackage.php @@ -19,7 +19,20 @@ final class PhabricatorPackagesPackage private $publisher = self::ATTACHABLE; public static function initializeNewPackage(PhabricatorUser $actor) { - return id(new self()); + $packages_application = id(new PhabricatorApplicationQuery()) + ->setViewer($actor) + ->withClasses(array('PhabricatorPackagesApplication')) + ->executeOne(); + + $view_policy = $packages_application->getPolicy( + PhabricatorPackagesPackageDefaultViewCapability::CAPABILITY); + + $edit_policy = $packages_application->getPolicy( + PhabricatorPackagesPackageDefaultEditCapability::CAPABILITY); + + return id(new self()) + ->setViewPolicy($view_policy) + ->setEditPolicy($edit_policy); } protected function getConfiguration() { diff --git a/src/applications/packages/storage/PhabricatorPackagesPublisher.php b/src/applications/packages/storage/PhabricatorPackagesPublisher.php index 80c797fdb6..09e417f763 100644 --- a/src/applications/packages/storage/PhabricatorPackagesPublisher.php +++ b/src/applications/packages/storage/PhabricatorPackagesPublisher.php @@ -15,7 +15,16 @@ final class PhabricatorPackagesPublisher protected $editPolicy; public static function initializeNewPublisher(PhabricatorUser $actor) { - return id(new self()); + $packages_application = id(new PhabricatorApplicationQuery()) + ->setViewer($actor) + ->withClasses(array('PhabricatorPackagesApplication')) + ->executeOne(); + + $edit_policy = $packages_application->getPolicy( + PhabricatorPackagesPublisherDefaultEditCapability::CAPABILITY); + + return id(new self()) + ->setEditPolicy($edit_policy); } protected function getConfiguration() { From 08a19f35f07c1feb63e4fd51cbc10ef4f02981a3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 22 Jul 2016 06:20:44 -0700 Subject: [PATCH 68/76] Add basic search capabilities to Packages Summary: Ref T8116. Add search-by-name and per-package / per-publisher search to Packages. Test Plan: Searched publishers, packages, versions by name. Searched packages by publisher. Searched versions by package. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8116 Differential Revision: https://secure.phabricator.com/D16320 --- .../20160722.pack.01.pubngrams.sql | 7 ++++++ .../20160722.pack.02.pkgngrams.sql | 7 ++++++ .../20160722.pack.03.versionngrams.sql | 7 ++++++ src/__phutil_library_map__.php | 11 ++++++++++ .../editor/PhabricatorPackagesEditor.php | 10 +++++++++ .../PhabricatorPackagesPackageEditor.php | 6 ----- .../PhabricatorPackagesPublisherEditor.php | 6 ----- .../PhabricatorPackagesVersionEditor.php | 6 ----- .../query/PhabricatorPackagesPackageQuery.php | 8 ++++++- ...PhabricatorPackagesPackageSearchEngine.php | 21 +++++++++++++++++- .../PhabricatorPackagesPublisherQuery.php | 16 +++++++++++--- ...abricatorPackagesPublisherSearchEngine.php | 11 +++++++++- .../query/PhabricatorPackagesVersionQuery.php | 6 +++++ ...PhabricatorPackagesVersionSearchEngine.php | 22 +++++++++++++++++-- .../storage/PhabricatorPackagesNgrams.php | 10 +++++++++ .../storage/PhabricatorPackagesPackage.php | 16 ++++++++++++-- .../PhabricatorPackagesPackageNameNgrams.php | 14 ++++++++++++ .../storage/PhabricatorPackagesPublisher.php | 16 ++++++++++++-- ...PhabricatorPackagesPublisherNameNgrams.php | 14 ++++++++++++ .../storage/PhabricatorPackagesVersion.php | 14 +++++++++++- .../PhabricatorPackagesVersionNameNgrams.php | 14 ++++++++++++ .../PhabricatorPackagesPackageDatasource.php | 2 +- 22 files changed, 212 insertions(+), 32 deletions(-) create mode 100644 resources/sql/autopatches/20160722.pack.01.pubngrams.sql create mode 100644 resources/sql/autopatches/20160722.pack.02.pkgngrams.sql create mode 100644 resources/sql/autopatches/20160722.pack.03.versionngrams.sql create mode 100644 src/applications/packages/storage/PhabricatorPackagesNgrams.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php diff --git a/resources/sql/autopatches/20160722.pack.01.pubngrams.sql b/resources/sql/autopatches/20160722.pack.01.pubngrams.sql new file mode 100644 index 0000000000..956ec58e8b --- /dev/null +++ b/resources/sql/autopatches/20160722.pack.01.pubngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_publishername_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160722.pack.02.pkgngrams.sql b/resources/sql/autopatches/20160722.pack.02.pkgngrams.sql new file mode 100644 index 0000000000..514482539a --- /dev/null +++ b/resources/sql/autopatches/20160722.pack.02.pkgngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_packagename_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160722.pack.03.versionngrams.sql b/resources/sql/autopatches/20160722.pack.03.versionngrams.sql new file mode 100644 index 0000000000..a5f85f546b --- /dev/null +++ b/resources/sql/autopatches/20160722.pack.03.versionngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_versionname_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6190d055c9..3a4d0df442 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2978,6 +2978,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesDAO' => 'applications/packages/storage/PhabricatorPackagesDAO.php', 'PhabricatorPackagesEditEngine' => 'applications/packages/editor/PhabricatorPackagesEditEngine.php', 'PhabricatorPackagesEditor' => 'applications/packages/editor/PhabricatorPackagesEditor.php', + 'PhabricatorPackagesNgrams' => 'applications/packages/storage/PhabricatorPackagesNgrams.php', 'PhabricatorPackagesPackage' => 'applications/packages/storage/PhabricatorPackagesPackage.php', 'PhabricatorPackagesPackageController' => 'applications/packages/controller/PhabricatorPackagesPackageController.php', 'PhabricatorPackagesPackageDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPackageDatasource.php', @@ -2989,6 +2990,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPackageEditor' => 'applications/packages/editor/PhabricatorPackagesPackageEditor.php', 'PhabricatorPackagesPackageKeyTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php', 'PhabricatorPackagesPackageListController' => 'applications/packages/controller/PhabricatorPackagesPackageListController.php', + 'PhabricatorPackagesPackageNameNgrams' => 'applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php', 'PhabricatorPackagesPackageNameTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php', 'PhabricatorPackagesPackagePHIDType' => 'applications/packages/phid/PhabricatorPackagesPackagePHIDType.php', 'PhabricatorPackagesPackagePublisherTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php', @@ -3009,6 +3011,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisherEditor' => 'applications/packages/editor/PhabricatorPackagesPublisherEditor.php', 'PhabricatorPackagesPublisherKeyTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherKeyTransaction.php', 'PhabricatorPackagesPublisherListController' => 'applications/packages/controller/PhabricatorPackagesPublisherListController.php', + 'PhabricatorPackagesPublisherNameNgrams' => 'applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php', 'PhabricatorPackagesPublisherNameTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php', 'PhabricatorPackagesPublisherPHIDType' => 'applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php', 'PhabricatorPackagesPublisherQuery' => 'applications/packages/query/PhabricatorPackagesPublisherQuery.php', @@ -3028,6 +3031,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesVersionEditEngine' => 'applications/packages/editor/PhabricatorPackagesVersionEditEngine.php', 'PhabricatorPackagesVersionEditor' => 'applications/packages/editor/PhabricatorPackagesVersionEditor.php', 'PhabricatorPackagesVersionListController' => 'applications/packages/controller/PhabricatorPackagesVersionListController.php', + 'PhabricatorPackagesVersionNameNgrams' => 'applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php', 'PhabricatorPackagesVersionNameTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php', 'PhabricatorPackagesVersionPHIDType' => 'applications/packages/phid/PhabricatorPackagesVersionPHIDType.php', 'PhabricatorPackagesVersionPackageTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php', @@ -7797,6 +7801,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesDAO' => 'PhabricatorLiskDAO', 'PhabricatorPackagesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorPackagesEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorPackagesNgrams' => 'PhabricatorSearchNgrams', 'PhabricatorPackagesPackage' => array( 'PhabricatorPackagesDAO', 'PhabricatorPolicyInterface', @@ -7805,6 +7810,7 @@ phutil_register_library_map(array( 'PhabricatorSubscribableInterface', 'PhabricatorProjectInterface', 'PhabricatorConduitResultInterface', + 'PhabricatorNgramsInterface', ), 'PhabricatorPackagesPackageController' => 'PhabricatorPackagesController', 'PhabricatorPackagesPackageDatasource' => 'PhabricatorTypeaheadDatasource', @@ -7816,6 +7822,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPackageEditor' => 'PhabricatorPackagesEditor', 'PhabricatorPackagesPackageKeyTransaction' => 'PhabricatorPackagesPackageTransactionType', 'PhabricatorPackagesPackageListController' => 'PhabricatorPackagesPackageController', + 'PhabricatorPackagesPackageNameNgrams' => 'PhabricatorPackagesNgrams', 'PhabricatorPackagesPackageNameTransaction' => 'PhabricatorPackagesPackageTransactionType', 'PhabricatorPackagesPackagePHIDType' => 'PhabricatorPHIDType', 'PhabricatorPackagesPackagePublisherTransaction' => 'PhabricatorPackagesPackageTransactionType', @@ -7834,6 +7841,7 @@ phutil_register_library_map(array( 'PhabricatorSubscribableInterface', 'PhabricatorProjectInterface', 'PhabricatorConduitResultInterface', + 'PhabricatorNgramsInterface', ), 'PhabricatorPackagesPublisherController' => 'PhabricatorPackagesController', 'PhabricatorPackagesPublisherDatasource' => 'PhabricatorTypeaheadDatasource', @@ -7844,6 +7852,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisherEditor' => 'PhabricatorPackagesEditor', 'PhabricatorPackagesPublisherKeyTransaction' => 'PhabricatorPackagesPublisherTransactionType', 'PhabricatorPackagesPublisherListController' => 'PhabricatorPackagesPublisherController', + 'PhabricatorPackagesPublisherNameNgrams' => 'PhabricatorPackagesNgrams', 'PhabricatorPackagesPublisherNameTransaction' => 'PhabricatorPackagesPublisherTransactionType', 'PhabricatorPackagesPublisherPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPackagesPublisherQuery' => 'PhabricatorPackagesQuery', @@ -7865,6 +7874,7 @@ phutil_register_library_map(array( 'PhabricatorSubscribableInterface', 'PhabricatorProjectInterface', 'PhabricatorConduitResultInterface', + 'PhabricatorNgramsInterface', ), 'PhabricatorPackagesVersionController' => 'PhabricatorPackagesController', 'PhabricatorPackagesVersionEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', @@ -7872,6 +7882,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesVersionEditEngine' => 'PhabricatorPackagesEditEngine', 'PhabricatorPackagesVersionEditor' => 'PhabricatorPackagesEditor', 'PhabricatorPackagesVersionListController' => 'PhabricatorPackagesVersionController', + 'PhabricatorPackagesVersionNameNgrams' => 'PhabricatorPackagesNgrams', 'PhabricatorPackagesVersionNameTransaction' => 'PhabricatorPackagesVersionTransactionType', 'PhabricatorPackagesVersionPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPackagesVersionPackageTransaction' => 'PhabricatorPackagesVersionTransactionType', diff --git a/src/applications/packages/editor/PhabricatorPackagesEditor.php b/src/applications/packages/editor/PhabricatorPackagesEditor.php index 5454ba92f4..0065791ad7 100644 --- a/src/applications/packages/editor/PhabricatorPackagesEditor.php +++ b/src/applications/packages/editor/PhabricatorPackagesEditor.php @@ -7,4 +7,14 @@ abstract class PhabricatorPackagesEditor return 'PhabricatorPasteApplication'; } + protected function supportsSearch() { + return true; + } + + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + } diff --git a/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php b/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php index 1446fa3cac..241981d706 100644 --- a/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php +++ b/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php @@ -24,12 +24,6 @@ final class PhabricatorPackagesPackageEditor return $types; } - protected function shouldPublishFeedStory( - PhabricatorLiskDAO $object, - array $xactions) { - return true; - } - protected function getMailTo(PhabricatorLiskDAO $object) { return array(); } diff --git a/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php b/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php index 6e8fb7caf3..5cadb6329f 100644 --- a/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php +++ b/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php @@ -21,12 +21,6 @@ final class PhabricatorPackagesPublisherEditor return $types; } - protected function shouldPublishFeedStory( - PhabricatorLiskDAO $object, - array $xactions) { - return true; - } - protected function getMailTo(PhabricatorLiskDAO $object) { return array(); } diff --git a/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php b/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php index 34ab962fb5..72bde82738 100644 --- a/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php +++ b/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php @@ -15,12 +15,6 @@ final class PhabricatorPackagesVersionEditor return pht('%s created %s.', $author, $object); } - protected function shouldPublishFeedStory( - PhabricatorLiskDAO $object, - array $xactions) { - return true; - } - protected function getMailTo(PhabricatorLiskDAO $object) { return array(); } diff --git a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php index a0e325c453..67cd3954c9 100644 --- a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php +++ b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php @@ -34,6 +34,12 @@ final class PhabricatorPackagesPackageQuery return $this; } + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new PhabricatorPackagesPackageNameNgrams(), + $ngrams); + } + public function newResultObject() { return new PhabricatorPackagesPackage(); } @@ -62,7 +68,7 @@ final class PhabricatorPackagesPackageQuery if ($this->publisherPHIDs !== null) { $where[] = qsprintf( $conn, - 'p.phid IN (%Ls)', + 'p.publisherPHID IN (%Ls)', $this->publisherPHIDs); } diff --git a/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php index 38fd264276..2ba031b057 100644 --- a/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php +++ b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php @@ -18,11 +18,30 @@ final class PhabricatorPackagesPackageSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + + if ($map['publisherPHIDs']) { + $query->withPublisherPHIDs($map['publisherPHIDs']); + } + return $query; } protected function buildCustomSearchFields() { - return array(); + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for packages by name substring.')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Publishers')) + ->setKey('publisherPHIDs') + ->setAliases(array('publisherPHID', 'publisher', 'publishers')) + ->setDatasource(new PhabricatorPackagesPublisherDatasource()) + ->setDescription(pht('Search for packages by publisher.')), + ); } protected function getURI($path) { diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php b/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php index 81a72a923c..d46535c20d 100644 --- a/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php +++ b/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php @@ -22,6 +22,12 @@ final class PhabricatorPackagesPublisherQuery return $this; } + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new PhabricatorPackagesPublisherNameNgrams(), + $ngrams); + } + public function newResultObject() { return new PhabricatorPackagesPublisher(); } @@ -36,25 +42,29 @@ final class PhabricatorPackagesPublisherQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'u.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'u.phid IN (%Ls)', $this->phids); } if ($this->publisherKeys !== null) { $where[] = qsprintf( $conn, - 'publisherKey IN (%Ls)', + 'u.publisherKey IN (%Ls)', $this->publisherKeys); } return $where; } + protected function getPrimaryTableAlias() { + return 'u'; + } + } diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php index 8d44c45ac2..4914fbef9e 100644 --- a/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php +++ b/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php @@ -18,11 +18,20 @@ final class PhabricatorPackagesPublisherSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + return $query; } protected function buildCustomSearchFields() { - return array(); + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for publishers by name substring.')), + ); } protected function getURI($path) { diff --git a/src/applications/packages/query/PhabricatorPackagesVersionQuery.php b/src/applications/packages/query/PhabricatorPackagesVersionQuery.php index 5dad6878b9..6f417e2f77 100644 --- a/src/applications/packages/query/PhabricatorPackagesVersionQuery.php +++ b/src/applications/packages/query/PhabricatorPackagesVersionQuery.php @@ -34,6 +34,12 @@ final class PhabricatorPackagesVersionQuery return $this; } + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new PhabricatorPackagesVersionNameNgrams(), + $ngrams); + } + public function newResultObject() { return new PhabricatorPackagesVersion(); } diff --git a/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php index 40cc42f920..368e7bf02f 100644 --- a/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php +++ b/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php @@ -18,13 +18,31 @@ final class PhabricatorPackagesVersionSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + + if ($map['packagePHIDs']) { + $query->withPackagePHIDs($map['packagePHIDs']); + } + return $query; } protected function buildCustomSearchFields() { - return array(); + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for versions by name substring.')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Packages')) + ->setKey('packagePHIDs') + ->setAliases(array('packagePHID', 'package', 'packages')) + ->setDatasource(new PhabricatorPackagesPackageDatasource()) + ->setDescription(pht('Search for versions by package.')), + ); } - protected function getURI($path) { return '/packages/version/'.$path; } diff --git a/src/applications/packages/storage/PhabricatorPackagesNgrams.php b/src/applications/packages/storage/PhabricatorPackagesNgrams.php new file mode 100644 index 0000000000..be21a63d8c --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesNgrams.php @@ -0,0 +1,10 @@ + true, self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text64', + 'name' => 'sort64', 'packageKey' => 'sort64', ), self::CONFIG_KEY_SCHEMA => array( @@ -207,6 +208,17 @@ final class PhabricatorPackagesPackage } +/* -( PhabricatorNgramsInterface )----------------------------------------- */ + + + public function newNgrams() { + return array( + id(new PhabricatorPackagesPackageNameNgrams()) + ->setValue($this->getName()), + ); + } + + /* -( PhabricatorConduitResultInterface )---------------------------------- */ diff --git a/src/applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php b/src/applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php new file mode 100644 index 0000000000..cfba763075 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php @@ -0,0 +1,14 @@ + true, self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text64', + 'name' => 'sort64', 'publisherKey' => 'sort64', ), self::CONFIG_KEY_SCHEMA => array( @@ -183,6 +184,17 @@ final class PhabricatorPackagesPublisher } +/* -( PhabricatorNgramsInterface )----------------------------------------- */ + + + public function newNgrams() { + return array( + id(new PhabricatorPackagesPublisherNameNgrams()) + ->setValue($this->getName()), + ); + } + + /* -( PhabricatorConduitResultInterface )---------------------------------- */ diff --git a/src/applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php b/src/applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php new file mode 100644 index 0000000000..290404ebe6 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php @@ -0,0 +1,14 @@ +setValue($this->getName()), + ); + } + + /* -( PhabricatorConduitResultInterface )---------------------------------- */ diff --git a/src/applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php b/src/applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php new file mode 100644 index 0000000000..cad5c9ad94 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php @@ -0,0 +1,14 @@ + Date: Fri, 22 Jul 2016 06:53:49 -0700 Subject: [PATCH 69/76] In Packages, give publishers a list of packages and packages a list of versions Summary: Ref T8116. Puts a list of packages on the publisher page, and a list of versions on the package page. Test Plan: Viewed a publisher, saw packages. Viewed a package, saw versions. Looked at list views. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8116 Differential Revision: https://secure.phabricator.com/D16321 --- src/__phutil_library_map__.php | 8 ++++ ...abricatorPackagesPackageViewController.php | 44 +++++++++++++++++- ...ricatorPackagesPublisherViewController.php | 45 ++++++++++++++++++- ...abricatorPackagesVersionViewController.php | 1 + ...PhabricatorPackagesPackageSearchEngine.php | 15 ++----- ...abricatorPackagesPublisherSearchEngine.php | 14 ++---- ...PhabricatorPackagesVersionSearchEngine.php | 14 ++---- .../PhabricatorPackagesPackageListView.php | 41 +++++++++++++++++ .../PhabricatorPackagesPublisherListView.php | 41 +++++++++++++++++ .../PhabricatorPackagesVersionListView.php | 40 +++++++++++++++++ .../packages/view/PhabricatorPackagesView.php | 3 ++ 11 files changed, 233 insertions(+), 33 deletions(-) create mode 100644 src/applications/packages/view/PhabricatorPackagesPackageListView.php create mode 100644 src/applications/packages/view/PhabricatorPackagesPublisherListView.php create mode 100644 src/applications/packages/view/PhabricatorPackagesVersionListView.php create mode 100644 src/applications/packages/view/PhabricatorPackagesView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3a4d0df442..1ac5528904 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2990,6 +2990,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPackageEditor' => 'applications/packages/editor/PhabricatorPackagesPackageEditor.php', 'PhabricatorPackagesPackageKeyTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php', 'PhabricatorPackagesPackageListController' => 'applications/packages/controller/PhabricatorPackagesPackageListController.php', + 'PhabricatorPackagesPackageListView' => 'applications/packages/view/PhabricatorPackagesPackageListView.php', 'PhabricatorPackagesPackageNameNgrams' => 'applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php', 'PhabricatorPackagesPackageNameTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php', 'PhabricatorPackagesPackagePHIDType' => 'applications/packages/phid/PhabricatorPackagesPackagePHIDType.php', @@ -3011,6 +3012,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisherEditor' => 'applications/packages/editor/PhabricatorPackagesPublisherEditor.php', 'PhabricatorPackagesPublisherKeyTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherKeyTransaction.php', 'PhabricatorPackagesPublisherListController' => 'applications/packages/controller/PhabricatorPackagesPublisherListController.php', + 'PhabricatorPackagesPublisherListView' => 'applications/packages/view/PhabricatorPackagesPublisherListView.php', 'PhabricatorPackagesPublisherNameNgrams' => 'applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php', 'PhabricatorPackagesPublisherNameTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php', 'PhabricatorPackagesPublisherPHIDType' => 'applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php', @@ -3031,6 +3033,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesVersionEditEngine' => 'applications/packages/editor/PhabricatorPackagesVersionEditEngine.php', 'PhabricatorPackagesVersionEditor' => 'applications/packages/editor/PhabricatorPackagesVersionEditor.php', 'PhabricatorPackagesVersionListController' => 'applications/packages/controller/PhabricatorPackagesVersionListController.php', + 'PhabricatorPackagesVersionListView' => 'applications/packages/view/PhabricatorPackagesVersionListView.php', 'PhabricatorPackagesVersionNameNgrams' => 'applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php', 'PhabricatorPackagesVersionNameTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php', 'PhabricatorPackagesVersionPHIDType' => 'applications/packages/phid/PhabricatorPackagesVersionPHIDType.php', @@ -3042,6 +3045,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesVersionTransactionQuery' => 'applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php', 'PhabricatorPackagesVersionTransactionType' => 'applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php', 'PhabricatorPackagesVersionViewController' => 'applications/packages/controller/PhabricatorPackagesVersionViewController.php', + 'PhabricatorPackagesView' => 'applications/packages/view/PhabricatorPackagesView.php', 'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php', 'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php', 'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php', @@ -7822,6 +7826,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPackageEditor' => 'PhabricatorPackagesEditor', 'PhabricatorPackagesPackageKeyTransaction' => 'PhabricatorPackagesPackageTransactionType', 'PhabricatorPackagesPackageListController' => 'PhabricatorPackagesPackageController', + 'PhabricatorPackagesPackageListView' => 'PhabricatorPackagesView', 'PhabricatorPackagesPackageNameNgrams' => 'PhabricatorPackagesNgrams', 'PhabricatorPackagesPackageNameTransaction' => 'PhabricatorPackagesPackageTransactionType', 'PhabricatorPackagesPackagePHIDType' => 'PhabricatorPHIDType', @@ -7852,6 +7857,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisherEditor' => 'PhabricatorPackagesEditor', 'PhabricatorPackagesPublisherKeyTransaction' => 'PhabricatorPackagesPublisherTransactionType', 'PhabricatorPackagesPublisherListController' => 'PhabricatorPackagesPublisherController', + 'PhabricatorPackagesPublisherListView' => 'PhabricatorPackagesView', 'PhabricatorPackagesPublisherNameNgrams' => 'PhabricatorPackagesNgrams', 'PhabricatorPackagesPublisherNameTransaction' => 'PhabricatorPackagesPublisherTransactionType', 'PhabricatorPackagesPublisherPHIDType' => 'PhabricatorPHIDType', @@ -7882,6 +7888,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesVersionEditEngine' => 'PhabricatorPackagesEditEngine', 'PhabricatorPackagesVersionEditor' => 'PhabricatorPackagesEditor', 'PhabricatorPackagesVersionListController' => 'PhabricatorPackagesVersionController', + 'PhabricatorPackagesVersionListView' => 'PhabricatorPackagesView', 'PhabricatorPackagesVersionNameNgrams' => 'PhabricatorPackagesNgrams', 'PhabricatorPackagesVersionNameTransaction' => 'PhabricatorPackagesVersionTransactionType', 'PhabricatorPackagesVersionPHIDType' => 'PhabricatorPHIDType', @@ -7893,6 +7900,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesVersionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPackagesVersionTransactionType' => 'PhabricatorPackagesTransactionType', 'PhabricatorPackagesVersionViewController' => 'PhabricatorPackagesVersionController', + 'PhabricatorPackagesView' => 'AphrontView', 'PhabricatorPagerUIExample' => 'PhabricatorUIExample', 'PhabricatorPassphraseApplication' => 'PhabricatorApplication', 'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider', diff --git a/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php b/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php index f0a9483a28..a79f5d3b4e 100644 --- a/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php +++ b/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php @@ -32,14 +32,21 @@ final class PhabricatorPackagesPackageViewController $header = $this->buildHeaderView($package); $curtain = $this->buildCurtain($package); + $versions_view = $this->buildVersionsView($package); + $timeline = $this->buildTransactionTimeline( $package, new PhabricatorPackagesPackageTransactionQuery()); + $timeline->setShouldTerminate(true); $package_view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) - ->setMainColumn($timeline); + ->setMainColumn( + array( + $versions_view, + $timeline, + )); return $this->newPage() ->setCrumbs($crumbs) @@ -84,4 +91,39 @@ final class PhabricatorPackagesPackageViewController return $curtain; } + private function buildVersionsView(PhabricatorPackagesPackage $package) { + $viewer = $this->getViewer(); + + $versions = id(new PhabricatorPackagesVersionQuery()) + ->setViewer($viewer) + ->withPackagePHIDs(array($package->getPHID())) + ->setLimit(25) + ->execute(); + + $versions_list = id(new PhabricatorPackagesVersionListView()) + ->setViewer($viewer) + ->setVersions($versions); + + $all_href = urisprintf( + 'version/?package=%s#R', + $package->getPHID()); + $all_href = $this->getApplicationURI($all_href); + + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-search') + ->setText(pht('View All')) + ->setHref($all_href); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Versions')) + ->addActionLink($view_all); + + $versions_view = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($versions_list); + + return $versions_view; + } } diff --git a/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php b/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php index fad00ad665..f0df21e93f 100644 --- a/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php +++ b/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php @@ -29,14 +29,21 @@ final class PhabricatorPackagesPublisherViewController $header = $this->buildHeaderView($publisher); $curtain = $this->buildCurtain($publisher); + $packages_view = $this->buildPackagesView($publisher); + $timeline = $this->buildTransactionTimeline( $publisher, new PhabricatorPackagesPublisherTransactionQuery()); + $timeline->setShouldTerminate(true); $publisher_view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) - ->setMainColumn($timeline); + ->setMainColumn( + array( + $packages_view, + $timeline, + )); return $this->newPage() ->setCrumbs($crumbs) @@ -81,4 +88,40 @@ final class PhabricatorPackagesPublisherViewController return $curtain; } + private function buildPackagesView(PhabricatorPackagesPublisher $publisher) { + $viewer = $this->getViewer(); + + $packages = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($viewer) + ->withPublisherPHIDs(array($publisher->getPHID())) + ->setLimit(25) + ->execute(); + + $packages_list = id(new PhabricatorPackagesPackageListView()) + ->setViewer($viewer) + ->setPackages($packages); + + $all_href = urisprintf( + 'package/?publisher=%s#R', + $publisher->getPHID()); + $all_href = $this->getApplicationURI($all_href); + + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-search') + ->setText(pht('View All')) + ->setHref($all_href); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Packages')) + ->addActionLink($view_all); + + $packages_view = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($packages_list); + + return $packages_view; + } + } diff --git a/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php b/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php index bf2abd1920..f2aacea057 100644 --- a/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php +++ b/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php @@ -39,6 +39,7 @@ final class PhabricatorPackagesVersionViewController $timeline = $this->buildTransactionTimeline( $version, new PhabricatorPackagesVersionTransactionQuery()); + $timeline->setShouldTerminate(true); $version_view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php index 2ba031b057..e01c8db503 100644 --- a/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php +++ b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php @@ -74,19 +74,12 @@ final class PhabricatorPackagesPackageSearchEngine array $handles) { assert_instances_of($packages, 'PhabricatorPackagesPackage'); - $viewer = $this->requireViewer(); - $list = id(new PHUIObjectItemListView()) - ->setViewer($viewer); - foreach ($packages as $package) { - $item = id(new PHUIObjectItemView()) - ->setObjectName($package->getFullKey()) - ->setHeader($package->getName()) - ->setHref($package->getURI()); - - $list->addItem($item); - } + $list = id(new PhabricatorPackagesPackageListView()) + ->setViewer($viewer) + ->setPackages($packages) + ->newListView(); return id(new PhabricatorApplicationSearchResultView()) ->setObjectList($list) diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php index 4914fbef9e..f11738f730 100644 --- a/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php +++ b/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php @@ -67,16 +67,10 @@ final class PhabricatorPackagesPublisherSearchEngine $viewer = $this->requireViewer(); - $list = id(new PHUIObjectItemListView()) - ->setViewer($viewer); - foreach ($publishers as $publisher) { - $item = id(new PHUIObjectItemView()) - ->setObjectName($publisher->getPublisherKey()) - ->setHeader($publisher->getName()) - ->setHref($publisher->getURI()); - - $list->addItem($item); - } + $list = id(new PhabricatorPackagesPublisherListView()) + ->setViewer($viewer) + ->setPublishers($publishers) + ->newListView(); return id(new PhabricatorApplicationSearchResultView()) ->setObjectList($list) diff --git a/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php index 368e7bf02f..da1581bf80 100644 --- a/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php +++ b/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php @@ -73,18 +73,12 @@ final class PhabricatorPackagesVersionSearchEngine array $handles) { assert_instances_of($versions, 'PhabricatorPackagesVersion'); - $viewer = $this->requireViewer(); - $list = id(new PHUIObjectItemListView()) - ->setViewer($viewer); - foreach ($versions as $version) { - $item = id(new PHUIObjectItemView()) - ->setHeader($version->getName()) - ->setHref($version->getURI()); - - $list->addItem($item); - } + $list = id(new PhabricatorPackagesVersionListView()) + ->setViewer($viewer) + ->setVersions($versions) + ->newListView(); return id(new PhabricatorApplicationSearchResultView()) ->setObjectList($list) diff --git a/src/applications/packages/view/PhabricatorPackagesPackageListView.php b/src/applications/packages/view/PhabricatorPackagesPackageListView.php new file mode 100644 index 0000000000..78022bde38 --- /dev/null +++ b/src/applications/packages/view/PhabricatorPackagesPackageListView.php @@ -0,0 +1,41 @@ +packages = $packages; + return $this; + } + + public function getPackages() { + return $this->packages; + } + + public function render() { + return $this->newListView(); + } + + public function newListView() { + $viewer = $this->getViewer(); + $packages = $this->getPackages(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + + foreach ($packages as $package) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($package->getFullKey()) + ->setHeader($package->getName()) + ->setHref($package->getURI()); + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/packages/view/PhabricatorPackagesPublisherListView.php b/src/applications/packages/view/PhabricatorPackagesPublisherListView.php new file mode 100644 index 0000000000..fe7b2b37e4 --- /dev/null +++ b/src/applications/packages/view/PhabricatorPackagesPublisherListView.php @@ -0,0 +1,41 @@ +publishers = $publishers; + return $this; + } + + public function getPublishers() { + return $this->publishers; + } + + public function render() { + return $this->newListView(); + } + + public function newListView() { + $viewer = $this->getViewer(); + $publishers = $this->getPublishers(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + + foreach ($publishers as $publisher) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($publisher->getPublisherKey()) + ->setHeader($publisher->getName()) + ->setHref($publisher->getURI()); + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/packages/view/PhabricatorPackagesVersionListView.php b/src/applications/packages/view/PhabricatorPackagesVersionListView.php new file mode 100644 index 0000000000..45840016ee --- /dev/null +++ b/src/applications/packages/view/PhabricatorPackagesVersionListView.php @@ -0,0 +1,40 @@ +versions = $versions; + return $this; + } + + public function getVersions() { + return $this->versions; + } + + public function render() { + return $this->newListView(); + } + + public function newListView() { + $viewer = $this->getViewer(); + $versions = $this->getVersions(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + + foreach ($versions as $version) { + $item = id(new PHUIObjectItemView()) + ->setHeader($version->getName()) + ->setHref($version->getURI()); + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/packages/view/PhabricatorPackagesView.php b/src/applications/packages/view/PhabricatorPackagesView.php new file mode 100644 index 0000000000..88cc6730cb --- /dev/null +++ b/src/applications/packages/view/PhabricatorPackagesView.php @@ -0,0 +1,3 @@ + Date: Thu, 28 Jul 2016 07:51:54 -0700 Subject: [PATCH 70/76] Clean up recurring event information on Calendar events Summary: Ref T11326. This adds prev/next links for recurring events (ala D16179) and moves the "accept/decline" buttons closer to the invite list. This might need some fiddling, but should be a little more human-friendly. Test Plan: {F1740541} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16339 --- ...PhabricatorCalendarEventViewController.php | 196 ++++++++++++++---- .../storage/PhabricatorCalendarEvent.php | 2 +- src/view/phui/PHUITwoColumnView.php | 29 ++- 3 files changed, 176 insertions(+), 51 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 08c7d91869..9d1c5b6349 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -49,6 +49,7 @@ final class PhabricatorCalendarEventViewController $subheader = $this->buildSubheaderView($event); $curtain = $this->buildCurtain($event); $details = $this->buildPropertySection($event); + $recurring = $this->buildRecurringSection($event); $description = $this->buildDescriptionView($event); $comment_view = id(new PhabricatorCalendarEventEditEngine()) @@ -58,6 +59,10 @@ final class PhabricatorCalendarEventViewController $timeline->setQuoteRef($monogram); $comment_view->setTransactionTimeline($timeline); + $details_header = id(new PHUIHeaderView()) + ->setHeader(pht('Details')); + $recurring_header = $this->buildRecurringHeader($event); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) @@ -67,7 +72,8 @@ final class PhabricatorCalendarEventViewController $comment_view, )) ->setCurtain($curtain) - ->addPropertySection(pht('Details'), $details) + ->addPropertySection($details_header, $details) + ->addPropertySection($recurring_header, $recurring) ->addPropertySection(pht('Description'), $description); return $this->newPage() @@ -92,10 +98,6 @@ final class PhabricatorCalendarEventViewController $status = pht('Active'); } - $invite_status = $event->getUserInviteStatus($viewer->getPHID()); - $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; - $is_invite_pending = ($invite_status == $status_invited); - $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($event->getName()) @@ -103,24 +105,10 @@ final class PhabricatorCalendarEventViewController ->setPolicyObject($event) ->setHeaderIcon($event->getIcon()); - if ($is_invite_pending) { - $decline_button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-times grey') - ->setHref($this->getApplicationURI("/event/decline/{$id}/")) - ->setWorkflow(true) - ->setText(pht('Decline')); - - $accept_button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-check green') - ->setHref($this->getApplicationURI("/event/accept/{$id}/")) - ->setWorkflow(true) - ->setText(pht('Accept')); - - $header->addActionLink($decline_button) - ->addActionLink($accept_button); + foreach ($this->buildRSVPActions($event) as $action) { + $header->addActionLink($action); } + return $header; } @@ -215,26 +203,6 @@ final class PhabricatorCalendarEventViewController $properties = id(new PHUIPropertyListView()) ->setUser($viewer); - if ($event->getIsRecurring()) { - $properties->addProperty( - pht('Recurs'), - ucwords(idx($event->getRecurrenceFrequency(), 'rule'))); - - if ($event->getRecurrenceEndDate()) { - $properties->addProperty( - pht('Recurrence Ends'), - phabricator_datetime($event->getRecurrenceEndDate(), $viewer)); - } - - if ($event->getInstanceOfEventPHID()) { - $properties->addProperty( - pht('Recurrence of Event'), - pht('%s of %s', - $event->getSequenceIndex(), - $viewer->renderHandle($event->getInstanceOfEventPHID())->render())); - } - } - $invitees = $event->getInvitees(); foreach ($invitees as $key => $invitee) { if ($invitee->isUninvited()) { @@ -293,6 +261,121 @@ final class PhabricatorCalendarEventViewController return $properties; } + private function buildRecurringHeader(PhabricatorCalendarEvent $event) { + $viewer = $this->getViewer(); + + if (!$event->getIsRecurring()) { + return null; + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Recurring Event')); + + $sequence = $event->getSequenceIndex(); + if ($event->isParentEvent()) { + $parent = $event; + } else { + $parent = $event->getParentEvent(); + } + + $next_uri = $parent->getURI().'/'.($sequence + 1); + if ($sequence) { + if ($sequence > 1) { + $previous_uri = $parent->getURI().'/'.($sequence - 1); + } else { + $previous_uri = $parent->getURI(); + } + $has_previous = true; + } else { + $has_previous = false; + $previous_uri = null; + } + + $prev_button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-chevron-left') + ->setHref($previous_uri) + ->setDisabled(!$has_previous) + ->setText(pht('Previous')); + + $next_button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-chevron-right') + ->setHref($next_uri) + ->setText(pht('Next')); + + $header + ->addActionLink($next_button) + ->addActionLink($prev_button); + + return $header; + } + + private function buildRecurringSection(PhabricatorCalendarEvent $event) { + $viewer = $this->getViewer(); + + if (!$event->getIsRecurring()) { + return null; + } + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $is_parent = $event->isParentEvent(); + if ($is_parent) { + $parent_link = null; + } else { + $parent = $event->getParentEvent(); + $parent_link = $viewer + ->renderHandle($parent->getPHID()) + ->render(); + } + + $rule = $event->getFrequencyRule(); + switch ($rule) { + case PhabricatorCalendarEvent::FREQUENCY_DAILY: + if ($is_parent) { + $message = pht('This event repeats every day.'); + } else { + $message = pht( + 'This event is an instance of %s, and repeats every day.', + $parent_link); + } + break; + case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + if ($is_parent) { + $message = pht('This event repeats every week.'); + } else { + $message = pht( + 'This event is an instance of %s, and repeats every week.', + $parent_link); + } + break; + case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + if ($is_parent) { + $message = pht('This event repeats every month.'); + } else { + $message = pht( + 'This event is an instance of %s, and repeats every month.', + $parent_link); + } + break; + case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + if ($is_parent) { + $message = pht('This event repeats every year.'); + } else { + $message = pht( + 'This event is an instance of %s, and repeats every year.', + $parent_link); + } + break; + } + + $properties->addProperty(pht('Event Series'), $message); + + return $properties; + } + private function buildDescriptionView( PhabricatorCalendarEvent $event) { $viewer = $this->getViewer(); @@ -309,7 +392,6 @@ final class PhabricatorCalendarEventViewController return null; } - private function loadEvent() { $request = $this->getRequest(); $viewer = $this->getViewer(); @@ -398,4 +480,32 @@ final class PhabricatorCalendarEventViewController } + private function buildRSVPActions(PhabricatorCalendarEvent $event) { + $viewer = $this->getViewer(); + $id = $event->getID(); + + $invite_status = $event->getUserInviteStatus($viewer->getPHID()); + $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; + $is_invite_pending = ($invite_status == $status_invited); + if (!$is_invite_pending) { + return array(); + } + + $decline_button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-times grey') + ->setHref($this->getApplicationURI("/event/decline/{$id}/")) + ->setWorkflow(true) + ->setText(pht('Decline')); + + $accept_button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-check green') + ->setHref($this->getApplicationURI("/event/accept/{$id}/")) + ->setWorkflow(true) + ->setText(pht('Accept')); + + return array($decline_button, $accept_button); + } + } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index ca42f872f7..6e53751117 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -457,7 +457,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function isParentEvent() { - return ($this->isRecurring && !$this->instanceOfEventPHID); + return ($this->getIsRecurring() && !$this->getInstanceOfEventPHID()); } public function isChildEvent() { diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 4ada03240d..1c56886f7a 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -48,7 +48,10 @@ final class PHUITwoColumnView extends AphrontTagView { } public function addPropertySection($title, $section) { - $this->propertySection[] = array($title, $section); + $this->propertySection[] = array( + 'header' => $title, + 'content' => $section, + ); return $this; } @@ -146,13 +149,25 @@ final class PHUITwoColumnView extends AphrontTagView { $sections = $this->propertySection; if ($sections) { - foreach ($sections as $content) { - if ($content[1]) { - $view[] = id(new PHUIObjectBoxView()) - ->setHeaderText($content[0]) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($content[1]); + foreach ($sections as $section) { + $section_header = $section['header']; + + $section_content = $section['content']; + if ($section_content === null) { + continue; } + + if ($section_header instanceof PHUIHeaderView) { + $header = $section_header; + } else { + $header = id(new PHUIHeaderView()) + ->setHeader($section_header); + } + + $view[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($section_content); } } From c715b42f3612fa47292914c935440f7513f2c7b4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Jul 2016 09:18:47 -0700 Subject: [PATCH 71/76] Fix "Blocked" task queries with multiple subtasks, and update language Summary: Ref T8126. See that task for discussion. This change: - Updates language to be more consistent ("Parents", "Subtasks") since I moved us away from the often-confusing "Block" language in T4788. - Fixes bugs with finding the wrong set of tasks if tasks have a mixture of open and closed subtasks or parents. Test Plan: - Created four tasks: no subtasks, one closed subtask, one open subtask, mixture of open and closed subtasks. - Created four more tasks: no parents, one closed parent, one open parent, mixture of open and closed parents. - Searched for all this stuff, got the proper results: {F1740683} {F1740684} {F1740685} {F1740686} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8126 Differential Revision: https://secure.phabricator.com/D16340 --- .../maniphest/query/ManiphestTaskQuery.php | 168 +++++++++--------- .../query/ManiphestTaskSearchEngine.php | 30 ++-- 2 files changed, 100 insertions(+), 98 deletions(-) diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index ab90029a81..f01346af1e 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -20,6 +20,8 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $subpriorityMin; private $subpriorityMax; private $bridgedObjectPHIDs; + private $hasOpenParents; + private $hasOpenSubtasks; private $fullTextSearch = ''; @@ -51,8 +53,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $needSubscriberPHIDs; private $needProjectPHIDs; - private $blockingTasks; - private $blockedTasks; public function withAuthors(array $authors) { $this->authorPHIDs = $authors; @@ -151,34 +151,16 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } - /** - * True returns tasks that are blocking other tasks only. - * False returns tasks that are not blocking other tasks only. - * Null returns tasks regardless of blocking status. - */ - public function withBlockingTasks($mode) { - $this->blockingTasks = $mode; + public function withOpenSubtasks($value) { + $this->hasOpenSubtasks = $value; return $this; } - public function shouldJoinBlockingTasks() { - return $this->blockingTasks !== null; - } - - /** - * True returns tasks that are blocked by other tasks only. - * False returns tasks that are not blocked by other tasks only. - * Null returns tasks regardless of blocked by status. - */ - public function withBlockedTasks($mode) { - $this->blockedTasks = $mode; + public function withOpenParents($value) { + $this->hasOpenParents = $value; return $this; } - public function shouldJoinBlockedTasks() { - return $this->blockedTasks !== null; - } - public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; @@ -335,7 +317,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $where = parent::buildWhereClauseParts($conn); $where[] = $this->buildStatusWhereClause($conn); - $where[] = $this->buildDependenciesWhereClause($conn); $where[] = $this->buildOwnerWhereClause($conn); $where[] = $this->buildFullTextWhereClause($conn); @@ -526,71 +507,65 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $fulltext_results); } - private function buildDependenciesWhereClause( - AphrontDatabaseConnection $conn) { - - if (!$this->shouldJoinBlockedTasks() && - !$this->shouldJoinBlockingTasks()) { - return null; - } - - $parts = array(); - if ($this->blockingTasks === true) { - $parts[] = qsprintf( - $conn, - 'blocking.dst IS NOT NULL AND blockingtask.status IN (%Ls)', - ManiphestTaskStatus::getOpenStatusConstants()); - } else if ($this->blockingTasks === false) { - $parts[] = qsprintf( - $conn, - 'blocking.dst IS NULL OR blockingtask.status NOT IN (%Ls)', - ManiphestTaskStatus::getOpenStatusConstants()); - } - - if ($this->blockedTasks === true) { - $parts[] = qsprintf( - $conn, - 'blocked.dst IS NOT NULL AND blockedtask.status IN (%Ls)', - ManiphestTaskStatus::getOpenStatusConstants()); - } else if ($this->blockedTasks === false) { - $parts[] = qsprintf( - $conn, - 'blocked.dst IS NULL OR blockedtask.status NOT IN (%Ls)', - ManiphestTaskStatus::getOpenStatusConstants()); - } - - return '('.implode(') OR (', $parts).')'; - } - - protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) { + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $open_statuses = ManiphestTaskStatus::getOpenStatusConstants(); $edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; + $task_table = $this->newResultObject()->getTableName(); $joins = array(); + if ($this->hasOpenParents !== null) { + $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; + + if ($this->hasOpenParents) { + $join_type = 'JOIN'; + } else { + $join_type = 'LEFT JOIN'; + } - if ($this->shouldJoinBlockingTasks()) { $joins[] = qsprintf( - $conn_r, - 'LEFT JOIN %T blocking ON blocking.src = task.phid '. - 'AND blocking.type = %d '. - 'LEFT JOIN %T blockingtask ON blocking.dst = blockingtask.phid', + $conn, + '%Q %T e_parent + ON e_parent.src = task.phid + AND e_parent.type = %d + %Q %T parent + ON e_parent.dst = parent.phid + AND parent.status IN (%Ls)', + $join_type, $edge_table, - ManiphestTaskDependedOnByTaskEdgeType::EDGECONST, - id(new ManiphestTask())->getTableName()); + $parent_type, + $join_type, + $task_table, + $open_statuses); } - if ($this->shouldJoinBlockedTasks()) { + + if ($this->hasOpenSubtasks !== null) { + $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; + + if ($this->hasOpenSubtasks) { + $join_type = 'JOIN'; + } else { + $join_type = 'LEFT JOIN'; + } + $joins[] = qsprintf( - $conn_r, - 'LEFT JOIN %T blocked ON blocked.src = task.phid '. - 'AND blocked.type = %d '. - 'LEFT JOIN %T blockedtask ON blocked.dst = blockedtask.phid', + $conn, + '%Q %T e_subtask + ON e_subtask.src = task.phid + AND e_subtask.type = %d + %Q %T subtask + ON e_subtask.dst = subtask.phid + AND subtask.status IN (%Ls)', + $join_type, $edge_table, - ManiphestTaskDependsOnTaskEdgeType::EDGECONST, - id(new ManiphestTask())->getTableName()); + $subtask_type, + $join_type, + $task_table, + $open_statuses); } if ($this->subscriberPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T e_ccs ON e_ccs.src = task.phid '. 'AND e_ccs.type = %s '. 'AND e_ccs.dst in (%Ls)', @@ -604,7 +579,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $ignore_group_phids = $this->getIgnoreGroupedProjectPHIDs(); if ($ignore_group_phids) { $joins[] = qsprintf( - $conn_r, + $conn, 'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src AND projectGroup.type = %d AND projectGroup.dst NOT IN (%Ls)', @@ -613,29 +588,30 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $ignore_group_phids); } else { $joins[] = qsprintf( - $conn_r, + $conn, 'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src AND projectGroup.type = %d', $edge_table, PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); } $joins[] = qsprintf( - $conn_r, + $conn, 'LEFT JOIN %T projectGroupName ON projectGroup.dst = projectGroupName.indexedObjectPHID', id(new ManiphestNameIndex())->getTableName()); break; } - $joins[] = parent::buildJoinClauseParts($conn_r); + $joins[] = parent::buildJoinClauseParts($conn); return $joins; } protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { - $joined_multiple_rows = $this->shouldJoinBlockingTasks() || - $this->shouldJoinBlockedTasks() || - ($this->shouldGroupQueryResultRows()); + $joined_multiple_rows = + ($this->hasOpenParents !== null) || + ($this->hasOpenSubtasks !== null) || + $this->shouldGroupQueryResultRows(); $joined_project_name = ($this->groupBy == self::GROUP_PROJECT); @@ -652,6 +628,30 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { } } + + protected function buildHavingClauseParts(AphrontDatabaseConnection $conn) { + $having = parent::buildHavingClauseParts($conn); + + if ($this->hasOpenParents !== null) { + if (!$this->hasOpenParents) { + $having[] = qsprintf( + $conn, + 'COUNT(parent.phid) = 0'); + } + } + + if ($this->hasOpenSubtasks !== null) { + if (!$this->hasOpenSubtasks) { + $having[] = qsprintf( + $conn, + 'COUNT(subtask.phid) = 0'); + } + } + + return $having; + } + + /** * Return project PHIDs which we should ignore when grouping tasks by * project. For example, if a user issues a query like: diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 0287966fa3..63d1688f5f 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -77,19 +77,21 @@ final class ManiphestTaskSearchEngine ->setLabel(pht('Contains Words')) ->setKey('fulltext'), id(new PhabricatorSearchThreeStateField()) - ->setLabel(pht('Blocking')) - ->setKey('blocking') + ->setLabel(pht('Open Parents')) + ->setKey('hasParents') + ->setAliases(array('blocking')) ->setOptions( pht('(Show All)'), - pht('Show Only Tasks Blocking Other Tasks'), - pht('Hide Tasks Blocking Other Tasks')), + pht('Show Only Tasks With Open Parents'), + pht('Show Only Tasks Without Open Parents')), id(new PhabricatorSearchThreeStateField()) - ->setLabel(pht('Blocked')) - ->setKey('blocked') + ->setLabel(pht('Open Subtasks')) + ->setKey('hasSubtasks') + ->setAliases(array('blocked')) ->setOptions( pht('(Show All)'), - pht('Show Only Task Blocked By Other Tasks'), - pht('Hide Tasks Blocked By Other Tasks')), + pht('Show Only Tasks With Open Subtasks'), + pht('Show Only Tasks Without Open Subtasks')), id(new PhabricatorSearchSelectField()) ->setLabel(pht('Group By')) ->setKey('group') @@ -121,8 +123,8 @@ final class ManiphestTaskSearchEngine 'statuses', 'priorities', 'fulltext', - 'blocking', - 'blocked', + 'hasParents', + 'hasSubtasks', 'group', 'order', 'ids', @@ -182,12 +184,12 @@ final class ManiphestTaskSearchEngine $query->withDateModifiedBefore($map['modifiedEnd']); } - if ($map['blocking'] !== null) { - $query->withBlockingTasks($map['blocking']); + if ($map['hasParents'] !== null) { + $query->withOpenParents($map['hasParents']); } - if ($map['blocked'] !== null) { - $query->withBlockedTasks($map['blocked']); + if ($map['hasSubtasks'] !== null) { + $query->withOpenSubtasks($map['hasSubtasks']); } if (strlen($map['fulltext'])) { From a372627fcd5454cb7073da9e5e65f49532261c8d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Jul 2016 10:47:40 -0700 Subject: [PATCH 72/76] Provide URI/API support for querying subtasks/parents of a particular task Summary: Ref T8126. Ref T4788. This adds a way to query by parent or subtask. I plan to link to this from the task graph (e.g., {nav View > Search Subtasks} or similar, in a dropdown on the "Task Graph" element) as a way to let us bail out if tasks have 300 subtasks and send the user to a big query result list. That'll give us more flexibility to tailor the UI for reasonable numbers of tasks. There's no UI for this unless you specify a query yourself, so the only ways to get to it are: - Manually put `?parentIDs=...` into the URI. - Use the API. - Future link from task graphs. It doesn't seem too useful to me on its own, outside of the context of links from tasks. Test Plan: - Manually put `?parentIDs=...` and `?subtaskIDs=...` into Maniphest query UI, got expected results. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4788, T8126 Differential Revision: https://secure.phabricator.com/D16341 --- .../maniphest/query/ManiphestTaskQuery.php | 51 +++++++++++++++++-- .../query/ManiphestTaskSearchEngine.php | 18 +++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index f01346af1e..8d2fe79735 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -22,6 +22,8 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $bridgedObjectPHIDs; private $hasOpenParents; private $hasOpenSubtasks; + private $parentTaskIDs; + private $subtaskIDs; private $fullTextSearch = ''; @@ -161,6 +163,16 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } + public function withParentTaskIDs(array $ids) { + $this->parentTaskIDs = $ids; + return $this; + } + + public function withSubtaskIDs(array $ids) { + $this->subtaskIDs = $ids; + return $this; + } + public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; @@ -512,10 +524,11 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; $task_table = $this->newResultObject()->getTableName(); + $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; + $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; + $joins = array(); if ($this->hasOpenParents !== null) { - $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; - if ($this->hasOpenParents) { $join_type = 'JOIN'; } else { @@ -539,8 +552,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { } if ($this->hasOpenSubtasks !== null) { - $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; - if ($this->hasOpenSubtasks) { $join_type = 'JOIN'; } else { @@ -602,6 +613,36 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { break; } + if ($this->parentTaskIDs !== null) { + $joins[] = qsprintf( + $conn, + 'JOIN %T e_has_parent + ON e_has_parent.src = task.phid + AND e_has_parent.type = %d + JOIN %T has_parent + ON e_has_parent.dst = has_parent.phid + AND has_parent.id IN (%Ld)', + $edge_table, + $parent_type, + $task_table, + $this->parentTaskIDs); + } + + if ($this->subtaskIDs !== null) { + $joins[] = qsprintf( + $conn, + 'JOIN %T e_has_subtask + ON e_has_subtask.src = task.phid + AND e_has_subtask.type = %d + JOIN %T has_subtask + ON e_has_subtask.dst = has_subtask.phid + AND has_subtask.id IN (%Ld)', + $edge_table, + $subtask_type, + $task_table, + $this->subtaskIDs); + } + $joins[] = parent::buildJoinClauseParts($conn); return $joins; @@ -611,6 +652,8 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $joined_multiple_rows = ($this->hasOpenParents !== null) || ($this->hasOpenSubtasks !== null) || + ($this->parentTaskIDs !== null) || + ($this->subtaskIDs !== null) || $this->shouldGroupQueryResultRows(); $joined_project_name = ($this->groupBy == self::GROUP_PROJECT); diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 63d1688f5f..0022940542 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -92,6 +92,14 @@ final class ManiphestTaskSearchEngine pht('(Show All)'), pht('Show Only Tasks With Open Subtasks'), pht('Show Only Tasks Without Open Subtasks')), + id(new PhabricatorIDsSearchField()) + ->setLabel(pht('Parent IDs')) + ->setKey('parentIDs') + ->setAliases(array('parentID')), + id(new PhabricatorIDsSearchField()) + ->setLabel(pht('Subtask IDs')) + ->setKey('subtaskIDs') + ->setAliases(array('subtaskID')), id(new PhabricatorSearchSelectField()) ->setLabel(pht('Group By')) ->setKey('group') @@ -125,6 +133,8 @@ final class ManiphestTaskSearchEngine 'fulltext', 'hasParents', 'hasSubtasks', + 'parentIDs', + 'subtaskIDs', 'group', 'order', 'ids', @@ -196,6 +206,14 @@ final class ManiphestTaskSearchEngine $query->withFullTextSearch($map['fulltext']); } + if ($map['parentIDs']) { + $query->withParentTaskIDs($map['parentIDs']); + } + + if ($map['subtaskIDs']) { + $query->withSubtaskIDs($map['subtaskIDs']); + } + $group = idx($map, 'group'); $group = idx($this->getGroupValues(), $group); if ($group) { From cebf4bbec6ba2a11bb90736d0189ebd8e597c359 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Jul 2016 11:45:42 -0700 Subject: [PATCH 73/76] In Task Graphs, provide a parent/child hint and fix weird strikethrough Summary: Fixes T11386. Ref T4788. - Apparently fix weird strikethrough effect? Spooky! - Provide a little icon hint in the left column about which tasks are direct parents/children, vs just reachable somehow. I don't think this is super useful/important, but seems maybe nice? Test Plan: {F1740779} Reviewers: chad Reviewed By: chad Maniphest Tasks: T4788, T11386 Differential Revision: https://secure.phabricator.com/D16342 --- resources/celerity/map.php | 6 +- .../graph/ManiphestTaskGraph.php | 55 ++++++++++++++++++- .../graph/PhabricatorObjectGraph.php | 4 ++ webroot/rsrc/css/aphront/table-view.css | 4 +- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b9eac87871..33f5f5af12 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'efc0f11c', + 'core.pkg.css' => '8b87d014', 'core.pkg.js' => '13c7e56a', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3fb7f532', @@ -25,7 +25,7 @@ return array( 'rsrc/css/aphront/notification.css' => '3f6c89c9', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758', - 'rsrc/css/aphront/table-view.css' => '8df59783', + 'rsrc/css/aphront/table-view.css' => '832656fd', 'rsrc/css/aphront/tokenizer.css' => '056da01b', 'rsrc/css/aphront/tooltip.css' => '1a07aea8', 'rsrc/css/aphront/typeahead-browse.css' => '8904346a', @@ -537,7 +537,7 @@ return array( 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '8df59783', + 'aphront-table-view-css' => '832656fd', 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', diff --git a/src/infrastructure/graph/ManiphestTaskGraph.php b/src/infrastructure/graph/ManiphestTaskGraph.php index 1568c8c656..f2f8f8843d 100644 --- a/src/infrastructure/graph/ManiphestTaskGraph.php +++ b/src/infrastructure/graph/ManiphestTaskGraph.php @@ -3,6 +3,8 @@ final class ManiphestTaskGraph extends PhabricatorObjectGraph { + private $seedMaps = array(); + protected function getEdgeTypes() { return array( ManiphestTaskDependedOnByTaskEdgeType::EDGECONST, @@ -57,7 +59,12 @@ final class ManiphestTaskGraph $object->getTitle()); $link = array( - $object->getMonogram(), + phutil_tag( + 'span', + array( + 'class' => 'object-name', + ), + $object->getMonogram()), ' ', $link, ); @@ -67,9 +74,33 @@ final class ManiphestTaskGraph $link = $viewer->renderHandle($phid); } + + + if ($this->isParentTask($object)) { + $marker = 'fa-chevron-circle-up bluegrey'; + $marker_tip = pht('Direct Parent'); + } else if ($this->isChildTask($object)) { + $marker = 'fa-chevron-circle-down bluegrey'; + $marker_tip = pht('Direct Subtask'); + } else { + $marker = null; + } + + if ($marker) { + $marker = id(new PHUIIconView()) + ->setIcon($marker) + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => $marker_tip, + 'align' => 'E', + )); + } + $link = AphrontTableView::renderSingleDisplayLine($link); return array( + $marker, $trace, $status, $assigned, @@ -81,6 +112,7 @@ final class ManiphestTaskGraph return $table ->setHeaders( array( + null, null, pht('Status'), pht('Assigned'), @@ -88,6 +120,7 @@ final class ManiphestTaskGraph )) ->setColumnClasses( array( + 'nudgeright', 'threads', 'graph-status', null, @@ -95,4 +128,24 @@ final class ManiphestTaskGraph )); } + private function isParentTask(ManiphestTask $task) { + $map = $this->getSeedMap(ManiphestTaskDependedOnByTaskEdgeType::EDGECONST); + return isset($map[$task->getPHID()]); + } + + private function isChildTask(ManiphestTask $task) { + $map = $this->getSeedMap(ManiphestTaskDependsOnTaskEdgeType::EDGECONST); + return isset($map[$task->getPHID()]); + } + + private function getSeedMap($type) { + if (!isset($this->seedMaps[$type])) { + $maps = $this->getEdges($type); + $phids = idx($maps, $this->getSeedPHID(), array()); + $phids = array_fuse($phids); + $this->seedMaps[$type] = $phids; + } + + return $this->seedMaps[$type]; + } } diff --git a/src/infrastructure/graph/PhabricatorObjectGraph.php b/src/infrastructure/graph/PhabricatorObjectGraph.php index 5e25cd8e33..0e8cd7e906 100644 --- a/src/infrastructure/graph/PhabricatorObjectGraph.php +++ b/src/infrastructure/graph/PhabricatorObjectGraph.php @@ -50,6 +50,10 @@ abstract class PhabricatorObjectGraph )); } + final public function getSeedPHID() { + return $this->seedPHID; + } + final public function isEmpty() { return (count($this->getNodes()) <= 2); } diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index dddd78f103..bf40c0e352 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -228,8 +228,8 @@ span.single-display-line-content { position: static; } -.aphront-table-view tr.closed td.object-link, -.aphront-table-view tr.alt-closed td.object-link { +.aphront-table-view tr.closed td.object-link .object-name, +.aphront-table-view tr.alt-closed td.object-link .object-name { text-decoration: line-through; color: rgba({$alphablack}, 0.5); } From ef5cb0630feba5f87434f6ee7e6ff09171004c72 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Jul 2016 13:26:36 -0700 Subject: [PATCH 74/76] Provide a link to parent/child tasks as a search result from task graphs Summary: Ref T4788. Add links to jump to search results with a task's parents or subtasks. This allows relationships to remain useful if there are a zillion of them, and you can sort/filter stuff more easily. Language might need some tweaking at some point, feeling a little un-brainy today with wordstuff. Test Plan: {F1740855} {F1740856} Reviewers: chad Reviewed By: chad Maniphest Tasks: T4788 Differential Revision: https://secure.phabricator.com/D16343 --- .../ManiphestTaskDetailController.php | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 34c7f54c11..0bc186f94f 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -106,7 +106,51 @@ final class ManiphestTaskDetailController extends ManiphestController { $graph_table = $task_graph->newGraphTable(); } - $view->addPropertySection(pht('Task Graph'), $graph_table); + $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; + $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; + + $parent_map = $task_graph->getEdges($parent_type); + $subtask_map = $task_graph->getEdges($subtask_type); + + $has_parents = (bool)idx($parent_map, $task->getPHID()); + $has_subtasks = (bool)idx($subtask_map, $task->getPHID()); + + $parents_uri = urisprintf( + '/?subtaskIDs=%d#R', + $task->getID()); + $parents_uri = $this->getApplicationURI($parents_uri); + + $subtasks_uri = urisprintf( + '/?parentIDs=%d#R', + $task->getID()); + $subtasks_uri = $this->getApplicationURI($subtasks_uri); + + $dropdown_menu = id(new PhabricatorActionListView()) + ->setViewer($viewer) + ->addAction( + id(new PhabricatorActionView()) + ->setHref($parents_uri) + ->setName(pht('Search Parent Tasks')) + ->setDisabled(!$has_parents) + ->setIcon('fa-chevron-circle-up')) + ->addAction( + id(new PhabricatorActionView()) + ->setHref($subtasks_uri) + ->setName(pht('Search Subtasks')) + ->setDisabled(!$has_subtasks) + ->setIcon('fa-chevron-circle-down')); + + $graph_menu = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-search') + ->setText(pht('Search...')) + ->setDropdownMenu($dropdown_menu); + + $graph_header = id(new PHUIHeaderView()) + ->setHeader(pht('Task Graph')) + ->addActionLink($graph_menu); + + $view->addPropertySection($graph_header, $graph_table); } return $this->newPage() From 15c7eb142570360df4d071dc716c2e70b0191886 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Jul 2016 13:54:10 -0700 Subject: [PATCH 75/76] When a task graph has too much stuff, only show adjacent nodes (direct parents/children) Summary: Ref T4788. This gives us a new level of graceful degradation, so now we show: - Zero through 100 connected tasks: whole graph. - More than 100 connnected tasks, but fewer than 100 direct parents/subtasks: just parents and subtasks, with "..." to hint that the graph is cut off. - More than 100 parents and children: just the "sorry, too much stuff" error message. Test Plan: {F1740882} Reviewers: chad Reviewed By: chad Maniphest Tasks: T4788 Differential Revision: https://secure.phabricator.com/D16344 --- .../ManiphestTaskDetailController.php | 41 +++++++++----- .../graph/ManiphestTaskGraph.php | 12 +++++ .../graph/PhabricatorObjectGraph.php | 53 +++++++++++++++++++ 3 files changed, 92 insertions(+), 14 deletions(-) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 0bc186f94f..4e27479337 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -94,27 +94,40 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setLimit($graph_limit) ->loadGraph(); if (!$task_graph->isEmpty()) { - if ($task_graph->isOverLimit()) { + $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; + $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; + $parent_map = $task_graph->getEdges($parent_type); + $subtask_map = $task_graph->getEdges($subtask_type); + $parent_list = idx($parent_map, $task->getPHID(), array()); + $subtask_list = idx($subtask_map, $task->getPHID(), array()); + $has_parents = (bool)$parent_list; + $has_subtasks = (bool)$subtask_list; + + $search_text = pht('Search...'); + + // First, get a count of direct parent tasks and subtasks. If there + // are too many of these, we just don't draw anything. You can use + // the search button to browse tasks with the search UI instead. + $direct_count = count($parent_list) + count($subtask_list); + + if ($direct_count > $graph_limit) { $message = pht( - 'Task graph too large to display (this task is connected to '. - 'more than %s other tasks).', - $graph_limit); + 'Task graph too large to display (this task is directly connected '. + 'to more than %s other tasks). Use %s to explore connected tasks.', + $graph_limit, + phutil_tag('strong', array(), $search_text)); $message = phutil_tag('em', array(), $message); $graph_table = id(new PHUIPropertyListView()) ->addTextContent($message); } else { + // If there aren't too many direct tasks, but there are too many total + // tasks, we'll only render directly connected tasks. + if ($task_graph->isOverLimit()) { + $task_graph->setRenderOnlyAdjacentNodes(true); + } $graph_table = $task_graph->newGraphTable(); } - $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; - $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; - - $parent_map = $task_graph->getEdges($parent_type); - $subtask_map = $task_graph->getEdges($subtask_type); - - $has_parents = (bool)idx($parent_map, $task->getPHID()); - $has_subtasks = (bool)idx($subtask_map, $task->getPHID()); - $parents_uri = urisprintf( '/?subtaskIDs=%d#R', $task->getID()); @@ -143,7 +156,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $graph_menu = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-search') - ->setText(pht('Search...')) + ->setText($search_text) ->setDropdownMenu($dropdown_menu); $graph_header = id(new PHUIHeaderView()) diff --git a/src/infrastructure/graph/ManiphestTaskGraph.php b/src/infrastructure/graph/ManiphestTaskGraph.php index f2f8f8843d..53727b3779 100644 --- a/src/infrastructure/graph/ManiphestTaskGraph.php +++ b/src/infrastructure/graph/ManiphestTaskGraph.php @@ -148,4 +148,16 @@ final class ManiphestTaskGraph return $this->seedMaps[$type]; } + + protected function newEllipsisRow() { + return array( + null, + null, + null, + null, + pht("\xC2\xB7 \xC2\xB7 \xC2\xB7"), + ); + } + + } diff --git a/src/infrastructure/graph/PhabricatorObjectGraph.php b/src/infrastructure/graph/PhabricatorObjectGraph.php index 0e8cd7e906..48e0fcbe73 100644 --- a/src/infrastructure/graph/PhabricatorObjectGraph.php +++ b/src/infrastructure/graph/PhabricatorObjectGraph.php @@ -10,6 +10,7 @@ abstract class PhabricatorObjectGraph private $objects; private $loadEntireGraph = false; private $limit; + private $adjacent; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -33,6 +34,15 @@ abstract class PhabricatorObjectGraph return $this->limit; } + final public function setRenderOnlyAdjacentNodes($adjacent) { + $this->adjacent = $adjacent; + return $this; + } + + final public function getRenderOnlyAdjacentNodes() { + return $this->adjacent; + } + abstract protected function getEdgeTypes(); abstract protected function getParentEdgeType(); abstract protected function newQuery(); @@ -40,6 +50,12 @@ abstract class PhabricatorObjectGraph abstract protected function newTable(AphrontTableView $table); abstract protected function isClosed($object); + protected function newEllipsisRow() { + return array( + '...', + ); + } + final public function setSeedPHID($phid) { $this->seedPHID = $phid; $this->edgeReach[$phid] = array_fill_keys($this->getEdgeTypes(), true); @@ -139,6 +155,32 @@ abstract class PhabricatorObjectGraph $ancestry = $this->getEdges($this->getParentEdgeType()); + $only_adjacent = $this->getRenderOnlyAdjacentNodes(); + if ($only_adjacent) { + $adjacent = array( + $this->getSeedPHID() => $this->getSeedPHID(), + ); + + foreach ($this->getEdgeTypes() as $edge_type) { + $map = $this->getEdges($edge_type); + $direct = idx($map, $this->getSeedPHID(), array()); + $adjacent += array_fuse($direct); + } + + foreach ($ancestry as $key => $list) { + if (!isset($adjacent[$key])) { + unset($ancestry[$key]); + continue; + } + + foreach ($list as $list_key => $item) { + if (!isset($adjacent[$item])) { + unset($ancestry[$key][$list_key]); + } + } + } + } + $objects = $this->newQuery() ->setViewer($viewer) ->withPHIDs(array_keys($ancestry)) @@ -157,6 +199,12 @@ abstract class PhabricatorObjectGraph $ii = 0; $rows = array(); $rowc = array(); + + if ($only_adjacent) { + $rows[] = $this->newEllipsisRow(); + $rowc[] = 'more'; + } + foreach ($ancestry as $phid => $ignored) { $object = idx($objects, $phid); $rows[] = $this->newTableRow($phid, $object, $traces[$ii++]); @@ -181,6 +229,11 @@ abstract class PhabricatorObjectGraph $rowc[] = $classes; } + if ($only_adjacent) { + $rows[] = $this->newEllipsisRow(); + $rowc[] = 'more'; + } + $table = id(new AphrontTableView($rows)) ->setClassName('object-graph-table') ->setRowClasses($rowc); From bbc2ae78589ba2579c50cfe5c08d8b803cb39d0a Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 29 Jul 2016 07:02:36 -0700 Subject: [PATCH 76/76] Fix task graph fatal for graphs containing restricted tasks Summary: Fixes T11392. If some tasks are restricted, we only have PHIDs for them, not objects. Just use the PHIDs instead. Test Plan: {F1741335} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11392 Differential Revision: https://secure.phabricator.com/D16345 --- src/infrastructure/graph/ManiphestTaskGraph.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/infrastructure/graph/ManiphestTaskGraph.php b/src/infrastructure/graph/ManiphestTaskGraph.php index 53727b3779..2fe62af599 100644 --- a/src/infrastructure/graph/ManiphestTaskGraph.php +++ b/src/infrastructure/graph/ManiphestTaskGraph.php @@ -74,12 +74,10 @@ final class ManiphestTaskGraph $link = $viewer->renderHandle($phid); } - - - if ($this->isParentTask($object)) { + if ($this->isParentTask($phid)) { $marker = 'fa-chevron-circle-up bluegrey'; $marker_tip = pht('Direct Parent'); - } else if ($this->isChildTask($object)) { + } else if ($this->isChildTask($phid)) { $marker = 'fa-chevron-circle-down bluegrey'; $marker_tip = pht('Direct Subtask'); } else { @@ -128,14 +126,14 @@ final class ManiphestTaskGraph )); } - private function isParentTask(ManiphestTask $task) { + private function isParentTask($task_phid) { $map = $this->getSeedMap(ManiphestTaskDependedOnByTaskEdgeType::EDGECONST); - return isset($map[$task->getPHID()]); + return isset($map[$task_phid]); } - private function isChildTask(ManiphestTask $task) { + private function isChildTask($task_phid) { $map = $this->getSeedMap(ManiphestTaskDependsOnTaskEdgeType::EDGECONST); - return isset($map[$task->getPHID()]); + return isset($map[$task_phid]); } private function getSeedMap($type) {