From 4e67413c2ce74f63b98400b6473e275f62147b68 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 25 Jul 2015 08:58:22 -0700 Subject: [PATCH 001/102] Slightly smaller badges Summary: Drops size about 20%. Ref T8954 Test Plan: Review in timeline, hovercards Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8954 Differential Revision: https://secure.phabricator.com/D13710 --- resources/celerity/map.php | 8 ++++---- webroot/rsrc/css/layout/phabricator-hovercard-view.css | 10 ++++++++++ webroot/rsrc/css/phui/phui-badge.css | 10 +++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c9f7585f45..5c319ed911 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -117,7 +117,7 @@ return array( 'rsrc/css/font/font-roboto-slab.css' => 'f24a53cb', 'rsrc/css/font/phui-font-icon-base.css' => '3dad2ae3', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', - 'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52', + 'rsrc/css/layout/phabricator-hovercard-view.css' => '2ca21170', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'bec2458e', 'rsrc/css/layout/phabricator-source-code-view.css' => '5e0178de', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', @@ -126,7 +126,7 @@ return array( 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '3ee9afd5', - 'rsrc/css/phui/phui-badge.css' => 'b6218fa8', + 'rsrc/css/phui/phui-badge.css' => '26db39af', 'rsrc/css/phui/phui-box.css' => 'a5bb366d', 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => 'd842f867', @@ -732,7 +732,7 @@ return array( 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => '5337623f', 'phabricator-hovercard' => '14ac66f5', - 'phabricator-hovercard-view-css' => '1239cd52', + 'phabricator-hovercard-view-css' => '2ca21170', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c1700f6f', 'phabricator-main-menu-view' => '2f670a96', @@ -775,7 +775,7 @@ return array( 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => 'd1861e06', 'phui-action-panel-css' => '3ee9afd5', - 'phui-badge-view-css' => 'b6218fa8', + 'phui-badge-view-css' => '26db39af', 'phui-box-css' => 'a5bb366d', 'phui-button-css' => '16020a60', 'phui-calendar-css' => 'ccabe893', diff --git a/webroot/rsrc/css/layout/phabricator-hovercard-view.css b/webroot/rsrc/css/layout/phabricator-hovercard-view.css index 23b3956763..321e281add 100644 --- a/webroot/rsrc/css/layout/phabricator-hovercard-view.css +++ b/webroot/rsrc/css/layout/phabricator-hovercard-view.css @@ -91,6 +91,16 @@ float: left; } +.hovercard-badges .phui-badge-mini { + height: 25px; + width: 25px; + line-height: 24px; +} + +.hovercard-badges .phui-badge-mini .phui-icon-view { + font-size: 12px; +} + .phabricator-hovercard-tail { width: 396px; float: left; diff --git a/webroot/rsrc/css/phui/phui-badge.css b/webroot/rsrc/css/phui/phui-badge.css index e5f04e2707..f613956844 100644 --- a/webroot/rsrc/css/phui/phui-badge.css +++ b/webroot/rsrc/css/phui/phui-badge.css @@ -171,12 +171,12 @@ .phui-badge-mini { background-color: {$greyborder}; border-radius: 18px; - height: 25px; - width: 25px; - line-height: 24px; + height: 20px; + width: 20px; + line-height: 19px; text-align: center; display: inline-block; - opacity: 0.7; + opacity: 0.8; } .phui-badge-mini:hover { @@ -185,7 +185,7 @@ .phui-badge-mini .phui-icon-view { color: #fff; - font-size: 12px; + font-size: 10px; margin: 0; } From 6337fa99a3abc4c091627cad3f51cc17f0faebf9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 25 Jul 2015 12:21:50 -0700 Subject: [PATCH 002/102] Render description changes in countdown timeline Summary: Renders standard description was changed timeline transaction in Countdown Test Plan: Edit Description, see transaction and change dialog. Reviewers: btrahan, epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13711 --- .../PhabricatorCountdownTransaction.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php index e3241acd63..eace665a89 100644 --- a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php +++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php @@ -152,4 +152,29 @@ final class PhabricatorCountdownTransaction return $tags; } + public function shouldHide() { + $old = $this->getOldValue(); + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($old === null); + } + return parent::shouldHide(); + } + + public function hasChangeDetails() { + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($this->getOldValue() !== null); + } + + return parent::hasChangeDetails(); + } + + public function renderChangeDetails(PhabricatorUser $viewer) { + return $this->renderTextCorpusChangeDetails( + $viewer, + $this->getOldValue(), + $this->getNewValue()); + } + } From 82edde8876ee5959c9aa77ffcc10fe7a94ff131f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 25 Jul 2015 13:34:43 -0700 Subject: [PATCH 003/102] Add MAILTAGs to Badges Summary: Still doesn't mail yet, but the settings now show up. Test Plan: View email settings, see Badges options. Reviewers: eadler, epriestley Reviewed By: eadler, epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13712 --- .../autopatches/20150725.badges.mailkey.1.sql | 2 ++ .../autopatches/20150725.badges.mailkey.2.php | 18 ++++++++++++ src/__phutil_library_map__.php | 4 +++ .../badges/editor/PhabricatorBadgesEditor.php | 19 ++++++++++++- .../mail/PhabricatorBadgesMailReceiver.php | 28 +++++++++++++++++++ .../mail/PhabricatorBadgesReplyHandler.php | 16 +++++++++++ .../badges/storage/PhabricatorBadgesBadge.php | 9 ++++++ .../storage/PhabricatorBadgesTransaction.php | 27 ++++++++++++++++++ 8 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20150725.badges.mailkey.1.sql create mode 100644 resources/sql/autopatches/20150725.badges.mailkey.2.php create mode 100644 src/applications/badges/mail/PhabricatorBadgesMailReceiver.php create mode 100644 src/applications/badges/mail/PhabricatorBadgesReplyHandler.php diff --git a/resources/sql/autopatches/20150725.badges.mailkey.1.sql b/resources/sql/autopatches/20150725.badges.mailkey.1.sql new file mode 100644 index 0000000000..5219a759b3 --- /dev/null +++ b/resources/sql/autopatches/20150725.badges.mailkey.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_badges.badges_badge + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20150725.badges.mailkey.2.php b/resources/sql/autopatches/20150725.badges.mailkey.2.php new file mode 100644 index 0000000000..8231492478 --- /dev/null +++ b/resources/sql/autopatches/20150725.badges.mailkey.2.php @@ -0,0 +1,18 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $badge) { + $id = $badge->getID(); + + echo pht('Adding mail key for badge %d...', $id); + echo "\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 64bad75915..7fed06cd35 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1631,10 +1631,12 @@ phutil_register_library_map(array( 'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php', 'PhabricatorBadgesIcon' => 'applications/badges/icon/PhabricatorBadgesIcon.php', 'PhabricatorBadgesListController' => 'applications/badges/controller/PhabricatorBadgesListController.php', + 'PhabricatorBadgesMailReceiver' => 'applications/badges/mail/PhabricatorBadgesMailReceiver.php', 'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php', 'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.php', 'PhabricatorBadgesRecipientsListView' => 'applications/badges/view/PhabricatorBadgesRecipientsListView.php', 'PhabricatorBadgesRemoveRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php', + 'PhabricatorBadgesReplyHandler' => 'applications/badges/mail/PhabricatorBadgesReplyHandler.php', 'PhabricatorBadgesSchemaSpec' => 'applications/badges/storage/PhabricatorBadgesSchemaSpec.php', 'PhabricatorBadgesSearchEngine' => 'applications/badges/query/PhabricatorBadgesSearchEngine.php', 'PhabricatorBadgesTransaction' => 'applications/badges/storage/PhabricatorBadgesTransaction.php', @@ -5402,10 +5404,12 @@ phutil_register_library_map(array( 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorBadgesIcon' => 'Phobject', 'PhabricatorBadgesListController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType', 'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorBadgesRecipientsListView' => 'AphrontTagView', 'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction', diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php index d060c6df7a..539f071b49 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditor.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php @@ -142,6 +142,23 @@ final class PhabricatorBadgesEditor return $errors; } + protected function shouldSendMail( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + public function getMailTagsMap() { + return array( + PhabricatorBadgesTransaction::MAILTAG_DETAILS => + pht('Someone changes the badge\'s details.'), + PhabricatorBadgesTransaction::MAILTAG_COMMENT => + pht('Someone comments on a badge.'), + PhabricatorBadgesTransaction::MAILTAG_OTHER => + pht('Other badge activity not listed above occurs.'), + ); + } + protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { @@ -149,7 +166,7 @@ final class PhabricatorBadgesEditor } protected function buildReplyHandler(PhabricatorLiskDAO $object) { - return id(new PhabricatorMacroReplyHandler()) + return id(new PhabricatorBadgesReplyHandler()) ->setMailReceiver($object); } diff --git a/src/applications/badges/mail/PhabricatorBadgesMailReceiver.php b/src/applications/badges/mail/PhabricatorBadgesMailReceiver.php new file mode 100644 index 0000000000..4e72c8422d --- /dev/null +++ b/src/applications/badges/mail/PhabricatorBadgesMailReceiver.php @@ -0,0 +1,28 @@ +setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + } + + protected function getTransactionReplyHandler() { + return new PhabricatorBadgesReplyHandler(); + } + +} diff --git a/src/applications/badges/mail/PhabricatorBadgesReplyHandler.php b/src/applications/badges/mail/PhabricatorBadgesReplyHandler.php new file mode 100644 index 0000000000..af03dd7e2a --- /dev/null +++ b/src/applications/badges/mail/PhabricatorBadgesReplyHandler.php @@ -0,0 +1,16 @@ + 'text255', 'quality' => 'text255', 'status' => 'text32', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_creator' => array( @@ -114,6 +116,13 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO return $this->assertAttached($this->recipientPHIDs); } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/badges/storage/PhabricatorBadgesTransaction.php b/src/applications/badges/storage/PhabricatorBadgesTransaction.php index e7ab4eab6a..c50297f768 100644 --- a/src/applications/badges/storage/PhabricatorBadgesTransaction.php +++ b/src/applications/badges/storage/PhabricatorBadgesTransaction.php @@ -10,6 +10,11 @@ final class PhabricatorBadgesTransaction const TYPE_STATUS = 'badges:status'; const TYPE_FLAVOR = 'badges:flavor'; + const MAILTAG_NAME = 'badges:name'; + const MAILTAG_DETAILS = 'badges:details'; + const MAILTAG_COMMENT = 'badges:comment'; + const MAILTAG_OTHER = 'badges:other'; + public function getApplicationName() { return 'badges'; } @@ -168,6 +173,28 @@ final class PhabricatorBadgesTransaction return parent::getTitleForFeed(); } + public function getMailTags() { + $tags = parent::getMailTags(); + + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + $tags[] = self::MAILTAG_COMMENT; + break; + case self::TYPE_NAME: + case self::TYPE_DESCRIPTION: + case self::TYPE_FLAVOR: + case self::TYPE_ICON: + case self::TYPE_STATUS: + case self::TYPE_QUALITY: + $tags[] = self::MAILTAG_DETAILS; + break; + default: + $tags[] = self::MAILTAG_OTHER; + break; + } + return $tags; + } + public function shouldHide() { $old = $this->getOldValue(); From a962aeaf85af471b2c8887294da45772bba487b8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 25 Jul 2015 15:47:07 -0700 Subject: [PATCH 004/102] Add mailKeys to Countdown Summary: Adds mailkeys and reply handler support Test Plan: Edit Countdown, New Countdown, no errors. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13713 --- .../20150725.countdown.mailkey.1.sql | 2 ++ .../20150725.countdown.mailkey.2.php | 18 ++++++++++++ src/__phutil_library_map__.php | 2 ++ .../editor/PhabricatorCountdownEditor.php | 7 +++++ .../mail/PhabricatorCountdownMailReceiver.php | 28 +++++++++++++++++++ .../storage/PhabricatorCountdown.php | 10 ++++++- 6 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20150725.countdown.mailkey.1.sql create mode 100644 resources/sql/autopatches/20150725.countdown.mailkey.2.php create mode 100644 src/applications/countdown/mail/PhabricatorCountdownMailReceiver.php diff --git a/resources/sql/autopatches/20150725.countdown.mailkey.1.sql b/resources/sql/autopatches/20150725.countdown.mailkey.1.sql new file mode 100644 index 0000000000..c53441e1cb --- /dev/null +++ b/resources/sql/autopatches/20150725.countdown.mailkey.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_countdown.countdown + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20150725.countdown.mailkey.2.php b/resources/sql/autopatches/20150725.countdown.mailkey.2.php new file mode 100644 index 0000000000..c07a763942 --- /dev/null +++ b/resources/sql/autopatches/20150725.countdown.mailkey.2.php @@ -0,0 +1,18 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $countdown) { + $id = $countdown->getID(); + + echo pht('Adding mail key for countdown %d...', $id); + echo "\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7fed06cd35..1ec46fffa7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1832,6 +1832,7 @@ phutil_register_library_map(array( 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', 'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php', 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', + 'PhabricatorCountdownMailReceiver' => 'applications/countdown/mail/PhabricatorCountdownMailReceiver.php', 'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php', 'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php', 'PhabricatorCountdownReplyHandler' => 'applications/countdown/mail/PhabricatorCountdownReplyHandler.php', @@ -5648,6 +5649,7 @@ phutil_register_library_map(array( 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', + 'PhabricatorCountdownMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCountdownRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCountdownReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index 7438e4ac02..05c5bbb7de 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -172,6 +172,13 @@ final class PhabricatorCountdownEditor array $xactions) { $body = parent::buildMailBody($object, $xactions); + $description = $object->getDescription(); + + if (strlen($description)) { + $body->addTextSection( + pht('COUNTDOWN DESCRIPTION'), + $object->getDescription()); + } $body->addLinkSection( pht('COUNTDOWN DETAIL'), diff --git a/src/applications/countdown/mail/PhabricatorCountdownMailReceiver.php b/src/applications/countdown/mail/PhabricatorCountdownMailReceiver.php new file mode 100644 index 0000000000..d0218de59b --- /dev/null +++ b/src/applications/countdown/mail/PhabricatorCountdownMailReceiver.php @@ -0,0 +1,28 @@ +setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + } + + protected function getTransactionReplyHandler() { + return new PhabricatorCountdownReplyHandler(); + } + +} diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php index 01fd5d2509..8753c66223 100644 --- a/src/applications/countdown/storage/PhabricatorCountdown.php +++ b/src/applications/countdown/storage/PhabricatorCountdown.php @@ -16,7 +16,7 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO protected $description; protected $viewPolicy; protected $editPolicy; - + protected $mailKey; protected $spacePHID; public static function initializeNewCountdown(PhabricatorUser $actor) { @@ -41,6 +41,7 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', 'description' => 'text', + 'mailKey' => 'bytes20', ), ) + parent::getConfiguration(); } @@ -54,6 +55,13 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO return 'C'.$this->getID(); } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ From b3226142878fb5f2d8782a9f2cf6e8db1da53daa Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 25 Jul 2015 15:47:26 -0700 Subject: [PATCH 005/102] What are these, badges for ants? Summary: Movw smaller badge CSS to just timeline, also slightly more smallerlike. Test Plan: timeline, hovercards, badges app Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13714 --- resources/celerity/map.php | 14 +++++++------- .../rsrc/css/layout/phabricator-hovercard-view.css | 10 ---------- webroot/rsrc/css/phui/phui-badge.css | 10 +++++----- webroot/rsrc/css/phui/phui-timeline-view.css | 10 ++++++++++ 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5c319ed911..32643ae448 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'f79ebe46', + 'core.pkg.css' => 'b7d43de8', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '9451634c', @@ -117,7 +117,7 @@ return array( 'rsrc/css/font/font-roboto-slab.css' => 'f24a53cb', 'rsrc/css/font/phui-font-icon-base.css' => '3dad2ae3', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', - 'rsrc/css/layout/phabricator-hovercard-view.css' => '2ca21170', + 'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'bec2458e', 'rsrc/css/layout/phabricator-source-code-view.css' => '5e0178de', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', @@ -126,7 +126,7 @@ return array( 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '3ee9afd5', - 'rsrc/css/phui/phui-badge.css' => '26db39af', + 'rsrc/css/phui/phui-badge.css' => 'b6218fa8', 'rsrc/css/phui/phui-box.css' => 'a5bb366d', 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => 'd842f867', @@ -151,7 +151,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-tag-view.css' => '402691cc', 'rsrc/css/phui/phui-text.css' => 'cf019f54', - 'rsrc/css/phui/phui-timeline-view.css' => 'fc23e7b7', + 'rsrc/css/phui/phui-timeline-view.css' => 'f1bccf73', 'rsrc/css/phui/phui-workboard-view.css' => '6a20991a', 'rsrc/css/phui/phui-workpanel-view.css' => '8cebb2b1', 'rsrc/css/sprite-login.css' => '1ebb9bf9', @@ -732,7 +732,7 @@ return array( 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => '5337623f', 'phabricator-hovercard' => '14ac66f5', - 'phabricator-hovercard-view-css' => '2ca21170', + 'phabricator-hovercard-view-css' => '1239cd52', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c1700f6f', 'phabricator-main-menu-view' => '2f670a96', @@ -775,7 +775,7 @@ return array( 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => 'd1861e06', 'phui-action-panel-css' => '3ee9afd5', - 'phui-badge-view-css' => '26db39af', + 'phui-badge-view-css' => 'b6218fa8', 'phui-box-css' => 'a5bb366d', 'phui-button-css' => '16020a60', 'phui-calendar-css' => 'ccabe893', @@ -807,7 +807,7 @@ return array( 'phui-tag-view-css' => '402691cc', 'phui-text-css' => 'cf019f54', 'phui-theme-css' => '6b451f24', - 'phui-timeline-view-css' => 'fc23e7b7', + 'phui-timeline-view-css' => 'f1bccf73', 'phui-workboard-view-css' => '6a20991a', 'phui-workpanel-view-css' => '8cebb2b1', 'phuix-action-list-view' => 'b5c256b8', diff --git a/webroot/rsrc/css/layout/phabricator-hovercard-view.css b/webroot/rsrc/css/layout/phabricator-hovercard-view.css index 321e281add..23b3956763 100644 --- a/webroot/rsrc/css/layout/phabricator-hovercard-view.css +++ b/webroot/rsrc/css/layout/phabricator-hovercard-view.css @@ -91,16 +91,6 @@ float: left; } -.hovercard-badges .phui-badge-mini { - height: 25px; - width: 25px; - line-height: 24px; -} - -.hovercard-badges .phui-badge-mini .phui-icon-view { - font-size: 12px; -} - .phabricator-hovercard-tail { width: 396px; float: left; diff --git a/webroot/rsrc/css/phui/phui-badge.css b/webroot/rsrc/css/phui/phui-badge.css index f613956844..e5f04e2707 100644 --- a/webroot/rsrc/css/phui/phui-badge.css +++ b/webroot/rsrc/css/phui/phui-badge.css @@ -171,12 +171,12 @@ .phui-badge-mini { background-color: {$greyborder}; border-radius: 18px; - height: 20px; - width: 20px; - line-height: 19px; + height: 25px; + width: 25px; + line-height: 24px; text-align: center; display: inline-block; - opacity: 0.8; + opacity: 0.7; } .phui-badge-mini:hover { @@ -185,7 +185,7 @@ .phui-badge-mini .phui-icon-view { color: #fff; - font-size: 10px; + font-size: 12px; margin: 0; } diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index fdef1d07a3..de6658f7dc 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -395,3 +395,13 @@ a.phui-timeline-menu .phui-icon-view { top: 52px; width: 54px; } + +.phui-timeline-badges .phui-badge-mini { + height: 18px; + width: 18px; + line-height: 16px; +} + +.phui-timeline-badges .phui-badge-mini .phui-icon-view { + font-size: 10px; +} From c8977d580ef14897a96b34ea52dfafc603f366ce Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 25 Jul 2015 16:41:41 -0700 Subject: [PATCH 006/102] Remove viewPolicy from Badges Summary: Drops the sql column. Test Plan: View logged in and logged out badges, edited some too. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13715 --- resources/sql/autopatches/20150725.badges.viewpolicy.3.sql | 2 ++ src/applications/badges/storage/PhabricatorBadgesBadge.php | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 resources/sql/autopatches/20150725.badges.viewpolicy.3.sql diff --git a/resources/sql/autopatches/20150725.badges.viewpolicy.3.sql b/resources/sql/autopatches/20150725.badges.viewpolicy.3.sql new file mode 100644 index 0000000000..a5933f4ade --- /dev/null +++ b/resources/sql/autopatches/20150725.badges.viewpolicy.3.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_badges.badges_badge + DROP COLUMN viewPolicy; diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php index 3a12056b5d..d7e13ffc61 100644 --- a/src/applications/badges/storage/PhabricatorBadgesBadge.php +++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php @@ -14,7 +14,6 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO protected $icon; protected $quality; protected $mailKey; - protected $viewPolicy; protected $editPolicy; protected $status; protected $creatorPHID; @@ -73,7 +72,6 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO ->setIcon(self::DEFAULT_ICON) ->setQuality(self::DEFAULT_QUALITY) ->setCreatorPHID($actor->getPHID()) - ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) ->setStatus(self::STATUS_OPEN); } From 37893ed7741397b6a30b4734b460bc2acad6f7be Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:19:52 -0700 Subject: [PATCH 007/102] Countdown mail support part Summary: I still can't figure out why feed/mail aren't firing, but this needs fixed regardless. (honestly, I spent 2 hours on it last night. ghosts) Test Plan: lint Reviewers: eadler, epriestley Reviewed By: eadler, epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13723 --- .../countdown/editor/PhabricatorCountdownEditor.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index 05c5bbb7de..6fc699646d 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -188,11 +188,13 @@ final class PhabricatorCountdownEditor } protected function getMailTo(PhabricatorLiskDAO $object) { - return array($object->getAuthorPHID()); + return array( + $object->getAuthorPHID(), + $this->requireActor()->getPHID(), + ); } - protected function getMailSubjectPrefix() { - return 'Countdown'; + return '[Countdown]'; } protected function buildReplyHandler(PhabricatorLiskDAO $object) { From cb1f4ea721972a0339ba5f38608b247250fe7ab0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:31:56 -0700 Subject: [PATCH 008/102] Better CSS for mobile workboards Summary: Fixes T8964 and a few other details. Cards float to 100%, headers too, policy moved to it's own line. Test Plan: Mobile workboards at 320 and 360px wide. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8964 Differential Revision: https://secure.phabricator.com/D13722 --- resources/celerity/map.php | 8 ++++---- webroot/rsrc/css/phui/phui-workboard-view.css | 6 ++++++ webroot/rsrc/css/phui/phui-workpanel-view.css | 5 +++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 32643ae448..65577ecf91 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -152,8 +152,8 @@ return array( 'rsrc/css/phui/phui-tag-view.css' => '402691cc', 'rsrc/css/phui/phui-text.css' => 'cf019f54', 'rsrc/css/phui/phui-timeline-view.css' => 'f1bccf73', - 'rsrc/css/phui/phui-workboard-view.css' => '6a20991a', - 'rsrc/css/phui/phui-workpanel-view.css' => '8cebb2b1', + 'rsrc/css/phui/phui-workboard-view.css' => '6704d68d', + 'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699', 'rsrc/css/sprite-login.css' => '1ebb9bf9', 'rsrc/css/sprite-main-header.css' => 'f07bbb87', 'rsrc/css/sprite-menu.css' => '9dd65b92', @@ -808,8 +808,8 @@ return array( 'phui-text-css' => 'cf019f54', 'phui-theme-css' => '6b451f24', 'phui-timeline-view-css' => 'f1bccf73', - 'phui-workboard-view-css' => '6a20991a', - 'phui-workpanel-view-css' => '8cebb2b1', + 'phui-workboard-view-css' => '6704d68d', + 'phui-workpanel-view-css' => 'adec7699', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8cf6d262', 'phuix-dropdown-menu' => 'bd4c8dca', diff --git a/webroot/rsrc/css/phui/phui-workboard-view.css b/webroot/rsrc/css/phui/phui-workboard-view.css index 7299059496..2befbd7823 100644 --- a/webroot/rsrc/css/phui/phui-workboard-view.css +++ b/webroot/rsrc/css/phui/phui-workboard-view.css @@ -129,6 +129,12 @@ padding: 0 8px; } +.device-phone .project-board-header .phui-header-subheader { + display: block; + margin: 8px 0 2px 0; + padding: 0; +} + .device-desktop .phabricator-icon-nav.project-board-nav .phabricator-nav-local { margin-top: 64px; diff --git a/webroot/rsrc/css/phui/phui-workpanel-view.css b/webroot/rsrc/css/phui/phui-workpanel-view.css index b042bda0e2..9b8943ceeb 100644 --- a/webroot/rsrc/css/phui/phui-workpanel-view.css +++ b/webroot/rsrc/css/phui/phui-workpanel-view.css @@ -90,6 +90,11 @@ width: 300px; } +.device-phone .aphront-multi-column-fixed .phui-workpanel-view, +.device-phone .phui-workpanel-view .phui-header-shell { + width: auto; +} + .phui-workpanel-body .phui-object-item-list-view { min-height: 54px; } From 2d35d6053a049664b62b8f061a1a2e6a513330af Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:42:29 -0700 Subject: [PATCH 009/102] Update Releeph Branch for handleProcess Summary: Updates Releeph Branch controllers Test Plan: bounce around releeph, arc lint Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13721 --- .../branch/ReleephBranchAccessController.php | 18 +++++------------- .../branch/ReleephBranchCreateController.php | 14 ++++---------- .../branch/ReleephBranchEditController.php | 17 +++++------------ .../branch/ReleephBranchHistoryController.php | 14 ++++---------- .../ReleephBranchNamePreviewController.php | 3 +-- .../branch/ReleephBranchViewController.php | 19 ++++++------------- 6 files changed, 25 insertions(+), 60 deletions(-) diff --git a/src/applications/releeph/controller/branch/ReleephBranchAccessController.php b/src/applications/releeph/controller/branch/ReleephBranchAccessController.php index 177fb82c8f..8b675ed2da 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchAccessController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchAccessController.php @@ -2,21 +2,14 @@ final class ReleephBranchAccessController extends ReleephBranchController { - private $action; - private $branchID; - - public function willProcessRequest(array $data) { - $this->action = $data['action']; - $this->branchID = $data['branchID']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $action = $request->getURIData('action'); + $id = $request->getURIData('branchID'); $branch = id(new ReleephBranchQuery()) ->setViewer($viewer) - ->withIDs(array($this->branchID)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -28,7 +21,6 @@ final class ReleephBranchAccessController extends ReleephBranchController { } $this->setBranch($branch); - $action = $this->action; switch ($action) { case 'close': case 're-open': diff --git a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php index 851724c6d9..d13383cc3b 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php @@ -2,19 +2,13 @@ final class ReleephBranchCreateController extends ReleephProductController { - private $productID; - - public function willProcessRequest(array $data) { - $this->productID = $data['projectID']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('projectID'); $product = id(new ReleephProductQuery()) ->setViewer($viewer) - ->withIDs(array($this->productID)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/releeph/controller/branch/ReleephBranchEditController.php b/src/applications/releeph/controller/branch/ReleephBranchEditController.php index 000e9535bf..6d66f5d9d5 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchEditController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchEditController.php @@ -2,15 +2,9 @@ final class ReleephBranchEditController extends ReleephBranchController { - private $branchID; - - public function willProcessRequest(array $data) { - $this->branchID = $data['branchID']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('branchID'); $branch = id(new ReleephBranchQuery()) ->setViewer($viewer) @@ -19,7 +13,7 @@ final class ReleephBranchEditController extends ReleephBranchController { PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withIDs(array($this->branchID)) + ->withIDs(array($id)) ->executeOne(); if (!$branch) { return new Aphront404Response(); @@ -40,8 +34,7 @@ final class ReleephBranchEditController extends ReleephBranchController { $symbolic_name); $branch->openTransaction(); - $branch - ->setSymbolicName($symbolic_name); + $branch->setSymbolicName($symbolic_name); if ($existing_with_same_symbolic_name) { $existing_with_same_symbolic_name diff --git a/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php b/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php index 46c0b47f6c..a77cdf8fb3 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php @@ -2,23 +2,17 @@ final class ReleephBranchHistoryController extends ReleephBranchController { - private $branchID; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->branchID = $data['branchID']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('branchID'); $branch = id(new ReleephBranchQuery()) ->setViewer($viewer) - ->withIDs(array($this->branchID)) + ->withIDs(array($id)) ->executeOne(); if (!$branch) { return new Aphront404Response(); diff --git a/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php b/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php index bf429f9034..f2dbd23d8d 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php @@ -3,8 +3,7 @@ final class ReleephBranchNamePreviewController extends ReleephController { - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { $is_symbolic = $request->getBool('isSymbolic'); $template = $request->getStr('template'); diff --git a/src/applications/releeph/controller/branch/ReleephBranchViewController.php b/src/applications/releeph/controller/branch/ReleephBranchViewController.php index 29e0f6d658..11615fe3ed 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchViewController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchViewController.php @@ -2,25 +2,18 @@ final class ReleephBranchViewController extends ReleephBranchController { - private $queryKey; - private $branchID; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->branchID = $data['branchID']; - $this->queryKey = idx($data, 'queryKey'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('branchID'); + $querykey = $request->getURIData('queryKey'); $branch = id(new ReleephBranchQuery()) ->setViewer($viewer) - ->withIDs(array($this->branchID)) + ->withIDs(array($id)) ->executeOne(); if (!$branch) { return new Aphront404Response(); @@ -29,7 +22,7 @@ final class ReleephBranchViewController extends ReleephBranchController { $controller = id(new PhabricatorApplicationSearchController()) ->setPreface($this->renderPreface()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine($this->getSearchEngine()) ->setNavigation($this->buildSideNavView()); From 7fa4e455b66df64ffb8f40b31b472d81de5580d1 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:42:40 -0700 Subject: [PATCH 010/102] Update Slowvote for handleRequest Summary: Moves to use handleRequest Test Plan: Make a new poll, edit, see list Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13720 --- .../PhabricatorSlowvoteCloseController.php | 18 ++++------- .../PhabricatorSlowvoteCommentController.php | 20 +++++------- .../PhabricatorSlowvoteEditController.php | 31 +++++++------------ .../PhabricatorSlowvoteListController.php | 10 ++---- .../PhabricatorSlowvotePollController.php | 20 +++++------- .../PhabricatorSlowvoteVoteController.php | 30 +++++++----------- 6 files changed, 47 insertions(+), 82 deletions(-) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php index c4e4fed588..05c12aec27 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php @@ -3,19 +3,13 @@ final class PhabricatorSlowvoteCloseController extends PhabricatorSlowvoteController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $poll = id(new PhabricatorSlowvoteQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -42,7 +36,7 @@ final class PhabricatorSlowvoteCloseController ->setNewValue($new_status); id(new PhabricatorSlowvoteEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteCommentController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteCommentController.php index 588f06b649..035eb577d8 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteCommentController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteCommentController.php @@ -3,23 +3,17 @@ final class PhabricatorSlowvoteCommentController extends PhabricatorSlowvoteController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); if (!$request->isFormPost()) { return new Aphront400Response(); } $poll = id(new PhabricatorSlowvoteQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$poll) { return new Aphront404Response(); @@ -38,7 +32,7 @@ final class PhabricatorSlowvoteCommentController ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorSlowvoteEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); @@ -57,7 +51,7 @@ final class PhabricatorSlowvoteCommentController if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) + ->setViewer($viewer) ->setTransactions($xactions) ->setIsPreview($is_preview); } else { diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php index 44d9cec792..e497a50fbc 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php @@ -3,21 +3,14 @@ final class PhabricatorSlowvoteEditController extends PhabricatorSlowvoteController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - - $request = $this->getRequest(); - $user = $request->getUser(); - - if ($this->id) { + if ($id) { $poll = id(new PhabricatorSlowvoteQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -29,7 +22,7 @@ final class PhabricatorSlowvoteEditController } $is_new = false; } else { - $poll = PhabricatorSlowvotePoll::initializeNewPoll($user); + $poll = PhabricatorSlowvotePoll::initializeNewPoll($viewer); $is_new = true; } @@ -119,7 +112,7 @@ final class PhabricatorSlowvoteEditController ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhabricatorSlowvoteEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); @@ -153,7 +146,7 @@ final class PhabricatorSlowvoteEditController 'protracted deliberation.')); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild($instructions) ->appendChild( id(new AphrontFormTextAreaControl()) @@ -164,7 +157,7 @@ final class PhabricatorSlowvoteEditController ->setError($e_question)) ->appendChild( id(new PhabricatorRemarkupControl()) - ->setUser($user) + ->setUser($viewer) ->setLabel(pht('Description')) ->setName('description') ->setValue($v_description)) @@ -232,7 +225,7 @@ final class PhabricatorSlowvoteEditController } $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setObject($poll) ->execute(); @@ -253,7 +246,7 @@ final class PhabricatorSlowvoteEditController $v_shuffle)) ->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setName('viewPolicy') ->setPolicyObject($poll) ->setPolicies($policies) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php index 8062fc4098..bbb96d4dc4 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php @@ -3,19 +3,15 @@ final class PhabricatorSlowvoteListController extends PhabricatorSlowvoteController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PhabricatorSlowvoteSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 9bac0a6088..40980e5f41 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -3,19 +3,13 @@ final class PhabricatorSlowvotePollController extends PhabricatorSlowvoteController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $poll = id(new PhabricatorSlowvoteQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needOptions(true) ->needChoices(true) ->needViewerChoices(true) @@ -26,7 +20,7 @@ final class PhabricatorSlowvotePollController $poll_view = id(new SlowvoteEmbedView()) ->setHeadless(true) - ->setUser($user) + ->setUser($viewer) ->setPoll($poll); if ($request->isAjax()) { @@ -44,7 +38,7 @@ final class PhabricatorSlowvotePollController $header = id(new PHUIHeaderView()) ->setHeader($poll->getQuestion()) - ->setUser($user) + ->setUser($viewer) ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($poll); diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php index 6cfeae0ac0..2d630bffc1 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php @@ -3,19 +3,13 @@ final class PhabricatorSlowvoteVoteController extends PhabricatorSlowvoteController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $poll = id(new PhabricatorSlowvoteQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needOptions(true) ->needViewerChoices(true) ->executeOne(); @@ -27,9 +21,9 @@ final class PhabricatorSlowvoteVoteController } $options = $poll->getOptions(); - $user_choices = $poll->getViewerChoices($user); + $viewer_choices = $poll->getViewerChoices($viewer); - $old_votes = mpull($user_choices, null, 'getOptionID'); + $old_votes = mpull($viewer_choices, null, 'getOptionID'); if ($request->isAjax()) { $vote = $request->getInt('vote'); @@ -50,12 +44,12 @@ final class PhabricatorSlowvoteVoteController } } - $this->updateVotes($user, $poll, $old_votes, $votes); + $this->updateVotes($viewer, $poll, $old_votes, $votes); $updated_choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( 'pollID = %d AND authorPHID = %s', $poll->getID(), - $user->getPHID()); + $viewer->getPHID()); $embed = id(new SlowvoteEmbedView()) ->setPoll($poll) @@ -76,12 +70,12 @@ final class PhabricatorSlowvoteVoteController $votes = $request->getArr('vote'); $votes = array_fuse($votes, $votes); - $this->updateVotes($user, $poll, $old_votes, $votes); + $this->updateVotes($viewer, $poll, $old_votes, $votes); return id(new AphrontRedirectResponse())->setURI('/V'.$poll->getID()); } - private function updateVotes($user, $poll, $old_votes, $votes) { + private function updateVotes($viewer, $poll, $old_votes, $votes) { if (!empty($votes) && count($votes) > 1 && $poll->getMethod() == PhabricatorSlowvotePoll::METHOD_PLURALITY) { return id(new Aphront400Response()); @@ -99,7 +93,7 @@ final class PhabricatorSlowvoteVoteController } id(new PhabricatorSlowvoteChoice()) - ->setAuthorPHID($user->getPHID()) + ->setAuthorPHID($viewer->getPHID()) ->setPollID($poll->getID()) ->setOptionID($vote) ->save(); From bccdbacf4419a8af4c7eae97f2fb90d03c694b52 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:51:37 -0700 Subject: [PATCH 011/102] Add tokens to Macro Summary: A foolish consistency is the hobgoblin of little minds, adored by little statesmen and philosophers and divines. Test Plan: Award a token to a macro. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13725 --- src/__phutil_library_map__.php | 1 + .../macro/storage/PhabricatorFileImageMacro.php | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1ec46fffa7..7f2a56e406 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5924,6 +5924,7 @@ phutil_register_library_map(array( 'PhabricatorSubscribableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', + 'PhabricatorTokenReceiverInterface', 'PhabricatorPolicyInterface', ), 'PhabricatorFileImageTransform' => 'PhabricatorFileTransform', diff --git a/src/applications/macro/storage/PhabricatorFileImageMacro.php b/src/applications/macro/storage/PhabricatorFileImageMacro.php index 0d68bf0c1b..72a6577924 100644 --- a/src/applications/macro/storage/PhabricatorFileImageMacro.php +++ b/src/applications/macro/storage/PhabricatorFileImageMacro.php @@ -5,6 +5,7 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO PhabricatorSubscribableInterface, PhabricatorApplicationTransactionInterface, PhabricatorFlaggableInterface, + PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface { protected $authorPHID; @@ -119,6 +120,16 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO } +/* -( PhabricatorTokenRecevierInterface )---------------------------------- */ + + + public function getUsersToNotifyOfTokenGiven() { + return array( + $this->getAuthorPHID(), + ); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ From 559bcc42c27a8a8ddf430bb664fd5a345317452d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:51:53 -0700 Subject: [PATCH 012/102] Add tipDirection to PHUIBadgeMiniView Summary: Makes PHUIBadgeMiniView a little easier to dictate layout. Apply in Timeline. Test Plan: Tested supercalifragilisticexpialidocious as a badge, saw it align properly. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13729 --- src/view/phui/PHUIBadgeMiniView.php | 8 ++++++++ src/view/phui/PHUITimelineView.php | 1 + 2 files changed, 9 insertions(+) diff --git a/src/view/phui/PHUIBadgeMiniView.php b/src/view/phui/PHUIBadgeMiniView.php index 5807336ca2..92d6fd8b8b 100644 --- a/src/view/phui/PHUIBadgeMiniView.php +++ b/src/view/phui/PHUIBadgeMiniView.php @@ -6,6 +6,7 @@ final class PHUIBadgeMiniView extends AphrontTagView { private $icon; private $quality; private $header; + private $tipDirection; public function setIcon($icon) { $this->icon = $icon; @@ -27,6 +28,11 @@ final class PHUIBadgeMiniView extends AphrontTagView { return $this; } + public function setTipDirection($direction) { + $this->tipDirection = $direction; + return $this; + } + protected function getTagName() { if ($this->href) { return 'a'; @@ -51,6 +57,8 @@ final class PHUIBadgeMiniView extends AphrontTagView { 'href' => $this->href, 'meta' => array( 'tip' => $this->header, + 'align' => $this->tipDirection, + 'size' => 300, ), ); } diff --git a/src/view/phui/PHUITimelineView.php b/src/view/phui/PHUITimelineView.php index 17178b8135..d8ba96c140 100644 --- a/src/view/phui/PHUITimelineView.php +++ b/src/view/phui/PHUITimelineView.php @@ -255,6 +255,7 @@ final class PHUITimelineView extends AphrontView { ->setIcon($badge->getIcon()) ->setQuality($badge->getQuality()) ->setHeader($badge->getName()) + ->setTipDirection('E') ->setHref('/badges/view/'.$badge->getID()); $event->addBadge($badge_view); From 0525d727ee3323776db5daa7eec7feda60b02fc3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:53:07 -0700 Subject: [PATCH 013/102] Clean up Countdown views Summary: Touch up Countdown timer in page, remarkup, and mobile. Test Plan: Test in page, remarkup, and mobile. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13732 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/application/countdown/timer.css | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 65577ecf91..72182531c0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -54,7 +54,7 @@ return array( 'rsrc/css/application/conpherence/update.css' => 'faf6be09', 'rsrc/css/application/conpherence/widget-pane.css' => '419fd50c', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', - 'rsrc/css/application/countdown/timer.css' => '4f02bd98', + 'rsrc/css/application/countdown/timer.css' => '3791bb38', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', 'rsrc/css/application/dashboard/dashboard.css' => 'eb458607', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', @@ -722,7 +722,7 @@ return array( 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => 'a76cefc9', - 'phabricator-countdown-css' => '4f02bd98', + 'phabricator-countdown-css' => '3791bb38', 'phabricator-dashboard-css' => 'eb458607', 'phabricator-drag-and-drop-file-upload' => '07de8873', 'phabricator-draggable-list' => 'a16ec1c6', diff --git a/webroot/rsrc/css/application/countdown/timer.css b/webroot/rsrc/css/application/countdown/timer.css index 34a732128c..acc5e61fc0 100644 --- a/webroot/rsrc/css/application/countdown/timer.css +++ b/webroot/rsrc/css/application/countdown/timer.css @@ -3,17 +3,26 @@ */ .phabricator-timer { - margin: 16px; + margin: 16px 16px 0 16px; border: 1px solid {$lightblueborder}; - border-radius: 4px; + border-radius: 3px; background: #ffffff; } +.device-phone .phabricator-timer { + margin-left: 8px; + margin-right: 8px; +} + .phabricator-remarkup .phabricator-timer { width: 360px; margin: 0 0 12px 0; } +.device-phone .phabricator-remarkup .phabricator-timer { + width: auto; +} + .phabricator-timer-header { font-size: {$biggerfontsize}; font-weight: bold; @@ -30,6 +39,7 @@ .phabricator-timer-table th { text-align: center; + font-size: {$biggerfontsize}; color: {$greytext}; width: 10%; padding: 4px; From d6d37558887bf5d4ce34e459aa98e221127388a8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:55:09 -0700 Subject: [PATCH 014/102] Prevent long text from breaking Badges Summary: Put super long text for title and flavor text on badges and resolve breaking issues. Test Plan: Tested Supercalifragilisticexpialidocious for title and flavor. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13730 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/phui/phui-badge.css | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 72182531c0..4511ce3cb9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -126,7 +126,7 @@ return array( 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '3ee9afd5', - 'rsrc/css/phui/phui-badge.css' => 'b6218fa8', + 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-box.css' => 'a5bb366d', 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => 'd842f867', @@ -775,7 +775,7 @@ return array( 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => 'd1861e06', 'phui-action-panel-css' => '3ee9afd5', - 'phui-badge-view-css' => 'b6218fa8', + 'phui-badge-view-css' => 'f25c3476', 'phui-box-css' => 'a5bb366d', 'phui-button-css' => '16020a60', 'phui-calendar-css' => 'ccabe893', diff --git a/webroot/rsrc/css/phui/phui-badge.css b/webroot/rsrc/css/phui/phui-badge.css index e5f04e2707..f45a88f258 100644 --- a/webroot/rsrc/css/phui/phui-badge.css +++ b/webroot/rsrc/css/phui/phui-badge.css @@ -60,7 +60,8 @@ display: block; position: absolute; width: 128px; - height: 100%; + height: 180px; + overflow: hidden; backface-visibility: hidden; -webkit-backface-visibility: hidden; @@ -113,6 +114,7 @@ font-weight: bold; padding-top: 12px; display: block; + word-break: break-word; } .phui-badge-quality { @@ -124,6 +126,7 @@ .phui-badge-view-subhead { color: {$lightgreytext}; font-size: {$smallerfontsize}; + word-break: break-word; } .phui-badge-card-back { From c02615283308aab36974980a0a3de02032cf9ec9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 07:56:52 -0700 Subject: [PATCH 015/102] Update Drydock for handleRequest Summary: Poked through the Drydock controllers and updated the codes. Test Plan: Built random fake stuff in Drydock Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13731 --- .../DrydockBlueprintCreateController.php | 5 ++--- .../DrydockBlueprintEditController.php | 18 ++++++----------- .../DrydockBlueprintListController.php | 10 +++------- .../DrydockBlueprintViewController.php | 14 ++++--------- .../controller/DrydockConsoleController.php | 5 ++--- .../controller/DrydockLeaseListController.php | 11 ++++------ .../DrydockLeaseReleaseController.php | 20 +++++++------------ .../controller/DrydockLeaseViewController.php | 14 ++++--------- .../controller/DrydockLogListController.php | 11 ++++------ .../DrydockResourceCloseController.php | 14 ++++--------- .../DrydockResourceListController.php | 11 ++++------ .../DrydockResourceViewController.php | 14 ++++--------- 12 files changed, 48 insertions(+), 99 deletions(-) diff --git a/src/applications/drydock/controller/DrydockBlueprintCreateController.php b/src/applications/drydock/controller/DrydockBlueprintCreateController.php index 56e0228077..5398a2327d 100644 --- a/src/applications/drydock/controller/DrydockBlueprintCreateController.php +++ b/src/applications/drydock/controller/DrydockBlueprintCreateController.php @@ -3,9 +3,8 @@ final class DrydockBlueprintCreateController extends DrydockBlueprintController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $this->requireApplicationCapability( DrydockCreateBlueprintsCapability::CAPABILITY); diff --git a/src/applications/drydock/controller/DrydockBlueprintEditController.php b/src/applications/drydock/controller/DrydockBlueprintEditController.php index 4211c80b4c..1c75bf708d 100644 --- a/src/applications/drydock/controller/DrydockBlueprintEditController.php +++ b/src/applications/drydock/controller/DrydockBlueprintEditController.php @@ -2,20 +2,14 @@ final class DrydockBlueprintEditController extends DrydockBlueprintController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if ($this->id) { + if ($id) { $blueprint = id(new DrydockBlueprintQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -27,7 +21,7 @@ final class DrydockBlueprintEditController extends DrydockBlueprintController { } $impl = $blueprint->getImplementation(); - $cancel_uri = $this->getApplicationURI('blueprint/'.$this->id.'/'); + $cancel_uri = $this->getApplicationURI('blueprint/'.$id.'/'); } else { $this->requireApplicationCapability( DrydockCreateBlueprintsCapability::CAPABILITY); diff --git a/src/applications/drydock/controller/DrydockBlueprintListController.php b/src/applications/drydock/controller/DrydockBlueprintListController.php index 31e716691d..ff09f19ff8 100644 --- a/src/applications/drydock/controller/DrydockBlueprintListController.php +++ b/src/applications/drydock/controller/DrydockBlueprintListController.php @@ -2,20 +2,16 @@ final class DrydockBlueprintListController extends DrydockBlueprintController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $request = $this->getRequest(); $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new DrydockBlueprintSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index be5229972e..33a27264b8 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -2,19 +2,13 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $blueprint = id(new DrydockBlueprintQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$blueprint) { return new Aphront404Response(); diff --git a/src/applications/drydock/controller/DrydockConsoleController.php b/src/applications/drydock/controller/DrydockConsoleController.php index fd2dff8ba5..118d49ea59 100644 --- a/src/applications/drydock/controller/DrydockConsoleController.php +++ b/src/applications/drydock/controller/DrydockConsoleController.php @@ -22,9 +22,8 @@ final class DrydockConsoleController extends DrydockController { return $nav; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $menu = id(new PHUIObjectItemListView()) ->setUser($viewer); diff --git a/src/applications/drydock/controller/DrydockLeaseListController.php b/src/applications/drydock/controller/DrydockLeaseListController.php index f5c0897717..e370467a5f 100644 --- a/src/applications/drydock/controller/DrydockLeaseListController.php +++ b/src/applications/drydock/controller/DrydockLeaseListController.php @@ -2,19 +2,16 @@ final class DrydockLeaseListController extends DrydockLeaseController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new DrydockLeaseSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/drydock/controller/DrydockLeaseReleaseController.php b/src/applications/drydock/controller/DrydockLeaseReleaseController.php index 1779edf1b2..4bac0330ba 100644 --- a/src/applications/drydock/controller/DrydockLeaseReleaseController.php +++ b/src/applications/drydock/controller/DrydockLeaseReleaseController.php @@ -2,19 +2,13 @@ final class DrydockLeaseReleaseController extends DrydockLeaseController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $lease = id(new DrydockLeaseQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$lease) { return new Aphront404Response(); @@ -25,7 +19,7 @@ final class DrydockLeaseReleaseController extends DrydockLeaseController { if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Lease Not Active')) ->appendChild( phutil_tag( @@ -39,7 +33,7 @@ final class DrydockLeaseReleaseController extends DrydockLeaseController { if (!$request->isDialogFormPost()) { $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Really release lease?')) ->appendChild( phutil_tag( diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index d597209b5a..92d215bcbb 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -2,19 +2,13 @@ final class DrydockLeaseViewController extends DrydockLeaseController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $lease = id(new DrydockLeaseQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$lease) { return new Aphront404Response(); diff --git a/src/applications/drydock/controller/DrydockLogListController.php b/src/applications/drydock/controller/DrydockLogListController.php index eaeb411d28..aecf77dc77 100644 --- a/src/applications/drydock/controller/DrydockLogListController.php +++ b/src/applications/drydock/controller/DrydockLogListController.php @@ -2,19 +2,16 @@ final class DrydockLogListController extends DrydockLogController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new DrydockLogSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/drydock/controller/DrydockResourceCloseController.php b/src/applications/drydock/controller/DrydockResourceCloseController.php index 7963612ee4..915bf89452 100644 --- a/src/applications/drydock/controller/DrydockResourceCloseController.php +++ b/src/applications/drydock/controller/DrydockResourceCloseController.php @@ -2,19 +2,13 @@ final class DrydockResourceCloseController extends DrydockResourceController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $resource = id(new DrydockResourceQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$resource) { return new Aphront404Response(); diff --git a/src/applications/drydock/controller/DrydockResourceListController.php b/src/applications/drydock/controller/DrydockResourceListController.php index 53ed62a743..d2f34ec25b 100644 --- a/src/applications/drydock/controller/DrydockResourceListController.php +++ b/src/applications/drydock/controller/DrydockResourceListController.php @@ -2,19 +2,16 @@ final class DrydockResourceListController extends DrydockResourceController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new DrydockResourceSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 9be8188191..4cc7349dac 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -2,19 +2,13 @@ final class DrydockResourceViewController extends DrydockResourceController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $resource = id(new DrydockResourceQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$resource) { return new Aphront404Response(); From 55b685d3fc1f7bf24dace36d3e1e3d286cd6540b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 08:07:12 -0700 Subject: [PATCH 016/102] Update Feed for handleRequest Summary: Updated Feed Test Plan: Visit feed, see feed Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13734 --- .../PhabricatorFeedDetailController.php | 18 ++++++------------ .../PhabricatorFeedListController.php | 10 +++------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/applications/feed/controller/PhabricatorFeedDetailController.php b/src/applications/feed/controller/PhabricatorFeedDetailController.php index 459011af80..4fac10d1a8 100644 --- a/src/applications/feed/controller/PhabricatorFeedDetailController.php +++ b/src/applications/feed/controller/PhabricatorFeedDetailController.php @@ -2,19 +2,13 @@ final class PhabricatorFeedDetailController extends PhabricatorFeedController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $story = id(new PhabricatorFeedQuery()) - ->setViewer($user) - ->withChronologicalKeys(array($this->id)) + ->setViewer($viewer) + ->withChronologicalKeys(array($id)) ->executeOne(); if (!$story) { return new Aphront404Response(); @@ -27,7 +21,7 @@ final class PhabricatorFeedDetailController extends PhabricatorFeedController { $feed = array($story); $builder = new PhabricatorFeedBuilder($feed); - $builder->setUser($user); + $builder->setUser($viewer); $feed_view = $builder->buildView(); $title = pht('Story'); diff --git a/src/applications/feed/controller/PhabricatorFeedListController.php b/src/applications/feed/controller/PhabricatorFeedListController.php index f317b477be..8451592362 100644 --- a/src/applications/feed/controller/PhabricatorFeedListController.php +++ b/src/applications/feed/controller/PhabricatorFeedListController.php @@ -2,19 +2,15 @@ final class PhabricatorFeedListController extends PhabricatorFeedController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PhabricatorFeedSearchEngine()) ->setNavigation($this->buildSideNavView()); From 18a9fe85c8359710b619c8b8de47ea559ffafafc Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 09:04:08 -0700 Subject: [PATCH 017/102] Convert Macro to handleRequest Summary: Converts Macro app Test Plan: Make Macro, Make meme Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13724 --- .../PhabricatorMacroAudioController.php | 14 +++------ .../PhabricatorMacroCommentController.php | 20 +++++-------- .../PhabricatorMacroDisableController.php | 22 +++++--------- .../PhabricatorMacroEditController.php | 30 ++++++++----------- .../PhabricatorMacroListController.php | 10 ++----- .../PhabricatorMacroMemeController.php | 13 ++++---- .../PhabricatorMacroMemeDialogController.php | 11 ++++--- .../PhabricatorMacroViewController.php | 22 +++++--------- 8 files changed, 53 insertions(+), 89 deletions(-) diff --git a/src/applications/macro/controller/PhabricatorMacroAudioController.php b/src/applications/macro/controller/PhabricatorMacroAudioController.php index e46b7c78e8..92b98e5564 100644 --- a/src/applications/macro/controller/PhabricatorMacroAudioController.php +++ b/src/applications/macro/controller/PhabricatorMacroAudioController.php @@ -2,26 +2,20 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { $this->requireApplicationCapability( PhabricatorMacroManageCapability::CAPABILITY); - $request = $this->getRequest(); - $viewer = $request->getUser(); - $macro = id(new PhabricatorMacroQuery()) ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, )) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$macro) { diff --git a/src/applications/macro/controller/PhabricatorMacroCommentController.php b/src/applications/macro/controller/PhabricatorMacroCommentController.php index f438e9b31a..f038140be1 100644 --- a/src/applications/macro/controller/PhabricatorMacroCommentController.php +++ b/src/applications/macro/controller/PhabricatorMacroCommentController.php @@ -3,23 +3,17 @@ final class PhabricatorMacroCommentController extends PhabricatorMacroController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); if (!$request->isFormPost()) { return new Aphront400Response(); } $macro = id(new PhabricatorMacroQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$macro) { return new Aphront404Response(); @@ -38,7 +32,7 @@ final class PhabricatorMacroCommentController ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorMacroEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); @@ -57,7 +51,7 @@ final class PhabricatorMacroCommentController if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) + ->setViewer($viewer) ->setTransactions($xactions) ->setIsPreview($is_preview); } else { diff --git a/src/applications/macro/controller/PhabricatorMacroDisableController.php b/src/applications/macro/controller/PhabricatorMacroDisableController.php index 0804e119e5..a9868647f0 100644 --- a/src/applications/macro/controller/PhabricatorMacroDisableController.php +++ b/src/applications/macro/controller/PhabricatorMacroDisableController.php @@ -3,28 +3,22 @@ final class PhabricatorMacroDisableController extends PhabricatorMacroController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { $this->requireApplicationCapability( PhabricatorMacroManageCapability::CAPABILITY); - $request = $this->getRequest(); - $user = $request->getUser(); - $macro = id(new PhabricatorMacroQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$macro) { return new Aphront404Response(); } - $view_uri = $this->getApplicationURI('/view/'.$this->id.'/'); + $view_uri = $this->getApplicationURI('/view/'.$id.'/'); if ($request->isDialogFormPost() || $macro->getIsDisabled()) { $xaction = id(new PhabricatorMacroTransaction()) @@ -32,7 +26,7 @@ final class PhabricatorMacroDisableController ->setNewValue($macro->getIsDisabled() ? 0 : 1); $editor = id(new PhabricatorMacroEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request); $xactions = $editor->applyTransactions($macro, array($xaction)); @@ -52,7 +46,7 @@ final class PhabricatorMacroDisableController 'Really disable the much-beloved image macro %s? '. 'It will be sorely missed.', $macro->getName()))) - ->setSubmitURI($this->getApplicationURI('/disable/'.$this->id.'/')) + ->setSubmitURI($this->getApplicationURI('/disable/'.$id.'/')) ->addSubmitButton(pht('Disable')) ->addCancelButton($view_uri); diff --git a/src/applications/macro/controller/PhabricatorMacroEditController.php b/src/applications/macro/controller/PhabricatorMacroEditController.php index f15e5364c2..9e988ef637 100644 --- a/src/applications/macro/controller/PhabricatorMacroEditController.php +++ b/src/applications/macro/controller/PhabricatorMacroEditController.php @@ -2,23 +2,17 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { $this->requireApplicationCapability( PhabricatorMacroManageCapability::CAPABILITY); - $request = $this->getRequest(); - $user = $request->getUser(); - - if ($this->id) { + if ($id) { $macro = id(new PhabricatorMacroQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needFiles(true) ->executeOne(); if (!$macro) { @@ -26,7 +20,7 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { } } else { $macro = new PhabricatorFileImageMacro(); - $macro->setAuthorPHID($user->getPHID()); + $macro->setAuthorPHID($viewer->getPHID()); } $errors = array(); @@ -66,7 +60,7 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { $_FILES['file'], array( 'name' => $request->getStr('name'), - 'authorPHID' => $user->getPHID(), + 'authorPHID' => $viewer->getPHID(), 'isExplicitUpload' => true, 'canCDN' => true, )); @@ -75,7 +69,7 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { // Rate limit outbound fetches to make this mechanism less useful for // scanning networks and ports. PhabricatorSystemActionEngine::willTakeAction( - array($user->getPHID()), + array($viewer->getPHID()), new PhabricatorFilesOutboundRequestAction(), 1); @@ -101,7 +95,7 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { $mime_type)); } else { $file - ->setAuthorPHID($user->getPHID()) + ->setAuthorPHID($viewer->getPHID()) ->save(); } } catch (HTTPFutureHTTPResponseStatus $status) { @@ -114,7 +108,7 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { } } else if ($request->getStr('phid')) { $file = id(new PhabricatorFileQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($request->getStr('phid'))) ->executeOne(); } @@ -152,7 +146,7 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { } $editor = id(new PhabricatorMacroEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); diff --git a/src/applications/macro/controller/PhabricatorMacroListController.php b/src/applications/macro/controller/PhabricatorMacroListController.php index 730f8de3b1..3847117275 100644 --- a/src/applications/macro/controller/PhabricatorMacroListController.php +++ b/src/applications/macro/controller/PhabricatorMacroListController.php @@ -2,19 +2,15 @@ final class PhabricatorMacroListController extends PhabricatorMacroController { - private $key; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->key = idx($data, 'key'); - } + public function handleRequest(AphrontRequest $request) { + $key = $request->getURIData('key'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->key) + ->setQueryKey($key) ->setSearchEngine(new PhabricatorMacroSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/macro/controller/PhabricatorMacroMemeController.php b/src/applications/macro/controller/PhabricatorMacroMemeController.php index ff25a8a8a4..03b13f6205 100644 --- a/src/applications/macro/controller/PhabricatorMacroMemeController.php +++ b/src/applications/macro/controller/PhabricatorMacroMemeController.php @@ -7,14 +7,13 @@ final class PhabricatorMacroMemeController return true; } - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { $macro_name = $request->getStr('macro'); $upper_text = $request->getStr('uppertext'); $lower_text = $request->getStr('lowertext'); - $user = $request->getUser(); + $viewer = $request->getViewer(); - $uri = self::generateMacro($user, $macro_name, + $uri = self::generateMacro($viewer, $macro_name, $upper_text, $lower_text); if ($uri === false) { return new Aphront404Response(); @@ -24,10 +23,10 @@ final class PhabricatorMacroMemeController ->setURI($uri); } - public static function generateMacro($user, $macro_name, $upper_text, + public static function generateMacro($viewer, $macro_name, $upper_text, $lower_text) { $macro = id(new PhabricatorMacroQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withNames(array($macro_name)) ->needFiles(true) ->executeOne(); @@ -46,7 +45,7 @@ final class PhabricatorMacroMemeController if ($xform) { $memefile = id(new PhabricatorFileQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($xform->getTransformedPHID())) ->executeOne(); if ($memefile) { diff --git a/src/applications/macro/controller/PhabricatorMacroMemeDialogController.php b/src/applications/macro/controller/PhabricatorMacroMemeDialogController.php index 25a223f5ac..5851cc5ad8 100644 --- a/src/applications/macro/controller/PhabricatorMacroMemeDialogController.php +++ b/src/applications/macro/controller/PhabricatorMacroMemeDialogController.php @@ -3,9 +3,8 @@ final class PhabricatorMacroMemeDialogController extends PhabricatorMacroController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $phid = head($request->getArr('macro')); $above = $request->getStr('above'); @@ -19,7 +18,7 @@ final class PhabricatorMacroMemeDialogController $errors[] = pht('Macro name is required.'); } else { $macro = id(new PhabricatorMacroQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); if (!$macro) { @@ -45,7 +44,7 @@ final class PhabricatorMacroMemeDialogController } $view = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Macro')) @@ -65,7 +64,7 @@ final class PhabricatorMacroMemeDialogController ->setValue($below)); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Create Meme')) ->appendForm($view) ->addCancelButton('/') diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index a031c7f92b..8915954fc4 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -3,23 +3,17 @@ final class PhabricatorMacroViewController extends PhabricatorMacroController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - public function shouldAllowPublic() { return true; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $macro = id(new PhabricatorMacroQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needFiles(true) ->executeOne(); if (!$macro) { @@ -55,7 +49,7 @@ final class PhabricatorMacroViewController new PhabricatorMacroTransactionQuery()); $header = id(new PHUIHeaderView()) - ->setUser($user) + ->setUser($viewer) ->setPolicyObject($macro) ->setHeader($title_long); @@ -71,10 +65,10 @@ final class PhabricatorMacroViewController ? pht('Add Comment') : pht('Grovel in Awe'); - $draft = PhabricatorDraft::newFromUserAndKey($user, $macro->getPHID()); + $draft = PhabricatorDraft::newFromUserAndKey($viewer, $macro->getPHID()); $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setObjectPHID($macro->getPHID()) ->setDraft($draft) ->setHeaderText($comment_header) From 6fb43305be70253829889e2679e1cc1e79a35f7c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 09:06:01 -0700 Subject: [PATCH 018/102] Convert Passhrase to handleRequest Summary: Converts Passphrase Test Plan: New Cred, Edit Cred, Lock, view, destroy Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13726 --- .../PassphraseCredentialConduitController.php | 14 ++++---------- .../PassphraseCredentialCreateController.php | 5 ++--- .../PassphraseCredentialDestroyController.php | 14 ++++---------- .../PassphraseCredentialEditController.php | 16 +++++----------- .../PassphraseCredentialListController.php | 10 +++------- .../PassphraseCredentialLockController.php | 14 ++++---------- .../PassphraseCredentialPublicController.php | 14 ++++---------- .../PassphraseCredentialRevealController.php | 14 ++++---------- .../PassphraseCredentialViewController.php | 14 ++++---------- 9 files changed, 34 insertions(+), 81 deletions(-) diff --git a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php index 4c65b5d07d..b86d18c227 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php @@ -3,19 +3,13 @@ final class PassphraseCredentialConduitController extends PassphraseController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php index a6462b8da8..cfddcbcc4a 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php @@ -2,9 +2,8 @@ final class PassphraseCredentialCreateController extends PassphraseController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $types = PassphraseCredentialType::getAllCreateableTypes(); $types = mpull($types, null, 'getCredentialType'); diff --git a/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php b/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php index 53f9e0650f..8858d0ef6b 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php @@ -3,19 +3,13 @@ final class PassphraseCredentialDestroyController extends PassphraseController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index 36033c7c01..8a2a8da70f 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -2,20 +2,14 @@ final class PassphraseCredentialEditController extends PassphraseController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if ($this->id) { + if ($id) { $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/passphrase/controller/PassphraseCredentialListController.php b/src/applications/passphrase/controller/PassphraseCredentialListController.php index db36a4a6ba..c4da29a984 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialListController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialListController.php @@ -2,19 +2,15 @@ final class PassphraseCredentialListController extends PassphraseController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PassphraseCredentialSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/passphrase/controller/PassphraseCredentialLockController.php b/src/applications/passphrase/controller/PassphraseCredentialLockController.php index 3abe4c8820..4a872d8667 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialLockController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialLockController.php @@ -3,19 +3,13 @@ final class PassphraseCredentialLockController extends PassphraseController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/passphrase/controller/PassphraseCredentialPublicController.php b/src/applications/passphrase/controller/PassphraseCredentialPublicController.php index dc129a327d..56fc6ac4ae 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialPublicController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialPublicController.php @@ -3,19 +3,13 @@ final class PassphraseCredentialPublicController extends PassphraseController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/passphrase/controller/PassphraseCredentialRevealController.php b/src/applications/passphrase/controller/PassphraseCredentialRevealController.php index e82dea7408..c61086b0e7 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialRevealController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialRevealController.php @@ -3,19 +3,13 @@ final class PassphraseCredentialRevealController extends PassphraseController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index ede3bab78f..535527912a 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -2,19 +2,13 @@ final class PassphraseCredentialViewController extends PassphraseController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$credential) { return new Aphront404Response(); From 564aa2c4ba6ef30ac048bbd84ab9076dfc9934fc Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 09:06:26 -0700 Subject: [PATCH 019/102] Update Config app for handleRequest Summary: Update all found callsites in Config. Test Plan: Check setup issues, databases, edit a value, remove a warning, everything I could click on. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13727 --- .../PhabricatorConfigAllController.php | 5 ++- ...abricatorConfigDatabaseIssueController.php | 5 ++- ...bricatorConfigDatabaseStatusController.php | 16 ++++----- .../PhabricatorConfigEditController.php | 36 ++++++++----------- .../PhabricatorConfigGroupController.php | 14 +++----- .../PhabricatorConfigHistoryController.php | 10 +++--- .../PhabricatorConfigIgnoreController.php | 31 +++++++--------- .../PhabricatorConfigIssueListController.php | 5 ++- .../PhabricatorConfigIssueViewController.php | 16 +++------ .../PhabricatorConfigListController.php | 5 ++- .../PhabricatorConfigWelcomeController.php | 5 ++- 11 files changed, 58 insertions(+), 90 deletions(-) diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php index 309304ed21..17bf65fd7b 100644 --- a/src/applications/config/controller/PhabricatorConfigAllController.php +++ b/src/applications/config/controller/PhabricatorConfigAllController.php @@ -3,9 +3,8 @@ final class PhabricatorConfigAllController extends PhabricatorConfigController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $db_values = id(new PhabricatorConfigEntry()) ->loadAllWhere('namespace = %s', 'default'); diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php index 252d8a73de..22df5104e4 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php @@ -3,9 +3,8 @@ final class PhabricatorConfigDatabaseIssueController extends PhabricatorConfigDatabaseController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $query = $this->buildSchemaQuery(); diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php index f69e794fb8..d886e98d74 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -8,16 +8,12 @@ final class PhabricatorConfigDatabaseStatusController private $column; private $key; - public function willProcessRequest(array $data) { - $this->database = idx($data, 'database'); - $this->table = idx($data, 'table'); - $this->column = idx($data, 'column'); - $this->key = idx($data, 'key'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $this->database = $request->getURIData('database'); + $this->table = $request->getURIData('table'); + $this->column = $request->getURIData('column'); + $this->key = $request->getURIData('key'); $query = $this->buildSchemaQuery(); diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index f3360b3496..90547f41f7 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -3,25 +3,19 @@ final class PhabricatorConfigEditController extends PhabricatorConfigController { - private $key; - - public function willProcessRequest(array $data) { - $this->key = $data['key']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $key = $request->getURIData('key'); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); - if (empty($options[$this->key])) { + if (empty($options[$key])) { $ancient = PhabricatorExtraConfigSetupCheck::getAncientConfig(); - if (isset($ancient[$this->key])) { + if (isset($ancient[$key])) { $desc = pht( "This configuration has been removed. You can safely delete ". "it.\n\n%s", - $ancient[$this->key]); + $ancient[$key]); } else { $desc = pht( 'This configuration option is unknown. It may be misspelled, '. @@ -32,14 +26,14 @@ final class PhabricatorConfigEditController // longer exists. Allow it to be edited so it can be reviewed and // deleted. $option = id(new PhabricatorConfigOption()) - ->setKey($this->key) + ->setKey($key) ->setType('wild') ->setDefault(null) ->setDescription($desc); $group = null; $group_uri = $this->getApplicationURI(); } else { - $option = $options[$this->key]; + $option = $options[$key]; $group = $option->getGroup(); $group_uri = $this->getApplicationURI('group/'.$group->getKey().'/'); } @@ -57,11 +51,11 @@ final class PhabricatorConfigEditController $config_entry = id(new PhabricatorConfigEntry()) ->loadOneWhere( 'configKey = %s AND namespace = %s', - $this->key, + $key, 'default'); if (!$config_entry) { $config_entry = id(new PhabricatorConfigEntry()) - ->setConfigKey($this->key) + ->setConfigKey($key) ->setNamespace('default') ->setIsDeleted(true); $config_entry->setPHID($config_entry->generatePHID()); @@ -81,7 +75,7 @@ final class PhabricatorConfigEditController if (!$errors) { $editor = id(new PhabricatorConfigEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); @@ -138,7 +132,7 @@ final class PhabricatorConfigEditController } $engine = new PhabricatorMarkupEngine(); - $engine->setViewer($user); + $engine->setViewer($viewer); $engine->addObject($option, 'description'); $engine->process(); $description = phutil_tag( @@ -149,7 +143,7 @@ final class PhabricatorConfigEditController $engine->getOutput($option, 'description')); $form - ->setUser($user) + ->setUser($viewer) ->addHiddenInput('issue', $request->getStr('issue')) ->appendChild( id(new AphrontFormMarkupControl()) @@ -194,7 +188,7 @@ final class PhabricatorConfigEditController ->setValue($this->renderDefaults($option, $config_entry))); } - $title = pht('Edit %s', $this->key); + $title = pht('Edit %s', $key); $short = pht('Edit'); $form_box = id(new PHUIObjectBoxView()) @@ -212,7 +206,7 @@ final class PhabricatorConfigEditController $crumbs->addTextCrumb($group->getName(), $group_uri); } - $crumbs->addTextCrumb($this->key, '/config/edit/'.$this->key); + $crumbs->addTextCrumb($key, '/config/edit/'.$key); $timeline = $this->buildTransactionTimeline( $config_entry, diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php index b9dffd9a08..4569491427 100644 --- a/src/applications/config/controller/PhabricatorConfigGroupController.php +++ b/src/applications/config/controller/PhabricatorConfigGroupController.php @@ -3,18 +3,12 @@ final class PhabricatorConfigGroupController extends PhabricatorConfigController { - private $groupKey; - - public function willProcessRequest(array $data) { - $this->groupKey = $data['key']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $group_key = $request->getURIData('key'); $groups = PhabricatorApplicationConfigOptions::loadAll(); - $options = idx($groups, $this->groupKey); + $options = idx($groups, $group_key); if (!$options) { return new Aphront404Response(); } diff --git a/src/applications/config/controller/PhabricatorConfigHistoryController.php b/src/applications/config/controller/PhabricatorConfigHistoryController.php index acb1d3c297..d86fb79878 100644 --- a/src/applications/config/controller/PhabricatorConfigHistoryController.php +++ b/src/applications/config/controller/PhabricatorConfigHistoryController.php @@ -3,12 +3,12 @@ final class PhabricatorConfigHistoryController extends PhabricatorConfigController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $xactions = id(new PhabricatorConfigTransactionQuery()) - ->setViewer($user) + ->setViewer($viewer) ->needComments(true) ->execute(); @@ -19,7 +19,7 @@ final class PhabricatorConfigHistoryController $view = $xaction->getApplicationTransactionViewObject(); $timeline = $view - ->setUser($user) + ->setUser($viewer) ->setTransactions($xactions) ->setRenderAsFeed(true) ->setObjectPHID(PhabricatorPHIDConstants::PHID_VOID); diff --git a/src/applications/config/controller/PhabricatorConfigIgnoreController.php b/src/applications/config/controller/PhabricatorConfigIgnoreController.php index ba634dec1a..80a859c147 100644 --- a/src/applications/config/controller/PhabricatorConfigIgnoreController.php +++ b/src/applications/config/controller/PhabricatorConfigIgnoreController.php @@ -3,38 +3,33 @@ final class PhabricatorConfigIgnoreController extends PhabricatorConfigController { - private $verb; - private $issue; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $issue = $request->getURIData('key'); + $verb = $request->getURIData('verb'); - public function willProcessRequest(array $data) { - $this->verb = $data['verb']; - $this->issue = $data['key']; - } - - public function processRequest() { - $request = $this->getRequest(); - $issue_uri = $this->getApplicationURI('issue/'.$this->issue.'/'); + $issue_uri = $this->getApplicationURI('issue/'.$issue.'/'); if ($request->isDialogFormPost()) { - $this->manageApplication(); + $this->manageApplication($issue); return id(new AphrontRedirectResponse())->setURI($issue_uri); } - if ($this->verb == 'ignore') { + if ($verb == 'ignore') { $title = pht('Really ignore this setup issue?'); $submit_title = pht('Ignore'); $body = pht( "You can ignore an issue if you don't want to fix it, or plan to ". "fix it later. Ignored issues won't appear on every page but will ". "still be shown in the list of open issues."); - } else if ($this->verb == 'unignore') { + } else if ($verb == 'unignore') { $title = pht('Unignore this setup issue?'); $submit_title = pht('Unignore'); $body = pht( 'This issue will no longer be suppressed, and will return to its '. 'rightful place as a global setup warning.'); } else { - throw new Exception(pht('Unrecognized verb: %s', $this->verb)); + throw new Exception(pht('Unrecognized verb: %s', $verb)); } $dialog = id(new AphrontDialogView()) @@ -47,15 +42,15 @@ final class PhabricatorConfigIgnoreController return id(new AphrontDialogResponse())->setDialog($dialog); } - public function manageApplication() { + public function manageApplication($issue) { $key = 'config.ignore-issues'; $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); $list = $config_entry->getValue(); - if (isset($list[$this->issue])) { - unset($list[$this->issue]); + if (isset($list[$issue])) { + unset($list[$issue]); } else { - $list[$this->issue] = true; + $list[$issue] = true; } PhabricatorConfigEditor::storeNewValue( diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php index d1d19860b9..89b8ea7cd6 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -3,9 +3,8 @@ final class PhabricatorConfigIssueListController extends PhabricatorConfigController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $nav = $this->buildSideNavView(); $nav->selectFilter('issue/'); diff --git a/src/applications/config/controller/PhabricatorConfigIssueViewController.php b/src/applications/config/controller/PhabricatorConfigIssueViewController.php index a2beb8ae76..e8d6e188a4 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueViewController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueViewController.php @@ -3,21 +3,15 @@ final class PhabricatorConfigIssueViewController extends PhabricatorConfigController { - private $issueKey; - - public function willProcessRequest(array $data) { - $this->issueKey = $data['key']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $issue_key = $request->getURIData('key'); $issues = PhabricatorSetupCheck::runAllChecks(); PhabricatorSetupCheck::setOpenSetupIssueKeys( PhabricatorSetupCheck::getUnignoredIssueKeys($issues)); - if (empty($issues[$this->issueKey])) { + if (empty($issues[$issue_key])) { $content = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Issue Resolved')) @@ -31,7 +25,7 @@ final class PhabricatorConfigIssueViewController pht('Return to Open Issue List'))); $title = pht('Resolved Issue'); } else { - $issue = $issues[$this->issueKey]; + $issue = $issues[$issue_key]; $content = $this->renderIssue($issue); $title = $issue->getShortName(); } diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php index 4da4f2ac01..6a1823ecda 100644 --- a/src/applications/config/controller/PhabricatorConfigListController.php +++ b/src/applications/config/controller/PhabricatorConfigListController.php @@ -3,9 +3,8 @@ final class PhabricatorConfigListController extends PhabricatorConfigController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $nav = $this->buildSideNavView(); $nav->selectFilter('/'); diff --git a/src/applications/config/controller/PhabricatorConfigWelcomeController.php b/src/applications/config/controller/PhabricatorConfigWelcomeController.php index 11132af08b..37442c2375 100644 --- a/src/applications/config/controller/PhabricatorConfigWelcomeController.php +++ b/src/applications/config/controller/PhabricatorConfigWelcomeController.php @@ -3,9 +3,8 @@ final class PhabricatorConfigWelcomeController extends PhabricatorConfigController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $nav = $this->buildSideNavView(); $nav->selectFilter('welcome/'); From 9643e3b244e3ed3bded849268c16372f8fb6c049 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 09:12:35 -0700 Subject: [PATCH 020/102] Update Facts for handleRequest Summary: Updated Facts Controllers Test Plan: Viewed Facts Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13733 --- .../controller/PhabricatorFactChartController.php | 5 ++--- .../fact/controller/PhabricatorFactHomeController.php | 11 +++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/controller/PhabricatorFactChartController.php index 92718f47f8..d6f3bc0c43 100644 --- a/src/applications/fact/controller/PhabricatorFactChartController.php +++ b/src/applications/fact/controller/PhabricatorFactChartController.php @@ -2,9 +2,8 @@ final class PhabricatorFactChartController extends PhabricatorFactController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $table = new PhabricatorFactRaw(); $conn_r = $table->establishConnection('r'); diff --git a/src/applications/fact/controller/PhabricatorFactHomeController.php b/src/applications/fact/controller/PhabricatorFactHomeController.php index b0e24cb79f..8e5c9511eb 100644 --- a/src/applications/fact/controller/PhabricatorFactHomeController.php +++ b/src/applications/fact/controller/PhabricatorFactHomeController.php @@ -6,9 +6,8 @@ final class PhabricatorFactHomeController extends PhabricatorFactController { return true; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); if ($request->isFormPost()) { $uri = new PhutilURI('/fact/chart/'); @@ -34,7 +33,7 @@ final class PhabricatorFactHomeController extends PhabricatorFactController { $spec = $specs[$fact->getFactType()]; $name = $spec->getName(); - $value = $spec->formatValueForDisplay($user, $fact->getValueX()); + $value = $spec->formatValueForDisplay($viewer, $fact->getValueX()); $rows[] = array($name, $value); } @@ -73,7 +72,7 @@ final class PhabricatorFactHomeController extends PhabricatorFactController { private function buildChartForm() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $table = new PhabricatorFactRaw(); $conn_r = $table->establishConnection('r'); @@ -106,7 +105,7 @@ final class PhabricatorFactHomeController extends PhabricatorFactController { } $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Y-Axis')) From 075705f5652674f62e40a6ba36837a83bcef3aea Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 09:13:41 -0700 Subject: [PATCH 021/102] Update Countdown for handleRequest Summary: Updates a couple of callsites Test Plan: View list, see countdowns Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13728 --- .../PhabricatorCountdownDeleteController.php | 16 +++++----------- .../PhabricatorCountdownListController.php | 10 +++------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php b/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php index 5dfbd18146..81129f3beb 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php @@ -3,19 +3,13 @@ final class PhabricatorCountdownDeleteController extends PhabricatorCountdownController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $countdown = id(new PhabricatorCountdownQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php index f3c34d23aa..031e5430fe 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownListController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php @@ -3,19 +3,15 @@ final class PhabricatorCountdownListController extends PhabricatorCountdownController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PhabricatorCountdownSearchEngine()) ->setNavigation($this->buildSideNavView()); From e90379dd2d6606a8f4e3e11e122d6e724b67923d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 09:41:53 -0700 Subject: [PATCH 022/102] Update Files for handleRequest Summary: Update Files application Test Plan: Upload a file, edit a file, view details, transforms, delete file Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13735 --- .../PhabricatorFileCommentController.php | 20 +++----- .../PhabricatorFileComposeController.php | 5 +- .../PhabricatorFileDataController.php | 14 ++---- .../PhabricatorFileDeleteController.php | 22 ++++----- .../PhabricatorFileDropUploadController.php | 5 +- .../PhabricatorFileEditController.php | 14 ++---- .../PhabricatorFileInfoController.php | 46 ++++++++----------- .../PhabricatorFileUploadDialogController.php | 7 ++- 8 files changed, 50 insertions(+), 83 deletions(-) diff --git a/src/applications/files/controller/PhabricatorFileCommentController.php b/src/applications/files/controller/PhabricatorFileCommentController.php index c527e6fc12..11833ea8d8 100644 --- a/src/applications/files/controller/PhabricatorFileCommentController.php +++ b/src/applications/files/controller/PhabricatorFileCommentController.php @@ -2,23 +2,17 @@ final class PhabricatorFileCommentController extends PhabricatorFileController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); if (!$request->isFormPost()) { return new Aphront400Response(); } $file = id(new PhabricatorFileQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$file) { return new Aphront404Response(); @@ -37,7 +31,7 @@ final class PhabricatorFileCommentController extends PhabricatorFileController { ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorFileEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); @@ -56,7 +50,7 @@ final class PhabricatorFileCommentController extends PhabricatorFileController { if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) + ->setViewer($viewer) ->setTransactions($xactions) ->setIsPreview($is_preview); } else { diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index f3e450add5..de5a6757a4 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -3,9 +3,8 @@ final class PhabricatorFileComposeController extends PhabricatorFileController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $colors = array( 'red' => pht('Verbillion'), diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index e528eb15f2..848560f099 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -7,19 +7,15 @@ final class PhabricatorFileDataController extends PhabricatorFileController { private $token; private $file; - public function willProcessRequest(array $data) { - $this->phid = $data['phid']; - $this->key = $data['key']; - $this->token = idx($data, 'token'); - } - public function shouldRequireLogin() { return false; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $this->getViewer(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $this->phid = $request->getURIData('phid'); + $this->key = $request->getURIData('key'); + $this->token = $request->getURIData('token'); $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); diff --git a/src/applications/files/controller/PhabricatorFileDeleteController.php b/src/applications/files/controller/PhabricatorFileDeleteController.php index ed245c5cba..a07fe2e91a 100644 --- a/src/applications/files/controller/PhabricatorFileDeleteController.php +++ b/src/applications/files/controller/PhabricatorFileDeleteController.php @@ -2,19 +2,13 @@ final class PhabricatorFileDeleteController extends PhabricatorFileController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $file = id(new PhabricatorFileQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -25,8 +19,8 @@ final class PhabricatorFileDeleteController extends PhabricatorFileController { return new Aphront404Response(); } - if (($user->getPHID() != $file->getAuthorPHID()) && - (!$user->getIsAdmin())) { + if (($viewer->getPHID() != $file->getAuthorPHID()) && + (!$viewer->getIsAdmin())) { return new Aphront403Response(); } @@ -36,7 +30,7 @@ final class PhabricatorFileDeleteController extends PhabricatorFileController { } $dialog = new AphrontDialogView(); - $dialog->setUser($user); + $dialog->setUser($viewer); $dialog->setTitle(pht('Really delete file?')); $dialog->appendChild(hsprintf( '

%s

', diff --git a/src/applications/files/controller/PhabricatorFileDropUploadController.php b/src/applications/files/controller/PhabricatorFileDropUploadController.php index 34f6bc81e8..fbc4daa93c 100644 --- a/src/applications/files/controller/PhabricatorFileDropUploadController.php +++ b/src/applications/files/controller/PhabricatorFileDropUploadController.php @@ -6,9 +6,8 @@ final class PhabricatorFileDropUploadController /** * @phutil-external-symbol class PhabricatorStartup */ - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); // NOTE: Throws if valid CSRF token is not present in the request. $request->validateCSRF(); diff --git a/src/applications/files/controller/PhabricatorFileEditController.php b/src/applications/files/controller/PhabricatorFileEditController.php index a496202093..9c416b588c 100644 --- a/src/applications/files/controller/PhabricatorFileEditController.php +++ b/src/applications/files/controller/PhabricatorFileEditController.php @@ -2,19 +2,13 @@ final class PhabricatorFileEditController extends PhabricatorFileController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 0e3d041eac..d9b5de14ee 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -2,26 +2,19 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { - private $phid; - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->phid = idx($data, 'phid'); - $this->id = idx($data, 'id'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $phid = $request->getURIData('phid'); - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - if ($this->phid) { + if ($phid) { $file = id(new PhabricatorFileQuery()) - ->setViewer($user) - ->withPHIDs(array($this->phid)) + ->setViewer($viewer) + ->withPHIDs(array($phid)) ->executeOne(); if (!$file) { @@ -30,8 +23,8 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { return id(new AphrontRedirectResponse())->setURI($file->getInfoURI()); } $file = id(new PhabricatorFileQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$file) { return new Aphront404Response(); @@ -40,7 +33,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $phid = $file->getPHID(); $header = id(new PHUIHeaderView()) - ->setUser($user) + ->setUser($viewer) ->setPolicyObject($file) ->setHeader($file->getName()); @@ -87,7 +80,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { } private function buildTransactionView(PhabricatorFile $file) { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $timeline = $this->buildTransactionTimeline( $file, @@ -99,10 +92,10 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { ? pht('Add Comment') : pht('Question File Integrity'); - $draft = PhabricatorDraft::newFromUserAndKey($user, $file->getPHID()); + $draft = PhabricatorDraft::newFromUserAndKey($viewer, $file->getPHID()); $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setObjectPHID($file->getPHID()) ->setDraft($draft) ->setHeaderText($add_comment_header) @@ -116,8 +109,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { } private function buildActionView(PhabricatorFile $file) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $id = $file->getID(); @@ -184,7 +176,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { PhabricatorFile $file, PhabricatorActionListView $actions) { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $properties = id(new PHUIPropertyListView()); $properties->setActionList($actions); @@ -193,12 +185,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { if ($file->getAuthorPHID()) { $properties->addProperty( pht('Author'), - $user->renderHandle($file->getAuthorPHID())); + $viewer->renderHandle($file->getAuthorPHID())); } $properties->addProperty( pht('Created'), - phabricator_datetime($file->getDateCreated(), $user)); + phabricator_datetime($file->getDateCreated(), $viewer)); $finfo = id(new PHUIPropertyListView()); @@ -276,7 +268,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $attached->addProperty( pht('Attached To'), - $user->renderHandleList($phids)); + $viewer->renderHandleList($phids)); } if ($file->isViewableImage()) { @@ -330,7 +322,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $box->addPropertyList($chunkinfo, pht('Chunks')); $chunks = id(new PhabricatorFileChunkQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withChunkHandles(array($file->getStorageHandle())) ->execute(); $chunks = msort($chunks, 'getByteStart'); diff --git a/src/applications/files/controller/PhabricatorFileUploadDialogController.php b/src/applications/files/controller/PhabricatorFileUploadDialogController.php index e99910c430..dd22caa74a 100644 --- a/src/applications/files/controller/PhabricatorFileUploadDialogController.php +++ b/src/applications/files/controller/PhabricatorFileUploadDialogController.php @@ -3,12 +3,11 @@ final class PhabricatorFileUploadDialogController extends PhabricatorFileController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Upload File')) ->appendChild(pht( 'To add files, drag and drop them into the comment text area.')) From 680254935560c2b5ba18e3224cb3f255bd40a73b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 09:56:03 -0700 Subject: [PATCH 023/102] Fix mail title in Countdown Summary: Fixes T8959. Countdown now makes feed stories and emails. Test Plan: Comment on, edit, a countdown. Reviewers: btrahan, epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8959 Differential Revision: https://secure.phabricator.com/D13738 --- .../countdown/editor/PhabricatorCountdownEditor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index 6fc699646d..97f8e5b947 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -160,7 +160,7 @@ final class PhabricatorCountdownEditor protected function buildMailTemplate(PhabricatorLiskDAO $object) { $monogram = $object->getMonogram(); - $name = $object->getName(); + $name = $object->getTitle(); return id(new PhabricatorMetaMTAMail()) ->setSubject("{$monogram}: {$name}") From 8d53f1511565db2aeef0b69c7ea5f7f50339d473 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Jul 2015 15:08:01 -0700 Subject: [PATCH 024/102] Put the HeraldActionRecord table back on the shelf Summary: Fixes T8958. I renamed this class and table (in D13644), but the migration `20150606.mlist.1.php` needs to load records from the table before the table rename applies. Put the table back and accept a tiny bit of cruft here to fix this problem. We can no-op the migration after a while. I'll cherry-pick this to `stable`. Test Plan: Applied migration. Created Herald rules. Reviewed existing Herald rules. Reviewers: btrahan Subscribers: epriestley Maniphest Tasks: T8958 Differential Revision: https://secure.phabricator.com/D13736 --- resources/sql/autopatches/20150727.heraldaction.1.sql | 2 ++ src/applications/herald/storage/HeraldActionRecord.php | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 resources/sql/autopatches/20150727.heraldaction.1.sql diff --git a/resources/sql/autopatches/20150727.heraldaction.1.sql b/resources/sql/autopatches/20150727.heraldaction.1.sql new file mode 100644 index 0000000000..f4cbf19504 --- /dev/null +++ b/resources/sql/autopatches/20150727.heraldaction.1.sql @@ -0,0 +1,2 @@ +RENAME TABLE {$NAMESPACE}_herald.herald_actionrecord + TO {$NAMESPACE}_herald.herald_action; diff --git a/src/applications/herald/storage/HeraldActionRecord.php b/src/applications/herald/storage/HeraldActionRecord.php index 1ad4721b21..dfbee3b79d 100644 --- a/src/applications/herald/storage/HeraldActionRecord.php +++ b/src/applications/herald/storage/HeraldActionRecord.php @@ -7,6 +7,14 @@ final class HeraldActionRecord extends HeraldDAO { protected $action; protected $target; + public function getTableName() { + // TODO: This class was renamed, but we have a migration which affects the + // table prior to to the rename. For now, having cruft here is cleaner than + // having it in the migration. We could rename this table again and no-op + // the migration after some time. See T8958. + return 'herald_action'; + } + protected function getConfiguration() { return array( self::CONFIG_SERIALIZATION => array( From 84049e6d075b8ae91de9bac4b3848f476789d61c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 15:12:03 -0700 Subject: [PATCH 025/102] Fix public/private profile edit note Summary: Fixes T8976. Checks state of note before applying to box. Test Plan: Set policy to login, edit profile. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8976 Differential Revision: https://secure.phabricator.com/D13739 --- .../controller/PhabricatorPeopleProfileEditController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php index 65bf9df0d5..f53278b536 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php @@ -84,10 +84,13 @@ final class PhabricatorPeopleProfileEditController $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Edit Profile')) - ->setInfoView($note) ->setValidationException($validation_exception) ->setForm($form); + if ($note) { + $form_box->setInfoView($note); + } + $nav = $this->buildIconNavView($user); $nav->selectFilter('/'); $nav->appendChild($form_box); From ebc3811bed3fc45f2d762f0acf33521175750c91 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 20:47:23 -0700 Subject: [PATCH 026/102] Cleaner Countdown widget UI Summary: Adds the launch date, normalize spacing a bit. Test Plan: Test a normal and embeded countdowns. {F670199} Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13740 --- resources/celerity/map.php | 4 +-- .../view/PhabricatorCountdownView.php | 11 ++++++++ .../rsrc/css/application/countdown/timer.css | 28 +++++++++++++++---- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4511ce3cb9..a107f206e4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -54,7 +54,7 @@ return array( 'rsrc/css/application/conpherence/update.css' => 'faf6be09', 'rsrc/css/application/conpherence/widget-pane.css' => '419fd50c', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', - 'rsrc/css/application/countdown/timer.css' => '3791bb38', + 'rsrc/css/application/countdown/timer.css' => '3d1355ec', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', 'rsrc/css/application/dashboard/dashboard.css' => 'eb458607', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', @@ -722,7 +722,7 @@ return array( 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => 'a76cefc9', - 'phabricator-countdown-css' => '3791bb38', + 'phabricator-countdown-css' => '3d1355ec', 'phabricator-dashboard-css' => 'eb458607', 'phabricator-drag-and-drop-file-upload' => '07de8873', 'phabricator-draggable-list' => 'a16ec1c6', diff --git a/src/applications/countdown/view/PhabricatorCountdownView.php b/src/applications/countdown/view/PhabricatorCountdownView.php index 7ede4ef887..2e8f1dedb3 100644 --- a/src/applications/countdown/view/PhabricatorCountdownView.php +++ b/src/applications/countdown/view/PhabricatorCountdownView.php @@ -56,6 +56,16 @@ final class PhabricatorCountdownView extends AphrontTagView { javelin_tag('td', array('sigil' => 'phabricator-timer-seconds'), '-'), ); + $epoch = $countdown->getEpoch(); + $launch_date = phabricator_datetime($epoch, $this->getUser()); + $foot = phutil_tag( + 'td', + array( + 'colspan' => '4', + 'class' => 'phabricator-timer-foot', + ), + $launch_date); + $container = celerity_generate_unique_node_id(); $content = phutil_tag( 'div', @@ -65,6 +75,7 @@ final class PhabricatorCountdownView extends AphrontTagView { phutil_tag('table', array('class' => 'phabricator-timer-table'), array( phutil_tag('tr', array(), $ths), phutil_tag('tr', array(), $dashes), + phutil_tag('tr', array(), $foot), )), )); diff --git a/webroot/rsrc/css/application/countdown/timer.css b/webroot/rsrc/css/application/countdown/timer.css index acc5e61fc0..f9f6450892 100644 --- a/webroot/rsrc/css/application/countdown/timer.css +++ b/webroot/rsrc/css/application/countdown/timer.css @@ -24,23 +24,26 @@ } .phabricator-timer-header { - font-size: {$biggerfontsize}; font-weight: bold; - padding: 4px 8px; + padding: 8px; background: {$bluebackground}; border-radius: 3px 3px 0 0; - border-bottom: 1px solid {$lightblueborder}; +} + +.phabricator-timer-header a { + color: {$darkbluetext}; + font-weight: normal; } .phabricator-timer-table { width: 100%; - margin: 8px 0; + margin: 8px 0 0 0; } .phabricator-timer-table th { text-align: center; font-size: {$biggerfontsize}; - color: {$greytext}; + color: {$bluetext}; width: 10%; padding: 4px; } @@ -49,10 +52,23 @@ padding: 4px; text-align: center; font-size: 36px; + font-weight: bold; overflow: hidden; line-height: auto; height: auto; - line-height: 36px; + line-height: 44px; +} + +.phabricator-timer-table td.phabricator-timer-foot { + font-size: {$normalfontsize}; + line-height: 16px; + border-top: 1px solid {$thinblueborder}; + color: {$bluetext}; + font-weight: normal; + padding: 8px; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; + text-align: center; } .phabricator-timer-table small { From 504579850f0e9b3f9123908706719cbf8ff4c5d4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 21:02:16 -0700 Subject: [PATCH 027/102] Make countdown embed width more forgiving Summary: For smaller feed columns, make countdown embed attempt to fit. Test Plan: Narrow Feed Column on Dashboard. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13741 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/application/countdown/timer.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a107f206e4..14a77746e2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -54,7 +54,7 @@ return array( 'rsrc/css/application/conpherence/update.css' => 'faf6be09', 'rsrc/css/application/conpherence/widget-pane.css' => '419fd50c', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', - 'rsrc/css/application/countdown/timer.css' => '3d1355ec', + 'rsrc/css/application/countdown/timer.css' => 'e7544472', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', 'rsrc/css/application/dashboard/dashboard.css' => 'eb458607', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', @@ -722,7 +722,7 @@ return array( 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => 'a76cefc9', - 'phabricator-countdown-css' => '3d1355ec', + 'phabricator-countdown-css' => 'e7544472', 'phabricator-dashboard-css' => 'eb458607', 'phabricator-drag-and-drop-file-upload' => '07de8873', 'phabricator-draggable-list' => 'a16ec1c6', diff --git a/webroot/rsrc/css/application/countdown/timer.css b/webroot/rsrc/css/application/countdown/timer.css index f9f6450892..8b87aa6ed2 100644 --- a/webroot/rsrc/css/application/countdown/timer.css +++ b/webroot/rsrc/css/application/countdown/timer.css @@ -9,13 +9,13 @@ background: #ffffff; } -.device-phone .phabricator-timer { +.device .phabricator-timer { margin-left: 8px; margin-right: 8px; } .phabricator-remarkup .phabricator-timer { - width: 360px; + max-width: 360px; margin: 0 0 12px 0; } From 62e052f3fc3511c7b6b159e18c48fc68d3e6eadd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 28 Jul 2015 07:44:55 -0700 Subject: [PATCH 028/102] Remove View Log action from Herald List Summary: Fixes T8979. I beleive this explicit page was merged into view, at least I can't find any place to link it now. Test Plan: View a Herald list, no icon. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8979 Differential Revision: https://secure.phabricator.com/D13743 --- src/applications/herald/query/HeraldRuleSearchEngine.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/applications/herald/query/HeraldRuleSearchEngine.php b/src/applications/herald/query/HeraldRuleSearchEngine.php index 63e6efc818..04b7a5d852 100644 --- a/src/applications/herald/query/HeraldRuleSearchEngine.php +++ b/src/applications/herald/query/HeraldRuleSearchEngine.php @@ -197,12 +197,6 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine { $item->addIcon('fa-lock grey', pht('Disabled')); } - $item->addAction( - id(new PHUIListItemView()) - ->setHref($this->getApplicationURI("history/{$id}/")) - ->setIcon('fa-file-text-o') - ->setName(pht('Edit Log'))); - $content_type_name = idx($content_type_map, $rule->getContentType()); $item->addAttribute(pht('Affects: %s', $content_type_name)); From a3a5176b7e17da4daaf7e8e24b09f65db8f2a331 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 28 Jul 2015 07:47:42 -0700 Subject: [PATCH 029/102] Add basic countdown timer to object list Summary: Allows a rough setting of a number and noun for object item list view. Test Plan: Use in countdown, set various times. {F670267} Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13742 --- resources/celerity/map.php | 6 ++--- .../PhabricatorCountdownSearchEngine.php | 13 ++++++---- src/view/phui/PHUIObjectItemView.php | 26 +++++++++++++++++++ .../css/phui/phui-object-item-list-view.css | 13 ++++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 14a77746e2..bb24a63231 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'b7d43de8', + 'core.pkg.css' => 'af140e11', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '9451634c', @@ -142,7 +142,7 @@ return array( 'rsrc/css/phui/phui-info-view.css' => '5b16bac6', 'rsrc/css/phui/phui-list.css' => '125599df', 'rsrc/css/phui/phui-object-box.css' => '407eaf5a', - 'rsrc/css/phui/phui-object-item-list-view.css' => 'a1b990b7', + 'rsrc/css/phui/phui-object-item-list-view.css' => '36ce366c', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-property-list-view.css' => 'aeb09581', @@ -797,7 +797,7 @@ return array( 'phui-inline-comment-view-css' => '9fadd6b8', 'phui-list-view-css' => '125599df', 'phui-object-box-css' => '407eaf5a', - 'phui-object-item-list-view-css' => 'a1b990b7', + 'phui-object-item-list-view-css' => '36ce366c', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', 'phui-property-list-view-css' => 'aeb09581', diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php index 74ee2b9518..b9f438ece8 100644 --- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -102,13 +102,9 @@ final class PhabricatorCountdownSearchEngine foreach ($countdowns as $countdown) { $id = $countdown->getID(); $ended = false; - $icon = 'fa-clock-o'; - $color = 'green'; $epoch = $countdown->getEpoch(); if ($epoch <= PhabricatorTime::getNow()) { $ended = true; - $icon = 'fa-check-square-o'; - $color = 'grey'; } $item = id(new PHUIObjectItemView()) @@ -116,7 +112,6 @@ final class PhabricatorCountdownSearchEngine ->setObject($countdown) ->setObjectName("C{$id}") ->setHeader($countdown->getTitle()) - ->setStatusIcon($icon.' '.$color) ->setHref($this->getApplicationURI("{$id}/")) ->addByline( pht( @@ -128,6 +123,14 @@ final class PhabricatorCountdownSearchEngine pht('Launched on %s', phabricator_datetime($epoch, $viewer))); $item->setDisabled(true); } else { + $time_left = ($epoch - PhabricatorTime::getNow()); + $num = round($time_left / (60 * 60 * 24)); + $noun = pht('Days'); + if ($num < 1) { + $num = round($time_left / (60 * 60), 1); + $noun = pht('Hours'); + } + $item->setCountdown($num, $noun); $item->addAttribute( phabricator_datetime($epoch, $viewer)); } diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 626dcd2781..973592a94c 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -24,6 +24,8 @@ final class PHUIObjectItemView extends AphrontTagView { private $imageIcon; private $titleText; private $badge; + private $countdownNum; + private $countdownNoun; const AGE_FRESH = 'fresh'; const AGE_STALE = 'stale'; @@ -105,6 +107,12 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } + public function setCountdown($num, $noun) { + $this->countdownNum = $num; + $this->countdownNoun = $noun; + return $this; + } + public function setTitleText($title_text) { $this->titleText = $title_text; return $this; @@ -603,6 +611,24 @@ final class PHUIObjectItemView extends AphrontTagView { $this->badge); } + if ($this->countdownNum) { + $countdown = phutil_tag( + 'div', + array( + 'class' => 'phui-object-item-countdown-number', + ), + array( + phutil_tag_div('', $this->countdownNum), + phutil_tag_div('', $this->countdownNoun), + )); + $column0 = phutil_tag( + 'div', + array( + 'class' => 'phui-object-item-col0 phui-object-item-countdown', + ), + $countdown); + } + $column1 = phutil_tag( 'div', array( diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css index 2be3de770d..fe965b1b00 100644 --- a/webroot/rsrc/css/phui/phui-object-item-list-view.css +++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css @@ -627,6 +627,19 @@ ul.phui-object-item-list-view .phui-object-item-selected left: 0; } +/* - Countdowns ------------------------------------------------------------ */ + +.phui-object-item-col0.phui-object-item-countdown { + width: 52px; + padding: 0; +} + +.phui-object-item-countdown .phui-object-item-countdown-number { + border-right: 1px solid {$thinblueborder}; + text-align: center; + color: {$bluetext}; +} + /* - Dashboards ------------------------------------------------------------ */ From 84b92afe08154c2c6f714a9a062cd84e4cef540e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 28 Jul 2015 07:56:59 -0700 Subject: [PATCH 030/102] Fix sending mail on Badges Summary: Hopefully fixes mail issues. Don't have a local way of testing email replies, though. Test Plan: Comment on a badge, get mail! Reviewers: epriestley Reviewed By: epriestley Subscribers: seayoung1112, epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13717 --- src/applications/badges/editor/PhabricatorBadgesEditor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php index 539f071b49..a6cd90bb92 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditor.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php @@ -181,6 +181,7 @@ final class PhabricatorBadgesEditor protected function getMailTo(PhabricatorLiskDAO $object) { return array( + $object->getCreatorPHID(), $this->requireActor()->getPHID(), ); } From eb152d3053a08f16a0f7f15354c268033ea64725 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 28 Jul 2015 07:57:15 -0700 Subject: [PATCH 031/102] Allow Tokens on Badge Summary: Might as well for consistency. Test Plan: Given a token to a badge. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13716 --- src/__phutil_library_map__.php | 1 + .../badges/storage/PhabricatorBadgesBadge.php | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7f2a56e406..a9cfc87768 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5391,6 +5391,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorSubscribableInterface', + 'PhabricatorTokenReceiverInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', ), diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php index d7e13ffc61..8829d90164 100644 --- a/src/applications/badges/storage/PhabricatorBadgesBadge.php +++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php @@ -5,6 +5,7 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO PhabricatorPolicyInterface, PhabricatorApplicationTransactionInterface, PhabricatorSubscribableInterface, + PhabricatorTokenReceiverInterface, PhabricatorFlaggableInterface, PhabricatorDestructibleInterface { @@ -189,6 +190,15 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO } +/* -( PhabricatorTokenReceiverInterface )---------------------------------- */ + + + public function getUsersToNotifyOfTokenGiven() { + return array($this->getCreatorPHID()); + } + + + /* -( PhabricatorDestructibleInterface )----------------------------------- */ From bbc1074cedfc0d4189aebf17c5175ff791cd104b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 28 Jul 2015 08:04:13 -0700 Subject: [PATCH 032/102] Allow upload of arbitrary text files Summary: Fixes T8984. Because of how drag-and-drop upload works, the text file with content `code` is interpreted as a forbidden variable. Disable this check for the drop upload controller. (The risk here is a general one where the controller redirects and bundles paramters; this controller does not do that, so it's safe to make this change.) Test Plan: Uploaded a text file containing only the string "code" (no quotes) by using drag-and-drop. Reviewers: chad Reviewed By: chad Subscribers: epriestley Maniphest Tasks: T8984 Differential Revision: https://secure.phabricator.com/D13744 --- .../controller/PhabricatorFileDropUploadController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/files/controller/PhabricatorFileDropUploadController.php b/src/applications/files/controller/PhabricatorFileDropUploadController.php index fbc4daa93c..222fc799c7 100644 --- a/src/applications/files/controller/PhabricatorFileDropUploadController.php +++ b/src/applications/files/controller/PhabricatorFileDropUploadController.php @@ -3,6 +3,12 @@ final class PhabricatorFileDropUploadController extends PhabricatorFileController { + public function shouldAllowRestrictedParameter($parameter_name) { + // Prevent false positives from file content when it is submitted via + // drag-and-drop upload. + return true; + } + /** * @phutil-external-symbol class PhabricatorStartup */ From 3d4df1fe8f7c3e49253388a3ed29b562f35fda07 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 28 Jul 2015 10:41:42 -0700 Subject: [PATCH 033/102] Add toolip to Mock History in Pholio Summary: Fixes T8985, Adds a tooltip with the filename Test Plan: Update a few Pholio mocks, see names when hovering Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8985 Differential Revision: https://secure.phabricator.com/D13746 --- src/applications/pholio/view/PholioMockThumbGridView.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/pholio/view/PholioMockThumbGridView.php b/src/applications/pholio/view/PholioMockThumbGridView.php index 8e9d3007c5..d7d174d928 100644 --- a/src/applications/pholio/view/PholioMockThumbGridView.php +++ b/src/applications/pholio/view/PholioMockThumbGridView.php @@ -116,6 +116,7 @@ final class PholioMockThumbGridView extends AphrontView { $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_THUMBGRID; $xform = PhabricatorFileTransform::getTransformByKey($preview_key); + Javelin::initBehavior('phabricator-tooltips'); $attributes = array( 'class' => 'pholio-mock-thumb-grid-image', @@ -161,11 +162,13 @@ final class PholioMockThumbGridView extends AphrontView { return javelin_tag( 'a', array( - 'sigil' => 'mock-thumbnail', + 'sigil' => 'mock-thumbnail has-tooltip', 'class' => implode(' ', $classes), 'href' => '#', 'meta' => array( 'imageID' => $image->getID(), + 'tip' => $image->getName(), + 'align' => 'N', ), ), array( From fb13692bd3e54a34bf7e6f89788eb00692e7cef7 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 28 Jul 2015 19:42:26 -0700 Subject: [PATCH 034/102] Add Priority to Maniphest Hovercard Summary: Fixes T5524, adds priority to Maniphest's hovercard. Also fixes Blocking language. Test Plan: Hover over a task, see new stuff. Reviewers: lpriestley, epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T5524 Differential Revision: https://secure.phabricator.com/D13750 --- .../maniphest/event/ManiphestHovercardEventListener.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/applications/maniphest/event/ManiphestHovercardEventListener.php b/src/applications/maniphest/event/ManiphestHovercardEventListener.php index a33a854bc6..5615b78d04 100644 --- a/src/applications/maniphest/event/ManiphestHovercardEventListener.php +++ b/src/applications/maniphest/event/ManiphestHovercardEventListener.php @@ -53,12 +53,15 @@ final class ManiphestHovercardEventListener extends PhabricatorEventListener { $owner = phutil_tag('em', array(), pht('None')); } $hovercard->addField(pht('Assigned To'), $owner); + $hovercard->addField( + pht('Priority'), + ManiphestTaskPriority::getTaskPriorityName($task->getPriority())); if ($edge_phids) { $edge_types = array( $e_project => pht('Projects'), - $e_dep_by => pht('Dependent Tasks'), - $e_dep_on => pht('Depends On'), + $e_dep_by => pht('Blocks'), + $e_dep_on => pht('Blocked By'), ); $max_count = 6; From b24935df2f74006c8221ee934c3d06b0781ac70b Mon Sep 17 00:00:00 2001 From: lkassianik Date: Tue, 28 Jul 2015 20:21:12 -0700 Subject: [PATCH 035/102] Uninstalling an application, as a non-admin, should give the same modal as clicking "Edit Policies" Summary: Fixes T8893, Uninstalling an application, as a non-admin, should give the same modal as clicking "Edit Policies". Test Plan: Open `/applications/view/PhabricatorAuditApplication/` and confirm that "Edit Policies" and "Uninstall" result in the same modal. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: epriestley, Korvin Maniphest Tasks: T8893 Differential Revision: https://secure.phabricator.com/D13753 --- .../PhabricatorApplicationUninstallController.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php index b43c728257..ae1c5f8748 100644 --- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php +++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php @@ -6,10 +6,6 @@ final class PhabricatorApplicationUninstallController private $application; private $action; - public function shouldRequireAdmin() { - return true; - } - public function willProcessRequest(array $data) { $this->application = $data['application']; $this->action = $data['action']; @@ -19,7 +15,15 @@ final class PhabricatorApplicationUninstallController $request = $this->getRequest(); $user = $request->getUser(); - $selected = PhabricatorApplication::getByClass($this->application); + $selected = id(new PhabricatorApplicationQuery()) + ->setViewer($user) + ->withClasses(array($this->application)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); if (!$selected) { return new Aphront404Response(); From a66be3fa28c7072b3365e0e6c5c3de2326ff13ea Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 29 Jul 2015 16:58:48 -0700 Subject: [PATCH 036/102] Update Fund for handleProcess Summary: Updates the Fund application Test Plan: New Initiative, Back, Close, View Backers Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13758 --- .../controller/FundBackerListController.php | 24 +++++++------------ .../FundInitiativeBackController.php | 14 ++++------- .../FundInitiativeCloseController.php | 14 ++++------- .../FundInitiativeEditController.php | 16 ++++--------- .../FundInitiativeListController.php | 14 ++++------- .../FundInitiativeViewController.php | 13 ++++------ 6 files changed, 31 insertions(+), 64 deletions(-) diff --git a/src/applications/fund/controller/FundBackerListController.php b/src/applications/fund/controller/FundBackerListController.php index 0cc0f5a0f5..ab75b123dd 100644 --- a/src/applications/fund/controller/FundBackerListController.php +++ b/src/applications/fund/controller/FundBackerListController.php @@ -3,26 +3,21 @@ final class FundBackerListController extends FundController { - private $id; - private $queryKey; private $initiative; public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $querykey = $request->getURIData('queryKey'); - public function processRequest() { - $request = $this->getRequest(); - - if ($this->id) { + if ($id) { $this->initiative = id(new FundInitiativeQuery()) - ->setViewer($request->getUser()) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$this->initiative) { return new Aphront404Response(); @@ -30,7 +25,7 @@ final class FundBackerListController } $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine($this->getEngine()) ->setNavigation($this->buildSideNavView()); @@ -66,8 +61,7 @@ final class FundBackerListController } private function getEngine() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $engine = id(new FundBackerSearchEngine()) ->setViewer($viewer); diff --git a/src/applications/fund/controller/FundInitiativeBackController.php b/src/applications/fund/controller/FundInitiativeBackController.php index cb4c12ea40..414a5ebd64 100644 --- a/src/applications/fund/controller/FundInitiativeBackController.php +++ b/src/applications/fund/controller/FundInitiativeBackController.php @@ -3,19 +3,13 @@ final class FundInitiativeBackController extends FundController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $initiative = id(new FundInitiativeQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$initiative) { return new Aphront404Response(); diff --git a/src/applications/fund/controller/FundInitiativeCloseController.php b/src/applications/fund/controller/FundInitiativeCloseController.php index 803b6242cf..6adddb0d80 100644 --- a/src/applications/fund/controller/FundInitiativeCloseController.php +++ b/src/applications/fund/controller/FundInitiativeCloseController.php @@ -3,19 +3,13 @@ final class FundInitiativeCloseController extends FundController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $initiative = id(new FundInitiativeQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/fund/controller/FundInitiativeEditController.php b/src/applications/fund/controller/FundInitiativeEditController.php index b63b4d6d6b..75f7d338c9 100644 --- a/src/applications/fund/controller/FundInitiativeEditController.php +++ b/src/applications/fund/controller/FundInitiativeEditController.php @@ -3,20 +3,14 @@ final class FundInitiativeEditController extends FundController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if ($this->id) { + if ($id) { $initiative = id(new FundInitiativeQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/fund/controller/FundInitiativeListController.php b/src/applications/fund/controller/FundInitiativeListController.php index c737de4828..6ba398fb2b 100644 --- a/src/applications/fund/controller/FundInitiativeListController.php +++ b/src/applications/fund/controller/FundInitiativeListController.php @@ -3,19 +3,15 @@ final class FundInitiativeListController extends FundController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new FundInitiativeSearchEngine()) ->setNavigation($this->buildSideNavView()); @@ -23,13 +19,13 @@ final class FundInitiativeListController } public function buildSideNavView() { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new FundInitiativeSearchEngine()) - ->setViewer($user) + ->setViewer($viewer) ->addNavigationItems($nav->getMenu()); $nav->addLabel(pht('Backers')); diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index 37618b22d2..3d12f1549c 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -3,23 +3,18 @@ final class FundInitiativeViewController extends FundController { - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $initiative = id(new FundInitiativeQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$initiative) { return new Aphront404Response(); From cdf03b0f7ae636560571c5d5027a467e3eb3872e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 29 Jul 2015 17:00:31 -0700 Subject: [PATCH 037/102] Update Flag for handleRequest Summary: Updates Flag app for handleRequest Test Plan: Flag, Unflag, list flags, remove flag Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13757 --- .../PhabricatorFlagDeleteController.php | 15 ++++-------- .../PhabricatorFlagEditController.php | 23 +++++++------------ .../PhabricatorFlagListController.php | 11 ++++----- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/applications/flag/controller/PhabricatorFlagDeleteController.php b/src/applications/flag/controller/PhabricatorFlagDeleteController.php index cf1202c251..4dcb2cc371 100644 --- a/src/applications/flag/controller/PhabricatorFlagDeleteController.php +++ b/src/applications/flag/controller/PhabricatorFlagDeleteController.php @@ -2,22 +2,17 @@ final class PhabricatorFlagDeleteController extends PhabricatorFlagController { - private $id; - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $flag = id(new PhabricatorFlag())->load($this->id); + $flag = id(new PhabricatorFlag())->load($id); if (!$flag) { return new Aphront404Response(); } - if ($flag->getOwnerPHID() != $user->getPHID()) { + if ($flag->getOwnerPHID() != $viewer->getPHID()) { return new Aphront400Response(); } diff --git a/src/applications/flag/controller/PhabricatorFlagEditController.php b/src/applications/flag/controller/PhabricatorFlagEditController.php index b6e2327cd4..5844a3801a 100644 --- a/src/applications/flag/controller/PhabricatorFlagEditController.php +++ b/src/applications/flag/controller/PhabricatorFlagEditController.php @@ -2,19 +2,12 @@ final class PhabricatorFlagEditController extends PhabricatorFlagController { - private $phid; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $phid = $request->getURIData('phid'); - public function willProcessRequest(array $data) { - $this->phid = $data['phid']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $phid = $this->phid; $handle = id(new PhabricatorHandleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); @@ -22,14 +15,14 @@ final class PhabricatorFlagEditController extends PhabricatorFlagController { return new Aphront404Response(); } - $flag = PhabricatorFlagQuery::loadUserFlag($user, $phid); + $flag = PhabricatorFlagQuery::loadUserFlag($viewer, $phid); if (!$flag) { $flag = new PhabricatorFlag(); - $flag->setOwnerPHID($user->getPHID()); + $flag->setOwnerPHID($viewer->getPHID()); $flag->setType($handle->getType()); $flag->setObjectPHID($handle->getPHID()); - $flag->setReasonPHID($user->getPHID()); + $flag->setReasonPHID($viewer->getPHID()); } if ($request->isDialogFormPost()) { @@ -43,7 +36,7 @@ final class PhabricatorFlagEditController extends PhabricatorFlagController { $type_name = $handle->getTypeName(); $dialog = new AphrontDialogView(); - $dialog->setUser($user); + $dialog->setUser($viewer); $dialog->setTitle(pht('Flag %s', $type_name)); diff --git a/src/applications/flag/controller/PhabricatorFlagListController.php b/src/applications/flag/controller/PhabricatorFlagListController.php index 9029a35a7e..88a6bcf7e0 100644 --- a/src/applications/flag/controller/PhabricatorFlagListController.php +++ b/src/applications/flag/controller/PhabricatorFlagListController.php @@ -2,19 +2,16 @@ final class PhabricatorFlagListController extends PhabricatorFlagController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PhabricatorFlagSearchEngine()) ->setNavigation($this->buildSideNavView()); From 77ababadcb78204a475a629012f0c575e56bf3a6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 29 Jul 2015 17:02:10 -0700 Subject: [PATCH 038/102] Update Legalpad for handleRequest Summary: Updates Legalpad for handleRequest instead of processRequest Test Plan: New Document, Sign Document, Add exemption, view signatures. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13756 --- .../LegalpadDocumentCommentController.php | 20 ++++----- .../LegalpadDocumentDoneController.php | 5 +-- .../LegalpadDocumentEditController.php | 30 ++++++------- .../LegalpadDocumentListController.php | 10 ++--- .../LegalpadDocumentManageController.php | 42 ++++++++----------- ...egalpadDocumentSignatureListController.php | 26 +++++------- ...ocumentSignatureVerificationController.php | 14 ++----- ...egalpadDocumentSignatureViewController.php | 14 ++----- 8 files changed, 63 insertions(+), 98 deletions(-) diff --git a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php b/src/applications/legalpad/controller/LegalpadDocumentCommentController.php index f0f557d67f..530696437b 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentCommentController.php @@ -2,23 +2,17 @@ final class LegalpadDocumentCommentController extends LegalpadController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); if (!$request->isFormPost()) { return new Aphront400Response(); } $document = id(new LegalpadDocumentQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needDocumentBodies(true) ->executeOne(); @@ -48,7 +42,7 @@ final class LegalpadDocumentCommentController extends LegalpadController { } $editor = id(new LegalpadDocumentEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect($request->isContinueRequest()) ->setIsPreview($is_preview); @@ -67,7 +61,7 @@ final class LegalpadDocumentCommentController extends LegalpadController { if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) + ->setViewer($viewer) ->setTransactions($xactions) ->setIsPreview($is_preview); } else { diff --git a/src/applications/legalpad/controller/LegalpadDocumentDoneController.php b/src/applications/legalpad/controller/LegalpadDocumentDoneController.php index e6601ba072..b12e3f65ad 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentDoneController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentDoneController.php @@ -6,9 +6,8 @@ final class LegalpadDocumentDoneController extends LegalpadController { return true; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); return $this->newDialog() ->setTitle(pht('Verify Signature')) diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php index 140a26a670..edbaaf99e2 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php @@ -3,25 +3,25 @@ final class LegalpadDocumentEditController extends LegalpadController { public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); - + $viewer = $request->getViewer(); $id = $request->getURIData('id'); + if (!$id) { $is_create = true; $this->requireApplicationCapability( LegalpadCreateDocumentsCapability::CAPABILITY); - $document = LegalpadDocument::initializeNewDocument($user); + $document = LegalpadDocument::initializeNewDocument($viewer); $body = id(new LegalpadDocumentBody()) - ->setCreatorPHID($user->getPHID()); + ->setCreatorPHID($viewer->getPHID()); $document->attachDocumentBody($body); $document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID); } else { $is_create = false; $document = id(new LegalpadDocumentQuery()) - ->setViewer($user) + ->setViewer($viewer) ->needDocumentBodies(true) ->requireCapabilities( array( @@ -94,7 +94,7 @@ final class LegalpadDocumentEditController extends LegalpadController { $v_require_signature = $request->getBool('requireSignature', 0); if ($v_require_signature) { - if (!$user->getIsAdmin()) { + if (!$viewer->getIsAdmin()) { $errors[] = pht('Only admins may require signature.'); } $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; @@ -104,7 +104,7 @@ final class LegalpadDocumentEditController extends LegalpadController { 'signing to use Phabricator.'); } } - if ($user->getIsAdmin()) { + if ($viewer->getIsAdmin()) { $xactions[] = id(new LegalpadTransaction()) ->setTransactionType(LegalpadTransaction::TYPE_REQUIRE_SIGNATURE) ->setNewValue($v_require_signature); @@ -114,7 +114,7 @@ final class LegalpadDocumentEditController extends LegalpadController { $editor = id(new LegalpadDocumentEditor()) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) - ->setActor($user); + ->setActor($viewer); $xactions = $editor->applyTransactions($document, $xactions); @@ -130,7 +130,7 @@ final class LegalpadDocumentEditController extends LegalpadController { } $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setID('document-title') @@ -162,7 +162,7 @@ final class LegalpadDocumentEditController extends LegalpadController { $form ->appendChild( id(new AphrontFormCheckboxControl()) - ->setDisabled(!$user->getIsAdmin()) + ->setDisabled(!$viewer->getIsAdmin()) ->setLabel(pht('Require Signature')) ->addCheckbox( 'requireSignature', @@ -175,7 +175,7 @@ final class LegalpadDocumentEditController extends LegalpadController { $form ->appendChild( id(new PhabricatorRemarkupControl()) - ->setUser($user) + ->setUser($viewer) ->setID('preamble') ->setLabel(pht('Preamble')) ->setValue($v_preamble) @@ -184,7 +184,7 @@ final class LegalpadDocumentEditController extends LegalpadController { pht('Optional help text for users signing this document.'))) ->appendChild( id(new PhabricatorRemarkupControl()) - ->setUser($user) + ->setUser($viewer) ->setID('document-text') ->setLabel(pht('Document Body')) ->setError($e_text) @@ -193,21 +193,21 @@ final class LegalpadDocumentEditController extends LegalpadController { ->setName('text')); $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setObject($document) ->execute(); $form ->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($document) ->setPolicies($policies) ->setName('can_view')) ->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($document) ->setPolicies($policies) diff --git a/src/applications/legalpad/controller/LegalpadDocumentListController.php b/src/applications/legalpad/controller/LegalpadDocumentListController.php index 3229107658..cbd92f87a9 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentListController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentListController.php @@ -2,19 +2,15 @@ final class LegalpadDocumentListController extends LegalpadController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new LegalpadDocumentSearchEngine()) ->setNavigation($this->buildSideNav()); diff --git a/src/applications/legalpad/controller/LegalpadDocumentManageController.php b/src/applications/legalpad/controller/LegalpadDocumentManageController.php index f24c0d03ce..9fc773258f 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentManageController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentManageController.php @@ -2,21 +2,15 @@ final class LegalpadDocumentManageController extends LegalpadController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); // NOTE: We require CAN_EDIT to view this page. $document = id(new LegalpadDocumentQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needDocumentBodies(true) ->needContributors(true) ->requireCapabilities( @@ -35,7 +29,7 @@ final class LegalpadDocumentManageController extends LegalpadController { $document_body = $document->getDocumentBody(); $engine = id(new PhabricatorMarkupEngine()) - ->setViewer($user); + ->setViewer($viewer); $engine->addObject( $document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); @@ -48,7 +42,7 @@ final class LegalpadDocumentManageController extends LegalpadController { $header = id(new PHUIHeaderView()) ->setHeader($title) - ->setUser($user) + ->setUser($viewer) ->setPolicyObject($document); $actions = $this->buildActionView($document); @@ -99,15 +93,15 @@ final class LegalpadDocumentManageController extends LegalpadController { } private function buildActionView(LegalpadDocument $document) { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $actions = id(new PhabricatorActionListView()) - ->setUser($user) + ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($document); $can_edit = PhabricatorPolicyFilter::hasCapability( - $user, + $viewer, $document, PhabricatorPolicyCapability::CAN_EDIT); @@ -141,10 +135,10 @@ final class LegalpadDocumentManageController extends LegalpadController { PhabricatorMarkupEngine $engine, PhabricatorActionListView $actions) { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($user) + ->setUser($viewer) ->setObject($document) ->setActionList($actions); @@ -154,11 +148,11 @@ final class LegalpadDocumentManageController extends LegalpadController { $properties->addProperty( pht('Last Updated'), - phabricator_datetime($document->getDateModified(), $user)); + phabricator_datetime($document->getDateModified(), $viewer)); $properties->addProperty( pht('Updated By'), - $user->renderHandle($document->getDocumentBody()->getCreatorPHID())); + $viewer->renderHandle($document->getDocumentBody()->getCreatorPHID())); $properties->addProperty( pht('Versions'), @@ -167,7 +161,7 @@ final class LegalpadDocumentManageController extends LegalpadController { if ($document->getContributors()) { $properties->addProperty( pht('Contributors'), - $user + $viewer ->renderHandleList($document->getContributors()) ->setAsInline(true)); } @@ -180,9 +174,9 @@ final class LegalpadDocumentManageController extends LegalpadController { private function buildAddCommentView( LegalpadDocument $document, $comment_form_id) { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); - $draft = PhabricatorDraft::newFromUserAndKey($user, $document->getPHID()); + $draft = PhabricatorDraft::newFromUserAndKey($viewer, $document->getPHID()); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); @@ -191,7 +185,7 @@ final class LegalpadDocumentManageController extends LegalpadController { : pht('Debate Legislation'); $form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setObjectPHID($document->getPHID()) ->setFormID($comment_form_id) ->setHeaderText($title) diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php index 945ad2dc39..5330049aae 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php @@ -2,23 +2,17 @@ final class LegalpadDocumentSignatureListController extends LegalpadController { - private $documentID; - private $queryKey; private $document; - public function willProcessRequest(array $data) { - $this->documentID = idx($data, 'id'); - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $querykey = $request->getURIData('queryKey'); - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - if ($this->documentID) { + if ($id) { $document = id(new LegalpadDocumentQuery()) - ->setViewer($user) - ->withIDs(array($this->documentID)) + ->setViewer($viewer) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -39,7 +33,7 @@ final class LegalpadDocumentSignatureListController extends LegalpadController { } $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine($engine) ->setNavigation($this->buildSideNav()); @@ -47,13 +41,13 @@ final class LegalpadDocumentSignatureListController extends LegalpadController { } public function buildSideNav($for_app = false) { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); $engine = id(new LegalpadDocumentSignatureSearchEngine()) - ->setViewer($user); + ->setViewer($viewer); if ($this->document) { $engine->setDocument($this->document); diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php index a940eb646f..b50b91e896 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php @@ -3,25 +3,19 @@ final class LegalpadDocumentSignatureVerificationController extends LegalpadController { - private $code; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->code = $data['code']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $code = $request->getURIData('code'); // NOTE: We're using the omnipotent user to handle logged-out signatures // and corporate signatures. $signature = id(new LegalpadDocumentSignatureQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withSecretKeys(array($this->code)) + ->withSecretKeys(array($code)) ->executeOne(); if (!$signature) { diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureViewController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureViewController.php index 6d58d1b693..6f2c447b71 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureViewController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureViewController.php @@ -2,19 +2,13 @@ final class LegalpadDocumentSignatureViewController extends LegalpadController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $signature = id(new LegalpadDocumentSignatureQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$signature) { return new Aphront404Response(); From 082ef68d855e11012885a4203a4e63adab2fd586 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 29 Jul 2015 17:03:13 -0700 Subject: [PATCH 039/102] Clean up Phame a little Summary: Remove redundent UI, use standard components, remove unused CSS. Test Plan: Use Phame Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13755 --- resources/celerity/map.php | 10 +- .../phame/controller/PhameController.php | 6 +- .../blog/PhameBlogEditController.php | 10 +- .../blog/PhameBlogListController.php | 13 +- .../blog/PhameBlogViewController.php | 17 +-- .../post/PhamePostEditController.php | 9 +- .../post/PhamePostListController.php | 13 +- .../post/PhamePostNewController.php | 12 +- .../phame/query/PhamePostSearchEngine.php | 2 +- src/applications/phame/view/PhamePostView.php | 1 + webroot/rsrc/css/application/phame/phame.css | 114 ------------------ webroot/rsrc/css/phui/phui-feed-story.css | 3 +- 12 files changed, 39 insertions(+), 171 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bb24a63231..0c82d9dc92 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'af140e11', + 'core.pkg.css' => '11d41499', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '9451634c', @@ -83,7 +83,7 @@ return array( 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => '1898e534', 'rsrc/css/application/people/people-profile.css' => '25970776', - 'rsrc/css/application/phame/phame.css' => '3259b53d', + 'rsrc/css/application/phame/phame.css' => 'bb147387', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => '95174bdd', @@ -131,7 +131,7 @@ return array( 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => 'd842f867', 'rsrc/css/phui/phui-document.css' => '0267054b', - 'rsrc/css/phui/phui-feed-story.css' => 'c7d8113a', + 'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23', 'rsrc/css/phui/phui-fontkit.css' => 'cb8ae7ad', 'rsrc/css/phui/phui-form-view.css' => '621b21c5', 'rsrc/css/phui/phui-form.css' => 'afdb2c6e', @@ -765,7 +765,7 @@ return array( 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-zindex-css' => '57ddcaa2', - 'phame-css' => '3259b53d', + 'phame-css' => 'bb147387', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', @@ -784,7 +784,7 @@ return array( 'phui-calendar-month-css' => '476be7e0', 'phui-crumbs-view-css' => 'd842f867', 'phui-document-view-css' => '0267054b', - 'phui-feed-story-css' => 'c7d8113a', + 'phui-feed-story-css' => 'b7b26d23', 'phui-font-icon-base-css' => '3dad2ae3', 'phui-fontkit-css' => 'cb8ae7ad', 'phui-form-css' => 'afdb2c6e', diff --git a/src/applications/phame/controller/PhameController.php b/src/applications/phame/controller/PhameController.php index adf72c0e7d..fba717e8d9 100644 --- a/src/applications/phame/controller/PhameController.php +++ b/src/applications/phame/controller/PhameController.php @@ -9,14 +9,10 @@ abstract class PhameController extends PhabricatorController { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI($base_uri); - $nav->addLabel(pht('Create')); - $nav->addFilter('post/new', pht('New Post')); - $nav->addFilter('blog/new', pht('New Blog')); - $nav->addLabel(pht('Posts')); + $nav->addFilter('post/all', pht('Latest Posts')); $nav->addFilter('post/draft', pht('My Drafts')); $nav->addFilter('post', pht('My Posts')); - $nav->addFilter('post/all', pht('All Posts')); $nav->addLabel(pht('Blogs')); $nav->addFilter('blog/user', pht('Joinable Blogs')); diff --git a/src/applications/phame/controller/blog/PhameBlogEditController.php b/src/applications/phame/controller/blog/PhameBlogEditController.php index 3b3d6bd92f..19862fbfbe 100644 --- a/src/applications/phame/controller/blog/PhameBlogEditController.php +++ b/src/applications/phame/controller/blog/PhameBlogEditController.php @@ -192,18 +192,14 @@ final class PhameBlogEditController ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Blogs'), $this->getApplicationURI('blog/')); $crumbs->addTextCrumb($page_title, $this->getApplicationURI('blog/new')); - $nav = $this->renderSideNavFilterView(); - $nav->selectFilter($id ? null : 'blog/new'); - $nav->appendChild( + return $this->buildApplicationPage( array( $crumbs, $form_box, - )); - - return $this->buildApplicationPage( - $nav, + ), array( 'title' => $page_title, )); diff --git a/src/applications/phame/controller/blog/PhameBlogListController.php b/src/applications/phame/controller/blog/PhameBlogListController.php index 16486b95da..6240c129dd 100644 --- a/src/applications/phame/controller/blog/PhameBlogListController.php +++ b/src/applications/phame/controller/blog/PhameBlogListController.php @@ -60,18 +60,23 @@ final class PhameBlogListController extends PhameController { private function renderBlogList( array $blogs, - PhabricatorUser $user, + PhabricatorUser $viewer, $nodata) { $view = new PHUIObjectItemListView(); $view->setNoDataString($nodata); - $view->setUser($user); + $view->setUser($viewer); foreach ($blogs as $blog) { + $id = $blog->getID(); $item = id(new PHUIObjectItemView()) + ->setUser($viewer) + ->setObject($blog) ->setHeader($blog->getName()) - ->setHref($this->getApplicationURI('blog/view/'.$blog->getID().'/')) - ->setObject($blog); + ->setStatusIcon('fa-star') + ->setHref($this->getApplicationURI("/blog/view/{$id}/")) + ->addAttribute($blog->getSkin()) + ->addAttribute($blog->getDomain()); $view->addItem($item); } diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php index 2196281c7d..c8eddfbad3 100644 --- a/src/applications/phame/controller/blog/PhameBlogViewController.php +++ b/src/applications/phame/controller/blog/PhameBlogViewController.php @@ -22,8 +22,6 @@ final class PhameBlogViewController extends PhameController { ->withBlogPHIDs(array($blog->getPHID())) ->executeWithCursorPager($pager); - $nav = $this->renderSideNavFilterView(null); - $header = id(new PHUIHeaderView()) ->setHeader($blog->getName()) ->setUser($user) @@ -36,29 +34,24 @@ final class PhameBlogViewController extends PhameController { $user, pht('This blog has no visible posts.')); - require_celerity_resource('phame-css'); - $post_list = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_LARGE) - ->addClass('phame-post-list') + $post_list = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Latest Posts')) ->appendChild($post_list); - $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Blogs'), $this->getApplicationURI('blog/')); $crumbs->addTextCrumb($blog->getName(), $this->getApplicationURI()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); - $nav->appendChild( + return $this->buildApplicationPage( array( $crumbs, $object_box, $post_list, - )); - - return $this->buildApplicationPage( - $nav, + ), array( 'title' => $blog->getName(), )); diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php index 2b3324f805..e79d435deb 100644 --- a/src/applications/phame/controller/post/PhamePostEditController.php +++ b/src/applications/phame/controller/post/PhamePostEditController.php @@ -169,7 +169,6 @@ final class PhamePostEditController extends PhameController { phutil_tag('div', array('id' => 'post-preview'), $loading), )); - require_celerity_resource('phame-css'); Javelin::initBehavior( 'phame-post-preview', array( @@ -190,16 +189,12 @@ final class PhamePostEditController extends PhameController { $page_title, $this->getApplicationURI('/post/view/'.$id.'/')); - $nav = $this->renderSideNavFilterView(null); - $nav->appendChild( + return $this->buildApplicationPage( array( $crumbs, $form_box, $preview_panel, - )); - - return $this->buildApplicationPage( - $nav, + ), array( 'title' => $page_title, )); diff --git a/src/applications/phame/controller/post/PhamePostListController.php b/src/applications/phame/controller/post/PhamePostListController.php index 6a02129bd8..b2b28bd4eb 100644 --- a/src/applications/phame/controller/post/PhamePostListController.php +++ b/src/applications/phame/controller/post/PhamePostListController.php @@ -21,12 +21,6 @@ final class PhamePostListController extends PhameController { $title = pht('Unpublished Drafts'); $nav->selectFilter('post/draft'); break; - case 'all': - $nodata = pht('There are no visible posts.'); - $title = pht('Posts'); - $nav->selectFilter('post/all'); - break; - default: case 'blogger': if ($bloggername) { $blogger = id(new PhabricatorUser())->loadOneWhere( @@ -48,6 +42,12 @@ final class PhamePostListController extends PhameController { } $title = pht('Posts by %s', $blogger); break; + default: + case 'all': + $nodata = pht('There are no visible posts.'); + $title = pht('Posts'); + $nav->selectFilter('post/all'); + break; } $pager = id(new AphrontCursorPagerView()) @@ -55,7 +55,6 @@ final class PhamePostListController extends PhameController { $posts = $query->executeWithCursorPager($pager); - require_celerity_resource('phame-css'); $post_list = $this->renderPostList($posts, $viewer, $nodata); $post_list = id(new PHUIObjectBoxView()) ->setHeaderText($title) diff --git a/src/applications/phame/controller/post/PhamePostNewController.php b/src/applications/phame/controller/post/PhamePostNewController.php index 94d8651f27..33330ef271 100644 --- a/src/applications/phame/controller/post/PhamePostNewController.php +++ b/src/applications/phame/controller/post/PhamePostNewController.php @@ -56,12 +56,10 @@ final class PhamePostNewController extends PhameController { )) ->execute(); - $nav = $this->renderSideNavFilterView(); - $nav->selectFilter('post/new'); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $view_uri); - $nav->appendChild($crumbs); + $display = array(); + $display[] = $crumbs; if (!$blogs) { $notification = id(new PHUIInfoView()) @@ -70,7 +68,7 @@ final class PhamePostNewController extends PhameController { pht('You do not have permission to join any blogs. Create a blog '. 'first, then you can post to it.')); - $nav->appendChild($notification); + $display[] = $notification; } else { $options = mpull($blogs, 'getName', 'getID'); asort($options); @@ -109,11 +107,11 @@ final class PhamePostNewController extends PhameController { ->setHeaderText($title) ->setForm($form); - $nav->appendChild($form_box); + $display[] = $form_box; } return $this->buildApplicationPage( - $nav, + $display, array( 'title' => $title, )); diff --git a/src/applications/phame/query/PhamePostSearchEngine.php b/src/applications/phame/query/PhamePostSearchEngine.php index 600d633627..a5feb3d634 100644 --- a/src/applications/phame/query/PhamePostSearchEngine.php +++ b/src/applications/phame/query/PhamePostSearchEngine.php @@ -102,7 +102,7 @@ final class PhamePostSearchEngine $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); - $result->setNoDataString(pht('No blogs found.')); + $result->setNoDataString(pht('No blogs posts found.')); return $result; } diff --git a/src/applications/phame/view/PhamePostView.php b/src/applications/phame/view/PhamePostView.php index 236ecc2b39..f35125f255 100644 --- a/src/applications/phame/view/PhamePostView.php +++ b/src/applications/phame/view/PhamePostView.php @@ -173,6 +173,7 @@ final class PhamePostView extends AphrontView { $uri = $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle()); + require_celerity_resource('phame-css'); $fb_comments = phutil_tag('div', array( 'class' => 'fb-comments', diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 2569711095..095602b22b 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -2,120 +2,6 @@ * @provides phame-css */ -.notice { - background: #F3F3FF; - border: 1px solid #008; - margin: 16px 16px 4px 26px; -} -.notice h3 { - background: #E3E3FF; - padding: 8px; -} - -.notice h4 { - font-weight: normal; - padding: 8px; -} - -.phame-post-preview-header { - margin: 0px 0px 16px 0px; -} - -.device-desktop .phame-post-list { - max-width: 600px; -} - -.phame-post-list .phui-info-view { - margin: 0; -} - -.phame-post-list .phui-icon-view:hover { - text-decoration: none; -} - -.blog-post-list { - clear: left; - float: left; - width: 70%; - margin: 16px 0px 16px 16px; - padding: 0px 8px 12px 8px; -} - -.device .blog-post-list { - float: none; - width: 90%; - margin: 16px auto; -} - -.blog-post-list-full { - clear: left; - float: left; - margin: 16px 0px 0px 0px; - padding: 0px 16px 0px 16px; -} - -.device .blog-post-list-full { - float: none; - margin: 16px auto; -} - -.blog-detail { - float: right; - clear: right; - width: 20%; - margin: 16px 16px 16px 0px; -} - -.device .blog-detail { - float: none; - margin: 16px auto; - width: 90%; -} - -.blog-detail .description { - margin: 16px 0px 16px 0px; -} - -.blog-detail .bloggers { - font-size: {$smallerfontsize}; -} - -.blog-post, -.blog-detail { - border: 1px solid #DBDBDB; - background: #F9F9F9; - padding: 20px; -} - -.blog-post { - margin: 0px 0px 20px 0px; -} - -.blog-post .header { - padding: 0px 0px 16px 0px; -} - -.blog-post .header h1 { - clear: none; -} - -.blog-post .header .last-updated { - color: {$greytext}; - clear: none; - font-size: {$smallerfontsize}; -} - -.blog-post .header .buttons { - float: right; -} -.blog-post .header .buttons a { - margin: 0px 0px 0px 12px; -} - -.more-and-comments { - padding: 12px 0px 12px 0px; -} - .fb-comments, .fb-comments span, .fb-comments iframe[style] { diff --git a/webroot/rsrc/css/phui/phui-feed-story.css b/webroot/rsrc/css/phui/phui-feed-story.css index 4bc505d0c1..9162d68328 100644 --- a/webroot/rsrc/css/phui/phui-feed-story.css +++ b/webroot/rsrc/css/phui/phui-feed-story.css @@ -61,8 +61,7 @@ } .phui-feed-story-bigtext-post h3 { - font-size: 18px; - font-weight: 200; + font-size: {$biggestfontsize}; line-height: 18px; color: {$darkgreytext}; margin: 0 0 5px 0; From 38259f0cc26f3b7d86b368300ec49c3ead4fa73d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 29 Jul 2015 17:05:46 -0700 Subject: [PATCH 040/102] Don't reset message if missing Room Title in New Conpherence Room Summary: Fixes T9000, sets a variable. Test Plan: Try to make a new room without a title, but with a message. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T9000 Differential Revision: https://secure.phabricator.com/D13754 --- .../controller/ConpherenceNewRoomController.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php index 5578741e89..40dd6b2572 100644 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ b/src/applications/conpherence/controller/ConpherenceNewRoomController.php @@ -7,6 +7,7 @@ final class ConpherenceNewRoomController extends ConpherenceController { $title = pht('New Room'); $e_title = true; + $v_message = null; $validation_exception = null; $conpherence = ConpherenceThread::initializeNewRoom($user); @@ -35,12 +36,12 @@ final class ConpherenceNewRoomController extends ConpherenceController { ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) ->setNewValue($request->getStr('joinPolicy')); - $message = $request->getStr('message'); - if ($message) { + $v_message = $request->getStr('message'); + if (strlen($v_message)) { $message_xactions = $editor->generateTransactionsFromText( $user, $conpherence, - $message); + $v_message); $xactions = array_merge($xactions, $message_xactions); } @@ -121,7 +122,8 @@ final class ConpherenceNewRoomController extends ConpherenceController { id(new PhabricatorRemarkupControl()) ->setUser($user) ->setName('message') - ->setLabel(pht('First Message'))); + ->setLabel(pht('First Message')) + ->setValue($v_message)); $dialog->appendChild($form); From 360eb852496d6e06aece67d5e3e1a20885d0b795 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 30 Jul 2015 11:04:51 -0700 Subject: [PATCH 041/102] Update to FontAwesome 4.4.0 Summary: New Calendar icons! Test Plan: Try new icons. See new icons. Reviewers: lpriestley, epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13761 --- resources/celerity/map.php | 14 +- src/view/phui/PHUIIconView.php | 85 ++++++- webroot/rsrc/css/font/font-awesome.css | 222 +++++++++++++++++- .../font/fontawesome/fontawesome-webfont.eot | Bin 60767 -> 68875 bytes .../font/fontawesome/fontawesome-webfont.ttf | Bin 122092 -> 138204 bytes .../font/fontawesome/fontawesome-webfont.woff | Bin 71508 -> 81284 bytes .../fontawesome/fontawesome-webfont.woff2 | Bin 56780 -> 64464 bytes 7 files changed, 307 insertions(+), 14 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0c82d9dc92..3970a41d57 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '11d41499', + 'core.pkg.css' => '2bb3ba73', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '9451634c', @@ -112,7 +112,7 @@ return array( 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/diviner/diviner-shared.css' => '5a337049', - 'rsrc/css/font/font-awesome.css' => 'e2e712fe', + 'rsrc/css/font/font-awesome.css' => 'd2fc4e8d', 'rsrc/css/font/font-lato.css' => '5ab1a46a', 'rsrc/css/font/font-roboto-slab.css' => 'f24a53cb', 'rsrc/css/font/phui-font-icon-base.css' => '3dad2ae3', @@ -159,10 +159,10 @@ return array( 'rsrc/css/sprite-menu.css' => '9dd65b92', 'rsrc/css/sprite-projects.css' => 'e5ad842a', 'rsrc/css/sprite-tokens.css' => '4f399012', - 'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '5fb6fb0e', - 'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => 'a653cb11', - 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => '80526fc8', - 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => '4924d54d', + 'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '7d5a4653', + 'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '531835e8', + 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => '427fe363', + 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => 'a9897054', 'rsrc/externals/font/lato/lato-bold.eot' => '99fbcf8c', 'rsrc/externals/font/lato/lato-bold.ttf' => '0a7141f7', 'rsrc/externals/font/lato/lato-bold.woff' => 'f5db2061', @@ -532,7 +532,7 @@ return array( 'diffusion-readme-css' => '2106ea08', 'diffusion-source-css' => '66fdf661', 'diviner-shared-css' => '5a337049', - 'font-fontawesome' => 'e2e712fe', + 'font-fontawesome' => 'd2fc4e8d', 'font-lato' => '5ab1a46a', 'font-roboto-slab' => 'f24a53cb', 'global-drag-and-drop-css' => '697324ad', diff --git a/src/view/phui/PHUIIconView.php b/src/view/phui/PHUIIconView.php index 0cb7be8db5..08e27b0753 100644 --- a/src/view/phui/PHUIIconView.php +++ b/src/view/phui/PHUIIconView.php @@ -65,11 +65,9 @@ final class PHUIIconView extends AphrontTagView { protected function getTagAttributes() { require_celerity_resource('phui-icon-view-css'); - $style = null; $classes = array(); $classes[] = 'phui-icon-view'; - if ($this->spriteIcon) { require_celerity_resource('sprite-'.$this->spriteSheet.'-css'); $classes[] = 'sprite-'.$this->spriteSheet; @@ -88,7 +86,6 @@ final class PHUIIconView extends AphrontTagView { } $style = 'background-image: url('.$this->image.');'; } - if ($this->text) { $classes[] = 'phui-icon-has-text'; $this->appendChild($this->text); @@ -647,6 +644,88 @@ final class PHUIIconView extends AphrontTagView { 'fa-train', 'fa-subway', 'fa-medium', + 'fa-git', + 'fa-y-combinator-square', + 'fa-yc-square', + 'fa-hacker-news', + 'fa-yc', + 'fa-y-combinator', + 'fa-optin-monster', + 'fa-opencart', + 'fa-expeditedssl', + 'fa-battery-4', + 'fa-battery-full', + 'fa-battery-3', + 'fa-battery-three-quarters', + 'fa-battery-2', + 'fa-battery-half', + 'fa-battery-1', + 'fa-battery-quarter', + 'fa-battery-0', + 'fa-battery-empty', + 'fa-mouse-pointer', + 'fa-i-cursor', + 'fa-object-group', + 'fa-object-ungroup', + 'fa-sticky-note', + 'fa-sticky-note-o', + 'fa-cc-jcb', + 'fa-cc-diners-club', + 'fa-clone', + 'fa-balance-scale', + 'fa-hourglass-o', + 'fa-hourglass-1', + 'fa-hourglass-start', + 'fa-hourglass-2', + 'fa-hourglass-half', + 'fa-hourglass-3', + 'fa-hourglass-end', + 'fa-hourglass', + 'fa-hand-grab-o', + 'fa-hand-rock-o', + 'fa-hand-stop-o', + 'fa-hand-paper-o', + 'fa-hand-scissors-o', + 'fa-hand-lizard-o', + 'fa-hand-spock-o', + 'fa-hand-pointer-o', + 'fa-hand-peace-o', + 'fa-trademark', + 'fa-registered', + 'fa-creative-commons', + 'fa-gg', + 'fa-gg-circle', + 'fa-tripadvisor', + 'fa-odnoklassniki', + 'fa-odnoklassniki-square', + 'fa-get-pocket', + 'fa-wikipedia-w', + 'fa-safari', + 'fa-chrome', + 'fa-firefox', + 'fa-opera', + 'fa-internet-explorer', + 'fa-tv', + 'fa-television', + 'fa-contao', + 'fa-500px', + 'fa-amazon', + 'fa-calendar-plus-o', + 'fa-calendar-minus-o', + 'fa-calendar-times-o', + 'fa-calendar-check-o', + 'fa-industry', + 'fa-map-pin', + 'fa-map-signs', + 'fa-map-o', + 'fa-map', + 'fa-commenting', + 'fa-commenting-o', + 'fa-houzz', + 'fa-vimeo', + 'fa-black-tie', + 'fa-fonticons', + ); } diff --git a/webroot/rsrc/css/font/font-awesome.css b/webroot/rsrc/css/font/font-awesome.css index 7766362550..1658708f4e 100644 --- a/webroot/rsrc/css/font/font-awesome.css +++ b/webroot/rsrc/css/font/font-awesome.css @@ -3,15 +3,15 @@ */ /*! - * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; - src: url('/rsrc/externals/font/fontawesome/fontawesome-webfont.eot?v=4.3.0'); - src: url('/rsrc/externals/font/fontawesome/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'), url('/rsrc/externals/font/fontawesome/fontawesome-webfont.woff2?v=4.3.0') format('woff2'), url('/rsrc/externals/font/fontawesome/fontawesome-webfont.woff?v=4.3.0') format('woff'), url('/rsrc/externals/font/fontawesome/fontawesome-webfont.ttf?v=4.3.0') format('truetype'); + src: url('/rsrc/externals/font/fontawesome/fontawesome-webfont.eot?v=4.4.0'); + src: url('/rsrc/externals/font/fontawesome/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'), url('/rsrc/externals/font/fontawesome/fontawesome-webfont.woff2?v=4.4.0') format('woff2'), url('/rsrc/externals/font/fontawesome/fontawesome-webfont.woff?v=4.4.0') format('woff'), url('/rsrc/externals/font/fontawesome/fontawesome-webfont.ttf?v=4.4.0') format('truetype'); font-weight: normal; font-style: normal; } @@ -40,6 +40,7 @@ width: 1.28571429em; text-align: center; } + /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ .fa-glass:before { @@ -502,6 +503,7 @@ .fa-credit-card:before { content: "\f09d"; } +.fa-feed:before, .fa-rss:before { content: "\f09e"; } @@ -1383,6 +1385,8 @@ .fa-git:before { content: "\f1d3"; } +.fa-y-combinator-square:before, +.fa-yc-square:before, .fa-hacker-news:before { content: "\f1d4"; } @@ -1407,7 +1411,6 @@ .fa-history:before { content: "\f1da"; } -.fa-genderless:before, .fa-circle-thin:before { content: "\f1db"; } @@ -1612,6 +1615,7 @@ .fa-mercury:before { content: "\f223"; } +.fa-intersex:before, .fa-transgender:before { content: "\f224"; } @@ -1639,6 +1643,9 @@ .fa-neuter:before { content: "\f22c"; } +.fa-genderless:before { + content: "\f22d"; +} .fa-facebook-official:before { content: "\f230"; } @@ -1673,3 +1680,210 @@ .fa-medium:before { content: "\f23a"; } +.fa-yc:before, +.fa-y-combinator:before { + content: "\f23b"; +} +.fa-optin-monster:before { + content: "\f23c"; +} +.fa-opencart:before { + content: "\f23d"; +} +.fa-expeditedssl:before { + content: "\f23e"; +} +.fa-battery-4:before, +.fa-battery-full:before { + content: "\f240"; +} +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "\f241"; +} +.fa-battery-2:before, +.fa-battery-half:before { + content: "\f242"; +} +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "\f243"; +} +.fa-battery-0:before, +.fa-battery-empty:before { + content: "\f244"; +} +.fa-mouse-pointer:before { + content: "\f245"; +} +.fa-i-cursor:before { + content: "\f246"; +} +.fa-object-group:before { + content: "\f247"; +} +.fa-object-ungroup:before { + content: "\f248"; +} +.fa-sticky-note:before { + content: "\f249"; +} +.fa-sticky-note-o:before { + content: "\f24a"; +} +.fa-cc-jcb:before { + content: "\f24b"; +} +.fa-cc-diners-club:before { + content: "\f24c"; +} +.fa-clone:before { + content: "\f24d"; +} +.fa-balance-scale:before { + content: "\f24e"; +} +.fa-hourglass-o:before { + content: "\f250"; +} +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "\f251"; +} +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "\f252"; +} +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "\f253"; +} +.fa-hourglass:before { + content: "\f254"; +} +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "\f255"; +} +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: "\f256"; +} +.fa-hand-scissors-o:before { + content: "\f257"; +} +.fa-hand-lizard-o:before { + content: "\f258"; +} +.fa-hand-spock-o:before { + content: "\f259"; +} +.fa-hand-pointer-o:before { + content: "\f25a"; +} +.fa-hand-peace-o:before { + content: "\f25b"; +} +.fa-trademark:before { + content: "\f25c"; +} +.fa-registered:before { + content: "\f25d"; +} +.fa-creative-commons:before { + content: "\f25e"; +} +.fa-gg:before { + content: "\f260"; +} +.fa-gg-circle:before { + content: "\f261"; +} +.fa-tripadvisor:before { + content: "\f262"; +} +.fa-odnoklassniki:before { + content: "\f263"; +} +.fa-odnoklassniki-square:before { + content: "\f264"; +} +.fa-get-pocket:before { + content: "\f265"; +} +.fa-wikipedia-w:before { + content: "\f266"; +} +.fa-safari:before { + content: "\f267"; +} +.fa-chrome:before { + content: "\f268"; +} +.fa-firefox:before { + content: "\f269"; +} +.fa-opera:before { + content: "\f26a"; +} +.fa-internet-explorer:before { + content: "\f26b"; +} +.fa-tv:before, +.fa-television:before { + content: "\f26c"; +} +.fa-contao:before { + content: "\f26d"; +} +.fa-500px:before { + content: "\f26e"; +} +.fa-amazon:before { + content: "\f270"; +} +.fa-calendar-plus-o:before { + content: "\f271"; +} +.fa-calendar-minus-o:before { + content: "\f272"; +} +.fa-calendar-times-o:before { + content: "\f273"; +} +.fa-calendar-check-o:before { + content: "\f274"; +} +.fa-industry:before { + content: "\f275"; +} +.fa-map-pin:before { + content: "\f276"; +} +.fa-map-signs:before { + content: "\f277"; +} +.fa-map-o:before { + content: "\f278"; +} +.fa-map:before { + content: "\f279"; +} +.fa-commenting:before { + content: "\f27a"; +} +.fa-commenting-o:before { + content: "\f27b"; +} +.fa-houzz:before { + content: "\f27c"; +} +.fa-vimeo:before { + content: "\f27d"; +} +.fa-black-tie:before { + content: "\f27e"; +} +.fa-fonticons:before { + content: "\f280"; +} diff --git a/webroot/rsrc/externals/font/fontawesome/fontawesome-webfont.eot b/webroot/rsrc/externals/font/fontawesome/fontawesome-webfont.eot index 33b2bb80055cc480e797de704925acaba4ba7d7d..a30335d748c65c0bab5880b4e6dba53f5c79206c 100644 GIT binary patch literal 68875 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

7r8$?kb-D3z%-c} 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 F002TuuHFCu literal 60767 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%0DKehr}RFC`2XTB_?==0w^)u1m5PYii@6f)6_5Ydu zv+NIZ_(Rt}Q++LT5!n8!J4x!>sE&v_3*cXat{Zq5;17w;B6$epw}$Rg`0nFJg5D-L zYvw@(goc5TeJjM($AJAZxZHZN}RzBcP0=_>ZI6WVGU zO#Nk-YqZTa3{!84P0K~GsI#32<+_AsXU43wILwZS(8n%S9)lP!Dg$$e2$$9$E?^Nj zql4do#+a8qEP(bD2)DpP|$dp<`TZ#bY6^~7Xv_Lle)77^OsVhMOm(@ z??8O8kA%}ZWpR&2v!7qFSw@TF6d*=9YT^Rtk(n8p=CQWvt1Om=n&5uP;GiT6 zMRvbm39kbp*KB`qoVg12w52Z)T}`X41P>D|q_%K#zuhwb+BpEogY0E)KnSy#@+(m5 z20@LG@LUEvk`I|OIUV^^0_YtG9AElBS!Dsh%k^P9r0moJ25Lkm-gh#igwBDhAOj0!EF&8MxV^-m1U1MEd?H7} zL;r;tfFIT|ei3-Z@gyM=!%Ba7Pa626JRAA`V<2D<{RLRT@0o=bE)XF)nFtUL67`2L z{?_Qz_`Yy2t+I)?9&z#z__Q%L3pnhN}U z_rN#WU)kD59D4whbSYERHY01jM7id50EuI1ctl?<_IT=Y5vP>(sNNkB&U5&F&^kBhm5y{o!y!F+4wdxXoy;!4$W`?_nL(+bK_QDAMUV1O0AwZ| z6j)s}9YEZbY-C^Y)9Ej`aS&~{sXCG2SS3ce$EY;Yv-c8TlrD$C85ATlLZpGP_YWfi z`RQ?z1@zIfa{yqfsUDMEPpwuX%XHdO+ASb3EPi1fBPocvfgsC0xa^CG2SWBPWQ&GS zpCXPti8b>WkYbf#Vg%A?&_UwUsUQE_t4GX?7QqUpKJ2Iw#%)Q4Ft(`9Ja&Yk{C@38 z@%T`)#wWy(kKfEH;ZBQ(m*Iq&L=<)4D7tNO{SsA4Fp4D?(Ex6nQS&f3TK|atgj`fE z2|OX0(&(ZqxJd~IANX&dvX?U14_<~h2(lP6k^H8ep;2HW6oPo?U%v{M>|{sU~;p zLTv$OTx3H^4zNUn4wUfo>j{CEvTC@C+cw+cW*ABH6u@!M2EdBL?1GbL_#e;7YDBas zic?MTazk(khXSyPeDom_I~wkLv?Wr8<%egEfM!*M9^kl$>zsVzaP}S!gcD3;Czy#58RTm?`p)RTS8I<-sC3+*n{A)P*rU!@Npj`e{x9xsif2v zTW`{q3p^?A!Mk60Q{(FLt(&TVe9z z0-!PiOV02JcNeq?AbJaI+B9xC;LB=}Ho0vH(@;Qe0zq~-8ckOa!(u@Wou`p_TR|QT z38H`lJE$G{q1egUX@&v$x7wNLWD#j*!D58GLv^bT+jpdKBrK#SsQsWK(+RO40VA^w z0nA7MN1Y1Fc#5JkwD5TtHG1t;lo=i)U+kFG?1Jh11h9382!marrRE2eZh;JGh`wNO zQA_~n?%97HOKLA^#oG(5*bgSllS%rOc(S%Yj00cYR;!D9G_90{pfq7D4I*$k?byOV zR|epi%oIJ{ou`5zS!-_dnxOa{uNv)(luMo^5TCOItq}2}sxCztLEzBGS)Mf6dzaw< z!GweAgvFYJu&mH(Vl9HJBV%=Jz~~i%nDGIF9ncTET-AQ=fv{L11&K_;ei!iht(!De;ym|y7ksL|^5Ko~B-vSh80++s?unD}bZaYa@ zPH4M$&fw;xEGN3_H1vHW><%-+dg7dfW)F8$bB+h7sThoOtteO(v{&-+iK}r$%G))# z*Nhx^!ZMj1VeG?EkWg+0CYQSX1t96fV9^3c+9C393LU&CHsFCa1q99$`zTMsEWwLc zxsw1|A?k8-m8HCrk6;K7dhNDJN3R9iws%6vTq_}PtR2CZ8TG;ltZ4I}sU+^s8`P3F5QxrypG1-{ zGlr^7$Wsy(lo=xfC~BpKfg<2z4OEeEF@~x{Pi7O#CvqMJy+f+}=CB_$&IuEslB@s# J000000038FvZ??8 diff --git a/webroot/rsrc/externals/font/fontawesome/fontawesome-webfont.ttf b/webroot/rsrc/externals/font/fontawesome/fontawesome-webfont.ttf index ed9372f8ea0fbaa04f42630a48887e4b38945345..d7994e13086b1ac1a216bd754c93e1bccd65f237 100644 GIT binary patch delta 19019 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 a{Y8oiTrq(w))2tr5G>B&2kvjAsQ&|VPX)FB delta 3717 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 b%7NbA42BxTcoSrlSuMv%Lh(uG6hi(FwBy){ diff --git a/webroot/rsrc/externals/font/fontawesome/fontawesome-webfont.woff b/webroot/rsrc/externals/font/fontawesome/fontawesome-webfont.woff index 8b280b98fa2fa261aa4b0f8fd061f772073ef83e..6fd4ede0f30f170eecb4156beb7235bf01fff00b 100644 GIT binary patch delta 61681 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 kgu|{=z_EPdwI%aS;AX3)Hq)$ delta 51819 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 diff --git a/webroot/rsrc/externals/font/fontawesome/fontawesome-webfont.woff2 b/webroot/rsrc/externals/font/fontawesome/fontawesome-webfont.woff2 index 3311d585145b1cc1b9581e914acbb32d8542b4f5..5560193ccc5d768df40766ba54491f1822ed683c 100644 GIT binary patch literal 64464 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 From d902dd6589658dd565092fd1851dde08801d0711 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 31 Jul 2015 09:14:33 -0700 Subject: [PATCH 042/102] Update Nuance for handleRequest Summary: Updates Nuance controllers Test Plan: Read carefully, lint. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13762 --- .../controller/NuanceItemEditController.php | 29 ++++--------------- .../controller/NuanceItemViewController.php | 25 ++++------------ .../NuanceRequestorEditController.php | 29 ++++--------------- .../NuanceRequestorViewController.php | 25 ++++------------ 4 files changed, 22 insertions(+), 86 deletions(-) diff --git a/src/applications/nuance/controller/NuanceItemEditController.php b/src/applications/nuance/controller/NuanceItemEditController.php index b5e02cbda6..89bfbda5c9 100644 --- a/src/applications/nuance/controller/NuanceItemEditController.php +++ b/src/applications/nuance/controller/NuanceItemEditController.php @@ -2,33 +2,16 @@ final class NuanceItemEditController extends NuanceController { - private $itemID; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function setItemID($item_id) { - $this->itemID = $item_id; - return $this; - } - public function getItemID() { - return $this->itemID; - } - - public function willProcessRequest(array $data) { - $this->setItemID(idx($data, 'id')); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $item_id = $this->getItemID(); - $is_new = !$item_id; - - if ($is_new) { + if (!$id) { $item = new NuanceItem(); } else { $item = id(new NuanceItemQuery()) - ->setViewer($user) - ->withIDs(array($item_id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); } diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php index e0ba457e89..9c7401cafe 100644 --- a/src/applications/nuance/controller/NuanceItemViewController.php +++ b/src/applications/nuance/controller/NuanceItemViewController.php @@ -2,28 +2,13 @@ final class NuanceItemViewController extends NuanceController { - private $itemID; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function setItemID($item_id) { - $this->itemID = $item_id; - return $this; - } - public function getItemID() { - return $this->itemID; - } - - public function willProcessRequest(array $data) { - $this->setItemID($data['id']); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $item_id = $this->getItemID(); $item = id(new NuanceItemQuery()) - ->setViewer($user) - ->withIDs(array($item_id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$item) { diff --git a/src/applications/nuance/controller/NuanceRequestorEditController.php b/src/applications/nuance/controller/NuanceRequestorEditController.php index 42405a227f..2efc49f711 100644 --- a/src/applications/nuance/controller/NuanceRequestorEditController.php +++ b/src/applications/nuance/controller/NuanceRequestorEditController.php @@ -2,34 +2,17 @@ final class NuanceRequestorEditController extends NuanceController { - private $requestorID; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function setRequestorID($requestor_id) { - $this->requestorID = $requestor_id; - return $this; - } - public function getRequestorID() { - return $this->requestorID; - } - - public function willProcessRequest(array $data) { - $this->setRequestorID(idx($data, 'id')); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $requestor_id = $this->getRequestorID(); - $is_new = !$requestor_id; - - if ($is_new) { + if (!$id) { $requestor = new NuanceRequestor(); } else { $requestor = id(new NuanceRequestorQuery()) - ->setViewer($user) - ->withIDs(array($requestor_id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); } diff --git a/src/applications/nuance/controller/NuanceRequestorViewController.php b/src/applications/nuance/controller/NuanceRequestorViewController.php index 08dcedb62a..8f6912c4d7 100644 --- a/src/applications/nuance/controller/NuanceRequestorViewController.php +++ b/src/applications/nuance/controller/NuanceRequestorViewController.php @@ -2,28 +2,13 @@ final class NuanceRequestorViewController extends NuanceController { - private $requestorID; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function setRequestorID($requestor_id) { - $this->requestorID = $requestor_id; - return $this; - } - public function getRequestorID() { - return $this->requestorID; - } - - public function willProcessRequest(array $data) { - $this->setRequestorID($data['id']); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $requestor_id = $this->getRequestorID(); $requestor = id(new NuanceRequestorQuery()) - ->setViewer($user) - ->withIDs(array($requestor_id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$requestor) { From deb20d6dae88eb95487a6398831b66a1fabed24b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 31 Jul 2015 09:14:44 -0700 Subject: [PATCH 043/102] Update Chatlog for handleRequest Summary: Updates Chatlog Test Plan: Use Chatlog Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13763 --- ...habricatorChatLogChannelListController.php | 7 ++--- ...PhabricatorChatLogChannelLogController.php | 28 ++++++++----------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php index a7c5c68153..cdff189633 100644 --- a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php +++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php @@ -7,12 +7,11 @@ final class PhabricatorChatLogChannelListController return true; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $channels = id(new PhabricatorChatLogChannelQuery()) - ->setViewer($user) + ->setViewer($viewer) ->execute(); $list = new PHUIObjectItemListView(); diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php index 4281adf421..8084356ced 100644 --- a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php +++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php @@ -3,19 +3,13 @@ final class PhabricatorChatLogChannelLogController extends PhabricatorChatLogController { - private $channelID; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->channelID = $data['channelID']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('channelID'); $uri = clone $request->getRequestURI(); $uri->setQueryParams(array()); @@ -25,12 +19,12 @@ final class PhabricatorChatLogChannelLogController $pager->setPageSize(250); $query = id(new PhabricatorChatLogQuery()) - ->setViewer($user) - ->withChannelIDs(array($this->channelID)); + ->setViewer($viewer) + ->withChannelIDs(array($id)); $channel = id(new PhabricatorChatLogChannelQuery()) - ->setViewer($user) - ->withIDs(array($this->channelID)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$channel) { @@ -115,7 +109,7 @@ final class PhabricatorChatLogChannelLogController $href = $uri->alter('at', $block['id']); $timestamp = $block['epoch']; - $timestamp = phabricator_datetime($timestamp, $user); + $timestamp = phabricator_datetime($timestamp, $viewer); $timestamp = phutil_tag( 'a', array( @@ -189,7 +183,7 @@ final class PhabricatorChatLogChannelLogController ->addTextCrumb($channel->getChannelName(), $uri); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setMethod('GET') ->setAction($uri) ->appendChild( @@ -273,7 +267,7 @@ final class PhabricatorChatLogChannelLogController AphrontRequest $request, PhabricatorChatLogQuery $query) { - $user = $request->getUser(); + $viewer = $request->getViewer(); $at_id = $request->getInt('at'); $at_date = $request->getStr('date'); @@ -298,7 +292,7 @@ final class PhabricatorChatLogChannelLogController ); } else if ($at_date) { - $timestamp = PhabricatorTime::parseLocalTime($at_date, $user); + $timestamp = PhabricatorTime::parseLocalTime($at_date, $viewer); if ($timestamp) { $context_logs = $query From e5bf2ac37389e084a8dc942c82ea82ba065e1074 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:39:29 -0700 Subject: [PATCH 044/102] Update Pholio for handleRequest Summary: Run through and update Pholio Test Plan: New Mock, Edit Mock, Inline Comment, Normal Comment, replace mock, view lists. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13774 --- .../PholioImageUploadController.php | 5 +- .../controller/PholioInlineController.php | 16 ++---- .../controller/PholioInlineListController.php | 20 +++---- .../PholioMockCommentController.php | 22 +++----- .../controller/PholioMockEditController.php | 44 +++++++--------- .../controller/PholioMockListController.php | 10 ++-- .../controller/PholioMockViewController.php | 52 ++++++++----------- 7 files changed, 67 insertions(+), 102 deletions(-) diff --git a/src/applications/pholio/controller/PholioImageUploadController.php b/src/applications/pholio/controller/PholioImageUploadController.php index 530563dc8f..0329d3eb1d 100644 --- a/src/applications/pholio/controller/PholioImageUploadController.php +++ b/src/applications/pholio/controller/PholioImageUploadController.php @@ -2,9 +2,8 @@ final class PholioImageUploadController extends PholioController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $phid = $request->getStr('filePHID'); $replaces_phid = $request->getStr('replacesPHID'); diff --git a/src/applications/pholio/controller/PholioInlineController.php b/src/applications/pholio/controller/PholioInlineController.php index 4cc5510b99..101ef9e758 100644 --- a/src/applications/pholio/controller/PholioInlineController.php +++ b/src/applications/pholio/controller/PholioInlineController.php @@ -2,18 +2,12 @@ final class PholioInlineController extends PholioController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if ($this->id) { - $inline = id(new PholioTransactionComment())->load($this->id); + if ($id) { + $inline = id(new PholioTransactionComment())->load($id); if (!$inline) { return new Aphront404Response(); diff --git a/src/applications/pholio/controller/PholioInlineListController.php b/src/applications/pholio/controller/PholioInlineListController.php index 0befd5dbd2..673a55bbaa 100644 --- a/src/applications/pholio/controller/PholioInlineListController.php +++ b/src/applications/pholio/controller/PholioInlineListController.php @@ -2,19 +2,13 @@ final class PholioInlineListController extends PholioController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $image = id(new PholioImageQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$image) { return new Aphront404Response(); @@ -23,8 +17,8 @@ final class PholioInlineListController extends PholioController { $inline_comments = id(new PholioTransactionComment())->loadAllWhere( 'imageid = %d AND (transactionphid IS NOT NULL OR (authorphid = %s AND transactionphid IS NULL))', - $this->id, - $user->getPHID()); + $id, + $viewer->getPHID()); $author_phids = mpull($inline_comments, 'getAuthorPHID'); $authors = $this->loadViewerHandles($author_phids); diff --git a/src/applications/pholio/controller/PholioMockCommentController.php b/src/applications/pholio/controller/PholioMockCommentController.php index 719d2db6ef..b127d0b1da 100644 --- a/src/applications/pholio/controller/PholioMockCommentController.php +++ b/src/applications/pholio/controller/PholioMockCommentController.php @@ -2,23 +2,17 @@ final class PholioMockCommentController extends PholioController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); if (!$request->isFormPost()) { return new Aphront400Response(); } $mock = id(new PholioMockQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needImages(true) ->executeOne(); @@ -38,7 +32,7 @@ final class PholioMockCommentController extends PholioController { $inline_comments = id(new PholioTransactionComment())->loadAllWhere( 'authorphid = %s AND transactionphid IS NULL AND imageid IN (%Ld)', - $user->getPHID(), + $viewer->getPHID(), mpull($mock->getImages(), 'getID')); if (!$inline_comments || strlen($comment)) { @@ -56,7 +50,7 @@ final class PholioMockCommentController extends PholioController { } $editor = id(new PholioMockEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect($request->isContinueRequest()) ->setIsPreview($is_preview); @@ -78,7 +72,7 @@ final class PholioMockCommentController extends PholioController { ->setMock($mock); return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) + ->setViewer($viewer) ->setTransactions($xactions) ->setTransactionView($xaction_view) ->setIsPreview($is_preview); diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index 728c7b29d6..18423e0050 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -2,26 +2,20 @@ final class PholioMockEditController extends PholioController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - if ($this->id) { + if ($id) { $mock = id(new PholioMockQuery()) - ->setViewer($user) + ->setViewer($viewer) ->needImages(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$mock) { @@ -35,7 +29,7 @@ final class PholioMockEditController extends PholioController { $files = mpull($mock_images, 'getFile'); $mock_images = mpull($mock_images, null, 'getFilePHID'); } else { - $mock = PholioMock::initializeNewMock($user); + $mock = PholioMock::initializeNewMock($viewer); $title = pht('Create Mock'); @@ -104,7 +98,7 @@ final class PholioMockEditController extends PholioController { $file_phids = $request->getArr('file_phids'); if ($file_phids) { $files = id(new PhabricatorFileQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); @@ -219,7 +213,7 @@ final class PholioMockEditController extends PholioController { $editor = id(new PholioMockEditor()) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) - ->setActor($user); + ->setActor($viewer); $xactions = $editor->applyTransactions($mock, $xactions); @@ -230,9 +224,9 @@ final class PholioMockEditController extends PholioController { } } - if ($this->id) { + if ($id) { $submit = id(new AphrontFormSubmitControl()) - ->addCancelButton('/M'.$this->id) + ->addCancelButton('/M'.$id) ->setValue(pht('Save')); } else { $submit = id(new AphrontFormSubmitControl()) @@ -241,7 +235,7 @@ final class PholioMockEditController extends PholioController { } $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setObject($mock) ->execute(); @@ -257,7 +251,7 @@ final class PholioMockEditController extends PholioController { } foreach ($display_mock_images as $mock_image) { $image_elements[] = id(new PholioUploadedImageView()) - ->setUser($user) + ->setUser($viewer) ->setImage($mock_image) ->setReplacesPHID($mock_image->getFilePHID()); } @@ -308,7 +302,7 @@ final class PholioMockEditController extends PholioController { require_celerity_resource('pholio-edit-css'); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild($order_control) ->appendChild( id(new AphrontFormTextControl()) @@ -321,9 +315,9 @@ final class PholioMockEditController extends PholioController { ->setName('description') ->setValue($v_desc) ->setLabel(pht('Description')) - ->setUser($user)); + ->setUser($viewer)); - if ($this->id) { + if ($id) { $form->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) @@ -346,11 +340,11 @@ final class PholioMockEditController extends PholioController { ->setLabel(pht('Subscribers')) ->setName('cc') ->setValue($v_cc) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) ->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($mock) ->setPolicies($policies) @@ -358,7 +352,7 @@ final class PholioMockEditController extends PholioController { ->setName('can_view')) ->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($mock) ->setPolicies($policies) diff --git a/src/applications/pholio/controller/PholioMockListController.php b/src/applications/pholio/controller/PholioMockListController.php index c4d7fb4117..b4fa17fc8a 100644 --- a/src/applications/pholio/controller/PholioMockListController.php +++ b/src/applications/pholio/controller/PholioMockListController.php @@ -2,19 +2,15 @@ final class PholioMockListController extends PholioController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PholioMockSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index b9084474d2..c5c6fed16c 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -2,8 +2,6 @@ final class PholioMockViewController extends PholioController { - private $id; - private $imageID; private $maniphestTaskPHIDs = array(); private function setManiphestTaskPHIDs($maniphest_task_phids) { @@ -18,18 +16,14 @@ final class PholioMockViewController extends PholioController { return true; } - public function willProcessRequest(array $data) { - $this->id = $data['id']; - $this->imageID = idx($data, 'imageID'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $image_id = $request->getURIData('imageID'); $mock = id(new PholioMockQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needImages(true) ->needInlineComments(true) ->executeOne(); @@ -44,7 +38,7 @@ final class PholioMockViewController extends PholioController { $this->setManiphestTaskPHIDs($phids); $engine = id(new PhabricatorMarkupEngine()) - ->setViewer($user); + ->setViewer($viewer); $engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION); $title = $mock->getName(); @@ -61,7 +55,7 @@ final class PholioMockViewController extends PholioController { $header = id(new PHUIHeaderView()) ->setHeader($title) - ->setUser($user) + ->setUser($viewer) ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($mock); @@ -81,9 +75,9 @@ final class PholioMockViewController extends PholioController { $mock_view = id(new PholioMockImagesView()) ->setRequestURI($request->getRequestURI()) ->setCommentFormID($comment_form_id) - ->setUser($user) + ->setUser($viewer) ->setMock($mock) - ->setImageID($this->imageID); + ->setImageID($image_id); $this->addExtraQuicksandConfig( array('mockViewConfig' => $mock_view->getBehaviorConfig())); @@ -101,7 +95,7 @@ final class PholioMockViewController extends PholioController { ->addPropertyList($properties); $thumb_grid = id(new PholioMockThumbGridView()) - ->setUser($user) + ->setUser($viewer) ->setMock($mock); $content = array( @@ -122,15 +116,15 @@ final class PholioMockViewController extends PholioController { } private function buildActionView(PholioMock $mock) { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $actions = id(new PhabricatorActionListView()) - ->setUser($user) + ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($mock); $can_edit = PhabricatorPolicyFilter::hasCapability( - $user, + $viewer, $mock, PhabricatorPolicyCapability::CAN_EDIT); @@ -147,7 +141,7 @@ final class PholioMockViewController extends PholioController { ->setIcon('fa-anchor') ->setName(pht('Edit Maniphest Tasks')) ->setHref("/search/attach/{$mock->getPHID()}/TASK/edge/") - ->setDisabled(!$user->isLoggedIn()) + ->setDisabled(!$viewer->isLoggedIn()) ->setWorkflow(true)); return $actions; @@ -158,25 +152,25 @@ final class PholioMockViewController extends PholioController { PhabricatorMarkupEngine $engine, PhabricatorActionListView $actions) { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($user) + ->setUser($viewer) ->setObject($mock) ->setActionList($actions); $properties->addProperty( pht('Author'), - $user->renderHandle($mock->getAuthorPHID())); + $viewer->renderHandle($mock->getAuthorPHID())); $properties->addProperty( pht('Created'), - phabricator_datetime($mock->getDateCreated(), $user)); + phabricator_datetime($mock->getDateCreated(), $viewer)); if ($this->getManiphestTaskPHIDs()) { $properties->addProperty( pht('Maniphest Tasks'), - $user->renderHandleList($this->getManiphestTaskPHIDs())); + $viewer->renderHandleList($this->getManiphestTaskPHIDs())); } $properties->invokeWillRenderEvent(); @@ -192,9 +186,9 @@ final class PholioMockViewController extends PholioController { } private function buildAddCommentView(PholioMock $mock, $comment_form_id) { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); - $draft = PhabricatorDraft::newFromUserAndKey($user, $mock->getPHID()); + $draft = PhabricatorDraft::newFromUserAndKey($viewer, $mock->getPHID()); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $title = $is_serious @@ -202,7 +196,7 @@ final class PholioMockViewController extends PholioController { : pht('History Beckons'); $form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setObjectPHID($mock->getPHID()) ->setFormID($comment_form_id) ->setDraft($draft) From deb06727eaf2eda2c63d11c2a4a1a6c617d726cd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:40:03 -0700 Subject: [PATCH 045/102] Update Help for handleRequest Summary: Updates Help for handleRequest Test Plan: Launch help dialog, close dialog Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13772 --- .../controller/PhabricatorHelpEditorProtocolController.php | 5 ++--- .../PhabricatorHelpKeyboardShortcutController.php | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php b/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php index 51dd4cfa0f..010e41ffcd 100644 --- a/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php +++ b/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php @@ -7,9 +7,8 @@ final class PhabricatorHelpEditorProtocolController return true; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $dialog = id(new AphrontDialogView()) ->setUser($viewer) diff --git a/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php b/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php index f91182859a..b136265d7f 100644 --- a/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php +++ b/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php @@ -7,9 +7,8 @@ final class PhabricatorHelpKeyboardShortcutController return true; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $keys = $request->getStr('keys'); try { @@ -59,7 +58,7 @@ final class PhabricatorHelpKeyboardShortcutController $rows); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Keyboard Shortcuts')) ->appendChild($table) ->addCancelButton('#', pht('Close')); From 9c39a9b361bd43083f04f8d944ebfc7a0ee05c84 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:40:26 -0700 Subject: [PATCH 046/102] Update UIExamples for handleRequest Summary: Updates UIExamples Test Plan: Visit application, click on different examples Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13769 --- .../PhabricatorUIExampleRenderController.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php index 00dd4daf41..32c60de1c5 100644 --- a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php +++ b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php @@ -2,17 +2,12 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController { - private $class; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->class = idx($data, 'class'); - } - - public function processRequest() { + public function handleRequest(AphrontRequest $request) { + $id = $request->getURIData('class'); $classes = id(new PhutilSymbolLoader()) ->setAncestorClass('PhabricatorUIExample') @@ -27,7 +22,7 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController { $nav->addFilter($class, $name); } - $selected = $nav->selectFilter($this->class, head_key($classes)); + $selected = $nav->selectFilter($id, head_key($classes)); $example = $classes[$selected]; $example->setRequest($this->getRequest()); From 42c0dd2b8e1beba88b7bb235dad59a347ed5dac6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:41:08 -0700 Subject: [PATCH 047/102] Add mail/feed to Slowvote Summary: Adds mailkeys, basic structure for publishing to feed, sending mail. Test Plan: New Poll, vote, comment, etc. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13749 --- .../20150725.slowvote.mailkey.1.sql | 2 + .../20150725.slowvote.mailkey.2.php | 18 ++++ src/__phutil_library_map__.php | 4 + .../editor/PhabricatorSlowvoteEditor.php | 69 +++++++++++- .../mail/PhabricatorSlowvoteMailReceiver.php | 28 +++++ .../mail/PhabricatorSlowvoteReplyHandler.php | 16 +++ .../storage/PhabricatorSlowvotePoll.php | 13 +++ .../PhabricatorSlowvoteTransaction.php | 101 ++++++++++++++++++ 8 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20150725.slowvote.mailkey.1.sql create mode 100644 resources/sql/autopatches/20150725.slowvote.mailkey.2.php create mode 100644 src/applications/slowvote/mail/PhabricatorSlowvoteMailReceiver.php create mode 100644 src/applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php diff --git a/resources/sql/autopatches/20150725.slowvote.mailkey.1.sql b/resources/sql/autopatches/20150725.slowvote.mailkey.1.sql new file mode 100644 index 0000000000..00591afa56 --- /dev/null +++ b/resources/sql/autopatches/20150725.slowvote.mailkey.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_slowvote.slowvote_poll + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20150725.slowvote.mailkey.2.php b/resources/sql/autopatches/20150725.slowvote.mailkey.2.php new file mode 100644 index 0000000000..fa6b2035df --- /dev/null +++ b/resources/sql/autopatches/20150725.slowvote.mailkey.2.php @@ -0,0 +1,18 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $slowvote) { + $id = $slowvote->getID(); + + echo pht('Adding mail key for Slowvote %d...', $id); + echo "\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a9cfc87768..d0c6f4af92 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2792,11 +2792,13 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteEditController' => 'applications/slowvote/controller/PhabricatorSlowvoteEditController.php', 'PhabricatorSlowvoteEditor' => 'applications/slowvote/editor/PhabricatorSlowvoteEditor.php', 'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/PhabricatorSlowvoteListController.php', + 'PhabricatorSlowvoteMailReceiver' => 'applications/slowvote/mail/PhabricatorSlowvoteMailReceiver.php', 'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/PhabricatorSlowvoteOption.php', 'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/PhabricatorSlowvotePoll.php', 'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/PhabricatorSlowvotePollController.php', 'PhabricatorSlowvotePollPHIDType' => 'applications/slowvote/phid/PhabricatorSlowvotePollPHIDType.php', 'PhabricatorSlowvoteQuery' => 'applications/slowvote/query/PhabricatorSlowvoteQuery.php', + 'PhabricatorSlowvoteReplyHandler' => 'applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php', 'PhabricatorSlowvoteSchemaSpec' => 'applications/slowvote/storage/PhabricatorSlowvoteSchemaSpec.php', 'PhabricatorSlowvoteSearchEngine' => 'applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php', 'PhabricatorSlowvoteTransaction' => 'applications/slowvote/storage/PhabricatorSlowvoteTransaction.php', @@ -6782,6 +6784,7 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteEditController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController', + 'PhabricatorSlowvoteMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvotePoll' => array( 'PhabricatorSlowvoteDAO', @@ -6797,6 +6800,7 @@ phutil_register_library_map(array( 'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvotePollPHIDType' => 'PhabricatorPHIDType', 'PhabricatorSlowvoteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorSlowvoteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorSlowvoteSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorSlowvoteSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorSlowvoteTransaction' => 'PhabricatorApplicationTransaction', diff --git a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php index d1bd6c18f0..047a5c99a8 100644 --- a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php +++ b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php @@ -8,7 +8,7 @@ final class PhabricatorSlowvoteEditor } public function getEditorObjectsDescription() { - return pht('Slowvotes'); + return pht('Slowvote'); } public function getTransactionTypes() { @@ -111,4 +111,71 @@ final class PhabricatorSlowvoteEditor return; } + protected function shouldSendMail( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + public function getMailTagsMap() { + return array( + PhabricatorSlowvoteTransaction::MAILTAG_DETAILS => + pht('Someone changes the poll details.'), + PhabricatorSlowvoteTransaction::MAILTAG_RESPONSES => + pht('Someone votes on a poll.'), + PhabricatorSlowvoteTransaction::MAILTAG_OTHER => + pht('Other poll activity not listed above occurs.'), + ); + } + + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $monogram = $object->getMonogram(); + $name = $object->getQuestion(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject("{$monogram}: {$name}") + ->addHeader('Thread-Topic', $monogram); + } + + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $body = parent::buildMailBody($object, $xactions); + $description = $object->getDescription(); + + if (strlen($description)) { + $body->addTextSection( + pht('SLOWVOTE DESCRIPTION'), + $object->getDescription()); + } + + $body->addLinkSection( + pht('SLOWVOTE DETAIL'), + PhabricatorEnv::getProductionURI('/'.$object->getMonogram())); + + return $body; + } + + protected function getMailTo(PhabricatorLiskDAO $object) { + return array( + $object->getAuthorPHID(), + $this->requireActor()->getPHID(), + ); + } + protected function getMailSubjectPrefix() { + return '[Slowvote]'; + } + + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new PhabricatorSlowvoteReplyHandler()) + ->setMailReceiver($object); + } + + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + } diff --git a/src/applications/slowvote/mail/PhabricatorSlowvoteMailReceiver.php b/src/applications/slowvote/mail/PhabricatorSlowvoteMailReceiver.php new file mode 100644 index 0000000000..78e608231f --- /dev/null +++ b/src/applications/slowvote/mail/PhabricatorSlowvoteMailReceiver.php @@ -0,0 +1,28 @@ +setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + } + + protected function getTransactionReplyHandler() { + return new PhabricatorSlowvoteReplyHandler(); + } + +} diff --git a/src/applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php b/src/applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php new file mode 100644 index 0000000000..b67327e506 --- /dev/null +++ b/src/applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php @@ -0,0 +1,16 @@ + 'uint32', 'description' => 'text', 'isClosed' => 'bool', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -106,6 +108,17 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO return $this; } + public function getMonogram() { + return 'V'.$this->getID(); + } + + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php index 9abf1f5801..e45fdb013f 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php @@ -9,6 +9,10 @@ final class PhabricatorSlowvoteTransaction const TYPE_SHUFFLE = 'vote:shuffle'; const TYPE_CLOSE = 'vote:close'; + const MAILTAG_DETAILS = 'vote:details'; + const MAILTAG_RESPONSES = 'vote:responses'; + const MAILTAG_OTHER = 'vote:vote'; + public function getApplicationName() { return 'slowvote'; } @@ -93,6 +97,82 @@ final class PhabricatorSlowvoteTransaction return parent::getTitle(); } + public function getTitleForFeed() { + $author_phid = $this->getAuthorPHID(); + $object_phid = $this->getObjectPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $type = $this->getTransactionType(); + switch ($type) { + case self::TYPE_QUESTION: + if ($old === null) { + return pht( + '%s created %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + + } else { + return pht( + '%s renamed %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + break; + case self::TYPE_DESCRIPTION: + if ($old === null) { + return pht( + '%s set the description of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + + } else { + return pht( + '%s edited the description of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + break; + case self::TYPE_RESPONSES: + // TODO: This could be more detailed + return pht( + '%s changed who can see the responses of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + + case self::TYPE_SHUFFLE: + if ($new) { + return pht( + '%s made %s responses appear in a random order.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + + } else { + return pht( + '%s made %s responses appear in a fixed order.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + case self::TYPE_CLOSE: + if ($new) { + return pht( + '%s closed %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + + } else { + return pht( + '%s reopened %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + break; + } + + return parent::getTitleForFeed(); + } + public function getIcon() { $old = $this->getOldValue(); $new = $this->getNewValue(); @@ -152,5 +232,26 @@ final class PhabricatorSlowvoteTransaction $this->getNewValue()); } + public function getMailTags() { + $tags = parent::getMailTags(); + + switch ($this->getTransactionType()) { + case self::TYPE_QUESTION: + case self::TYPE_DESCRIPTION: + case self::TYPE_SHUFFLE: + case self::TYPE_CLOSE: + $tags[] = self::MAILTAG_DETAILS; + break; + case self::TYPE_RESPONSES: + $tags[] = self::MAILTAG_RESPONSES; + break; + default: + $tags[] = self::MAILTAG_OTHER; + break; + } + + return $tags; + } + } From 6d59f3d1f2d41ce5f90d2ea10f3204353d543a01 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:41:36 -0700 Subject: [PATCH 048/102] Update Herald for handleRequest Summary: Update Herald for handleRequest Test Plan: New rule, test console, logs, lists. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13765 --- .../controller/HeraldDisableController.php | 18 ++++--------- .../herald/controller/HeraldNewController.php | 5 ++-- .../controller/HeraldRuleController.php | 26 +++++++------------ .../controller/HeraldRuleListController.php | 10 +++---- .../controller/HeraldRuleViewController.php | 14 +++------- .../HeraldTestConsoleController.php | 15 ++++------- .../HeraldTranscriptListController.php | 10 +++---- 7 files changed, 31 insertions(+), 67 deletions(-) diff --git a/src/applications/herald/controller/HeraldDisableController.php b/src/applications/herald/controller/HeraldDisableController.php index edf15f80f6..054f30e7d3 100644 --- a/src/applications/herald/controller/HeraldDisableController.php +++ b/src/applications/herald/controller/HeraldDisableController.php @@ -2,18 +2,10 @@ final class HeraldDisableController extends HeraldController { - private $id; - private $action; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - $this->action = $data['action']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - $id = $this->id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $action = $request->getURIData('action'); $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) @@ -35,7 +27,7 @@ final class HeraldDisableController extends HeraldController { $view_uri = $this->getApplicationURI("rule/{$id}/"); - $is_disable = ($this->action === 'disable'); + $is_disable = ($action === 'disable'); if ($request->isFormPost()) { $xaction = id(new HeraldRuleTransaction()) diff --git a/src/applications/herald/controller/HeraldNewController.php b/src/applications/herald/controller/HeraldNewController.php index 2bbc00456f..53e1faf7d6 100644 --- a/src/applications/herald/controller/HeraldNewController.php +++ b/src/applications/herald/controller/HeraldNewController.php @@ -2,9 +2,8 @@ final class HeraldNewController extends HeraldController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index 5f447abffd..385cb65d0d 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -2,24 +2,16 @@ final class HeraldRuleController extends HeraldController { - private $id; - private $filter; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = (int)idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $content_type_map = HeraldAdapter::getEnabledAdapterMap($user); + $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); - if ($this->id) { - $id = $this->id; + if ($id) { $rule = id(new HeraldRuleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( @@ -33,7 +25,7 @@ final class HeraldRuleController extends HeraldController { $cancel_uri = $this->getApplicationURI("rule/{$id}/"); } else { $rule = new HeraldRule(); - $rule->setAuthorPHID($user->getPHID()); + $rule->setAuthorPHID($viewer->getPHID()); $rule->setMustMatchAll(1); $content_type = $request->getStr('content_type'); @@ -58,7 +50,7 @@ final class HeraldRuleController extends HeraldController { if ($rule->isObjectRule()) { $rule->setTriggerObjectPHID($request->getStr('targetPHID')); $object = id(new PhabricatorObjectQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($rule->getTriggerObjectPHID())) ->requireCapabilities( array( @@ -128,7 +120,7 @@ final class HeraldRuleController extends HeraldController { $rule_type_name = $rule_type_map[$rule->getRuleType()]; $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setID('herald-rule-edit-form') ->addHiddenInput('content_type', $rule->getContentType()) ->addHiddenInput('rule_type', $rule->getRuleType()) diff --git a/src/applications/herald/controller/HeraldRuleListController.php b/src/applications/herald/controller/HeraldRuleListController.php index 8847b5d06b..490d84212d 100644 --- a/src/applications/herald/controller/HeraldRuleListController.php +++ b/src/applications/herald/controller/HeraldRuleListController.php @@ -2,19 +2,15 @@ final class HeraldRuleListController extends HeraldController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new HeraldRuleSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index f1ba8e44d7..f6ec235a9e 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -2,19 +2,13 @@ final class HeraldRuleViewController extends HeraldController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needConditionsAndActions(true) ->executeOne(); if (!$rule) { diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php index 7cd610f35a..a7741ba3ce 100644 --- a/src/applications/herald/controller/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/HeraldTestConsoleController.php @@ -2,13 +2,8 @@ final class HeraldTestConsoleController extends HeraldController { - public function processRequest() { - - $request = $this->getRequest(); - $user = $request->getUser(); - - $request = $this->getRequest(); - + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $object_name = trim($request->getStr('object_name')); $e_name = true; @@ -21,7 +16,7 @@ final class HeraldTestConsoleController extends HeraldController { if (!$errors) { $object = id(new PhabricatorObjectQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withNames(array($object_name)) ->executeOne(); @@ -57,7 +52,7 @@ final class HeraldTestConsoleController extends HeraldController { $adapter->setIsNewObject(false); $rules = id(new HeraldRuleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withContentTypes(array($adapter->getAdapterContentType())) ->withDisabled(false) ->needConditionsAndActions(true) @@ -80,7 +75,7 @@ final class HeraldTestConsoleController extends HeraldController { } $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendRemarkupInstructions( pht( 'Enter an object to test rules for, like a Diffusion commit (e.g., '. diff --git a/src/applications/herald/controller/HeraldTranscriptListController.php b/src/applications/herald/controller/HeraldTranscriptListController.php index d53130d476..81865eee4a 100644 --- a/src/applications/herald/controller/HeraldTranscriptListController.php +++ b/src/applications/herald/controller/HeraldTranscriptListController.php @@ -2,8 +2,6 @@ final class HeraldTranscriptListController extends HeraldController { - private $queryKey; - public function buildSideNavView($for_app = false) { $user = $this->getRequest()->getUser(); @@ -32,13 +30,11 @@ final class HeraldTranscriptListController extends HeraldController { return $crumbs; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new HeraldTranscriptSearchEngine()) ->setNavigation($this->buildSideNavView()); From eea94aaf672496dde1293e20cf7da2a18c25a04f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:41:57 -0700 Subject: [PATCH 049/102] Update XHProf for handleRequest Summary: Updates XHProf for handleRequest Test Plan: Use XHProf Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13770 --- .../PhabricatorXHProfProfileController.php | 12 +++--------- .../PhabricatorXHProfSampleListController.php | 18 ++++++++---------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/applications/xhprof/controller/PhabricatorXHProfProfileController.php b/src/applications/xhprof/controller/PhabricatorXHProfProfileController.php index 7f3b155cdd..85815d8589 100644 --- a/src/applications/xhprof/controller/PhabricatorXHProfProfileController.php +++ b/src/applications/xhprof/controller/PhabricatorXHProfProfileController.php @@ -3,22 +3,16 @@ final class PhabricatorXHProfProfileController extends PhabricatorXHProfController { - private $phid; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->phid = $data['phid']; - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { + $phid = $request->getURIData('phid'); $file = id(new PhabricatorFileQuery()) ->setViewer($request->getUser()) - ->withPHIDs(array($this->phid)) + ->withPHIDs(array($phid)) ->executeOne(); if (!$file) { return new Aphront404Response(); diff --git a/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php b/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php index 98f20436e4..65ca5593f3 100644 --- a/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php +++ b/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php @@ -3,24 +3,22 @@ final class PhabricatorXHProfSampleListController extends PhabricatorXHProfController { - private $view; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->view = idx($data, 'view', 'all'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $view = $request->getURIData('view'); - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + if (!$view) { + $view = 'all'; + } $pager = new PHUIPagerView(); $pager->setOffset($request->getInt('page')); - switch ($this->view) { + switch ($view) { case 'sampled': $clause = 'sampleRate > 0'; $show_type = false; @@ -78,7 +76,7 @@ final class PhabricatorXHProfSampleListController $item->addIcon( 'none', - phabricator_datetime($sample->getDateCreated(), $user)); + phabricator_datetime($sample->getDateCreated(), $viewer)); $list->addItem($item); } From 917fa250d0169119ae15902d774a12222f8562f1 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:42:47 -0700 Subject: [PATCH 050/102] Update Paste for handleRequest Summary: Updates Paste Test Plan: New paste, edit, view lists Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13766 --- .../PhabricatorPasteCommentController.php | 20 ++++------- .../PhabricatorPasteEditController.php | 36 ++++++++----------- .../PhabricatorPasteListController.php | 10 ++---- .../PhabricatorPasteViewController.php | 30 ++++++++-------- 4 files changed, 39 insertions(+), 57 deletions(-) diff --git a/src/applications/paste/controller/PhabricatorPasteCommentController.php b/src/applications/paste/controller/PhabricatorPasteCommentController.php index cd35c4a36e..06596e5be7 100644 --- a/src/applications/paste/controller/PhabricatorPasteCommentController.php +++ b/src/applications/paste/controller/PhabricatorPasteCommentController.php @@ -3,23 +3,17 @@ final class PhabricatorPasteCommentController extends PhabricatorPasteController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); if (!$request->isFormPost()) { return new Aphront400Response(); } $paste = id(new PhabricatorPasteQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$paste) { return new Aphront404Response(); @@ -38,7 +32,7 @@ final class PhabricatorPasteCommentController ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorPasteEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); @@ -57,7 +51,7 @@ final class PhabricatorPasteCommentController if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) + ->setViewer($viewer) ->setTransactions($xactions) ->setIsPreview($is_preview); } else { diff --git a/src/applications/paste/controller/PhabricatorPasteEditController.php b/src/applications/paste/controller/PhabricatorPasteEditController.php index dcc4900951..db9ca7de99 100644 --- a/src/applications/paste/controller/PhabricatorPasteEditController.php +++ b/src/applications/paste/controller/PhabricatorPasteEditController.php @@ -2,29 +2,23 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $parent = null; $parent_id = null; - if (!$this->id) { + if (!$id) { $is_create = true; - $paste = PhabricatorPaste::initializeNewPaste($user); + $paste = PhabricatorPaste::initializeNewPaste($viewer); $parent_id = $request->getStr('parent'); if ($parent_id) { // NOTE: If the Paste is forked from a paste which the user no longer // has permission to see, we still let them edit it. $parent = id(new PhabricatorPasteQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($parent_id)) ->needContent(true) ->needRawContent(true) @@ -37,19 +31,19 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { } } - $paste->setAuthorPHID($user->getPHID()); + $paste->setAuthorPHID($viewer->getPHID()); $paste->attachRawContent(''); } else { $is_create = false; $paste = id(new PhabricatorPasteQuery()) - ->setViewer($user) + ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needRawContent(true) ->executeOne(); if (!$paste) { @@ -97,7 +91,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { if ($is_create || ($v_text !== $paste->getRawContent())) { $file = PhabricatorPasteEditor::initializeFileForPaste( - $user, + $viewer, $v_title, $v_text); @@ -129,7 +123,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhabricatorPasteEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); @@ -148,7 +142,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); $form - ->setUser($user) + ->setUser($viewer) ->addHiddenInput('parent', $parent_id) ->appendChild( id(new AphrontFormTextControl()) @@ -163,13 +157,13 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ->setOptions($langs)); $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setObject($paste) ->execute(); $form->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($paste) ->setPolicies($policies) @@ -179,7 +173,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { $form->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($paste) ->setPolicies($policies) diff --git a/src/applications/paste/controller/PhabricatorPasteListController.php b/src/applications/paste/controller/PhabricatorPasteListController.php index 3d8cf7fc11..2092443ffc 100644 --- a/src/applications/paste/controller/PhabricatorPasteListController.php +++ b/src/applications/paste/controller/PhabricatorPasteListController.php @@ -2,19 +2,15 @@ final class PhabricatorPasteListController extends PhabricatorPasteController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PhabricatorPasteSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index 75f2532358..ebdf4e48c3 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -5,7 +5,6 @@ */ final class PhabricatorPasteViewController extends PhabricatorPasteController { - private $id; private $highlightMap; public function shouldAllowPublic() { @@ -13,7 +12,6 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { } public function willProcessRequest(array $data) { - $this->id = $data['id']; $raw_lines = idx($data, 'lines'); $map = array(); if ($raw_lines) { @@ -31,13 +29,13 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $this->highlightMap = $map; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $paste = id(new PhabricatorPasteQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needContent(true) ->executeOne(); if (!$paste) { @@ -45,7 +43,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { } $file = id(new PhabricatorFileQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($paste->getFilePHID())) ->executeOne(); if (!$file) { @@ -53,13 +51,13 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { } $forks = id(new PhabricatorPasteQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withParentPHIDs(array($paste->getPHID())) ->execute(); $fork_phids = mpull($forks, 'getPHID'); $header = $this->buildHeaderView($paste); - $actions = $this->buildActionView($user, $paste, $file); + $actions = $this->buildActionView($viewer, $paste, $file); $properties = $this->buildPropertyView($paste, $fork_phids, $actions); $object_box = id(new PHUIObjectBoxView()) @@ -90,10 +88,10 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { ? pht('Add Comment') : pht('Eat Paste'); - $draft = PhabricatorDraft::newFromUserAndKey($user, $paste->getPHID()); + $draft = PhabricatorDraft::newFromUserAndKey($viewer, $paste->getPHID()); $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setObjectPHID($paste->getPHID()) ->setDraft($draft) ->setHeaderText($add_comment_header) @@ -126,20 +124,20 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { } private function buildActionView( - PhabricatorUser $user, + PhabricatorUser $viewer, PhabricatorPaste $paste, PhabricatorFile $file) { $can_edit = PhabricatorPolicyFilter::hasCapability( - $user, + $viewer, $paste, PhabricatorPolicyCapability::CAN_EDIT); - $can_fork = $user->isLoggedIn(); + $can_fork = $viewer->isLoggedIn(); $fork_uri = $this->getApplicationURI('/create/?parent='.$paste->getID()); return id(new PhabricatorActionListView()) - ->setUser($user) + ->setUser($viewer) ->setObject($paste) ->setObjectURI($this->getRequest()->getRequestURI()) ->addAction( From 2ff460158423ea98b30cca4207bb68a339b0e309 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:43:14 -0700 Subject: [PATCH 051/102] Update Calendar for handleRequest Summary: Run through Calendar and update/cleanup processRequest Test Plan: New Event, Edit, etc. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13747 --- ...abricatorCalendarEventCancelController.php | 24 +++++++------------ ...bricatorCalendarEventCommentController.php | 24 ++++++++----------- ...PhabricatorCalendarEventEditController.php | 6 ++--- ...ricatorCalendarEventEditIconController.php | 11 +++------ ...PhabricatorCalendarEventJoinController.php | 6 ++--- ...PhabricatorCalendarEventViewController.php | 14 ++++------- 6 files changed, 30 insertions(+), 55 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php index 8cbd594bea..4be5a77d11 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php @@ -3,20 +3,14 @@ final class PhabricatorCalendarEventCancelController extends PhabricatorCalendarController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $sequence = $request->getURIData('sequence'); $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -26,7 +20,7 @@ final class PhabricatorCalendarEventCancelController if ($sequence) { $parent_event = $event; - $event = $parent_event->generateNthGhost($sequence, $user); + $event = $parent_event->generateNthGhost($sequence, $viewer); $event->attachParentEvent($parent_event); } @@ -51,10 +45,10 @@ final class PhabricatorCalendarEventCancelController return id(new AphrontRedirectResponse())->setURI($cancel_uri); } else if ($sequence) { $event = $this->createEventFromGhost( - $user, + $viewer, $event, $sequence); - $event->applyViewerTimezone($user); + $event->applyViewerTimezone($viewer); } $xactions = array(); @@ -65,7 +59,7 @@ final class PhabricatorCalendarEventCancelController ->setNewValue(!$is_cancelled); $editor = id(new PhabricatorCalendarEventEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php index 9094de5401..876712a455 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php @@ -3,24 +3,20 @@ final class PhabricatorCalendarEventCommentController extends PhabricatorCalendarController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - public function handleRequest(AphrontRequest $request) { if (!$request->isFormPost()) { return new Aphront400Response(); } - $user = $request->getUser(); + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $is_preview = $request->isPreviewRequest(); $draft = PhabricatorDraft::buildFromRequest($request); $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$event) { return new Aphront404Response(); @@ -29,7 +25,7 @@ final class PhabricatorCalendarEventCommentController $index = $request->getURIData('sequence'); if ($index && !$is_preview) { $result = $this->getEventAtIndexForGhostPHID( - $user, + $viewer, $event->getPHID(), $index); @@ -37,10 +33,10 @@ final class PhabricatorCalendarEventCommentController $event = $result; } else { $event = $this->createEventFromGhost( - $user, + $viewer, $event, $index); - $event->applyViewerTimezone($user); + $event->applyViewerTimezone($viewer); } } @@ -54,7 +50,7 @@ final class PhabricatorCalendarEventCommentController ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorCalendarEventEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); @@ -73,7 +69,7 @@ final class PhabricatorCalendarEventCommentController if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) + ->setViewer($viewer) ->setTransactions($xactions) ->setIsPreview($is_preview); } else { diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 07da53fa67..e55b0debaa 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -5,10 +5,6 @@ final class PhabricatorCalendarEventEditController private $id; - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - public function isCreate() { return !$this->id; } @@ -16,6 +12,8 @@ final class PhabricatorCalendarEventEditController 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; diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditIconController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditIconController.php index a8c9af97fb..e93baa52bd 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditIconController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditIconController.php @@ -3,19 +3,14 @@ final class PhabricatorCalendarEventEditIconController extends PhabricatorCalendarController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - public function handleRequest(AphrontRequest $request) { $viewer = $request->getUser(); + $id = $request->getURIData('id'); - if ($this->id) { + if ($id) { $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php index 60cde4f561..a7f41c4a0c 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php @@ -3,14 +3,12 @@ final class PhabricatorCalendarEventJoinController extends PhabricatorCalendarController { - private $id; - const ACTION_ACCEPT = 'accept'; const ACTION_DECLINE = 'decline'; const ACTION_JOIN = 'join'; public function handleRequest(AphrontRequest $request) { - $this->id = $request->getURIData('id'); + $id = $request->getURIData('id'); $action = $request->getURIData('action'); $request = $this->getRequest(); @@ -20,7 +18,7 @@ final class PhabricatorCalendarEventJoinController $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$event) { diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 580e5ea5ee..a0d5be4e4d 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -3,26 +3,20 @@ final class PhabricatorCalendarEventViewController extends PhabricatorCalendarController { - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + 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($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$event) { return new Aphront404Response(); From 1d2c47f1104c3962152604b81a15e0e34a58a342 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 15:43:47 -0700 Subject: [PATCH 052/102] Update Phortune for handleRequest Summary: Updates Phortune controllers for handleRequest Test Plan: New Invoice, new merchant, new subscriptions, manual bill, pay bill, anything I could find to test. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13768 --- .../PhortuneAccountEditController.php | 16 ++++------ .../PhortuneAccountListController.php | 5 ++-- .../PhortuneCartAcceptController.php | 14 +++------ .../PhortuneCartCancelController.php | 19 ++++-------- .../PhortuneCartCheckoutController.php | 14 +++------ .../PhortuneCartUpdateController.php | 14 +++------ .../controller/PhortuneCartViewController.php | 14 +++------ .../PhortuneChargeListController.php | 23 +++++--------- .../controller/PhortuneLandingController.php | 11 ++++--- .../PhortuneMerchantEditController.php | 16 ++++------ .../PhortuneMerchantListController.php | 13 ++++---- .../PhortuneMerchantViewController.php | 14 +++------ .../PhortunePaymentMethodCreateController.php | 14 +++------ ...PhortunePaymentMethodDisableController.php | 14 +++------ .../PhortunePaymentMethodEditController.php | 14 +++------ .../PhortuneProductListController.php | 9 +++--- .../PhortuneProductViewController.php | 20 +++++-------- .../PhortuneProviderActionController.php | 15 ++++------ .../PhortuneProviderDisableController.php | 14 +++------ .../PhortuneProviderEditController.php | 16 ++++------ .../PhortuneSubscriptionListController.php | 30 +++++++------------ 21 files changed, 104 insertions(+), 215 deletions(-) diff --git a/src/applications/phortune/controller/PhortuneAccountEditController.php b/src/applications/phortune/controller/PhortuneAccountEditController.php index 7847420549..f51f1293d9 100644 --- a/src/applications/phortune/controller/PhortuneAccountEditController.php +++ b/src/applications/phortune/controller/PhortuneAccountEditController.php @@ -2,20 +2,14 @@ final class PhortuneAccountEditController extends PhortuneController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if ($this->id) { + if ($id) { $account = id(new PhortuneAccountQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/phortune/controller/PhortuneAccountListController.php b/src/applications/phortune/controller/PhortuneAccountListController.php index 8613cbda4d..0de082fa01 100644 --- a/src/applications/phortune/controller/PhortuneAccountListController.php +++ b/src/applications/phortune/controller/PhortuneAccountListController.php @@ -2,9 +2,8 @@ final class PhortuneAccountListController extends PhortuneController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $accounts = id(new PhortuneAccountQuery()) ->setViewer($viewer) diff --git a/src/applications/phortune/controller/PhortuneCartAcceptController.php b/src/applications/phortune/controller/PhortuneCartAcceptController.php index 3db7467290..cb53e66f50 100644 --- a/src/applications/phortune/controller/PhortuneCartAcceptController.php +++ b/src/applications/phortune/controller/PhortuneCartAcceptController.php @@ -3,15 +3,9 @@ final class PhortuneCartAcceptController extends PhortuneCartController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); // You must control the merchant to accept orders. $authority = $this->loadMerchantAuthority(); @@ -21,7 +15,7 @@ final class PhortuneCartAcceptController $cart = id(new PhortuneCartQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->withMerchantPHIDs(array($authority->getPHID())) ->needPurchases(true) ->executeOne(); diff --git a/src/applications/phortune/controller/PhortuneCartCancelController.php b/src/applications/phortune/controller/PhortuneCartCancelController.php index 3aedceb6b0..c4a26c0d00 100644 --- a/src/applications/phortune/controller/PhortuneCartCancelController.php +++ b/src/applications/phortune/controller/PhortuneCartCancelController.php @@ -3,23 +3,16 @@ final class PhortuneCartCancelController extends PhortuneCartController { - private $id; - private $action; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - $this->action = $data['action']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $action = $request->getURIData('action'); $authority = $this->loadMerchantAuthority(); $cart_query = id(new PhortuneCartQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needPurchases(true); if ($authority) { @@ -31,7 +24,7 @@ final class PhortuneCartCancelController return new Aphront404Response(); } - switch ($this->action) { + switch ($action) { case 'cancel': // You must be able to edit the account to cancel an order. PhabricatorPolicyFilter::requireCapability( diff --git a/src/applications/phortune/controller/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/PhortuneCartCheckoutController.php index 0d8e55f5c3..e22a9521fd 100644 --- a/src/applications/phortune/controller/PhortuneCartCheckoutController.php +++ b/src/applications/phortune/controller/PhortuneCartCheckoutController.php @@ -3,19 +3,13 @@ final class PhortuneCartCheckoutController extends PhortuneCartController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $cart = id(new PhortuneCartQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needPurchases(true) ->executeOne(); if (!$cart) { diff --git a/src/applications/phortune/controller/PhortuneCartUpdateController.php b/src/applications/phortune/controller/PhortuneCartUpdateController.php index ea571ccf8a..3d49611d2d 100644 --- a/src/applications/phortune/controller/PhortuneCartUpdateController.php +++ b/src/applications/phortune/controller/PhortuneCartUpdateController.php @@ -3,21 +3,15 @@ final class PhortuneCartUpdateController extends PhortuneCartController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $authority = $this->loadMerchantAuthority(); $cart_query = id(new PhortuneCartQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needPurchases(true); if ($authority) { diff --git a/src/applications/phortune/controller/PhortuneCartViewController.php b/src/applications/phortune/controller/PhortuneCartViewController.php index 9e133002f5..f4eb0d9612 100644 --- a/src/applications/phortune/controller/PhortuneCartViewController.php +++ b/src/applications/phortune/controller/PhortuneCartViewController.php @@ -3,21 +3,15 @@ final class PhortuneCartViewController extends PhortuneCartController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $authority = $this->loadMerchantAuthority(); $query = id(new PhortuneCartQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needPurchases(true); if ($authority) { diff --git a/src/applications/phortune/controller/PhortuneChargeListController.php b/src/applications/phortune/controller/PhortuneChargeListController.php index 85a5a0cfb2..b8edb92507 100644 --- a/src/applications/phortune/controller/PhortuneChargeListController.php +++ b/src/applications/phortune/controller/PhortuneChargeListController.php @@ -3,26 +3,19 @@ final class PhortuneChargeListController extends PhortuneController { - private $accountID; - private $queryKey; - private $account; - public function willProcessRequest(array $data) { - $this->accountID = idx($data, 'accountID'); - $this->queryKey = idx($data, 'queryKey'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $querykey = $request->getURIData('queryKey'); + $account_id = $request->getURIData('accountID'); $engine = new PhortuneChargeSearchEngine(); - if ($this->accountID) { + if ($account_id) { $account = id(new PhortuneAccountQuery()) ->setViewer($viewer) - ->withIDs(array($this->accountID)) + ->withIDs(array($account_id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -39,7 +32,7 @@ final class PhortuneChargeListController } $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); @@ -47,7 +40,7 @@ final class PhortuneChargeListController } public function buildSideNavView() { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); diff --git a/src/applications/phortune/controller/PhortuneLandingController.php b/src/applications/phortune/controller/PhortuneLandingController.php index ddff75f9ec..2a019c5df9 100644 --- a/src/applications/phortune/controller/PhortuneLandingController.php +++ b/src/applications/phortune/controller/PhortuneLandingController.php @@ -2,18 +2,17 @@ final class PhortuneLandingController extends PhortuneController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $accounts = id(new PhortuneAccountQuery()) - ->setViewer($user) - ->withMemberPHIDs(array($user->getPHID())) + ->setViewer($viewer) + ->withMemberPHIDs(array($viewer->getPHID())) ->execute(); if (!$accounts) { $account = PhortuneAccount::createNewAccount( - $user, + $viewer, PhabricatorContentSource::newFromRequest($request)); $accounts = array($account); } diff --git a/src/applications/phortune/controller/PhortuneMerchantEditController.php b/src/applications/phortune/controller/PhortuneMerchantEditController.php index adb6e39c44..d3b396ddb0 100644 --- a/src/applications/phortune/controller/PhortuneMerchantEditController.php +++ b/src/applications/phortune/controller/PhortuneMerchantEditController.php @@ -3,20 +3,14 @@ final class PhortuneMerchantEditController extends PhortuneMerchantController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if ($this->id) { + if ($id) { $merchant = id(new PhortuneMerchantQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/phortune/controller/PhortuneMerchantListController.php b/src/applications/phortune/controller/PhortuneMerchantListController.php index 6849393de7..48fb02195b 100644 --- a/src/applications/phortune/controller/PhortuneMerchantListController.php +++ b/src/applications/phortune/controller/PhortuneMerchantListController.php @@ -3,19 +3,16 @@ final class PhortuneMerchantListController extends PhortuneMerchantController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PhortuneMerchantSearchEngine()) ->setNavigation($this->buildSideNavView()); @@ -23,7 +20,7 @@ final class PhortuneMerchantListController } public function buildSideNavView() { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); diff --git a/src/applications/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php index 2ca9f70c80..4b515c44c5 100644 --- a/src/applications/phortune/controller/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php @@ -3,19 +3,13 @@ final class PhortuneMerchantViewController extends PhortuneMerchantController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $merchant = id(new PhortuneMerchantQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$merchant) { return new Aphront404Response(); diff --git a/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php index 6b7e55bb54..dd783d4c51 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php @@ -3,19 +3,13 @@ final class PhortunePaymentMethodCreateController extends PhortuneController { - private $accountID; - - public function willProcessRequest(array $data) { - $this->accountID = $data['accountID']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $account_id = $request->getURIData('accountID'); $account = id(new PhortuneAccountQuery()) ->setViewer($viewer) - ->withIDs(array($this->accountID)) + ->withIDs(array($account_id)) ->executeOne(); if (!$account) { return new Aphront404Response(); diff --git a/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php b/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php index 8f5124bdb4..4b8fb76010 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php @@ -3,19 +3,13 @@ final class PhortunePaymentMethodDisableController extends PhortuneController { - private $methodID; - - public function willProcessRequest(array $data) { - $this->methodID = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $method_id = $request->getURIData('methodID'); $method = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) - ->withIDs(array($this->methodID)) + ->withIDs(array($method_id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php index 2db514a1cf..27edd72a59 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php @@ -3,19 +3,13 @@ final class PhortunePaymentMethodEditController extends PhortuneController { - private $methodID; - - public function willProcessRequest(array $data) { - $this->methodID = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $method_id = $request->getURIData('id'); $method = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) - ->withIDs(array($this->methodID)) + ->withIDs(array($method_id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/phortune/controller/PhortuneProductListController.php b/src/applications/phortune/controller/PhortuneProductListController.php index 8a1181b33e..a82effa6d4 100644 --- a/src/applications/phortune/controller/PhortuneProductListController.php +++ b/src/applications/phortune/controller/PhortuneProductListController.php @@ -2,15 +2,14 @@ final class PhortuneProductListController extends PhabricatorController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $pager = new AphrontCursorPagerView(); $pager->readFromRequest($request); $query = id(new PhortuneProductQuery()) - ->setViewer($user); + ->setViewer($viewer); $products = $query->executeWithCursorPager($pager); @@ -27,7 +26,7 @@ final class PhortuneProductListController extends PhabricatorController { ->setIcon('fa-plus-square')); $product_list = id(new PHUIObjectItemListView()) - ->setUser($user) + ->setUser($viewer) ->setNoDataString(pht('No products.')); foreach ($products as $product) { diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php index 02426f07ca..73d120bfd9 100644 --- a/src/applications/phortune/controller/PhortuneProductViewController.php +++ b/src/applications/phortune/controller/PhortuneProductViewController.php @@ -2,19 +2,13 @@ final class PhortuneProductViewController extends PhortuneController { - private $productID; - - public function willProcessRequest(array $data) { - $this->productID = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $product = id(new PhortuneProductQuery()) - ->setViewer($user) - ->withIDs(array($this->productID)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$product) { return new Aphront404Response(); @@ -28,7 +22,7 @@ final class PhortuneProductViewController extends PhortuneController { $edit_uri = $this->getApplicationURI('product/edit/'.$product->getID().'/'); $actions = id(new PhabricatorActionListView()) - ->setUser($user) + ->setUser($viewer) ->setObjectURI($request->getRequestURI()); $crumbs = $this->buildApplicationCrumbs(); @@ -40,7 +34,7 @@ final class PhortuneProductViewController extends PhortuneController { $request->getRequestURI()); $properties = id(new PHUIPropertyListView()) - ->setUser($user) + ->setUser($viewer) ->setActionList($actions) ->addProperty( pht('Price'), diff --git a/src/applications/phortune/controller/PhortuneProviderActionController.php b/src/applications/phortune/controller/PhortuneProviderActionController.php index b856b67d97..52d5453f42 100644 --- a/src/applications/phortune/controller/PhortuneProviderActionController.php +++ b/src/applications/phortune/controller/PhortuneProviderActionController.php @@ -3,14 +3,8 @@ final class PhortuneProviderActionController extends PhortuneController { - private $id; private $action; - public function willProcessRequest(array $data) { - $this->id = $data['id']; - $this->setAction($data['action']); - } - public function setAction($action) { $this->action = $action; return $this; @@ -20,13 +14,14 @@ final class PhortuneProviderActionController return $this->action; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $this->setAction($request->getURIData('action')); $provider_config = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$provider_config) { return new Aphront404Response(); diff --git a/src/applications/phortune/controller/PhortuneProviderDisableController.php b/src/applications/phortune/controller/PhortuneProviderDisableController.php index e398cbc886..03236b54bc 100644 --- a/src/applications/phortune/controller/PhortuneProviderDisableController.php +++ b/src/applications/phortune/controller/PhortuneProviderDisableController.php @@ -3,19 +3,13 @@ final class PhortuneProviderDisableController extends PhortuneMerchantController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $provider_config = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/phortune/controller/PhortuneProviderEditController.php b/src/applications/phortune/controller/PhortuneProviderEditController.php index 3ed97b5a4b..f956c41d68 100644 --- a/src/applications/phortune/controller/PhortuneProviderEditController.php +++ b/src/applications/phortune/controller/PhortuneProviderEditController.php @@ -3,20 +3,14 @@ final class PhortuneProviderEditController extends PhortuneMerchantController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if ($this->id) { + if ($id) { $provider_config = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/phortune/controller/PhortuneSubscriptionListController.php b/src/applications/phortune/controller/PhortuneSubscriptionListController.php index 4fbdb804c3..469960f4bc 100644 --- a/src/applications/phortune/controller/PhortuneSubscriptionListController.php +++ b/src/applications/phortune/controller/PhortuneSubscriptionListController.php @@ -3,29 +3,21 @@ final class PhortuneSubscriptionListController extends PhortuneController { - private $accountID; - private $merchantID; - private $queryKey; - private $merchant; private $account; - public function willProcessRequest(array $data) { - $this->merchantID = idx($data, 'merchantID'); - $this->accountID = idx($data, 'accountID'); - $this->queryKey = idx($data, 'queryKey'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $querykey = $request->getURIData('queryKey'); + $merchant_id = $request->getURIData('merchantID'); + $account_id = $request->getURIData('accountID'); $engine = new PhortuneSubscriptionSearchEngine(); - if ($this->merchantID) { + if ($merchant_id) { $merchant = id(new PhortuneMerchantQuery()) ->setViewer($viewer) - ->withIDs(array($this->merchantID)) + ->withIDs(array($merchant_id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -38,10 +30,10 @@ final class PhortuneSubscriptionListController $this->merchant = $merchant; $viewer->grantAuthority($merchant); $engine->setMerchant($merchant); - } else if ($this->accountID) { + } else if ($account_id) { $account = id(new PhortuneAccountQuery()) ->setViewer($viewer) - ->withIDs(array($this->accountID)) + ->withIDs(array($account_id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -58,7 +50,7 @@ final class PhortuneSubscriptionListController } $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); @@ -66,7 +58,7 @@ final class PhortuneSubscriptionListController } public function buildSideNavView() { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); From 36103dfa182c8d864b3d19b03ac4cd4ff07d3c0c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 16:49:27 -0700 Subject: [PATCH 053/102] Update Auth for handleRequest Summary: Updates Auth app for handleRequest Test Plan: Tested what I could, Log in, Log out, Change Password, New account, Verify account... but extra eyes very helpful here. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13748 --- .../PhabricatorAuthConfirmLinkController.php | 14 +++------ ...bricatorAuthDowngradeSessionController.php | 5 ++-- .../PhabricatorAuthFinishController.php | 5 ++-- .../PhabricatorAuthLinkController.php | 25 ++++++---------- .../PhabricatorAuthLoginController.php | 12 +++----- ...PhabricatorAuthNeedsApprovalController.php | 7 ++--- ...bricatorAuthNeedsMultiFactorController.php | 5 ++-- ...bricatorAuthOldOAuthRedirectController.php | 14 ++++----- .../PhabricatorAuthOneTimeLoginController.php | 30 +++++++------------ .../PhabricatorAuthRegisterController.php | 15 ++++------ .../PhabricatorAuthRevokeTokenController.php | 16 ++++------ .../PhabricatorAuthSSHKeyEditController.php | 2 +- ...bricatorAuthTerminateSessionController.php | 16 ++++------ .../PhabricatorAuthUnlinkController.php | 10 ++----- .../PhabricatorAuthValidateController.php | 5 ++-- .../PhabricatorDisabledUserController.php | 11 +++---- .../PhabricatorEmailLoginController.php | 3 +- ...PhabricatorEmailVerificationController.php | 26 +++++++--------- .../PhabricatorLogoutController.php | 13 ++++---- .../PhabricatorMustVerifyEmailController.php | 13 ++++---- .../PhabricatorRefreshCSRFController.php | 7 ++--- 21 files changed, 95 insertions(+), 159 deletions(-) diff --git a/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php b/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php index 32c0101b8a..799a8e691e 100644 --- a/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php @@ -3,17 +3,11 @@ final class PhabricatorAuthConfirmLinkController extends PhabricatorAuthController { - private $accountKey; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $accountkey = $request->getURIData('akey'); - public function willProcessRequest(array $data) { - $this->accountKey = idx($data, 'akey'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $result = $this->loadAccountForRegistrationOrLinking($this->accountKey); + $result = $this->loadAccountForRegistrationOrLinking($accountkey); list($account, $provider, $response) = $result; if ($response) { diff --git a/src/applications/auth/controller/PhabricatorAuthDowngradeSessionController.php b/src/applications/auth/controller/PhabricatorAuthDowngradeSessionController.php index c4b6b2ad43..4981845876 100644 --- a/src/applications/auth/controller/PhabricatorAuthDowngradeSessionController.php +++ b/src/applications/auth/controller/PhabricatorAuthDowngradeSessionController.php @@ -3,9 +3,8 @@ final class PhabricatorAuthDowngradeSessionController extends PhabricatorAuthController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $panel_uri = '/settings/panel/sessions/'; diff --git a/src/applications/auth/controller/PhabricatorAuthFinishController.php b/src/applications/auth/controller/PhabricatorAuthFinishController.php index 82f4d72b26..387679b44e 100644 --- a/src/applications/auth/controller/PhabricatorAuthFinishController.php +++ b/src/applications/auth/controller/PhabricatorAuthFinishController.php @@ -15,9 +15,8 @@ final class PhabricatorAuthFinishController return true; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); // If the user already has a full session, just kick them out of here. $has_partial_session = $viewer->hasSession() && diff --git a/src/applications/auth/controller/PhabricatorAuthLinkController.php b/src/applications/auth/controller/PhabricatorAuthLinkController.php index 75d63004b4..d50bcf1d8a 100644 --- a/src/applications/auth/controller/PhabricatorAuthLinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthLinkController.php @@ -3,25 +3,18 @@ final class PhabricatorAuthLinkController extends PhabricatorAuthController { - private $action; - private $providerKey; - - public function willProcessRequest(array $data) { - $this->providerKey = $data['pkey']; - $this->action = $data['action']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $action = $request->getURIData('action'); + $provider_key = $request->getURIData('pkey'); $provider = PhabricatorAuthProvider::getEnabledProviderByKey( - $this->providerKey); + $provider_key); if (!$provider) { return new Aphront404Response(); } - switch ($this->action) { + switch ($action) { case 'link': if (!$provider->shouldAllowAccountLink()) { return $this->renderErrorPage( @@ -50,7 +43,7 @@ final class PhabricatorAuthLinkController $provider->getProviderDomain(), $viewer->getPHID()); - switch ($this->action) { + switch ($action) { case 'link': if ($account) { return $this->renderErrorPage( @@ -81,7 +74,7 @@ final class PhabricatorAuthLinkController PhabricatorCookies::setClientIDCookie($request); - switch ($this->action) { + switch ($action) { case 'link': id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( $viewer, @@ -107,7 +100,7 @@ final class PhabricatorAuthLinkController $form); } - switch ($this->action) { + switch ($action) { case 'link': $name = pht('Link Account'); $title = pht('Link %s Account', $provider->getProviderName()); diff --git a/src/applications/auth/controller/PhabricatorAuthLoginController.php b/src/applications/auth/controller/PhabricatorAuthLoginController.php index e3cbeaa2c6..65d462cb8e 100644 --- a/src/applications/auth/controller/PhabricatorAuthLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthLoginController.php @@ -20,18 +20,14 @@ final class PhabricatorAuthLoginController return parent::shouldAllowRestrictedParameter($parameter_name); } - public function willProcessRequest(array $data) { - $this->providerKey = $data['pkey']; - $this->extraURIData = idx($data, 'extra'); - } - public function getExtraURIData() { return $this->extraURIData; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $this->providerKey = $request->getURIData('pkey'); + $this->extraURIData = $request->getURIData('extra'); $response = $this->loadProvider(); if ($response) { diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php b/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php index 8e0bf99551..0d07470560 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php @@ -15,16 +15,15 @@ final class PhabricatorAuthNeedsApprovalController return false; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $wait_for_approval = pht( "Your account has been created, but needs to be approved by an ". "administrator. You'll receive an email once your account is approved."); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Wait for Approval')) ->appendChild($wait_for_approval) ->addCancelButton('/', pht('Wait Patiently')); diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php index 975355ec97..aaf3864156 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php @@ -9,9 +9,8 @@ final class PhabricatorAuthNeedsMultiFactorController return false; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $panel = id(new PhabricatorMultiFactorSettingsPanel()) ->setUser($viewer) diff --git a/src/applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php b/src/applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php index cc7f362583..6b75b929ab 100644 --- a/src/applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php +++ b/src/applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php @@ -3,8 +3,6 @@ final class PhabricatorAuthOldOAuthRedirectController extends PhabricatorAuthController { - private $provider; - public function shouldRequireLogin() { return false; } @@ -16,11 +14,9 @@ final class PhabricatorAuthOldOAuthRedirectController return parent::shouldAllowRestrictedParameter($parameter_name); } - public function willProcessRequest(array $data) { - $this->provider = $data['provider']; - } - - public function processRequest() { + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $provider = $request->getURIData('provider'); // TODO: Most OAuth providers are OK with changing the redirect URI, but // Google and GitHub are strict. We need to respect the old OAuth URI until // we can get installs to migrate. This just keeps the old OAuth URI working @@ -31,11 +27,11 @@ final class PhabricatorAuthOldOAuthRedirectController 'github' => 'github:github.com', ); - if (!isset($provider_map[$this->provider])) { + if (!isset($provider_map[$provider])) { return new Aphront404Response(); } - $provider_key = $provider_map[$this->provider]; + $provider_key = $provider_map[$provider]; $uri = $this->getRequest()->getRequestURI(); $uri->setPath($this->getApplicationURI('login/'.$provider_key.'/')); diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index 312367d03a..91f3d6a984 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -3,24 +3,16 @@ final class PhabricatorAuthOneTimeLoginController extends PhabricatorAuthController { - private $id; - private $key; - private $emailID; - private $linkType; - public function shouldRequireLogin() { return false; } - public function willProcessRequest(array $data) { - $this->linkType = $data['type']; - $this->id = $data['id']; - $this->key = $data['key']; - $this->emailID = idx($data, 'emailID'); - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); + $link_type = $request->getURIData('key'); + $key = $request->getURIData('type'); + $email_id = $request->getURIData('emailID'); if ($request->getUser()->isLoggedIn()) { return $this->renderError( @@ -29,7 +21,7 @@ final class PhabricatorAuthOneTimeLoginController $target_user = id(new PhabricatorPeopleQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$target_user) { return new Aphront404Response(); @@ -58,11 +50,11 @@ final class PhabricatorAuthOneTimeLoginController // - get a "verified" address you don't control. $target_email = null; - if ($this->emailID) { + if ($email_id) { $target_email = id(new PhabricatorUserEmail())->loadOneWhere( 'userPHID = %s AND id = %d', $target_user->getPHID(), - $this->emailID); + $email_id); if (!$target_email) { return new Aphront404Response(); } @@ -72,7 +64,7 @@ final class PhabricatorAuthOneTimeLoginController $token = $engine->loadOneTimeLoginKey( $target_user, $target_email, - $this->key); + $key); if (!$token) { return $this->newDialog() @@ -154,7 +146,7 @@ final class PhabricatorAuthOneTimeLoginController // then log a user in to an account they control via sneaky invisible // form submissions. - switch ($this->linkType) { + switch ($link_type) { case PhabricatorAuthSessionEngine::ONETIME_WELCOME: $title = pht('Welcome to Phabricator'); break; diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php index 9341345143..655f63acb9 100644 --- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php +++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php @@ -3,26 +3,21 @@ final class PhabricatorAuthRegisterController extends PhabricatorAuthController { - private $accountKey; - public function shouldRequireLogin() { return false; } - public function willProcessRequest(array $data) { - $this->accountKey = idx($data, 'akey'); - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $account_key = $request->getURIData('akey'); if ($request->getUser()->isLoggedIn()) { return $this->renderError(pht('You are already logged in.')); } $is_setup = false; - if (strlen($this->accountKey)) { - $result = $this->loadAccountForRegistrationOrLinking($this->accountKey); + if (strlen($account_key)) { + $result = $this->loadAccountForRegistrationOrLinking($account_key); list($account, $provider, $response) = $result; $is_default = false; } else if ($this->isFirstTimeSetup()) { diff --git a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php index 27981eee27..c1f0c21cb1 100644 --- a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php +++ b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php @@ -3,23 +3,17 @@ final class PhabricatorAuthRevokeTokenController extends PhabricatorAuthController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $is_all = ($this->id === 'all'); + $is_all = ($id === 'all'); $query = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($viewer->getPHID())); if (!$is_all) { - $query->withIDs(array($this->id)); + $query->withIDs(array($id)); } $tokens = $query->execute(); diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php index bb4acd0b4d..d09d52cc14 100644 --- a/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php +++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php @@ -5,8 +5,8 @@ final class PhabricatorAuthSSHKeyEditController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); - $id = $request->getURIData('id'); + if ($id) { $key = id(new PhabricatorAuthSSHKeyQuery()) ->setViewer($viewer) diff --git a/src/applications/auth/controller/PhabricatorAuthTerminateSessionController.php b/src/applications/auth/controller/PhabricatorAuthTerminateSessionController.php index d2534c4a45..ae8179a798 100644 --- a/src/applications/auth/controller/PhabricatorAuthTerminateSessionController.php +++ b/src/applications/auth/controller/PhabricatorAuthTerminateSessionController.php @@ -3,23 +3,17 @@ final class PhabricatorAuthTerminateSessionController extends PhabricatorAuthController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $is_all = ($this->id === 'all'); + $is_all = ($id === 'all'); $query = id(new PhabricatorAuthSessionQuery()) ->setViewer($viewer) ->withIdentityPHIDs(array($viewer->getPHID())); if (!$is_all) { - $query->withIDs(array($this->id)); + $query->withIDs(array($id)); } $current_key = PhabricatorHash::digest( diff --git a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php index a5bdf90b70..3f694207b9 100644 --- a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php @@ -5,13 +5,9 @@ final class PhabricatorAuthUnlinkController private $providerKey; - public function willProcessRequest(array $data) { - $this->providerKey = $data['pkey']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $this->providerKey = $request->getURIData('pkey'); list($type, $domain) = explode(':', $this->providerKey, 2); diff --git a/src/applications/auth/controller/PhabricatorAuthValidateController.php b/src/applications/auth/controller/PhabricatorAuthValidateController.php index c91f3c4504..bb45a68acf 100644 --- a/src/applications/auth/controller/PhabricatorAuthValidateController.php +++ b/src/applications/auth/controller/PhabricatorAuthValidateController.php @@ -15,9 +15,8 @@ final class PhabricatorAuthValidateController return true; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $failures = array(); diff --git a/src/applications/auth/controller/PhabricatorDisabledUserController.php b/src/applications/auth/controller/PhabricatorDisabledUserController.php index 842f2daad6..39e390d44a 100644 --- a/src/applications/auth/controller/PhabricatorDisabledUserController.php +++ b/src/applications/auth/controller/PhabricatorDisabledUserController.php @@ -7,15 +7,16 @@ final class PhabricatorDisabledUserController return false; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - if (!$user->getIsDisabled()) { + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); + + if (!$viewer->getIsDisabled()) { return new Aphront404Response(); } return id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Account Disabled')) ->addCancelButton('/logout/', pht('Okay')) ->appendParagraph(pht('Your account has been disabled.')); diff --git a/src/applications/auth/controller/PhabricatorEmailLoginController.php b/src/applications/auth/controller/PhabricatorEmailLoginController.php index 9db360d51d..26609133ea 100644 --- a/src/applications/auth/controller/PhabricatorEmailLoginController.php +++ b/src/applications/auth/controller/PhabricatorEmailLoginController.php @@ -7,8 +7,7 @@ final class PhabricatorEmailLoginController return false; } - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) { return new Aphront400Response(); diff --git a/src/applications/auth/controller/PhabricatorEmailVerificationController.php b/src/applications/auth/controller/PhabricatorEmailVerificationController.php index ea5f273d79..83a370139c 100644 --- a/src/applications/auth/controller/PhabricatorEmailVerificationController.php +++ b/src/applications/auth/controller/PhabricatorEmailVerificationController.php @@ -3,12 +3,6 @@ final class PhabricatorEmailVerificationController extends PhabricatorAuthController { - private $code; - - public function willProcessRequest(array $data) { - $this->code = $data['code']; - } - public function shouldRequireEmailVerification() { // Since users need to be able to hit this endpoint in order to verify // email, we can't ever require email verification here. @@ -21,11 +15,11 @@ final class PhabricatorEmailVerificationController return false; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $code = $request->getURIData('code'); - if ($user->getIsDisabled()) { + if ($viewer->getIsDisabled()) { // We allowed unapproved and disabled users to hit this controller, but // want to kick out disabled users now. return new Aphront400Response(); @@ -33,8 +27,8 @@ final class PhabricatorEmailVerificationController $email = id(new PhabricatorUserEmail())->loadOneWhere( 'userPHID = %s AND verificationCode = %s', - $user->getPHID(), - $this->code); + $viewer->getPHID(), + $code); $submit = null; @@ -46,7 +40,7 @@ final class PhabricatorEmailVerificationController 'user. Make sure you followed the link in the email correctly and are '. 'logged in with the user account associated with the email address.'); $continue = pht('Rats!'); - } else if ($email->getIsVerified() && $user->getIsEmailVerified()) { + } else if ($email->getIsVerified() && $viewer->getIsEmailVerified()) { $title = pht('Address Already Verified'); $content = pht( 'This email address has already been verified.'); @@ -54,8 +48,8 @@ final class PhabricatorEmailVerificationController } else if ($request->isFormPost()) { id(new PhabricatorUserEditor()) - ->setActor($user) - ->verifyEmail($user, $email); + ->setActor($viewer) + ->verifyEmail($viewer, $email); $title = pht('Address Verified'); $content = pht( @@ -72,7 +66,7 @@ final class PhabricatorEmailVerificationController } $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle($title) ->addCancelButton('/', $continue) ->appendChild($content); diff --git a/src/applications/auth/controller/PhabricatorLogoutController.php b/src/applications/auth/controller/PhabricatorLogoutController.php index 127e5b5e1f..de3ac50e5d 100644 --- a/src/applications/auth/controller/PhabricatorLogoutController.php +++ b/src/applications/auth/controller/PhabricatorLogoutController.php @@ -26,14 +26,13 @@ final class PhabricatorLogoutController } public function handleRequest(AphrontRequest $request) { - $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $this->getViewer(); if ($request->isFormPost()) { $log = PhabricatorUserLog::initializeNewLog( - $user, - $user->getPHID(), + $viewer, + $viewer->getPHID(), PhabricatorUserLog::ACTION_LOGOUT); $log->save(); @@ -43,7 +42,7 @@ final class PhabricatorLogoutController $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); if (strlen($phsid)) { $session = id(new PhabricatorAuthSessionQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withSessionKeys(array($phsid)) ->executeOne(); if ($session) { @@ -56,9 +55,9 @@ final class PhabricatorLogoutController ->setURI('/auth/loggedout/'); } - if ($user->getPHID()) { + if ($viewer->getPHID()) { $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Log out of Phabricator?')) ->appendChild(pht('Are you sure you want to log out?')) ->addSubmitButton(pht('Logout')) diff --git a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php index e64096e4be..779196382d 100644 --- a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php +++ b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php @@ -13,13 +13,12 @@ final class PhabricatorMustVerifyEmailController return false; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); - $email = $user->loadPrimaryEmail(); + $email = $viewer->loadPrimaryEmail(); - if ($user->getIsEmailVerified()) { + if ($viewer->getIsEmailVerified()) { return id(new AphrontRedirectResponse())->setURI('/'); } @@ -27,7 +26,7 @@ final class PhabricatorMustVerifyEmailController $sent = null; if ($request->isFormPost()) { - $email->sendVerificationEmail($user); + $email->sendVerificationEmail($viewer); $sent = new PHUIInfoView(); $sent->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $sent->setTitle(pht('Email Sent')); @@ -48,7 +47,7 @@ final class PhabricatorMustVerifyEmailController 'to try sending another one.'); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle(pht('Check Your Email')) ->appendParagraph($must_verify) ->appendParagraph($send_again) diff --git a/src/applications/auth/controller/PhabricatorRefreshCSRFController.php b/src/applications/auth/controller/PhabricatorRefreshCSRFController.php index 19d7aa7eb1..fc1d5cc02d 100644 --- a/src/applications/auth/controller/PhabricatorRefreshCSRFController.php +++ b/src/applications/auth/controller/PhabricatorRefreshCSRFController.php @@ -2,14 +2,13 @@ final class PhabricatorRefreshCSRFController extends PhabricatorAuthController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); return id(new AphrontAjaxResponse()) ->setContent( array( - 'token' => $user->getCSRFToken(), + 'token' => $viewer->getCSRFToken(), )); } From f087bce27da5045c70f5df41129bafa29a87bd6d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 16:57:18 -0700 Subject: [PATCH 054/102] Update Audit for handleRequest Summary: Updates Audit controllers for handleRequest Test Plan: See list of audits, raise concern, clear concern, comment. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13771 --- .../PhabricatorAuditAddCommentController.php | 13 +++++----- .../PhabricatorAuditPreviewController.php | 24 +++++++------------ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/applications/audit/controller/PhabricatorAuditAddCommentController.php b/src/applications/audit/controller/PhabricatorAuditAddCommentController.php index 92c9beb933..b4bb950806 100644 --- a/src/applications/audit/controller/PhabricatorAuditAddCommentController.php +++ b/src/applications/audit/controller/PhabricatorAuditAddCommentController.php @@ -3,9 +3,8 @@ final class PhabricatorAuditAddCommentController extends PhabricatorAuditController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); if (!$request->isFormPost()) { return new Aphront403Response(); @@ -13,7 +12,7 @@ final class PhabricatorAuditAddCommentController $commit_phid = $request->getStr('commit'); $commit = id(new DiffusionCommitQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($commit_phid)) ->needAuditRequests(true) ->executeOne(); @@ -61,7 +60,7 @@ final class PhabricatorAuditAddCommentController } $inlines = PhabricatorAuditInlineComment::loadDraftComments( - $user, + $viewer, $commit->getPHID()); foreach ($inlines as $inline) { $xactions[] = id(new PhabricatorAuditTransaction()) @@ -70,14 +69,14 @@ final class PhabricatorAuditAddCommentController } id(new PhabricatorAuditEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnMissingFields(true) ->applyTransactions($commit, $xactions); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', - $user->getPHID(), + $viewer->getPHID(), 'diffusion-audit-'.$commit->getID()); if ($draft) { $draft->delete(); diff --git a/src/applications/audit/controller/PhabricatorAuditPreviewController.php b/src/applications/audit/controller/PhabricatorAuditPreviewController.php index f27b55ae34..2b0212370c 100644 --- a/src/applications/audit/controller/PhabricatorAuditPreviewController.php +++ b/src/applications/audit/controller/PhabricatorAuditPreviewController.php @@ -3,17 +3,11 @@ final class PhabricatorAuditPreviewController extends PhabricatorAuditController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $commit = id(new PhabricatorRepositoryCommit())->load($this->id); + $commit = id(new PhabricatorRepositoryCommit())->load($id); if (!$commit) { return new Aphront404Response(); } @@ -23,7 +17,7 @@ final class PhabricatorAuditPreviewController $action = $request->getStr('action'); if ($action != PhabricatorAuditActionConstants::COMMENT) { $action_xaction = id(new PhabricatorAuditTransaction()) - ->setAuthorPHID($user->getPHID()) + ->setAuthorPHID($viewer->getPHID()) ->setObjectPHID($commit->getPHID()) ->setTransactionType(PhabricatorAuditActionConstants::ACTION) ->setNewValue($action); @@ -52,7 +46,7 @@ final class PhabricatorAuditPreviewController $content = $request->getStr('content'); if (strlen($content)) { $xactions[] = id(new PhabricatorAuditTransaction()) - ->setAuthorPHID($user->getPHID()) + ->setAuthorPHID($viewer->getPHID()) ->setObjectPHID($commit->getPHID()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( @@ -72,13 +66,13 @@ final class PhabricatorAuditPreviewController $view = id(new PhabricatorAuditTransactionView()) ->setIsPreview(true) - ->setUser($user) + ->setUser($viewer) ->setObjectPHID($commit->getPHID()) ->setTransactions($xactions); id(new PhabricatorDraft()) - ->setAuthorPHID($user->getPHID()) - ->setDraftKey('diffusion-audit-'.$this->id) + ->setAuthorPHID($viewer->getPHID()) + ->setDraftKey('diffusion-audit-'.$id) ->setDraft($content) ->replaceOrDelete(); From 3b0ca7b76995853e28edf30fb63030cfc989a6ff Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Aug 2015 17:06:57 -0700 Subject: [PATCH 055/102] Partially update Maniphest for handleProcess Summary: Starts conversion of Maniphest to handleProcess, chopping up to reduce errors. Test Plan: New Task, Edit Task, Change Priority, Move on workboard, view reports, batch edit tasks Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13773 --- .../controller/ManiphestExportController.php | 36 ++++----- .../controller/ManiphestReportController.php | 40 +++++----- .../ManiphestSubpriorityController.php | 11 ++- .../ManiphestTaskDetailController.php | 41 +++++----- .../ManiphestTaskEditController.php | 76 +++++++++---------- .../ManiphestTaskListController.php | 10 +-- .../ManiphestTransactionPreviewController.php | 27 +++---- .../ManiphestTransactionSaveController.php | 25 +++--- 8 files changed, 115 insertions(+), 151 deletions(-) diff --git a/src/applications/maniphest/controller/ManiphestExportController.php b/src/applications/maniphest/controller/ManiphestExportController.php index 6db04a6e32..88e6849afb 100644 --- a/src/applications/maniphest/controller/ManiphestExportController.php +++ b/src/applications/maniphest/controller/ManiphestExportController.php @@ -2,27 +2,19 @@ final class ManiphestExportController extends ManiphestController { - private $key; - - public function willProcessRequest(array $data) { - $this->key = $data['key']; - return $this; - } - /** * @phutil-external-symbol class PHPExcel * @phutil-external-symbol class PHPExcel_IOFactory * @phutil-external-symbol class PHPExcel_Style_NumberFormat * @phutil-external-symbol class PHPExcel_Cell_DataType */ - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $key = $request->getURIData('key'); $ok = @include_once 'PHPExcel.php'; if (!$ok) { - $dialog = new AphrontDialogView(); - $dialog->setUser($user); + $dialog = $this->newDialog(); $inst1 = pht( 'This system does not have PHPExcel installed. This software '. @@ -54,14 +46,14 @@ final class ManiphestExportController extends ManiphestController { // for that here, since it fatals if we don't have the ZipArchive class. $saved = id(new PhabricatorSavedQueryQuery()) - ->setViewer($user) - ->withQueryKeys(array($this->key)) + ->setViewer($viewer) + ->withQueryKeys(array($key)) ->executeOne(); if (!$saved) { $engine = id(new ManiphestTaskSearchEngine()) - ->setViewer($user); - if ($engine->isBuiltinQuery($this->key)) { - $saved = $engine->buildSavedQueryFromBuiltin($this->key); + ->setViewer($viewer); + if ($engine->isBuiltinQuery($key)) { + $saved = $engine->buildSavedQueryFromBuiltin($key); } if (!$saved) { return new Aphront404Response(); @@ -76,7 +68,7 @@ final class ManiphestExportController extends ManiphestController { if (!$request->isDialogFormPost()) { $dialog = new AphrontDialogView(); - $dialog->setUser($user); + $dialog->setUser($viewer); $dialog->setTitle(pht('Export Tasks to Excel')); $dialog->appendChild( @@ -108,22 +100,22 @@ final class ManiphestExportController extends ManiphestController { $saved->setParameter('limit', PHP_INT_MAX); $engine = id(new ManiphestTaskSearchEngine()) - ->setViewer($user); + ->setViewer($viewer); $query = $engine->buildQueryFromSavedQuery($saved); - $query->setViewer($user); + $query->setViewer($viewer); $tasks = $query->execute(); $all_projects = array_mergev(mpull($tasks, 'getProjectPHIDs')); $all_assigned = mpull($tasks, 'getOwnerPHID'); $handles = id(new PhabricatorHandleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array_merge($all_projects, $all_assigned)) ->execute(); $workbook = new PHPExcel(); - $format->buildWorkbook($workbook, $tasks, $handles, $user); + $format->buildWorkbook($workbook, $tasks, $handles, $viewer); $writer = PHPExcel_IOFactory::createWriter($workbook, 'Excel2007'); ob_start(); diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index bdf446150e..6c2ce9fec3 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -4,13 +4,9 @@ final class ManiphestReportController extends ManiphestController { private $view; - public function willProcessRequest(array $data) { - $this->view = idx($data, 'view'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $this->view = $request->getURIData('view'); if ($request->isFormPost()) { $uri = $request->getRequestURI(); @@ -64,7 +60,7 @@ final class ManiphestReportController extends ManiphestController { public function renderBurn() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $handle = null; @@ -132,7 +128,7 @@ final class ManiphestReportController extends ManiphestController { $day_bucket = phabricator_format_local_time( $row['dateCreated'], - $user, + $viewer, 'Yz'); $day_buckets[$day_bucket] = $row['dateCreated']; if (empty($stats[$day_bucket])) { @@ -166,12 +162,12 @@ final class ManiphestReportController extends ManiphestController { $week_bucket = phabricator_format_local_time( $epoch, - $user, + $viewer, 'YW'); if ($week_bucket != $last_week) { if ($week) { $rows[] = $this->formatBurnRow( - pht('Week of %s', phabricator_date($last_week_epoch, $user)), + pht('Week of %s', phabricator_date($last_week_epoch, $viewer)), $week); $rowc[] = 'week'; } @@ -182,12 +178,12 @@ final class ManiphestReportController extends ManiphestController { $month_bucket = phabricator_format_local_time( $epoch, - $user, + $viewer, 'Ym'); if ($month_bucket != $last_month) { if ($month) { $rows[] = $this->formatBurnRow( - phabricator_format_local_time($last_month_epoch, $user, 'F, Y'), + phabricator_format_local_time($last_month_epoch, $viewer, 'F, Y'), $month); $rowc[] = 'month'; } @@ -196,7 +192,7 @@ final class ManiphestReportController extends ManiphestController { $last_month_epoch = $epoch; } - $rows[] = $this->formatBurnRow(phabricator_date($epoch, $user), $info); + $rows[] = $this->formatBurnRow(phabricator_date($epoch, $viewer), $info); $rowc[] = null; $week['open'] += $info['open']; $week['close'] += $info['close']; @@ -315,10 +311,10 @@ final class ManiphestReportController extends ManiphestController { private function renderReportFilters(array $tokens, $has_window) { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorProjectDatasource()) @@ -390,11 +386,11 @@ final class ManiphestReportController extends ManiphestController { public function renderOpenTasks() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $query = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()); switch ($this->view) { @@ -420,7 +416,7 @@ final class ManiphestReportController extends ManiphestController { $recently_closed = $this->loadRecentlyClosedTasks(); - $date = phabricator_date(time(), $user); + $date = phabricator_date(time(), $viewer); switch ($this->view) { case 'user': @@ -615,7 +611,7 @@ final class ManiphestReportController extends ManiphestController { $cclass[] = 'n'; list($ignored, $window_epoch) = $this->getWindow(); - $edate = phabricator_datetime($window_epoch, $user); + $edate = phabricator_datetime($window_epoch, $viewer); $cname[] = javelin_tag( 'span', array( @@ -726,7 +722,7 @@ final class ManiphestReportController extends ManiphestController { */ private function getWindow() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $window_str = $this->getRequest()->getStr('window', '12 AM 7 days ago'); @@ -736,7 +732,7 @@ final class ManiphestReportController extends ManiphestController { // Do locale-aware parsing so that the user's timezone is assumed for // time windows like "3 PM", rather than assuming the server timezone. - $window_epoch = PhabricatorTime::parseLocalTime($window_str, $user); + $window_epoch = PhabricatorTime::parseLocalTime($window_str, $viewer); if (!$window_epoch) { $error = 'Invalid'; $window_epoch = time() - (60 * 60 * 24 * 7); diff --git a/src/applications/maniphest/controller/ManiphestSubpriorityController.php b/src/applications/maniphest/controller/ManiphestSubpriorityController.php index 44b13955fb..25920212c8 100644 --- a/src/applications/maniphest/controller/ManiphestSubpriorityController.php +++ b/src/applications/maniphest/controller/ManiphestSubpriorityController.php @@ -2,16 +2,15 @@ final class ManiphestSubpriorityController extends ManiphestController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); if (!$request->validateCSRF()) { return new Aphront403Response(); } $task = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($request->getInt('task'))) ->needProjectPHIDs(true) ->requireCapabilities( @@ -26,7 +25,7 @@ final class ManiphestSubpriorityController extends ManiphestController { if ($request->getInt('after')) { $after_task = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($request->getInt('after'))) ->executeOne(); if (!$after_task) { @@ -52,7 +51,7 @@ final class ManiphestSubpriorityController extends ManiphestController { ->setNewValue($sub); $editor = id(new ManiphestTransactionEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 0d4fa74398..8619987201 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -2,27 +2,21 @@ final class ManiphestTaskDetailController extends ManiphestController { - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); $e_title = null; $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); $task = id(new ManiphestTaskQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->needSubscriberPHIDs(true) ->executeOne(); if (!$task) { @@ -33,7 +27,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $parent_task = null; if ($workflow && is_numeric($workflow)) { $parent_task = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($workflow)) ->executeOne(); } @@ -42,7 +36,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $task, PhabricatorCustomField::ROLE_VIEW); $field_list - ->setViewer($user) + ->setViewer($viewer) ->readFieldsFromStorage($task); $e_commit = ManiphestTaskHasCommitEdgeType::EDGECONST; @@ -83,7 +77,7 @@ final class ManiphestTaskDetailController extends ManiphestController { } $phids = array_keys($phids); - $handles = $user->loadHandles($phids); + $handles = $viewer->loadHandles($phids); $info_view = null; if ($parent_task) { @@ -115,7 +109,7 @@ final class ManiphestTaskDetailController extends ManiphestController { } $engine = new PhabricatorMarkupEngine(); - $engine->setViewer($user); + $engine->setViewer($viewer); $engine->setContextObject($task); $engine->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION); @@ -169,12 +163,13 @@ final class ManiphestTaskDetailController extends ManiphestController { } $default_claim = array( - $user->getPHID() => $user->getUsername().' ('.$user->getRealName().')', + $viewer->getPHID() => $viewer->getUsername(). + ' ('.$viewer->getRealName().')', ); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', - $user->getPHID(), + $viewer->getPHID(), $task->getPHID()); if ($draft) { $draft_text = $draft->getDraft(); @@ -188,7 +183,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $comment_form = new AphrontFormView(); $comment_form - ->setUser($user) + ->setUser($viewer) ->setWorkflow(true) ->setAction('/maniphest/transaction/save/') ->setEncType('multipart/form-data') @@ -249,12 +244,12 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setControlStyle('display: none')) ->appendChild( id(new PhabricatorRemarkupControl()) - ->setUser($user) + ->setUser($viewer) ->setLabel(pht('Comments')) ->setName('comments') ->setValue($draft_text) ->setID('transaction-comments') - ->setUser($user)) + ->setUser($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Submit'))); @@ -288,7 +283,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ); // TODO: Initializing these behaviors for logged out users fatals things. - if ($user->isLoggedIn()) { + if ($viewer->isLoggedIn()) { Javelin::initBehavior('maniphest-transaction-controls', array( 'select' => 'transaction-action', 'controlMap' => $control_map, @@ -330,11 +325,11 @@ final class ManiphestTaskDetailController extends ManiphestController { $task, $field_list, $edges, $actions, $handles); $description = $this->buildDescriptionView($task, $engine); - if (!$user->isLoggedIn()) { + if (!$viewer->isLoggedIn()) { // TODO: Eventually, everything should run through this. For now, we're // only using it to get a consistent "Login to Comment" button. $comment_box = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setRequestURI($request->getRequestURI()); $preview_panel = null; } else { diff --git a/src/applications/maniphest/controller/ManiphestTaskEditController.php b/src/applications/maniphest/controller/ManiphestTaskEditController.php index 72b75c876f..703bb61768 100644 --- a/src/applications/maniphest/controller/ManiphestTaskEditController.php +++ b/src/applications/maniphest/controller/ManiphestTaskEditController.php @@ -2,15 +2,9 @@ final class ManiphestTaskEditController extends ManiphestController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); $response_type = $request->getStr('responseType', 'task'); $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER); @@ -26,22 +20,22 @@ final class ManiphestTaskEditController extends ManiphestController { $can_edit_status = $this->hasApplicationCapability( ManiphestEditStatusCapability::CAPABILITY); $can_create_projects = PhabricatorPolicyFilter::hasCapability( - $user, + $viewer, PhabricatorApplication::getByClass('PhabricatorProjectApplication'), ProjectCreateProjectsCapability::CAPABILITY); $parent_task = null; $template_id = null; - if ($this->id) { + if ($id) { $task = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needSubscriberPHIDs(true) ->needProjectPHIDs(true) ->executeOne(); @@ -49,7 +43,7 @@ final class ManiphestTaskEditController extends ManiphestController { return new Aphront404Response(); } } else { - $task = ManiphestTask::initializeNewTask($user); + $task = ManiphestTask::initializeNewTask($viewer); // We currently do not allow you to set the task status when creating // a new task, although now that statuses are custom it might make @@ -82,7 +76,7 @@ final class ManiphestTaskEditController extends ManiphestController { } $default_projects = id(new PhabricatorObjectQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withNames($tokens) ->execute(); $default_projects = mpull($default_projects, 'getPHID'); @@ -109,12 +103,12 @@ final class ManiphestTaskEditController extends ManiphestController { $assign = $request->getStr('assign'); if (strlen($assign)) { $assign_user = id(new PhabricatorPeopleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withUsernames(array($assign)) ->executeOne(); if (!$assign_user) { $assign_user = id(new PhabricatorPeopleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($assign)) ->executeOne(); } @@ -132,7 +126,7 @@ final class ManiphestTaskEditController extends ManiphestController { $parent_id = $request->getInt('parent'); if (strlen($parent_id)) { $parent_task = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($parent_id)) ->executeOne(); if (!$parent_task) { @@ -150,7 +144,7 @@ final class ManiphestTaskEditController extends ManiphestController { $field_list = PhabricatorCustomField::getObjectFields( $task, PhabricatorCustomField::ROLE_EDIT); - $field_list->setViewer($user); + $field_list->setViewer($viewer); $field_list->readFieldsFromStorage($task); $aux_fields = $field_list->getFields(); @@ -250,7 +244,7 @@ final class ManiphestTaskEditController extends ManiphestController { // allow for putting a task in a project column at creation -only- if (!$task->getID() && $column_phid && $projects) { $column = id(new PhabricatorProjectColumnQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withProjectPHIDs($projects) ->withPHIDs(array($column_phid)) ->executeOne(); @@ -329,7 +323,7 @@ final class ManiphestTaskEditController extends ManiphestController { 'new' => $is_new, 'transactions' => $transactions, )); - $event->setUser($user); + $event->setUser($viewer); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); @@ -337,7 +331,7 @@ final class ManiphestTaskEditController extends ManiphestController { $transactions = $event->getValue('transactions'); $editor = id(new ManiphestTransactionEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->applyTransactions($task, $transactions); @@ -349,7 +343,7 @@ final class ManiphestTaskEditController extends ManiphestController { 'new' => $is_new, 'transactions' => $transactions, )); - $event->setUser($user); + $event->setUser($viewer); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); } @@ -372,19 +366,19 @@ final class ManiphestTaskEditController extends ManiphestController { $owner = null; if ($task->getOwnerPHID()) { $owner = id(new PhabricatorHandleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($task->getOwnerPHID())) ->executeOne(); } $tasks = id(new ProjectBoardTaskCard()) - ->setViewer($user) + ->setViewer($viewer) ->setTask($task) ->setOwner($owner) ->setCanEdit(true) ->getItem(); $column = id(new PhabricatorProjectColumnQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($request->getStr('columnPHID'))) ->executeOne(); if (!$column) { @@ -403,13 +397,13 @@ final class ManiphestTaskEditController extends ManiphestController { } $positions = id(new PhabricatorProjectColumnPositionQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withColumns(array($column)) ->execute(); $task_phids = mpull($positions, 'getObjectPHID'); $column_tasks = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs($task_phids) ->execute(); @@ -461,11 +455,11 @@ final class ManiphestTaskEditController extends ManiphestController { } else { if (!$task->getID()) { $task->attachSubscriberPHIDs(array( - $user->getPHID(), + $viewer->getPHID(), )); if ($template_id) { $template_task = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($template_id)) ->needSubscriberPHIDs(true) ->needProjectPHIDs(true) @@ -473,7 +467,7 @@ final class ManiphestTaskEditController extends ManiphestController { if ($template_task) { $cc_phids = array_unique(array_merge( $template_task->getSubscriberPHIDs(), - array($user->getPHID()))); + array($viewer->getPHID()))); $task->attachSubscriberPHIDs($cc_phids); $task->attachProjectPHIDs($template_task->getProjectPHIDs()); $task->setOwnerPHID($template_task->getOwnerPHID()); @@ -499,7 +493,7 @@ final class ManiphestTaskEditController extends ManiphestController { if ($fields) { id(new PhabricatorCustomFieldList($fields)) - ->setViewer($user) + ->setViewer($viewer) ->readFieldsFromStorage($template_task); foreach ($fields as $key => $field) { @@ -563,7 +557,7 @@ final class ManiphestTaskEditController extends ManiphestController { $form = new AphrontFormView(); $form - ->setUser($user) + ->setUser($viewer) ->addHiddenInput('template', $template_id) ->addHiddenInput('responseType', $response_type) ->addHiddenInput('order', $order) @@ -575,7 +569,7 @@ final class ManiphestTaskEditController extends ManiphestController { ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Parent Task')) - ->setValue($user->renderHandle($parent_task->getPHID()))) + ->setValue($viewer->renderHandle($parent_task->getPHID()))) ->addHiddenInput('parent', $parent_task->getID()); } @@ -607,7 +601,7 @@ final class ManiphestTaskEditController extends ManiphestController { } $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setObject($task) ->execute(); @@ -617,7 +611,7 @@ final class ManiphestTaskEditController extends ManiphestController { ->setLabel(pht('Assigned To')) ->setName('assigned_to') ->setValue($assigned_value) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorPeopleDatasource()) ->setLimit(1)); } @@ -628,7 +622,7 @@ final class ManiphestTaskEditController extends ManiphestController { ->setLabel(pht('CC')) ->setName('cc') ->setValue($cc_value) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())); if ($can_edit_priority) { @@ -645,7 +639,7 @@ final class ManiphestTaskEditController extends ManiphestController { $form ->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($task) ->setPolicies($policies) @@ -653,7 +647,7 @@ final class ManiphestTaskEditController extends ManiphestController { ->setName('viewPolicy')) ->appendChild( id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($task) ->setPolicies($policies) @@ -696,14 +690,14 @@ final class ManiphestTaskEditController extends ManiphestController { ->setName('description') ->setID('description-textarea') ->setValue($task->getDescription()) - ->setUser($user); + ->setUser($viewer); $form ->appendChild($description_control); if ($request->isAjax()) { $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle($header_name) ->appendChild( diff --git a/src/applications/maniphest/controller/ManiphestTaskListController.php b/src/applications/maniphest/controller/ManiphestTaskListController.php index 9d2fa33716..ddb2827e83 100644 --- a/src/applications/maniphest/controller/ManiphestTaskListController.php +++ b/src/applications/maniphest/controller/ManiphestTaskListController.php @@ -3,19 +3,15 @@ final class ManiphestTaskListController extends ManiphestController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine( id(new ManiphestTaskSearchEngine()) ->setShowBatchControls(true)) diff --git a/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php b/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php index 0d7988cd64..65756ca8e4 100644 --- a/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php +++ b/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php @@ -2,29 +2,22 @@ final class ManiphestTransactionPreviewController extends ManiphestController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); $comments = $request->getStr('comments'); $task = id(new ManiphestTaskQuery()) - ->setViewer($user) - ->withIDs(array($this->id)) + ->setViewer($viewer) + ->withIDs(array($id)) ->executeOne(); if (!$task) { return new Aphront404Response(); } id(new PhabricatorDraft()) - ->setAuthorPHID($user->getPHID()) + ->setAuthorPHID($viewer->getPHID()) ->setDraftKey($task->getPHID()) ->setDraft($comments) ->replaceOrDelete(); @@ -32,7 +25,7 @@ final class ManiphestTransactionPreviewController extends ManiphestController { $action = $request->getStr('action'); $transaction = new ManiphestTransaction(); - $transaction->setAuthorPHID($user->getPHID()); + $transaction->setAuthorPHID($viewer->getPHID()); $transaction->setTransactionType($action); // This should really be split into a separate transaction, but it should @@ -104,7 +97,7 @@ final class ManiphestTransactionPreviewController extends ManiphestController { $transaction->setNewValue($value); break; } - $phids[] = $user->getPHID(); + $phids[] = $viewer->getPHID(); $handles = $this->loadViewerHandles($phids); @@ -112,7 +105,7 @@ final class ManiphestTransactionPreviewController extends ManiphestController { $transactions[] = $transaction; $engine = new PhabricatorMarkupEngine(); - $engine->setViewer($user); + $engine->setViewer($viewer); $engine->setContextObject($task); if ($transaction->hasComment()) { $engine->addObject( @@ -124,7 +117,7 @@ final class ManiphestTransactionPreviewController extends ManiphestController { $transaction->setHandles($handles); $view = id(new PhabricatorApplicationTransactionView()) - ->setUser($user) + ->setUser($viewer) ->setTransactions($transactions) ->setIsPreview(true); diff --git a/src/applications/maniphest/controller/ManiphestTransactionSaveController.php b/src/applications/maniphest/controller/ManiphestTransactionSaveController.php index f7693d9416..b060b18cab 100644 --- a/src/applications/maniphest/controller/ManiphestTransactionSaveController.php +++ b/src/applications/maniphest/controller/ManiphestTransactionSaveController.php @@ -2,12 +2,11 @@ final class ManiphestTransactionSaveController extends ManiphestController { - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $task = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($request->getStr('taskID'))) ->needSubscriberPHIDs(true) ->needProjectPHIDs(true) @@ -102,7 +101,7 @@ final class ManiphestTransactionSaveController extends ManiphestController { // this task. $assign = new ManiphestTransaction(); $assign->setTransactionType(ManiphestTransaction::TYPE_OWNER); - $assign->setNewValue($user->getPHID()); + $assign->setNewValue($viewer->getPHID()); $transactions[] = $assign; $implicitly_claimed = true; @@ -114,10 +113,10 @@ final class ManiphestTransactionSaveController extends ManiphestController { $user_owns_task = true; } else { if ($action == ManiphestTransaction::TYPE_OWNER) { - if ($transaction->getNewValue() == $user->getPHID()) { + if ($transaction->getNewValue() == $viewer->getPHID()) { $user_owns_task = true; } - } else if ($task->getOwnerPHID() == $user->getPHID()) { + } else if ($task->getOwnerPHID() == $viewer->getPHID()) { $user_owns_task = true; } } @@ -125,8 +124,8 @@ final class ManiphestTransactionSaveController extends ManiphestController { if (!$user_owns_task) { // If we aren't making the user the new task owner and they aren't the // existing task owner, add them to CC unless they're aleady CC'd. - if (!in_array($user->getPHID(), $task->getSubscriberPHIDs())) { - $implicit_ccs[] = $user->getPHID(); + if (!in_array($viewer->getPHID(), $task->getSubscriberPHIDs())) { + $implicit_ccs[] = $viewer->getPHID(); } } @@ -164,7 +163,7 @@ final class ManiphestTransactionSaveController extends ManiphestController { 'new' => false, 'transactions' => $transactions, )); - $event->setUser($user); + $event->setUser($viewer); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); @@ -172,7 +171,7 @@ final class ManiphestTransactionSaveController extends ManiphestController { $transactions = $event->getValue('transactions'); $editor = id(new ManiphestTransactionEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect($request->isContinueRequest()); @@ -187,7 +186,7 @@ final class ManiphestTransactionSaveController extends ManiphestController { $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', - $user->getPHID(), + $viewer->getPHID(), $task->getPHID()); if ($draft) { $draft->delete(); @@ -200,7 +199,7 @@ final class ManiphestTransactionSaveController extends ManiphestController { 'new' => false, 'transactions' => $transactions, )); - $event->setUser($user); + $event->setUser($viewer); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); From f0269d696cfe138e4b98055f484132acb7d6ec86 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 3 Aug 2015 07:01:42 -0700 Subject: [PATCH 056/102] Calendar list objects that are ghost events should link to a ghost event, not the parent Summary: Fixes T9034, Calendar list objects that are ghost events should link to a ghost event, not the parent. Test Plan: Open All Events in Calendar, make sure ghost objects open the ghost instance, not the parent instance. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: epriestley, Korvin Maniphest Tasks: T9034 Differential Revision: https://secure.phabricator.com/D13764 --- .../query/PhabricatorCalendarEventSearchEngine.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 4d2193f2bb..8614e68f56 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -287,10 +287,21 @@ final class PhabricatorCalendarEventSearchEngine $event->getDuration()); } + if ($event->getIsGhostEvent()) { + $title_text = $event->getMonogram() + .' (' + .$event->getSequenceIndex() + .'): ' + .$event->getName(); + } else { + $title_text = $event->getMonogram().': '.$event->getName(); + } + $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($event) - ->setHeader($viewer->renderHandle($event->getPHID())->render()) + ->setHeader($title_text) + ->setHref($event->getURI()) ->addAttribute($event_date_info) ->addAttribute($attendees) ->addIcon('none', $duration); From b4b5d60f77fbcac420d773f6fa9bef1c6eebdfa9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Aug 2015 08:01:43 -0700 Subject: [PATCH 057/102] Fix 'key'/'type' swap in email reset / one-time-login controller Summary: Fixes T9046. These got swapped around during refactoring. Test Plan: - Used `bin/auth recover` prior to patch (failed). - Used `bin/auth recover` after patch (worked). Reviewers: joshuaspence, chad Reviewed By: chad Subscribers: epriestley Maniphest Tasks: T9046 Differential Revision: https://secure.phabricator.com/D13778 --- .../auth/controller/PhabricatorAuthOneTimeLoginController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index 91f3d6a984..6dfa49d860 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -10,8 +10,8 @@ final class PhabricatorAuthOneTimeLoginController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $id = $request->getURIData('id'); - $link_type = $request->getURIData('key'); - $key = $request->getURIData('type'); + $link_type = $request->getURIData('type'); + $key = $request->getURIData('key'); $email_id = $request->getURIData('emailID'); if ($request->getUser()->isLoggedIn()) { From 56dd5211f0d02dd373aac703ef62ade550e6fba0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 17 Jul 2015 10:27:38 -0700 Subject: [PATCH 058/102] Make Herald action modularization more aggressive Summary: Ref T8726. Herald actions are technically sort-of modular already, but make them more aggressively modular similar to `HeraldField`. I plan to obsolete and replace `HeraldCustomAction`. Test Plan: Saw actions in nice groups; created and ran a "Do Nothing" action. Transcripts are a bit rough for now. Reviewers: btrahan Reviewed By: btrahan Subscribers: joshuaspence, epriestley Maniphest Tasks: T8726 Differential Revision: https://secure.phabricator.com/D13646 --- resources/celerity/map.php | 22 ++-- src/__phutil_library_map__.php | 18 ++- .../herald/HeraldDifferentialDiffAdapter.php | 1 - .../HeraldDifferentialRevisionAdapter.php | 2 - .../diffusion/herald/HeraldCommitAdapter.php | 2 - .../herald/HeraldPreCommitAdapter.php | 2 - .../herald/action/HeraldAction.php | 118 ++++++++++++++++++ .../herald/action/HeraldActionGroup.php | 28 +++++ .../herald/action/HeraldDoNothingAction.php | 32 +++++ .../herald/action/HeraldNotifyActionGroup.php | 15 +++ .../action/HeraldPreventActionGroup.php | 15 +++ .../action/HeraldSupportActionGroup.php | 15 +++ .../action/HeraldUtilityActionGroup.php | 15 +++ .../herald/adapter/HeraldAdapter.php | 116 ++++++++++++++--- .../controller/HeraldRuleController.php | 87 ++++++++----- .../controller/HeraldTranscriptController.php | 8 +- .../herald/field/HeraldFieldGroup.php | 14 +-- src/applications/herald/group/HeraldGroup.php | 15 +++ .../herald/HeraldManiphestTaskAdapter.php | 2 - .../pholio/herald/HeraldPholioMockAdapter.php | 2 - .../herald/PhrictionDocumentHeraldAdapter.php | 2 - .../js/application/herald/HeraldRuleEditor.js | 40 +++--- 22 files changed, 467 insertions(+), 104 deletions(-) create mode 100644 src/applications/herald/action/HeraldAction.php create mode 100644 src/applications/herald/action/HeraldActionGroup.php create mode 100644 src/applications/herald/action/HeraldDoNothingAction.php create mode 100644 src/applications/herald/action/HeraldNotifyActionGroup.php create mode 100644 src/applications/herald/action/HeraldPreventActionGroup.php create mode 100644 src/applications/herald/action/HeraldSupportActionGroup.php create mode 100644 src/applications/herald/action/HeraldUtilityActionGroup.php create mode 100644 src/applications/herald/group/HeraldGroup.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3970a41d57..4d3fa6944b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -382,7 +382,7 @@ return array( 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', - 'rsrc/js/application/herald/HeraldRuleEditor.js' => '52684226', + 'rsrc/js/application/herald/HeraldRuleEditor.js' => '91a6031b', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', @@ -538,7 +538,7 @@ return array( 'global-drag-and-drop-css' => '697324ad', 'harbormaster-css' => '49d64eb4', 'herald-css' => '826075fa', - 'herald-rule-editor' => '52684226', + 'herald-rule-editor' => '91a6031b', 'herald-test-css' => '778b008e', 'inline-comment-summary-css' => '51efda3a', 'javelin-aphlict' => '5359e785', @@ -1172,15 +1172,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - 52684226 => array( - 'multirow-row-manager', - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-json', - 'phabricator-prefab', - ), '5359e785' => array( 'javelin-install', 'javelin-util', @@ -1539,6 +1530,15 @@ return array( 'javelin-dom', 'javelin-stratcom', ), + '91a6031b' => array( + 'multirow-row-manager', + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-json', + 'phabricator-prefab', + ), '93d0c9e3' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d0c6f4af92..1644d3af2c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1007,6 +1007,8 @@ phutil_register_library_map(array( 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', 'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php', + 'HeraldAction' => 'applications/herald/action/HeraldAction.php', + 'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php', 'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php', 'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php', 'HeraldAlwaysField' => 'applications/herald/field/HeraldAlwaysField.php', @@ -1024,6 +1026,7 @@ phutil_register_library_map(array( 'HeraldDifferentialDiffAdapter' => 'applications/differential/herald/HeraldDifferentialDiffAdapter.php', 'HeraldDifferentialRevisionAdapter' => 'applications/differential/herald/HeraldDifferentialRevisionAdapter.php', 'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php', + 'HeraldDoNothingAction' => 'applications/herald/action/HeraldDoNothingAction.php', 'HeraldEditFieldGroup' => 'applications/herald/field/HeraldEditFieldGroup.php', 'HeraldEffect' => 'applications/herald/engine/HeraldEffect.php', 'HeraldEmptyFieldValue' => 'applications/herald/value/HeraldEmptyFieldValue.php', @@ -1032,17 +1035,20 @@ phutil_register_library_map(array( 'HeraldFieldGroup' => 'applications/herald/field/HeraldFieldGroup.php', 'HeraldFieldTestCase' => 'applications/herald/field/__tests__/HeraldFieldTestCase.php', 'HeraldFieldValue' => 'applications/herald/value/HeraldFieldValue.php', + 'HeraldGroup' => 'applications/herald/group/HeraldGroup.php', 'HeraldInvalidActionException' => 'applications/herald/engine/exception/HeraldInvalidActionException.php', 'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php', 'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php', 'HeraldManiphestTaskAdapter' => 'applications/maniphest/herald/HeraldManiphestTaskAdapter.php', 'HeraldNewController' => 'applications/herald/controller/HeraldNewController.php', 'HeraldNewObjectField' => 'applications/herald/field/HeraldNewObjectField.php', + 'HeraldNotifyActionGroup' => 'applications/herald/action/HeraldNotifyActionGroup.php', 'HeraldObjectTranscript' => 'applications/herald/storage/transcript/HeraldObjectTranscript.php', 'HeraldPholioMockAdapter' => 'applications/pholio/herald/HeraldPholioMockAdapter.php', 'HeraldPreCommitAdapter' => 'applications/diffusion/herald/HeraldPreCommitAdapter.php', 'HeraldPreCommitContentAdapter' => 'applications/diffusion/herald/HeraldPreCommitContentAdapter.php', 'HeraldPreCommitRefAdapter' => 'applications/diffusion/herald/HeraldPreCommitRefAdapter.php', + 'HeraldPreventActionGroup' => 'applications/herald/action/HeraldPreventActionGroup.php', 'HeraldProjectsField' => 'applications/project/herald/HeraldProjectsField.php', 'HeraldRecursiveConditionsException' => 'applications/herald/engine/exception/HeraldRecursiveConditionsException.php', 'HeraldRelatedFieldGroup' => 'applications/herald/field/HeraldRelatedFieldGroup.php', @@ -1065,6 +1071,7 @@ phutil_register_library_map(array( 'HeraldSelectFieldValue' => 'applications/herald/value/HeraldSelectFieldValue.php', 'HeraldSpaceField' => 'applications/spaces/herald/HeraldSpaceField.php', 'HeraldSubscribersField' => 'applications/subscriptions/herald/HeraldSubscribersField.php', + 'HeraldSupportActionGroup' => 'applications/herald/action/HeraldSupportActionGroup.php', 'HeraldSupportFieldGroup' => 'applications/herald/field/HeraldSupportFieldGroup.php', 'HeraldTestConsoleController' => 'applications/herald/controller/HeraldTestConsoleController.php', 'HeraldTextFieldValue' => 'applications/herald/value/HeraldTextFieldValue.php', @@ -1077,6 +1084,7 @@ phutil_register_library_map(array( 'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php', 'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php', 'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php', + 'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php', 'Javelin' => 'infrastructure/javelin/Javelin.php', 'JavelinReactorUIExample' => 'applications/uiexample/examples/JavelinReactorUIExample.php', 'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php', @@ -4684,6 +4692,8 @@ phutil_register_library_map(array( 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWorker' => 'PhabricatorWorker', + 'HeraldAction' => 'Phobject', + 'HeraldActionGroup' => 'HeraldGroup', 'HeraldActionRecord' => 'HeraldDAO', 'HeraldAdapter' => 'Phobject', 'HeraldAlwaysField' => 'HeraldField', @@ -4701,25 +4711,29 @@ phutil_register_library_map(array( 'HeraldDifferentialDiffAdapter' => 'HeraldDifferentialAdapter', 'HeraldDifferentialRevisionAdapter' => 'HeraldDifferentialAdapter', 'HeraldDisableController' => 'HeraldController', + 'HeraldDoNothingAction' => 'HeraldAction', 'HeraldEditFieldGroup' => 'HeraldFieldGroup', 'HeraldEffect' => 'Phobject', 'HeraldEmptyFieldValue' => 'HeraldFieldValue', 'HeraldEngine' => 'Phobject', 'HeraldField' => 'Phobject', - 'HeraldFieldGroup' => 'Phobject', + 'HeraldFieldGroup' => 'HeraldGroup', 'HeraldFieldTestCase' => 'PhutilTestCase', 'HeraldFieldValue' => 'Phobject', + 'HeraldGroup' => 'Phobject', 'HeraldInvalidActionException' => 'Exception', 'HeraldInvalidConditionException' => 'Exception', 'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability', 'HeraldManiphestTaskAdapter' => 'HeraldAdapter', 'HeraldNewController' => 'HeraldController', 'HeraldNewObjectField' => 'HeraldField', + 'HeraldNotifyActionGroup' => 'HeraldActionGroup', 'HeraldObjectTranscript' => 'Phobject', 'HeraldPholioMockAdapter' => 'HeraldAdapter', 'HeraldPreCommitAdapter' => 'HeraldAdapter', 'HeraldPreCommitContentAdapter' => 'HeraldPreCommitAdapter', 'HeraldPreCommitRefAdapter' => 'HeraldPreCommitAdapter', + 'HeraldPreventActionGroup' => 'HeraldActionGroup', 'HeraldProjectsField' => 'HeraldField', 'HeraldRecursiveConditionsException' => 'Exception', 'HeraldRelatedFieldGroup' => 'HeraldFieldGroup', @@ -4748,6 +4762,7 @@ phutil_register_library_map(array( 'HeraldSelectFieldValue' => 'HeraldFieldValue', 'HeraldSpaceField' => 'HeraldField', 'HeraldSubscribersField' => 'HeraldField', + 'HeraldSupportActionGroup' => 'HeraldActionGroup', 'HeraldSupportFieldGroup' => 'HeraldFieldGroup', 'HeraldTestConsoleController' => 'HeraldController', 'HeraldTextFieldValue' => 'HeraldFieldValue', @@ -4764,6 +4779,7 @@ phutil_register_library_map(array( 'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HeraldTranscriptTestCase' => 'PhabricatorTestCase', + 'HeraldUtilityActionGroup' => 'HeraldActionGroup', 'Javelin' => 'Phobject', 'JavelinReactorUIExample' => 'PhabricatorUIExample', 'JavelinUIExample' => 'PhabricatorUIExample', diff --git a/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php b/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php index 225ba5b73d..fe5ff3f439 100644 --- a/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php @@ -75,7 +75,6 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { return array_merge( array( self::ACTION_BLOCK, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); } diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index d3264860d6..10ae15c1e6 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -169,7 +169,6 @@ final class HeraldDifferentialRevisionAdapter self::ACTION_ADD_BLOCKING_REVIEWERS, self::ACTION_APPLY_BUILD_PLANS, self::ACTION_REQUIRE_SIGNATURE, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: @@ -181,7 +180,6 @@ final class HeraldDifferentialRevisionAdapter self::ACTION_FLAG, self::ACTION_ADD_REVIEWERS, self::ACTION_ADD_BLOCKING_REVIEWERS, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); } diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 0210185ed9..087dafd844 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -94,7 +94,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { self::ACTION_EMAIL, self::ACTION_AUDIT, self::ACTION_APPLY_BUILD_PLANS, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: @@ -105,7 +104,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_AUDIT, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); } diff --git a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php index 07f1f0d2c7..7e5e117d85 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php @@ -81,14 +81,12 @@ abstract class HeraldPreCommitAdapter extends HeraldAdapter { array( self::ACTION_BLOCK, self::ACTION_EMAIL, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( self::ACTION_EMAIL, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); } diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php new file mode 100644 index 0000000000..f78d30cf56 --- /dev/null +++ b/src/applications/herald/action/HeraldAction.php @@ -0,0 +1,118 @@ +getActionConstant() => $this); + } + + protected function getDatasource() { + throw new PhutilMethodNotImplementedException(); + } + + protected function getDatasourceValueMap() { + return null; + } + + public function getHeraldActionStandardType() { + throw new PhutilMethodNotImplementedException(); + } + + public function getHeraldActionValueType() { + switch ($this->getHeraldActionStandardType()) { + case self::STANDARD_NONE: + return new HeraldEmptyFieldValue(); + case self::STANDARD_PHID_LIST: + $tokenizer = id(new HeraldTokenizerFieldValue()) + ->setKey($this->getHeraldFieldName()) + ->setDatasource($this->getDatasource()); + + $value_map = $this->getDatasourceValueMap(); + if ($value_map !== null) { + $tokenizer->setValueMap($value_map); + } + + return $tokenizer; + } + + throw new PhutilMethodNotImplementedException(); + } + + public function willSaveActionValue($value) { + return $value; + } + + final public function setAdapter(HeraldAdapter $adapter) { + $this->adapter = $adapter; + return $this; + } + + final public function getAdapter() { + return $this->adapter; + } + + final public function getActionConstant() { + $class = new ReflectionClass($this); + + $const = $class->getConstant('ACTIONCONST'); + if ($const === false) { + throw new Exception( + pht( + '"%s" class "%s" must define a "%s" property.', + __CLASS__, + get_class($this), + 'ACTIONCONST')); + } + + $limit = self::getActionConstantByteLimit(); + if (!is_string($const) || (strlen($const) > $limit)) { + throw new Exception( + pht( + '"%s" class "%s" has an invalid "%s" property. Action constants '. + 'must be strings and no more than %s bytes in length.', + __CLASS__, + get_class($this), + 'ACTIONCONST', + new PhutilNumber($limit))); + } + + return $const; + } + + final public static function getActionConstantByteLimit() { + return 64; + } + + final public static function getAllActions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getActionConstant') + ->execute(); + } + + protected function logEffect($type, $data = null) { + return; + } + + final public function getApplyTranscript(HeraldEffect $effect) { + $context = 'v2/'.phutil_json_encode($this->applyLog); + $this->applyLog = array(); + return new HeraldApplyTranscript($effect, true, $context); + } + +} diff --git a/src/applications/herald/action/HeraldActionGroup.php b/src/applications/herald/action/HeraldActionGroup.php new file mode 100644 index 0000000000..a087909609 --- /dev/null +++ b/src/applications/herald/action/HeraldActionGroup.php @@ -0,0 +1,28 @@ +getConstant('ACTIONGROUPKEY'); + if ($const === false) { + throw new Exception( + pht( + '"%s" class "%s" must define a "%s" property.', + __CLASS__, + get_class($this), + 'ACTIONGROUPKEY')); + } + + return $const; + } + + final public static function getAllActionGroups() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getGroupKey') + ->setSortMethod('getSortKey') + ->execute(); + } +} diff --git a/src/applications/herald/action/HeraldDoNothingAction.php b/src/applications/herald/action/HeraldDoNothingAction.php new file mode 100644 index 0000000000..58b9f98cf2 --- /dev/null +++ b/src/applications/herald/action/HeraldDoNothingAction.php @@ -0,0 +1,32 @@ +logEffect($effect, self::DO_NOTHING); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + +} diff --git a/src/applications/herald/action/HeraldNotifyActionGroup.php b/src/applications/herald/action/HeraldNotifyActionGroup.php new file mode 100644 index 0000000000..df8c395341 --- /dev/null +++ b/src/applications/herald/action/HeraldNotifyActionGroup.php @@ -0,0 +1,15 @@ +emailPHIDs); @@ -615,6 +615,78 @@ abstract class HeraldAdapter extends Phobject { /* -( Actions )------------------------------------------------------------ */ + private function getActionImplementationMap() { + if ($this->actionMap === null) { + // We can't use PhutilClassMapQuery here because action expansion + // depends on the adapter and object. + + $object = $this->getObject(); + + $map = array(); + $all = HeraldAction::getAllActions(); + foreach ($all as $key => $action) { + $action = id(clone $action)->setAdapter($this); + + if (!$action->supportsObject($object)) { + continue; + } + + $subactions = $action->getActionsForObject($object); + foreach ($subactions as $subkey => $subaction) { + if (isset($map[$subkey])) { + throw new Exception( + pht( + 'Two HeraldActions (of classes "%s" and "%s") have the same '. + 'action key ("%s") after expansion for an object of class '. + '"%s" inside adapter "%s". Each action must have a unique '. + 'action key.', + get_class($subaction), + get_class($map[$subkey]), + $subkey, + get_class($object), + get_class($this))); + } + + $subaction = id(clone $subaction)->setAdapter($this); + + $map[$subkey] = $subaction; + } + } + $this->actionMap = $map; + } + + return $this->actionMap; + } + + private function getActionsForRuleType($rule_type) { + $actions = $this->getActionImplementationMap(); + + foreach ($actions as $key => $action) { + if (!$action->supportsRuleType($rule_type)) { + unset($actions[$key]); + } + } + + return $actions; + } + + private function getActionImplementation($key) { + return idx($this->getActionImplementationMap(), $key); + } + + public function getActionKeys() { + return array_keys($this->getActionImplementationMap()); + } + + public function getActionGroupKey($action_key) { + $action = $this->getActionImplementation($action_key); + if (!$action) { + return null; + } + + return $action->getActionGroupKey(); + } + public function getCustomActionsForRuleType($rule_type) { $results = array(); foreach ($this->getCustomActions() as $custom_action) { @@ -640,6 +712,10 @@ abstract class HeraldAdapter extends Phobject { } } + foreach ($this->getActionsForRuleType($rule_type) as $key => $action) { + $actions[] = $key; + } + return $actions; } @@ -648,7 +724,6 @@ abstract class HeraldAdapter extends Phobject { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: $standard = array( - self::ACTION_NOTHING => pht('Do nothing'), self::ACTION_ADD_CC => pht('Add Subscribers'), self::ACTION_REMOVE_CC => pht('Remove Subscribers'), self::ACTION_EMAIL => pht('Send an email to'), @@ -666,7 +741,6 @@ abstract class HeraldAdapter extends Phobject { break; case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: $standard = array( - self::ACTION_NOTHING => pht('Do nothing'), self::ACTION_ADD_CC => pht('Add me as a subscriber'), self::ACTION_REMOVE_CC => pht('Remove me as a subscriber'), self::ACTION_EMAIL => pht('Send me an email'), @@ -685,6 +759,10 @@ abstract class HeraldAdapter extends Phobject { $custom_actions = $this->getCustomActionsForRuleType($rule_type); $standard += mpull($custom_actions, 'getActionName', 'getActionKey'); + foreach ($this->getActionsForRuleType($rule_type) as $key => $action) { + $standard[$key] = $action->getHeraldActionName(); + } + return $standard; } @@ -692,6 +770,14 @@ abstract class HeraldAdapter extends Phobject { HeraldRule $rule, HeraldActionRecord $action) { + $impl = $this->getActionImplementation($action->getAction()); + if ($impl) { + $target = $action->getTarget(); + $target = $impl->willSaveActionValue($target); + $action->setTarget($target); + return; + } + $target = $action->getTarget(); if (is_array($target)) { $target = array_keys($target); @@ -720,7 +806,6 @@ abstract class HeraldAdapter extends Phobject { } break; case self::ACTION_BLOCK: - case self::ACTION_NOTHING: break; default: throw new HeraldInvalidActionException( @@ -744,6 +829,11 @@ abstract class HeraldAdapter extends Phobject { } public function getValueTypeForAction($action, $rule_type) { + $impl = $this->getActionImplementation($action); + if ($impl) { + return $impl->getHeraldActionValueType(); + } + $is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); if ($is_personal) { @@ -751,7 +841,6 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_ADD_CC: case self::ACTION_REMOVE_CC: case self::ACTION_EMAIL: - case self::ACTION_NOTHING: case self::ACTION_AUDIT: case self::ACTION_ASSIGN_TASK: case self::ACTION_ADD_REVIEWERS: @@ -771,8 +860,6 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_EMAIL: return $this->buildTokenizerFieldValue( new PhabricatorMetaMTAMailableDatasource()); - case self::ACTION_NOTHING: - return new HeraldEmptyFieldValue(); case self::ACTION_ADD_PROJECTS: case self::ACTION_REMOVE_PROJECTS: return $this->buildTokenizerFieldValue( @@ -1108,6 +1195,12 @@ abstract class HeraldAdapter extends Phobject { protected function applyStandardEffect(HeraldEffect $effect) { $action = $effect->getAction(); + $impl = $this->getActionImplementation($action); + if ($impl) { + $impl->applyEffect($this->getObject(), $effect); + return $impl->getApplyTranscript($effect); + } + $rule_type = $effect->getRule()->getRuleType(); $supported = $this->getActions($rule_type); $supported = array_fuse($supported); @@ -1133,8 +1226,6 @@ abstract class HeraldAdapter extends Phobject { return $this->applyFlagEffect($effect); case self::ACTION_EMAIL: return $this->applyEmailEffect($effect); - case self::ACTION_NOTHING: - return $this->applyNothingEffect($effect); default: break; } @@ -1153,13 +1244,6 @@ abstract class HeraldAdapter extends Phobject { return $result; } - private function applyNothingEffect(HeraldEffect $effect) { - return new HeraldApplyTranscript( - $effect, - true, - pht('Did nothing.')); - } - /** * @task apply */ diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index 385cb65d0d..f83ab301e4 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -427,40 +427,10 @@ final class HeraldRuleController extends HeraldController { } } - $group_map = array(); - foreach ($field_map as $field_key => $field_name) { - $group_key = $adapter->getFieldGroupKey($field_key); - $group_map[$group_key][$field_key] = $field_name; - } - - $field_groups = HeraldFieldGroup::getAllFieldGroups(); - - $groups = array(); - foreach ($group_map as $group_key => $options) { - asort($options); - - $field_group = idx($field_groups, $group_key); - if ($field_group) { - $group_label = $field_group->getGroupLabel(); - $group_order = $field_group->getSortKey(); - } else { - $group_label = nonempty($group_key, pht('Other')); - $group_order = 'Z'; - } - - $groups[] = array( - 'label' => $group_label, - 'options' => $options, - 'order' => $group_order, - ); - } - - $groups = array_values(isort($groups, 'order')); - $config_info = array(); - $config_info['fields'] = $groups; + $config_info['fields'] = $this->getFieldGroups($adapter, $field_map); $config_info['conditions'] = $all_conditions; - $config_info['actions'] = $action_map; + $config_info['actions'] = $this->getActionGroups($adapter, $action_map); $config_info['valueMap'] = array(); foreach ($field_map as $field => $name) { @@ -492,7 +462,7 @@ final class HeraldRuleController extends HeraldController { $config_info['rule_type'] = $rule->getRuleType(); - foreach ($config_info['actions'] as $action => $name) { + foreach ($action_map as $action => $name) { try { $value_key = $adapter->getValueTypeForAction( $action, @@ -659,4 +629,55 @@ final class HeraldRuleController extends HeraldController { return $all_rules; } + private function getFieldGroups(HeraldAdapter $adapter, array $field_map) { + $group_map = array(); + foreach ($field_map as $field_key => $field_name) { + $group_key = $adapter->getFieldGroupKey($field_key); + $group_map[$group_key][$field_key] = $field_name; + } + + return $this->getGroups( + $group_map, + HeraldFieldGroup::getAllFieldGroups()); + } + + private function getActionGroups(HeraldAdapter $adapter, array $action_map) { + $group_map = array(); + foreach ($action_map as $action_key => $action_name) { + $group_key = $adapter->getActionGroupKey($action_key); + $group_map[$group_key][$action_key] = $action_name; + } + + return $this->getGroups( + $group_map, + HeraldActionGroup::getAllActionGroups()); + } + + private function getGroups(array $item_map, array $group_list) { + assert_instances_of($group_list, 'HeraldGroup'); + + $groups = array(); + foreach ($item_map as $group_key => $options) { + asort($options); + + $group_object = idx($group_list, $group_key); + if ($group_object) { + $group_label = $group_object->getGroupLabel(); + $group_order = $group_object->getSortKey(); + } else { + $group_label = nonempty($group_key, pht('Other')); + $group_order = 'Z'; + } + + $groups[] = array( + 'label' => $group_label, + 'options' => $options, + 'order' => $group_order, + ); + } + + return array_values(isort($groups, 'order')); + } + + } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index eac808b54e..27c44aafc5 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -347,9 +347,6 @@ final class HeraldTranscriptController extends HeraldController { $target = $apply_xscript->getTarget(); switch ($apply_xscript->getAction()) { - case HeraldAdapter::ACTION_NOTHING: - $target = null; - break; case HeraldAdapter::ACTION_FLAG: $target = PhabricatorFlagColor::getColorName($target); break; @@ -358,6 +355,8 @@ final class HeraldTranscriptController extends HeraldController { $target = $target; break; default: + // TODO: This should be driven by HeraldActions. + if (is_array($target) && $target) { foreach ($target as $k => $phid) { if (isset($handles[$phid])) { @@ -388,6 +387,9 @@ final class HeraldTranscriptController extends HeraldController { $item->setHeader(pht('%s: %s', $rule, $target)); $item->addAttribute($apply_xscript->getReason()); + + // TODO: This is a bit of a mess while actions convert. + $item->addAttribute( pht('Outcome: %s', $apply_xscript->getAppliedReason())); diff --git a/src/applications/herald/field/HeraldFieldGroup.php b/src/applications/herald/field/HeraldFieldGroup.php index a8f2f229d5..adb7fbe372 100644 --- a/src/applications/herald/field/HeraldFieldGroup.php +++ b/src/applications/herald/field/HeraldFieldGroup.php @@ -1,12 +1,6 @@ getGroupOrder(), $this->getGroupLabel()); - } - final public static function getAllFieldGroups() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) diff --git a/src/applications/herald/group/HeraldGroup.php b/src/applications/herald/group/HeraldGroup.php new file mode 100644 index 0000000000..348ea1d24a --- /dev/null +++ b/src/applications/herald/group/HeraldGroup.php @@ -0,0 +1,15 @@ +getGroupOrder(), $this->getGroupLabel()); + } + +} diff --git a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php index eade548740..8965c7e028 100644 --- a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php +++ b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php @@ -76,7 +76,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_ASSIGN_TASK, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: @@ -87,7 +86,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_ASSIGN_TASK, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); } diff --git a/src/applications/pholio/herald/HeraldPholioMockAdapter.php b/src/applications/pholio/herald/HeraldPholioMockAdapter.php index 2c87c394e9..105a2b56e9 100644 --- a/src/applications/pholio/herald/HeraldPholioMockAdapter.php +++ b/src/applications/pholio/herald/HeraldPholioMockAdapter.php @@ -54,7 +54,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { array( self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: @@ -63,7 +62,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, self::ACTION_FLAG, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); } diff --git a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php index 48f72d57db..a400cdb881 100644 --- a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php +++ b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php @@ -56,7 +56,6 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, self::ACTION_EMAIL, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: @@ -66,7 +65,6 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_FLAG, - self::ACTION_NOTHING, ), parent::getActions($rule_type)); } diff --git a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js index bf1db41dfc..82100062ed 100644 --- a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js +++ b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js @@ -322,21 +322,11 @@ JX.install('HeraldRuleEditor', { _renderCondition : function(row_id) { var groups = this._config.info.fields; - var optgroups = []; - for (var ii = 0; ii < groups.length; ii++) { - var group = groups[ii]; - var options = []; - for (var k in group.options) { - options.push(JX.$N('option', {value: k}, group.options[k])); - } - optgroups.push(JX.$N('optgroup', {label: group.label}, options)); - } - var attrs = { sigil: 'field-select' }; - var field_select = JX.$N('select', attrs, optgroups); + var field_select = this._renderGroupSelect(groups, attrs); field_select.value = this._config.conditions[row_id][0]; var field_cell = JX.$N('td', {sigil: 'field-cell'}, field_select); @@ -352,6 +342,21 @@ JX.install('HeraldRuleEditor', { delete actions[k]; } }, + + _renderGroupSelect: function(groups, attrs) { + var optgroups = []; + for (var ii = 0; ii < groups.length; ii++) { + var group = groups[ii]; + var options = []; + for (var k in group.options) { + options.push(JX.$N('option', {value: k}, group.options[k])); + } + optgroups.push(JX.$N('optgroup', {label: group.label}, options)); + } + + return JX.$N('select', attrs, optgroups); + }, + _newAction : function(data) { data = data || []; var temprow = this._actionsRowManager.addRow([]); @@ -361,11 +366,16 @@ JX.install('HeraldRuleEditor', { this._renderAction(data)); this._onactionchange(r); }, + _renderAction : function(action) { - var action_select = this._renderSelect( - this._config.info.actions, - action[0], - 'action-select'); + var groups = this._config.info.actions; + var attrs = { + sigil: 'action-select' + }; + + var action_select = this._renderGroupSelect(groups, attrs); + action_select.value = action[0]; + var action_cell = JX.$N('td', {sigil: 'action-cell'}, action_select); var target_cell = JX.$N( From 8d9bd791f7f263648c5a811695a000889a136be7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 18 Jul 2015 05:54:26 -0700 Subject: [PATCH 059/102] Modularize Herald "flag" action, plus update transcripts Summary: Ref T8726. This modularizes "Mark with flag", plus rebuilds transcripts in a more modern/flexible way. The big transcript stuff is: - Transcripts are now translatable. - Transcripts can now show multiple outputs from a single action. For example, an action like "add A, B, C to subscribers" can now say "added A; B is invalid; C was already subscribed". Test Plan: {F637784} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: joshuaspence, eadler, epriestley Maniphest Tasks: T8726 Differential Revision: https://secure.phabricator.com/D13649 --- resources/celerity/map.php | 4 +- src/__phutil_library_map__.php | 2 + .../HeraldDifferentialRevisionAdapter.php | 1 - .../diffusion/herald/HeraldCommitAdapter.php | 1 - .../PhabricatorFlagAddFlagHeraldAction.php | 88 ++++ .../herald/action/HeraldAction.php | 51 +- .../herald/action/HeraldDoNothingAction.php | 20 +- .../herald/adapter/HeraldAdapter.php | 84 +--- .../PhabricatorHeraldApplication.php | 2 +- .../controller/HeraldRuleController.php | 1 - .../controller/HeraldTranscriptController.php | 446 +++++++----------- .../herald/HeraldManiphestTaskAdapter.php | 1 - .../pholio/herald/HeraldPholioMockAdapter.php | 1 - .../herald/PhrictionDocumentHeraldAdapter.php | 1 - .../css/application/herald/herald-test.css | 71 +-- 15 files changed, 349 insertions(+), 425 deletions(-) create mode 100644 src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4d3fa6944b..372eefe09f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ return array( 'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad', 'rsrc/css/application/flag/flag.css' => '5337623f', 'rsrc/css/application/harbormaster/harbormaster.css' => '49d64eb4', - 'rsrc/css/application/herald/herald-test.css' => '778b008e', + 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald.css' => '826075fa', 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 'rsrc/css/application/maniphest/report.css' => 'f6931fdf', @@ -539,7 +539,7 @@ return array( 'harbormaster-css' => '49d64eb4', 'herald-css' => '826075fa', 'herald-rule-editor' => '91a6031b', - 'herald-test-css' => '778b008e', + 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', 'javelin-aphlict' => '5359e785', 'javelin-behavior' => '61cbc29a', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1644d3af2c..4254da43cc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2110,6 +2110,7 @@ phutil_register_library_map(array( 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', 'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php', 'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php', + 'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php', 'PhabricatorFlagColor' => 'applications/flag/constants/PhabricatorFlagColor.php', 'PhabricatorFlagConstants' => 'applications/flag/constants/PhabricatorFlagConstants.php', 'PhabricatorFlagController' => 'applications/flag/controller/PhabricatorFlagController.php', @@ -5988,6 +5989,7 @@ phutil_register_library_map(array( 'PhabricatorFlagDAO', 'PhabricatorPolicyInterface', ), + 'PhabricatorFlagAddFlagHeraldAction' => 'HeraldAction', 'PhabricatorFlagColor' => 'PhabricatorFlagConstants', 'PhabricatorFlagConstants' => 'Phobject', 'PhabricatorFlagController' => 'PhabricatorController', diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index 10ae15c1e6..38a6e8b376 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -177,7 +177,6 @@ final class HeraldDifferentialRevisionAdapter self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, self::ACTION_EMAIL, - self::ACTION_FLAG, self::ACTION_ADD_REVIEWERS, self::ACTION_ADD_BLOCKING_REVIEWERS, ), diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 087dafd844..146fba2fc3 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -102,7 +102,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, self::ACTION_EMAIL, - self::ACTION_FLAG, self::ACTION_AUDIT, ), parent::getActions($rule_type)); diff --git a/src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php b/src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php new file mode 100644 index 0000000000..8c2490fd28 --- /dev/null +++ b/src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php @@ -0,0 +1,88 @@ +getAdapter()->getPHID(); + $rule = $effect->getRule(); + $author = $rule->getAuthor(); + + $flag = PhabricatorFlagQuery::loadUserFlag($author, $phid); + if ($flag) { + $this->logEffect(self::DO_IGNORE, $flag->getColor()); + return; + } + + $flag = id(new PhabricatorFlag()) + ->setOwnerPHID($author->getPHID()) + ->setType(phid_get_type($phid)) + ->setObjectPHID($phid) + ->setReasonPHID($rule->getPHID()) + ->setColor($effect->getTarget()) + ->setNote('') + ->save(); + + $this->logEffect(self::DO_FLAG, $flag->getColor()); + } + + public function getHeraldActionValueType() { + return id(new HeraldSelectFieldValue()) + ->setKey('flag.color') + ->setOptions(PhabricatorFlagColor::getColorNameMap()) + ->setDefault(PhabricatorFlagColor::COLOR_BLUE); + } + + protected function getActionEffectMap() { + return array( + self::DO_IGNORE => array( + 'icon' => 'fa-times', + 'color' => 'grey', + 'name' => pht('Already Marked'), + ), + self::DO_FLAG => array( + 'icon' => 'fa-flag', + 'name' => pht('Flagged'), + ), + ); + } + + public function renderActionDescription($value) { + $color = PhabricatorFlagColor::getColorName($value); + return pht('Mark with %s flag.', $color); + } + + public function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_IGNORE: + return pht( + 'Already marked with %s flag.', + PhabricatorFlagColor::getColorName($data)); + case self::DO_FLAG: + return pht( + 'Marked with "%s" flag.', + PhabricatorFlagColor::getColorName($data)); + } + } + +} diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index f78d30cf56..bad7a3b6ba 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -3,6 +3,7 @@ abstract class HeraldAction extends Phobject { private $adapter; + private $viewer; private $applyLog = array(); const STANDARD_NONE = 'standard.none'; @@ -12,6 +13,7 @@ abstract class HeraldAction extends Phobject { abstract public function supportsObject($object); abstract public function supportsRuleType($rule_type); abstract public function applyEffect($object, HeraldEffect $effect); + abstract public function renderActionEffectDescription($type, $data); public function getActionGroupKey() { return null; @@ -66,6 +68,15 @@ abstract class HeraldAction extends Phobject { return $this->adapter; } + final public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + final public function getActionConstant() { $class = new ReflectionClass($this); @@ -106,13 +117,49 @@ abstract class HeraldAction extends Phobject { } protected function logEffect($type, $data = null) { - return; + if (!is_string($type)) { + throw new Exception( + pht( + 'Effect type passed to "%s" must be a scalar string.', + 'logEffect()')); + } + + $this->applyLog[] = array( + 'type' => $type, + 'data' => $data, + ); + + return $this; } final public function getApplyTranscript(HeraldEffect $effect) { - $context = 'v2/'.phutil_json_encode($this->applyLog); + $context = $this->applyLog; $this->applyLog = array(); return new HeraldApplyTranscript($effect, true, $context); } + protected function getActionEffectMap() { + throw new PhutilMethodNotImplementedException(); + } + + private function getActionEffectSpec($type) { + $map = $this->getActionEffectMap(); + return idx($map, $type, array()); + } + + public function renderActionEffectIcon($type, $data) { + $map = $this->getActionEffectSpec($type); + return idx($map, 'icon'); + } + + public function renderActionEffectColor($type, $data) { + $map = $this->getActionEffectSpec($type); + return idx($map, 'color'); + } + + public function renderActionEffectName($type, $data) { + $map = $this->getActionEffectSpec($type); + return idx($map, 'name'); + } + } diff --git a/src/applications/herald/action/HeraldDoNothingAction.php b/src/applications/herald/action/HeraldDoNothingAction.php index 58b9f98cf2..110e5707ac 100644 --- a/src/applications/herald/action/HeraldDoNothingAction.php +++ b/src/applications/herald/action/HeraldDoNothingAction.php @@ -22,11 +22,29 @@ final class HeraldDoNothingAction extends HeraldAction { } public function applyEffect($object, HeraldEffect $effect) { - $this->logEffect($effect, self::DO_NOTHING); + $this->logEffect(self::DO_NOTHING); } public function getHeraldActionStandardType() { return self::STANDARD_NONE; } + protected function getActionEffectMap() { + return array( + self::DO_NOTHING => array( + 'icon' => 'fa-check', + 'color' => 'grey', + 'name' => pht('Did Nothing'), + ), + ); + } + + public function renderActionDescription($value) { + return pht('Do nothing.'); + } + + public function renderActionEffectDescription($type, $data) { + return pht('Did nothing.'); + } + } diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 3ef95c8757..0c630876c6 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -30,7 +30,6 @@ abstract class HeraldAdapter extends Phobject { const ACTION_REMOVE_CC = 'remcc'; const ACTION_EMAIL = 'email'; const ACTION_AUDIT = 'audit'; - const ACTION_FLAG = 'flag'; const ACTION_ASSIGN_TASK = 'assigntask'; const ACTION_ADD_PROJECTS = 'addprojects'; const ACTION_REMOVE_PROJECTS = 'removeprojects'; @@ -670,7 +669,7 @@ abstract class HeraldAdapter extends Phobject { return $actions; } - private function getActionImplementation($key) { + public function getActionImplementation($key) { return idx($this->getActionImplementationMap(), $key); } @@ -728,7 +727,6 @@ abstract class HeraldAdapter extends Phobject { self::ACTION_REMOVE_CC => pht('Remove Subscribers'), self::ACTION_EMAIL => pht('Send an email to'), self::ACTION_AUDIT => pht('Trigger an Audit by'), - self::ACTION_FLAG => pht('Mark with flag'), self::ACTION_ASSIGN_TASK => pht('Assign task to'), self::ACTION_ADD_PROJECTS => pht('Add projects'), self::ACTION_REMOVE_PROJECTS => pht('Remove projects'), @@ -745,7 +743,6 @@ abstract class HeraldAdapter extends Phobject { self::ACTION_REMOVE_CC => pht('Remove me as a subscriber'), self::ACTION_EMAIL => pht('Send me an email'), self::ACTION_AUDIT => pht('Trigger an Audit by me'), - self::ACTION_FLAG => pht('Mark with flag'), self::ACTION_ASSIGN_TASK => pht('Assign task to me'), self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'), self::ACTION_ADD_BLOCKING_REVIEWERS => @@ -798,13 +795,6 @@ abstract class HeraldAdapter extends Phobject { // For personal rules, force these actions to target the rule owner. $target = array($author_phid); break; - case self::ACTION_FLAG: - // Make sure flag color is valid; set to blue if not. - $color_map = PhabricatorFlagColor::getColorNameMap(); - if (empty($color_map[$target])) { - $target = PhabricatorFlagColor::COLOR_BLUE; - } - break; case self::ACTION_BLOCK: break; default: @@ -846,8 +836,6 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_ADD_REVIEWERS: case self::ACTION_ADD_BLOCKING_REVIEWERS: return new HeraldEmptyFieldValue(); - case self::ACTION_FLAG: - return $this->buildFlagColorFieldValue(); case self::ACTION_ADD_PROJECTS: case self::ACTION_REMOVE_PROJECTS: return $this->buildTokenizerFieldValue( @@ -864,8 +852,6 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_REMOVE_PROJECTS: return $this->buildTokenizerFieldValue( new PhabricatorProjectDatasource()); - case self::ACTION_FLAG: - return $this->buildFlagColorFieldValue(); case self::ACTION_ASSIGN_TASK: return $this->buildTokenizerFieldValue( new PhabricatorPeopleDatasource()); @@ -893,13 +879,6 @@ abstract class HeraldAdapter extends Phobject { throw new Exception(pht("Unknown or invalid action '%s'.", $action)); } - private function buildFlagColorFieldValue() { - return id(new HeraldSelectFieldValue()) - ->setKey('flag.color') - ->setOptions(PhabricatorFlagColor::getColorNameMap()) - ->setDefault(PhabricatorFlagColor::COLOR_BLUE); - } - private function buildTokenizerFieldValue( PhabricatorTypeaheadDatasource $datasource) { @@ -1051,7 +1030,7 @@ abstract class HeraldAdapter extends Phobject { ), array( $icon, - $this->renderActionAsText($action, $handles), + $this->renderActionAsText($viewer, $action, $handles), )); } @@ -1083,8 +1062,16 @@ abstract class HeraldAdapter extends Phobject { } private function renderActionAsText( + PhabricatorUser $viewer, HeraldActionRecord $action, PhabricatorHandleList $handles) { + + $impl = $this->getActionImplementation($action->getAction()); + if ($impl) { + $value = $action->getTarget(); + return $impl->renderActionDescription($viewer, $value); + } + $rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; $action_type = $action->getAction(); @@ -1118,15 +1105,14 @@ abstract class HeraldAdapter extends Phobject { HeraldActionRecord $action, PhabricatorHandleList $handles) { + // TODO: This should be driven through HeraldAction. + $target = $action->getTarget(); if (!is_array($target)) { $target = array($target); } foreach ($target as $index => $val) { switch ($action->getAction()) { - case self::ACTION_FLAG: - $target[$index] = PhabricatorFlagColor::getColorName($val); - break; default: $handle = $handles->getHandleIfExists($val); if ($handle) { @@ -1222,8 +1208,6 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_ADD_CC: case self::ACTION_REMOVE_CC: return $this->applySubscribersEffect($effect); - case self::ACTION_FLAG: - return $this->applyFlagEffect($effect); case self::ACTION_EMAIL: return $this->applyEmailEffect($effect); default: @@ -1364,50 +1348,6 @@ abstract class HeraldAdapter extends Phobject { return new HeraldApplyTranscript($effect, true, $message); } - - /** - * @task apply - */ - private function applyFlagEffect(HeraldEffect $effect) { - $phid = $this->getPHID(); - $color = $effect->getTarget(); - - $rule = $effect->getRule(); - $user = $rule->getAuthor(); - - $flag = PhabricatorFlagQuery::loadUserFlag($user, $phid); - if ($flag) { - return new HeraldApplyTranscript( - $effect, - false, - pht('Object already flagged.')); - } - - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($user) - ->withPHIDs(array($phid)) - ->executeOne(); - - $flag = new PhabricatorFlag(); - $flag->setOwnerPHID($user->getPHID()); - $flag->setType($handle->getType()); - $flag->setObjectPHID($handle->getPHID()); - - // TOOD: Should really be transcript PHID, but it doesn't exist yet. - $flag->setReasonPHID($user->getPHID()); - - $flag->setColor($color); - $flag->setNote( - pht('Flagged by Herald Rule "%s".', $rule->getName())); - $flag->save(); - - return new HeraldApplyTranscript( - $effect, - true, - pht('Added flag.')); - } - - /** * @task apply */ diff --git a/src/applications/herald/application/PhabricatorHeraldApplication.php b/src/applications/herald/application/PhabricatorHeraldApplication.php index b74a27d9f2..e3537d9722 100644 --- a/src/applications/herald/application/PhabricatorHeraldApplication.php +++ b/src/applications/herald/application/PhabricatorHeraldApplication.php @@ -58,7 +58,7 @@ final class PhabricatorHeraldApplication extends PhabricatorApplication { 'transcript/' => array( '' => 'HeraldTranscriptListController', '(?:query/(?P[^/]+)/)?' => 'HeraldTranscriptListController', - '(?P[1-9]\d*)/(?:(?P\w+)/)?' + '(?P[1-9]\d*)/' => 'HeraldTranscriptController', ), ), diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index f83ab301e4..cd35307723 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -367,7 +367,6 @@ final class HeraldRuleController extends HeraldController { $serial_actions = array(); foreach ($rule->getActions() as $action) { switch ($action->getAction()) { - case HeraldAdapter::ACTION_FLAG: case HeraldAdapter::ACTION_BLOCK: $current_value = $action->getTarget(); break; diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index 27c44aafc5..713ce52804 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -2,43 +2,26 @@ final class HeraldTranscriptController extends HeraldController { - const FILTER_AFFECTED = 'affected'; - const FILTER_OWNED = 'owned'; - const FILTER_ALL = 'all'; - - private $id; - private $filter; private $handles; private $adapter; - public function willProcessRequest(array $data) { - $this->id = $data['id']; - $map = $this->getFilterMap(); - $this->filter = idx($data, 'filter'); - if (empty($map[$this->filter])) { - $this->filter = self::FILTER_ALL; - } - } - private function getAdapter() { return $this->adapter; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $xscript = id(new HeraldTranscriptQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$xscript) { return new Aphront404Response(); } require_celerity_resource('herald-test-css'); - - $nav = $this->buildSideNav(); + $content = array(); $object_xscript = $xscript->getObjectTranscript(); if (!$object_xscript) { @@ -49,7 +32,7 @@ final class HeraldTranscriptController extends HeraldController { 'p', array(), pht('Details of this transcript have been garbage collected.'))); - $nav->appendChild($notice); + $content[] = $notice; } else { $map = HeraldAdapter::getEnabledAdapterMap($viewer); $object_type = $object_xscript->getType(); @@ -65,9 +48,7 @@ final class HeraldTranscriptController extends HeraldController { $this->adapter = HeraldAdapter::getAdapterForContentType($object_type); - $filter = $this->getFilterPHIDs(); - $this->filterTranscript($xscript, $filter); - $phids = array_merge($filter, $this->getTranscriptPHIDs($xscript)); + $phids = $this->getTranscriptPHIDs($xscript); $phids = array_unique($phids); $phids = array_filter($phids); @@ -82,23 +63,16 @@ final class HeraldTranscriptController extends HeraldController { pht( 'This was a dry run to test Herald rules, '. 'no actions were executed.')); - $nav->appendChild($notice); + $content[] = $notice; } $warning_panel = $this->buildWarningPanel($xscript); - $nav->appendChild($warning_panel); + $content[] = $warning_panel; - $apply_xscript_panel = $this->buildApplyTranscriptPanel( - $xscript); - $nav->appendChild($apply_xscript_panel); - - $action_xscript_panel = $this->buildActionTranscriptPanel( - $xscript); - $nav->appendChild($action_xscript_panel); - - $object_xscript_panel = $this->buildObjectTranscriptPanel( - $xscript); - $nav->appendChild($object_xscript_panel); + $content[] = array( + $this->buildActionTranscriptPanel($xscript), + $this->buildObjectTranscriptPanel($xscript), + ); } $crumbs = id($this->buildApplicationCrumbs()) @@ -106,10 +80,12 @@ final class HeraldTranscriptController extends HeraldController { pht('Transcripts'), $this->getApplicationURI('/transcript/')) ->addTextCrumb($xscript->getID()); - $nav->setCrumbs($crumbs); return $this->buildApplicationPage( - $nav, + array( + $crumbs, + $content, + ), array( 'title' => pht('Transcript'), )); @@ -146,33 +122,6 @@ final class HeraldTranscriptController extends HeraldController { return phutil_tag('span', array('class' => 'condition-test-value'), $value); } - private function buildSideNav() { - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI('/herald/transcript/'.$this->id.'/')); - - $items = array(); - $filters = $this->getFilterMap(); - foreach ($filters as $key => $name) { - $nav->addFilter($key, $name); - } - $nav->selectFilter($this->filter, null); - - return $nav; - } - - protected function getFilterMap() { - return array( - self::FILTER_ALL => pht('All Rules'), - self::FILTER_OWNED => pht('Rules I Own'), - self::FILTER_AFFECTED => pht('Rules that Affected Me'), - ); - } - - - protected function getFilterPHIDs() { - return array($this->getRequest()->getUser()->getPHID()); - } - protected function getTranscriptPHIDs($xscript) { $phids = array(); @@ -228,71 +177,6 @@ final class HeraldTranscriptController extends HeraldController { return $phids; } - protected function filterTranscript($xscript, $filter_phids) { - $filter_owned = ($this->filter == self::FILTER_OWNED); - $filter_affected = ($this->filter == self::FILTER_AFFECTED); - - if (!$filter_owned && !$filter_affected) { - // No filtering to be done. - return; - } - - if (!$xscript->getObjectTranscript()) { - return; - } - - $user_phid = $this->getRequest()->getUser()->getPHID(); - - $keep_apply_xscripts = array(); - $keep_rule_xscripts = array(); - - $filter_phids = array_fill_keys($filter_phids, true); - - $rule_xscripts = $xscript->getRuleTranscripts(); - foreach ($xscript->getApplyTranscripts() as $id => $apply_xscript) { - $rule_id = $apply_xscript->getRuleID(); - if ($filter_owned) { - if (empty($rule_xscripts[$rule_id])) { - // No associated rule so you can't own this effect. - continue; - } - if ($rule_xscripts[$rule_id]->getRuleOwner() != $user_phid) { - continue; - } - } else if ($filter_affected) { - $targets = (array)$apply_xscript->getTarget(); - if (!array_select_keys($filter_phids, $targets)) { - continue; - } - } - $keep_apply_xscripts[$id] = true; - if ($rule_id) { - $keep_rule_xscripts[$rule_id] = true; - } - } - - foreach ($rule_xscripts as $rule_id => $rule_xscript) { - if ($filter_owned && $rule_xscript->getRuleOwner() == $user_phid) { - $keep_rule_xscripts[$rule_id] = true; - } - } - - $xscript->setRuleTranscripts( - array_intersect_key( - $xscript->getRuleTranscripts(), - $keep_rule_xscripts)); - - $xscript->setApplyTranscripts( - array_intersect_key( - $xscript->getApplyTranscripts(), - $keep_apply_xscripts)); - - $xscript->setConditionTranscripts( - array_intersect_key( - $xscript->getConditionTranscripts(), - $keep_rule_xscripts)); - } - private function buildWarningPanel(HeraldTranscript $xscript) { $request = $this->getRequest(); $panel = null; @@ -333,165 +217,185 @@ final class HeraldTranscriptController extends HeraldController { return $panel; } - private function buildApplyTranscriptPanel(HeraldTranscript $xscript) { - $handles = $this->handles; - $adapter = $this->getAdapter(); - - $rule_type_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; - $action_names = $adapter->getActionNameMap($rule_type_global); - - $list = new PHUIObjectItemListView(); - $list->setStates(true); - $list->setNoDataString(pht('No actions were taken.')); - foreach ($xscript->getApplyTranscripts() as $apply_xscript) { - - $target = $apply_xscript->getTarget(); - switch ($apply_xscript->getAction()) { - case HeraldAdapter::ACTION_FLAG: - $target = PhabricatorFlagColor::getColorName($target); - break; - case HeraldAdapter::ACTION_BLOCK: - // Target is a text string. - $target = $target; - break; - default: - // TODO: This should be driven by HeraldActions. - - if (is_array($target) && $target) { - foreach ($target as $k => $phid) { - if (isset($handles[$phid])) { - $target[$k] = $handles[$phid]->getName(); - } - } - $target = implode(', ', $target); - } else if (is_string($target)) { - $target = $target; - } else { - $target = ''; - } - break; - } - - $item = new PHUIObjectItemView(); - - if ($apply_xscript->getApplied()) { - $item->setState(PHUIObjectItemView::STATE_SUCCESS); - } else { - $item->setState(PHUIObjectItemView::STATE_FAIL); - } - - $rule = idx( - $action_names, - $apply_xscript->getAction(), - pht('Unknown Action "%s"', $apply_xscript->getAction())); - - $item->setHeader(pht('%s: %s', $rule, $target)); - $item->addAttribute($apply_xscript->getReason()); - - // TODO: This is a bit of a mess while actions convert. - - $item->addAttribute( - pht('Outcome: %s', $apply_xscript->getAppliedReason())); - - $list->addItem($item); - } - - $box = new PHUIObjectBoxView(); - $box->setHeaderText(pht('Actions Taken')); - $box->appendChild($list); - - return $box; - } - private function buildActionTranscriptPanel(HeraldTranscript $xscript) { $action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID'); $adapter = $this->getAdapter(); - $field_names = $adapter->getFieldNameMap(); $condition_names = $adapter->getConditionNameMap(); $handles = $this->handles; - $rule_markup = array(); - foreach ($xscript->getRuleTranscripts() as $rule_id => $rule) { - $cond_markup = array(); - foreach ($xscript->getConditionTranscriptsForRule($rule_id) as $cond) { - if ($cond->getNote()) { - $note = phutil_tag_div('herald-condition-note', $cond->getNote()); + $action_map = $xscript->getApplyTranscripts(); + $action_map = mgroup($action_map, 'getRuleID'); + + $rule_list = id(new PHUIObjectItemListView()) + ->setNoDataString(pht('No Herald rules applied to this object.')); + + foreach ($xscript->getRuleTranscripts() as $rule_xscript) { + $rule_id = $rule_xscript->getRuleID(); + + $rule_item = id(new PHUIObjectItemView()) + ->setObjectName(pht('H%d', $rule_id)) + ->setHeader($rule_xscript->getRuleName()); + + if (!$rule_xscript->getResult()) { + $rule_item->setDisabled(true); + } + + $rule_list->addItem($rule_item); + + // Build the field/condition transcript. + + $cond_xscripts = $xscript->getConditionTranscriptsForRule($rule_id); + + $cond_list = id(new PHUIStatusListView()); + $cond_list->addItem( + id(new PHUIStatusItemView()) + ->setTarget(phutil_tag('strong', array(), pht('Conditions')))); + + foreach ($cond_xscripts as $cond_xscript) { + if ($cond_xscript->getResult()) { + $icon = 'fa-check'; + $color = 'green'; + $result = pht('Passed'); + } else { + $icon = 'fa-times'; + $color = 'red'; + $result = pht('Failed'); + } + + if ($cond_xscript->getNote()) { + $note = phutil_tag( + 'div', + array( + 'class' => 'herald-condition-note', + ), + $cond_xscript->getNote()); } else { $note = null; } - if ($cond->getResult()) { - $result = phutil_tag( - 'span', - array('class' => 'herald-outcome condition-pass'), - "\xE2\x9C\x93"); + // TODO: This is not really translatable and should be driven through + // HeraldField. + $explanation = pht( + '%s %s %s', + idx($field_names, $cond_xscript->getFieldName(), pht('Unknown')), + idx($condition_names, $cond_xscript->getCondition(), pht('Unknown')), + $this->renderConditionTestValue($cond_xscript, $handles)); + + $cond_item = id(new PHUIStatusItemView()) + ->setIcon($icon, $color) + ->setTarget($result) + ->setNote(array($explanation, $note)); + + $cond_list->addItem($cond_item); + } + + if ($rule_xscript->getResult()) { + $last_icon = 'fa-check-circle'; + $last_color = 'green'; + $last_result = pht('Passed'); + $last_note = pht('Rule passed.'); + } else { + $last_icon = 'fa-times-circle'; + $last_color = 'red'; + $last_result = pht('Failed'); + $last_note = pht('Rule failed.'); + } + + $cond_last = id(new PHUIStatusItemView()) + ->setIcon($last_icon, $last_color) + ->setTarget(phutil_tag('strong', array(), $last_result)) + ->setNote($last_note); + $cond_list->addItem($cond_last); + + $cond_box = id(new PHUIBoxView()) + ->appendChild($cond_list) + ->addMargin(PHUI::MARGIN_LARGE_LEFT); + + $rule_item->appendChild($cond_box); + + if (!$rule_xscript->getResult()) { + // If the rule didn't pass, don't generate an action transcript since + // actions didn't apply. + continue; + } + + $cond_box->addMargin(PHUI::MARGIN_MEDIUM_BOTTOM); + + $action_xscripts = idx($action_map, $rule_id, array()); + foreach ($action_xscripts as $action_xscript) { + $action_key = $action_xscript->getAction(); + $action = $adapter->getActionImplementation($action_key); + + if ($action) { + $name = $action->getHeraldActionName(); + $action->setViewer($this->getViewer()); } else { - $result = phutil_tag( - 'span', - array('class' => 'herald-outcome condition-fail'), - "\xE2\x9C\x98"); + $name = pht('Unknown Action ("%s")', $action_key); } - $cond_markup[] = phutil_tag( - 'li', - array(), - pht( - '%s Condition: %s %s %s%s', - $result, - idx($field_names, $cond->getFieldName(), pht('Unknown')), - idx($condition_names, $cond->getCondition(), pht('Unknown')), - $this->renderConditionTestValue($cond, $handles), - $note)); + $name = pht('Action: %s', $name); + + $action_list = id(new PHUIStatusListView()); + $action_list->addItem( + id(new PHUIStatusItemView()) + ->setTarget(phutil_tag('strong', array(), $name))); + + $action_box = id(new PHUIBoxView()) + ->appendChild($action_list) + ->addMargin(PHUI::MARGIN_LARGE_LEFT); + + $rule_item->appendChild($action_box); + + $log = $action_xscript->getAppliedReason(); + + // Handle older transcripts which used a static string to record + // action results. + if (!is_array($log)) { + $action_list->addItem( + id(new PHUIStatusItemView()) + ->setIcon('fa-clock-o', 'grey') + ->setTarget(pht('Old Transcript')) + ->setNote( + pht( + 'This is an old transcript which uses an obsolete log '. + 'format. Detailed action information is not available.'))); + continue; + } + + foreach ($log as $entry) { + $type = idx($entry, 'type'); + $data = idx($entry, 'data'); + + if ($action) { + $icon = $action->renderActionEffectIcon($type, $data); + $color = $action->renderActionEffectColor($type, $data); + $name = $action->renderActionEffectName($type, $data); + $note = $action->renderActionEffectDescription($type, $data); + } else { + $icon = 'fa-question-circle'; + $color = 'indigo'; + $name = pht('Unknown Effect ("%s")', $type); + $note = null; + } + + $action_item = id(new PHUIStatusItemView()) + ->setIcon($icon, $color) + ->setTarget($name) + ->setNote($note); + + $action_list->addItem($action_item); + } } - - if ($rule->getResult()) { - $result = phutil_tag( - 'span', - array('class' => 'herald-outcome rule-pass'), - pht('PASS')); - $class = 'herald-rule-pass'; - } else { - $result = phutil_tag( - 'span', - array('class' => 'herald-outcome rule-fail'), - pht('FAIL')); - $class = 'herald-rule-fail'; - } - - $cond_markup[] = phutil_tag( - 'li', - array(), - array($result, $rule->getReason())); - $user_phid = $this->getRequest()->getUser()->getPHID(); - - $name = $rule->getRuleName(); - - $rule_markup[] = - phutil_tag( - 'li', - array( - 'class' => $class, - ), - phutil_tag_div('rule-name', array( - phutil_tag('strong', array(), $name), - ' ', - phutil_tag('ul', array(), $cond_markup), - ))); } - $box = null; - if ($rule_markup) { - $box = new PHUIObjectBoxView(); - $box->setHeaderText(pht('Rule Details')); - $box->appendChild(phutil_tag( - 'ul', - array('class' => 'herald-explain-list'), - $rule_markup)); - } + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Rule Transcript')) + ->appendChild($rule_list); + return $box; } diff --git a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php index 8965c7e028..7aad56ee24 100644 --- a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php +++ b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php @@ -84,7 +84,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, self::ACTION_EMAIL, - self::ACTION_FLAG, self::ACTION_ASSIGN_TASK, ), parent::getActions($rule_type)); diff --git a/src/applications/pholio/herald/HeraldPholioMockAdapter.php b/src/applications/pholio/herald/HeraldPholioMockAdapter.php index 105a2b56e9..330f4294ad 100644 --- a/src/applications/pholio/herald/HeraldPholioMockAdapter.php +++ b/src/applications/pholio/herald/HeraldPholioMockAdapter.php @@ -61,7 +61,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { array( self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, - self::ACTION_FLAG, ), parent::getActions($rule_type)); } diff --git a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php index a400cdb881..05a09fb877 100644 --- a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php +++ b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php @@ -64,7 +64,6 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, self::ACTION_EMAIL, - self::ACTION_FLAG, ), parent::getActions($rule_type)); } diff --git a/webroot/rsrc/css/application/herald/herald-test.css b/webroot/rsrc/css/application/herald/herald-test.css index 178d54653d..67b49145d5 100644 --- a/webroot/rsrc/css/application/herald/herald-test.css +++ b/webroot/rsrc/css/application/herald/herald-test.css @@ -2,79 +2,10 @@ * @provides herald-test-css */ -ul.herald-explain-list { - margin: 12px; -} - -div.herald-condition-note { - clear: both; - margin: .5em 0em .53em 86px; - padding: .5em 1em; - background: #FFFF00; - font-weight: bold; -} - -ul.herald-explain-list .herald-outcome { - margin-right: 6px; - width: 50px; - text-align: center; - position: relative; - float: left; - font-weight: bold; - padding: 1px 2px; -} - -ul.herald-explain-list .condition-fail, -ul.herald-explain-list .rule-fail { +.herald-condition-note { color: {$red}; } -ul.herald-explain-list .condition-pass, -ul.herald-explain-list .rule-pass { - color: {$green}; -} - -ul.herald-explain-list li.herald-rule-pass, -ul.herald-explain-list li.herald-rule-fail { - margin: 0 0 8px; -} - -ul.herald-explain-list div.rule-name { - padding: 4px 0 8px 0; -} - -ul.herald-explain-list li.herald-rule-fail, -ul.herald-explain-list li.herald-rule-pass { - background: #ffffff; -} - -ul.herald-explain-list ul { - margin: 4px; -} - -ul.herald-explain-list ul li { - padding: 2px 0; -} - -.outcome-failure, -.outcome-success { - font-weight: bold; -} - -.outcome-failure { - color: {$red}; -} - -.outcome-success { - color: {$green}; -} - -.action-header { - font-weight: bold; - padding-top: 12px; - border-bottom: 1px solid {$thinblueborder}; -} - textarea.herald-field-value-transcript { width: 100%; height: 10em; From 8ae08a3de70ba857f7513d766a8360dece7d6423 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 18 Jul 2015 08:07:31 -0700 Subject: [PATCH 060/102] Make "Add Subscribers" and "Remove Subscribers" Herald actions modular Summary: Ref T8726. Converts these actions to be modular. No real surprises in this change. Test Plan: {F658709} - Wrote some rules. - Migrated them forward. - Used a bunch of these rules. Reviewers: btrahan Reviewed By: btrahan Subscribers: joshuaspence, epriestley Maniphest Tasks: T8726 Differential Revision: https://secure.phabricator.com/D13699 --- .../sql/autopatches/20150724.herald.1.sql | 27 ++ src/__phutil_library_map__.php | 10 + .../HeraldDifferentialRevisionAdapter.php | 4 - .../diffusion/herald/HeraldCommitAdapter.php | 4 - .../herald/action/HeraldAction.php | 24 +- .../herald/adapter/HeraldAdapter.php | 144 +++-------- .../herald/HeraldManiphestTaskAdapter.php | 4 - .../pholio/herald/HeraldPholioMockAdapter.php | 19 -- .../herald/PhrictionDocumentHeraldAdapter.php | 4 - ...icatorSubscriptionsAddSelfHeraldAction.php | 37 +++ ...ubscriptionsAddSubscribersHeraldAction.php | 40 +++ .../PhabricatorSubscriptionsHeraldAction.php | 235 ++++++++++++++++++ ...torSubscriptionsRemoveSelfHeraldAction.php | 37 +++ ...criptionsRemoveSubscribersHeraldAction.php | 40 +++ .../PhabricatorUSEnglishTranslation.php | 34 +++ 15 files changed, 513 insertions(+), 150 deletions(-) create mode 100644 resources/sql/autopatches/20150724.herald.1.sql create mode 100644 src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php create mode 100644 src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php create mode 100644 src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php create mode 100644 src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php create mode 100644 src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php diff --git a/resources/sql/autopatches/20150724.herald.1.sql b/resources/sql/autopatches/20150724.herald.1.sql new file mode 100644 index 0000000000..aaf9b69196 --- /dev/null +++ b/resources/sql/autopatches/20150724.herald.1.sql @@ -0,0 +1,27 @@ +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'subscribers.add' + WHERE r.ruleType != 'personal' + AND a.action = 'addcc'; + +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'subscribers.self.add' + WHERE r.ruleType = 'personal' + AND a.action = 'addcc'; + +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'subscribers.remove' + WHERE r.ruleType != 'personal' + AND a.action = 'remcc'; + +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'subscribers.self.remove' + WHERE r.ruleType = 'personal' + AND a.action = 'remcc'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4254da43cc..8904abc6de 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2880,10 +2880,15 @@ phutil_register_library_map(array( 'PhabricatorSubscribedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorSubscribedToObjectEdgeType.php', 'PhabricatorSubscribersQuery' => 'applications/subscriptions/query/PhabricatorSubscribersQuery.php', 'PhabricatorSubscriptionTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorSubscriptionTriggerClock.php', + 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php', + 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php', 'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php', 'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php', 'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php', + 'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php', 'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php', + 'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php', + 'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php', 'PhabricatorSubscriptionsSubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php', 'PhabricatorSubscriptionsSubscribersPolicyRule' => 'applications/subscriptions/policyrule/PhabricatorSubscriptionsSubscribersPolicyRule.php', 'PhabricatorSubscriptionsTransactionController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php', @@ -6894,10 +6899,15 @@ phutil_register_library_map(array( 'PhabricatorSubscribedToObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorSubscribersQuery' => 'PhabricatorQuery', 'PhabricatorSubscriptionTriggerClock' => 'PhabricatorTriggerClock', + 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', + 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsApplication' => 'PhabricatorApplication', 'PhabricatorSubscriptionsEditController' => 'PhabricatorController', 'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor', + 'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction', 'PhabricatorSubscriptionsListController' => 'PhabricatorController', + 'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', + 'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsSubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorSubscriptionsSubscribersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorSubscriptionsTransactionController' => 'PhabricatorController', diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index 38a6e8b376..47a132338a 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -162,8 +162,6 @@ final class HeraldDifferentialRevisionAdapter case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_ADD_REVIEWERS, self::ACTION_ADD_BLOCKING_REVIEWERS, @@ -174,8 +172,6 @@ final class HeraldDifferentialRevisionAdapter case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_ADD_REVIEWERS, self::ACTION_ADD_BLOCKING_REVIEWERS, diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 146fba2fc3..302027b539 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -89,8 +89,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: return array_merge( array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_AUDIT, self::ACTION_APPLY_BUILD_PLANS, @@ -99,8 +97,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_AUDIT, ), diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index bad7a3b6ba..529f3b7471 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -41,7 +41,7 @@ abstract class HeraldAction extends Phobject { return new HeraldEmptyFieldValue(); case self::STANDARD_PHID_LIST: $tokenizer = id(new HeraldTokenizerFieldValue()) - ->setKey($this->getHeraldFieldName()) + ->setKey($this->getHeraldActionName()) ->setDatasource($this->getDatasource()); $value_map = $this->getDatasourceValueMap(); @@ -56,6 +56,17 @@ abstract class HeraldAction extends Phobject { } public function willSaveActionValue($value) { + try { + $type = $this->getHeraldActionStandardType(); + } catch (PhutilMethodNotImplementedException $ex) { + return $value; + } + + switch ($type) { + case self::STANDARD_PHID_LIST: + return array_keys($value); + } + return $value; } @@ -162,4 +173,15 @@ abstract class HeraldAction extends Phobject { return idx($map, 'name'); } + protected function renderHandleList($phids) { + if (!is_array($phids)) { + return pht('(Invalid List)'); + } + + return $this->getViewer() + ->renderHandleList($phids) + ->setAsInline(true) + ->render(); + } + } diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 0c630876c6..4e7e49ea66 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -26,8 +26,6 @@ abstract class HeraldAdapter extends Phobject { const CONDITION_IS_TRUE = 'true'; const CONDITION_IS_FALSE = 'false'; - const ACTION_ADD_CC = 'addcc'; - const ACTION_REMOVE_CC = 'remcc'; const ACTION_EMAIL = 'email'; const ACTION_AUDIT = 'audit'; const ACTION_ASSIGN_TASK = 'assigntask'; @@ -46,9 +44,9 @@ abstract class HeraldAdapter extends Phobject { private $queuedTransactions = array(); private $emailPHIDs = array(); private $forcedEmailPHIDs = array(); - private $unsubscribedPHIDs; private $fieldMap; private $actionMap; + private $edgeCache = array(); public function getEmailPHIDs() { return array_values($this->emailPHIDs); @@ -179,7 +177,7 @@ abstract class HeraldAdapter extends Phobject { return $this->queuedTransactions; } - protected function newTransaction() { + public function newTransaction() { $object = $this->newObject(); if (!($object instanceof PhabricatorApplicationTransactionInterface)) { @@ -723,8 +721,6 @@ abstract class HeraldAdapter extends Phobject { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: $standard = array( - self::ACTION_ADD_CC => pht('Add Subscribers'), - self::ACTION_REMOVE_CC => pht('Remove Subscribers'), self::ACTION_EMAIL => pht('Send an email to'), self::ACTION_AUDIT => pht('Trigger an Audit by'), self::ACTION_ASSIGN_TASK => pht('Assign task to'), @@ -739,8 +735,6 @@ abstract class HeraldAdapter extends Phobject { break; case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: $standard = array( - self::ACTION_ADD_CC => pht('Add me as a subscriber'), - self::ACTION_REMOVE_CC => pht('Remove me as a subscriber'), self::ACTION_EMAIL => pht('Send me an email'), self::ACTION_AUDIT => pht('Trigger an Audit by me'), self::ACTION_ASSIGN_TASK => pht('Assign task to me'), @@ -786,8 +780,6 @@ abstract class HeraldAdapter extends Phobject { if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { switch ($action->getAction()) { case self::ACTION_EMAIL: - case self::ACTION_ADD_CC: - case self::ACTION_REMOVE_CC: case self::ACTION_AUDIT: case self::ACTION_ASSIGN_TASK: case self::ACTION_ADD_REVIEWERS: @@ -828,8 +820,6 @@ abstract class HeraldAdapter extends Phobject { if ($is_personal) { switch ($action) { - case self::ACTION_ADD_CC: - case self::ACTION_REMOVE_CC: case self::ACTION_EMAIL: case self::ACTION_AUDIT: case self::ACTION_ASSIGN_TASK: @@ -843,8 +833,6 @@ abstract class HeraldAdapter extends Phobject { } } else { switch ($action) { - case self::ACTION_ADD_CC: - case self::ACTION_REMOVE_CC: case self::ACTION_EMAIL: return $this->buildTokenizerFieldValue( new PhabricatorMetaMTAMailableDatasource()); @@ -1048,17 +1036,24 @@ abstract class HeraldAdapter extends Phobject { PhabricatorUser $viewer) { $field_type = $condition->getFieldName(); + $field = $this->getFieldImplementation($field_type); - $default = pht('(Unknown Field "%s")', $field_type); + if (!$field) { + return pht('Unknown Field: "%s"', $field_type); + } - $field_name = idx($this->getFieldNameMap(), $field_type, $default); + $field_name = $field->getHeraldFieldName(); $condition_type = $condition->getFieldCondition(); $condition_name = idx($this->getConditionNameMap(), $condition_type); $value = $this->renderConditionValueAsText($condition, $handles, $viewer); - return hsprintf(' %s %s %s', $field_name, $condition_name, $value); + return array( + $field_name, + $condition_name, + $value, + ); } private function renderActionAsText( @@ -1068,8 +1063,10 @@ abstract class HeraldAdapter extends Phobject { $impl = $this->getActionImplementation($action->getAction()); if ($impl) { + $impl->setViewer($viewer); + $value = $action->getTarget(); - return $impl->renderActionDescription($viewer, $value); + return $impl->renderActionDescription($value); } $rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; @@ -1180,14 +1177,16 @@ abstract class HeraldAdapter extends Phobject { */ protected function applyStandardEffect(HeraldEffect $effect) { $action = $effect->getAction(); + $rule_type = $effect->getRule()->getRuleType(); $impl = $this->getActionImplementation($action); if ($impl) { - $impl->applyEffect($this->getObject(), $effect); - return $impl->getApplyTranscript($effect); + if ($impl->supportsRuleType($rule_type)) { + $impl->applyEffect($this->getObject(), $effect); + return $impl->getApplyTranscript($effect); + } } - $rule_type = $effect->getRule()->getRuleType(); $supported = $this->getActions($rule_type); $supported = array_fuse($supported); if (empty($supported[$action])) { @@ -1205,9 +1204,6 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_ADD_PROJECTS: case self::ACTION_REMOVE_PROJECTS: return $this->applyProjectsEffect($effect); - case self::ACTION_ADD_CC: - case self::ACTION_REMOVE_CC: - return $this->applySubscribersEffect($effect); case self::ACTION_EMAIL: return $this->applyEmailEffect($effect); default: @@ -1257,96 +1253,6 @@ abstract class HeraldAdapter extends Phobject { pht('Added projects.')); } - /** - * @task apply - */ - private function applySubscribersEffect(HeraldEffect $effect) { - if ($effect->getAction() == self::ACTION_ADD_CC) { - $kind = '+'; - $is_add = true; - } else { - $kind = '-'; - $is_add = false; - } - - $subscriber_phids = array_fuse($effect->getTarget()); - if (!$subscriber_phids) { - return new HeraldApplyTranscript( - $effect, - false, - pht('This action lists no users or objects to affect.')); - } - - // The "Add Subscribers" rule only adds subscribers who haven't previously - // unsubscribed from the object explicitly. Filter these subscribers out - // before continuing. - $unsubscribed = array(); - if ($is_add) { - if ($this->unsubscribedPHIDs === null) { - $this->unsubscribedPHIDs = PhabricatorEdgeQuery::loadDestinationPHIDs( - $this->getObject()->getPHID(), - PhabricatorObjectHasUnsubscriberEdgeType::EDGECONST); - } - - foreach ($this->unsubscribedPHIDs as $phid) { - if (isset($subscriber_phids[$phid])) { - $unsubscribed[$phid] = $phid; - unset($subscriber_phids[$phid]); - } - } - } - - if (!$subscriber_phids) { - return new HeraldApplyTranscript( - $effect, - false, - pht('All targets have previously unsubscribed explicitly.')); - } - - // Filter out PHIDs which aren't valid subscribers. Lower levels of the - // stack will fail loudly if we try to add subscribers with invalid PHIDs - // or unknown PHID types, so drop them here. - $invalid = array(); - foreach ($subscriber_phids as $phid) { - $type = phid_get_type($phid); - switch ($type) { - case PhabricatorPeopleUserPHIDType::TYPECONST: - case PhabricatorProjectProjectPHIDType::TYPECONST: - break; - default: - $invalid[$phid] = $phid; - unset($subscriber_phids[$phid]); - break; - } - } - - if (!$subscriber_phids) { - return new HeraldApplyTranscript( - $effect, - false, - pht('All targets are invalid as subscribers.')); - } - - $xaction = $this->newTransaction() - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue( - array( - $kind => $subscriber_phids, - )); - - $this->queueTransaction($xaction); - - // TODO: We could be more detailed about this, but doing it meaningfully - // probably requires substantial changes to how transactions are rendered - // first. - if ($is_add) { - $message = pht('Subscribed targets.'); - } else { - $message = pht('Unsubscribed targets.'); - } - - return new HeraldApplyTranscript($effect, true, $message); - } /** * @task apply @@ -1370,5 +1276,15 @@ abstract class HeraldAdapter extends Phobject { pht('Added mailable to mail targets.')); } + public function loadEdgePHIDs($type) { + if (!isset($this->edgeCache[$type])) { + $phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $this->getObject()->getPHID(), + $type); + + $this->edgeCache[$type] = array_fuse($phids); + } + return $this->edgeCache[$type]; + } } diff --git a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php index 7aad56ee24..0ef1399e54 100644 --- a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php +++ b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php @@ -72,8 +72,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_ASSIGN_TASK, ), @@ -81,8 +79,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_ASSIGN_TASK, ), diff --git a/src/applications/pholio/herald/HeraldPholioMockAdapter.php b/src/applications/pholio/herald/HeraldPholioMockAdapter.php index 330f4294ad..57fa74c3a1 100644 --- a/src/applications/pholio/herald/HeraldPholioMockAdapter.php +++ b/src/applications/pholio/herald/HeraldPholioMockAdapter.php @@ -47,25 +47,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter { } } - public function getActions($rule_type) { - switch ($rule_type) { - case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: - return array_merge( - array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, - ), - parent::getActions($rule_type)); - case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: - return array_merge( - array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, - ), - parent::getActions($rule_type)); - } - } - public function getHeraldName() { return 'M'.$this->getMock()->getID(); } diff --git a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php index 05a09fb877..17128a5773 100644 --- a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php +++ b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php @@ -53,16 +53,12 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, self::ACTION_EMAIL, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( - self::ACTION_ADD_CC, - self::ACTION_REMOVE_CC, self::ACTION_EMAIL, ), parent::getActions($rule_type)); diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php new file mode 100644 index 0000000000..44199422b0 --- /dev/null +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php @@ -0,0 +1,37 @@ +getRule()->getAuthorPHID(); + return $this->applySubscribe(array($phid), $is_add = true); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Add rule author as subscriber.'); + } + +} diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php new file mode 100644 index 0000000000..7fbec15e9b --- /dev/null +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php @@ -0,0 +1,40 @@ +applySubscribe($effect->getTarget(), $is_add = true); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorMetaMTAMailableDatasource(); + } + + public function renderActionDescription($value) { + return pht('Add subscribers: %s.', $this->renderHandleList($value)); + } + +} diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php new file mode 100644 index 0000000000..66e0d62959 --- /dev/null +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php @@ -0,0 +1,235 @@ +getAdapter(); + + if ($is_add) { + $kind = '+'; + } else { + $kind = '-'; + } + + $subscriber_phids = array_fuse($phids); + if (!$subscriber_phids) { + $this->logEffect(self::DO_NO_TARGETS); + return; + } + + // The "Add Subscribers" rule only adds subscribers who haven't previously + // unsubscribed from the object explicitly. Filter these subscribers out + // before continuing. + if ($is_add) { + $unsubscribed = $adapter->loadEdgePHIDs( + PhabricatorObjectHasUnsubscriberEdgeType::EDGECONST); + + foreach ($unsubscribed as $phid) { + if (isset($subscriber_phids[$phid])) { + $unsubscribed[$phid] = $phid; + unset($subscriber_phids[$phid]); + } + } + + if ($unsubscribed) { + $this->logEffect( + self::DO_PREVIOUSLY_UNSUBSCRIBED, + array_values($unsubscribed)); + } + } + + if (!$subscriber_phids) { + return; + } + + // Filter out PHIDs which aren't valid subscribers. Lower levels of the + // stack will fail loudly if we try to add subscribers with invalid PHIDs + // or unknown PHID types, so drop them here. + $invalid = array(); + foreach ($subscriber_phids as $phid) { + $type = phid_get_type($phid); + switch ($type) { + case PhabricatorPeopleUserPHIDType::TYPECONST: + case PhabricatorProjectProjectPHIDType::TYPECONST: + break; + default: + $invalid[$phid] = $phid; + unset($subscriber_phids[$phid]); + break; + } + } + + if ($invalid) { + $this->logEffect(self::DO_INVALID, array_values($invalid)); + } + + if (!$subscriber_phids) { + return; + } + + $auto = array(); + $object = $adapter->getObject(); + foreach ($subscriber_phids as $phid) { + if ($object->isAutomaticallySubscribed($phid)) { + $auto[$phid] = $phid; + unset($subscriber_phids[$phid]); + } + } + + if ($auto) { + $this->logEffect(self::DO_AUTOSUBSCRIBED, array_values($auto)); + } + + if (!$subscriber_phids) { + return; + } + + $current = $adapter->loadEdgePHIDs( + PhabricatorObjectHasSubscriberEdgeType::EDGECONST); + + if ($is_add) { + $already = array(); + foreach ($subscriber_phids as $phid) { + if (isset($current[$phid])) { + $already[$phid] = $phid; + unset($subscriber_phids[$phid]); + } + } + + if ($already) { + $this->logEffect(self::DO_ALREADY_SUBSCRIBED, $already); + } + } else { + $already = array(); + foreach ($subscriber_phids as $phid) { + if (empty($current[$phid])) { + $already[$phid] = $phid; + unset($subscriber_phids[$phid]); + } + } + + if ($already) { + $this->logEffect(self::DO_ALREADY_UNSUBSCRIBED, $already); + } + } + + if (!$subscriber_phids) { + return; + } + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) + ->setNewValue( + array( + $kind => $subscriber_phids, + )); + + $adapter->queueTransaction($xaction); + + if ($is_add) { + $this->logEffect(self::DO_SUBSCRIBED, $subscriber_phids); + } else { + $this->logEffect(self::DO_UNSUBSCRIBED, $subscriber_phids); + } + } + + protected function getActionEffectMap() { + return array( + self::DO_NO_TARGETS => array( + 'icon' => 'fa-ban', + 'color' => 'grey', + 'name' => pht('No Targets'), + ), + self::DO_PREVIOUSLY_UNSUBSCRIBED => array( + 'icon' => 'fa-minus-circle', + 'color' => 'grey', + 'name' => pht('Previously Unsubscribed'), + ), + self::DO_AUTOSUBSCRIBED => array( + 'icon' => 'fa-envelope', + 'color' => 'grey', + 'name' => pht('Automatically Subscribed'), + ), + self::DO_INVALID => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Invalid Targets'), + ), + self::DO_ALREADY_SUBSCRIBED => array( + 'icon' => 'fa-chevron-right', + 'color' => 'grey', + 'name' => pht('Already Subscribed'), + ), + self::DO_ALREADY_UNSUBSCRIBED => array( + 'icon' => 'fa-chevron-right', + 'color' => 'grey', + 'name' => pht('Already Unsubscribed'), + ), + self::DO_SUBSCRIBED => array( + 'icon' => 'fa-envelope', + 'color' => 'green', + 'name' => pht('Added Subscribers'), + ), + self::DO_UNSUBSCRIBED => array( + 'icon' => 'fa-minus-circle', + 'color' => 'green', + 'name' => pht('Removed Subscribers'), + ), + ); + } + + public function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_NO_TARGETS: + return pht('Rule lists no targets.'); + case self::DO_PREVIOUSLY_UNSUBSCRIBED: + return pht( + 'Declined to resubscribe %s target(s) because they previously '. + 'unsubscribed: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_INVALID: + return pht( + 'Declined to act on %s invalid target(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_AUTOSUBSCRIBED: + return pht( + '%s automatically subscribed target(s) were not affected: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ALREADY_SUBSCRIBED: + return pht( + '%s target(s) are already subscribed: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ALREADY_UNSUBSCRIBED: + return pht( + '%s target(s) are not subscribed: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_SUBSCRIBED: + return pht( + 'Added %s subscriber(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_UNSUBSCRIBED: + return pht( + 'Removed %s subscriber(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + } + } + + +} diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php new file mode 100644 index 0000000000..324a04c449 --- /dev/null +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php @@ -0,0 +1,37 @@ +getRule()->getAuthorPHID(); + return $this->applySubscribe(array($phid), $is_add = false); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Remove rule author as subscriber.'); + } + +} diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php new file mode 100644 index 0000000000..71f433811e --- /dev/null +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php @@ -0,0 +1,40 @@ +applySubscribe($effect->getTarget(), $is_add = false); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorMetaMTAMailableDatasource(); + } + + public function renderActionDescription($value) { + return pht('Remove subscribers: %s.', $this->renderHandleList($value)); + } + +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index d0466fbb7d..c52137bdf2 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1182,6 +1182,7 @@ final class PhabricatorUSEnglishTranslation '%s Task', '%s Tasks', ), + '%s added %s badge(s) for %s: %s.' => array( array( '%s added a badge for %s: %3$s.', @@ -1256,6 +1257,39 @@ final class PhabricatorUSEnglishTranslation ), ), + '%s automatically subscribed target(s) were not affected: %s.' => array( + 'An automatically subscribed target was not affected: %2$s.', + 'Automatically subscribed targets were not affected: %2$s.', + ), + + 'Declined to resubscribe %s target(s) because they previously '. + 'unsubscribed: %s.' => array( + 'Delined to resubscribe a target because they previously '. + 'unsubscribed: %2$s.', + 'Declined to resubscribe targets because they previously '. + 'unsubscribed: %2$s.', + ), + + '%s target(s) are not subscribed: %s.' => array( + 'A target is not subscribed: %2$s.', + 'Targets are not subscribed: %2$s.', + ), + + '%s target(s) are already subscribed: %s.' => array( + 'A target is already subscribed: %2$s.', + 'Targets are already subscribed: %2$s.', + ), + + 'Added %s subscriber(s): %s.' => array( + 'Added a subscriber: %2$s.', + 'Added subscribers: %2$s.', + ), + + 'Removed %s subscriber(s): %s.' => array( + 'Removed a subscriber: %2$s.', + 'Removed subscribers: %2$s.', + ), + ); } From 51fead17cf132864165b912af44f02683d0aa871 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 24 Jul 2015 08:19:47 -0700 Subject: [PATCH 061/102] Modularize "Send an Email" Herald actions Summary: Ref T8726. No surprises. Test Plan: Created rules using both action variants, applied upgrade, saw rules still work correctly. {F658842} Reviewers: btrahan Reviewed By: btrahan Subscribers: joshuaspence, epriestley Maniphest Tasks: T8726 Differential Revision: https://secure.phabricator.com/D13701 --- .../sql/autopatches/20150724.herald.2.sql | 13 ++++ src/__phutil_library_map__.php | 6 ++ .../HeraldDifferentialRevisionAdapter.php | 2 - .../diffusion/herald/HeraldCommitAdapter.php | 2 - .../herald/HeraldPreCommitAdapter.php | 2 - .../herald/adapter/HeraldAdapter.php | 41 +++-------- .../herald/HeraldManiphestTaskAdapter.php | 2 - .../PhabricatorMetaMTAEmailHeraldAction.php | 69 +++++++++++++++++++ ...bricatorMetaMTAEmailOthersHeraldAction.php | 32 +++++++++ ...habricatorMetaMTAEmailSelfHeraldAction.php | 34 +++++++++ .../herald/PhrictionDocumentHeraldAdapter.php | 18 ----- ...icatorSubscriptionsAddSelfHeraldAction.php | 8 --- ...ubscriptionsAddSubscribersHeraldAction.php | 8 --- .../PhabricatorSubscriptionsHeraldAction.php | 8 +++ ...torSubscriptionsRemoveSelfHeraldAction.php | 8 --- ...criptionsRemoveSubscribersHeraldAction.php | 8 --- .../PhabricatorUSEnglishTranslation.php | 13 ++++ 17 files changed, 183 insertions(+), 91 deletions(-) create mode 100644 resources/sql/autopatches/20150724.herald.2.sql create mode 100644 src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php create mode 100644 src/applications/metamta/herald/PhabricatorMetaMTAEmailOthersHeraldAction.php create mode 100644 src/applications/metamta/herald/PhabricatorMetaMTAEmailSelfHeraldAction.php diff --git a/resources/sql/autopatches/20150724.herald.2.sql b/resources/sql/autopatches/20150724.herald.2.sql new file mode 100644 index 0000000000..eef860a24c --- /dev/null +++ b/resources/sql/autopatches/20150724.herald.2.sql @@ -0,0 +1,13 @@ +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'email.other' + WHERE r.ruleType != 'personal' + AND a.action = 'email'; + +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'email.self' + WHERE r.ruleType = 'personal' + AND a.action = 'email'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8904abc6de..f46f0538b1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2274,6 +2274,9 @@ phutil_register_library_map(array( 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/PhabricatorMetaMTADAO.php', 'PhabricatorMetaMTAEmailBodyParser' => 'applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php', 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/parser/__tests__/PhabricatorMetaMTAEmailBodyParserTestCase.php', + 'PhabricatorMetaMTAEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php', + 'PhabricatorMetaMTAEmailOthersHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailOthersHeraldAction.php', + 'PhabricatorMetaMTAEmailSelfHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailSelfHeraldAction.php', 'PhabricatorMetaMTAErrorMailAction' => 'applications/metamta/action/PhabricatorMetaMTAErrorMailAction.php', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php', 'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php', @@ -6170,6 +6173,9 @@ phutil_register_library_map(array( 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTAEmailBodyParser' => 'Phobject', 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase', + 'PhabricatorMetaMTAEmailHeraldAction' => 'HeraldAction', + 'PhabricatorMetaMTAEmailOthersHeraldAction' => 'PhabricatorMetaMTAEmailHeraldAction', + 'PhabricatorMetaMTAEmailSelfHeraldAction' => 'PhabricatorMetaMTAEmailHeraldAction', 'PhabricatorMetaMTAErrorMailAction' => 'PhabricatorSystemAction', 'PhabricatorMetaMTAMail' => array( 'PhabricatorMetaMTADAO', diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index 47a132338a..a2d7f002d3 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -162,7 +162,6 @@ final class HeraldDifferentialRevisionAdapter case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( - self::ACTION_EMAIL, self::ACTION_ADD_REVIEWERS, self::ACTION_ADD_BLOCKING_REVIEWERS, self::ACTION_APPLY_BUILD_PLANS, @@ -172,7 +171,6 @@ final class HeraldDifferentialRevisionAdapter case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( - self::ACTION_EMAIL, self::ACTION_ADD_REVIEWERS, self::ACTION_ADD_BLOCKING_REVIEWERS, ), diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 302027b539..41971e1c2f 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -89,7 +89,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: return array_merge( array( - self::ACTION_EMAIL, self::ACTION_AUDIT, self::ACTION_APPLY_BUILD_PLANS, ), @@ -97,7 +96,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( - self::ACTION_EMAIL, self::ACTION_AUDIT, ), parent::getActions($rule_type)); diff --git a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php index 7e5e117d85..448f4eaefd 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php @@ -80,13 +80,11 @@ abstract class HeraldPreCommitAdapter extends HeraldAdapter { return array_merge( array( self::ACTION_BLOCK, - self::ACTION_EMAIL, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( - self::ACTION_EMAIL, ), parent::getActions($rule_type)); } diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 4e7e49ea66..3de06e63d6 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -26,7 +26,6 @@ abstract class HeraldAdapter extends Phobject { const CONDITION_IS_TRUE = 'true'; const CONDITION_IS_FALSE = 'false'; - const ACTION_EMAIL = 'email'; const ACTION_AUDIT = 'audit'; const ACTION_ASSIGN_TASK = 'assigntask'; const ACTION_ADD_PROJECTS = 'addprojects'; @@ -56,6 +55,14 @@ abstract class HeraldAdapter extends Phobject { return array_values($this->forcedEmailPHIDs); } + public function addEmailPHID($phid, $force) { + $this->emailPHIDs[$phid] = $phid; + if ($force) { + $this->forcedEmailPHIDs[$phid] = $phid; + } + return $this; + } + public function getCustomActions() { if ($this->customActions === null) { $custom_actions = id(new PhutilSymbolLoader()) @@ -721,7 +728,6 @@ abstract class HeraldAdapter extends Phobject { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: $standard = array( - self::ACTION_EMAIL => pht('Send an email to'), self::ACTION_AUDIT => pht('Trigger an Audit by'), self::ACTION_ASSIGN_TASK => pht('Assign task to'), self::ACTION_ADD_PROJECTS => pht('Add projects'), @@ -735,7 +741,6 @@ abstract class HeraldAdapter extends Phobject { break; case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: $standard = array( - self::ACTION_EMAIL => pht('Send me an email'), self::ACTION_AUDIT => pht('Trigger an Audit by me'), self::ACTION_ASSIGN_TASK => pht('Assign task to me'), self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'), @@ -779,7 +784,6 @@ abstract class HeraldAdapter extends Phobject { $rule_type = $rule->getRuleType(); if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { switch ($action->getAction()) { - case self::ACTION_EMAIL: case self::ACTION_AUDIT: case self::ACTION_ASSIGN_TASK: case self::ACTION_ADD_REVIEWERS: @@ -820,7 +824,6 @@ abstract class HeraldAdapter extends Phobject { if ($is_personal) { switch ($action) { - case self::ACTION_EMAIL: case self::ACTION_AUDIT: case self::ACTION_ASSIGN_TASK: case self::ACTION_ADD_REVIEWERS: @@ -833,9 +836,6 @@ abstract class HeraldAdapter extends Phobject { } } else { switch ($action) { - case self::ACTION_EMAIL: - return $this->buildTokenizerFieldValue( - new PhabricatorMetaMTAMailableDatasource()); case self::ACTION_ADD_PROJECTS: case self::ACTION_REMOVE_PROJECTS: return $this->buildTokenizerFieldValue( @@ -1204,8 +1204,6 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_ADD_PROJECTS: case self::ACTION_REMOVE_PROJECTS: return $this->applyProjectsEffect($effect); - case self::ACTION_EMAIL: - return $this->applyEmailEffect($effect); default: break; } @@ -1253,29 +1251,6 @@ abstract class HeraldAdapter extends Phobject { pht('Added projects.')); } - - /** - * @task apply - */ - private function applyEmailEffect(HeraldEffect $effect) { - foreach ($effect->getTarget() as $phid) { - $this->emailPHIDs[$phid] = $phid; - - // If this is a personal rule, we'll force delivery of a real email. This - // effect is stronger than notification preferences, so you get an actual - // email even if your preferences are set to "Notify" or "Ignore". - $rule = $effect->getRule(); - if ($rule->isPersonalRule()) { - $this->forcedEmailPHIDs[$phid] = $phid; - } - } - - return new HeraldApplyTranscript( - $effect, - true, - pht('Added mailable to mail targets.')); - } - public function loadEdgePHIDs($type) { if (!isset($this->edgeCache[$type])) { $phids = PhabricatorEdgeQuery::loadDestinationPHIDs( diff --git a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php index 0ef1399e54..83ca7c5333 100644 --- a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php +++ b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php @@ -72,14 +72,12 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( - self::ACTION_EMAIL, self::ACTION_ASSIGN_TASK, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( - self::ACTION_EMAIL, self::ACTION_ASSIGN_TASK, ), parent::getActions($rule_type)); diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php new file mode 100644 index 0000000000..07f216447d --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php @@ -0,0 +1,69 @@ +getAdapter(); + + foreach ($phids as $phid) { + $adapter->addEmailPHID($phid, $force); + } + + if ($force) { + $this->logEffect(self::DO_FORCE, $phids); + } else { + $this->logEffect(self::DO_SEND, $phids); + } + } + + protected function getActionEffectMap() { + return array( + self::DO_SEND => array( + 'icon' => 'fa-envelope', + 'color' => 'green', + 'name' => pht('Sent Mail'), + ), + self::DO_FORCE => array( + 'icon' => 'fa-envelope', + 'color' => 'blue', + 'name' => pht('Forced Mail'), + ), + ); + } + + public function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_SEND: + return pht( + 'Queued email to be delivered to %s target(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_FORCE: + return pht( + 'Queued email to be delivered to %s target(s), ignoring their '. + 'notification preferences: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + } + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailOthersHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailOthersHeraldAction.php new file mode 100644 index 0000000000..d569848e01 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailOthersHeraldAction.php @@ -0,0 +1,32 @@ +applyEmail($effect->getTarget(), $force = false); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorMetaMTAMailableDatasource(); + } + + public function renderActionDescription($value) { + return pht('Send an email to: %s.', $this->renderHandleList($value)); + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailSelfHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailSelfHeraldAction.php new file mode 100644 index 0000000000..67563da521 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailSelfHeraldAction.php @@ -0,0 +1,34 @@ +getRule()->getAuthorPHID(); + + // For personal rules, we'll force delivery of a real email. This effect + // is stronger than notification preferences, so you get an actual email + // even if your preferences are set to "Notify" or "Ignore". + + return $this->applyEmail(array($phid), $force = true); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Send an email to rule author.'); + } + +} diff --git a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php index 17128a5773..575d4b3dd8 100644 --- a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php +++ b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php @@ -48,24 +48,6 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter { } } - public function getActions($rule_type) { - switch ($rule_type) { - case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: - return array_merge( - array( - self::ACTION_EMAIL, - ), - parent::getActions($rule_type)); - case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: - return array_merge( - array( - self::ACTION_EMAIL, - ), - parent::getActions($rule_type)); - } - } - - public function getHeraldName() { return pht('Wiki Document %d', $this->getDocument()->getID()); } diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php index 44199422b0..20df5e06c4 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php @@ -9,14 +9,6 @@ final class PhabricatorSubscriptionsAddSelfHeraldAction return pht('Add me as a subscriber'); } - public function getActionGroupKey() { - return HeraldSupportActionGroup::ACTIONGROUPKEY; - } - - public function supportsObject($object) { - return ($object instanceof PhabricatorSubscribableInterface); - } - public function supportsRuleType($rule_type) { return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); } diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php index 7fbec15e9b..14f72d313a 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php @@ -9,14 +9,6 @@ final class PhabricatorSubscriptionsAddSubscribersHeraldAction return pht('Add subscribers'); } - public function getActionGroupKey() { - return HeraldSupportActionGroup::ACTIONGROUPKEY; - } - - public function supportsObject($object) { - return ($object instanceof PhabricatorSubscribableInterface); - } - public function supportsRuleType($rule_type) { return ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); } diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php index 66e0d62959..374fcfa1fb 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php @@ -12,6 +12,14 @@ abstract class PhabricatorSubscriptionsHeraldAction const DO_SUBSCRIBED = 'do.subscribed'; const DO_UNSUBSCRIBED = 'do.unsubscribed'; + public function getActionGroupKey() { + return HeraldSupportActionGroup::ACTIONGROUPKEY; + } + + public function supportsObject($object) { + return ($object instanceof PhabricatorSubscribableInterface); + } + protected function applySubscribe(array $phids, $is_add) { $adapter = $this->getAdapter(); diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php index 324a04c449..5d751cb54a 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php @@ -9,14 +9,6 @@ final class PhabricatorSubscriptionsRemoveSelfHeraldAction return pht('Remove me as a subscriber'); } - public function getActionGroupKey() { - return HeraldSupportActionGroup::ACTIONGROUPKEY; - } - - public function supportsObject($object) { - return ($object instanceof PhabricatorSubscribableInterface); - } - public function supportsRuleType($rule_type) { return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); } diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php index 71f433811e..67ece2cb72 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php @@ -9,14 +9,6 @@ final class PhabricatorSubscriptionsRemoveSubscribersHeraldAction return pht('Remove subscribers'); } - public function getActionGroupKey() { - return HeraldSupportActionGroup::ACTIONGROUPKEY; - } - - public function supportsObject($object) { - return ($object instanceof PhabricatorSubscribableInterface); - } - public function supportsRuleType($rule_type) { return ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index c52137bdf2..a23ce1293c 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1290,6 +1290,19 @@ final class PhabricatorUSEnglishTranslation 'Removed subscribers: %2$s.', ), + 'Queued email to be delivered to %s target(s): %s.' => array( + 'Queued email to be delivered to target: %2$s.', + 'Queued email to be delivered to targets: %2$s.', + ), + + 'Queued email to be delivered to %s target(s), ignoring their '. + 'notification preferences: %s.' => array( + 'Queued email to be delivered to target, ignoring notification '. + 'preferences: %2$s.', + 'Queued email to be delivered to targets, ignoring notification '. + 'preferences: %2$s.', + ), + ); } From 37829926702bd7bc3e1fd8b97411e9aee4cf1f02 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 24 Jul 2015 10:56:52 -0700 Subject: [PATCH 062/102] Modularize "add projects" and "remove projects" Herald actions Summary: Ref T8726. Convert these to be modular. Test Plan: - Created rules using these actions. - Upgraded them. - Verified they still work. {F659266} Reviewers: btrahan Reviewed By: btrahan Subscribers: joshuaspence, epriestley Maniphest Tasks: T8726 Differential Revision: https://secure.phabricator.com/D13705 --- .../sql/autopatches/20150724.herald.3.sql | 11 ++ src/__phutil_library_map__.php | 6 + .../herald/adapter/HeraldAdapter.php | 58 ------ .../PhabricatorProjectAddHeraldAction.php | 28 +++ .../herald/PhabricatorProjectHeraldAction.php | 180 ++++++++++++++++++ .../PhabricatorProjectRemoveHeraldAction.php | 28 +++ .../PhabricatorUSEnglishTranslation.php | 20 ++ 7 files changed, 273 insertions(+), 58 deletions(-) create mode 100644 resources/sql/autopatches/20150724.herald.3.sql create mode 100644 src/applications/project/herald/PhabricatorProjectAddHeraldAction.php create mode 100644 src/applications/project/herald/PhabricatorProjectHeraldAction.php create mode 100644 src/applications/project/herald/PhabricatorProjectRemoveHeraldAction.php diff --git a/resources/sql/autopatches/20150724.herald.3.sql b/resources/sql/autopatches/20150724.herald.3.sql new file mode 100644 index 0000000000..58caa58407 --- /dev/null +++ b/resources/sql/autopatches/20150724.herald.3.sql @@ -0,0 +1,11 @@ +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'projects.add' + WHERE a.action = 'addprojects'; + +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'projects.remove' + WHERE a.action = 'removeprojects'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f46f0538b1..1ddf479fa5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2540,6 +2540,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php', 'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php', 'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php', + 'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php', 'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php', 'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php', 'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php', @@ -2572,6 +2573,7 @@ phutil_register_library_map(array( 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', + 'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php', 'PhabricatorProjectIcon' => 'applications/project/icon/PhabricatorProjectIcon.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', @@ -2593,6 +2595,7 @@ phutil_register_library_map(array( 'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php', 'PhabricatorProjectProjectPHIDType' => 'applications/project/phid/PhabricatorProjectProjectPHIDType.php', 'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php', + 'PhabricatorProjectRemoveHeraldAction' => 'applications/project/herald/PhabricatorProjectRemoveHeraldAction.php', 'PhabricatorProjectSchemaSpec' => 'applications/project/storage/PhabricatorProjectSchemaSpec.php', 'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php', 'PhabricatorProjectSearchField' => 'applications/project/searchfield/PhabricatorProjectSearchField.php', @@ -6496,6 +6499,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectApplication' => 'PhabricatorApplication', 'PhabricatorProjectArchiveController' => 'PhabricatorProjectController', 'PhabricatorProjectBoardController' => 'PhabricatorProjectController', @@ -6539,6 +6543,7 @@ phutil_register_library_map(array( 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorProjectFeedController' => 'PhabricatorProjectController', + 'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectIcon' => 'Phobject', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectLogicalAndDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -6559,6 +6564,7 @@ phutil_register_library_map(array( 'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectProjectPHIDType' => 'PhabricatorPHIDType', 'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorProjectRemoveHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorProjectSearchField' => 'PhabricatorSearchTokenizerField', diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 3de06e63d6..f2466d94f2 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -28,8 +28,6 @@ abstract class HeraldAdapter extends Phobject { const ACTION_AUDIT = 'audit'; const ACTION_ASSIGN_TASK = 'assigntask'; - const ACTION_ADD_PROJECTS = 'addprojects'; - const ACTION_REMOVE_PROJECTS = 'removeprojects'; const ACTION_ADD_REVIEWERS = 'addreviewers'; const ACTION_ADD_BLOCKING_REVIEWERS = 'addblockingreviewers'; const ACTION_APPLY_BUILD_PLANS = 'applybuildplans'; @@ -707,15 +705,6 @@ abstract class HeraldAdapter extends Phobject { $actions = $custom_actions; - $object = $this->newObject(); - - if (($object instanceof PhabricatorProjectInterface)) { - if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) { - $actions[] = self::ACTION_ADD_PROJECTS; - $actions[] = self::ACTION_REMOVE_PROJECTS; - } - } - foreach ($this->getActionsForRuleType($rule_type) as $key => $action) { $actions[] = $key; } @@ -730,8 +719,6 @@ abstract class HeraldAdapter extends Phobject { $standard = array( self::ACTION_AUDIT => pht('Trigger an Audit by'), self::ACTION_ASSIGN_TASK => pht('Assign task to'), - self::ACTION_ADD_PROJECTS => pht('Add projects'), - self::ACTION_REMOVE_PROJECTS => pht('Remove projects'), self::ACTION_ADD_REVIEWERS => pht('Add reviewers'), self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add blocking reviewers'), self::ACTION_APPLY_BUILD_PLANS => pht('Run build plans'), @@ -829,17 +816,9 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_ADD_REVIEWERS: case self::ACTION_ADD_BLOCKING_REVIEWERS: return new HeraldEmptyFieldValue(); - case self::ACTION_ADD_PROJECTS: - case self::ACTION_REMOVE_PROJECTS: - return $this->buildTokenizerFieldValue( - new PhabricatorProjectDatasource()); } } else { switch ($action) { - case self::ACTION_ADD_PROJECTS: - case self::ACTION_REMOVE_PROJECTS: - return $this->buildTokenizerFieldValue( - new PhabricatorProjectDatasource()); case self::ACTION_ASSIGN_TASK: return $this->buildTokenizerFieldValue( new PhabricatorPeopleDatasource()); @@ -1200,14 +1179,6 @@ abstract class HeraldAdapter extends Phobject { $rule_type)); } - switch ($action) { - case self::ACTION_ADD_PROJECTS: - case self::ACTION_REMOVE_PROJECTS: - return $this->applyProjectsEffect($effect); - default: - break; - } - $result = $this->handleCustomHeraldEffect($effect); if (!$result) { @@ -1222,35 +1193,6 @@ abstract class HeraldAdapter extends Phobject { return $result; } - /** - * @task apply - */ - private function applyProjectsEffect(HeraldEffect $effect) { - - if ($effect->getAction() == self::ACTION_ADD_PROJECTS) { - $kind = '+'; - } else { - $kind = '-'; - } - - $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $project_phids = $effect->getTarget(); - $xaction = $this->newTransaction() - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $project_type) - ->setNewValue( - array( - $kind => array_fuse($project_phids), - )); - - $this->queueTransaction($xaction); - - return new HeraldApplyTranscript( - $effect, - true, - pht('Added projects.')); - } - public function loadEdgePHIDs($type) { if (!isset($this->edgeCache[$type])) { $phids = PhabricatorEdgeQuery::loadDestinationPHIDs( diff --git a/src/applications/project/herald/PhabricatorProjectAddHeraldAction.php b/src/applications/project/herald/PhabricatorProjectAddHeraldAction.php new file mode 100644 index 0000000000..5d2a348eca --- /dev/null +++ b/src/applications/project/herald/PhabricatorProjectAddHeraldAction.php @@ -0,0 +1,28 @@ +applyProjects($effect->getTarget(), $is_add = true); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorProjectDatasource(); + } + + public function renderActionDescription($value) { + return pht('Add projects: %s.', $this->renderHandleList($value)); + } + +} diff --git a/src/applications/project/herald/PhabricatorProjectHeraldAction.php b/src/applications/project/herald/PhabricatorProjectHeraldAction.php new file mode 100644 index 0000000000..599a218213 --- /dev/null +++ b/src/applications/project/herald/PhabricatorProjectHeraldAction.php @@ -0,0 +1,180 @@ +getAdapter(); + + if (!$phids) { + $this->logEffect(self::DO_NO_TARGETS); + return; + } + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($phids) + ->execute(); + $projects = mpull($projects, null, 'getPHID'); + + $invalid = array(); + foreach ($phids as $phid) { + if (empty($projects[$phid])) { + $invalid[] = $phid; + unset($phids[$phid]); + } + } + + if ($invalid) { + $this->logEffect(self::DO_INVALID, $invalid); + } + + if (!$phids) { + return; + } + + $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + + $current = $adapter->loadEdgePHIDs($project_type); + + if ($is_add) { + $already = array(); + foreach ($phids as $phid) { + if (isset($current[$phid])) { + $already[$phid] = $phid; + unset($phids[$phid]); + } + } + + if ($already) { + $this->logEffect(self::DO_ALREADY_ASSOCIATED, $already); + } + } else { + $already = array(); + foreach ($phids as $phid) { + if (empty($current[$phid])) { + $already[$phid] = $phid; + unset($phids[$phid]); + } + } + + if ($already) { + $this->logEffect(self::DO_ALREADY_UNASSOCIATED, $already); + } + } + + if (!$phids) { + return; + } + + if ($is_add) { + $kind = '+'; + } else { + $kind = '-'; + } + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $project_type) + ->setNewValue( + array( + $kind => $phids, + )); + + $adapter->queueTransaction($xaction); + + if ($is_add) { + $this->logEffect(self::DO_ADD_PROJECTS, $phids); + } else { + $this->logEffect(self::DO_REMOVE_PROJECTS, $phids); + } + } + + protected function getActionEffectMap() { + return array( + self::DO_NO_TARGETS => array( + 'icon' => 'fa-ban', + 'color' => 'grey', + 'name' => pht('No Targets'), + ), + self::DO_INVALID => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Invalid Targets'), + ), + self::DO_ALREADY_ASSOCIATED => array( + 'icon' => 'fa-chevron-right', + 'color' => 'grey', + 'name' => pht('Already Associated'), + ), + self::DO_ALREADY_UNASSOCIATED => array( + 'icon' => 'fa-chevron-right', + 'color' => 'grey', + 'name' => pht('Already Unassociated'), + ), + self::DO_ADD_PROJECTS => array( + 'icon' => 'fa-briefcase', + 'color' => 'green', + 'name' => pht('Added Projects'), + ), + self::DO_REMOVE_PROJECTS => array( + 'icon' => 'fa-minus-circle', + 'color' => 'green', + 'name' => pht('Removed Projects'), + ), + ); + } + + public function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_NO_TARGETS: + return pht('Rule lists no projects.'); + case self::DO_INVALID: + return pht( + 'Declined to act on %s invalid project(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ALREADY_ASSOCIATED: + return pht( + '%s project(s) are already associated: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ALREADY_UNASSOCIATED: + return pht( + '%s project(s) are not associated: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ADD_PROJECTS: + return pht( + 'Added %s project(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_REMOVE_PROJECTS: + return pht( + 'Removed %s project(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + } + } + +} diff --git a/src/applications/project/herald/PhabricatorProjectRemoveHeraldAction.php b/src/applications/project/herald/PhabricatorProjectRemoveHeraldAction.php new file mode 100644 index 0000000000..77e7071b13 --- /dev/null +++ b/src/applications/project/herald/PhabricatorProjectRemoveHeraldAction.php @@ -0,0 +1,28 @@ +applyProjects($effect->getTarget(), $is_add = false); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorProjectDatasource(); + } + + public function renderActionDescription($value) { + return pht('Remove projects: %s.', $this->renderHandleList($value)); + } + +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index a23ce1293c..d4343b2ec9 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1303,6 +1303,26 @@ final class PhabricatorUSEnglishTranslation 'preferences: %2$s.', ), + '%s project(s) are not associated: %s.' => array( + 'A project is not associated: %2$s.', + 'Projects are not associated: %2$s.', + ), + + '%s project(s) are already associated: %s.' => array( + 'A project is already associated: %2$s.', + 'Projects are already associated: %2$s.', + ), + + 'Added %s project(s): %s.' => array( + 'Added a project: %2$s.', + 'Added projects: %2$s.', + ), + + 'Removed %s project(s): %s.' => array( + 'Removed a project: %2$s.', + 'Removed projects: %2$s.', + ), + ); } From 8d8ee18ce0fd7d89094dd16a6766d2b993d2a4af Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 24 Jul 2015 12:51:30 -0700 Subject: [PATCH 063/102] Modularize Maniphest Herald "Assign Task" action Summary: Ref T8726. No surprises here. Test Plan: - Created rules using this action. - Applied migration. - Verified rules still work. {F659324} Reviewers: btrahan Reviewed By: btrahan Subscribers: joshuaspence, epriestley Maniphest Tasks: T8726 Differential Revision: https://secure.phabricator.com/D13706 --- .../sql/autopatches/20150724.herald.4.sql | 13 ++ src/__phutil_library_map__.php | 8 ++ .../action/HeraldApplicationActionGroup.php | 15 +++ .../herald/adapter/HeraldAdapter.php | 8 -- .../controller/HeraldTranscriptController.php | 4 +- .../editor/ManiphestTransactionEditor.php | 17 --- .../herald/HeraldManiphestTaskAdapter.php | 50 -------- .../ManiphestTaskAssignHeraldAction.php | 116 ++++++++++++++++++ .../ManiphestTaskAssignOtherHeraldAction.php | 34 +++++ .../ManiphestTaskAssignSelfHeraldAction.php | 29 +++++ 10 files changed, 218 insertions(+), 76 deletions(-) create mode 100644 resources/sql/autopatches/20150724.herald.4.sql create mode 100644 src/applications/herald/action/HeraldApplicationActionGroup.php create mode 100644 src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php create mode 100644 src/applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php create mode 100644 src/applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php diff --git a/resources/sql/autopatches/20150724.herald.4.sql b/resources/sql/autopatches/20150724.herald.4.sql new file mode 100644 index 0000000000..193aca9a91 --- /dev/null +++ b/resources/sql/autopatches/20150724.herald.4.sql @@ -0,0 +1,13 @@ +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'maniphest.assign.other' + WHERE r.ruleType != 'personal' + AND a.action = 'assigntask'; + +UPDATE {$NAMESPACE}_herald.herald_actionrecord a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'maniphest.assign.self' + WHERE r.ruleType = 'personal' + AND a.action = 'assigntask'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1ddf479fa5..9a37356065 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1013,6 +1013,7 @@ phutil_register_library_map(array( 'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php', 'HeraldAlwaysField' => 'applications/herald/field/HeraldAlwaysField.php', 'HeraldAnotherRuleField' => 'applications/herald/field/HeraldAnotherRuleField.php', + 'HeraldApplicationActionGroup' => 'applications/herald/action/HeraldApplicationActionGroup.php', 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php', 'HeraldBasicFieldGroup' => 'applications/herald/field/HeraldBasicFieldGroup.php', 'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php', @@ -1186,6 +1187,9 @@ phutil_register_library_map(array( 'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php', 'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php', 'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php', + 'ManiphestTaskAssignHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php', + 'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php', + 'ManiphestTaskAssignSelfHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php', 'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php', 'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php', 'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php', @@ -4710,6 +4714,7 @@ phutil_register_library_map(array( 'HeraldAdapter' => 'Phobject', 'HeraldAlwaysField' => 'HeraldField', 'HeraldAnotherRuleField' => 'HeraldField', + 'HeraldApplicationActionGroup' => 'HeraldActionGroup', 'HeraldApplyTranscript' => 'Phobject', 'HeraldBasicFieldGroup' => 'HeraldFieldGroup', 'HeraldCommitAdapter' => 'HeraldAdapter', @@ -4922,6 +4927,9 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', 'PhabricatorSpacesInterface', ), + 'ManiphestTaskAssignHeraldAction' => 'HeraldAction', + 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', + 'ManiphestTaskAssignSelfHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule', diff --git a/src/applications/herald/action/HeraldApplicationActionGroup.php b/src/applications/herald/action/HeraldApplicationActionGroup.php new file mode 100644 index 0000000000..b2b6e8d2c6 --- /dev/null +++ b/src/applications/herald/action/HeraldApplicationActionGroup.php @@ -0,0 +1,15 @@ + pht('Trigger an Audit by'), - self::ACTION_ASSIGN_TASK => pht('Assign task to'), self::ACTION_ADD_REVIEWERS => pht('Add reviewers'), self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add blocking reviewers'), self::ACTION_APPLY_BUILD_PLANS => pht('Run build plans'), @@ -729,7 +727,6 @@ abstract class HeraldAdapter extends Phobject { case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: $standard = array( self::ACTION_AUDIT => pht('Trigger an Audit by me'), - self::ACTION_ASSIGN_TASK => pht('Assign task to me'), self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'), self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add me as a blocking reviewer'), @@ -772,7 +769,6 @@ abstract class HeraldAdapter extends Phobject { if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { switch ($action->getAction()) { case self::ACTION_AUDIT: - case self::ACTION_ASSIGN_TASK: case self::ACTION_ADD_REVIEWERS: case self::ACTION_ADD_BLOCKING_REVIEWERS: // For personal rules, force these actions to target the rule owner. @@ -812,16 +808,12 @@ abstract class HeraldAdapter extends Phobject { if ($is_personal) { switch ($action) { case self::ACTION_AUDIT: - case self::ACTION_ASSIGN_TASK: case self::ACTION_ADD_REVIEWERS: case self::ACTION_ADD_BLOCKING_REVIEWERS: return new HeraldEmptyFieldValue(); } } else { switch ($action) { - case self::ACTION_ASSIGN_TASK: - return $this->buildTokenizerFieldValue( - new PhabricatorPeopleDatasource()); case self::ACTION_AUDIT: case self::ACTION_ADD_REVIEWERS: case self::ACTION_ADD_BLOCKING_REVIEWERS: diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index 713ce52804..f0bad8d393 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -233,7 +233,9 @@ final class HeraldTranscriptController extends HeraldController { $rule_list = id(new PHUIObjectItemListView()) ->setNoDataString(pht('No Herald rules applied to this object.')); - foreach ($xscript->getRuleTranscripts() as $rule_xscript) { + $rule_xscripts = $xscript->getRuleTranscripts(); + $rule_xscripts = msort($rule_xscripts, 'getRuleID'); + foreach ($rule_xscripts as $rule_xscript) { $rule_id = $rule_xscript->getRuleID(); $rule_item = id(new PHUIObjectItemView()) diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 39111644cb..dcc518e06b 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -506,23 +506,6 @@ final class ManiphestTransactionEditor ->setTask($object); } - protected function didApplyHeraldRules( - PhabricatorLiskDAO $object, - HeraldAdapter $adapter, - HeraldTranscript $transcript) { - - $xactions = array(); - - $assign_phid = $adapter->getAssignPHID(); - if ($assign_phid) { - $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) - ->setNewValue($assign_phid); - } - - return $xactions; - } - protected function requireCapabilities( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { diff --git a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php index 83ca7c5333..d504a71214 100644 --- a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php +++ b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php @@ -3,7 +3,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { private $task; - private $assignPHID; protected function newObject() { return new ManiphestTask(); @@ -55,61 +54,12 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter { return $this->task; } - public function setAssignPHID($assign_phid) { - $this->assignPHID = $assign_phid; - return $this; - } - public function getAssignPHID() { - return $this->assignPHID; - } - public function getAdapterContentName() { return pht('Maniphest Tasks'); } - public function getActions($rule_type) { - switch ($rule_type) { - case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: - return array_merge( - array( - self::ACTION_ASSIGN_TASK, - ), - parent::getActions($rule_type)); - case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: - return array_merge( - array( - self::ACTION_ASSIGN_TASK, - ), - parent::getActions($rule_type)); - } - } - public function getHeraldName() { return 'T'.$this->getTask()->getID(); } - public function applyHeraldEffects(array $effects) { - assert_instances_of($effects, 'HeraldEffect'); - - $result = array(); - foreach ($effects as $effect) { - $action = $effect->getAction(); - switch ($action) { - case self::ACTION_ASSIGN_TASK: - $target_array = $effect->getTarget(); - $assign_phid = reset($target_array); - $this->setAssignPHID($assign_phid); - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Assigned task.')); - break; - default: - $result[] = $this->applyStandardEffect($effect); - break; - } - } - return $result; - } - } diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php new file mode 100644 index 0000000000..9e031bc8b5 --- /dev/null +++ b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php @@ -0,0 +1,116 @@ +logEffect(self::DO_EMPTY); + return; + } + + $adapter = $this->getAdapter(); + $object = $adapter->getObject(); + + if ($object->getOwnerPHID() == $phid) { + $this->logEffect(self::DO_ALREADY, array($phid)); + return; + } + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($phid)) + ->executeOne(); + if (!$user) { + $this->logEffect(self::DO_INVALID, array($phid)); + return; + } + + $can_view = PhabricatorPolicyFilter::hasCapability( + $user, + $object, + PhabricatorPolicyCapability::CAN_VIEW); + if (!$can_view) { + $this->logEffect(self::DO_PERMISSION, array($phid)); + return; + } + + $xaction = $adapter->newTransaction() + ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setNewValue($phid); + + $adapter->queueTransaction($xaction); + + $this->logEffect(self::DO_ASSIGN, array($phid)); + } + + protected function getActionEffectMap() { + return array( + self::DO_EMPTY => array( + 'icon' => 'fa-ban', + 'color' => 'grey', + 'name' => pht('Empty Action'), + ), + self::DO_ALREADY => array( + 'icon' => 'fa-user', + 'color' => 'grey', + 'name' => pht('Already Assigned'), + ), + self::DO_INVALID => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Invalid Owner'), + ), + self::DO_PERMISSION => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('No Permission'), + ), + self::DO_ASSIGN => array( + 'icon' => 'fa-user', + 'color' => 'green', + 'name' => pht('Assigned Task'), + ), + ); + } + + public function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_EMPTY: + return pht('Action lists no user to assign.'); + case self::DO_ALREADY: + return pht( + 'User is already task owner: %s.', + $this->renderHandleList($data)); + case self::DO_INVALID: + return pht( + 'User is invalid: %s.', + $this->renderHandleList($data)); + case self::DO_PERMISSION: + return pht( + 'User does not have permission to see task: %s.', + $this->renderHandleList($data)); + case self::DO_ASSIGN: + return pht( + 'Assigned task to: %s.', + $this->renderHandleList($data)); + } + } + +} diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php new file mode 100644 index 0000000000..6df37056e3 --- /dev/null +++ b/src/applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php @@ -0,0 +1,34 @@ +applyAssign($effect->getTarget()); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + // TODO: Eventually, it would be nice to get "limit = 1" exported from here + // up to the UI. + return new PhabricatorPeopleDatasource(); + } + + public function renderActionDescription($value) { + return pht('Assign task to: %s.', $this->renderHandleList($value)); + } + +} diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php new file mode 100644 index 0000000000..a48bafb53e --- /dev/null +++ b/src/applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php @@ -0,0 +1,29 @@ +getRule()->getAuthorPHID(); + return $this->applyAssign(array($phid)); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Assign task to rule author.'); + } + +} From a335004a91a903766f89505912967c83bcf9e8ff Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Jul 2015 11:39:35 -0700 Subject: [PATCH 064/102] Modularize Differential Reviewer actions in Herald Ref T8726. Modularizes the "Add Reviewers" and "Add Blocking Reviewers" Herald actions. --- ...724.herald.1.sql => 20150730.herald.1.sql} | 8 +- ...724.herald.2.sql => 20150730.herald.2.sql} | 4 +- ...724.herald.3.sql => 20150730.herald.3.sql} | 4 +- ...724.herald.4.sql => 20150730.herald.4.sql} | 4 +- .../sql/autopatches/20150730.herald.5.sql | 27 ++ src/__phutil_library_map__.php | 10 + .../editor/DifferentialTransactionEditor.php | 59 ----- ...iewersAddBlockingReviewersHeraldAction.php | 33 +++ ...alReviewersAddBlockingSelfHeraldAction.php | 30 +++ ...ntialReviewersAddReviewersHeraldAction.php | 33 +++ ...fferentialReviewersAddSelfHeraldAction.php | 30 +++ .../DifferentialReviewersHeraldAction.php | 240 ++++++++++++++++++ .../HeraldDifferentialRevisionAdapter.php | 53 +--- .../herald/adapter/HeraldAdapter.php | 17 -- .../PhabricatorUSEnglishTranslation.php | 10 + 15 files changed, 426 insertions(+), 136 deletions(-) rename resources/sql/autopatches/{20150724.herald.1.sql => 20150730.herald.1.sql} (76%) rename resources/sql/autopatches/{20150724.herald.2.sql => 20150730.herald.2.sql} (75%) rename resources/sql/autopatches/{20150724.herald.3.sql => 20150730.herald.3.sql} (72%) rename resources/sql/autopatches/{20150724.herald.4.sql => 20150730.herald.4.sql} (77%) create mode 100644 resources/sql/autopatches/20150730.herald.5.sql create mode 100644 src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php create mode 100644 src/applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php create mode 100644 src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php create mode 100644 src/applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php create mode 100644 src/applications/differential/herald/DifferentialReviewersHeraldAction.php diff --git a/resources/sql/autopatches/20150724.herald.1.sql b/resources/sql/autopatches/20150730.herald.1.sql similarity index 76% rename from resources/sql/autopatches/20150724.herald.1.sql rename to resources/sql/autopatches/20150730.herald.1.sql index aaf9b69196..e6e097f82c 100644 --- a/resources/sql/autopatches/20150724.herald.1.sql +++ b/resources/sql/autopatches/20150730.herald.1.sql @@ -1,25 +1,25 @@ -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'subscribers.add' WHERE r.ruleType != 'personal' AND a.action = 'addcc'; -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'subscribers.self.add' WHERE r.ruleType = 'personal' AND a.action = 'addcc'; -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'subscribers.remove' WHERE r.ruleType != 'personal' AND a.action = 'remcc'; -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'subscribers.self.remove' diff --git a/resources/sql/autopatches/20150724.herald.2.sql b/resources/sql/autopatches/20150730.herald.2.sql similarity index 75% rename from resources/sql/autopatches/20150724.herald.2.sql rename to resources/sql/autopatches/20150730.herald.2.sql index eef860a24c..3d0b5a0a05 100644 --- a/resources/sql/autopatches/20150724.herald.2.sql +++ b/resources/sql/autopatches/20150730.herald.2.sql @@ -1,11 +1,11 @@ -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'email.other' WHERE r.ruleType != 'personal' AND a.action = 'email'; -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'email.self' diff --git a/resources/sql/autopatches/20150724.herald.3.sql b/resources/sql/autopatches/20150730.herald.3.sql similarity index 72% rename from resources/sql/autopatches/20150724.herald.3.sql rename to resources/sql/autopatches/20150730.herald.3.sql index 58caa58407..e9fcbff59b 100644 --- a/resources/sql/autopatches/20150724.herald.3.sql +++ b/resources/sql/autopatches/20150730.herald.3.sql @@ -1,10 +1,10 @@ -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'projects.add' WHERE a.action = 'addprojects'; -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'projects.remove' diff --git a/resources/sql/autopatches/20150724.herald.4.sql b/resources/sql/autopatches/20150730.herald.4.sql similarity index 77% rename from resources/sql/autopatches/20150724.herald.4.sql rename to resources/sql/autopatches/20150730.herald.4.sql index 193aca9a91..1f5c35a851 100644 --- a/resources/sql/autopatches/20150724.herald.4.sql +++ b/resources/sql/autopatches/20150730.herald.4.sql @@ -1,11 +1,11 @@ -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'maniphest.assign.other' WHERE r.ruleType != 'personal' AND a.action = 'assigntask'; -UPDATE {$NAMESPACE}_herald.herald_actionrecord a +UPDATE {$NAMESPACE}_herald.herald_action a JOIN {$NAMESPACE}_herald.herald_rule r ON a.ruleID = r.id SET a.action = 'maniphest.assign.self' diff --git a/resources/sql/autopatches/20150730.herald.5.sql b/resources/sql/autopatches/20150730.herald.5.sql new file mode 100644 index 0000000000..84c9eea3c5 --- /dev/null +++ b/resources/sql/autopatches/20150730.herald.5.sql @@ -0,0 +1,27 @@ +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'differential.reviewers.blocking' + WHERE r.ruleType != 'personal' + AND a.action = 'addreviewers'; + +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'differential.reviewers.self.blocking' + WHERE r.ruleType = 'personal' + AND a.action = 'addreviewers'; + +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'differential.reviewers.add' + WHERE r.ruleType != 'personal' + AND a.action = 'addblockingreviewers'; + +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'differential.reviewers.self.add' + WHERE r.ruleType = 'personal' + AND a.action = 'addblockingreviewers'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9a37356065..090dbc06e4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -435,7 +435,12 @@ phutil_register_library_map(array( 'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php', 'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php', 'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php', + 'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php', + 'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php', + 'DifferentialReviewersAddReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php', + 'DifferentialReviewersAddSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php', 'DifferentialReviewersField' => 'applications/differential/customfield/DifferentialReviewersField.php', + 'DifferentialReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersHeraldAction.php', 'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php', 'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php', 'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php', @@ -4044,7 +4049,12 @@ phutil_register_library_map(array( 'DifferentialReviewer' => 'Phobject', 'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialReviewerStatus' => 'Phobject', + 'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction', + 'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction', + 'DifferentialReviewersAddReviewersHeraldAction' => 'DifferentialReviewersHeraldAction', + 'DifferentialReviewersAddSelfHeraldAction' => 'DifferentialReviewersHeraldAction', 'DifferentialReviewersField' => 'DifferentialCoreCustomField', + 'DifferentialReviewersHeraldAction' => 'HeraldAction', 'DifferentialReviewersView' => 'AphrontView', 'DifferentialRevision' => array( 'DifferentialDAO', diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index c73bb53956..0e8548c0a6 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1588,11 +1588,6 @@ final class DifferentialTransactionEditor $revision, $revision->getActiveDiff()); - $reviewers = $revision->getReviewerStatus(); - $reviewer_phids = mpull($reviewers, 'getReviewerPHID'); - - $adapter->setExplicitReviewers($reviewer_phids); - return $adapter; } @@ -1603,60 +1598,6 @@ final class DifferentialTransactionEditor $xactions = array(); - // Build a transaction to adjust reviewers. - $reviewers = array( - DifferentialReviewerStatus::STATUS_ADDED => - array_keys($adapter->getReviewersAddedByHerald()), - DifferentialReviewerStatus::STATUS_BLOCKING => - array_keys($adapter->getBlockingReviewersAddedByHerald()), - ); - - $old_reviewers = $object->getReviewerStatus(); - $old_reviewers = mpull($old_reviewers, null, 'getReviewerPHID'); - - $value = array(); - foreach ($reviewers as $status => $phids) { - foreach ($phids as $phid) { - if ($phid == $object->getAuthorPHID()) { - // Don't try to add the revision's author as a reviewer, since this - // isn't valid and doesn't make sense. - continue; - } - - // If the target is already a reviewer, don't try to change anything - // if their current status is at least as strong as the new status. - // For example, don't downgrade an "Accepted" to a "Blocking Reviewer". - $old_reviewer = idx($old_reviewers, $phid); - if ($old_reviewer) { - $old_status = $old_reviewer->getStatus(); - - $old_strength = DifferentialReviewerStatus::getStatusStrength( - $old_status); - $new_strength = DifferentialReviewerStatus::getStatusStrength( - $status); - - if ($new_strength <= $old_strength) { - continue; - } - } - - $value['+'][$phid] = array( - 'data' => array( - 'status' => $status, - ), - ); - } - } - - if ($value) { - $edge_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; - - $xactions[] = id(new DifferentialTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $edge_reviewer) - ->setNewValue($value); - } - // Require legalpad document signatures. $legal_phids = $adapter->getRequiredSignatureDocumentPHIDs(); if ($legal_phids) { diff --git a/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php new file mode 100644 index 0000000000..0ad16ea83b --- /dev/null +++ b/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php @@ -0,0 +1,33 @@ +applyReviewers($effect->getTarget(), $is_blocking = true); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorMetaMTAMailableDatasource(); + } + + public function renderActionDescription($value) { + return pht('Add blocking reviewers: %s.', $this->renderHandleList($value)); + } + +} + diff --git a/src/applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php new file mode 100644 index 0000000000..d173e4b814 --- /dev/null +++ b/src/applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php @@ -0,0 +1,30 @@ +getRule()->getAuthorPHID(); + return $this->applyReviewers(array($phid), $is_blocking = true); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Add rule author as blocking reviewer.'); + } + +} + diff --git a/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php new file mode 100644 index 0000000000..9530f903bf --- /dev/null +++ b/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php @@ -0,0 +1,33 @@ +applyReviewers($effect->getTarget(), $is_blocking = false); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorMetaMTAMailableDatasource(); + } + + public function renderActionDescription($value) { + return pht('Add reviewers: %s.', $this->renderHandleList($value)); + } + +} + diff --git a/src/applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php new file mode 100644 index 0000000000..de22adbd27 --- /dev/null +++ b/src/applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php @@ -0,0 +1,30 @@ +getRule()->getAuthorPHID(); + return $this->applyReviewers(array($phid), $is_blocking = false); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Add rule author as reviewer.'); + } + +} + diff --git a/src/applications/differential/herald/DifferentialReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersHeraldAction.php new file mode 100644 index 0000000000..3694bdb087 --- /dev/null +++ b/src/applications/differential/herald/DifferentialReviewersHeraldAction.php @@ -0,0 +1,240 @@ +getAdapter(); + $object = $adapter->getObject(); + + $phids = array_fuse($phids); + if (!$phids) { + $this->logEffect(self::DO_NO_TARGETS); + return; + } + + // Don't try to add revision authors as reviewers. + $authors = array(); + foreach ($phids as $phid) { + if ($phid == $object->getAuthorPHID()) { + $authors[] = $phid; + unset($phids[$phid]); + } + } + + if ($authors) { + $this->logEffect(self::DO_AUTHORS, $authors); + } + + if (!$phids) { + return; + } + + $reviewers = $object->getReviewerStatus(); + $reviewers = mpull($reviewers, null, 'getReviewerPHID'); + + if ($is_blocking) { + $new_status = DifferentialReviewerStatus::STATUS_BLOCKING; + } else { + $new_status = DifferentialReviewerStatus::STATUS_ADDED; + } + + $new_strength = DifferentialReviewerStatus::getStatusStrength( + $new_status); + + $already = array(); + foreach ($phids as $phid) { + if (!isset($reviewers[$phid])) { + continue; + } + + // If we're applying a stronger status (usually, upgrading a reviewer + // into a blocking reviewer), skip this check so we apply the change. + $old_strength = DifferentialReviewerStatus::getStatusStrength( + $reviewers[$phid]->getStatus()); + if ($old_strength <= $new_strength) { + continue; + } + + $already[] = $phid; + unset($phids[$phid]); + } + + if ($already) { + $this->logEffect(self::DO_ALREADY_REVIEWERS, $already); + } + + if (!$phids) { + return; + } + + $targets = id(new PhabricatorObjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($phids) + ->execute(); + $targets = mpull($targets, null, 'getPHID'); + + $invalid = array(); + foreach ($phids as $phid) { + if (empty($targets[$phid])) { + $invalid[] = $phid; + unset($phids[$phid]); + } + } + + if ($invalid) { + $this->logEffect(self::DO_INVALID, $invalid); + } + + if (!$phids) { + return; + } + + $no_access = array(); + foreach ($targets as $phid => $target) { + if (!($target instanceof PhabricatorUser)) { + continue; + } + + $can_view = PhabricatorPolicyFilter::hasCapability( + $target, + $object, + PhabricatorPolicyCapability::CAN_VIEW); + if ($can_view) { + continue; + } + + $no_access[] = $phid; + unset($phids[$phid]); + } + + if ($no_access) { + $this->logEffect(self::DO_PERMISSION, $no_access); + } + + if (!$phids) { + return; + } + + $value = array(); + foreach ($phids as $phid) { + $value[$phid] = array( + 'data' => array( + 'status' => $new_status, + ), + ); + } + + $edgetype_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $edgetype_reviewer) + ->setNewValue( + array( + '+' => $value, + )); + + $adapter->queueTransaction($xaction); + + if ($is_blocking) { + $this->logEffect(self::DO_ADD_BLOCKING_REVIEWERS, $phids); + } else { + $this->logEffect(self::DO_ADD_REVIEWERS, $phids); + } + } + + protected function getActionEffectMap() { + return array( + self::DO_NO_TARGETS => array( + 'icon' => 'fa-ban', + 'color' => 'grey', + 'name' => pht('No Targets'), + ), + self::DO_AUTHORS => array( + 'icon' => 'fa-user', + 'color' => 'grey', + 'name' => pht('Revision Author'), + ), + self::DO_INVALID => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Invalid Targets'), + ), + self::DO_ALREADY_REVIEWERS => array( + 'icon' => 'fa-user', + 'color' => 'grey', + 'name' => pht('Already Reviewers'), + ), + self::DO_PERMISSION => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('No Permission'), + ), + self::DO_ADD_REVIEWERS => array( + 'icon' => 'fa-user', + 'color' => 'green', + 'name' => pht('Added Reviewers'), + ), + self::DO_ADD_BLOCKING_REVIEWERS => array( + 'icon' => 'fa-user', + 'color' => 'green', + 'name' => pht('Added Blocking Reviewers'), + ), + ); + } + + public function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_NO_TARGETS: + return pht('Rule lists no targets.'); + case self::DO_AUTHORS: + return pht( + 'Declined to add revision author as reviewer: %s.', + $this->renderHandleList($data)); + case self::DO_INVALID: + return pht( + 'Declined to act on %s invalid target(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ALREADY_REVIEWERS: + return pht( + '%s target(s) were already reviewers: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_PERMISSION: + return pht( + '%s target(s) do not have permission to see the revision: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ADD_REVIEWERS: + return pht( + 'Added %s reviewer(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ADD_BLOCKING_REVIEWERS: + return pht( + 'Added %s blocking reviewer(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + } + } + + +} diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index a2d7f002d3..9166e0e060 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -5,9 +5,6 @@ final class HeraldDifferentialRevisionAdapter protected $revision; - protected $explicitReviewers; - protected $addReviewerPHIDs = array(); - protected $blockingReviewerPHIDs = array(); protected $buildPlans = array(); protected $requiredSignatureDocumentPHIDs = array(); @@ -83,19 +80,6 @@ final class HeraldDifferentialRevisionAdapter return $object; } - public function setExplicitReviewers($explicit_reviewers) { - $this->explicitReviewers = $explicit_reviewers; - return $this; - } - - public function getReviewersAddedByHerald() { - return $this->addReviewerPHIDs; - } - - public function getBlockingReviewersAddedByHerald() { - return $this->blockingReviewerPHIDs; - } - public function getRequiredSignatureDocumentPHIDs() { return $this->requiredSignatureDocumentPHIDs; } @@ -147,14 +131,8 @@ final class HeraldDifferentialRevisionAdapter } public function loadReviewers() { - // TODO: This can probably go away as I believe it's just a performance - // optimization, just retaining it while modularizing fields to limit the - // scope of that change. - if (isset($this->explicitReviewers)) { - return array_keys($this->explicitReviewers); - } else { - return $this->revision->getReviewers(); - } + $reviewers = $this->getObject()->getReviewerStatus(); + return mpull($reviewers, 'getReviewerPHID'); } public function getActions($rule_type) { @@ -162,18 +140,13 @@ final class HeraldDifferentialRevisionAdapter case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( - self::ACTION_ADD_REVIEWERS, - self::ACTION_ADD_BLOCKING_REVIEWERS, self::ACTION_APPLY_BUILD_PLANS, self::ACTION_REQUIRE_SIGNATURE, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( - array( - self::ACTION_ADD_REVIEWERS, - self::ACTION_ADD_BLOCKING_REVIEWERS, - ), + array(), parent::getActions($rule_type)); } } @@ -186,26 +159,6 @@ final class HeraldDifferentialRevisionAdapter foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { - case self::ACTION_ADD_REVIEWERS: - foreach ($effect->getTarget() as $phid) { - $this->addReviewerPHIDs[$phid] = true; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Added reviewers.')); - break; - case self::ACTION_ADD_BLOCKING_REVIEWERS: - // This adds reviewers normally, it just also marks them blocking. - foreach ($effect->getTarget() as $phid) { - $this->addReviewerPHIDs[$phid] = true; - $this->blockingReviewerPHIDs[$phid] = true; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Added blocking reviewers.')); - break; case self::ACTION_APPLY_BUILD_PLANS: foreach ($effect->getTarget() as $phid) { $this->buildPlans[] = $phid; diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 36a47dc75b..c4759a937e 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -27,8 +27,6 @@ abstract class HeraldAdapter extends Phobject { const CONDITION_IS_FALSE = 'false'; const ACTION_AUDIT = 'audit'; - const ACTION_ADD_REVIEWERS = 'addreviewers'; - const ACTION_ADD_BLOCKING_REVIEWERS = 'addblockingreviewers'; const ACTION_APPLY_BUILD_PLANS = 'applybuildplans'; const ACTION_BLOCK = 'block'; const ACTION_REQUIRE_SIGNATURE = 'signature'; @@ -717,8 +715,6 @@ abstract class HeraldAdapter extends Phobject { case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: $standard = array( self::ACTION_AUDIT => pht('Trigger an Audit by'), - self::ACTION_ADD_REVIEWERS => pht('Add reviewers'), - self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add blocking reviewers'), self::ACTION_APPLY_BUILD_PLANS => pht('Run build plans'), self::ACTION_REQUIRE_SIGNATURE => pht('Require legal signatures'), self::ACTION_BLOCK => pht('Block change with message'), @@ -727,9 +723,6 @@ abstract class HeraldAdapter extends Phobject { case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: $standard = array( self::ACTION_AUDIT => pht('Trigger an Audit by me'), - self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'), - self::ACTION_ADD_BLOCKING_REVIEWERS => - pht('Add me as a blocking reviewer'), ); break; default: @@ -769,10 +762,6 @@ abstract class HeraldAdapter extends Phobject { if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { switch ($action->getAction()) { case self::ACTION_AUDIT: - case self::ACTION_ADD_REVIEWERS: - case self::ACTION_ADD_BLOCKING_REVIEWERS: - // For personal rules, force these actions to target the rule owner. - $target = array($author_phid); break; case self::ACTION_BLOCK: break; @@ -808,17 +797,11 @@ abstract class HeraldAdapter extends Phobject { if ($is_personal) { switch ($action) { case self::ACTION_AUDIT: - case self::ACTION_ADD_REVIEWERS: - case self::ACTION_ADD_BLOCKING_REVIEWERS: return new HeraldEmptyFieldValue(); } } else { switch ($action) { case self::ACTION_AUDIT: - case self::ACTION_ADD_REVIEWERS: - case self::ACTION_ADD_BLOCKING_REVIEWERS: - return $this->buildTokenizerFieldValue( - new PhabricatorProjectOrUserDatasource()); case self::ACTION_APPLY_BUILD_PLANS: return $this->buildTokenizerFieldValue( new HarbormasterBuildPlanDatasource()); diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index d4343b2ec9..6284ed7a97 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1323,6 +1323,16 @@ final class PhabricatorUSEnglishTranslation 'Removed projects: %2$s.', ), + 'Added %s reviewer(s): %s.' => array( + 'Added a reviewer: %2$s.', + 'Added reviewers: %2$s.', + ), + + 'Added %s blocking reviewer(s): %s.' => array( + 'Added a blocking reviewer: %2$s.', + 'Added blocking reviewers: %2$s.', + ), + ); } From fdd379a026c3620df745210f2519e5f3b68ebb96 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Jul 2015 12:09:17 -0700 Subject: [PATCH 065/102] Modularize the Legalpad "Require Signature" Herald Action Ref T8726. Modularizes "Require Signatures" for Legalpad. --- .../sql/autopatches/20150730.herald.6.sql | 6 + src/__phutil_library_map__.php | 2 + .../editor/DifferentialTransactionEditor.php | 32 +-- .../HeraldDifferentialRevisionAdapter.php | 15 -- .../herald/adapter/HeraldAdapter.php | 5 - .../LegalpadRequireSignatureHeraldAction.php | 197 ++++++++++++++++++ .../PhabricatorUSEnglishTranslation.php | 5 + 7 files changed, 211 insertions(+), 51 deletions(-) create mode 100644 resources/sql/autopatches/20150730.herald.6.sql create mode 100644 src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php diff --git a/resources/sql/autopatches/20150730.herald.6.sql b/resources/sql/autopatches/20150730.herald.6.sql new file mode 100644 index 0000000000..08188c6d4f --- /dev/null +++ b/resources/sql/autopatches/20150730.herald.6.sql @@ -0,0 +1,6 @@ +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'legalpad.require' + WHERE r.ruleType != 'personal' + AND a.action = 'signature'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 090dbc06e4..50df87873b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1124,6 +1124,7 @@ phutil_register_library_map(array( 'LegalpadMailReceiver' => 'applications/legalpad/mail/LegalpadMailReceiver.php', 'LegalpadObjectNeedsSignatureEdgeType' => 'applications/legalpad/edge/LegalpadObjectNeedsSignatureEdgeType.php', 'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php', + 'LegalpadRequireSignatureHeraldAction' => 'applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php', 'LegalpadSchemaSpec' => 'applications/legalpad/storage/LegalpadSchemaSpec.php', 'LegalpadSignatureNeededByObjectEdgeType' => 'applications/legalpad/edge/LegalpadSignatureNeededByObjectEdgeType.php', 'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php', @@ -4852,6 +4853,7 @@ phutil_register_library_map(array( 'LegalpadMailReceiver' => 'PhabricatorObjectMailReceiver', 'LegalpadObjectNeedsSignatureEdgeType' => 'PhabricatorEdgeType', 'LegalpadReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'LegalpadRequireSignatureHeraldAction' => 'HeraldAction', 'LegalpadSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'LegalpadSignatureNeededByObjectEdgeType' => 'PhabricatorEdgeType', 'LegalpadTransaction' => 'PhabricatorApplicationTransaction', diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 0e8548c0a6..ff4894ec3e 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1596,43 +1596,13 @@ final class DifferentialTransactionEditor HeraldAdapter $adapter, HeraldTranscript $transcript) { - $xactions = array(); - - // Require legalpad document signatures. - $legal_phids = $adapter->getRequiredSignatureDocumentPHIDs(); - if ($legal_phids) { - // We only require signatures of documents which have not already - // been signed. In general, this reduces the amount of churn that - // signature rules cause. - - $signatures = id(new LegalpadDocumentSignatureQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withDocumentPHIDs($legal_phids) - ->withSignerPHIDs(array($object->getAuthorPHID())) - ->execute(); - $signed_phids = mpull($signatures, 'getDocumentPHID'); - $legal_phids = array_diff($legal_phids, $signed_phids); - - // If we still have something to trigger, add the edges. - if ($legal_phids) { - $edge_legal = LegalpadObjectNeedsSignatureEdgeType::EDGECONST; - $xactions[] = id(new DifferentialTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $edge_legal) - ->setNewValue( - array( - '+' => array_fuse($legal_phids), - )); - } - } - // Apply build plans. HarbormasterBuildable::applyBuildPlans( $adapter->getDiff()->getPHID(), $adapter->getPHID(), $adapter->getBuildPlans()); - return $xactions; + return array(); } /** diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index 9166e0e060..ce84a209ca 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -6,7 +6,6 @@ final class HeraldDifferentialRevisionAdapter protected $revision; protected $buildPlans = array(); - protected $requiredSignatureDocumentPHIDs = array(); protected $affectedPackages; protected $changesets; @@ -80,10 +79,6 @@ final class HeraldDifferentialRevisionAdapter return $object; } - public function getRequiredSignatureDocumentPHIDs() { - return $this->requiredSignatureDocumentPHIDs; - } - public function getBuildPlans() { return $this->buildPlans; } @@ -141,7 +136,6 @@ final class HeraldDifferentialRevisionAdapter return array_merge( array( self::ACTION_APPLY_BUILD_PLANS, - self::ACTION_REQUIRE_SIGNATURE, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: @@ -168,15 +162,6 @@ final class HeraldDifferentialRevisionAdapter true, pht('Applied build plans.')); break; - case self::ACTION_REQUIRE_SIGNATURE: - foreach ($effect->getTarget() as $phid) { - $this->requiredSignatureDocumentPHIDs[] = $phid; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Required signatures.')); - break; default: $result[] = $this->applyStandardEffect($effect); break; diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index c4759a937e..9858545d5c 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -29,7 +29,6 @@ abstract class HeraldAdapter extends Phobject { const ACTION_AUDIT = 'audit'; const ACTION_APPLY_BUILD_PLANS = 'applybuildplans'; const ACTION_BLOCK = 'block'; - const ACTION_REQUIRE_SIGNATURE = 'signature'; private $contentSource; private $isNewObject; @@ -716,7 +715,6 @@ abstract class HeraldAdapter extends Phobject { $standard = array( self::ACTION_AUDIT => pht('Trigger an Audit by'), self::ACTION_APPLY_BUILD_PLANS => pht('Run build plans'), - self::ACTION_REQUIRE_SIGNATURE => pht('Require legal signatures'), self::ACTION_BLOCK => pht('Block change with message'), ); break; @@ -805,9 +803,6 @@ abstract class HeraldAdapter extends Phobject { case self::ACTION_APPLY_BUILD_PLANS: return $this->buildTokenizerFieldValue( new HarbormasterBuildPlanDatasource()); - case self::ACTION_REQUIRE_SIGNATURE: - return $this->buildTokenizerFieldValue( - new LegalpadDocumentDatasource()); case self::ACTION_BLOCK: return new HeraldTextFieldValue(); } diff --git a/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php b/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php new file mode 100644 index 0000000000..092840df68 --- /dev/null +++ b/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php @@ -0,0 +1,197 @@ +getAdapter(); + $edgetype_legal = LegalpadObjectNeedsSignatureEdgeType::EDGECONST; + + $phids = array_fuse($phids); + + if (!$phids) { + $this->logEffect(self::DO_NO_TARGETS); + return; + } + + $current = $adapter->loadEdgePHIDs($edgetype_legal); + + $already = array(); + foreach ($phids as $phid) { + if (isset($current[$phid])) { + $already[] = $phid; + unset($phids[$phid]); + } + } + + if ($already) { + $this->logEffect(self::DO_ALREADY_REQUIRED, $phids); + } + + if (!$phids) { + return; + } + + $documents = id(new LegalpadDocumentQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($phids) + ->execute(); + $documents = mpull($documents, null, 'getPHID'); + + $invalid = array(); + foreach ($phids as $phid) { + if (empty($documents[$phid])) { + $invalid[] = $phid; + unset($documents[$phid]); + } + } + + if ($invalid) { + $this->logEffect(self::DO_INVALID, $phids); + } + + if (!$phids) { + return; + } + + $object = $adapter->getObject(); + $author_phid = $object->getAuthorPHID(); + + $signatures = id(new LegalpadDocumentSignatureQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withDocumentPHIDs($phids) + ->withSignerPHIDs(array($author_phid)) + ->execute(); + $signatures = mpull($signatures, null, 'getDocumentPHID'); + + $signed = array(); + foreach ($phids as $phid) { + if (isset($signatures[$phid])) { + $signed[] = $phid; + unset($phids[$phid]); + } + } + + if ($signed) { + $this->logEffect(self::DO_SIGNED, $phids); + } + + if (!$phids) { + return; + } + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $edgetype_legal) + ->setNewValue( + array( + '+' => $phids, + )); + + $adapter->queueTransaction($xaction); + + $this->logEffect(self::DO_REQUIRED, $phids); + } + + protected function getActionEffectMap() { + return array( + self::DO_NO_TARGETS => array( + 'icon' => 'fa-ban', + 'color' => 'grey', + 'name' => pht('No Targets'), + ), + self::DO_INVALID => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Invalid Targets'), + ), + self::DO_ALREADY_REQUIRED => array( + 'icon' => 'fa-terminal', + 'color' => 'grey', + 'name' => pht('Already Required'), + ), + self::DO_SIGNED => array( + 'icon' => 'fa-terminal', + 'color' => 'green', + 'name' => pht('Already Signed'), + ), + self::DO_REQUIRED => array( + 'icon' => 'fa-terminal', + 'color' => 'green', + 'name' => pht('Required Signature'), + ), + ); + } + + public function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_NO_TARGETS: + return pht('Rule lists no targets.'); + case self::DO_INVALID: + return pht( + '%s document(s) are not valid: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_ALREADY_REQUIRED: + return pht( + '%s document signature(s) are already required: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_SIGNED: + return pht( + '%s document(s) are already signed: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_REQUIRED: + return pht( + 'Required %s signature(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + } + } + + public function getHeraldActionName() { + return pht('Require signatures'); + } + + public function supportsRuleType($rule_type) { + return ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); + } + + public function applyEffect($object, HeraldEffect $effect) { + return $this->applyRequire($effect->getTarget()); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new LegalpadDocumentDatasource(); + } + + public function renderActionDescription($value) { + return pht( + 'Require document signatures: %s.', + $this->renderHandleList($value)); + } +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 6284ed7a97..00bc82305c 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1333,6 +1333,11 @@ final class PhabricatorUSEnglishTranslation 'Added blocking reviewers: %2$s.', ), + 'Required %s signature(s): %s.' => array( + 'Required a signature: %2$s.', + 'Required signatures: %2$s.', + ), + ); } From 776caa507b15ef013d3d623d30b294181e2b4c11 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Jul 2015 12:22:35 -0700 Subject: [PATCH 066/102] Modularize the Harbormaster "Run build plan" Herald action Ref T8726. Modularizes "Run build plan" in Differential and Diffusion. --- .../sql/autopatches/20150730.herald.7.sql | 6 + src/__phutil_library_map__.php | 13 +- .../audit/editor/PhabricatorAuditEditor.php | 5 - .../editor/DifferentialTransactionEditor.php | 14 -- .../HeraldDifferentialRevisionAdapter.php | 59 +++------ .../diffusion/herald/HeraldCommitAdapter.php | 41 +++--- .../HarbormasterBuildableAdapterInterface.php | 34 +++++ .../HarbormasterRunBuildPlansHeraldAction.php | 121 ++++++++++++++++++ .../storage/HarbormasterBuildable.php | 2 +- .../herald/adapter/HeraldAdapter.php | 5 - ...habricatorApplicationTransactionEditor.php | 7 + 11 files changed, 223 insertions(+), 84 deletions(-) create mode 100644 resources/sql/autopatches/20150730.herald.7.sql create mode 100644 src/applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php create mode 100644 src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php diff --git a/resources/sql/autopatches/20150730.herald.7.sql b/resources/sql/autopatches/20150730.herald.7.sql new file mode 100644 index 0000000000..403a90ac26 --- /dev/null +++ b/resources/sql/autopatches/20150730.herald.7.sql @@ -0,0 +1,6 @@ +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'harbormaster.build' + WHERE r.ruleType != 'personal' + AND a.action = 'applybuildplans'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 50df87873b..08a183d20d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -963,6 +963,7 @@ phutil_register_library_map(array( 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', 'HarbormasterBuildableActionController' => 'applications/harbormaster/controller/HarbormasterBuildableActionController.php', + 'HarbormasterBuildableAdapterInterface' => 'applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php', 'HarbormasterBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildableInterface.php', 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', 'HarbormasterBuildablePHIDType' => 'applications/harbormaster/phid/HarbormasterBuildablePHIDType.php', @@ -996,6 +997,7 @@ phutil_register_library_map(array( 'HarbormasterQueryBuildablesConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php', 'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php', 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', + 'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php', 'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php', 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', 'HarbormasterSendMessageConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php', @@ -4703,6 +4705,7 @@ phutil_register_library_map(array( 'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction', 'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HarbormasterScratchTable' => 'HarbormasterDAO', 'HarbormasterSendMessageConduitAPIMethod' => 'HarbormasterConduitAPIMethod', @@ -4728,7 +4731,10 @@ phutil_register_library_map(array( 'HeraldApplicationActionGroup' => 'HeraldActionGroup', 'HeraldApplyTranscript' => 'Phobject', 'HeraldBasicFieldGroup' => 'HeraldFieldGroup', - 'HeraldCommitAdapter' => 'HeraldAdapter', + 'HeraldCommitAdapter' => array( + 'HeraldAdapter', + 'HarbormasterBuildableAdapterInterface', + ), 'HeraldCondition' => 'HeraldDAO', 'HeraldConditionTranscript' => 'Phobject', 'HeraldContentSourceField' => 'HeraldField', @@ -4737,7 +4743,10 @@ phutil_register_library_map(array( 'HeraldDAO' => 'PhabricatorLiskDAO', 'HeraldDifferentialAdapter' => 'HeraldAdapter', 'HeraldDifferentialDiffAdapter' => 'HeraldDifferentialAdapter', - 'HeraldDifferentialRevisionAdapter' => 'HeraldDifferentialAdapter', + 'HeraldDifferentialRevisionAdapter' => array( + 'HeraldDifferentialAdapter', + 'HarbormasterBuildableAdapterInterface', + ), 'HeraldDisableController' => 'HeraldController', 'HeraldDoNothingAction' => 'HeraldAction', 'HeraldEditFieldGroup' => 'HeraldFieldGroup', diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 95f8ee8311..2612cff04d 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -901,11 +901,6 @@ final class PhabricatorAuditEditor 'auditReasonMap', $this->auditReasonMap); } - HarbormasterBuildable::applyBuildPlans( - $object->getPHID(), - $object->getRepository()->getPHID(), - $adapter->getBuildPlans()); - $limit = self::MAX_FILES_SHOWN_IN_EMAIL; $files = $adapter->loadAffectedPaths(); sort($files); diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index ff4894ec3e..2a8e907145 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1591,20 +1591,6 @@ final class DifferentialTransactionEditor return $adapter; } - protected function didApplyHeraldRules( - PhabricatorLiskDAO $object, - HeraldAdapter $adapter, - HeraldTranscript $transcript) { - - // Apply build plans. - HarbormasterBuildable::applyBuildPlans( - $adapter->getDiff()->getPHID(), - $adapter->getPHID(), - $adapter->getBuildPlans()); - - return array(); - } - /** * Update the table which links Differential revisions to paths they affect, * so Diffusion can efficiently find pending revisions for a given file. diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index ce84a209ca..a47d433357 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -1,16 +1,17 @@ buildPlans; - } - public function getHeraldName() { return $this->revision->getTitle(); } @@ -130,44 +127,24 @@ final class HeraldDifferentialRevisionAdapter return mpull($reviewers, 'getReviewerPHID'); } - public function getActions($rule_type) { - switch ($rule_type) { - case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: - return array_merge( - array( - self::ACTION_APPLY_BUILD_PLANS, - ), - parent::getActions($rule_type)); - case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: - return array_merge( - array(), - parent::getActions($rule_type)); - } + +/* -( HarbormasterBuildableAdapterInterface )------------------------------ */ + + + public function getHarbormasterBuildablePHID() { + return $this->getDiff()->getPHID(); } - public function applyHeraldEffects(array $effects) { - assert_instances_of($effects, 'HeraldEffect'); + public function getHarbormasterContainerPHID() { + return $this->getObject()->getPHID(); + } - $result = array(); + public function getQueuedHarbormasterBuildPlanPHIDs() { + return $this->buildPlanPHIDs; + } - foreach ($effects as $effect) { - $action = $effect->getAction(); - switch ($action) { - case self::ACTION_APPLY_BUILD_PLANS: - foreach ($effect->getTarget() as $phid) { - $this->buildPlans[] = $phid; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Applied build plans.')); - break; - default: - $result[] = $this->applyStandardEffect($effect); - break; - } - } - return $result; + public function queueHarbormasterBuildPlanPHID($phid) { + $this->buildPlanPHIDs[] = $phid; } } diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 41971e1c2f..14f07fe575 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -1,6 +1,8 @@ auditMap; } - public function getBuildPlans() { - return $this->buildPlans; - } - public function getHeraldName() { return 'r'. @@ -343,15 +341,6 @@ final class HeraldCommitAdapter extends HeraldAdapter { true, pht('Triggered an audit.')); break; - case self::ACTION_APPLY_BUILD_PLANS: - foreach ($effect->getTarget() as $phid) { - $this->buildPlans[] = $phid; - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Applied build plans.')); - break; default: $result[] = $this->applyStandardEffect($effect); break; @@ -360,4 +349,24 @@ final class HeraldCommitAdapter extends HeraldAdapter { return $result; } + +/* -( HarbormasterBuildableAdapterInterface )------------------------------ */ + + + public function getHarbormasterBuildablePHID() { + return $this->getObject()->getPHID(); + } + + public function getHarbormasterContainerPHID() { + return $this->getObject()->getRepository()->getPHID(); + } + + public function getQueuedHarbormasterBuildPlanPHIDs() { + return $this->buildPlanPHIDs; + } + + public function queueHarbormasterBuildPlanPHID($phid) { + $this->buildPlanPHIDs[] = $phid; + } + } diff --git a/src/applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php b/src/applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php new file mode 100644 index 0000000000..cf6cda8f95 --- /dev/null +++ b/src/applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php @@ -0,0 +1,34 @@ +getObject()->getPHID(); + } + + public function getHarbormasterContainerPHID() { + return null; + } + + public function getQueuedHarbormasterBuildPlanPHIDs() { + return $this->buildPlanPHIDs; + } + + public function queueHarbormasterBuildPlanPHID($phid) { + $this->buildPlanPHIDs[] = $phid; + } + +*/ diff --git a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php new file mode 100644 index 0000000000..f06cdd1b26 --- /dev/null +++ b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php @@ -0,0 +1,121 @@ +getAdapter(); + return ($adapter instanceof HarbormasterBuildableAdapterInterface); + } + + protected function applyBuilds(array $phids) { + $adapter = $this->getAdapter(); + + $phids = array_fuse($phids); + if (!$phids) { + $this->logEffect(self::DO_NO_TARGETS); + return; + } + + $plans = id(new HarbormasterBuildPlanQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($phids) + ->execute(); + $plans = mpull($plans, null, 'getPHID'); + + $invalid = array(); + foreach ($phids as $phid) { + if (empty($plans[$phid])) { + $invalid[] = $phid; + unset($plans[$phid]); + } + } + + if ($invalid) { + $this->logEffect(self::DO_INVALID, $phids); + } + + if (!$phids) { + return; + } + + foreach ($phids as $phid) { + $adapter->queueHarbormasterBuildPlanPHID($phid); + } + + $this->logEffect(self::DO_BUILD, $phids); + } + + protected function getActionEffectMap() { + return array( + self::DO_NO_TARGETS => array( + 'icon' => 'fa-ban', + 'color' => 'grey', + 'name' => pht('No Targets'), + ), + self::DO_INVALID => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Invalid Targets'), + ), + self::DO_ALREADY_REQUIRED => array( + 'icon' => 'fa-play', + 'color' => 'green', + 'name' => pht('Building'), + ), + ); + } + + public function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_NO_TARGETS: + return pht('Rule lists no targets.'); + case self::DO_INVALID: + return pht( + '%s build plan(s) are not valid: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_REQUIRED: + return pht( + 'Started %s build(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + } + } + + public function getHeraldActionName() { + return pht('Run build plans'); + } + + public function supportsRuleType($rule_type) { + return ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); + } + + public function applyEffect($object, HeraldEffect $effect) { + return $this->applyBuilds($effect->getTarget()); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new HarbormasterBuildPlanDatasource(); + } + + public function renderActionDescription($value) { + return pht( + 'Run build plans: %s.', + $this->renderHandleList($value)); + } +} diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index a9a694a3f5..0376ebc890 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -104,7 +104,7 @@ final class HarbormasterBuildable extends HarbormasterDAO $container_phid, array $plan_phids) { - if (count($plan_phids) === 0) { + if (!$plan_phids) { return; } diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 9858545d5c..0c32d3238a 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -27,7 +27,6 @@ abstract class HeraldAdapter extends Phobject { const CONDITION_IS_FALSE = 'false'; const ACTION_AUDIT = 'audit'; - const ACTION_APPLY_BUILD_PLANS = 'applybuildplans'; const ACTION_BLOCK = 'block'; private $contentSource; @@ -714,7 +713,6 @@ abstract class HeraldAdapter extends Phobject { case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: $standard = array( self::ACTION_AUDIT => pht('Trigger an Audit by'), - self::ACTION_APPLY_BUILD_PLANS => pht('Run build plans'), self::ACTION_BLOCK => pht('Block change with message'), ); break; @@ -800,9 +798,6 @@ abstract class HeraldAdapter extends Phobject { } else { switch ($action) { case self::ACTION_AUDIT: - case self::ACTION_APPLY_BUILD_PLANS: - return $this->buildTokenizerFieldValue( - new HarbormasterBuildPlanDatasource()); case self::ACTION_BLOCK: return new HeraldTextFieldValue(); } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 0b6dd2de91..033fedf805 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -2811,6 +2811,13 @@ abstract class PhabricatorApplicationTransactionEditor $this->setHeraldAdapter($adapter); $this->setHeraldTranscript($xscript); + if ($adapter instanceof HarbormasterBuildableAdapterInterface) { + HarbormasterBuildable::applyBuildPlans( + $adapter->getHarbormasterBuildablePHID(), + $adapter->getHarbormasterContainerPHID(), + $adapter->getQueuedHarbormasterBuildPlanPHIDs()); + } + return array_merge( $this->didApplyHeraldRules($object, $adapter, $xscript), $adapter->getQueuedTransactions()); From 6f6d88794b25ee24d9b1242937e21859c44ad543 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Aug 2015 06:01:24 -0700 Subject: [PATCH 067/102] Modularize the Diffusion "Add Auditors" Herald action Ref T8726. --- .../sql/autopatches/20150803.herald.1.sql | 13 ++ src/__phutil_library_map__.php | 6 + .../audit/editor/PhabricatorAuditEditor.php | 26 +-- .../DifferentialReviewersHeraldAction.php | 2 +- ...ffusionAuditorsAddAuditorsHeraldAction.php | 33 +++ .../DiffusionAuditorsAddSelfHeraldAction.php | 30 +++ .../herald/DiffusionAuditorsHeraldAction.php | 76 +++++++ .../diffusion/herald/HeraldCommitAdapter.php | 51 ----- .../PhabricatorFlagAddFlagHeraldAction.php | 2 +- .../HarbormasterRunBuildPlansHeraldAction.php | 6 +- .../herald/action/HeraldAction.php | 188 +++++++++++++++++- .../herald/action/HeraldDoNothingAction.php | 2 +- .../herald/adapter/HeraldAdapter.php | 13 +- .../controller/HeraldTranscriptController.php | 2 +- .../LegalpadRequireSignatureHeraldAction.php | 2 +- .../ManiphestTaskAssignHeraldAction.php | 2 +- .../PhabricatorMetaMTAEmailHeraldAction.php | 2 +- .../herald/PhabricatorProjectHeraldAction.php | 2 +- .../PhabricatorSubscriptionsHeraldAction.php | 2 +- .../PhabricatorUSEnglishTranslation.php | 10 + 20 files changed, 365 insertions(+), 105 deletions(-) create mode 100644 resources/sql/autopatches/20150803.herald.1.sql create mode 100644 src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php create mode 100644 src/applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php create mode 100644 src/applications/diffusion/herald/DiffusionAuditorsHeraldAction.php diff --git a/resources/sql/autopatches/20150803.herald.1.sql b/resources/sql/autopatches/20150803.herald.1.sql new file mode 100644 index 0000000000..21f462b8c8 --- /dev/null +++ b/resources/sql/autopatches/20150803.herald.1.sql @@ -0,0 +1,13 @@ +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'diffusion.auditors.add' + WHERE r.ruleType != 'personal' + AND a.action = 'audit'; + +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'diffusion.auditors.self.add' + WHERE r.ruleType = 'personal' + AND a.action = 'audit'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 08a183d20d..1d92b7a6de 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -498,6 +498,9 @@ phutil_register_library_map(array( 'DifferentialUpdateUnitResultsConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateUnitResultsConduitAPIMethod.php', 'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php', 'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php', + 'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php', + 'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php', + 'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', @@ -4129,6 +4132,9 @@ phutil_register_library_map(array( 'DifferentialUpdateUnitResultsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialViewPolicyField' => 'DifferentialCoreCustomField', 'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction', + 'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction', + 'DiffusionAuditorsHeraldAction' => 'HeraldAction', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', 'DiffusionBranchTableView' => 'DiffusionView', diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 2612cff04d..e9dd384812 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -877,30 +877,6 @@ final class PhabricatorAuditEditor HeraldAdapter $adapter, HeraldTranscript $transcript) { - $xactions = array(); - - $audit_phids = $adapter->getAuditMap(); - foreach ($audit_phids as $phid => $rule_ids) { - foreach ($rule_ids as $rule_id) { - $this->addAuditReason( - $phid, - pht( - '%s Triggered Audit', - "H{$rule_id}")); - } - } - - if ($audit_phids) { - $xactions[] = id(new PhabricatorAuditTransaction()) - ->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS) - ->setNewValue(array_fuse(array_keys($audit_phids))) - ->setMetadataValue( - 'auditStatus', - PhabricatorAuditStatusConstants::AUDIT_REQUIRED) - ->setMetadataValue( - 'auditReasonMap', $this->auditReasonMap); - } - $limit = self::MAX_FILES_SHOWN_IN_EMAIL; $files = $adapter->loadAffectedPaths(); sort($files); @@ -914,7 +890,7 @@ final class PhabricatorAuditEditor } $this->affectedFiles = implode("\n", $files); - return $xactions; + return array(); } private function isCommitMostlyImported(PhabricatorLiskDAO $object) { diff --git a/src/applications/differential/herald/DifferentialReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersHeraldAction.php index 3694bdb087..848dfac9ff 100644 --- a/src/applications/differential/herald/DifferentialReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersHeraldAction.php @@ -200,7 +200,7 @@ abstract class DifferentialReviewersHeraldAction ); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_NO_TARGETS: return pht('Rule lists no targets.'); diff --git a/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php b/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php new file mode 100644 index 0000000000..36b01b96f8 --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php @@ -0,0 +1,33 @@ +getRule(); + return $this->applyAuditors($effect->getTarget(), $rule); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new DiffusionAuditorDatasource(); + } + + public function renderActionDescription($value) { + return pht('Add auditors: %s.', $this->renderHandleList($value)); + } + +} diff --git a/src/applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php b/src/applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php new file mode 100644 index 0000000000..d27876d40e --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php @@ -0,0 +1,30 @@ +getRule(); + $phid = $rule->getAuthorPHID(); + return $this->applyAuditors(array($phid), $rule); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Add rule author as auditor.'); + } + +} diff --git a/src/applications/diffusion/herald/DiffusionAuditorsHeraldAction.php b/src/applications/diffusion/herald/DiffusionAuditorsHeraldAction.php new file mode 100644 index 0000000000..32830cb673 --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionAuditorsHeraldAction.php @@ -0,0 +1,76 @@ +getAdapter(); + $object = $adapter->getObject(); + + $auditors = $object->getAudits(); + $auditors = mpull($auditors, null, 'getAuditorPHID'); + $current = array_keys($auditors); + + $allowed_types = array( + PhabricatorPeopleUserPHIDType::TYPECONST, + PhabricatorProjectProjectPHIDType::TYPECONST, + PhabricatorOwnersPackagePHIDType::TYPECONST, + ); + + $targets = $this->loadStandardTargets($phids, $allowed_types, $current); + if (!$targets) { + return; + } + + $phids = array_fuse(array_keys($targets)); + + // TODO: Convert this to be translatable, structured data eventually. + $reason_map = array(); + foreach ($phids as $phid) { + $reason_map[$phid][] = pht('%s Triggered Audit', $rule->getMonogram()); + } + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS) + ->setNewValue($phids) + ->setMetadataValue( + 'auditStatus', + PhabricatorAuditStatusConstants::AUDIT_REQUIRED) + ->setMetadataValue('auditReasonMap', $reason_map); + + $adapter->queueTransaction($xaction); + + $this->logEffect(self::DO_ADD_AUDITORS, $phids); + } + + protected function getActionEffectMap() { + return array( + self::DO_ADD_AUDITORS => array( + 'icon' => 'fa-user', + 'color' => 'green', + 'name' => pht('Added Auditors'), + ), + ); + } + + protected function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_ADD_AUDITORS: + return pht( + 'Added %s auditor(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + } + } + +} diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 14f07fe575..ab3e850475 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -12,8 +12,6 @@ final class HeraldCommitAdapter protected $commitData; private $commitDiff; - protected $auditMap = array(); - protected $affectedPaths; protected $affectedRevision; protected $affectedPackages; @@ -86,24 +84,6 @@ final class HeraldCommitAdapter return pht('This rule can trigger for **repositories** and **projects**.'); } - public function getActions($rule_type) { - switch ($rule_type) { - case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: - case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: - return array_merge( - array( - self::ACTION_AUDIT, - ), - parent::getActions($rule_type)); - case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: - return array_merge( - array( - self::ACTION_AUDIT, - ), - parent::getActions($rule_type)); - } - } - public static function newLegacyAdapter( PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, @@ -149,10 +129,6 @@ final class HeraldCommitAdapter return $this; } - public function getAuditMap() { - return $this->auditMap; - } - public function getHeraldName() { return 'r'. @@ -322,33 +298,6 @@ final class HeraldCommitAdapter return $result; } - public function applyHeraldEffects(array $effects) { - assert_instances_of($effects, 'HeraldEffect'); - - $result = array(); - foreach ($effects as $effect) { - $action = $effect->getAction(); - switch ($action) { - case self::ACTION_AUDIT: - foreach ($effect->getTarget() as $phid) { - if (empty($this->auditMap[$phid])) { - $this->auditMap[$phid] = array(); - } - $this->auditMap[$phid][] = $effect->getRule()->getID(); - } - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Triggered an audit.')); - break; - default: - $result[] = $this->applyStandardEffect($effect); - break; - } - } - return $result; - } - /* -( HarbormasterBuildableAdapterInterface )------------------------------ */ diff --git a/src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php b/src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php index 8c2490fd28..e4ae03915f 100644 --- a/src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php +++ b/src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php @@ -72,7 +72,7 @@ final class PhabricatorFlagAddFlagHeraldAction extends HeraldAction { return pht('Mark with %s flag.', $color); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_IGNORE: return pht( diff --git a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php index f06cdd1b26..82b9c09316 100644 --- a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php +++ b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php @@ -68,7 +68,7 @@ final class HarbormasterRunBuildPlansHeraldAction 'color' => 'red', 'name' => pht('Invalid Targets'), ), - self::DO_ALREADY_REQUIRED => array( + self::DO_BUILD => array( 'icon' => 'fa-play', 'color' => 'green', 'name' => pht('Building'), @@ -76,7 +76,7 @@ final class HarbormasterRunBuildPlansHeraldAction ); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_NO_TARGETS: return pht('Rule lists no targets.'); @@ -85,7 +85,7 @@ final class HarbormasterRunBuildPlansHeraldAction '%s build plan(s) are not valid: %s.', new PhutilNumber(count($data)), $this->renderHandleList($data)); - case self::DO_REQUIRED: + case self::DO_BUILD: return pht( 'Started %s build(s): %s.', new PhutilNumber(count($data)), diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index 529f3b7471..2785e7d833 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -9,11 +9,20 @@ abstract class HeraldAction extends Phobject { const STANDARD_NONE = 'standard.none'; const STANDARD_PHID_LIST = 'standard.phid.list'; + const DO_STANDARD_EMPTY = 'do.standard.empty'; + const DO_STANDARD_NO_EFFECT = 'do.standard.no-effect'; + const DO_STANDARD_INVALID = 'do.standard.invalid'; + const DO_STANDARD_UNLOADABLE = 'do.standard.unloadable'; + const DO_STANDARD_PERMISSION = 'do.standard.permission'; + abstract public function getHeraldActionName(); abstract public function supportsObject($object); abstract public function supportsRuleType($rule_type); abstract public function applyEffect($object, HeraldEffect $effect); - abstract public function renderActionEffectDescription($type, $data); + + protected function renderActionEffectDescription($type, $data) { + return null; + } public function getActionGroupKey() { return null; @@ -154,21 +163,21 @@ abstract class HeraldAction extends Phobject { } private function getActionEffectSpec($type) { - $map = $this->getActionEffectMap(); + $map = $this->getActionEffectMap() + $this->getStandardEffectMap(); return idx($map, $type, array()); } - public function renderActionEffectIcon($type, $data) { + final public function renderActionEffectIcon($type, $data) { $map = $this->getActionEffectSpec($type); return idx($map, 'icon'); } - public function renderActionEffectColor($type, $data) { + final public function renderActionEffectColor($type, $data) { $map = $this->getActionEffectSpec($type); return idx($map, 'color'); } - public function renderActionEffectName($type, $data) { + final public function renderActionEffectName($type, $data) { $map = $this->getActionEffectSpec($type); return idx($map, 'name'); } @@ -184,4 +193,173 @@ abstract class HeraldAction extends Phobject { ->render(); } + protected function loadStandardTargets( + array $phids, + array $allowed_types, + array $current_value) { + + $phids = array_fuse($phids); + if (!$phids) { + $this->logEffect(self::DO_STANDARD_EMPTY); + } + + $current_value = array_fuse($current_value); + $no_effect = array(); + foreach ($phids as $phid) { + if (isset($current_value[$phid])) { + $no_effect[] = $phid; + unset($phids[$phid]); + } + } + + if ($no_effect) { + $this->logEffect(self::DO_STANDARD_NO_EFFECT, $no_effect); + } + + if (!$phids) { + return; + } + + $allowed_types = array_fuse($allowed_types); + $invalid = array(); + foreach ($phids as $phid) { + $type = phid_get_type($phid); + if ($type == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { + $invalid[] = $phid; + unset($phids[$phid]); + continue; + } + + if ($allowed_types && empty($allowed_types[$type])) { + $invalid[] = $phid; + unset($phids[$phid]); + continue; + } + } + + if ($invalid) { + $this->logEffect(self::DO_STANDARD_INVALID, $invalid); + } + + if (!$phids) { + return; + } + + $targets = id(new PhabricatorObjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($phids) + ->execute(); + $targets = mpull($targets, null, 'getPHID'); + + $unloadable = array(); + foreach ($phids as $phid) { + if (empty($targets[$phid])) { + $unloadable[] = $phid; + unset($phids[$phid]); + } + } + + if ($unloadable) { + $this->logEffect(self::DO_STANDARD_UNLOADABLE, $unloadable); + } + + if (!$phids) { + return; + } + + $adapter = $this->getAdapter(); + $object = $adapter->getObject(); + + if ($object instanceof PhabricatorPolicyInterface) { + $no_permission = array(); + foreach ($targets as $phid => $target) { + if (!($target instanceof PhabricatorUser)) { + continue; + } + + $can_view = PhabricatorPolicyFilter::hasCapability( + $target, + $object, + PhabricatorPolicyCapability::CAN_VIEW); + if ($can_view) { + continue; + } + + $no_permission[] = $phid; + unset($targets[$phid]); + } + } + + if ($no_permission) { + $this->logEffect(self::DO_STANDARD_PERMISSION, $no_permission); + } + + return $targets; + } + + protected function getStandardEffectMap() { + return array( + self::DO_STANDARD_EMPTY => array( + 'icon' => 'fa-ban', + 'color' => 'grey', + 'name' => pht('No Targets'), + ), + self::DO_STANDARD_NO_EFFECT => array( + 'icon' => 'fa-circle-o', + 'color' => 'grey', + 'name' => pht('No Effect'), + ), + self::DO_STANDARD_INVALID => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Invalid Targets'), + ), + self::DO_STANDARD_UNLOADABLE => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Unloadable Targets'), + ), + self::DO_STANDARD_PERMISSION => array( + 'icon' => 'fa-lock', + 'color' => 'red', + 'name' => pht('No Permission'), + ), + ); + } + + final public function renderEffectDescription($type, $data) { + $result = $this->renderActionEffectDescription($type, $data); + if ($result !== null) { + return $result; + } + + switch ($type) { + case self::DO_STANDARD_EMPTY: + return pht( + 'This action specifies no targets.'); + case self::DO_STANDARD_NO_EFFECT: + return pht( + 'This action has no effect on %s target(s): %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_STANDARD_INVALID: + return pht( + '%s target(s) are invalid or of the wrong type: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_STANDARD_UNLOADABLE: + return pht( + '%s target(s) could not be loaded: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + case self::DO_STANDARD_PERMISSION: + return pht( + '%s target(s) do not have permission to see this object: %s.', + new PhutilNumber(count($data)), + $this->renderHandleList($data)); + } + + return null; + } + } diff --git a/src/applications/herald/action/HeraldDoNothingAction.php b/src/applications/herald/action/HeraldDoNothingAction.php index 110e5707ac..21141bf47f 100644 --- a/src/applications/herald/action/HeraldDoNothingAction.php +++ b/src/applications/herald/action/HeraldDoNothingAction.php @@ -43,7 +43,7 @@ final class HeraldDoNothingAction extends HeraldAction { return pht('Do nothing.'); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { return pht('Did nothing.'); } diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 0c32d3238a..7f9c0665e7 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -26,7 +26,6 @@ abstract class HeraldAdapter extends Phobject { const CONDITION_IS_TRUE = 'true'; const CONDITION_IS_FALSE = 'false'; - const ACTION_AUDIT = 'audit'; const ACTION_BLOCK = 'block'; private $contentSource; @@ -712,13 +711,11 @@ abstract class HeraldAdapter extends Phobject { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: $standard = array( - self::ACTION_AUDIT => pht('Trigger an Audit by'), self::ACTION_BLOCK => pht('Block change with message'), ); break; case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: $standard = array( - self::ACTION_AUDIT => pht('Trigger an Audit by me'), ); break; default: @@ -757,8 +754,6 @@ abstract class HeraldAdapter extends Phobject { $rule_type = $rule->getRuleType(); if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { switch ($action->getAction()) { - case self::ACTION_AUDIT: - break; case self::ACTION_BLOCK: break; default: @@ -790,14 +785,8 @@ abstract class HeraldAdapter extends Phobject { $is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); - if ($is_personal) { + if (!$is_personal) { switch ($action) { - case self::ACTION_AUDIT: - return new HeraldEmptyFieldValue(); - } - } else { - switch ($action) { - case self::ACTION_AUDIT: case self::ACTION_BLOCK: return new HeraldTextFieldValue(); } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index f0bad8d393..64563b7ef8 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -376,7 +376,7 @@ final class HeraldTranscriptController extends HeraldController { $icon = $action->renderActionEffectIcon($type, $data); $color = $action->renderActionEffectColor($type, $data); $name = $action->renderActionEffectName($type, $data); - $note = $action->renderActionEffectDescription($type, $data); + $note = $action->renderEffectDescription($type, $data); } else { $icon = 'fa-question-circle'; $color = 'indigo'; diff --git a/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php b/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php index 092840df68..9ff4c08b13 100644 --- a/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php +++ b/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php @@ -142,7 +142,7 @@ final class LegalpadRequireSignatureHeraldAction ); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_NO_TARGETS: return pht('Rule lists no targets.'); diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php index 9e031bc8b5..169446f7cf 100644 --- a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php @@ -90,7 +90,7 @@ abstract class ManiphestTaskAssignHeraldAction ); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_EMPTY: return pht('Action lists no user to assign.'); diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php index 07f216447d..60e72ce26a 100644 --- a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php @@ -50,7 +50,7 @@ abstract class PhabricatorMetaMTAEmailHeraldAction ); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_SEND: return pht( diff --git a/src/applications/project/herald/PhabricatorProjectHeraldAction.php b/src/applications/project/herald/PhabricatorProjectHeraldAction.php index 599a218213..2d8c2c9b48 100644 --- a/src/applications/project/herald/PhabricatorProjectHeraldAction.php +++ b/src/applications/project/herald/PhabricatorProjectHeraldAction.php @@ -145,7 +145,7 @@ abstract class PhabricatorProjectHeraldAction ); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_NO_TARGETS: return pht('Rule lists no projects.'); diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php index 374fcfa1fb..5ac859a527 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php @@ -196,7 +196,7 @@ abstract class PhabricatorSubscriptionsHeraldAction ); } - public function renderActionEffectDescription($type, $data) { + protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_NO_TARGETS: return pht('Rule lists no targets.'); diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 00bc82305c..108319139d 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1338,6 +1338,16 @@ final class PhabricatorUSEnglishTranslation 'Required signatures: %2$s.', ), + 'Started %s build(s): %s.' => array( + 'Started a build: %2$s.', + 'Started builds: %2$s.', + ), + + 'Added %s auditor(s): %s.' => array( + 'Added an auditor: %2$s.', + 'Added auditors: %2$s.', + ), + ); } From a3e2f655eb87fd2ed602ae3b06d8b118d9b51c8e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Aug 2015 12:54:40 -0700 Subject: [PATCH 068/102] Modularize the Diffusion/Differential "Block" Herald actions Also removes HeraldCustomAction. This completes action modularization. Ref T8726. --- .../sql/autopatches/20150803.herald.2.sql | 13 ++ src/__phutil_library_map__.php | 6 +- .../editor/DifferentialDiffEditor.php | 3 +- .../herald/DifferentialBlockHeraldAction.php | 56 ++++++ .../herald/HeraldDifferentialDiffAdapter.php | 39 ---- .../engine/DiffusionCommitHookEngine.php | 4 +- .../herald/DiffusionBlockHeraldAction.php | 56 ++++++ .../herald/HeraldPreCommitAdapter.php | 39 ---- .../herald/action/HeraldAction.php | 23 +++ .../herald/adapter/HeraldAdapter.php | 188 ++++-------------- .../herald/extension/HeraldCustomAction.php | 20 -- 11 files changed, 195 insertions(+), 252 deletions(-) create mode 100644 resources/sql/autopatches/20150803.herald.2.sql create mode 100644 src/applications/differential/herald/DifferentialBlockHeraldAction.php create mode 100644 src/applications/diffusion/herald/DiffusionBlockHeraldAction.php delete mode 100644 src/applications/herald/extension/HeraldCustomAction.php diff --git a/resources/sql/autopatches/20150803.herald.2.sql b/resources/sql/autopatches/20150803.herald.2.sql new file mode 100644 index 0000000000..4d48bc03b6 --- /dev/null +++ b/resources/sql/autopatches/20150803.herald.2.sql @@ -0,0 +1,13 @@ +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'diffusion.block' + WHERE r.contentType != 'differential.diff' + AND a.action = 'block'; + +UPDATE {$NAMESPACE}_herald.herald_action a + JOIN {$NAMESPACE}_herald.herald_rule r + ON a.ruleID = r.id + SET a.action = 'differential.block' + WHERE r.contentType = 'differential.diff' + AND a.action = 'block'; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1d92b7a6de..3927a010b0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -303,6 +303,7 @@ phutil_register_library_map(array( 'DifferentialAuditorsField' => 'applications/differential/customfield/DifferentialAuditorsField.php', 'DifferentialAuthorField' => 'applications/differential/customfield/DifferentialAuthorField.php', 'DifferentialBlameRevisionField' => 'applications/differential/customfield/DifferentialBlameRevisionField.php', + 'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php', 'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php', 'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php', 'DifferentialChangeType' => 'applications/differential/constants/DifferentialChangeType.php', @@ -501,6 +502,7 @@ phutil_register_library_map(array( 'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php', 'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php', 'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php', + 'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', @@ -1031,7 +1033,6 @@ phutil_register_library_map(array( 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php', 'HeraldContentSourceField' => 'applications/herald/field/HeraldContentSourceField.php', 'HeraldController' => 'applications/herald/controller/HeraldController.php', - 'HeraldCustomAction' => 'applications/herald/extension/HeraldCustomAction.php', 'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php', 'HeraldDifferentialAdapter' => 'applications/differential/herald/HeraldDifferentialAdapter.php', 'HeraldDifferentialDiffAdapter' => 'applications/differential/herald/HeraldDifferentialDiffAdapter.php', @@ -3908,6 +3909,7 @@ phutil_register_library_map(array( 'DifferentialAuditorsField' => 'DifferentialStoredCustomField', 'DifferentialAuthorField' => 'DifferentialCustomField', 'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField', + 'DifferentialBlockHeraldAction' => 'HeraldAction', 'DifferentialBranchField' => 'DifferentialCustomField', 'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialChangeType' => 'Phobject', @@ -4135,6 +4137,7 @@ phutil_register_library_map(array( 'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsHeraldAction' => 'HeraldAction', + 'DiffusionBlockHeraldAction' => 'HeraldAction', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', 'DiffusionBranchTableView' => 'DiffusionView', @@ -4745,7 +4748,6 @@ phutil_register_library_map(array( 'HeraldConditionTranscript' => 'Phobject', 'HeraldContentSourceField' => 'HeraldField', 'HeraldController' => 'PhabricatorController', - 'HeraldCustomAction' => 'Phobject', 'HeraldDAO' => 'PhabricatorLiskDAO', 'HeraldDifferentialAdapter' => 'HeraldAdapter', 'HeraldDifferentialDiffAdapter' => 'HeraldDifferentialAdapter', diff --git a/src/applications/differential/editor/DifferentialDiffEditor.php b/src/applications/differential/editor/DifferentialDiffEditor.php index 235ab84c21..0058485e63 100644 --- a/src/applications/differential/editor/DifferentialDiffEditor.php +++ b/src/applications/differential/editor/DifferentialDiffEditor.php @@ -131,10 +131,11 @@ final class DifferentialDiffEditor $rules = mpull($rules, null, 'getID'); $effects = $engine->applyRules($rules, $adapter); + $action_block = DifferentialBlockHeraldAction::ACTIONCONST; $blocking_effect = null; foreach ($effects as $effect) { - if ($effect->getAction() == HeraldAdapter::ACTION_BLOCK) { + if ($effect->getAction() == $action_block) { $blocking_effect = $effect; break; } diff --git a/src/applications/differential/herald/DifferentialBlockHeraldAction.php b/src/applications/differential/herald/DifferentialBlockHeraldAction.php new file mode 100644 index 0000000000..b43e415b13 --- /dev/null +++ b/src/applications/differential/herald/DifferentialBlockHeraldAction.php @@ -0,0 +1,56 @@ +logEffect(self::DO_BLOCK); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_TEXT; + } + + public function renderActionDescription($value) { + return pht('Block diff with message: %s', $value); + } + + protected function getActionEffectMap() { + return array( + self::DO_BLOCK => array( + 'icon' => 'fa-stop', + 'color' => 'red', + 'name' => pht('Blocked Diff'), + ), + ); + } + + protected function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_ADD_AUDITORS: + return pht('Blocked diff.'); + } + } +} diff --git a/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php b/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php index fe5ff3f439..9528978050 100644 --- a/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php @@ -63,43 +63,4 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { return pht('New Diff'); } - public function getActionNameMap($rule_type) { - return array( - self::ACTION_BLOCK => pht('Block diff with message'), - ) + parent::getActionNameMap($rule_type); - } - - public function getActions($rule_type) { - switch ($rule_type) { - case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: - return array_merge( - array( - self::ACTION_BLOCK, - ), - parent::getActions($rule_type)); - } - } - - public function applyHeraldEffects(array $effects) { - assert_instances_of($effects, 'HeraldEffect'); - - $result = array(); - foreach ($effects as $effect) { - $action = $effect->getAction(); - switch ($action) { - case self::ACTION_BLOCK: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Blocked diff.')); - break; - default: - $result[] = $this->applyStandardEffect($effect); - break; - } - } - - return $result; - } - } diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index f84eb13bdf..ee257ebee0 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -325,9 +325,11 @@ final class DiffusionCommitHookEngine extends Phobject { $this->emailPHIDs[$email_phid] = $email_phid; } + $block_action = DiffusionBlockHeraldAction::ACTIONCONST; + if ($blocking_effect === null) { foreach ($effects as $effect) { - if ($effect->getAction() == HeraldAdapter::ACTION_BLOCK) { + if ($effect->getAction() == $block_action) { $blocking_effect = $effect; $blocked_update = $update; break; diff --git a/src/applications/diffusion/herald/DiffusionBlockHeraldAction.php b/src/applications/diffusion/herald/DiffusionBlockHeraldAction.php new file mode 100644 index 0000000000..4b56500b65 --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionBlockHeraldAction.php @@ -0,0 +1,56 @@ +logEffect(self::DO_BLOCK); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_TEXT; + } + + public function renderActionDescription($value) { + return pht('Block push with message: %s', $value); + } + + protected function getActionEffectMap() { + return array( + self::DO_BLOCK => array( + 'icon' => 'fa-stop', + 'color' => 'red', + 'name' => pht('Blocked Push'), + ), + ); + } + + protected function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_ADD_AUDITORS: + return pht('Blocked push.'); + } + } +} diff --git a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php index 448f4eaefd..6b5b94b578 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php @@ -73,43 +73,4 @@ abstract class HeraldPreCommitAdapter extends HeraldAdapter { $this->hookEngine->getRepository()->getProjectPHIDs()); } - public function getActions($rule_type) { - switch ($rule_type) { - case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: - case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: - return array_merge( - array( - self::ACTION_BLOCK, - ), - parent::getActions($rule_type)); - case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: - return array_merge( - array( - ), - parent::getActions($rule_type)); - } - } - - public function applyHeraldEffects(array $effects) { - assert_instances_of($effects, 'HeraldEffect'); - - $result = array(); - foreach ($effects as $effect) { - $action = $effect->getAction(); - switch ($action) { - case self::ACTION_BLOCK: - $result[] = new HeraldApplyTranscript( - $effect, - true, - pht('Blocked push.')); - break; - default: - $result[] = $this->applyStandardEffect($effect); - break; - } - } - - return $result; - } - } diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index 2785e7d833..d7d764c99d 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -8,12 +8,15 @@ abstract class HeraldAction extends Phobject { const STANDARD_NONE = 'standard.none'; const STANDARD_PHID_LIST = 'standard.phid.list'; + const STANDARD_TEXT = 'standard.text'; const DO_STANDARD_EMPTY = 'do.standard.empty'; const DO_STANDARD_NO_EFFECT = 'do.standard.no-effect'; const DO_STANDARD_INVALID = 'do.standard.invalid'; const DO_STANDARD_UNLOADABLE = 'do.standard.unloadable'; const DO_STANDARD_PERMISSION = 'do.standard.permission'; + const DO_STANDARD_INVALID_ACTION = 'do.standard.invalid-action'; + const DO_STANDARD_WRONG_RULE_TYPE = 'do.standard.wrong-rule-type'; abstract public function getHeraldActionName(); abstract public function supportsObject($object); @@ -48,6 +51,8 @@ abstract class HeraldAction extends Phobject { switch ($this->getHeraldActionStandardType()) { case self::STANDARD_NONE: return new HeraldEmptyFieldValue(); + case self::STANDARD_TEXT: + return new HeraldTextFieldValue(); case self::STANDARD_PHID_LIST: $tokenizer = id(new HeraldTokenizerFieldValue()) ->setKey($this->getHeraldActionName()) @@ -324,6 +329,16 @@ abstract class HeraldAction extends Phobject { 'color' => 'red', 'name' => pht('No Permission'), ), + self::DO_STANDARD_INVALID_ACTION => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Invalid Action'), + ), + self::DO_STANDARD_WRONG_RULE_TYPE => array( + 'icon' => 'fa-ban', + 'color' => 'red', + 'name' => pht('Wrong Rule Type'), + ), ); } @@ -357,6 +372,14 @@ abstract class HeraldAction extends Phobject { '%s target(s) do not have permission to see this object: %s.', new PhutilNumber(count($data)), $this->renderHandleList($data)); + case self::DO_STANDARD_INVALID_ACTION: + return pht( + 'No implementation is available for rule "%s".', + $data); + case self::DO_STANDARD_WRONG_RULE_TYPE: + return pht( + 'This action does not support rules of type "%s".', + $data); } return null; diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 7f9c0665e7..5d5e2a55f5 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -26,12 +26,9 @@ abstract class HeraldAdapter extends Phobject { const CONDITION_IS_TRUE = 'true'; const CONDITION_IS_FALSE = 'false'; - const ACTION_BLOCK = 'block'; - private $contentSource; private $isNewObject; private $applicationEmail; - private $customActions = null; private $queuedTransactions = array(); private $emailPHIDs = array(); private $forcedEmailPHIDs = array(); @@ -55,37 +52,6 @@ abstract class HeraldAdapter extends Phobject { return $this; } - public function getCustomActions() { - if ($this->customActions === null) { - $custom_actions = id(new PhutilSymbolLoader()) - ->setAncestorClass('HeraldCustomAction') - ->loadObjects(); - - foreach ($custom_actions as $key => $object) { - if (!$object->appliesToAdapter($this)) { - unset($custom_actions[$key]); - } - } - - $this->customActions = array(); - foreach ($custom_actions as $action) { - $key = $action->getActionKey(); - - if (array_key_exists($key, $this->customActions)) { - throw new Exception( - pht( - "More than one Herald custom action implementation ". - "handles the action key: '%s'.", - $key)); - } - - $this->customActions[$key] = $action; - } - } - - return $this->customActions; - } - public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source; return $this; @@ -145,19 +111,6 @@ abstract class HeraldAdapter extends Phobject { return $result; } - protected function handleCustomHeraldEffect(HeraldEffect $effect) { - $custom_action = idx($this->getCustomActions(), $effect->getAction()); - - if ($custom_action !== null) { - return $custom_action->applyEffect( - $this, - $this->getObject(), - $effect); - } - - return null; - } - public function isAvailableToUser(PhabricatorUser $viewer) { $applications = id(new PhabricatorApplicationQuery()) ->setViewer($viewer) @@ -654,6 +607,20 @@ abstract class HeraldAdapter extends Phobject { return $this->actionMap; } + private function requireActionImplementation($action_key) { + $action = $this->getActionImplementation($action_key); + + if (!$action) { + throw new Exception( + pht( + 'No action with key "%s" is available to Herald adapter "%s".', + $action_key, + get_class($this))); + } + + return $action; + } + private function getActionsForRuleType($rule_type) { $actions = $this->getActionImplementationMap(); @@ -683,22 +650,8 @@ abstract class HeraldAdapter extends Phobject { return $action->getActionGroupKey(); } - public function getCustomActionsForRuleType($rule_type) { - $results = array(); - foreach ($this->getCustomActions() as $custom_action) { - if ($custom_action->appliesToRuleType($rule_type)) { - $results[] = $custom_action; - } - } - return $results; - } - public function getActions($rule_type) { - $custom_actions = $this->getCustomActionsForRuleType($rule_type); - $custom_actions = mpull($custom_actions, 'getActionKey'); - - $actions = $custom_actions; - + $actions = array(); foreach ($this->getActionsForRuleType($rule_type) as $key => $action) { $actions[] = $key; } @@ -707,62 +660,21 @@ abstract class HeraldAdapter extends Phobject { } public function getActionNameMap($rule_type) { - switch ($rule_type) { - case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: - case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: - $standard = array( - self::ACTION_BLOCK => pht('Block change with message'), - ); - break; - case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: - $standard = array( - ); - break; - default: - throw new Exception(pht("Unknown rule type '%s'!", $rule_type)); - } - - $custom_actions = $this->getCustomActionsForRuleType($rule_type); - $standard += mpull($custom_actions, 'getActionName', 'getActionKey'); - + $map = array(); foreach ($this->getActionsForRuleType($rule_type) as $key => $action) { - $standard[$key] = $action->getHeraldActionName(); + $map[$key] = $action->getHeraldActionName(); } - return $standard; + return $map; } public function willSaveAction( HeraldRule $rule, HeraldActionRecord $action) { - $impl = $this->getActionImplementation($action->getAction()); - if ($impl) { - $target = $action->getTarget(); - $target = $impl->willSaveActionValue($target); - $action->setTarget($target); - return; - } - + $impl = $this->requireActionImplementation($action->getAction()); $target = $action->getTarget(); - if (is_array($target)) { - $target = array_keys($target); - } - - $author_phid = $rule->getAuthorPHID(); - - $rule_type = $rule->getRuleType(); - if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { - switch ($action->getAction()) { - case self::ACTION_BLOCK: - break; - default: - throw new HeraldInvalidActionException( - pht( - 'Unrecognized action type "%s"!', - $action->getAction())); - } - } + $target = $impl->willSaveActionValue($target); $action->setTarget($target); } @@ -778,26 +690,8 @@ abstract class HeraldAdapter extends Phobject { } public function getValueTypeForAction($action, $rule_type) { - $impl = $this->getActionImplementation($action); - if ($impl) { - return $impl->getHeraldActionValueType(); - } - - $is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); - - if (!$is_personal) { - switch ($action) { - case self::ACTION_BLOCK: - return new HeraldTextFieldValue(); - } - } - - $custom_action = idx($this->getCustomActions(), $action); - if ($custom_action !== null) { - return $custom_action->getActionType(); - } - - throw new Exception(pht("Unknown or invalid action '%s'.", $action)); + $impl = $this->requireActionImplementation($action); + return $impl->getHeraldActionValueType(); } private function buildTokenizerFieldValue( @@ -1113,38 +1007,32 @@ abstract class HeraldAdapter extends Phobject { $rule_type = $effect->getRule()->getRuleType(); $impl = $this->getActionImplementation($action); - if ($impl) { - if ($impl->supportsRuleType($rule_type)) { - $impl->applyEffect($this->getObject(), $effect); - return $impl->getApplyTranscript($effect); - } - } - - $supported = $this->getActions($rule_type); - $supported = array_fuse($supported); - if (empty($supported[$action])) { + if (!$impl) { return new HeraldApplyTranscript( $effect, false, - pht( - 'Adapter "%s" does not support action "%s" for rule type "%s".', - get_class($this), - $action, - $rule_type)); + array( + array( + HeraldAction::DO_STANDARD_INVALID_ACTION, + $action, + ), + )); } - $result = $this->handleCustomHeraldEffect($effect); - - if (!$result) { + if (!$impl->supportsRuleType($rule_type)) { return new HeraldApplyTranscript( $effect, false, - pht( - 'No custom action exists to handle rule action "%s".', - $action)); + array( + array( + HeraldAction::DO_STANDARD_WRONG_RULE_TYPE, + $rule_type, + ), + )); } - return $result; + $impl->applyEffect($this->getObject(), $effect); + return $impl->getApplyTranscript($effect); } public function loadEdgePHIDs($type) { diff --git a/src/applications/herald/extension/HeraldCustomAction.php b/src/applications/herald/extension/HeraldCustomAction.php deleted file mode 100644 index 2701a8ae66..0000000000 --- a/src/applications/herald/extension/HeraldCustomAction.php +++ /dev/null @@ -1,20 +0,0 @@ - Date: Mon, 3 Aug 2015 13:34:31 -0700 Subject: [PATCH 069/102] Share target filtering code in HeraldAction Ref T8726. This shares some target filtering code with the base class. --- ...iewersAddBlockingReviewersHeraldAction.php | 1 - ...alReviewersAddBlockingSelfHeraldAction.php | 1 - ...ntialReviewersAddReviewersHeraldAction.php | 1 - ...fferentialReviewersAddSelfHeraldAction.php | 1 - .../DifferentialReviewersHeraldAction.php | 115 ++-------------- .../HarbormasterRunBuildPlansHeraldAction.php | 50 +------ .../LegalpadRequireSignatureHeraldAction.php | 78 +---------- .../ManiphestTaskAssignHeraldAction.php | 73 ++--------- .../PhabricatorMetaMTAEmailHeraldAction.php | 16 +++ .../herald/PhabricatorProjectHeraldAction.php | 81 ++---------- .../PhabricatorSubscriptionsHeraldAction.php | 124 +++++------------- ...habricatorApplicationTransactionEditor.php | 18 +-- .../PhabricatorUSEnglishTranslation.php | 10 ++ 13 files changed, 118 insertions(+), 451 deletions(-) diff --git a/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php index 0ad16ea83b..79a00bc940 100644 --- a/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php @@ -30,4 +30,3 @@ final class DifferentialReviewersAddBlockingReviewersHeraldAction } } - diff --git a/src/applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php index d173e4b814..938f298955 100644 --- a/src/applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php @@ -27,4 +27,3 @@ final class DifferentialReviewersAddBlockingSelfHeraldAction } } - diff --git a/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php index 9530f903bf..45f209385e 100644 --- a/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php @@ -30,4 +30,3 @@ final class DifferentialReviewersAddReviewersHeraldAction } } - diff --git a/src/applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php index de22adbd27..a7e9991901 100644 --- a/src/applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php @@ -27,4 +27,3 @@ final class DifferentialReviewersAddSelfHeraldAction } } - diff --git a/src/applications/differential/herald/DifferentialReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersHeraldAction.php index 848dfac9ff..c568537f7c 100644 --- a/src/applications/differential/herald/DifferentialReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersHeraldAction.php @@ -3,11 +3,7 @@ abstract class DifferentialReviewersHeraldAction extends HeraldAction { - const DO_NO_TARGETS = 'do.no-targets'; const DO_AUTHORS = 'do.authors'; - const DO_INVALID = 'do.invalid'; - const DO_ALREADY_REVIEWERS = 'do.already-reviewers'; - const DO_PERMISSION = 'do.permission'; const DO_ADD_REVIEWERS = 'do.add-reviewers'; const DO_ADD_BLOCKING_REVIEWERS = 'do.add-blocking-reviewers'; @@ -24,10 +20,6 @@ abstract class DifferentialReviewersHeraldAction $object = $adapter->getObject(); $phids = array_fuse($phids); - if (!$phids) { - $this->logEffect(self::DO_NO_TARGETS); - return; - } // Don't try to add revision authors as reviewers. $authors = array(); @@ -40,10 +32,9 @@ abstract class DifferentialReviewersHeraldAction if ($authors) { $this->logEffect(self::DO_AUTHORS, $authors); - } - - if (!$phids) { - return; + if (!$phids) { + return; + } } $reviewers = $object->getReviewerStatus(); @@ -58,7 +49,7 @@ abstract class DifferentialReviewersHeraldAction $new_strength = DifferentialReviewerStatus::getStatusStrength( $new_status); - $already = array(); + $current = array(); foreach ($phids as $phid) { if (!isset($reviewers[$phid])) { continue; @@ -72,65 +63,20 @@ abstract class DifferentialReviewersHeraldAction continue; } - $already[] = $phid; - unset($phids[$phid]); + $current[] = $phid; } - if ($already) { - $this->logEffect(self::DO_ALREADY_REVIEWERS, $already); - } + $allowed_types = array( + PhabricatorPeopleUserPHIDType::TYPECONST, + PhabricatorProjectProjectPHIDType::TYPECONST, + ); - if (!$phids) { + $targets = $this->loadStandardTargets($phids, $allowed_types, $current); + if (!$targets) { return; } - $targets = id(new PhabricatorObjectQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($phids) - ->execute(); - $targets = mpull($targets, null, 'getPHID'); - - $invalid = array(); - foreach ($phids as $phid) { - if (empty($targets[$phid])) { - $invalid[] = $phid; - unset($phids[$phid]); - } - } - - if ($invalid) { - $this->logEffect(self::DO_INVALID, $invalid); - } - - if (!$phids) { - return; - } - - $no_access = array(); - foreach ($targets as $phid => $target) { - if (!($target instanceof PhabricatorUser)) { - continue; - } - - $can_view = PhabricatorPolicyFilter::hasCapability( - $target, - $object, - PhabricatorPolicyCapability::CAN_VIEW); - if ($can_view) { - continue; - } - - $no_access[] = $phid; - unset($phids[$phid]); - } - - if ($no_access) { - $this->logEffect(self::DO_PERMISSION, $no_access); - } - - if (!$phids) { - return; - } + $phids = array_fuse(array_keys($targets)); $value = array(); foreach ($phids as $phid) { @@ -162,31 +108,11 @@ abstract class DifferentialReviewersHeraldAction protected function getActionEffectMap() { return array( - self::DO_NO_TARGETS => array( - 'icon' => 'fa-ban', - 'color' => 'grey', - 'name' => pht('No Targets'), - ), self::DO_AUTHORS => array( 'icon' => 'fa-user', 'color' => 'grey', 'name' => pht('Revision Author'), ), - self::DO_INVALID => array( - 'icon' => 'fa-ban', - 'color' => 'red', - 'name' => pht('Invalid Targets'), - ), - self::DO_ALREADY_REVIEWERS => array( - 'icon' => 'fa-user', - 'color' => 'grey', - 'name' => pht('Already Reviewers'), - ), - self::DO_PERMISSION => array( - 'icon' => 'fa-ban', - 'color' => 'red', - 'name' => pht('No Permission'), - ), self::DO_ADD_REVIEWERS => array( 'icon' => 'fa-user', 'color' => 'green', @@ -202,27 +128,10 @@ abstract class DifferentialReviewersHeraldAction protected function renderActionEffectDescription($type, $data) { switch ($type) { - case self::DO_NO_TARGETS: - return pht('Rule lists no targets.'); case self::DO_AUTHORS: return pht( 'Declined to add revision author as reviewer: %s.', $this->renderHandleList($data)); - case self::DO_INVALID: - return pht( - 'Declined to act on %s invalid target(s): %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); - case self::DO_ALREADY_REVIEWERS: - return pht( - '%s target(s) were already reviewers: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); - case self::DO_PERMISSION: - return pht( - '%s target(s) do not have permission to see the revision: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); case self::DO_ADD_REVIEWERS: return pht( 'Added %s reviewer(s): %s.', diff --git a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php index 82b9c09316..830598e812 100644 --- a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php +++ b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php @@ -3,8 +3,6 @@ final class HarbormasterRunBuildPlansHeraldAction extends HeraldAction { - const DO_NO_TARGETS = 'do.no-targets'; - const DO_INVALID = 'do.invalid'; const DO_BUILD = 'do.build'; const ACTIONCONST = 'harbormaster.build'; @@ -21,33 +19,16 @@ final class HarbormasterRunBuildPlansHeraldAction protected function applyBuilds(array $phids) { $adapter = $this->getAdapter(); - $phids = array_fuse($phids); - if (!$phids) { - $this->logEffect(self::DO_NO_TARGETS); + $allowed_types = array( + HarbormasterBuildPlanPHIDType::TYPECONST, + ); + + $targets = $this->loadStandardTargets($phids, $allowed_types, array()); + if (!$targets) { return; } - $plans = id(new HarbormasterBuildPlanQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($phids) - ->execute(); - $plans = mpull($plans, null, 'getPHID'); - - $invalid = array(); - foreach ($phids as $phid) { - if (empty($plans[$phid])) { - $invalid[] = $phid; - unset($plans[$phid]); - } - } - - if ($invalid) { - $this->logEffect(self::DO_INVALID, $phids); - } - - if (!$phids) { - return; - } + $phids = array_fuse(array_keys($targets)); foreach ($phids as $phid) { $adapter->queueHarbormasterBuildPlanPHID($phid); @@ -58,16 +39,6 @@ final class HarbormasterRunBuildPlansHeraldAction protected function getActionEffectMap() { return array( - self::DO_NO_TARGETS => array( - 'icon' => 'fa-ban', - 'color' => 'grey', - 'name' => pht('No Targets'), - ), - self::DO_INVALID => array( - 'icon' => 'fa-ban', - 'color' => 'red', - 'name' => pht('Invalid Targets'), - ), self::DO_BUILD => array( 'icon' => 'fa-play', 'color' => 'green', @@ -78,13 +49,6 @@ final class HarbormasterRunBuildPlansHeraldAction protected function renderActionEffectDescription($type, $data) { switch ($type) { - case self::DO_NO_TARGETS: - return pht('Rule lists no targets.'); - case self::DO_INVALID: - return pht( - '%s build plan(s) are not valid: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); case self::DO_BUILD: return pht( 'Started %s build(s): %s.', diff --git a/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php b/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php index 9ff4c08b13..22d41449ad 100644 --- a/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php +++ b/src/applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php @@ -3,9 +3,6 @@ final class LegalpadRequireSignatureHeraldAction extends HeraldAction { - const DO_NO_TARGETS = 'do.no-targets'; - const DO_ALREADY_REQUIRED = 'do.already-required'; - const DO_INVALID = 'do.invalid'; const DO_SIGNED = 'do.signed'; const DO_REQUIRED = 'do.required'; @@ -24,54 +21,20 @@ final class LegalpadRequireSignatureHeraldAction protected function applyRequire(array $phids) { $adapter = $this->getAdapter(); + $edgetype_legal = LegalpadObjectNeedsSignatureEdgeType::EDGECONST; - - $phids = array_fuse($phids); - - if (!$phids) { - $this->logEffect(self::DO_NO_TARGETS); - return; - } - $current = $adapter->loadEdgePHIDs($edgetype_legal); - $already = array(); - foreach ($phids as $phid) { - if (isset($current[$phid])) { - $already[] = $phid; - unset($phids[$phid]); - } - } + $allowed_types = array( + PhabricatorLegalpadDocumentPHIDType::TYPECONST, + ); - if ($already) { - $this->logEffect(self::DO_ALREADY_REQUIRED, $phids); - } - - if (!$phids) { + $targets = $this->loadStandardTargets($phids, $allowed_types, $current); + if (!$targets) { return; } - $documents = id(new LegalpadDocumentQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($phids) - ->execute(); - $documents = mpull($documents, null, 'getPHID'); - - $invalid = array(); - foreach ($phids as $phid) { - if (empty($documents[$phid])) { - $invalid[] = $phid; - unset($documents[$phid]); - } - } - - if ($invalid) { - $this->logEffect(self::DO_INVALID, $phids); - } - - if (!$phids) { - return; - } + $phids = array_fuse(array_keys($targets)); $object = $adapter->getObject(); $author_phid = $object->getAuthorPHID(); @@ -114,21 +77,6 @@ final class LegalpadRequireSignatureHeraldAction protected function getActionEffectMap() { return array( - self::DO_NO_TARGETS => array( - 'icon' => 'fa-ban', - 'color' => 'grey', - 'name' => pht('No Targets'), - ), - self::DO_INVALID => array( - 'icon' => 'fa-ban', - 'color' => 'red', - 'name' => pht('Invalid Targets'), - ), - self::DO_ALREADY_REQUIRED => array( - 'icon' => 'fa-terminal', - 'color' => 'grey', - 'name' => pht('Already Required'), - ), self::DO_SIGNED => array( 'icon' => 'fa-terminal', 'color' => 'green', @@ -144,18 +92,6 @@ final class LegalpadRequireSignatureHeraldAction protected function renderActionEffectDescription($type, $data) { switch ($type) { - case self::DO_NO_TARGETS: - return pht('Rule lists no targets.'); - case self::DO_INVALID: - return pht( - '%s document(s) are not valid: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); - case self::DO_ALREADY_REQUIRED: - return pht( - '%s document signature(s) are already required: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); case self::DO_SIGNED: return pht( '%s document(s) are already signed: %s.', diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php index 169446f7cf..79e74e7466 100644 --- a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php @@ -3,10 +3,6 @@ abstract class ManiphestTaskAssignHeraldAction extends HeraldAction { - const DO_EMPTY = 'do.send'; - const DO_ALREADY = 'do.already'; - const DO_INVALID = 'do.invalid'; - const DO_PERMISSION = 'do.permission'; const DO_ASSIGN = 'do.assign'; public function supportsObject($object) { @@ -18,38 +14,21 @@ abstract class ManiphestTaskAssignHeraldAction } protected function applyAssign(array $phids) { - $phid = head($phids); - - if (!$phid) { - $this->logEffect(self::DO_EMPTY); - return; - } - $adapter = $this->getAdapter(); $object = $adapter->getObject(); - if ($object->getOwnerPHID() == $phid) { - $this->logEffect(self::DO_ALREADY, array($phid)); + $current = array($object->getOwnerPHID()); + + $allowed_types = array( + PhabricatorPeopleUserPHIDType::TYPECONST, + ); + + $targets = $this->loadStandardTargets($phids, $allowed_types, $current); + if (!$targets) { return; } - $user = id(new PhabricatorPeopleQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($phid)) - ->executeOne(); - if (!$user) { - $this->logEffect(self::DO_INVALID, array($phid)); - return; - } - - $can_view = PhabricatorPolicyFilter::hasCapability( - $user, - $object, - PhabricatorPolicyCapability::CAN_VIEW); - if (!$can_view) { - $this->logEffect(self::DO_PERMISSION, array($phid)); - return; - } + $phid = head_key($targets); $xaction = $adapter->newTransaction() ->setTransactionType(ManiphestTransaction::TYPE_OWNER) @@ -62,26 +41,6 @@ abstract class ManiphestTaskAssignHeraldAction protected function getActionEffectMap() { return array( - self::DO_EMPTY => array( - 'icon' => 'fa-ban', - 'color' => 'grey', - 'name' => pht('Empty Action'), - ), - self::DO_ALREADY => array( - 'icon' => 'fa-user', - 'color' => 'grey', - 'name' => pht('Already Assigned'), - ), - self::DO_INVALID => array( - 'icon' => 'fa-ban', - 'color' => 'red', - 'name' => pht('Invalid Owner'), - ), - self::DO_PERMISSION => array( - 'icon' => 'fa-ban', - 'color' => 'red', - 'name' => pht('No Permission'), - ), self::DO_ASSIGN => array( 'icon' => 'fa-user', 'color' => 'green', @@ -92,20 +51,6 @@ abstract class ManiphestTaskAssignHeraldAction protected function renderActionEffectDescription($type, $data) { switch ($type) { - case self::DO_EMPTY: - return pht('Action lists no user to assign.'); - case self::DO_ALREADY: - return pht( - 'User is already task owner: %s.', - $this->renderHandleList($data)); - case self::DO_INVALID: - return pht( - 'User is invalid: %s.', - $this->renderHandleList($data)); - case self::DO_PERMISSION: - return pht( - 'User does not have permission to see task: %s.', - $this->renderHandleList($data)); case self::DO_ASSIGN: return pht( 'Assigned task to: %s.', diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php index 60e72ce26a..835bde8d4d 100644 --- a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php @@ -24,6 +24,22 @@ abstract class PhabricatorMetaMTAEmailHeraldAction protected function applyEmail(array $phids, $force) { $adapter = $this->getAdapter(); + $allowed_types = array( + PhabricatorPeopleUserPHIDType::TYPECONST, + PhabricatorProjectProjectPHIDType::TYPECONST, + ); + + // There's no stateful behavior for this action: we always just send an + // email. + $current = array(); + + $targets = $this->loadStandardTargets($phids, $allowed_types, $current); + if (!$targets) { + return; + } + + $phids = array_fuse(array_keys($targets)); + foreach ($phids as $phid) { $adapter->addEmailPHID($phid, $force); } diff --git a/src/applications/project/herald/PhabricatorProjectHeraldAction.php b/src/applications/project/herald/PhabricatorProjectHeraldAction.php index 2d8c2c9b48..a720ebe5b0 100644 --- a/src/applications/project/herald/PhabricatorProjectHeraldAction.php +++ b/src/applications/project/herald/PhabricatorProjectHeraldAction.php @@ -3,10 +3,6 @@ abstract class PhabricatorProjectHeraldAction extends HeraldAction { - const DO_NO_TARGETS = 'do.no-targets'; - const DO_INVALID = 'do.invalid'; - const DO_ALREADY_ASSOCIATED = 'do.already-associated'; - const DO_ALREADY_UNASSOCIATED = 'do.already-unassociated'; const DO_ADD_PROJECTS = 'do.add-projects'; const DO_REMOVE_PROJECTS = 'do.remove-projects'; @@ -23,38 +19,24 @@ abstract class PhabricatorProjectHeraldAction } protected function applyProjects(array $phids, $is_add) { - $phids = array_fuse($phids); $adapter = $this->getAdapter(); - if (!$phids) { - $this->logEffect(self::DO_NO_TARGETS); + $allowed_types = array( + PhabricatorProjectProjectPHIDType::TYPECONST, + ); + + // Detection of "No Effect" is a bit tricky for this action, so just do it + // manually a little later on. + $current = array(); + + $targets = $this->loadStandardTargets($phids, $allowed_types, $current); + if (!$targets) { return; } - $projects = id(new PhabricatorProjectQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($phids) - ->execute(); - $projects = mpull($projects, null, 'getPHID'); - - $invalid = array(); - foreach ($phids as $phid) { - if (empty($projects[$phid])) { - $invalid[] = $phid; - unset($phids[$phid]); - } - } - - if ($invalid) { - $this->logEffect(self::DO_INVALID, $invalid); - } - - if (!$phids) { - return; - } + $phids = array_fuse(array_keys($targets)); $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $current = $adapter->loadEdgePHIDs($project_type); if ($is_add) { @@ -67,7 +49,7 @@ abstract class PhabricatorProjectHeraldAction } if ($already) { - $this->logEffect(self::DO_ALREADY_ASSOCIATED, $already); + $this->logEffect(self::DO_STANDARD_NO_EFFECT, $already); } } else { $already = array(); @@ -79,7 +61,7 @@ abstract class PhabricatorProjectHeraldAction } if ($already) { - $this->logEffect(self::DO_ALREADY_UNASSOCIATED, $already); + $this->logEffect(self::DO_STANDARD_NO_EFFECT, $already); } } @@ -112,26 +94,6 @@ abstract class PhabricatorProjectHeraldAction protected function getActionEffectMap() { return array( - self::DO_NO_TARGETS => array( - 'icon' => 'fa-ban', - 'color' => 'grey', - 'name' => pht('No Targets'), - ), - self::DO_INVALID => array( - 'icon' => 'fa-ban', - 'color' => 'red', - 'name' => pht('Invalid Targets'), - ), - self::DO_ALREADY_ASSOCIATED => array( - 'icon' => 'fa-chevron-right', - 'color' => 'grey', - 'name' => pht('Already Associated'), - ), - self::DO_ALREADY_UNASSOCIATED => array( - 'icon' => 'fa-chevron-right', - 'color' => 'grey', - 'name' => pht('Already Unassociated'), - ), self::DO_ADD_PROJECTS => array( 'icon' => 'fa-briefcase', 'color' => 'green', @@ -147,23 +109,6 @@ abstract class PhabricatorProjectHeraldAction protected function renderActionEffectDescription($type, $data) { switch ($type) { - case self::DO_NO_TARGETS: - return pht('Rule lists no projects.'); - case self::DO_INVALID: - return pht( - 'Declined to act on %s invalid project(s): %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); - case self::DO_ALREADY_ASSOCIATED: - return pht( - '%s project(s) are already associated: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); - case self::DO_ALREADY_UNASSOCIATED: - return pht( - '%s project(s) are not associated: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); case self::DO_ADD_PROJECTS: return pht( 'Added %s project(s): %s.', diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php index 5ac859a527..6ec7b6f776 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php @@ -3,12 +3,8 @@ abstract class PhabricatorSubscriptionsHeraldAction extends HeraldAction { - const DO_NO_TARGETS = 'do.no-targets'; const DO_PREVIOUSLY_UNSUBSCRIBED = 'do.previously-unsubscribed'; - const DO_INVALID = 'do.invalid'; const DO_AUTOSUBSCRIBED = 'do.autosubscribed'; - const DO_ALREADY_SUBSCRIBED = 'do.already-subscribed'; - const DO_ALREADY_UNSUBSCRIBED = 'do.already-unsubscribed'; const DO_SUBSCRIBED = 'do.subscribed'; const DO_UNSUBSCRIBED = 'do.unsubscribed'; @@ -23,18 +19,22 @@ abstract class PhabricatorSubscriptionsHeraldAction protected function applySubscribe(array $phids, $is_add) { $adapter = $this->getAdapter(); - if ($is_add) { - $kind = '+'; - } else { - $kind = '-'; - } + $allowed_types = array( + PhabricatorPeopleUserPHIDType::TYPECONST, + PhabricatorProjectProjectPHIDType::TYPECONST, + ); - $subscriber_phids = array_fuse($phids); - if (!$subscriber_phids) { - $this->logEffect(self::DO_NO_TARGETS); + // Evaluating "No Effect" is a bit tricky for this rule type, so just + // do it manually below. + $current = array(); + + $targets = $this->loadStandardTargets($phids, $allowed_types, $current); + if (!$targets) { return; } + $phids = array_fuse(array_keys($targets)); + // The "Add Subscribers" rule only adds subscribers who haven't previously // unsubscribed from the object explicitly. Filter these subscribers out // before continuing. @@ -43,9 +43,9 @@ abstract class PhabricatorSubscriptionsHeraldAction PhabricatorObjectHasUnsubscriberEdgeType::EDGECONST); foreach ($unsubscribed as $phid) { - if (isset($subscriber_phids[$phid])) { + if (isset($phids[$phid])) { $unsubscribed[$phid] = $phid; - unset($subscriber_phids[$phid]); + unset($phids[$phid]); } } @@ -56,41 +56,16 @@ abstract class PhabricatorSubscriptionsHeraldAction } } - if (!$subscriber_phids) { - return; - } - - // Filter out PHIDs which aren't valid subscribers. Lower levels of the - // stack will fail loudly if we try to add subscribers with invalid PHIDs - // or unknown PHID types, so drop them here. - $invalid = array(); - foreach ($subscriber_phids as $phid) { - $type = phid_get_type($phid); - switch ($type) { - case PhabricatorPeopleUserPHIDType::TYPECONST: - case PhabricatorProjectProjectPHIDType::TYPECONST: - break; - default: - $invalid[$phid] = $phid; - unset($subscriber_phids[$phid]); - break; - } - } - - if ($invalid) { - $this->logEffect(self::DO_INVALID, array_values($invalid)); - } - - if (!$subscriber_phids) { + if (!$phids) { return; } $auto = array(); $object = $adapter->getObject(); - foreach ($subscriber_phids as $phid) { + foreach ($phids as $phid) { if ($object->isAutomaticallySubscribed($phid)) { $auto[$phid] = $phid; - unset($subscriber_phids[$phid]); + unset($phids[$phid]); } } @@ -98,7 +73,7 @@ abstract class PhabricatorSubscriptionsHeraldAction $this->logEffect(self::DO_AUTOSUBSCRIBED, array_values($auto)); } - if (!$subscriber_phids) { + if (!$phids) { return; } @@ -107,57 +82,58 @@ abstract class PhabricatorSubscriptionsHeraldAction if ($is_add) { $already = array(); - foreach ($subscriber_phids as $phid) { + foreach ($phids as $phid) { if (isset($current[$phid])) { $already[$phid] = $phid; - unset($subscriber_phids[$phid]); + unset($phids[$phid]); } } if ($already) { - $this->logEffect(self::DO_ALREADY_SUBSCRIBED, $already); + $this->logEffect(self::DO_STANDARD_NO_EFFECT, $already); } } else { $already = array(); - foreach ($subscriber_phids as $phid) { + foreach ($phids as $phid) { if (empty($current[$phid])) { $already[$phid] = $phid; - unset($subscriber_phids[$phid]); + unset($phids[$phid]); } } if ($already) { - $this->logEffect(self::DO_ALREADY_UNSUBSCRIBED, $already); + $this->logEffect(self::DO_STANDARD_NO_EFFECT, $already); } } - if (!$subscriber_phids) { + if (!$phids) { return; } + if ($is_add) { + $kind = '+'; + } else { + $kind = '-'; + } + $xaction = $adapter->newTransaction() ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue( array( - $kind => $subscriber_phids, + $kind => $phids, )); $adapter->queueTransaction($xaction); if ($is_add) { - $this->logEffect(self::DO_SUBSCRIBED, $subscriber_phids); + $this->logEffect(self::DO_SUBSCRIBED, $phids); } else { - $this->logEffect(self::DO_UNSUBSCRIBED, $subscriber_phids); + $this->logEffect(self::DO_UNSUBSCRIBED, $phids); } } protected function getActionEffectMap() { return array( - self::DO_NO_TARGETS => array( - 'icon' => 'fa-ban', - 'color' => 'grey', - 'name' => pht('No Targets'), - ), self::DO_PREVIOUSLY_UNSUBSCRIBED => array( 'icon' => 'fa-minus-circle', 'color' => 'grey', @@ -168,21 +144,6 @@ abstract class PhabricatorSubscriptionsHeraldAction 'color' => 'grey', 'name' => pht('Automatically Subscribed'), ), - self::DO_INVALID => array( - 'icon' => 'fa-ban', - 'color' => 'red', - 'name' => pht('Invalid Targets'), - ), - self::DO_ALREADY_SUBSCRIBED => array( - 'icon' => 'fa-chevron-right', - 'color' => 'grey', - 'name' => pht('Already Subscribed'), - ), - self::DO_ALREADY_UNSUBSCRIBED => array( - 'icon' => 'fa-chevron-right', - 'color' => 'grey', - 'name' => pht('Already Unsubscribed'), - ), self::DO_SUBSCRIBED => array( 'icon' => 'fa-envelope', 'color' => 'green', @@ -198,34 +159,17 @@ abstract class PhabricatorSubscriptionsHeraldAction protected function renderActionEffectDescription($type, $data) { switch ($type) { - case self::DO_NO_TARGETS: - return pht('Rule lists no targets.'); case self::DO_PREVIOUSLY_UNSUBSCRIBED: return pht( 'Declined to resubscribe %s target(s) because they previously '. 'unsubscribed: %s.', new PhutilNumber(count($data)), $this->renderHandleList($data)); - case self::DO_INVALID: - return pht( - 'Declined to act on %s invalid target(s): %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); case self::DO_AUTOSUBSCRIBED: return pht( '%s automatically subscribed target(s) were not affected: %s.', new PhutilNumber(count($data)), $this->renderHandleList($data)); - case self::DO_ALREADY_SUBSCRIBED: - return pht( - '%s target(s) are already subscribed: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); - case self::DO_ALREADY_UNSUBSCRIBED: - return pht( - '%s target(s) are not subscribed: %s.', - new PhutilNumber(count($data)), - $this->renderHandleList($data)); case self::DO_SUBSCRIBED: return pht( 'Added %s subscriber(s): %s.', diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 033fedf805..45782758f8 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1706,7 +1706,7 @@ abstract class PhabricatorApplicationTransactionEditor $lists = array($new_set, $new_add, $new_rem); foreach ($lists as $list) { - $this->checkEdgeList($list); + $this->checkEdgeList($list, $xaction->getMetadataValue('edge:type')); } $result = array(); @@ -1743,7 +1743,7 @@ abstract class PhabricatorApplicationTransactionEditor return $result; } - private function checkEdgeList($list) { + private function checkEdgeList($list, $edge_type) { if (!$list) { return; } @@ -1751,16 +1751,18 @@ abstract class PhabricatorApplicationTransactionEditor if (phid_get_type($key) === PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { throw new Exception( pht( - "Edge transactions must have destination PHIDs as in edge ". - "lists (found key '%s').", - $key)); + 'Edge transactions must have destination PHIDs as in edge '. + 'lists (found key "%s" on transaction of type "%s").', + $key, + $edge_type)); } if (!is_array($item) && $item !== $key) { throw new Exception( pht( - "Edge transactions must have PHIDs or edge specs as values ". - "(found value '%s').", - $item)); + 'Edge transactions must have PHIDs or edge specs as values '. + '(found value "%s" on transaction of type "%s").', + $item, + $edge_type)); } } } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 108319139d..8566710cda 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1348,6 +1348,16 @@ final class PhabricatorUSEnglishTranslation 'Added auditors: %2$s.', ), + '%s target(s) do not have permission to see this object: %s.' => array( + 'A target does not have permission to see this object: %2$s.', + 'Targets do not have permission to see this object: %2$s.', + ), + + 'This action has no effect on %s target(s): %s.' => array( + 'This action has no effect on a target: %2$s.', + 'This action has no effect on targets: %2$s.', + ), + ); } From 4139305f73f074aa2ea703942b912ae393314f1d Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 4 Aug 2015 08:26:17 +1000 Subject: [PATCH 070/102] Mark `renderActionDescription` as abstract Summary: I got caught by this... my custom Herald action doesn't implement this method and now I am hitting this exception: ``` Call to undefined method HeraldHipChatNotificationAction::renderActionDescription() /usr/src/phabricator/src/applications/herald/adapter/HeraldAdapter.php:896 ``` Test Plan: N/A Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13779 --- src/applications/herald/action/HeraldAction.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index d7d764c99d..bc47fee88b 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -23,6 +23,8 @@ abstract class HeraldAction extends Phobject { abstract public function supportsRuleType($rule_type); abstract public function applyEffect($object, HeraldEffect $effect); + abstract public function renderActionDescription($value); + protected function renderActionEffectDescription($type, $data) { return null; } From 05c571794b5d0eb8a56c8632290e128552beaa2b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Aug 2015 15:49:10 -0700 Subject: [PATCH 071/102] Put spaces back in condition rendering Summary: Fixes T9055. I mostly tested with "Always" rules so I missed this. Test Plan: {F687280} Reviewers: chad, joshuaspence Reviewed By: joshuaspence Maniphest Tasks: T9055 Differential Revision: https://secure.phabricator.com/D13780 --- src/applications/herald/adapter/HeraldAdapter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 5d5e2a55f5..b46e9430c1 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -878,7 +878,9 @@ abstract class HeraldAdapter extends Phobject { return array( $field_name, + ' ', $condition_name, + ' ', $value, ); } From 46bd6b30f8fd55b4ef964dfd0663eaba1e84357b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Aug 2015 17:05:21 -0700 Subject: [PATCH 072/102] Fix undefined ACTION_BLOCK class constant in Herald Summary: Fixes T9054. This didn't get fully cleaned up. Test Plan: Edited several rules, saw actions faithfully represented. Reviewers: joshuaspence, chad Reviewed By: chad Maniphest Tasks: T9054 Differential Revision: https://secure.phabricator.com/D13781 --- .../herald/action/HeraldAction.php | 17 +++++++++++++ .../herald/adapter/HeraldAdapter.php | 14 +++++++++-- .../controller/HeraldRuleController.php | 24 ++++--------------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index bc47fee88b..02a9dfa60a 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -86,6 +86,23 @@ abstract class HeraldAction extends Phobject { return $value; } + public function getEditorValue(PhabricatorUser $viewer, $target) { + try { + $type = $this->getHeraldActionStandardType(); + } catch (PhutilMethodNotImplementedException $ex) { + return $target; + } + + switch ($type) { + case self::STANDARD_PHID_LIST: + $handles = $viewer->loadHandles($target); + $handles = iterator_to_array($handles); + return mpull($handles, 'getName', 'getPHID'); + } + + return $target; + } + final public function setAdapter(HeraldAdapter $adapter) { $this->adapter = $adapter; return $this; diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index b46e9430c1..bec6677230 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -770,8 +770,7 @@ abstract class HeraldAdapter extends Phobject { public function getEditorValueForCondition( PhabricatorUser $viewer, - HeraldCondition $condition, - array $handles) { + HeraldCondition $condition) { $field = $this->requireFieldImplementation($condition->getFieldName()); @@ -781,6 +780,17 @@ abstract class HeraldAdapter extends Phobject { $condition->getValue()); } + public function getEditorValueForAction( + PhabricatorUser $viewer, + HeraldActionRecord $action_record) { + + $action = $this->requireActionImplementation($action_record->getAction()); + + return $action->getEditorValue( + $viewer, + $action_record->getTarget()); + } + public function renderRuleAsText( HeraldRule $rule, PhabricatorHandleList $handles, diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index cd35307723..ce7a46cd00 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -348,8 +348,7 @@ final class HeraldRuleController extends HeraldController { foreach ($rule->getConditions() as $condition) { $value = $adapter->getEditorValueForCondition( $this->getViewer(), - $condition, - $handles); + $condition); $serial_conditions[] = array( $condition->getFieldName(), @@ -366,26 +365,13 @@ final class HeraldRuleController extends HeraldController { if ($rule->getActions()) { $serial_actions = array(); foreach ($rule->getActions() as $action) { - switch ($action->getAction()) { - case HeraldAdapter::ACTION_BLOCK: - $current_value = $action->getTarget(); - break; - default: - if (is_array($action->getTarget())) { - $target_map = array(); - foreach ((array)$action->getTarget() as $fbid) { - $target_map[$fbid] = $handles[$fbid]->getName(); - } - $current_value = $target_map; - } else { - $current_value = $action->getTarget(); - } - break; - } + $value = $adapter->getEditorValueForAction( + $this->getViewer(), + $action); $serial_actions[] = array( $action->getAction(), - $current_value, + $value, ); } } From 386d2b62f819fd0cb6e2373417b85688d5ea8a1e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 4 Aug 2015 07:32:19 -0700 Subject: [PATCH 073/102] Fix some copy/paste slop with BLOCK actions in Herald transcripts Summary: Fixes T9060. These actions still work fine, but the transcripts got messed up a bit. Test Plan: Viewed transcripts with blocking actions. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9060 Differential Revision: https://secure.phabricator.com/D13782 --- .../differential/herald/DifferentialBlockHeraldAction.php | 2 +- .../diffusion/herald/DiffusionBlockHeraldAction.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/herald/DifferentialBlockHeraldAction.php b/src/applications/differential/herald/DifferentialBlockHeraldAction.php index b43e415b13..33ad4a3480 100644 --- a/src/applications/differential/herald/DifferentialBlockHeraldAction.php +++ b/src/applications/differential/herald/DifferentialBlockHeraldAction.php @@ -49,7 +49,7 @@ final class DifferentialBlockHeraldAction protected function renderActionEffectDescription($type, $data) { switch ($type) { - case self::DO_ADD_AUDITORS: + case self::DO_BLOCK: return pht('Blocked diff.'); } } diff --git a/src/applications/diffusion/herald/DiffusionBlockHeraldAction.php b/src/applications/diffusion/herald/DiffusionBlockHeraldAction.php index 4b56500b65..32656d78fe 100644 --- a/src/applications/diffusion/herald/DiffusionBlockHeraldAction.php +++ b/src/applications/diffusion/herald/DiffusionBlockHeraldAction.php @@ -49,7 +49,7 @@ final class DiffusionBlockHeraldAction protected function renderActionEffectDescription($type, $data) { switch ($type) { - case self::DO_ADD_AUDITORS: + case self::DO_BLOCK: return pht('Blocked push.'); } } From 1b744d9cb646ff491ef268b4bf2d600893446de3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Aug 2015 09:31:29 -0700 Subject: [PATCH 074/102] Remove device=false from Daemons application Summary: Fixes T9064, marks page as mobile ready. Test Plan: Shrink Page Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9064 Differential Revision: https://secure.phabricator.com/D13783 --- .../daemon/controller/PhabricatorDaemonConsoleController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php index beaabae074..9f54726bc9 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php @@ -222,7 +222,6 @@ final class PhabricatorDaemonConsoleController $nav, array( 'title' => pht('Console'), - 'device' => false, )); } From ba2cc4f2eecf03bf4362c5844da3a5029e376522 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Aug 2015 09:32:21 -0700 Subject: [PATCH 075/102] Update Settings for handleRequest Summary: Run through the Settings controllers Test Plan: Test various settings pages, save some settings. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13775 --- .../PhabricatorSettingsAdjustController.php | 3 +-- .../PhabricatorSettingsMainController.php | 22 +++++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/applications/settings/controller/PhabricatorSettingsAdjustController.php b/src/applications/settings/controller/PhabricatorSettingsAdjustController.php index 2f6b2bcab1..7211431f16 100644 --- a/src/applications/settings/controller/PhabricatorSettingsAdjustController.php +++ b/src/applications/settings/controller/PhabricatorSettingsAdjustController.php @@ -3,8 +3,7 @@ final class PhabricatorSettingsAdjustController extends PhabricatorController { - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { $user = $request->getUser(); $prefs = $user->loadPreferences(); diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php index 80753b17ca..7a2799935c 100644 --- a/src/applications/settings/controller/PhabricatorSettingsMainController.php +++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php @@ -3,8 +3,6 @@ final class PhabricatorSettingsMainController extends PhabricatorController { - private $id; - private $key; private $user; private function getUser() { @@ -12,24 +10,20 @@ final class PhabricatorSettingsMainController } private function isSelf() { - $viewer_phid = $this->getRequest()->getUser()->getPHID(); + $viewer_phid = $this->getViewer()->getPHID(); $user_phid = $this->getUser()->getPHID(); return ($viewer_phid == $user_phid); } - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - $this->key = idx($data, 'key'); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); + $key = $request->getURIData('key'); - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if ($this->id) { + if ($id) { $user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -49,7 +43,7 @@ final class PhabricatorSettingsMainController $panels = $this->buildPanels(); $nav = $this->renderSideNav($panels); - $key = $nav->selectFilter($this->key, head($panels)->getPanelKey()); + $key = $nav->selectFilter($key, head($panels)->getPanelKey()); $panel = $panels[$key]; $panel->setUser($this->getUser()); From f5be68c59c39ac83cb8cf03989efa899ab9feb77 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Aug 2015 09:32:56 -0700 Subject: [PATCH 076/102] Update Notifications for handleRequest Summary: Updates Notifcations for handleRequest Test Plan: Test as much as I could, mark all, view, lists. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8628 Differential Revision: https://secure.phabricator.com/D13767 --- .../PhabricatorNotificationClearController.php | 9 ++++----- ...PhabricatorNotificationIndividualController.php | 5 ++--- .../PhabricatorNotificationListController.php | 14 +++++--------- .../PhabricatorNotificationPanelController.php | 12 +++++------- .../PhabricatorNotificationStatusController.php | 3 ++- .../PhabricatorNotificationTestController.php | 5 ++--- 6 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/applications/notification/controller/PhabricatorNotificationClearController.php b/src/applications/notification/controller/PhabricatorNotificationClearController.php index ba781234e7..0b6027d379 100644 --- a/src/applications/notification/controller/PhabricatorNotificationClearController.php +++ b/src/applications/notification/controller/PhabricatorNotificationClearController.php @@ -3,10 +3,9 @@ final class PhabricatorNotificationClearController extends PhabricatorNotificationController { - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $chrono_key = $request->getStr('chronoKey'); - $user = $request->getUser(); if ($request->isDialogFormPost()) { $table = new PhabricatorFeedStoryNotification(); @@ -16,7 +15,7 @@ final class PhabricatorNotificationClearController 'UPDATE %T SET hasViewed = 1 '. 'WHERE userPHID = %s AND hasViewed = 0 and chronologicalKey <= %s', $table->getTableName(), - $user->getPHID(), + $viewer->getPHID(), $chrono_key); return id(new AphrontReloadResponse()) @@ -24,7 +23,7 @@ final class PhabricatorNotificationClearController } $dialog = new AphrontDialogView(); - $dialog->setUser($user); + $dialog->setUser($viewer); $dialog->addCancelButton('/notification/'); if ($chrono_key) { $dialog->setTitle(pht('Really mark all notifications as read?')); diff --git a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php index 6e6f7361df..68214d1965 100644 --- a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php +++ b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php @@ -3,9 +3,8 @@ final class PhabricatorNotificationIndividualController extends PhabricatorNotificationController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $stories = id(new PhabricatorNotificationQuery()) ->setViewer($viewer) diff --git a/src/applications/notification/controller/PhabricatorNotificationListController.php b/src/applications/notification/controller/PhabricatorNotificationListController.php index b7ab15df90..718c2431a1 100644 --- a/src/applications/notification/controller/PhabricatorNotificationListController.php +++ b/src/applications/notification/controller/PhabricatorNotificationListController.php @@ -3,15 +3,11 @@ final class PhabricatorNotificationListController extends PhabricatorNotificationController { - private $queryKey; + public function handleRequest(AphrontRequest $request) { + $querykey = $request->getURIData('queryKey'); - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } - - public function processRequest() { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($querykey) ->setSearchEngine(new PhabricatorNotificationSearchEngine()) ->setNavigation($this->buildSideNavView()); @@ -19,13 +15,13 @@ final class PhabricatorNotificationListController } public function buildSideNavView() { - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new PhabricatorNotificationSearchEngine()) - ->setViewer($user) + ->setViewer($viewer) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); diff --git a/src/applications/notification/controller/PhabricatorNotificationPanelController.php b/src/applications/notification/controller/PhabricatorNotificationPanelController.php index 3e8f16b07a..c16069a0e6 100644 --- a/src/applications/notification/controller/PhabricatorNotificationPanelController.php +++ b/src/applications/notification/controller/PhabricatorNotificationPanelController.php @@ -3,14 +3,12 @@ final class PhabricatorNotificationPanelController extends PhabricatorNotificationController { - public function processRequest() { - - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $query = id(new PhabricatorNotificationQuery()) - ->setViewer($user) - ->withUserPHIDs(array($user->getPHID())) + ->setViewer($viewer) + ->withUserPHIDs(array($viewer->getPHID())) ->setLimit(15); $stories = $query->execute(); @@ -81,7 +79,7 @@ final class PhabricatorNotificationPanelController $connection_ui); $unread_count = id(new PhabricatorFeedStoryNotification()) - ->countUnread($user); + ->countUnread($viewer); $json = array( 'content' => $content, diff --git a/src/applications/notification/controller/PhabricatorNotificationStatusController.php b/src/applications/notification/controller/PhabricatorNotificationStatusController.php index 2c9e308985..e889c7af5e 100644 --- a/src/applications/notification/controller/PhabricatorNotificationStatusController.php +++ b/src/applications/notification/controller/PhabricatorNotificationStatusController.php @@ -3,7 +3,8 @@ final class PhabricatorNotificationStatusController extends PhabricatorNotificationController { - public function processRequest() { + public function handleRequest(AphrontRequest $request) { + try { $status = PhabricatorNotificationClient::getServerStatus(); $status = $this->renderServerStatus($status); diff --git a/src/applications/notification/controller/PhabricatorNotificationTestController.php b/src/applications/notification/controller/PhabricatorNotificationTestController.php index 706242818c..5c76e53357 100644 --- a/src/applications/notification/controller/PhabricatorNotificationTestController.php +++ b/src/applications/notification/controller/PhabricatorNotificationTestController.php @@ -3,9 +3,8 @@ final class PhabricatorNotificationTestController extends PhabricatorNotificationController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); $story_type = 'PhabricatorNotificationTestFeedStory'; $story_data = array( From ec7a0837d56dcd7fe23211332163b642a56cead2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Aug 2015 09:33:13 -0700 Subject: [PATCH 077/102] Update XHPhast for handleRequest Summary: Updates XHPhast Test Plan: Ran a few queries Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13776 --- .../PhabricatorXHPASTViewFrameController.php | 10 ++-------- .../PhabricatorXHPASTViewFramesetController.php | 10 ++-------- .../PhabricatorXHPASTViewInputController.php | 2 +- .../controller/PhabricatorXHPASTViewRunController.php | 10 ++++------ .../PhabricatorXHPASTViewStreamController.php | 2 +- .../controller/PhabricatorXHPASTViewTreeController.php | 2 +- 6 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewFrameController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewFrameController.php index 560d4203a0..2c5a43687c 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewFrameController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewFrameController.php @@ -3,18 +3,12 @@ final class PhabricatorXHPASTViewFrameController extends PhabricatorXHPASTViewController { - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $id = $this->id; + public function handleRequest(AphrontRequest $request) { + $id = $request->getURIData('id'); return $this->buildStandardPageResponse( phutil_tag( diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php index a21bb30b68..de446b5e44 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php @@ -3,18 +3,12 @@ final class PhabricatorXHPASTViewFramesetController extends PhabricatorXHPASTViewController { - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $id = $this->id; + public function handleRequest(AphrontRequest $request) { + $id = $request->getURIData('id'); $response = new AphrontWebpageResponse(); $response->setFrameable(true); diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewInputController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewInputController.php index 48ba6afc33..6e1fd87db7 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewInputController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewInputController.php @@ -3,7 +3,7 @@ final class PhabricatorXHPASTViewInputController extends PhabricatorXHPASTViewPanelController { - public function processRequest() { + public function handleRequest(AphrontRequest $request) { $input = $this->getStorageTree()->getInput(); return $this->buildXHPASTViewPanelResponse($input); } diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php index f5a79a4b28..dd9cf85433 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php @@ -3,10 +3,8 @@ final class PhabricatorXHPASTViewRunController extends PhabricatorXHPASTViewController { - public function processRequest() { - - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); if ($request->isFormPost()) { $source = $request->getStr('source'); @@ -24,7 +22,7 @@ final class PhabricatorXHPASTViewRunController $storage_tree = new PhabricatorXHPASTViewParseTree(); $storage_tree->setInput($source); $storage_tree->setStdout($stdout); - $storage_tree->setAuthorPHID($user->getPHID()); + $storage_tree->setAuthorPHID($viewer->getPHID()); $storage_tree->save(); return id(new AphrontRedirectResponse()) @@ -32,7 +30,7 @@ final class PhabricatorXHPASTViewRunController } $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Source')) diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewStreamController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewStreamController.php index 4a098f1591..3fe1046f10 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewStreamController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewStreamController.php @@ -3,7 +3,7 @@ final class PhabricatorXHPASTViewStreamController extends PhabricatorXHPASTViewPanelController { - public function processRequest() { + public function handleRequest(AphrontRequest $request) { $storage = $this->getStorageTree(); $input = $storage->getInput(); $stdout = $storage->getStdout(); diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewTreeController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewTreeController.php index 7da285f121..1b4eec6441 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewTreeController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewTreeController.php @@ -7,7 +7,7 @@ final class PhabricatorXHPASTViewTreeController return true; } - public function processRequest() { + public function handleRequest(AphrontRequest $request) { $storage = $this->getStorageTree(); $input = $storage->getInput(); $stdout = $storage->getStdout(); From 328210a1a645de4f3981d04fe786d4f7a3c6550c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Aug 2015 10:48:48 -0700 Subject: [PATCH 078/102] Add mobile menu to Ponder Summary: Ref T3578, adds mobile menus to Ponder Test Plan: Test Ponder on mobile and desktop, click in Ask Question links Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3578 Differential Revision: https://secure.phabricator.com/D13784 --- .../PhabricatorPonderApplication.php | 32 +++++++++++++------ .../ponder/controller/PonderController.php | 15 +++++++-- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/applications/ponder/application/PhabricatorPonderApplication.php b/src/applications/ponder/application/PhabricatorPonderApplication.php index 999580e90b..953f683128 100644 --- a/src/applications/ponder/application/PhabricatorPonderApplication.php +++ b/src/applications/ponder/application/PhabricatorPonderApplication.php @@ -48,17 +48,29 @@ final class PhabricatorPonderApplication extends PhabricatorApplication { public function getRoutes() { return array( - '/Q(?P[1-9]\d*)' => 'PonderQuestionViewController', + '/Q(?P[1-9]\d*)' + => 'PonderQuestionViewController', '/ponder/' => array( - '(?:query/(?P[^/]+)/)?' => 'PonderQuestionListController', - 'answer/add/' => 'PonderAnswerSaveController', - 'answer/edit/(?P\d+)/' => 'PonderAnswerEditController', - 'answer/comment/(?P\d+)/' => 'PonderAnswerCommentController', - 'answer/history/(?P\d+)/' => 'PonderAnswerHistoryController', - 'question/edit/(?:(?P\d+)/)?' => 'PonderQuestionEditController', - 'question/comment/(?P\d+)/' => 'PonderQuestionCommentController', - 'question/history/(?P\d+)/' => 'PonderQuestionHistoryController', - 'preview/' => 'PhabricatorMarkupPreviewController', + '(?:query/(?P[^/]+)/)?' + => 'PonderQuestionListController', + 'answer/add/' + => 'PonderAnswerSaveController', + 'answer/edit/(?P\d+)/' + => 'PonderAnswerEditController', + 'answer/comment/(?P\d+)/' + => 'PonderAnswerCommentController', + 'answer/history/(?P\d+)/' + => 'PonderAnswerHistoryController', + 'question/edit/(?:(?P\d+)/)?' + => 'PonderQuestionEditController', + 'question/create/' + => 'PonderQuestionEditController', + 'question/comment/(?P\d+)/' + => 'PonderQuestionCommentController', + 'question/history/(?P\d+)/' + => 'PonderQuestionHistoryController', + 'preview/' + => 'PhabricatorMarkupPreviewController', 'question/(?Popen|close)/(?P[1-9]\d*)/' => 'PonderQuestionStatusController', 'vote/' => 'PonderVoteSaveController', diff --git a/src/applications/ponder/controller/PonderController.php b/src/applications/ponder/controller/PonderController.php index 30bd412504..a14d70c773 100644 --- a/src/applications/ponder/controller/PonderController.php +++ b/src/applications/ponder/controller/PonderController.php @@ -2,12 +2,16 @@ abstract class PonderController extends PhabricatorController { - protected function buildSideNavView() { + protected function buildSideNavView($for_app = false) { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + if ($for_app) { + $nav->addFilter('question/create/', pht('Ask Question')); + } + id(new PonderQuestionSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); @@ -17,13 +21,18 @@ abstract class PonderController extends PhabricatorController { return $nav; } + public function buildApplicationMenu() { + return $this->buildSideNavView($for_app = true)->getMenu(); + } + protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); + $href = $this->getApplicationURI('question/create/'); $crumbs ->addAction( id(new PHUIListItemView()) - ->setName(pht('Create Question')) - ->setHref('/ponder/question/edit/') + ->setName(pht('Ask Question')) + ->setHref($href) ->setIcon('fa-plus-square')); return $crumbs; From 4e7b5defc300b6af060277a35c956c5c745c00cd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Aug 2015 10:49:15 -0700 Subject: [PATCH 079/102] Add mailKeys to Ponder Answer Summary: Ref T3846. Adds mailkey generation and migration. Test Plan: Ran the migration, see keys in mysql. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3846 Differential Revision: https://secure.phabricator.com/D13785 --- .../20150804.ponder.answer.mailkey.1.sql | 2 ++ .../20150804.ponder.answer.mailkey.2.php | 18 ++++++++++++++++++ .../ponder/storage/PonderAnswer.php | 9 +++++++++ 3 files changed, 29 insertions(+) create mode 100644 resources/sql/autopatches/20150804.ponder.answer.mailkey.1.sql create mode 100644 resources/sql/autopatches/20150804.ponder.answer.mailkey.2.php diff --git a/resources/sql/autopatches/20150804.ponder.answer.mailkey.1.sql b/resources/sql/autopatches/20150804.ponder.answer.mailkey.1.sql new file mode 100644 index 0000000000..63ba3494f9 --- /dev/null +++ b/resources/sql/autopatches/20150804.ponder.answer.mailkey.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_ponder.ponder_answer + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20150804.ponder.answer.mailkey.2.php b/resources/sql/autopatches/20150804.ponder.answer.mailkey.2.php new file mode 100644 index 0000000000..e643a5dab5 --- /dev/null +++ b/resources/sql/autopatches/20150804.ponder.answer.mailkey.2.php @@ -0,0 +1,18 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $answer) { + $id = $answer->getID(); + + echo pht('Adding mail key for Answer %d...', $id); + echo "\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} diff --git a/src/applications/ponder/storage/PonderAnswer.php b/src/applications/ponder/storage/PonderAnswer.php index 57e0e2ea21..10ada70c5e 100644 --- a/src/applications/ponder/storage/PonderAnswer.php +++ b/src/applications/ponder/storage/PonderAnswer.php @@ -18,6 +18,7 @@ final class PonderAnswer extends PonderDAO protected $content; protected $contentSource; + protected $mailKey; protected $voteCount; private $vote; @@ -71,6 +72,7 @@ final class PonderAnswer extends PonderDAO self::CONFIG_COLUMN_SCHEMA => array( 'voteCount' => 'sint32', 'content' => 'text', + 'mailKey' => 'bytes20', // T6203/NULLABILITY // This should always exist. @@ -113,6 +115,13 @@ final class PonderAnswer extends PonderDAO return self::MARKUP_FIELD_CONTENT; } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ From f0e7c65d8835ed4c57bb8fb85da5196c4624835c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Aug 2015 12:45:50 -0700 Subject: [PATCH 080/102] Set a max-height on codeblocks in Conpherence Summary: Code blocks in Conpherence can be massive and disruptive to a chat-like experience. This caps the height. Test Plan: Add a ginormous code block to Conpherence, see it hit max-height Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13790 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/application/conpherence/message-pane.css | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 372eefe09f..f177c19d22 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -48,7 +48,7 @@ return array( 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '86396117', 'rsrc/css/application/conpherence/menu.css' => 'f99fee4c', - 'rsrc/css/application/conpherence/message-pane.css' => '2c16d204', + 'rsrc/css/application/conpherence/message-pane.css' => 'dd4f8a3b', 'rsrc/css/application/conpherence/notification.css' => '6cdcc253', 'rsrc/css/application/conpherence/transaction.css' => '85d0974c', 'rsrc/css/application/conpherence/update.css' => 'faf6be09', @@ -514,7 +514,7 @@ return array( 'config-welcome-css' => '6abd79be', 'conpherence-durable-column-view' => '86396117', 'conpherence-menu-css' => 'f99fee4c', - 'conpherence-message-pane-css' => '2c16d204', + 'conpherence-message-pane-css' => 'dd4f8a3b', 'conpherence-notification-css' => '6cdcc253', 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '85d0974c', diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css index 9da3de6d7f..886700ccf5 100644 --- a/webroot/rsrc/css/application/conpherence/message-pane.css +++ b/webroot/rsrc/css/application/conpherence/message-pane.css @@ -344,3 +344,7 @@ height: 24px; width: 100%; } + +.conpherence-message .phabricator-remarkup .remarkup-code-block pre { + max-height: 200px; +} From 630fb06c428e674fe04be3ff6d9cfd05fd1e1d4f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 4 Aug 2015 13:05:52 -0700 Subject: [PATCH 081/102] Document how to use `harbormaster.sendmessage` to report lint and unit results Summary: Fixes T7419. This doesn't really do anything, just adds documentation. Test Plan: - Read the documentation: {F688899} - Created a build plan which makes an HTTP request to `example.com` and waits for a result. - Ran that build plan manually. - Called `harbormaster.sendmessage` manually with the example lint/unit values to provide a result. - Saw the results report correctly and the message ("fail") process as expected: {F688902} Reviewers: chad Reviewed By: chad Maniphest Tasks: T7419 Differential Revision: https://secure.phabricator.com/D13789 --- src/__phutil_library_map__.php | 2 + .../conduit/method/ConduitAPIMethod.php | 27 ++- .../query/PhabricatorConduitSearchEngine.php | 2 +- ...arbormasterSendMessageConduitAPIMethod.php | 198 +++++++++++++++++- .../engine/HarbormasterBuildEngine.php | 9 +- .../engine/HarbormasterMessageType.php | 44 ++++ .../build/HarbormasterBuildLintMessage.php | 57 ++++- .../build/HarbormasterBuildUnitMessage.php | 56 ++++- 8 files changed, 365 insertions(+), 30 deletions(-) create mode 100644 src/applications/harbormaster/engine/HarbormasterMessageType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3927a010b0..779c48bd3c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -990,6 +990,7 @@ phutil_register_library_map(array( 'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php', 'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php', 'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php', + 'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php', 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', 'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php', @@ -4702,6 +4703,7 @@ phutil_register_library_map(array( 'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow', 'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow', 'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'HarbormasterMessageType' => 'Phobject', 'HarbormasterObject' => 'HarbormasterDAO', 'HarbormasterPlanController' => 'HarbormasterController', 'HarbormasterPlanDisableController' => 'HarbormasterPlanController', diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php index 4472f80864..39fa9b66f4 100644 --- a/src/applications/conduit/method/ConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitAPIMethod.php @@ -1,18 +1,41 @@ getMethodDescription(); + } + + + /** + * Get a detailed description of the method. + * + * This method should return remarkup. + * + * @return string Detailed description of the method. + * @task info + */ abstract public function getMethodDescription(); + abstract protected function defineParamTypes(); abstract protected function defineReturnType(); diff --git a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php index 5e12cf7f99..6c067a8ff4 100644 --- a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php +++ b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php @@ -174,7 +174,7 @@ final class PhabricatorConduitSearchEngine $item = id(new PHUIObjectItemView()) ->setHeader($method_name) ->setHref($this->getApplicationURI('method/'.$method_name.'/')) - ->addAttribute($method->getMethodDescription()); + ->addAttribute($method->getMethodSummary()); switch ($method->getMethodStatus()) { case ConduitAPIMethod::METHOD_STATUS_STABLE: diff --git a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php index 9590f90cec..3a2d808239 100644 --- a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php @@ -7,20 +7,206 @@ final class HarbormasterSendMessageConduitAPIMethod return 'harbormaster.sendmessage'; } - public function getMethodDescription() { + public function getMethodSummary() { return pht( - 'Send a message to a build target, notifying it of results in an '. - 'external system.'); + 'Send a message about the status of a build target to Harbormaster, '. + 'notifying the application of build results in an external system.'); + } + + public function getMethodDescription() { + $messages = HarbormasterMessageType::getAllMessages(); + + $head_type = pht('Constant'); + $head_desc = pht('Description'); + $head_key = pht('Key'); + $head_type = pht('Type'); + $head_name = pht('Name'); + + $rows = array(); + $rows[] = "| {$head_type} | {$head_desc} |"; + $rows[] = '|--------------|--------------|'; + foreach ($messages as $message) { + $description = HarbormasterMessageType::getMessageDescription($message); + $rows[] = "| `{$message}` | {$description} |"; + } + $message_table = implode("\n", $rows); + + $rows = array(); + $rows[] = "| {$head_key} | {$head_type} | {$head_desc} |"; + $rows[] = '|-------------|--------------|--------------|'; + $unit_spec = HarbormasterBuildUnitMessage::getParameterSpec(); + foreach ($unit_spec as $key => $parameter) { + $type = idx($parameter, 'type'); + $type = str_replace('|', pht(' or '), $type); + $description = idx($parameter, 'description'); + $rows[] = "| `{$key}` | //{$type}// | {$description} |"; + } + $unit_table = implode("\n", $rows); + + $rows = array(); + $rows[] = "| {$head_key} | {$head_name} | {$head_desc} |"; + $rows[] = '|-------------|--------------|--------------|'; + $results = ArcanistUnitTestResult::getAllResultCodes(); + foreach ($results as $result_code) { + $name = ArcanistUnitTestResult::getResultCodeName($result_code); + $description = ArcanistUnitTestResult::getResultCodeDescription( + $result_code); + $rows[] = "| `{$result_code}` | **{$name}** | {$description} |"; + } + $result_table = implode("\n", $rows); + + $rows = array(); + $rows[] = "| {$head_key} | {$head_type} | {$head_desc} |"; + $rows[] = '|-------------|--------------|--------------|'; + $lint_spec = HarbormasterBuildLintMessage::getParameterSpec(); + foreach ($lint_spec as $key => $parameter) { + $type = idx($parameter, 'type'); + $type = str_replace('|', pht(' or '), $type); + $description = idx($parameter, 'description'); + $rows[] = "| `{$key}` | //{$type}// | {$description} |"; + } + $lint_table = implode("\n", $rows); + + $rows = array(); + $rows[] = "| {$head_key} | {$head_name} |"; + $rows[] = '|-------------|--------------|'; + $severities = ArcanistLintSeverity::getLintSeverities(); + foreach ($severities as $key => $name) { + $rows[] = "| `{$key}` | **{$name}** |"; + } + $severity_table = implode("\n", $rows); + + $valid_unit = array( + array( + 'name' => 'PassingTest', + 'result' => ArcanistUnitTestResult::RESULT_PASS, + ), + array( + 'name' => 'FailingTest', + 'result' => ArcanistUnitTestResult::RESULT_FAIL, + ), + ); + + $valid_lint = array( + array( + 'name' => pht('Syntax Error'), + 'code' => 'EXAMPLE1', + 'severity' => ArcanistLintSeverity::SEVERITY_ERROR, + 'path' => 'path/to/example.c', + 'line' => 17, + 'char' => 3, + ), + array( + 'name' => pht('Not A Haiku'), + 'code' => 'EXAMPLE2', + 'severity' => ArcanistLintSeverity::SEVERITY_ERROR, + 'path' => 'path/to/source.cpp', + 'line' => 23, + 'char' => 1, + 'description' => pht( + 'This function definition is not a haiku.'), + ), + ); + + $json = new PhutilJSON(); + $valid_unit = $json->encodeAsList($valid_unit); + $valid_lint = $json->encodeAsList($valid_lint); + + return pht( + "Send a message about the status of a build target to Harbormaster, ". + "notifying the application of build results in an external system.". + "\n\n". + "Sending Messages\n". + "================\n". + "If you run external builds, you can use this method to publish build ". + "results back into Harbormaster after the external system finishes work ". + "or as it makes progress.". + "\n\n". + "The simplest way to use this method is to call it once after the ". + "build finishes with a `pass` or `fail` message. This will record the ". + "build result, and continue the next step in the build if the build was ". + "waiting for a result.". + "\n\n". + "When you send a status message about a build target, you can ". + "optionally include detailed `lint` or `unit` results alongside the ". + "message. See below for details.". + "\n\n". + "If you want to report intermediate results but a build hasn't ". + "completed yet, you can use the `work` message. This message doesn't ". + "have any direct effects, but allows you to send additional data to ". + "update the progress of the build target. The target will continue ". + "waiting for a completion message, but the UI will update to show the ". + "progress which has been made.". + "\n\n". + "Message Types\n". + "=============\n". + "When you send Harbormaster a message, you must include a `type`, ". + "which describes the overall state of the build. For example, use ". + "`pass` to tell Harbomaster that a build completed successfully.". + "\n\n". + "Supported message types are:". + "\n\n". + "%s". + "\n\n". + "Unit Results\n". + "============\n". + "You can report test results alongside a message. The simplest way to ". + "do this is to report all the results alongside a `pass` or `fail` ". + "message, but you can also send a `work` message to report intermediate ". + "results.\n\n". + "To provide unit test results, pass a list of results in the `unit` ". + "parameter. Each result shoud be a dictionary with these keys:". + "\n\n". + "%s". + "\n\n". + "The `result` parameter recognizes these test results:". + "\n\n". + "%s". + "\n\n". + "This is a simple, valid value for the `unit` parameter. It reports ". + "one passing test and one failing test:\n\n". + "\n\n". + "```lang=json\n". + "%s". + "```". + "\n\n". + "Lint Results\n". + "============\n". + "Like unit test results, you can report lint results alongside a ". + "message. The `lint` parameter should contain results as a list of ". + "dictionaries with these keys:". + "\n\n". + "%s". + "\n\n". + "The `severity` parameter recognizes these severity levels:". + "\n\n". + "%s". + "\n\n". + "This is a simple, valid value for the `lint` parameter. It reports one ". + "error and one warning:". + "\n\n". + "```lang=json\n". + "%s". + "```". + "\n\n", + $message_table, + $unit_table, + $result_table, + $valid_unit, + $lint_table, + $severity_table, + $valid_lint); } protected function defineParamTypes() { - $type_const = $this->formatStringConstants(array('pass', 'fail')); + $messages = HarbormasterMessageType::getAllMessages(); + $type_const = $this->formatStringConstants($messages); return array( 'buildTargetPHID' => 'required phid', - 'lint' => 'optional list', - 'unit' => 'optional list', 'type' => 'required '.$type_const, + 'unit' => 'optional list', + 'lint' => 'optional list', ); } diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index 8d8c26818e..fd895979cd 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -317,14 +317,17 @@ final class HarbormasterBuildEngine extends Phobject { foreach ($messages as $message) { $target = $waiting_targets[$message->getBuildTargetPHID()]; - $new_status = null; switch ($message->getType()) { - case 'pass': + case HarbormasterMessageType::MESSAGE_PASS: $new_status = HarbormasterBuildTarget::STATUS_PASSED; break; - case 'fail': + case HarbormasterMessageType::MESSAGE_FAIL: $new_status = HarbormasterBuildTarget::STATUS_FAILED; break; + case HarbormasterMessageType::MESSAGE_WORK: + default: + $new_status = null; + break; } if ($new_status !== null) { diff --git a/src/applications/harbormaster/engine/HarbormasterMessageType.php b/src/applications/harbormaster/engine/HarbormasterMessageType.php new file mode 100644 index 0000000000..5900817c69 --- /dev/null +++ b/src/applications/harbormaster/engine/HarbormasterMessageType.php @@ -0,0 +1,44 @@ + array( + 'description' => pht( + 'Report that the target is complete, and the target has passed.'), + ), + self::MESSAGE_FAIL => array( + 'description' => pht( + 'Report that the target is complete, and the target has failed.'), + ), + self::MESSAGE_WORK => array( + 'description' => pht( + 'Report that work on the target is ongoing. This message can be '. + 'used to report partial results during a build.'), + ), + ); + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php index c9957b0149..2bd37b2f79 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php @@ -20,21 +20,60 @@ final class HarbormasterBuildLintMessage ->setBuildTargetPHID($build_target->getPHID()); } + public static function getParameterSpec() { + return array( + 'name' => array( + 'type' => 'string', + 'description' => pht( + 'Short message name, like "Syntax Error".'), + ), + 'code' => array( + 'type' => 'string', + 'description' => pht( + 'Lint message code identifying the type of message, like "ERR123".'), + ), + 'severity' => array( + 'type' => 'string', + 'description' => pht( + 'Severity of the message.'), + ), + 'path' => array( + 'type' => 'string', + 'description' => pht( + 'Path to the file containing the lint message, from the project '. + 'root.'), + ), + 'line' => array( + 'type' => 'optional int', + 'description' => pht( + 'Line number in the file where the text which triggered the '. + 'message first appears. The first line of the file is line 1, '. + 'not line 0.'), + ), + 'char' => array( + 'type' => 'optional int', + 'description' => pht( + 'Byte position on the line where the text which triggered the '. + 'message starts. The first byte on the line is byte 1, not byte '. + '0. This position is byte-based (not character-based) because '. + 'not all lintable files have a valid character encoding.'), + ), + 'description' => array( + 'type' => 'optional string', + 'description' => pht( + 'Long explanation of the lint message.'), + ), + ); + } + public static function newFromDictionary( HarbormasterBuildTarget $build_target, array $dict) { $obj = self::initializeNewLintMessage($build_target); - $spec = array( - 'path' => 'string', - 'line' => 'optional int', - 'char' => 'optional int', - 'code' => 'string', - 'severity' => 'string', - 'name' => 'string', - 'description' => 'optional string', - ); + $spec = self::getParameterSpec(); + $spec = ipull($spec, 'type'); // We're just going to ignore extra keys for now, to make it easier to // add stuff here later on. diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php index 2b9108b035..1062b64a3a 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php @@ -19,21 +19,59 @@ final class HarbormasterBuildUnitMessage ->setBuildTargetPHID($build_target->getPHID()); } + public static function getParameterSpec() { + return array( + 'name' => array( + 'type' => 'string', + 'description' => pht( + 'Short test name, like "ExampleTest".'), + ), + 'result' => array( + 'type' => 'string', + 'description' => pht( + 'Result of the test.'), + ), + 'namespace' => array( + 'type' => 'optional string', + 'description' => pht( + 'Optional namespace for this test. This is organizational and '. + 'is often a class or module name, like "ExampleTestCase".'), + ), + 'engine' => array( + 'type' => 'optional string', + 'description' => pht( + 'Test engine running the test, like "JavascriptTestEngine". This '. + 'primarily prevents collisions between tests with the same name '. + 'in different test suites (for example, a Javascript test and a '. + 'Python test).'), + ), + 'duration' => array( + 'type' => 'optional float|int', + 'description' => pht( + 'Runtime duration of the test, in seconds.'), + ), + 'path' => array( + 'type' => 'optional string', + 'description' => pht( + 'Path to the file where the test is declared, relative to the '. + 'project root.'), + ), + 'coverage' => array( + 'type' => 'optional map', + 'description' => pht( + 'Coverage information for this test.'), + ), + ); + } + public static function newFromDictionary( HarbormasterBuildTarget $build_target, array $dict) { $obj = self::initializeNewUnitMessage($build_target); - $spec = array( - 'engine' => 'optional string', - 'namespace' => 'optional string', - 'name' => 'string', - 'result' => 'string', - 'duration' => 'optional float|int', - 'path' => 'optional string', - 'coverage' => 'optional map', - ); + $spec = self::getParameterSpec(); + $spec = ipull($spec, 'type'); // We're just going to ignore extra keys for now, to make it easier to // add stuff here later on. From 135d0c9ee7a6d0552ba2f16bd36aef9b574de16c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Aug 2015 15:41:09 -0700 Subject: [PATCH 082/102] Add Edit/View Policy to Ponder Questions Summary: Ref T3578, adds ability to set a default edit and view policy for questions. Not sure what to set viewPolicy to ? Test Plan: Test an old question, edit policy still on myself. Test a new question, see new default. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3578 Differential Revision: https://secure.phabricator.com/D13791 --- .../20150804.ponder.question.1.sql | 5 ++ .../20150804.ponder.question.2.sql | 2 + .../20150804.ponder.question.3.sql | 2 + src/__phutil_library_map__.php | 4 ++ .../PhabricatorPonderApplication.php | 13 +++++ .../PonderQuestionDefaultEditCapability.php | 12 +++++ .../PonderQuestionDefaultViewCapability.php | 16 ++++++ .../PonderQuestionEditController.php | 50 ++++++++++++++----- .../ponder/editor/PonderQuestionEditor.php | 3 ++ .../ponder/storage/PonderQuestion.php | 32 +++++++++--- 10 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 resources/sql/autopatches/20150804.ponder.question.1.sql create mode 100644 resources/sql/autopatches/20150804.ponder.question.2.sql create mode 100644 resources/sql/autopatches/20150804.ponder.question.3.sql create mode 100644 src/applications/ponder/capability/PonderQuestionDefaultEditCapability.php create mode 100644 src/applications/ponder/capability/PonderQuestionDefaultViewCapability.php diff --git a/resources/sql/autopatches/20150804.ponder.question.1.sql b/resources/sql/autopatches/20150804.ponder.question.1.sql new file mode 100644 index 0000000000..8ea3b2c34c --- /dev/null +++ b/resources/sql/autopatches/20150804.ponder.question.1.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_ponder.ponder_question + ADD editPolicy VARBINARY(64) NOT NULL; + +ALTER TABLE {$NAMESPACE}_ponder.ponder_question + ADD viewPolicy VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20150804.ponder.question.2.sql b/resources/sql/autopatches/20150804.ponder.question.2.sql new file mode 100644 index 0000000000..ddca10f753 --- /dev/null +++ b/resources/sql/autopatches/20150804.ponder.question.2.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_ponder.ponder_question + SET editPolicy = authorPHID WHERE editPolicy = ''; diff --git a/resources/sql/autopatches/20150804.ponder.question.3.sql b/resources/sql/autopatches/20150804.ponder.question.3.sql new file mode 100644 index 0000000000..0a359453ea --- /dev/null +++ b/resources/sql/autopatches/20150804.ponder.question.3.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_ponder.ponder_question + SET viewPolicy = 'users' WHERE viewPolicy = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 779c48bd3c..9636e776db 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3399,6 +3399,8 @@ phutil_register_library_map(array( 'PonderEditor' => 'applications/ponder/editor/PonderEditor.php', 'PonderQuestion' => 'applications/ponder/storage/PonderQuestion.php', 'PonderQuestionCommentController' => 'applications/ponder/controller/PonderQuestionCommentController.php', + 'PonderQuestionDefaultEditCapability' => 'applications/ponder/capability/PonderQuestionDefaultEditCapability.php', + 'PonderQuestionDefaultViewCapability' => 'applications/ponder/capability/PonderQuestionDefaultViewCapability.php', 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', 'PonderQuestionHasVotingUserEdgeType' => 'applications/ponder/edge/PonderQuestionHasVotingUserEdgeType.php', @@ -7590,6 +7592,8 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', ), 'PonderQuestionCommentController' => 'PonderController', + 'PonderQuestionDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'PonderQuestionDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PonderQuestionEditController' => 'PonderController', 'PonderQuestionEditor' => 'PonderEditor', 'PonderQuestionHasVotingUserEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/ponder/application/PhabricatorPonderApplication.php b/src/applications/ponder/application/PhabricatorPonderApplication.php index 953f683128..11e90dc274 100644 --- a/src/applications/ponder/application/PhabricatorPonderApplication.php +++ b/src/applications/ponder/application/PhabricatorPonderApplication.php @@ -91,6 +91,19 @@ final class PhabricatorPonderApplication extends PhabricatorApplication { ); } + protected function getCustomCapabilities() { + return array( + PonderQuestionDefaultViewCapability::CAPABILITY => array( + 'template' => PonderQuestionPHIDType::TYPECONST, + 'capability' => PhabricatorPolicyCapability::CAN_VIEW, + ), + PonderQuestionDefaultEditCapability::CAPABILITY => array( + 'template' => PonderQuestionPHIDType::TYPECONST, + 'capability' => PhabricatorPolicyCapability::CAN_EDIT, + ), + ); + } + public function getApplicationSearchDocumentTypes() { return array( PonderQuestionPHIDType::TYPECONST, diff --git a/src/applications/ponder/capability/PonderQuestionDefaultEditCapability.php b/src/applications/ponder/capability/PonderQuestionDefaultEditCapability.php new file mode 100644 index 0000000000..2f9ca73b0f --- /dev/null +++ b/src/applications/ponder/capability/PonderQuestionDefaultEditCapability.php @@ -0,0 +1,12 @@ +getViewer(); + $viewer = $request->getViewer(); $id = $request->getURIData('id'); if ($id) { $question = id(new PonderQuestionQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( @@ -24,17 +24,14 @@ final class PonderQuestionEditController extends PonderController { PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); } else { - $question = id(new PonderQuestion()) - ->setStatus(PonderQuestionStatus::STATUS_OPEN) - ->setAuthorPHID($user->getPHID()) - ->setVoteCount(0) - ->setAnswerCount(0) - ->setHeat(0.0); + $question = PonderQuestion::initializeNewQuestion($viewer); $v_projects = array(); } $v_title = $question->getTitle(); $v_content = $question->getContent(); + $v_view = $question->getViewPolicy(); + $v_edit = $question->getEditPolicy(); $errors = array(); $e_title = true; @@ -42,6 +39,8 @@ final class PonderQuestionEditController extends PonderController { $v_title = $request->getStr('title'); $v_content = $request->getStr('content'); $v_projects = $request->getArr('projects'); + $v_view = $request->getStr('viewPolicy'); + $v_edit = $request->getStr('editPolicy'); $len = phutil_utf8_strlen($v_title); if ($len < 1) { @@ -64,6 +63,14 @@ final class PonderQuestionEditController extends PonderController { ->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT) ->setNewValue($v_content); + $xactions[] = id(clone $template) + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) + ->setNewValue($v_view); + + $xactions[] = id(clone $template) + ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) + ->setNewValue($v_edit); + $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PonderQuestionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) @@ -71,7 +78,7 @@ final class PonderQuestionEditController extends PonderController { ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PonderQuestionEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); @@ -82,8 +89,13 @@ final class PonderQuestionEditController extends PonderController { } } + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($question) + ->execute(); + $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Question')) @@ -92,12 +104,26 @@ final class PonderQuestionEditController extends PonderController { ->setError($e_title)) ->appendChild( id(new PhabricatorRemarkupControl()) - ->setUser($user) + ->setUser($viewer) ->setName('content') ->setID('content') ->setValue($v_content) ->setLabel(pht('Description')) - ->setUser($user)); + ->setUser($viewer)) + ->appendControl( + id(new AphrontFormPolicyControl()) + ->setName('viewPolicy') + ->setPolicyObject($question) + ->setPolicies($policies) + ->setValue($v_view) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) + ->appendControl( + id(new AphrontFormPolicyControl()) + ->setName('editPolicy') + ->setPolicyObject($question) + ->setPolicies($policies) + ->setValue($v_edit) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)); $form->appendControl( id(new AphrontFormTokenizerControl()) diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index 9dbe58cb0e..5d375b2972 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -66,6 +66,9 @@ final class PonderQuestionEditor $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_COMMENT; + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + $types[] = PonderQuestionTransaction::TYPE_TITLE; $types[] = PonderQuestionTransaction::TYPE_CONTENT; $types[] = PonderQuestionTransaction::TYPE_ANSWERS; diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index 1568c59245..17eee8ffb1 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -21,6 +21,8 @@ final class PonderQuestion extends PonderDAO protected $status; protected $content; protected $contentSource; + protected $viewPolicy; + protected $editPolicy; protected $voteCount; protected $answerCount; @@ -31,6 +33,27 @@ final class PonderQuestion extends PonderDAO private $vote; private $comments; + public static function initializeNewQuestion(PhabricatorUser $actor) { + $app = id(new PhabricatorApplicationQuery()) + ->setViewer($actor) + ->withClasses(array('PhabricatorPonderApplication')) + ->executeOne(); + + $view_policy = $app->getPolicy( + PonderQuestionDefaultViewCapability::CAPABILITY); + $edit_policy = $app->getPolicy( + PonderQuestionDefaultEditCapability::CAPABILITY); + + return id(new PonderQuestion()) + ->setAuthorPHID($actor->getPHID()) + ->setViewPolicy($view_policy) + ->setEditPolicy($edit_policy) + ->setStatus(PonderQuestionStatus::STATUS_OPEN) + ->setVoteCount(0) + ->setAnswerCount(0) + ->setHeat(0.0); + } + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -234,15 +257,12 @@ final class PonderQuestion extends PonderDAO } public function getPolicy($capability) { - $policy = PhabricatorPolicies::POLICY_NOONE; - switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: - $policy = PhabricatorPolicies::POLICY_USER; - break; + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); } - - return $policy; } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { From fdc1662bfd912337f39593c5c141babf2d7ab3f1 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Aug 2015 09:31:37 -0700 Subject: [PATCH 083/102] Add Projects to Ponder Search View Summary: Ref T3578 Allows display of projects if available on individual ponder search results. Test Plan: Added projects to my questions, see them display on search results. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3578 Differential Revision: https://secure.phabricator.com/D13793 --- .../ponder/query/PonderQuestionQuery.php | 28 ++++++++++++++++++- .../query/PonderQuestionSearchEngine.php | 27 +++++++++++++++++- .../ponder/storage/PonderQuestion.php | 11 ++++++++ .../PhabricatorUSEnglishTranslation.php | 2 ++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/applications/ponder/query/PonderQuestionQuery.php b/src/applications/ponder/query/PonderQuestionQuery.php index 78bc42a7d3..74d8230327 100644 --- a/src/applications/ponder/query/PonderQuestionQuery.php +++ b/src/applications/ponder/query/PonderQuestionQuery.php @@ -8,6 +8,8 @@ final class PonderQuestionQuery private $authorPHIDs; private $answererPHIDs; + private $needProjectPHIDs; + private $status = 'status-any'; const STATUS_ANY = 'status-any'; @@ -52,6 +54,11 @@ final class PonderQuestionQuery return $this; } + public function needProjectPHIDs($need_projects) { + $this->needProjectPHIDs = $need_projects; + return $this; + } + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); @@ -109,6 +116,9 @@ final class PonderQuestionQuery } protected function willFilterPage(array $questions) { + + $phids = mpull($questions, 'getPHID'); + if ($this->needAnswers) { $aquery = id(new PonderAnswerQuery()) ->setViewer($this->getViewer()) @@ -133,7 +143,7 @@ final class PonderQuestionQuery $etype = PonderQuestionHasVotingUserEdgeType::EDGECONST; $edges = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs(mpull($questions, 'getPHID')) + ->withSourcePHIDs($phids) ->withDestinationPHIDs(array($viewer_phid)) ->withEdgeTypes(array($etype)) ->needEdgeData(true) @@ -148,6 +158,22 @@ final class PonderQuestionQuery } } + if ($this->needProjectPHIDs) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs($phids) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + $edge_query->execute(); + + foreach ($questions as $question) { + $project_phids = $edge_query->getDestinationPHIDs( + array($question->getPHID())); + $question->attachProjectPHIDs($project_phids); + } + } + return $questions; } diff --git a/src/applications/ponder/query/PonderQuestionSearchEngine.php b/src/applications/ponder/query/PonderQuestionSearchEngine.php index 885e39bbf9..1b475c7b58 100644 --- a/src/applications/ponder/query/PonderQuestionSearchEngine.php +++ b/src/applications/ponder/query/PonderQuestionSearchEngine.php @@ -12,7 +12,8 @@ final class PonderQuestionSearchEngine } public function newQuery() { - return new PonderQuestionQuery(); + return id(new PonderQuestionQuery()) + ->needProjectPHIDs(true); } protected function buildQueryFromParameters(array $map) { @@ -112,6 +113,18 @@ final class PonderQuestionSearchEngine $viewer = $this->requireViewer(); + $proj_phids = array(); + foreach ($questions as $question) { + foreach ($question->getProjectPHIDs() as $project_phid) { + $proj_phids[] = $project_phid; + } + } + + $proj_handles = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs($proj_phids) + ->execute(); + $view = id(new PHUIObjectItemListView()) ->setUser($viewer); @@ -129,6 +142,10 @@ final class PonderQuestionSearchEngine $item->setObject($question); $item->setStatusIcon($icon.' '.$color, $full_status); + $project_handles = array_select_keys( + $proj_handles, + $question->getProjectPHIDs()); + $created_date = phabricator_date($question->getDateCreated(), $viewer); $item->addIcon('none', $created_date); $item->addByline( @@ -139,6 +156,14 @@ final class PonderQuestionSearchEngine $item->addAttribute( pht('%d Answer(s)', $question->getAnswerCount())); + if ($project_handles) { + $item->addAttribute( + id(new PHUIHandleTagListView()) + ->setLimit(4) + ->setSlim(true) + ->setHandles($project_handles)); + } + $view->addItem($item); } diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index 17eee8ffb1..e7788aa978 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -33,6 +33,8 @@ final class PonderQuestion extends PonderDAO private $vote; private $comments; + private $projectPHIDs = self::ATTACHABLE; + public static function initializeNewQuestion(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) @@ -164,6 +166,15 @@ final class PonderQuestion extends PonderDAO return $this->answers; } + public function getProjectPHIDs() { + return $this->assertAttached($this->projectPHIDs); + } + + public function attachProjectPHIDs(array $phids) { + $this->projectPHIDs = $phids; + return $this; + } + public function getMarkupField() { return self::MARKUP_FIELD_CONTENT; } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 8566710cda..98d062766b 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -32,6 +32,8 @@ final class PhabricatorUSEnglishTranslation '%d path(s)' => array('%d path', '%d paths'), '%d diff(s)' => array('%d diff', '%d diffs'), + '%d Answer(s)' => array('%d Answer', '%d Answers'), + '%s DIFF LINK(S)' => array('DIFF LINK', 'DIFF LINKS'), 'You successfully created %d diff(s).' => array( 'You successfully created %d diff.', From a3b955f948c873767b301066f3c50b06ad42ff48 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Aug 2015 09:38:14 -0700 Subject: [PATCH 084/102] Add Spaces to Ponder Summary: Ref T8493, Ref T3578. Adds spaces support to ponder. Test Plan: Ask a question in a new space, see new question. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3578, T8493 Differential Revision: https://secure.phabricator.com/D13792 --- .../sql/autopatches/20150804.ponder.spaces.4.sql | 2 ++ src/__phutil_library_map__.php | 1 + .../controller/PonderQuestionEditController.php | 7 +++++++ .../controller/PonderQuestionViewController.php | 4 +++- .../ponder/editor/PonderQuestionEditor.php | 1 + .../ponder/storage/PonderQuestion.php | 15 +++++++++++++-- 6 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 resources/sql/autopatches/20150804.ponder.spaces.4.sql diff --git a/resources/sql/autopatches/20150804.ponder.spaces.4.sql b/resources/sql/autopatches/20150804.ponder.spaces.4.sql new file mode 100644 index 0000000000..4282b8a509 --- /dev/null +++ b/resources/sql/autopatches/20150804.ponder.spaces.4.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_ponder.ponder_question + ADD spacePHID VARBINARY(64); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9636e776db..36957d0458 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -7590,6 +7590,7 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorSpacesInterface', ), 'PonderQuestionCommentController' => 'PonderController', 'PonderQuestionDefaultEditCapability' => 'PhabricatorPolicyCapability', diff --git a/src/applications/ponder/controller/PonderQuestionEditController.php b/src/applications/ponder/controller/PonderQuestionEditController.php index 1540460f12..23e15625a2 100644 --- a/src/applications/ponder/controller/PonderQuestionEditController.php +++ b/src/applications/ponder/controller/PonderQuestionEditController.php @@ -32,6 +32,7 @@ final class PonderQuestionEditController extends PonderController { $v_content = $question->getContent(); $v_view = $question->getViewPolicy(); $v_edit = $question->getEditPolicy(); + $v_space = $question->getSpacePHID(); $errors = array(); $e_title = true; @@ -41,6 +42,7 @@ final class PonderQuestionEditController extends PonderController { $v_projects = $request->getArr('projects'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); + $v_space = $request->getStr('spacePHID'); $len = phutil_utf8_strlen($v_title); if ($len < 1) { @@ -71,6 +73,10 @@ final class PonderQuestionEditController extends PonderController { ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($v_edit); + $xactions[] = id(clone $template) + ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) + ->setNewValue($v_space); + $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PonderQuestionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) @@ -114,6 +120,7 @@ final class PonderQuestionEditController extends PonderController { id(new AphrontFormPolicyControl()) ->setName('viewPolicy') ->setPolicyObject($question) + ->setSpacePHID($v_space) ->setPolicies($policies) ->setValue($v_view) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index d2b5a53826..f24714df50 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -38,7 +38,9 @@ final class PonderQuestionViewController extends PonderController { } $header = id(new PHUIHeaderView()) - ->setHeader($question->getTitle()); + ->setHeader($question->getTitle()) + ->setUser($user) + ->setPolicyObject($question); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $header->setStatus('fa-square-o', 'bluegrey', pht('Open')); diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index 5d375b2972..af984f4b4d 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -68,6 +68,7 @@ final class PonderQuestionEditor $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + $types[] = PhabricatorTransactions::TYPE_SPACE; $types[] = PonderQuestionTransaction::TYPE_TITLE; $types[] = PonderQuestionTransaction::TYPE_CONTENT; diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index e7788aa978..f2d81c4838 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -10,7 +10,8 @@ final class PonderQuestion extends PonderDAO PhabricatorPolicyInterface, PhabricatorTokenReceiverInterface, PhabricatorProjectInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorSpacesInterface { const MARKUP_FIELD_CONTENT = 'markup:content'; @@ -23,6 +24,7 @@ final class PonderQuestion extends PonderDAO protected $contentSource; protected $viewPolicy; protected $editPolicy; + protected $spacePHID; protected $voteCount; protected $answerCount; @@ -53,7 +55,8 @@ final class PonderQuestion extends PonderDAO ->setStatus(PonderQuestionStatus::STATUS_OPEN) ->setVoteCount(0) ->setAnswerCount(0) - ->setHeat(0.0); + ->setHeat(0.0) + ->setSpacePHID($actor->getDefaultSpacePHID()); } protected function getConfiguration() { @@ -329,4 +332,12 @@ final class PonderQuestion extends PonderDAO $this->saveTransaction(); } + +/* -( PhabricatorSpacesInterface )----------------------------------------- */ + + + public function getSpacePHID() { + return $this->spacePHID; + } + } From e05e539a9c10c740e90bdb94e1e2fae2dce5da43 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Aug 2015 09:42:39 -0700 Subject: [PATCH 085/102] Clean up Ponder CSS Summary: Ref T3578, Removes old cruft and updates Ponder UI very slightly. Test Plan: Use Ponder alot, new answers, comments, questions. Grep for removed CSS. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3578 Differential Revision: https://secure.phabricator.com/D13786 --- resources/celerity/map.php | 10 +-- .../PonderAnswerHistoryController.php | 1 + .../PonderQuestionHistoryController.php | 1 + .../PonderQuestionViewController.php | 55 ++++++------ .../ponder/view/PonderVotableView.php | 2 +- .../rsrc/css/application/ponder/comments.css | 85 ------------------- webroot/rsrc/css/application/ponder/feed.css | 74 ---------------- .../ponder/{vote.css => ponder-view.css} | 19 ++++- webroot/rsrc/css/application/ponder/post.css | 25 ------ 9 files changed, 52 insertions(+), 220 deletions(-) delete mode 100644 webroot/rsrc/css/application/ponder/comments.css delete mode 100644 webroot/rsrc/css/application/ponder/feed.css rename webroot/rsrc/css/application/ponder/{vote.css => ponder-view.css} (61%) delete mode 100644 webroot/rsrc/css/application/ponder/post.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f177c19d22..17a6048b7d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -94,10 +94,7 @@ return array( 'rsrc/css/application/policy/policy-edit.css' => '815c66f7', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', - 'rsrc/css/application/ponder/comments.css' => '865a67e6', - 'rsrc/css/application/ponder/feed.css' => 'e62615b6', - 'rsrc/css/application/ponder/post.css' => '9d415218', - 'rsrc/css/application/ponder/vote.css' => 'aea452b0', + 'rsrc/css/application/ponder/ponder-view.css' => 'fcd6b398', 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', @@ -816,10 +813,7 @@ return array( 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', - 'ponder-comment-table-css' => '865a67e6', - 'ponder-feed-view-css' => 'e62615b6', - 'ponder-post-css' => '9d415218', - 'ponder-vote-css' => 'aea452b0', + 'ponder-view-css' => 'fcd6b398', 'project-icon-css' => '4e3eaa5a', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', diff --git a/src/applications/ponder/controller/PonderAnswerHistoryController.php b/src/applications/ponder/controller/PonderAnswerHistoryController.php index 31296aeaf6..b2c42de8c8 100644 --- a/src/applications/ponder/controller/PonderAnswerHistoryController.php +++ b/src/applications/ponder/controller/PonderAnswerHistoryController.php @@ -24,6 +24,7 @@ final class PonderAnswerHistoryController extends PonderController { $aid = $answer->getID(); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); $crumbs->addTextCrumb("Q{$qid}", "/Q{$qid}"); $crumbs->addTextCrumb("A{$aid}", "/Q{$qid}#{$aid}"); $crumbs->addTextCrumb(pht('History')); diff --git a/src/applications/ponder/controller/PonderQuestionHistoryController.php b/src/applications/ponder/controller/PonderQuestionHistoryController.php index 3d62a7fcb6..91ded8c094 100644 --- a/src/applications/ponder/controller/PonderQuestionHistoryController.php +++ b/src/applications/ponder/controller/PonderQuestionHistoryController.php @@ -22,6 +22,7 @@ final class PonderQuestionHistoryController extends PonderController { $qid = $question->getID(); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); $crumbs->addTextCrumb("Q{$qid}", "/Q{$qid}"); $crumbs->addTextCrumb(pht('History')); diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index f24714df50..51f165b408 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -3,11 +3,11 @@ final class PonderQuestionViewController extends PonderController { public function handleRequest(AphrontRequest $request) { - $user = $request->getViewer(); + $viewer = $request->getViewer(); $id = $request->getURIData('id'); $question = id(new PonderQuestionQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($id)) ->needAnswers(true) ->needViewerVotes(true) @@ -16,15 +16,15 @@ final class PonderQuestionViewController extends PonderController { return new Aphront404Response(); } - $question->attachVotes($user->getPHID()); + $question->attachVotes($viewer->getPHID()); $question_xactions = $this->buildQuestionTransactions($question); $answers = $this->buildAnswers($question->getAnswers()); $authors = mpull($question->getAnswers(), null, 'getAuthorPHID'); - if (isset($authors[$user->getPHID()])) { + if (isset($authors[$viewer->getPHID()])) { $answer_add_panel = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->appendChild( pht( 'You have already answered this question. You can not answer '. @@ -33,14 +33,14 @@ final class PonderQuestionViewController extends PonderController { $answer_add_panel = new PonderAddAnswerView(); $answer_add_panel ->setQuestion($question) - ->setUser($user) + ->setUser($viewer) ->setActionURI('/ponder/answer/add/'); } - $header = id(new PHUIHeaderView()) - ->setHeader($question->getTitle()) - ->setUser($user) - ->setPolicyObject($question); + $header = new PHUIHeaderView(); + $header->setHeader($question->getTitle()); + $header->setUser($viewer); + $header->setPolicyObject($question); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $header->setStatus('fa-square-o', 'bluegrey', pht('Open')); @@ -58,13 +58,22 @@ final class PonderQuestionViewController extends PonderController { $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); $crumbs->addTextCrumb('Q'.$id, '/Q'.$id); - return $this->buildApplicationPage( + $ponder_view = phutil_tag( + 'div', + array( + 'class' => 'ponder-question-view', + ), array( $crumbs, $object_box, $question_xactions, $answers, $answer_add_panel, + )); + + return $this->buildApplicationPage( + array( + $ponder_view, ), array( 'title' => 'Q'.$question->getID().' '.$question->getTitle(), @@ -75,9 +84,8 @@ final class PonderQuestionViewController extends PonderController { } private function buildActionListView(PonderQuestion $question) { + $viewer = $this->getViewer(); $request = $this->getRequest(); - $viewer = $request->getUser(); - $id = $question->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -86,7 +94,7 @@ final class PonderQuestionViewController extends PonderController { PhabricatorPolicyCapability::CAN_EDIT); $view = id(new PhabricatorActionListView()) - ->setUser($request->getUser()) + ->setUser($viewer) ->setObject($question) ->setObjectURI($request->getRequestURI()); @@ -130,16 +138,12 @@ final class PonderQuestionViewController extends PonderController { PonderQuestion $question, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($question) ->setActionList($actions); - $view->addProperty( - pht('Status'), - PonderQuestionStatus::getQuestionStatusFullName($question->getStatus())); - $view->addProperty( pht('Author'), $viewer->renderHandle($question->getAuthorPHID())); @@ -209,8 +213,7 @@ final class PonderQuestionViewController extends PonderController { * standard fashion. This is necessary to scale this application. */ private function buildAnswers(array $answers) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $out = array(); @@ -283,9 +286,8 @@ final class PonderQuestionViewController extends PonderController { } private function buildAnswerActions(PonderAnswer $answer) { + $viewer = $this->getViewer(); $request = $this->getRequest(); - $viewer = $request->getUser(); - $id = $answer->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -294,7 +296,7 @@ final class PonderQuestionViewController extends PonderController { PhabricatorPolicyCapability::CAN_EDIT); $view = id(new PhabricatorActionListView()) - ->setUser($request->getUser()) + ->setUser($viewer) ->setObject($answer) ->setObjectURI($request->getRequestURI()); @@ -319,7 +321,7 @@ final class PonderQuestionViewController extends PonderController { PonderAnswer $answer, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($answer) @@ -366,7 +368,7 @@ final class PonderQuestionViewController extends PonderController { $hide_id = celerity_generate_unique_node_id(); Javelin::initBehavior('phabricator-reveal-content'); - require_celerity_resource('ponder-comment-table-css'); + require_celerity_resource('ponder-view-css'); $show = phutil_tag( 'div', @@ -389,6 +391,7 @@ final class PonderQuestionViewController extends PonderController { $hide = phutil_tag( 'div', array( + 'class' => 'ponder-comments-view', 'id' => $hide_id, 'style' => 'display: none', ), diff --git a/src/applications/ponder/view/PonderVotableView.php b/src/applications/ponder/view/PonderVotableView.php index 11cbe6cde0..dbd6ba32a8 100644 --- a/src/applications/ponder/view/PonderVotableView.php +++ b/src/applications/ponder/view/PonderVotableView.php @@ -28,7 +28,7 @@ final class PonderVotableView extends AphrontView { } public function render() { - require_celerity_resource('ponder-vote-css'); + require_celerity_resource('ponder-view-css'); require_celerity_resource('javelin-behavior-ponder-votebox'); Javelin::initBehavior('ponder-votebox', array()); diff --git a/webroot/rsrc/css/application/ponder/comments.css b/webroot/rsrc/css/application/ponder/comments.css deleted file mode 100644 index ca712b1d74..0000000000 --- a/webroot/rsrc/css/application/ponder/comments.css +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @provides ponder-comment-table-css - */ - -.ponder-show-comments { - text-align: center; - padding: 8px; - margin: 0 16px; - float: right; - font-weight: bold; - background: #fff; - border-bottom: 1px solid {$blueborder}; - border-left: 1px solid {$lightblueborder}; - border-right: 1px solid {$lightblueborder}; -} - -.ponder-comments { - width: 480px; - margin: 5px 0 0 60px; -} - -.device .ponder-comments { - width: 100%; - margin: 5px 0 0 0; -} - -.ponder-comments th { - width: 0px; - height: 0px; -} - -.ponder-comments td { - vertical-align: top; - padding: 6px; - border-bottom: 1px solid {$thinblueborder}; - background: #fff; -} - -.ponder-datestamp { - font-size: {$smallestfontsize}; - color: {$greytext}; -} - -.ponder-label { - display: block; - width: 100%; - font-weight: bold; - color: #333; - text-align: left; - margin: 0px 0px 6px; - padding: 6px 4px; - background: #ccc; - border-bottom: 1px solid #aaa; - cursor: pointer; -} - -td .aphront-form-control { - padding: 0; -} - -td .aphront-form-control-submit { - display: block; -} - -td .aphront-form-input { - margin: 0; - width: 100%; -} - -td .aphront-form-control textarea { - height: 50px; -} - -.ponder-comment-markup p { - margin: 0 0 5px 0; -} - -.ponder-comment-markup h2, -.ponder-comment-markup h3, -.ponder-comment-markup h4, -.ponder-comment-markup h5 { - margin: 0; - font-size: inherit; - font-weight: normal; -} diff --git a/webroot/rsrc/css/application/ponder/feed.css b/webroot/rsrc/css/application/ponder/feed.css deleted file mode 100644 index e2c0eefb21..0000000000 --- a/webroot/rsrc/css/application/ponder/feed.css +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @provides ponder-feed-view-css - */ - -.ponder-question-summary { - width: 100%; - background: #DFDFE3; - float: left; - clear: both; - margin-top: 1px; - padding: 1px; -} - -.ponder-answer-summary { - width : 100%; - background : #DFDFE3; - float : left; - clear : both; - margin-top : 1px; - padding : 1px; -} - -.ponder-summary-votes { - width : 50px; - height : 36pt; - font-size : 18pt; - text-align : center; - background : #EEE; - border : 1px solid #BBB; - float : left; - margin : 2px; - padding-top : 2px; -} - -.ponder-summary-answers { - width : 50px; - height : 36pt; - font-size : 18pt; - text-align : center; - background : #EEE; - border : 1px solid #BBB; - float : left; - margin : 2px; - padding-top : 2px; -} - -.ponder-question-label { - font-size : 6pt; -} - -h2.ponder-question-title { - font-size : 12pt; - margin : 2px; - padding : 0; -} - -h2.ponder-answer-title { - font-size : 12pt; - margin : 2px; - padding : 0; -} - -.ponder-metadata { - padding-left : 5px; - width : 650px; - float : left; -} - -.ponder-small-metadata { - font-size : 7.5pt; - color : #555; - margin : 0; - text-align : right; -} diff --git a/webroot/rsrc/css/application/ponder/vote.css b/webroot/rsrc/css/application/ponder/ponder-view.css similarity index 61% rename from webroot/rsrc/css/application/ponder/vote.css rename to webroot/rsrc/css/application/ponder/ponder-view.css index 3436df9882..237c3e7676 100644 --- a/webroot/rsrc/css/application/ponder/vote.css +++ b/webroot/rsrc/css/application/ponder/ponder-view.css @@ -1,5 +1,5 @@ /** - * @provides ponder-vote-css + * @provides ponder-view-css */ .ponder-votable { @@ -40,3 +40,20 @@ line-height: 20px; font-weight: bold; } + +.ponder-show-comments { + text-align: center; + padding: 8px; + margin: 0 16px; + float: right; + font-weight: bold; + background: #fff; + border-bottom: 1px solid {$blueborder}; + border-left: 1px solid {$lightblueborder}; + border-right: 1px solid {$lightblueborder}; +} + +.device-desktop .ponder-comments-view { + width: 90%; + margin: 0 auto; +} diff --git a/webroot/rsrc/css/application/ponder/post.css b/webroot/rsrc/css/application/ponder/post.css deleted file mode 100644 index c1f3c67c55..0000000000 --- a/webroot/rsrc/css/application/ponder/post.css +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @provides ponder-post-css - */ - -.ponder-post-list { - max-width: 1162px; -} - -.ponder-add-answer-panel { - max-width: 1162px; -} - -.ponder-post-list .anchor-target { - background-color: #ffffdd; - border-color: #ffff00; -} - -.ponder-post-core .phabricator-remarkup .remarkup-code-block { - width: 88ex; - width: 81ch; -} - -.ponder-question { - background: white; -} From b34dc6164af2cd24e9e7abf1901ca3206ebb9874 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Aug 2015 11:59:38 -0700 Subject: [PATCH 086/102] Add Subscribers to Passphrase Summary: Fixes T9078, Adds SubscribableInterface to Passphrase. Test Plan: Create a new passphrase, see myself subscribed. Subscribe to other Passphrases. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9078 Differential Revision: https://secure.phabricator.com/D13799 --- src/__phutil_library_map__.php | 1 + .../PassphraseCredentialViewController.php | 2 ++ .../storage/PassphraseCredential.php | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 36957d0458..ca01782e65 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5189,6 +5189,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorFlaggableInterface', + 'PhabricatorSubscribableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', ), diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index 535527912a..46fd0c4c7a 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -193,6 +193,8 @@ final class PassphraseCredentialViewController extends PassphraseController { $viewer->renderHandleList($used_by_phids)); } + $properties->invokeWillRenderEvent(); + $description = $credential->getDescription(); if (strlen($description)) { $properties->addSectionHeader( diff --git a/src/applications/passphrase/storage/PassphraseCredential.php b/src/applications/passphrase/storage/PassphraseCredential.php index 3ad1351061..1c58ced82d 100644 --- a/src/applications/passphrase/storage/PassphraseCredential.php +++ b/src/applications/passphrase/storage/PassphraseCredential.php @@ -5,6 +5,7 @@ final class PassphraseCredential extends PassphraseDAO PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, PhabricatorFlaggableInterface, + PhabricatorSubscribableInterface, PhabricatorDestructibleInterface, PhabricatorSpacesInterface { @@ -147,6 +148,23 @@ final class PassphraseCredential extends PassphraseDAO return null; } + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + public function shouldShowSubscribersProperty() { + return true; + } + + public function shouldAllowSubscription($phid) { + return true; + } + + /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( From 2383e741aa7410da8db6a39b84e8f4e0cd486e59 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Aug 2015 12:40:47 -0700 Subject: [PATCH 087/102] Add user icons / colors for subscriber transactions Summary: Adds additional icon states for subscriber transactions. Also updated "eraser" to "trash" (man that icon is bad). Test Plan: add a subscriber, remove a subscriber. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13800 --- .../PhabricatorApplicationTransaction.php | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index e8811da760..d6d961c780 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -362,11 +362,23 @@ abstract class PhabricatorApplicationTransaction case PhabricatorTransactions::TYPE_COMMENT: $comment = $this->getComment(); if ($comment && $comment->getIsRemoved()) { - return 'fa-eraser'; + return 'fa-trash'; } return 'fa-comment'; case PhabricatorTransactions::TYPE_SUBSCRIBERS: - return 'fa-envelope'; + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && $rem) { + return 'fa-user'; + } else if ($add) { + return 'fa-user-plus'; + } else if ($rem) { + return 'fa-user-times'; + } else { + return 'fa-user'; + } case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: @@ -416,6 +428,21 @@ abstract class PhabricatorApplicationTransaction return 'red'; } break; + case PhabricatorTransactions::TYPE_SUBSCRIBERS: + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && $rem) { + return 'green'; + } else if ($add) { + return 'green'; + } else if ($rem) { + return 'black'; + } else { + return null; + } + break; } return null; } From 425c12dc97dd284169b7c5a5c871a63903ead5e2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Aug 2015 13:40:44 -0700 Subject: [PATCH 088/102] Send correct result in Phortune Charge List Summary: Fixes T9074, we're sending $table and should send $result Test Plan: Vast Wealth fake CC charge! Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9074 Differential Revision: https://secure.phabricator.com/D13802 --- src/applications/phortune/query/PhortuneChargeSearchEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phortune/query/PhortuneChargeSearchEngine.php b/src/applications/phortune/query/PhortuneChargeSearchEngine.php index 598614d885..0d6c2cfd59 100644 --- a/src/applications/phortune/query/PhortuneChargeSearchEngine.php +++ b/src/applications/phortune/query/PhortuneChargeSearchEngine.php @@ -130,6 +130,6 @@ final class PhortuneChargeSearchEngine $result = new PhabricatorApplicationSearchResultView(); $result->setTable($table); - return $table; + return $result; } } From 5eeda6040ecae7fc6b9692d6ab5c94b137198259 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Aug 2015 13:44:55 -0700 Subject: [PATCH 089/102] Allow setting of Archive/Active on Paste Summary: Ref T9076, adds basic plumbing for setting the state of a Paste. Test Plan: Archive Paste, Activate Paste, New Paste Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9076 Differential Revision: https://secure.phabricator.com/D13801 --- .../autopatches/20150805.paste.status.1.sql | 2 ++ .../autopatches/20150805.paste.status.2.sql | 2 ++ .../PhabricatorPasteEditController.php | 12 +++++++++++ .../PhabricatorPasteViewController.php | 12 +++++++++++ .../paste/editor/PhabricatorPasteEditor.php | 8 +++++++ .../paste/storage/PhabricatorPaste.php | 17 +++++++++++++++ .../storage/PhabricatorPasteTransaction.php | 21 +++++++++++++++++++ 7 files changed, 74 insertions(+) create mode 100644 resources/sql/autopatches/20150805.paste.status.1.sql create mode 100644 resources/sql/autopatches/20150805.paste.status.2.sql diff --git a/resources/sql/autopatches/20150805.paste.status.1.sql b/resources/sql/autopatches/20150805.paste.status.1.sql new file mode 100644 index 0000000000..c9b98bf58c --- /dev/null +++ b/resources/sql/autopatches/20150805.paste.status.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_pastebin.pastebin_paste + ADD status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150805.paste.status.2.sql b/resources/sql/autopatches/20150805.paste.status.2.sql new file mode 100644 index 0000000000..be2fe2c485 --- /dev/null +++ b/resources/sql/autopatches/20150805.paste.status.2.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_pastebin.pastebin_paste + SET status = 'active' WHERE status = ''; diff --git a/src/applications/paste/controller/PhabricatorPasteEditController.php b/src/applications/paste/controller/PhabricatorPasteEditController.php index db9ca7de99..19b394c18a 100644 --- a/src/applications/paste/controller/PhabricatorPasteEditController.php +++ b/src/applications/paste/controller/PhabricatorPasteEditController.php @@ -64,6 +64,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { } $v_view_policy = $paste->getViewPolicy(); $v_edit_policy = $paste->getEditPolicy(); + $v_status = $paste->getStatus(); if ($is_create) { $v_projects = array(); @@ -85,6 +86,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { $v_edit_policy = $request->getStr('can_edit'); $v_projects = $request->getArr('projects'); $v_space = $request->getStr('spacePHID'); + $v_status = $request->getStr('status'); // NOTE: The author is the only editor and can always view the paste, // so it's impossible for them to choose an invalid policy. @@ -115,6 +117,9 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { $xactions[] = id(new PhabricatorPasteTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) ->setNewValue($v_space); + $xactions[] = id(new PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) + ->setNewValue($v_status); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PhabricatorPasteTransaction()) @@ -180,6 +185,13 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ->setValue($v_edit_policy) ->setName('can_edit')); + $form->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Status')) + ->setName('status') + ->setValue($v_status) + ->setOptions($paste->getStatusNameMap())); + $form->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index ebdf4e48c3..0273636d00 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -115,9 +115,21 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { private function buildHeaderView(PhabricatorPaste $paste) { $title = (nonempty($paste->getTitle())) ? $paste->getTitle() : pht('(An Untitled Masterwork)'); + + if ($paste->isArchived()) { + $header_icon = 'fa-ban'; + $header_name = pht('Archived'); + $header_color = 'dark'; + } else { + $header_icon = 'fa-check'; + $header_name = pht('Active'); + $header_color = 'bluegrey'; + } + $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($this->getRequest()->getUser()) + ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($paste); return $header; diff --git a/src/applications/paste/editor/PhabricatorPasteEditor.php b/src/applications/paste/editor/PhabricatorPasteEditor.php index 732db28bd7..bae2003afe 100644 --- a/src/applications/paste/editor/PhabricatorPasteEditor.php +++ b/src/applications/paste/editor/PhabricatorPasteEditor.php @@ -33,6 +33,7 @@ final class PhabricatorPasteEditor $types[] = PhabricatorPasteTransaction::TYPE_CONTENT; $types[] = PhabricatorPasteTransaction::TYPE_TITLE; $types[] = PhabricatorPasteTransaction::TYPE_LANGUAGE; + $types[] = PhabricatorPasteTransaction::TYPE_STATUS; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_COMMENT; @@ -51,6 +52,8 @@ final class PhabricatorPasteEditor return $object->getTitle(); case PhabricatorPasteTransaction::TYPE_LANGUAGE: return $object->getLanguage(); + case PhabricatorPasteTransaction::TYPE_STATUS: + return $object->getStatus(); } } @@ -62,6 +65,7 @@ final class PhabricatorPasteEditor case PhabricatorPasteTransaction::TYPE_CONTENT: case PhabricatorPasteTransaction::TYPE_TITLE: case PhabricatorPasteTransaction::TYPE_LANGUAGE: + case PhabricatorPasteTransaction::TYPE_STATUS: return $xaction->getNewValue(); } } @@ -80,6 +84,9 @@ final class PhabricatorPasteEditor case PhabricatorPasteTransaction::TYPE_LANGUAGE: $object->setLanguage($xaction->getNewValue()); return; + case PhabricatorPasteTransaction::TYPE_STATUS: + $object->setStatus($xaction->getNewValue()); + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -93,6 +100,7 @@ final class PhabricatorPasteEditor case PhabricatorPasteTransaction::TYPE_CONTENT: case PhabricatorPasteTransaction::TYPE_TITLE: case PhabricatorPasteTransaction::TYPE_LANGUAGE: + case PhabricatorPasteTransaction::TYPE_STATUS: return; } diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php index 661701ba09..0fab56b724 100644 --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -20,8 +20,12 @@ final class PhabricatorPaste extends PhabricatorPasteDAO protected $viewPolicy; protected $editPolicy; protected $mailKey; + protected $status; protected $spacePHID; + const STATUS_ACTIVE = 'active'; + const STATUS_ARCHIVED = 'archived'; + private $content = self::ATTACHABLE; private $rawContent = self::ATTACHABLE; @@ -36,12 +40,20 @@ final class PhabricatorPaste extends PhabricatorPasteDAO return id(new PhabricatorPaste()) ->setTitle('') + ->setStatus(self::STATUS_ACTIVE) ->setAuthorPHID($actor->getPHID()) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) ->setSpacePHID($actor->getDefaultSpacePHID()); } + public static function getStatusNameMap() { + return array( + self::STATUS_ACTIVE => pht('Active'), + self::STATUS_ARCHIVED => pht('Archived'), + ); + } + public function getURI() { return '/'.$this->getMonogram(); } @@ -54,6 +66,7 @@ final class PhabricatorPaste extends PhabricatorPasteDAO return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( + 'status' => 'text32', 'title' => 'text255', 'language' => 'text64', 'mailKey' => 'bytes20', @@ -85,6 +98,10 @@ final class PhabricatorPaste extends PhabricatorPasteDAO PhabricatorPastePastePHIDType::TYPECONST); } + public function isArchived() { + return ($this->getStatus() == self::STATUS_ARCHIVED); + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); diff --git a/src/applications/paste/storage/PhabricatorPasteTransaction.php b/src/applications/paste/storage/PhabricatorPasteTransaction.php index 34cc6bd92d..6372f38a2b 100644 --- a/src/applications/paste/storage/PhabricatorPasteTransaction.php +++ b/src/applications/paste/storage/PhabricatorPasteTransaction.php @@ -6,6 +6,7 @@ final class PhabricatorPasteTransaction const TYPE_CONTENT = 'paste.create'; const TYPE_TITLE = 'paste.title'; const TYPE_LANGUAGE = 'paste.language'; + const TYPE_STATUS = 'paste.status'; const MAILTAG_CONTENT = 'paste-content'; const MAILTAG_OTHER = 'paste-other'; @@ -89,6 +90,12 @@ final class PhabricatorPasteTransaction "%s updated the paste's language.", $this->renderHandleLink($author_phid)); break; + case self::TYPE_STATUS: + return pht( + "%s updated the paste's status.", + $this->renderHandleLink($author_phid)); + break; + } return parent::getTitle(); @@ -128,6 +135,20 @@ final class PhabricatorPasteTransaction $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; + case self::TYPE_STATUS: + switch ($new) { + case self::STATUS_OPEN: + return pht( + '%s activated %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + case self::STATUS_CLOSED: + return pht( + '%s archived %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + break; } return parent::getTitleForFeed(); From 8860f4724fa43b05f4d520f3c64f6a8d7a0e3ddd Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 6 Aug 2015 04:19:42 -0700 Subject: [PATCH 090/102] Lump Harbormaster build steps into groups Summary: Ref T8089. We have a lot of broken/confusing/prototype build steps that I want to hide from users when we unprototype Harbormaster. The dialog is also just kind of unwieldy. Organize this UI a little better and put all the sketchy junk in a "prototypes" group that you can't see unless prototypes are enabled. This doesn't break anything (the old steps will still work fine), but should reduce user confusion. Test Plan: Old UI: {F691439} New UI (prototypes off): {F691440} New UI (prototypes on): {F691441} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8089 Differential Revision: https://secure.phabricator.com/D13803 --- src/__phutil_library_map__.php | 12 +++ .../HarbormasterPlanViewController.php | 2 +- .../HarbormasterStepAddController.php | 95 +++++++++++++------ ...ormasterArcLintBuildStepImplementation.php | 4 + ...ormasterArcUnitBuildStepImplementation.php | 4 + .../HarbormasterBuildStepImplementation.php | 4 + ...ormasterCommandBuildStepImplementation.php | 4 + ...sterHTTPRequestBuildStepImplementation.php | 4 + ...masterLeaseHostBuildStepImplementation.php | 4 + ...PublishFragmentBuildStepImplementation.php | 5 + ...rbormasterSleepBuildStepImplementation.php | 5 + .../HarbormasterThrowExceptionBuildStep.php | 4 + ...rUploadArtifactBuildStepImplementation.php | 4 + ...WaitForPreviousBuildStepImplementation.php | 4 + .../stepgroup/HarbormasterBuildStepGroup.php | 52 ++++++++++ .../HarbormasterBuiltinBuildStepGroup.php | 20 ++++ .../HarbormasterExternalBuildStepGroup.php | 16 ++++ .../HarbormasterOtherBuildStepGroup.php | 20 ++++ .../HarbormasterPrototypeBuildStepGroup.php | 24 +++++ .../HarbormasterTestBuildStepGroup.php | 16 ++++ 20 files changed, 272 insertions(+), 31 deletions(-) create mode 100644 src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php create mode 100644 src/applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php create mode 100644 src/applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php create mode 100644 src/applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php create mode 100644 src/applications/harbormaster/stepgroup/HarbormasterPrototypeBuildStepGroup.php create mode 100644 src/applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ca01782e65..fbdbc739fa 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -951,6 +951,7 @@ phutil_register_library_map(array( 'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php', 'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php', 'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php', + 'HarbormasterBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php', 'HarbormasterBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildStepImplementation.php', 'HarbormasterBuildStepImplementationTestCase' => 'applications/harbormaster/step/__tests__/HarbormasterBuildStepImplementationTestCase.php', 'HarbormasterBuildStepPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php', @@ -978,10 +979,12 @@ phutil_register_library_map(array( 'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php', 'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php', 'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php', + 'HarbormasterBuiltinBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php', 'HarbormasterCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php', 'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php', 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', + 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', @@ -992,12 +995,14 @@ phutil_register_library_map(array( 'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php', 'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php', 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', + 'HarbormasterOtherBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php', 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', 'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php', 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', 'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php', 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', + 'HarbormasterPrototypeBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterPrototypeBuildStepGroup.php', 'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php', 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php', 'HarbormasterQueryBuildablesConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php', @@ -1013,6 +1018,7 @@ phutil_register_library_map(array( 'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php', 'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php', 'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php', + 'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php', 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', @@ -4660,6 +4666,7 @@ phutil_register_library_map(array( ), 'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField', 'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor', + 'HarbormasterBuildStepGroup' => 'Phobject', 'HarbormasterBuildStepImplementation' => 'Phobject', 'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase', 'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType', @@ -4693,10 +4700,12 @@ phutil_register_library_map(array( 'HarbormasterBuildableTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildableViewController' => 'HarbormasterController', + 'HarbormasterBuiltinBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod', 'HarbormasterController' => 'PhabricatorController', 'HarbormasterDAO' => 'PhabricatorLiskDAO', + 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLintMessagesController' => 'HarbormasterController', @@ -4707,12 +4716,14 @@ phutil_register_library_map(array( 'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow', 'HarbormasterMessageType' => 'Phobject', 'HarbormasterObject' => 'HarbormasterDAO', + 'HarbormasterOtherBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterPlanController' => 'HarbormasterController', 'HarbormasterPlanDisableController' => 'HarbormasterPlanController', 'HarbormasterPlanEditController' => 'HarbormasterPlanController', 'HarbormasterPlanListController' => 'HarbormasterPlanController', 'HarbormasterPlanRunController' => 'HarbormasterController', 'HarbormasterPlanViewController' => 'HarbormasterPlanController', + 'HarbormasterPrototypeBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod', @@ -4728,6 +4739,7 @@ phutil_register_library_map(array( 'HarbormasterStepEditController' => 'HarbormasterController', 'HarbormasterTargetEngine' => 'Phobject', 'HarbormasterTargetWorker' => 'HarbormasterWorker', + 'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 'HarbormasterUIEventListener' => 'PhabricatorEventListener', 'HarbormasterUnitMessagesController' => 'HarbormasterController', diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php index 417b9a700e..da35e389a0 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -235,7 +235,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController { id(new PHUIIconView()) ->setIconFont('fa-plus')) ->setDisabled(!$can_edit) - ->setWorkflow(true)); + ->setWorkflow(!$can_edit)); $step_box = id(new PHUIObjectBoxView()) ->setHeader($header) diff --git a/src/applications/harbormaster/controller/HarbormasterStepAddController.php b/src/applications/harbormaster/controller/HarbormasterStepAddController.php index 371a1d1db2..21cdc3f12c 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepAddController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepAddController.php @@ -23,48 +23,83 @@ final class HarbormasterStepAddController extends HarbormasterController { $plan_id = $plan->getID(); $cancel_uri = $this->getApplicationURI("plan/{$plan_id}/"); + $plan_title = pht('Plan %d', $plan_id); $all = HarbormasterBuildStepImplementation::getImplementations(); - foreach ($all as $key => $impl) { - if ($impl->shouldRequireAutotargeting()) { - unset($all[$key]); + $all = msort($all, 'getName'); + + $all_groups = HarbormasterBuildStepGroup::getAllGroups(); + foreach ($all as $impl) { + $group_key = $impl->getBuildStepGroupKey(); + if (empty($all_groups[$group_key])) { + throw new Exception( + pht( + 'Build step "%s" has step group key "%s", but no step group '. + 'with that key exists.', + get_class($impl), + $group_key)); } } - $errors = array(); - if ($request->isFormPost()) { - $class = $request->getStr('class'); - if (empty($all[$class])) { - $errors[] = pht('Choose the type of build step you want to add.'); + $groups = mgroup($all, 'getBuildStepGroupKey'); + $lists = array(); + + $enabled_groups = HarbormasterBuildStepGroup::getAllEnabledGroups(); + foreach ($enabled_groups as $group) { + $list = id(new PHUIObjectItemListView()) + ->setHeader($group->getGroupName()) + ->setNoDataString( + pht( + 'This group has no available build steps.')); + + $steps = idx($groups, $group->getGroupKey(), array()); + + foreach ($steps as $key => $impl) { + if ($impl->shouldRequireAutotargeting()) { + unset($steps[$key]); + continue; + } } - if (!$errors) { + + if (!$steps && !$group->shouldShowIfEmpty()) { + continue; + } + + foreach ($steps as $key => $impl) { + $class = get_class($impl); + $new_uri = $this->getApplicationURI("step/new/{$plan_id}/{$class}/"); - return id(new AphrontRedirectResponse())->setURI($new_uri); + + $item = id(new PHUIObjectItemView()) + ->setHeader($impl->getName()) + ->setHref($new_uri) + ->addAttribute($impl->getGenericDescription()); + + $list->addItem($item); } + + $lists[] = $list; } - $control = id(new AphrontFormRadioButtonControl()) - ->setName('class'); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($plan_title, $cancel_uri) + ->addTextCrumb(pht('Add Build Step')); - foreach ($all as $class => $implementation) { - $control->addButton( - $class, - $implementation->getName(), - $implementation->getGenericDescription()); - } + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Add Build Step')) + ->appendChild($lists); - if ($errors) { - $errors = id(new PHUIInfoView()) - ->setErrors($errors); - } - - return $this->newDialog() - ->setTitle(pht('Add New Step')) - ->addSubmitButton(pht('Add Build Step')) - ->addCancelButton($cancel_uri) - ->appendChild($errors) - ->appendParagraph(pht('Choose a type of build step to add:')) - ->appendChild($control); + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => array( + $plan_title, + pht('Add Build Step'), + ), + )); } } diff --git a/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php index 93b6e498c7..ed69ab807d 100644 --- a/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php @@ -25,6 +25,10 @@ final class HarbormasterArcLintBuildStepImplementation return pht('Automatic `arc lint` step.'); } + public function getBuildStepGroupKey() { + return HarbormasterBuiltinBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php index 4f3805949f..d2b41a06a6 100644 --- a/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php @@ -25,6 +25,10 @@ final class HarbormasterArcUnitBuildStepImplementation return pht('Automatic `arc unit` step.'); } + public function getBuildStepGroupKey() { + return HarbormasterBuiltinBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index c831b711dd..15f2dd9943 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -41,6 +41,10 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { */ abstract public function getName(); + public function getBuildStepGroupKey() { + return HarbormasterOtherBuildStepGroup::GROUPKEY; + } + /** * The generic description of the implementation. */ diff --git a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php index 5dbfd80262..931ade75a7 100644 --- a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php @@ -13,6 +13,10 @@ final class HarbormasterCommandBuildStepImplementation return pht('Run a command on Drydock host.'); } + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function getDescription() { return pht( 'Run command %s on host %s.', diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php index 5d361b3de3..3910ac0b24 100644 --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -11,6 +11,10 @@ final class HarbormasterHTTPRequestBuildStepImplementation return pht('Make an HTTP request.'); } + public function getBuildStepGroupKey() { + return HarbormasterExternalBuildStepGroup::GROUPKEY; + } + public function getDescription() { $domain = null; $uri = $this->getSetting('uri'); diff --git a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php index 28e14c964e..d5f461506a 100644 --- a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php @@ -11,6 +11,10 @@ final class HarbormasterLeaseHostBuildStepImplementation return pht('Obtain a lease on a Drydock host for performing builds.'); } + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php index e008d5d50b..dc59717a23 100644 --- a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php @@ -11,6 +11,11 @@ final class HarbormasterPublishFragmentBuildStepImplementation return pht('Publish a fragment based on a file artifact.'); } + + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function getDescription() { return pht( 'Publish file artifact %s as fragment %s.', diff --git a/src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php index 968c5b99cb..60221b782c 100644 --- a/src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php @@ -11,6 +11,11 @@ final class HarbormasterSleepBuildStepImplementation return pht('Sleep for a specified number of seconds.'); } + + public function getBuildStepGroupKey() { + return HarbormasterTestBuildStepGroup::GROUPKEY; + } + public function getDescription() { return pht( 'Sleep for %s seconds.', diff --git a/src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php b/src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php index 402a03ba20..06947317ef 100644 --- a/src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php +++ b/src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php @@ -11,6 +11,10 @@ final class HarbormasterThrowExceptionBuildStep return pht('Throw an exception.'); } + public function getBuildStepGroupKey() { + return HarbormasterTestBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php index 4172da8917..75b48eb3c6 100644 --- a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php @@ -11,6 +11,10 @@ final class HarbormasterUploadArtifactBuildStepImplementation return pht('Upload a file from a host to Phabricator.'); } + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function getDescription() { return pht( 'Upload %s from %s.', diff --git a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php index 288cb1b1fa..f4e965b6c2 100644 --- a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php @@ -13,6 +13,10 @@ final class HarbormasterWaitForPreviousBuildStepImplementation 'before continuing.'); } + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php b/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php new file mode 100644 index 0000000000..bb439b75b4 --- /dev/null +++ b/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php @@ -0,0 +1,52 @@ +getConstant('GROUPKEY'); + if ($const === false) { + throw new Exception( + pht( + '"%s" class "%s" must define a "%s" property.', + __CLASS__, + get_class($this), + 'GROUPKEY')); + } + + return $const; + } + + final public static function getAllGroups() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getGroupKey') + ->setSortMethod('getGroupOrder') + ->execute(); + } + + final public static function getAllEnabledGroups() { + $groups = self::getAllGroups(); + + foreach ($groups as $key => $group) { + if (!$group->isEnabled()) { + unset($groups[$key]); + } + } + + return $groups; + } + +} diff --git a/src/applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php b/src/applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php new file mode 100644 index 0000000000..c730048e78 --- /dev/null +++ b/src/applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php @@ -0,0 +1,20 @@ + Date: Thu, 6 Aug 2015 09:54:00 -0700 Subject: [PATCH 091/102] Unprototype Harbormaster Summary: Ref T8089. There's still some cleanup but none of it needs to block this. Test Plan: Viewed `/applications/`, read documentation. Reviewers: chad Reviewed By: chad Subscribers: devurandom, swisspol Maniphest Tasks: T8089 Differential Revision: https://secure.phabricator.com/D13811 --- .../PhabricatorHarbormasterApplication.php | 13 +- src/docs/user/userguide/harbormaster.diviner | 230 ++++++++++++++++++ 2 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 src/docs/user/userguide/harbormaster.diviner diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php index 016e2a5cbc..266ac4fafc 100644 --- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php +++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php @@ -36,16 +36,21 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication { ); } - public function isPrototype() { - return true; - } - public function getRemarkupRules() { return array( new HarbormasterRemarkupRule(), ); } + public function getHelpDocumentationArticles(PhabricatorUser $viewer) { + return array( + array( + 'name' => pht('Harbormaster User Guide'), + 'href' => PhabricatorEnv::getDoclink('Harbormaster User Guide'), + ), + ); + } + public function getRoutes() { return array( '/B(?P[1-9]\d*)' => 'HarbormasterBuildableViewController', diff --git a/src/docs/user/userguide/harbormaster.diviner b/src/docs/user/userguide/harbormaster.diviner new file mode 100644 index 0000000000..23ed15d5e4 --- /dev/null +++ b/src/docs/user/userguide/harbormaster.diviner @@ -0,0 +1,230 @@ +@title Harbormaster User Guide +@group userguide + +Guide to Harbormaster, a build and continuous integration application. + +Overview +======== + +WARNING: Harbormaster is still very rough. Read this document carefully to +understand what it can and can not do and what to expect in the future. + +The Harbormaster application provides build and continuous integration support +for Phabricator. + +Harbormaster is not a mature application. You should expect it to have major +missing capabilities and to change substantially over time. The current version +of Harbormaster can perform some basic build tasks, but has many limitations +and is not a complete build platform. + +In particular, you should be aware of these common limitations: + + - **Creating Build Plans**: Harbormaster ships with only very basic, crude + tools for writing build plans. There are no default integrations with + `arc unit` or systems like Jenkins. Build plans are likely to change + substantially over time. + - **Triggering Builds**: Harbormaster can only trigger builds through Herald + rules. It can not currently run periodic builds. + - **Executing Builds**: Harbormaster can only execute builds in a remote + system, like Jenkins. It can not currently host builds. + - **Change Handoff**: Change handoff is covered in rough edges and tradeoffs. + + +Harbormaster Basics +=================== + +Use Harbormaster to run builds or tests on code reviews and commits. In general, +the Harbormaster workflow looks like this today: + + - You create a new "Build Plan" which describes how to build a project (which + tests to run, which commands to execute, etc). + - You configure Harbormaster to trigger the plan when relevant code reviews + are created or relevant commits are pushed or discovered. + - Harbormaster executes the plan and reports the results, allowing you to see + if a change or commit breaks tests. + +The remainder of this document walks through these steps in more detail. + + +Concepts and Terminology +======================== + +Harbormaster uses these concepts to describe builds: + + - **Build Step**: Describes a single step in a build process, like running a + command. + - **Build Plan**: A collection of build steps which describe a build process. + You'll create build plans to tell Harbormaster which commands it needs to + run to perform a build. + - **Buildable**: A reference to an object from another application which can + have builds run against it. In the upstream, code reviews (from + Differential) and commits (from Diffusion) are buildable. + - **Build**: Created by running a build plan against a buildable. Collects + results from running build commands and shows build progress, status and + results. A build describes what happened when an entire build plan was + run. + - **Build Target**: Builds are made up of build targets, which are created + automatically when Harbormaster runs the individual steps in a build. A + build target describes what happened when a specific build step was run. + + +Creating a Build Plan +===================== + +NOTE: Build plans are currently crude and subject to change in future versions +of Harbormaster. + +A build plan tells Harbormaster how to run a build: which commands to run, +services to call, and so on. Builds start with a build plan. + +To create a build plan, navigate to {nav Harbormaster > Manage Build Plans > +New Build Plan}. + +Build plans are composed of "Build Steps". Each step describes an individual +action (like running a command) and the sequence of steps as a whole comprise +the plan. For example, you might want to run one command to build a binary, +then a second command to execute unit tests. Add steps to your build plan +with {nav Add Build Step}. + +Currently, the only useful type of build step is "Make HTTP Request", which you +can use to make a call to an external build system like Jenkins. Today, most +plans should therefor look something like this: + + - Use a "Make HTTP Request" step to tell Jenkins or some other similar + external build system about the code. + - Have the build step "Wait for Message" after the external system is + notified. + - Write custom code on the build server to respond to the request, run a + build, then report the results back to Phabricator by calling the + `harbormaster.sendmessage` Conduit API. + +You'll need to write a nontrivial amount of code to get this working today. +In the future, Harbormaster will become more powerful and have more builtin +support for interacting with build systems. + + +Triggering Builds +================= + +NOTE: Harbormaster can not currently watch a branch (like "build 'master' every +time it changes") or run periodic builds (like "build every hour"). These +capabilities may be added in the future. + +You can run builds manually by using {nav Run Plan Manually} from the detail +screen of a build plan. This will execute a manual build immediately, and can +be used to test that plans work properly. + +To trigger a build automatically, write a Herald rule which executes the "Run +build plans" action. The simplest rule would just use the "Always" condition +and run a single build plan, but you can use more complex conditions to control +which plans run on which code. + +This action is available for commits and revisions, as either can be built +with Harbormaster. This action is only available for "Project" or "Global" +rules. + +Change Handoff +============== + +NOTE: Change handoff is currently very rough. It may improve in the future. + +If you want to build code reviews in an external system, it will need to be +able to construct a working copy with the changes before it can build them. + +There are three ways to do this: + + - **Automatic Staging Areas**: Recommended. This is the simplest and + cleanest way to hand changes to an external build system. + - **Manual Staging Areas**: Recommended if you can not use automatic + staging areas. This is a simple way to hand changes to an external build + system, but not as clean as automatic staging areas. + - **`arc patch`**: Not recommended. This mechanism is the most difficult to + configure and debug, and is not nearly as reliable as handoff via staging + areas. + +With staging areas, `arc` pushes a copy of the local changes somewhere as a +side effect of running `arc diff`. In Git, it pushes changes to a tag like +`phabricator/diff/123` in a designated remote. + +The build system can then interact with this copy using normal VCS commands. +This is simpler to configure, use, troubleshoot and work with than `arc patch`. + +With `arc patch`, the build system downloads patches from Phabricator and +applies them to a local working copy. This is more complex and more error-prone +than staging areas. + +**Automatic Staging Areas**: This is the recommended mechanism for change +handoff. This mechanism has not been built yet, so you can not use it. + +**Manual Staging Areas**: If you can not use automatic staging areas, manual +staging areas are the next best approach. Manual staging areas are only +supported under Git, but work with both hosted and imported repositories. + +Manual staging areas work like this: + + - You configure a staging area for the repository you want to be able to + run builds for. A staging area is just a remote repository that you're + designating for temporary storage. + - Once a staging area is configured, `arc diff` will automatically push a + copy of the changes to the staging area as a side effect when creating + and updating reviews. + - Your build system can pull changes directly from the configured staging + area. + +Configure a staging area by navigating to {nav Diffusion > +(Choose a Repository) > Edit Repository > Edit Staging}. You'll enter the +remote URI of a repository to use as a staging area, and `arc diff` will push +changes to tags like `phabricator/diff/123`. + +There are several ways to select a staging area: + + - You can use the repository itself as its own staging area, but this will + clog it up with a lot of tags that users probably don't care about. This is + simplest to configure but will be disruptive and potentially confusing to + users. + - You can create a single staging repository and have all other + repositories use it as a staging area. This is simple to configure and + won't disrupt or confuse users, but you won't be able to set granular + permissions on the staging repository: all the staged changes in a + repository are visible to anyone who has access to the repository, even if + they came from a repository that the viewer does not have access to. + - You can create a staging repository for each standard repository. This will + give you the most control, but is also the most time consuming to configure. + - You can use a hybrid approach and have several staging repositories, each + of which is used for one or more standard repositories. This will let you + strike a balance between setup costs and granularity. + - Using automatic staging areas avoids all this complexity by using the + repository as its own staging area but hiding the tags from users. + +Once you've configured a staging area, have your build system clone the staging +area repository and do a checkout of the relevant tag in order to perform a +build. + +**`arc patch`**: You can also have the build system pull changes out of +Phabricator as patches and apply them with `arc patch`. This mechanism is the +most complex to configure and debug, and is much less reliable than using +staging areas. It is not recommended. + +To use `arc patch`-based handoff, install PHP on your build server and set up +`arc`. Create a "bot" user for your build system and generate a Conduit token +in {nav Settings > Conduit API Tokens}. Then have your build system clone the +repository and run `arc patch` to apply the changes: + + $ arc patch --conduit-token --diff + +This will usually work, but is more complex and less reliable than using a +staging area. + + +Troubleshooting +=============== + +You can troubleshoot Harbormaster by using `bin/harbormaster` from the command +line. Run it as `bin/harbormaster help` for details. + +In particular, you can run manual builds in the foreground from the CLI to see +more details about what they're doing: + + phabricator/ $ ./bin/harbormaster build D123 --plan 456 --trace + +This may help you understand or debug issues with a build plan. From 580790cd6e35920b2bfd713a0c3f8ad7a1cf9d1e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 6 Aug 2015 09:54:17 -0700 Subject: [PATCH 092/102] Prevent Harbormaster autobuilds from being stopped, paused or restarted Summary: Fixes T8657. "Auto" builds are pushed into Harbormaster by an external system (currently, `arc`) so it does not make sense to stop or resume them: Harbormaster has no way to control the external system. Test Plan: - Tried to restart an autobuild, got an error. - Restarted a normal build. - Did "Restart All" on a buildable, got restarts on non-autoplans and no restarts on autoplans. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8657 Differential Revision: https://secure.phabricator.com/D13812 --- .../storage/build/HarbormasterBuild.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 439780e466..8d1128d009 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -214,6 +214,10 @@ final class HarbormasterBuild extends HarbormasterDAO $this->getBuildStatus() === self::STATUS_BUILDING; } + public function isAutobuild() { + return ($this->getPlanAutoKey() !== null); + } + public function createLog( HarbormasterBuildTarget $build_target, $log_source, @@ -336,16 +340,28 @@ final class HarbormasterBuild extends HarbormasterDAO } public function canRestartBuild() { + if ($this->isAutobuild()) { + return false; + } + return !$this->isRestarting(); } public function canStopBuild() { + if ($this->isAutobuild()) { + return false; + } + return !$this->isComplete() && !$this->isStopped() && !$this->isStopping(); } public function canResumeBuild() { + if ($this->isAutobuild()) { + return false; + } + return $this->isStopped() && !$this->isResuming(); } From 50e084dcda9c33e2cf18c3c4d56eec0457886a3c Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 6 Aug 2015 11:32:17 -0700 Subject: [PATCH 093/102] Add a rough `bin/mail volume` command for showing mail volume Summary: Ref T7013. This might help us understand the problem better. Test Plan: ``` $ ./bin/mail volume +==============+============+ | User | Unfiltered | +==============+============+ | admin | 136 | | dog | 31 | | epriestley | 24 | | ducksey | 18 | | saurus | 8 | | example-list | 7 | | squeakybirdo | 3 | | nnn | 3 | | facebooker | 2 | +==============+============+ Mail sent in the last 30 days. "Unfiltered" is raw volume before preferences were applied. ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T7013 Differential Revision: https://secure.phabricator.com/D13813 --- src/__phutil_library_map__.php | 2 + ...habricatorMailManagementVolumeWorkflow.php | 79 +++++++++++++++++++ .../query/PhabricatorMetaMTAMailQuery.php | 48 ++++++++--- 3 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 src/applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fbdbc739fa..296a4d7703 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2257,6 +2257,7 @@ phutil_register_library_map(array( 'PhabricatorMailManagementSendTestWorkflow' => 'applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php', 'PhabricatorMailManagementShowInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowInboundWorkflow.php', 'PhabricatorMailManagementShowOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php', + 'PhabricatorMailManagementVolumeWorkflow' => 'applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php', 'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php', 'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php', 'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php', @@ -6183,6 +6184,7 @@ phutil_register_library_map(array( 'PhabricatorMailManagementSendTestWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementShowInboundWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow', + 'PhabricatorMailManagementVolumeWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorMailReceiver' => 'Phobject', 'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php new file mode 100644 index 0000000000..c907bf1050 --- /dev/null +++ b/src/applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php @@ -0,0 +1,79 @@ +setName('volume') + ->setSynopsis( + pht('Show how much mail users have received in the last 30 days.')) + ->setExamples( + '**volume**') + ->setArguments( + array( + )); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + $viewer = $this->getViewer(); + + $since = (PhabricatorTime::getNow() - phutil_units('30 days in seconds')); + $until = PhabricatorTime::getNow(); + + $mails = id(new PhabricatorMetaMTAMailQuery()) + ->setViewer($viewer) + ->withDateCreatedBetween($since, $until) + ->execute(); + + $unfiltered = array(); + + foreach ($mails as $mail) { + $unfiltered_actors = mpull($mail->loadAllActors(), 'getPHID'); + foreach ($unfiltered_actors as $phid) { + if (empty($unfiltered[$phid])) { + $unfiltered[$phid] = 0; + } + $unfiltered[$phid]++; + } + } + + arsort($unfiltered); + + $table = id(new PhutilConsoleTable()) + ->setBorders(true) + ->addColumn( + 'user', + array( + 'title' => pht('User'), + )) + ->addColumn( + 'unfiltered', + array( + 'title' => pht('Unfiltered'), + )); + + $handles = $viewer->loadHandles(array_keys($unfiltered)); + $names = mpull(iterator_to_array($handles), 'getName', 'getPHID'); + + foreach ($unfiltered as $phid => $count) { + $table->addRow( + array( + 'user' => idx($names, $phid), + 'unfiltered' => $count, + )); + } + + $table->draw(); + + echo "\n"; + echo pht('Mail sent in the last 30 days.')."\n"; + echo pht( + '"Unfiltered" is raw volume before preferences were applied.')."\n"; + echo "\n"; + + return 0; + } + +} diff --git a/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php index a0965b7133..e0dadb5f13 100644 --- a/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php +++ b/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php @@ -7,6 +7,8 @@ final class PhabricatorMetaMTAMailQuery private $phids; private $actorPHIDs; private $recipientPHIDs; + private $createdMin; + private $createdMax; public function withIDs(array $ids) { $this->ids = $ids; @@ -28,53 +30,73 @@ final class PhabricatorMetaMTAMailQuery return $this; } + public function withDateCreatedBetween($min, $max) { + $this->createdMin = $min; + $this->createdMax = $max; + return $this; + } + protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'mail.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'mail.phid IN (%Ls)', $this->phids); } if ($this->actorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'mail.actorPHID IN (%Ls)', $this->actorPHIDs); } if ($this->recipientPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'recipient.dst IN (%Ls)', $this->recipientPHIDs); } if ($this->actorPHIDs === null && $this->recipientPHIDs === null) { $viewer = $this->getViewer(); - $where[] = qsprintf( - $conn_r, - 'edge.dst = %s OR actorPHID = %s', - $viewer->getPHID(), - $viewer->getPHID()); + if (!$viewer->isOmnipotent()) { + $where[] = qsprintf( + $conn, + 'edge.dst = %s OR actorPHID = %s', + $viewer->getPHID(), + $viewer->getPHID()); + } } - $where[] = $this->buildPagingClause($conn_r); + if ($this->createdMin !== null) { + $where[] = qsprintf( + $conn, + 'mail.dateCreated >= %d', + $this->createdMin); + } - return $this->formatWhereClause($where); + if ($this->createdMax !== null) { + $where[] = qsprintf( + $conn, + 'mail.dateCreated <= %d', + $this->createdMax); + } + + return $where; } protected function buildJoinClause(AphrontDatabaseConnection $conn) { From cd0402dce214402bfef730681d9fbd02d4819d80 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 6 Aug 2015 12:11:58 -0700 Subject: [PATCH 094/102] Remove AphrontTwoColumnView Summary: Never used. Test Plan: grep Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13814 --- resources/celerity/map.php | 2 - src/__phutil_library_map__.php | 4 -- .../PhabricatorTwoColumnUIExample.php | 36 ---------- src/view/layout/AphrontTwoColumnView.php | 66 ------------------- webroot/rsrc/css/aphront/two-column.css | 41 ------------ 5 files changed, 149 deletions(-) delete mode 100644 src/applications/uiexample/examples/PhabricatorTwoColumnUIExample.php delete mode 100644 src/view/layout/AphrontTwoColumnView.php delete mode 100644 webroot/rsrc/css/aphront/two-column.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 17a6048b7d..bd0ef032b1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -28,7 +28,6 @@ return array( 'rsrc/css/aphront/table-view.css' => 'e3632cc9', 'rsrc/css/aphront/tokenizer.css' => '04875312', 'rsrc/css/aphront/tooltip.css' => '7672b60f', - 'rsrc/css/aphront/two-column.css' => '16ab3ad2', 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', 'rsrc/css/aphront/typeahead.css' => '0e403212', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', @@ -500,7 +499,6 @@ return array( 'aphront-table-view-css' => 'e3632cc9', 'aphront-tokenizer-control-css' => '04875312', 'aphront-tooltip-css' => '7672b60f', - 'aphront-two-column-view-css' => '16ab3ad2', 'aphront-typeahead-control-css' => '0e403212', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 296a4d7703..0f00bd7430 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -165,7 +165,6 @@ phutil_register_library_map(array( 'AphrontTableView' => 'view/control/AphrontTableView.php', 'AphrontTagView' => 'view/AphrontTagView.php', 'AphrontTokenizerTemplateView' => 'view/control/AphrontTokenizerTemplateView.php', - 'AphrontTwoColumnView' => 'view/layout/AphrontTwoColumnView.php', 'AphrontTypeaheadTemplateView' => 'view/control/AphrontTypeaheadTemplateView.php', 'AphrontURIMapper' => 'aphront/AphrontURIMapper.php', 'AphrontUnhandledExceptionResponse' => 'aphront/response/AphrontUnhandledExceptionResponse.php', @@ -2986,7 +2985,6 @@ phutil_register_library_map(array( 'PhabricatorTrivialTestCase' => 'infrastructure/testing/__tests__/PhabricatorTrivialTestCase.php', 'PhabricatorTwitchAuthProvider' => 'applications/auth/provider/PhabricatorTwitchAuthProvider.php', 'PhabricatorTwitterAuthProvider' => 'applications/auth/provider/PhabricatorTwitterAuthProvider.php', - 'PhabricatorTwoColumnUIExample' => 'applications/uiexample/examples/PhabricatorTwoColumnUIExample.php', 'PhabricatorTypeaheadApplication' => 'applications/typeahead/application/PhabricatorTypeaheadApplication.php', 'PhabricatorTypeaheadCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php', 'PhabricatorTypeaheadDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php', @@ -3767,7 +3765,6 @@ phutil_register_library_map(array( 'AphrontTableView' => 'AphrontView', 'AphrontTagView' => 'AphrontView', 'AphrontTokenizerTemplateView' => 'AphrontView', - 'AphrontTwoColumnView' => 'AphrontView', 'AphrontTypeaheadTemplateView' => 'AphrontView', 'AphrontURIMapper' => 'Phobject', 'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse', @@ -7048,7 +7045,6 @@ phutil_register_library_map(array( 'PhabricatorTrivialTestCase' => 'PhabricatorTestCase', 'PhabricatorTwitchAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorTwitterAuthProvider' => 'PhabricatorOAuth1AuthProvider', - 'PhabricatorTwoColumnUIExample' => 'PhabricatorUIExample', 'PhabricatorTypeaheadApplication' => 'PhabricatorApplication', 'PhabricatorTypeaheadCompositeDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorTypeaheadDatasource' => 'Phobject', diff --git a/src/applications/uiexample/examples/PhabricatorTwoColumnUIExample.php b/src/applications/uiexample/examples/PhabricatorTwoColumnUIExample.php deleted file mode 100644 index fd9b422a26..0000000000 --- a/src/applications/uiexample/examples/PhabricatorTwoColumnUIExample.php +++ /dev/null @@ -1,36 +0,0 @@ - 'border: 1px solid blue; padding: 20px;', - ), - 'Mary, mary quite contrary.'); - - $side = phutil_tag( - 'div', - array( - 'style' => 'border: 1px solid red; padding: 20px;', - ), - 'How does your garden grow?'); - - - $content = id(new AphrontTwoColumnView()) - ->setMainColumn($main) - ->setSideColumn($side); - - return $content; - } -} diff --git a/src/view/layout/AphrontTwoColumnView.php b/src/view/layout/AphrontTwoColumnView.php deleted file mode 100644 index c29a658ff5..0000000000 --- a/src/view/layout/AphrontTwoColumnView.php +++ /dev/null @@ -1,66 +0,0 @@ -mainColumn = $main; - return $this; - } - - public function setSideColumn($side) { - $this->sideColumn = $side; - return $this; - } - - public function setCentered($centered) { - $this->centered = $centered; - return $this; - } - - public function setNoPadding($padding) { - $this->padding = $padding; - return $this; - } - - public function render() { - require_celerity_resource('aphront-two-column-view-css'); - - $main = phutil_tag( - 'div', - array( - 'class' => 'aphront-main-column', - ), - $this->mainColumn); - - $side = phutil_tag( - 'div', - array( - 'class' => 'aphront-side-column', - ), - $this->sideColumn); - - $classes = array('aphront-two-column'); - if ($this->centered) { - $classes = array('aphront-two-column-centered'); - } - - if ($this->padding) { - $classes[] = 'aphront-two-column-padded'; - } - - return phutil_tag( - 'div', - array( - 'class' => implode(' ', $classes), - ), - array( - $main, - $side, - )); - } -} diff --git a/webroot/rsrc/css/aphront/two-column.css b/webroot/rsrc/css/aphront/two-column.css deleted file mode 100644 index e9a0217e8c..0000000000 --- a/webroot/rsrc/css/aphront/two-column.css +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @provides aphront-two-column-view-css - */ - -.aphront-two-column { - position: relative; -} - -.device-desktop .aphront-two-column.aphront-two-column-padded { - margin: 20px; -} - -.device-desktop .aphront-two-column .aphront-main-column { - margin-right: 300px; -} - -.device-desktop .aphront-two-column .aphront-side-column { - width: 300px; - position: absolute; - top: 0; - right: 0; -} - -.device-desktop .aphront-two-column-centered { - width: 980px; - margin: 0 auto; -} - -.device-desktop .aphront-two-column-centered .aphront-main-column { - float: left; - width: 820px; -} - -.device-desktop .aphront-two-column-centered .aphront-side-column { - width: 160px; - float: right; -} - -.device-phone .aphront-two-column.aphront-two-column-padded { - margin: 10px; -} From 900ce2fd378447f678f5cd44222dae0be7c898a0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 6 Aug 2015 12:22:00 -0700 Subject: [PATCH 095/102] Fix line-height in property-list-view Summary: This is causing mis-alignment and is no longer needed. Test Plan: Check alignment in Photoshop Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13815 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-property-list-view.css | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bd0ef032b1..da014bccde 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '2bb3ba73', + 'core.pkg.css' => '33799ec4', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '9451634c', @@ -141,7 +141,7 @@ return array( 'rsrc/css/phui/phui-object-item-list-view.css' => '36ce366c', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', - 'rsrc/css/phui/phui-property-list-view.css' => 'aeb09581', + 'rsrc/css/phui/phui-property-list-view.css' => '15bbe0b0', 'rsrc/css/phui/phui-remarkup-preview.css' => '867f85b3', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '888cedb8', @@ -795,7 +795,7 @@ return array( 'phui-object-item-list-view-css' => '36ce366c', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', - 'phui-property-list-view-css' => 'aeb09581', + 'phui-property-list-view-css' => '15bbe0b0', 'phui-remarkup-preview-css' => '867f85b3', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '888cedb8', diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css index b4acc54994..71a096e69e 100644 --- a/webroot/rsrc/css/phui/phui-property-list-view.css +++ b/webroot/rsrc/css/phui/phui-property-list-view.css @@ -68,7 +68,6 @@ .phui-property-list-value { color: {$darkgreytext}; - line-height: 17px; } .device-desktop .phui-property-list-value { From 31cfdef0f782526bbc9abf7ef1d891b080128f9b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 6 Aug 2015 17:30:47 -0700 Subject: [PATCH 096/102] Reduce colors in ApplicationTransactions for subscriptions Summary: Remove previously added colors. Test Plan: Load page, see less color (task) Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13816 --- .../storage/PhabricatorApplicationTransaction.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index d6d961c780..c7be284ecd 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -428,21 +428,6 @@ abstract class PhabricatorApplicationTransaction return 'red'; } break; - case PhabricatorTransactions::TYPE_SUBSCRIBERS: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - if ($add && $rem) { - return 'green'; - } else if ($add) { - return 'green'; - } else if ($rem) { - return 'black'; - } else { - return null; - } - break; } return null; } From 2c2eeed65d68043f9b9469d2e135599e9a749688 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 6 Aug 2015 18:38:14 -0700 Subject: [PATCH 097/102] Fix Paste transactions Summary: We're using the wrong constants here, bad copy and paste job. Test Plan: Archive a Paste, check transactions. Reviewers: Subscribers: --- .../paste/storage/PhabricatorPasteTransaction.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/paste/storage/PhabricatorPasteTransaction.php b/src/applications/paste/storage/PhabricatorPasteTransaction.php index 6372f38a2b..54b712ebee 100644 --- a/src/applications/paste/storage/PhabricatorPasteTransaction.php +++ b/src/applications/paste/storage/PhabricatorPasteTransaction.php @@ -137,12 +137,12 @@ final class PhabricatorPasteTransaction break; case self::TYPE_STATUS: switch ($new) { - case self::STATUS_OPEN: + case PhabricatorPaste::STATUS_ACTIVE: return pht( '%s activated %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); - case self::STATUS_CLOSED: + case PhabricatorPaste::STATUS_ARCHIVED: return pht( '%s archived %s.', $this->renderHandleLink($author_phid), From 58b622852f37de95b9e92d20775ed2757928afde Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 6 Aug 2015 18:44:54 -0700 Subject: [PATCH 098/102] Fix Up Paste transactions Summary: We're using the wrong constants here, bad copy and paste job. Also add colors, icons. Test Plan: Archive a Paste, check transactions. Reviewers: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13817 --- .../storage/PhabricatorPasteTransaction.php | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/applications/paste/storage/PhabricatorPasteTransaction.php b/src/applications/paste/storage/PhabricatorPasteTransaction.php index 54b712ebee..d6b962298b 100644 --- a/src/applications/paste/storage/PhabricatorPasteTransaction.php +++ b/src/applications/paste/storage/PhabricatorPasteTransaction.php @@ -55,6 +55,15 @@ final class PhabricatorPasteTransaction case self::TYPE_LANGUAGE: return 'fa-pencil'; break; + case self::TYPE_STATUS: + $new = $this->getNewValue(); + switch ($new) { + case PhabricatorPaste::STATUS_ACTIVE: + return 'fa-check'; + case PhabricatorPaste::STATUS_ARCHIVED: + return 'fa-ban'; + } + break; } return parent::getIcon(); } @@ -91,9 +100,16 @@ final class PhabricatorPasteTransaction $this->renderHandleLink($author_phid)); break; case self::TYPE_STATUS: - return pht( - "%s updated the paste's status.", - $this->renderHandleLink($author_phid)); + switch ($new) { + case PhabricatorPaste::STATUS_ACTIVE: + return pht( + '%s activated this paste.', + $this->renderHandleLink($author_phid)); + case PhabricatorPaste::STATUS_ARCHIVED: + return pht( + '%s archived this paste.', + $this->renderHandleLink($author_phid)); + } break; } @@ -161,6 +177,14 @@ final class PhabricatorPasteTransaction switch ($this->getTransactionType()) { case self::TYPE_CONTENT: return PhabricatorTransactions::COLOR_GREEN; + case self::TYPE_STATUS: + switch ($new) { + case PhabricatorPaste::STATUS_ACTIVE: + return 'green'; + case PhabricatorPaste::STATUS_ARCHIVED: + return 'indigo'; + } + break; } return parent::getColor(); From f7b16c3bf9bd8f2fab37354f13fe6cc484b30904 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 7 Aug 2015 08:14:20 -0700 Subject: [PATCH 099/102] SetUser on Timeline UIExample Summary: Fixes T9103, sets the user Test Plan: visit uiexamples timeline Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9103 Differential Revision: https://secure.phabricator.com/D13823 --- src/applications/uiexample/examples/PHUITimelineExample.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/uiexample/examples/PHUITimelineExample.php b/src/applications/uiexample/examples/PHUITimelineExample.php index d3df3ded71..661d2e6899 100644 --- a/src/applications/uiexample/examples/PHUITimelineExample.php +++ b/src/applications/uiexample/examples/PHUITimelineExample.php @@ -196,6 +196,7 @@ final class PHUITimelineExample extends PhabricatorUIExample { } $timeline = id(new PHUITimelineView()); + $timeline->setUser($user); foreach ($events as $event) { $timeline->addEvent($event); } From af71eba5cc8ee2ad7ee63541ce0c830c6be8b949 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 7 Aug 2015 08:17:33 -0700 Subject: [PATCH 100/102] Mark UIExamples as a prototype Summary: Ref T9103. This application is only useful for developing Phabricator, and in general is not kept "production ready". Mark it as a prototype. Test Plan: visit /applications/, see it marked as prototype. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9103 Differential Revision: https://secure.phabricator.com/D13822 --- .../application/PhabricatorUIExamplesApplication.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/uiexample/application/PhabricatorUIExamplesApplication.php b/src/applications/uiexample/application/PhabricatorUIExamplesApplication.php index c347a101e9..3b3d38ef14 100644 --- a/src/applications/uiexample/application/PhabricatorUIExamplesApplication.php +++ b/src/applications/uiexample/application/PhabricatorUIExamplesApplication.php @@ -30,6 +30,10 @@ final class PhabricatorUIExamplesApplication extends PhabricatorApplication { return self::GROUP_DEVELOPER; } + public function isPrototype() { + return true; + } + public function getApplicationOrder() { return 0.110; } From 79f2e81f38bed6f2cf0b938ccbd0383a5bc3c9b2 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Sat, 8 Aug 2015 10:19:44 +1000 Subject: [PATCH 101/102] Various linter fixes Summary: Self-explanatory. Test Plan: Eyeball it. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13808 --- scripts/symbols/import_repository_symbols.php | 2 +- src/applications/audit/editor/PhabricatorAuditEditor.php | 2 +- src/applications/diffusion/query/DiffusionCommitQuery.php | 2 +- .../maniphest/conduit/ManiphestUpdateConduitAPIMethod.php | 2 +- .../phame/controller/blog/PhameBlogListController.php | 2 +- .../ponder/controller/PonderQuestionEditController.php | 2 +- .../markup/rule/PhabricatorObjectRemarkupRule.php | 2 +- src/view/phui/calendar/PHUICalendarMonthView.php | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/symbols/import_repository_symbols.php b/scripts/symbols/import_repository_symbols.php index 94cf601a34..2ac2b3c8a9 100755 --- a/scripts/symbols/import_repository_symbols.php +++ b/scripts/symbols/import_repository_symbols.php @@ -203,7 +203,7 @@ foreach ($input as $key => $line) { } } - if (count ($symbols) >= $args->getArg('max-transaction')) { + if (count($symbols) >= $args->getArg('max-transaction')) { try { echo pht( "Committing %s symbols...\n", diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index e9dd384812..7c0f3d2e34 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -170,7 +170,7 @@ final class PhabricatorAuditEditor $audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED; $audit_reason = $this->getAuditReasons($phid); } - $requests[] = id (new PhabricatorRepositoryAuditRequest()) + $requests[] = id(new PhabricatorRepositoryAuditRequest()) ->setCommitPHID($object->getPHID()) ->setAuditorPHID($phid) ->setAuditStatus($audit_requested) diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index 7098e7416f..45f8b063af 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -300,7 +300,7 @@ final class DiffusionCommitQuery $where = array(); if ($this->repositoryPHIDs !== null) { - $map_repositories = id (new PhabricatorRepositoryQuery()) + $map_repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withPHIDs($this->repositoryPHIDs) ->execute(); diff --git a/src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php index ad659af259..d4adee9570 100644 --- a/src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php @@ -38,7 +38,7 @@ final class ManiphestUpdateConduitAPIMethod extends ManiphestConduitAPIMethod { 'phid')); } - $query = id (new ManiphestTaskQuery()) + $query = id(new ManiphestTaskQuery()) ->setViewer($request->getUser()) ->needSubscriberPHIDs(true) ->needProjectPHIDs(true); diff --git a/src/applications/phame/controller/blog/PhameBlogListController.php b/src/applications/phame/controller/blog/PhameBlogListController.php index 6240c129dd..9d0253d89c 100644 --- a/src/applications/phame/controller/blog/PhameBlogListController.php +++ b/src/applications/phame/controller/blog/PhameBlogListController.php @@ -38,7 +38,7 @@ final class PhameBlogListController extends PhameController { $blog_list = $this->renderBlogList($blogs, $user, $nodata); $blog_list->setPager($pager); - $box = id (new PHUIObjectBoxView()) + $box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setObjectList($blog_list); diff --git a/src/applications/ponder/controller/PonderQuestionEditController.php b/src/applications/ponder/controller/PonderQuestionEditController.php index 23e15625a2..cf8b5f9de1 100644 --- a/src/applications/ponder/controller/PonderQuestionEditController.php +++ b/src/applications/ponder/controller/PonderQuestionEditController.php @@ -139,7 +139,7 @@ final class PonderQuestionEditController extends PonderController { ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())); - $form ->appendChild( + $form->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($this->getApplicationURI()) ->setValue(pht('Ask Away!'))); diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php index f5c936539e..896c52276f 100644 --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -53,7 +53,7 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { return $uri; } - protected function renderObjectRefForAnyMedia ( + protected function renderObjectRefForAnyMedia( $object, PhabricatorObjectHandle $handle, $anchor, diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 06d15bbcc9..0308f542e5 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -253,7 +253,7 @@ final class PHUICalendarMonthView extends AphrontView { $today_class .= ' last-weekday'; } - $today_slot = phutil_tag ( + $today_slot = phutil_tag( 'div', array( 'class' => $today_class, From da1e711abbf5296862256dfe120157bafc59bdd9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 8 Aug 2015 07:43:08 -0700 Subject: [PATCH 102/102] Hide skipped tests by default in Differential build result summary view Summary: Ref T8096. Currently, we hide passing tests by default (they aren't interesting) but should also hide skipped tests by default (they aren't interesting either). Test Plan: {F694485} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8096 Differential Revision: https://secure.phabricator.com/D13821 --- .../differential/customfield/DifferentialUnitField.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/customfield/DifferentialUnitField.php b/src/applications/differential/customfield/DifferentialUnitField.php index daff8cf0d5..b5de9fce53 100644 --- a/src/applications/differential/customfield/DifferentialUnitField.php +++ b/src/applications/differential/customfield/DifferentialUnitField.php @@ -56,8 +56,14 @@ final class DifferentialUnitField protected function newHarbormasterMessageView(array $messages) { foreach ($messages as $key => $message) { - if ($message->getResult() == ArcanistUnitTestResult::RESULT_PASS) { - unset($messages[$key]); + switch ($message->getResult()) { + case ArcanistUnitTestResult::RESULT_PASS: + case ArcanistUnitTestResult::RESULT_SKIP: + // Don't show "Pass" or "Skip" in the UI since they aren't very + // interesting. The user can click through to the full results if + // they want details. + unset($messages[$key]); + break; } }