diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d15c396bc9..56dc1941da 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,14 +7,14 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'a3c49f47', + 'core.pkg.css' => '85697954', 'core.pkg.js' => 'cbdbd552', 'darkconsole.pkg.js' => 'df001cab', - 'differential.pkg.css' => '0f9c3082', + 'differential.pkg.css' => '8af45893', 'differential.pkg.js' => '73337d1d', 'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.js' => 'bfc0737b', - 'maniphest.pkg.css' => 'f5d89daf', + 'maniphest.pkg.css' => 'e34dfbec', 'maniphest.pkg.js' => 'df4aa49f', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/context-bar.css' => '1c3b0529', @@ -24,7 +24,7 @@ return array( 'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d', 'rsrc/css/aphront/list-filter-view.css' => '2ae43867', 'rsrc/css/aphront/multi-column.css' => '1b95ab2e', - 'rsrc/css/aphront/notification.css' => 'ef2c9b34', + 'rsrc/css/aphront/notification.css' => '9c279160', 'rsrc/css/aphront/pager-view.css' => '2e3539af', 'rsrc/css/aphront/panel-view.css' => '5846dfa2', 'rsrc/css/aphront/phabricator-nav-view.css' => '9283c2df', @@ -60,7 +60,7 @@ return array( 'rsrc/css/application/differential/revision-comment.css' => '48186045', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', - 'rsrc/css/application/differential/table-of-contents.css' => '6bf8e1d2', + 'rsrc/css/application/differential/table-of-contents.css' => '63f3ef4a', 'rsrc/css/application/diffusion/diffusion-icons.css' => '9c5828da', 'rsrc/css/application/diffusion/diffusion-source.css' => '66fdf661', 'rsrc/css/application/feed/feed.css' => '7bfc6f12', @@ -72,15 +72,15 @@ return array( 'rsrc/css/application/maniphest/batch-editor.css' => '8f380ebc', 'rsrc/css/application/maniphest/report.css' => '6fc16517', 'rsrc/css/application/maniphest/task-edit.css' => '8e23031b', - 'rsrc/css/application/maniphest/task-summary.css' => '00c3be7a', + 'rsrc/css/application/maniphest/task-summary.css' => '13ed8360', 'rsrc/css/application/objectselector/object-selector.css' => '029a133d', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => 'aa1767d1', - 'rsrc/css/application/people/people-profile.css' => '5402f7a5', + 'rsrc/css/application/people/people-profile.css' => '0d5f6498', 'rsrc/css/application/phame/phame.css' => '19ecc703', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', - 'rsrc/css/application/pholio/pholio.css' => '47dffb9c', + 'rsrc/css/application/pholio/pholio.css' => '95174bdd', 'rsrc/css/application/phortune/phortune-credit-card-form.css' => 'b25b4beb', 'rsrc/css/application/phortune/phortune.css' => '9149f103', 'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad', @@ -104,12 +104,12 @@ return array( 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '40151074', 'rsrc/css/core/remarkup.css' => '45313445', - 'rsrc/css/core/syntax.css' => '863f3cd8', + 'rsrc/css/core/syntax.css' => '56c1ba38', 'rsrc/css/core/z-index.css' => '44e1d311', 'rsrc/css/diviner/diviner-shared.css' => '38813222', 'rsrc/css/font/font-awesome.css' => '73d075c3', 'rsrc/css/font/font-source-sans-pro.css' => '91d53463', - 'rsrc/css/font/phui-font-icon-base.css' => 'eb84f033', + 'rsrc/css/font/phui-font-icon-base.css' => '3dad2ae3', 'rsrc/css/layout/phabricator-action-header-view.css' => '83e2cc86', 'rsrc/css/layout/phabricator-crumbs-view.css' => 'a49339de', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', @@ -119,13 +119,13 @@ return array( 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'de035c8a', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2', - 'rsrc/css/phui/calendar/phui-calendar.css' => '5e1ad989', + 'rsrc/css/phui/calendar/phui-calendar.css' => '8675968e', 'rsrc/css/phui/phui-action-list.css' => '9ee9910a', 'rsrc/css/phui/phui-box.css' => '7b3a2eed', 'rsrc/css/phui/phui-button.css' => 'c7412aa1', 'rsrc/css/phui/phui-document.css' => 'a5615198', 'rsrc/css/phui/phui-feed-story.css' => '55dc7732', - 'rsrc/css/phui/phui-fontkit.css' => 'fff25cfa', + 'rsrc/css/phui/phui-fontkit.css' => '9c3d2dce', 'rsrc/css/phui/phui-form-view.css' => '1ff38f33', 'rsrc/css/phui/phui-form.css' => 'b78ec020', 'rsrc/css/phui/phui-header-view.css' => '39594ac0', @@ -134,15 +134,15 @@ return array( 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-list.css' => '43ed2d93', 'rsrc/css/phui/phui-object-box.css' => 'e9f7e938', - 'rsrc/css/phui/phui-object-item-list-view.css' => 'e1e6425f', + 'rsrc/css/phui/phui-object-item-list-view.css' => '5053dee8', 'rsrc/css/phui/phui-pinboard-view.css' => '3dd4a269', 'rsrc/css/phui/phui-property-list-view.css' => '86f9df88', 'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b', 'rsrc/css/phui/phui-spacing.css' => '042804d6', - 'rsrc/css/phui/phui-status.css' => '2f562399', - 'rsrc/css/phui/phui-tag-view.css' => 'c4158073', - 'rsrc/css/phui/phui-text.css' => '23e9b4b7', - 'rsrc/css/phui/phui-timeline-view.css' => 'bbd990d0', + 'rsrc/css/phui/phui-status.css' => '888cedb8', + 'rsrc/css/phui/phui-tag-view.css' => 'b0c282e0', + 'rsrc/css/phui/phui-text.css' => 'cf019f54', + 'rsrc/css/phui/phui-timeline-view.css' => '8c6fefe7', 'rsrc/css/phui/phui-workboard-view.css' => '2bf82d00', 'rsrc/css/phui/phui-workpanel-view.css' => '198c7e6c', 'rsrc/css/sprite-apps-large.css' => '20ec0cc0', @@ -150,7 +150,7 @@ return array( 'rsrc/css/sprite-conpherence.css' => '3b4a0487', 'rsrc/css/sprite-docs.css' => '5f65d0da', 'rsrc/css/sprite-gradient.css' => '4bdb98a7', - 'rsrc/css/sprite-login.css' => 'cf08ac44', + 'rsrc/css/sprite-login.css' => 'a355d921', 'rsrc/css/sprite-main-header.css' => '92720ee2', 'rsrc/css/sprite-menu.css' => '8fead76a', 'rsrc/css/sprite-payments.css' => 'cc085d44', @@ -323,8 +323,8 @@ return array( 'rsrc/image/sprite-docs-X2.png' => '6dc1adad', 'rsrc/image/sprite-docs.png' => '4636297f', 'rsrc/image/sprite-gradient.png' => 'ec15a417', - 'rsrc/image/sprite-login-X2.png' => '46f95dcc', - 'rsrc/image/sprite-login.png' => '4e0e66ee', + 'rsrc/image/sprite-login-X2.png' => '5ae6de3a', + 'rsrc/image/sprite-login.png' => '07f2c67c', 'rsrc/image/sprite-main-header.png' => '83521873', 'rsrc/image/sprite-menu-X2.png' => '39d78f97', 'rsrc/image/sprite-menu.png' => '259dab45', @@ -524,7 +524,7 @@ return array( 'differential-revision-comment-css' => '48186045', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', - 'differential-table-of-contents-css' => '6bf8e1d2', + 'differential-table-of-contents-css' => '63f3ef4a', 'diffusion-icons-css' => '9c5828da', 'diffusion-source-css' => '66fdf661', 'diviner-shared-css' => '38813222', @@ -692,13 +692,13 @@ return array( 'maniphest-batch-editor' => '8f380ebc', 'maniphest-report-css' => '6fc16517', 'maniphest-task-edit-css' => '8e23031b', - 'maniphest-task-summary-css' => '00c3be7a', + 'maniphest-task-summary-css' => '13ed8360', 'multirow-row-manager' => '41e47dea', 'owners-path-editor' => 'aa1733d0', 'owners-path-editor-css' => '2f00933b', 'paste-css' => 'aa1767d1', 'path-typeahead' => 'f7fc67ec', - 'people-profile-css' => '5402f7a5', + 'people-profile-css' => '0d5f6498', 'phabricator-action-list-view-css' => '9ee9910a', 'phabricator-application-launch-view-css' => '5d71008f', 'phabricator-busy' => '6453c869', @@ -722,7 +722,7 @@ return array( 'phabricator-main-menu-view' => 'a1c976b2', 'phabricator-nav-view-css' => '9283c2df', 'phabricator-notification' => '0c6946e7', - 'phabricator-notification-css' => 'ef2c9b34', + 'phabricator-notification-css' => '9c279160', 'phabricator-notification-menu-css' => '6aa0a74b', 'phabricator-object-selector-css' => '029a133d', 'phabricator-phtize' => 'd254d646', @@ -753,7 +753,7 @@ return array( 'phabricator-welcome-page' => 'c370f13b', 'phabricator-zindex-css' => '44e1d311', 'phame-css' => '19ecc703', - 'pholio-css' => '47dffb9c', + 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', 'phortune-credit-card-form' => '2290aeef', @@ -764,14 +764,14 @@ return array( 'phui-action-header-view-css' => '83e2cc86', 'phui-box-css' => '7b3a2eed', 'phui-button-css' => 'c7412aa1', - 'phui-calendar-css' => '5e1ad989', + 'phui-calendar-css' => '8675968e', 'phui-calendar-day-css' => 'de035c8a', 'phui-calendar-list-css' => 'c1d0ca59', 'phui-calendar-month-css' => 'a92e47d2', 'phui-document-view-css' => 'a5615198', 'phui-feed-story-css' => '55dc7732', - 'phui-font-icon-base-css' => 'eb84f033', - 'phui-fontkit-css' => 'fff25cfa', + 'phui-font-icon-base-css' => '3dad2ae3', + 'phui-fontkit-css' => '9c3d2dce', 'phui-form-css' => 'b78ec020', 'phui-form-view-css' => '1ff38f33', 'phui-header-view-css' => '39594ac0', @@ -780,15 +780,15 @@ return array( 'phui-info-panel-css' => '27ea50a1', 'phui-list-view-css' => '43ed2d93', 'phui-object-box-css' => 'e9f7e938', - 'phui-object-item-list-view-css' => 'e1e6425f', + 'phui-object-item-list-view-css' => '5053dee8', 'phui-pinboard-view-css' => '3dd4a269', 'phui-property-list-view-css' => '86f9df88', 'phui-remarkup-preview-css' => '19ad512b', 'phui-spacing-css' => '042804d6', - 'phui-status-list-view-css' => '2f562399', - 'phui-tag-view-css' => 'c4158073', - 'phui-text-css' => '23e9b4b7', - 'phui-timeline-view-css' => 'bbd990d0', + 'phui-status-list-view-css' => '888cedb8', + 'phui-tag-view-css' => 'b0c282e0', + 'phui-text-css' => 'cf019f54', + 'phui-timeline-view-css' => '8c6fefe7', 'phui-workboard-view-css' => '2bf82d00', 'phui-workpanel-view-css' => '198c7e6c', 'phuix-action-list-view' => 'b5c256b8', @@ -815,13 +815,13 @@ return array( 'sprite-conpherence-css' => '3b4a0487', 'sprite-docs-css' => '5f65d0da', 'sprite-gradient-css' => '4bdb98a7', - 'sprite-login-css' => 'cf08ac44', + 'sprite-login-css' => 'a355d921', 'sprite-main-header-css' => '92720ee2', 'sprite-menu-css' => '8fead76a', 'sprite-payments-css' => 'cc085d44', 'sprite-projects-css' => '7578fa56', 'sprite-tokens-css' => '1706b943', - 'syntax-highlighting-css' => '863f3cd8', + 'syntax-highlighting-css' => '56c1ba38', 'tokens-css' => '3d0f239e', ), 'requires' => array( diff --git a/resources/sprite/login_1x/MediaWiki.png b/resources/sprite/login_1x/MediaWiki.png index e6994b22ce..ee9c853ed7 100644 Binary files a/resources/sprite/login_1x/MediaWiki.png and b/resources/sprite/login_1x/MediaWiki.png differ diff --git a/resources/sprite/login_2x/MediaWiki.png b/resources/sprite/login_2x/MediaWiki.png index 23d6662a53..7f067251e4 100644 Binary files a/resources/sprite/login_2x/MediaWiki.png and b/resources/sprite/login_2x/MediaWiki.png differ diff --git a/resources/sprite/manifest/login.json b/resources/sprite/manifest/login.json index 5182e52b0f..37b9511ba4 100644 --- a/resources/sprite/manifest/login.json +++ b/resources/sprite/manifest/login.json @@ -74,7 +74,7 @@ "login-MediaWiki" : { "name" : "login-MediaWiki", "rule" : ".login-MediaWiki", - "hash" : "6e9e75fdad545f415d78719bd2dee3ec" + "hash" : "51c7e71ab0e44126e72d8a130779b02c" }, "login-Openid" : { "name" : "login-Openid", diff --git a/resources/sql/autopatches/20141011.phortunemerchedit.sql b/resources/sql/autopatches/20141011.phortunemerchedit.sql new file mode 100644 index 0000000000..ee1efcd20c --- /dev/null +++ b/resources/sql/autopatches/20141011.phortunemerchedit.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phortune.phortune_merchant + DROP editPolicy; diff --git a/resources/sql/autopatches/20141012.phortunecartxaction.sql b/resources/sql/autopatches/20141012.phortunecartxaction.sql new file mode 100644 index 0000000000..9789377b37 --- /dev/null +++ b/resources/sql/autopatches/20141012.phortunecartxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_phortune.phortune_carttransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, + newValue LONGTEXT COLLATE utf8_bin NOT NULL, + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, + metadata LONGTEXT COLLATE utf8_bin NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141013.phortunecartkey.sql b/resources/sql/autopatches/20141013.phortunecartkey.sql new file mode 100644 index 0000000000..73c2425050 --- /dev/null +++ b/resources/sql/autopatches/20141013.phortunecartkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phortune.phortune_cart + ADD mailKey BINARY(20) NOT NULL; diff --git a/resources/sql/autopatches/20141016.almanac.device.sql b/resources/sql/autopatches/20141016.almanac.device.sql new file mode 100644 index 0000000000..d309524fa0 --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.device.sql @@ -0,0 +1,22 @@ +TRUNCATE TABLE {$NAMESPACE}_almanac.almanac_device; + +ALTER TABLE {$NAMESPACE}_almanac.almanac_device + CHANGE name name VARCHAR(128) NOT NULL COLLATE utf8_bin; + +ALTER TABLE {$NAMESPACE}_almanac.almanac_device + ADD nameIndex BINARY(12) NOT NULL; + +ALTER TABLE {$NAMESPACE}_almanac.almanac_device + ADD mailKey BINARY(20) NOT NULL; + +ALTER TABLE {$NAMESPACE}_almanac.almanac_device + ADD UNIQUE KEY `key_name` (nameIndex); + +ALTER TABLE {$NAMESPACE}_almanac.almanac_device + ADD KEY `key_nametext` (name); + +ALTER TABLE {$NAMESPACE}_almanac.almanac_device + ADD viewPolicy VARBINARY(64) NOT NULL; + +ALTER TABLE {$NAMESPACE}_almanac.almanac_device + ADD editPolicy VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20141016.almanac.dxaction.sql b/resources/sql/autopatches/20141016.almanac.dxaction.sql new file mode 100644 index 0000000000..5600ec6276 --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.dxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_devicetransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, + newValue LONGTEXT COLLATE utf8_bin NOT NULL, + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, + metadata LONGTEXT COLLATE utf8_bin NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141016.almanac.interface.sql b/resources/sql/autopatches/20141016.almanac.interface.sql new file mode 100644 index 0000000000..a23450e1bd --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.interface.sql @@ -0,0 +1,13 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_interface ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + devicePHID VARBINARY(64) NOT NULL, + networkPHID VARBINARY(64) NOT NULL, + address VARCHAR(128) NOT NULL COLLATE utf8_bin, + port INT UNSIGNED NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_location` (networkPHID, address, port), + KEY `key_device` (devicePHID) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141016.almanac.network.sql b/resources/sql/autopatches/20141016.almanac.network.sql new file mode 100644 index 0000000000..d35ab3ea36 --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.network.sql @@ -0,0 +1,11 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_network ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(128) NOT NULL COLLATE utf8_bin, + mailKey BINARY(20) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141016.almanac.nxaction.sql b/resources/sql/autopatches/20141016.almanac.nxaction.sql new file mode 100644 index 0000000000..60a2679cf6 --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.nxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_networktransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, + newValue LONGTEXT COLLATE utf8_bin NOT NULL, + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, + metadata LONGTEXT COLLATE utf8_bin NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141016.almanac.service.sql b/resources/sql/autopatches/20141016.almanac.service.sql new file mode 100644 index 0000000000..7891dff409 --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.service.sql @@ -0,0 +1,14 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_service ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(128) NOT NULL COLLATE utf8_bin, + nameIndex BINARY(12) NOT NULL, + mailKey BINARY(20) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_name` (nameIndex), + KEY `key_nametext` (name) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141016.almanac.sxaction.sql b/resources/sql/autopatches/20141016.almanac.sxaction.sql new file mode 100644 index 0000000000..ea154f8b86 --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.sxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_servicetransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, + newValue LONGTEXT COLLATE utf8_bin NOT NULL, + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, + metadata LONGTEXT COLLATE utf8_bin NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141017.almanac.binding.sql b/resources/sql/autopatches/20141017.almanac.binding.sql new file mode 100644 index 0000000000..da02069b43 --- /dev/null +++ b/resources/sql/autopatches/20141017.almanac.binding.sql @@ -0,0 +1,14 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_binding ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + servicePHID VARBINARY(64) NOT NULL, + devicePHID VARBINARY(64) NOT NULL, + interfacePHID VARBINARY(64) NOT NULL, + mailKey BINARY(20) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_service` (servicePHID, interfacePHID), + KEY `key_device` (devicePHID), + KEY `key_interface` (interfacePHID) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141017.almanac.bxaction.sql b/resources/sql/autopatches/20141017.almanac.bxaction.sql new file mode 100644 index 0000000000..12cd214e4a --- /dev/null +++ b/resources/sql/autopatches/20141017.almanac.bxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_bindingtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, + newValue LONGTEXT COLLATE utf8_bin NOT NULL, + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, + metadata LONGTEXT COLLATE utf8_bin NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141025.phriction.1.xaction.sql b/resources/sql/autopatches/20141025.phriction.1.xaction.sql new file mode 100644 index 0000000000..c2527cf1bc --- /dev/null +++ b/resources/sql/autopatches/20141025.phriction.1.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_transaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, + newValue LONGTEXT COLLATE utf8_bin NOT NULL, + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, + metadata LONGTEXT COLLATE utf8_bin NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141025.phriction.2.xaction.sql b/resources/sql/autopatches/20141025.phriction.2.xaction.sql new file mode 100644 index 0000000000..ddbb4c5da9 --- /dev/null +++ b/resources/sql/autopatches/20141025.phriction.2.xaction.sql @@ -0,0 +1,16 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_transaction_comment ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + transactionPHID VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, + authorPHID VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + viewPolicy VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + editPolicy VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + commentVersion INT UNSIGNED NOT NULL, + content LONGTEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + contentSource LONGTEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + isDeleted TINYINT(1) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 diff --git a/resources/sql/autopatches/20141025.phriction.mailkey.sql b/resources/sql/autopatches/20141025.phriction.mailkey.sql new file mode 100644 index 0000000000..7f0bd4d1f2 --- /dev/null +++ b/resources/sql/autopatches/20141025.phriction.mailkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_document + ADD mailKey VARCHAR(20) NOT NULL COLLATE utf8_bin; diff --git a/resources/sql/patches/000.project.sql b/resources/sql/patches/000.project.sql index 62221ce871..794eb2737f 100644 --- a/resources/sql/patches/000.project.sql +++ b/resources/sql/patches/000.project.sql @@ -1,6 +1,6 @@ create table {$NAMESPACE}_project.project ( id int unsigned not null auto_increment primary key, - name varchar(255) not null, + name varchar(255) COLLATE `binary` not null, unique key (name), phid varchar(64) binary not null, authorPHID varchar(64) binary not null, diff --git a/resources/sql/patches/002.oauth.sql b/resources/sql/patches/002.oauth.sql index 4f68261188..24cce576a0 100644 --- a/resources/sql/patches/002.oauth.sql +++ b/resources/sql/patches/002.oauth.sql @@ -1,8 +1,8 @@ create table {$NAMESPACE}_user.user_oauthinfo ( id int unsigned not null auto_increment primary key, userID int unsigned not null, - oauthProvider varchar(255) not null, - oauthUID varchar(255) not null, + oauthProvider varchar(255) COLLATE `binary` not null, + oauthUID varchar(255) COLLATE `binary` not null, unique key (userID, oauthProvider), unique key (oauthProvider, oauthUID), dateCreated int unsigned not null, diff --git a/resources/sql/patches/004.daemonrepos.sql b/resources/sql/patches/004.daemonrepos.sql index e58aac7233..9e46210ca3 100644 --- a/resources/sql/patches/004.daemonrepos.sql +++ b/resources/sql/patches/004.daemonrepos.sql @@ -23,6 +23,6 @@ create table {$NAMESPACE}_timeline.timeline_eventdata ( ); create table {$NAMESPACE}_timeline.timeline_cursor ( - name varchar(255) not null primary key, + name varchar(255) COLLATE `binary` not null primary key, position int unsigned not null ); diff --git a/resources/sql/patches/010.herald.sql b/resources/sql/patches/010.herald.sql index 57f86acc03..1a28ce262e 100644 --- a/resources/sql/patches/010.herald.sql +++ b/resources/sql/patches/010.herald.sql @@ -7,7 +7,7 @@ CREATE TABLE {$NAMESPACE}_herald.herald_action ( CREATE TABLE {$NAMESPACE}_herald.herald_rule ( id int unsigned not null auto_increment primary key, - name varchar(255) not null, + name varchar(255) COLLATE `binary` not null, authorPHID varchar(64) binary not null, contentType varchar(255) not null, mustMatchAll bool not null, diff --git a/resources/sql/patches/011.badcommit.sql b/resources/sql/patches/011.badcommit.sql index 58dbc45993..a9c0853126 100644 --- a/resources/sql/patches/011.badcommit.sql +++ b/resources/sql/patches/011.badcommit.sql @@ -1,4 +1,4 @@ CREATE TABLE {$NAMESPACE}_repository.repository_badcommit ( - fullCommitName varchar(255) binary not null primary key, + fullCommitName varchar(255) COLLATE `binary` not null primary key, description longblob not null ); diff --git a/resources/sql/patches/018.owners.sql b/resources/sql/patches/018.owners.sql index c30eb010c5..de7dde64d0 100644 --- a/resources/sql/patches/018.owners.sql +++ b/resources/sql/patches/018.owners.sql @@ -2,7 +2,7 @@ CREATE TABLE {$NAMESPACE}_owners.owners_package ( id int unsigned not null auto_increment primary key, phid varchar(64) binary not null, unique key(phid), - name varchar(255) not null, + name varchar(255) COLLATE `binary` not null, unique key(name), description text not null, primaryOwnerPHID varchar(64) binary diff --git a/resources/sql/patches/019.arcprojects.sql b/resources/sql/patches/019.arcprojects.sql index e6e0405268..ac3ab8828a 100644 --- a/resources/sql/patches/019.arcprojects.sql +++ b/resources/sql/patches/019.arcprojects.sql @@ -2,7 +2,7 @@ CREATE TABLE {$NAMESPACE}_repository.repository_arcanistproject ( id int unsigned not null auto_increment primary key, phid varchar(64) binary not null, unique key(phid), - name varchar(255) not null, + name varchar(255) COLLATE `binary` not null, unique key (name), repositoryID int unsigned ); diff --git a/resources/sql/patches/035.proxyimage.sql b/resources/sql/patches/035.proxyimage.sql index 78564aa43d..425506bcf4 100644 --- a/resources/sql/patches/035.proxyimage.sql +++ b/resources/sql/patches/035.proxyimage.sql @@ -1,6 +1,6 @@ CREATE TABLE {$NAMESPACE}_file.file_proxyimage ( id int unsigned not null primary key auto_increment, - uri varchar(255) binary not null, + uri varchar(255) COLLATE `binary` not null, unique key(uri), filePHID varchar(64) binary not null ) ENGINE=InnoDB; diff --git a/resources/sql/patches/040.transform.sql b/resources/sql/patches/040.transform.sql index 0f35801d43..bb47b810a1 100644 --- a/resources/sql/patches/040.transform.sql +++ b/resources/sql/patches/040.transform.sql @@ -1,7 +1,7 @@ CREATE TABLE {$NAMESPACE}_file.file_transformedfile ( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - originalPHID varchar(64) BINARY NOT NULL, - transform varchar(255) BINARY NOT NULL, + originalPHID varchar(64) COLLATE `binary` NOT NULL, + transform varchar(255) COLLATE `binary` NOT NULL, unique key (originalPHID, transform), transformedPHID varchar(64) BINARY NOT NULL, key (transformedPHID), diff --git a/resources/sql/patches/068.maniphestauxiliarystorage.sql b/resources/sql/patches/068.maniphestauxiliarystorage.sql index bd7576b186..17c94f9516 100644 --- a/resources/sql/patches/068.maniphestauxiliarystorage.sql +++ b/resources/sql/patches/068.maniphestauxiliarystorage.sql @@ -1,7 +1,7 @@ create table {$NAMESPACE}_maniphest.maniphest_taskauxiliarystorage (id int unsigned not null auto_increment primary key, taskPHID varchar(64) binary not null, - name varchar(255) not null, + name varchar(255) COLLATE `binary` not null, value varchar(255) not null, unique key (taskPHID,name), dateCreated int unsigned not null, diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php index 0b0b6da9b1..f4d8d0f947 100755 --- a/scripts/sql/manage_storage.php +++ b/scripts/sql/manage_storage.php @@ -64,6 +64,13 @@ try { 'help' => 'Do not actually change anything, just show what would be '. 'changed.', ), + array( + 'name' => 'disable-utf8mb4', + 'help' => pht( + 'Disable utf8mb4, even if the database supports it. This is an '. + 'advanced feature used for testing changes to Phabricator; you '. + 'should not normally use this flag.'), + ) )); } catch (PhutilArgumentUsageException $ex) { $args->printUsageException($ex); @@ -126,6 +133,7 @@ $api->setHost($default_host); $api->setPort($default_port); $api->setPassword($password); $api->setNamespace($args->getArg('namespace')); +$api->setDisableUTF8MB4($args->getArg('disable-utf8mb4')); try { queryfx( diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8be0a4aea2..629d943304 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -9,14 +9,67 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( + 'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php', + 'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php', + 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', + 'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php', + 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', + 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', + 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', + 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', + 'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php', + 'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php', 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', + 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', + 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', + 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', + 'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php', + 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', + 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', + 'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php', + 'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php', + 'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php', 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php', 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', + 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', + 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', + 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', + 'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php', + 'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php', + 'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php', + 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', + 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', + 'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php', + 'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php', 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', + 'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php', + 'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php', + 'AlmanacNetwork' => 'applications/almanac/storage/AlmanacNetwork.php', + 'AlmanacNetworkController' => 'applications/almanac/controller/AlmanacNetworkController.php', + 'AlmanacNetworkEditController' => 'applications/almanac/controller/AlmanacNetworkEditController.php', + 'AlmanacNetworkEditor' => 'applications/almanac/editor/AlmanacNetworkEditor.php', + 'AlmanacNetworkListController' => 'applications/almanac/controller/AlmanacNetworkListController.php', + 'AlmanacNetworkPHIDType' => 'applications/almanac/phid/AlmanacNetworkPHIDType.php', + 'AlmanacNetworkQuery' => 'applications/almanac/query/AlmanacNetworkQuery.php', + 'AlmanacNetworkSearchEngine' => 'applications/almanac/query/AlmanacNetworkSearchEngine.php', + 'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php', + 'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php', + 'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php', + 'AlmanacService' => 'applications/almanac/storage/AlmanacService.php', + 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', + 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', + 'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php', + 'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php', + 'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php', + 'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php', + 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', + 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', + 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', + 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 'Aphront400Response' => 'aphront/response/Aphront400Response.php', 'Aphront403Response' => 'aphront/response/Aphront403Response.php', @@ -109,22 +162,22 @@ phutil_register_library_map(array( 'CalendarConstants' => 'applications/calendar/constants/CalendarConstants.php', 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', - 'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php', - 'CelerityManagementMapWorkflow' => 'infrastructure/celerity/management/CelerityManagementMapWorkflow.php', - 'CelerityManagementWorkflow' => 'infrastructure/celerity/management/CelerityManagementWorkflow.php', - 'CelerityPhabricatorResourceController' => 'infrastructure/celerity/CelerityPhabricatorResourceController.php', - 'CelerityPhabricatorResources' => 'infrastructure/celerity/resources/CelerityPhabricatorResources.php', - 'CelerityPhysicalResources' => 'infrastructure/celerity/resources/CelerityPhysicalResources.php', - 'CelerityResourceController' => 'infrastructure/celerity/CelerityResourceController.php', - 'CelerityResourceGraph' => 'infrastructure/celerity/CelerityResourceGraph.php', - 'CelerityResourceMap' => 'infrastructure/celerity/CelerityResourceMap.php', - 'CelerityResourceMapGenerator' => 'infrastructure/celerity/CelerityResourceMapGenerator.php', - 'CelerityResourceTransformer' => 'infrastructure/celerity/CelerityResourceTransformer.php', - 'CelerityResourceTransformerTestCase' => 'infrastructure/celerity/__tests__/CelerityResourceTransformerTestCase.php', - 'CelerityResources' => 'infrastructure/celerity/resources/CelerityResources.php', - 'CelerityResourcesOnDisk' => 'infrastructure/celerity/resources/CelerityResourcesOnDisk.php', - 'CeleritySpriteGenerator' => 'infrastructure/celerity/CeleritySpriteGenerator.php', - 'CelerityStaticResourceResponse' => 'infrastructure/celerity/CelerityStaticResourceResponse.php', + 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', + 'CelerityManagementMapWorkflow' => 'applications/celerity/management/CelerityManagementMapWorkflow.php', + 'CelerityManagementWorkflow' => 'applications/celerity/management/CelerityManagementWorkflow.php', + 'CelerityPhabricatorResourceController' => 'applications/celerity/controller/CelerityPhabricatorResourceController.php', + 'CelerityPhabricatorResources' => 'applications/celerity/resources/CelerityPhabricatorResources.php', + 'CelerityPhysicalResources' => 'applications/celerity/resources/CelerityPhysicalResources.php', + 'CelerityResourceController' => 'applications/celerity/controller/CelerityResourceController.php', + 'CelerityResourceGraph' => 'applications/celerity/CelerityResourceGraph.php', + 'CelerityResourceMap' => 'applications/celerity/CelerityResourceMap.php', + 'CelerityResourceMapGenerator' => 'applications/celerity/CelerityResourceMapGenerator.php', + 'CelerityResourceTransformer' => 'applications/celerity/CelerityResourceTransformer.php', + 'CelerityResourceTransformerTestCase' => 'applications/celerity/__tests__/CelerityResourceTransformerTestCase.php', + 'CelerityResources' => 'applications/celerity/resources/CelerityResources.php', + 'CelerityResourcesOnDisk' => 'applications/celerity/resources/CelerityResourcesOnDisk.php', + 'CeleritySpriteGenerator' => 'applications/celerity/CeleritySpriteGenerator.php', + 'CelerityStaticResourceResponse' => 'applications/celerity/CelerityStaticResourceResponse.php', 'ChatLogConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogConduitAPIMethod.php', 'ChatLogQueryConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogQueryConduitAPIMethod.php', 'ChatLogRecordConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogRecordConduitAPIMethod.php', @@ -185,18 +238,18 @@ phutil_register_library_map(array( 'ConpherenceViewController' => 'applications/conpherence/controller/ConpherenceViewController.php', 'ConpherenceWidgetController' => 'applications/conpherence/controller/ConpherenceWidgetController.php', 'ConpherenceWidgetView' => 'applications/conpherence/view/ConpherenceWidgetView.php', - 'DarkConsoleController' => 'aphront/console/DarkConsoleController.php', - 'DarkConsoleCore' => 'aphront/console/DarkConsoleCore.php', - 'DarkConsoleDataController' => 'aphront/console/DarkConsoleDataController.php', - 'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/DarkConsoleErrorLogPlugin.php', - 'DarkConsoleErrorLogPluginAPI' => 'aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php', - 'DarkConsoleEventPlugin' => 'aphront/console/plugin/DarkConsoleEventPlugin.php', - 'DarkConsoleEventPluginAPI' => 'aphront/console/plugin/event/DarkConsoleEventPluginAPI.php', - 'DarkConsolePlugin' => 'aphront/console/plugin/DarkConsolePlugin.php', - 'DarkConsoleRequestPlugin' => 'aphront/console/plugin/DarkConsoleRequestPlugin.php', - 'DarkConsoleServicesPlugin' => 'aphront/console/plugin/DarkConsoleServicesPlugin.php', - 'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/DarkConsoleXHProfPlugin.php', - 'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php', + 'DarkConsoleController' => 'applications/console/controller/DarkConsoleController.php', + 'DarkConsoleCore' => 'applications/console/core/DarkConsoleCore.php', + 'DarkConsoleDataController' => 'applications/console/controller/DarkConsoleDataController.php', + 'DarkConsoleErrorLogPlugin' => 'applications/console/plugin/DarkConsoleErrorLogPlugin.php', + 'DarkConsoleErrorLogPluginAPI' => 'applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php', + 'DarkConsoleEventPlugin' => 'applications/console/plugin/DarkConsoleEventPlugin.php', + 'DarkConsoleEventPluginAPI' => 'applications/console/plugin/event/DarkConsoleEventPluginAPI.php', + 'DarkConsolePlugin' => 'applications/console/plugin/DarkConsolePlugin.php', + 'DarkConsoleRequestPlugin' => 'applications/console/plugin/DarkConsoleRequestPlugin.php', + 'DarkConsoleServicesPlugin' => 'applications/console/plugin/DarkConsoleServicesPlugin.php', + 'DarkConsoleXHProfPlugin' => 'applications/console/plugin/DarkConsoleXHProfPlugin.php', + 'DarkConsoleXHProfPluginAPI' => 'applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php', 'DatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DatabaseConfigurationProvider.php', 'DefaultDatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DefaultDatabaseConfigurationProvider.php', 'DifferentialAction' => 'applications/differential/constants/DifferentialAction.php', @@ -330,6 +383,7 @@ phutil_register_library_map(array( 'DifferentialReviewersField' => 'applications/differential/customfield/DifferentialReviewersField.php', 'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php', 'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php', + 'DifferentialRevisionCloseDetailsController' => 'applications/differential/controller/DifferentialRevisionCloseDetailsController.php', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/DifferentialRevisionControlSystem.php', 'DifferentialRevisionDetailView' => 'applications/differential/view/DifferentialRevisionDetailView.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', @@ -684,6 +738,7 @@ phutil_register_library_map(array( 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', + 'FundInitiativeIndexer' => 'applications/fund/search/FundInitiativeIndexer.php', 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', 'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php', 'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php', @@ -1320,6 +1375,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 'PhabricatorCalendarViewController' => 'applications/calendar/controller/PhabricatorCalendarViewController.php', 'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php', + 'PhabricatorCelerityApplication' => 'applications/celerity/application/PhabricatorCelerityApplication.php', 'PhabricatorCelerityTestCase' => '__tests__/PhabricatorCelerityTestCase.php', 'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php', 'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php', @@ -1405,6 +1461,7 @@ phutil_register_library_map(array( 'PhabricatorConfigWelcomeController' => 'applications/config/controller/PhabricatorConfigWelcomeController.php', 'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php', 'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php', + 'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php', 'PhabricatorContentSource' => 'applications/metamta/contentsource/PhabricatorContentSource.php', 'PhabricatorContentSourceView' => 'applications/metamta/contentsource/PhabricatorContentSourceView.php', 'PhabricatorController' => 'applications/base/controller/PhabricatorController.php', @@ -2567,16 +2624,23 @@ phutil_register_library_map(array( 'PhortuneCartCancelController' => 'applications/phortune/controller/PhortuneCartCancelController.php', 'PhortuneCartCheckoutController' => 'applications/phortune/controller/PhortuneCartCheckoutController.php', 'PhortuneCartController' => 'applications/phortune/controller/PhortuneCartController.php', + 'PhortuneCartEditor' => 'applications/phortune/editor/PhortuneCartEditor.php', 'PhortuneCartImplementation' => 'applications/phortune/cart/PhortuneCartImplementation.php', 'PhortuneCartListController' => 'applications/phortune/controller/PhortuneCartListController.php', 'PhortuneCartPHIDType' => 'applications/phortune/phid/PhortuneCartPHIDType.php', 'PhortuneCartQuery' => 'applications/phortune/query/PhortuneCartQuery.php', + 'PhortuneCartReplyHandler' => 'applications/phortune/mail/PhortuneCartReplyHandler.php', 'PhortuneCartSearchEngine' => 'applications/phortune/query/PhortuneCartSearchEngine.php', + 'PhortuneCartTransaction' => 'applications/phortune/storage/PhortuneCartTransaction.php', + 'PhortuneCartTransactionQuery' => 'applications/phortune/query/PhortuneCartTransactionQuery.php', 'PhortuneCartUpdateController' => 'applications/phortune/controller/PhortuneCartUpdateController.php', 'PhortuneCartViewController' => 'applications/phortune/controller/PhortuneCartViewController.php', 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', + 'PhortuneChargeListController' => 'applications/phortune/controller/PhortuneChargeListController.php', 'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php', 'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php', + 'PhortuneChargeSearchEngine' => 'applications/phortune/query/PhortuneChargeSearchEngine.php', + 'PhortuneChargeTableView' => 'applications/phortune/view/PhortuneChargeTableView.php', 'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php', 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', 'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php', @@ -2587,11 +2651,13 @@ phutil_register_library_map(array( 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', + 'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php', 'PhortuneMerchant' => 'applications/phortune/storage/PhortuneMerchant.php', 'PhortuneMerchantCapability' => 'applications/phortune/capability/PhortuneMerchantCapability.php', 'PhortuneMerchantController' => 'applications/phortune/controller/PhortuneMerchantController.php', 'PhortuneMerchantEditController' => 'applications/phortune/controller/PhortuneMerchantEditController.php', 'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php', + 'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php', 'PhortuneMerchantListController' => 'applications/phortune/controller/PhortuneMerchantListController.php', 'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php', 'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php', @@ -2603,6 +2669,7 @@ phutil_register_library_map(array( 'PhortuneMultiplePaymentProvidersException' => 'applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php', 'PhortuneNoPaymentProviderException' => 'applications/phortune/exception/PhortuneNoPaymentProviderException.php', 'PhortuneNotImplementedException' => 'applications/phortune/exception/PhortuneNotImplementedException.php', + 'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php', 'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php', 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/PhortunePaymentMethodCreateController.php', @@ -2629,7 +2696,6 @@ phutil_register_library_map(array( 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 'PhortunePurchasePHIDType' => 'applications/phortune/phid/PhortunePurchasePHIDType.php', 'PhortunePurchaseQuery' => 'applications/phortune/query/PhortunePurchaseQuery.php', - 'PhortunePurchaseViewController' => 'applications/phortune/controller/PhortunePurchaseViewController.php', 'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php', 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', @@ -2710,9 +2776,14 @@ phutil_register_library_map(array( 'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php', 'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php', 'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php', + 'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php', 'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php', 'PhrictionSearchEngine' => 'applications/phriction/query/PhrictionSearchEngine.php', 'PhrictionSearchIndexer' => 'applications/phriction/search/PhrictionSearchIndexer.php', + 'PhrictionTransaction' => 'applications/phriction/storage/PhrictionTransaction.php', + 'PhrictionTransactionComment' => 'applications/phriction/storage/PhrictionTransactionComment.php', + 'PhrictionTransactionEditor' => 'applications/phriction/editor/PhrictionTransactionEditor.php', + 'PhrictionTransactionQuery' => 'applications/phriction/query/PhrictionTransactionQuery.php', 'PonderAddAnswerView' => 'applications/ponder/view/PonderAddAnswerView.php', 'PonderAnswer' => 'applications/ponder/storage/PonderAnswer.php', 'PonderAnswerCommentController' => 'applications/ponder/controller/PonderAnswerCommentController.php', @@ -2762,6 +2833,7 @@ phutil_register_library_map(array( 'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', + 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php', 'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php', 'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php', @@ -2878,8 +2950,8 @@ phutil_register_library_map(array( ), 'function' => array( '_phabricator_time_format' => 'view/viewutils.php', - 'celerity_generate_unique_node_id' => 'infrastructure/celerity/api.php', - 'celerity_get_resource_uri' => 'infrastructure/celerity/api.php', + 'celerity_generate_unique_node_id' => 'applications/celerity/api.php', + 'celerity_get_resource_uri' => 'applications/celerity/api.php', 'implode_selected_handle_links' => 'applications/phid/handle/view/render.php', 'javelin_tag' => 'infrastructure/javelin/markup.php', 'phabricator_date' => 'view/viewutils.php', @@ -2892,20 +2964,85 @@ phutil_register_library_map(array( 'phid_get_subtype' => 'applications/phid/utils.php', 'phid_get_type' => 'applications/phid/utils.php', 'phid_group_by_type' => 'applications/phid/utils.php', - 'require_celerity_resource' => 'infrastructure/celerity/api.php', + 'require_celerity_resource' => 'applications/celerity/api.php', ), 'xmap' => array( + 'AlmanacAddress' => 'Phobject', + 'AlmanacBinding' => array( + 'AlmanacDAO', + 'PhabricatorPolicyInterface', + ), + 'AlmanacBindingEditController' => 'AlmanacServiceController', + 'AlmanacBindingEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', + 'AlmanacBindingQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacBindingTableView' => 'AphrontView', + 'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacBindingViewController' => 'AlmanacServiceController', 'AlmanacConduitUtil' => 'Phobject', + 'AlmanacConsoleController' => 'AlmanacController', + 'AlmanacController' => 'PhabricatorController', + 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', + 'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability', + 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacDAO' => 'PhabricatorLiskDAO', 'AlmanacDevice' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', ), + 'AlmanacDeviceController' => 'AlmanacController', + 'AlmanacDeviceEditController' => 'AlmanacDeviceController', + 'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacDeviceListController' => 'AlmanacDeviceController', 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 'AlmanacDeviceProperty' => 'AlmanacDAO', 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacDeviceViewController' => 'AlmanacDeviceController', + 'AlmanacInterface' => array( + 'AlmanacDAO', + 'PhabricatorPolicyInterface', + ), + 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', + 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', + 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', + 'AlmanacInterfaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacInterfaceTableView' => 'AphrontView', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'AlmanacNames' => 'Phobject', + 'AlmanacNamesTestCase' => 'PhabricatorTestCase', + 'AlmanacNetwork' => array( + 'AlmanacDAO', + 'PhabricatorPolicyInterface', + ), + 'AlmanacNetworkController' => 'AlmanacController', + 'AlmanacNetworkEditController' => 'AlmanacNetworkController', + 'AlmanacNetworkEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacNetworkListController' => 'AlmanacNetworkController', + 'AlmanacNetworkPHIDType' => 'PhabricatorPHIDType', + 'AlmanacNetworkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacNetworkSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacNetworkViewController' => 'AlmanacNetworkController', + 'AlmanacService' => array( + 'AlmanacDAO', + 'PhabricatorPolicyInterface', + ), + 'AlmanacServiceController' => 'AlmanacController', + 'AlmanacServiceEditController' => 'AlmanacServiceController', + 'AlmanacServiceEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacServiceListController' => 'AlmanacServiceController', + 'AlmanacServicePHIDType' => 'PhabricatorPHIDType', + 'AlmanacServiceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacServiceViewController' => 'AlmanacServiceController', 'Aphront304Response' => 'AphrontResponse', 'Aphront400Response' => 'AphrontResponse', 'Aphront403Response' => 'AphrontHTMLResponse', @@ -3216,6 +3353,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorProjectInterface', ), + 'DifferentialRevisionCloseDetailsController' => 'DifferentialController', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionHasTaskEdgeType' => 'PhabricatorEdgeType', @@ -3582,6 +3720,7 @@ phutil_register_library_map(array( 'FundInitiativeCloseController' => 'FundController', 'FundInitiativeEditController' => 'FundController', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', + 'FundInitiativeIndexer' => 'PhabricatorSearchDocumentIndexer', 'FundInitiativeListController' => 'FundController', 'FundInitiativePHIDType' => 'PhabricatorPHIDType', 'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -4284,6 +4423,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarViewController' => 'PhabricatorCalendarController', 'PhabricatorCampfireProtocolAdapter' => 'PhabricatorBotBaseStreamingProtocolAdapter', + 'PhabricatorCelerityApplication' => 'PhabricatorApplication', 'PhabricatorCelerityTestCase' => 'PhabricatorTestCase', 'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase', 'PhabricatorChangesetResponse' => 'AphrontProxyResponse', @@ -4380,6 +4520,7 @@ phutil_register_library_map(array( 'PhabricatorConfigWelcomeController' => 'PhabricatorConfigController', 'PhabricatorConpherenceApplication' => 'PhabricatorApplication', 'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorConsoleApplication' => 'PhabricatorApplication', 'PhabricatorContentSourceView' => 'AphrontView', 'PhabricatorController' => 'AphrontController', 'PhabricatorCookies' => 'Phobject', @@ -5634,18 +5775,25 @@ phutil_register_library_map(array( 'PhortuneCartCancelController' => 'PhortuneCartController', 'PhortuneCartCheckoutController' => 'PhortuneCartController', 'PhortuneCartController' => 'PhortuneController', + 'PhortuneCartEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneCartListController' => 'PhortuneController', 'PhortuneCartPHIDType' => 'PhabricatorPHIDType', 'PhortuneCartQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhortuneCartReplyHandler' => 'PhabricatorMailReplyHandler', 'PhortuneCartSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhortuneCartTransaction' => 'PhabricatorApplicationTransaction', + 'PhortuneCartTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneCartUpdateController' => 'PhortuneCartController', 'PhortuneCartViewController' => 'PhortuneCartController', 'PhortuneCharge' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', ), + 'PhortuneChargeListController' => 'PhortuneController', 'PhortuneChargePHIDType' => 'PhabricatorPHIDType', 'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhortuneChargeSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhortuneChargeTableView' => 'AphrontView', 'PhortuneController' => 'PhabricatorController', 'PhortuneCurrency' => 'Phobject', 'PhortuneCurrencySerializer' => 'PhabricatorLiskSerializer', @@ -5654,6 +5802,7 @@ phutil_register_library_map(array( 'PhortuneErrCode' => 'PhortuneConstants', 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType', + 'PhortuneMemberHasMerchantEdgeType' => 'PhabricatorEdgeType', 'PhortuneMerchant' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', @@ -5662,6 +5811,7 @@ phutil_register_library_map(array( 'PhortuneMerchantController' => 'PhortuneController', 'PhortuneMerchantEditController' => 'PhortuneMerchantController', 'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneMerchantListController' => 'PhortuneMerchantController', 'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType', 'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -5673,6 +5823,7 @@ phutil_register_library_map(array( 'PhortuneMultiplePaymentProvidersException' => 'Exception', 'PhortuneNoPaymentProviderException' => 'Exception', 'PhortuneNotImplementedException' => 'Exception', + 'PhortuneOrderTableView' => 'AphrontView', 'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider', 'PhortunePaymentMethod' => array( 'PhortuneDAO', @@ -5709,7 +5860,6 @@ phutil_register_library_map(array( ), 'PhortunePurchasePHIDType' => 'PhabricatorPHIDType', 'PhortunePurchaseQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhortunePurchaseViewController' => 'PhortuneController', 'PhortuneSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider', 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', @@ -5813,9 +5963,14 @@ phutil_register_library_map(array( 'PhrictionMoveController' => 'PhrictionController', 'PhrictionNewController' => 'PhrictionController', 'PhrictionRemarkupRule' => 'PhutilRemarkupRule', + 'PhrictionReplyHandler' => 'PhabricatorMailReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrictionSearchIndexer' => 'PhabricatorSearchDocumentIndexer', + 'PhrictionTransaction' => 'PhabricatorApplicationTransaction', + 'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment', + 'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhrictionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PonderAddAnswerView' => 'AphrontView', 'PonderAnswer' => array( 'PonderDAO', @@ -5881,6 +6036,7 @@ phutil_register_library_map(array( 'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'QueryFormattingTestCase' => 'PhabricatorTestCase', 'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification', 'ReleephBranch' => array( diff --git a/src/aphront/AphrontController.php b/src/aphront/AphrontController.php index 352e56ee11..55c2f9f30e 100644 --- a/src/aphront/AphrontController.php +++ b/src/aphront/AphrontController.php @@ -28,25 +28,45 @@ abstract class AphrontController extends Phobject { return $response; } - abstract public function processRequest(); + public function handleRequest(AphrontRequest $request) { + if (method_exists($this, 'processRequest')) { + return $this->processRequest(); + } - final public function __construct(AphrontRequest $request) { + throw new PhutilMethodNotImplementedException( + pht( + 'Controllers must implement either handleRequest() (recommended) '. + 'or processRequest() (deprecated).')); + } + + final public function setRequest(AphrontRequest $request) { $this->request = $request; + return $this; } final public function getRequest() { + if (!$this->request) { + throw new Exception(pht('Call setRequest() before getRequest()!')); + } return $this->request; } + final public function getViewer() { + return $this->getRequest()->getViewer(); + } + final public function delegateToController(AphrontController $controller) { + $request = $this->getRequest(); + $controller->setDelegatingController($this); + $controller->setRequest($request); $application = $this->getCurrentApplication(); if ($application) { $controller->setCurrentApplication($application); } - return $controller->processRequest(); + return $controller->handleRequest($request); } final public function setCurrentApplication( diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index 7e14ece3f9..a9800a66f2 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -25,12 +25,26 @@ final class AphrontRequest { private $requestData; private $user; private $applicationConfiguration; + private $uriData; final public function __construct($host, $path) { $this->host = $host; $this->path = $path; } + final public function setURIMap(array $uri_data) { + $this->uriData = $uri_data; + return $this; + } + + final public function getURIMap() { + return $this->uriData; + } + + final public function getURIData($key, $default = null) { + return idx($this->uriData, $key, $default); + } + final public function setApplicationConfiguration( $application_configuration) { $this->applicationConfiguration = $application_configuration; @@ -476,6 +490,10 @@ final class AphrontRequest { return $this->user; } + final public function getViewer() { + return $this->user; + } + final public function getRequestURI() { $get = $_GET; unset($get['__path__']); diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 237acbb0a5..8afba8aad8 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -11,7 +11,6 @@ abstract class AphrontApplicationConfiguration { private $console; abstract public function getApplicationName(); - abstract public function getURIMap(); abstract public function buildRequest(); abstract public function build404Controller(); abstract public function buildRedirectController($uri, $external); @@ -64,8 +63,8 @@ abstract class AphrontApplicationConfiguration { * first test if the HTTP_HOST is configured as a valid Phabricator URI. If * it isn't, we do a special check to see if it's a custom domain for a blog * in the Phame application and if that fails we error. Otherwise, we test - * the URI against all builtin routes from @{method:getURIMap}, then against - * all application routes from installed @{class:PhabricatorApplication}s. + * against all application routes from installed + * @{class:PhabricatorApplication}s. * * If we match a route, we construct the controller it points at, build it, * and return it. @@ -212,7 +211,6 @@ abstract class AphrontApplicationConfiguration { */ final public function buildControllerForPath($path) { $maps = array(); - $maps[] = array(null, $this->getURIMap()); $applications = PhabricatorApplication::getAllInstalledApplications(); foreach ($applications as $application) { @@ -241,7 +239,7 @@ abstract class AphrontApplicationConfiguration { $request = $this->getRequest(); - $controller = newv($controller_class, array($request)); + $controller = newv($controller_class, array()); if ($current_application) { $controller->setCurrentApplication($current_application); } diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index 147f9849b6..e057b06c4a 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -14,31 +14,6 @@ class AphrontDefaultApplicationConfiguration return 'aphront-default'; } - public function getURIMap() { - return $this->getResourceURIMapRules() + array( - '/~/' => array( - '' => 'DarkConsoleController', - 'data/(?P[^/]+)/' => 'DarkConsoleDataController', - ), - ); - } - - protected function getResourceURIMapRules() { - $extensions = CelerityResourceController::getSupportedResourceTypes(); - $extensions = array_keys($extensions); - $extensions = implode('|', $extensions); - - return array( - '/res/' => array( - '(?:(?P[0-9]+)T/)?'. - '(?P[^/]+)/'. - '(?P[a-f0-9]{8})/'. - '(?P.+\.(?:'.$extensions.'))' - => 'CelerityPhabricatorResourceController', - ), - ); - } - /** * @phutil-external-symbol class PhabricatorStartup */ @@ -177,13 +152,14 @@ class AphrontDefaultApplicationConfiguration // // Possibly we should add a header here like "you need to login to see // the thing you are trying to look at". - $login_controller = new PhabricatorAuthStartController($request); + $login_controller = new PhabricatorAuthStartController(); + $login_controller->setRequest($request); $auth_app_class = 'PhabricatorAuthApplication'; $auth_app = PhabricatorApplication::getByClass($auth_app_class); $login_controller->setCurrentApplication($auth_app); - return $login_controller->processRequest(); + return $login_controller->handleRequest($request); } $list = $ex->getMoreInfo(); @@ -297,12 +273,12 @@ class AphrontDefaultApplicationConfiguration } public function build404Controller() { - return array(new Phabricator404Controller($this->getRequest()), array()); + return array(new Phabricator404Controller(), array()); } public function buildRedirectController($uri, $external) { return array( - new PhabricatorRedirectController($this->getRequest()), + new PhabricatorRedirectController(), array( 'uri' => $uri, 'external' => $external, diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index d24e81aeeb..625850b37f 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -30,12 +30,48 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { return true; } - public function isLaunchable() { - return false; + public function getRoutes() { + return array( + '/almanac/' => array( + '' => 'AlmanacConsoleController', + 'service/' => array( + '(?:query/(?P[^/]+)/)?' => 'AlmanacServiceListController', + 'edit/(?:(?P\d+)/)?' => 'AlmanacServiceEditController', + 'view/(?P[^/]+)/' => 'AlmanacServiceViewController', + ), + 'device/' => array( + '(?:query/(?P[^/]+)/)?' => 'AlmanacDeviceListController', + 'edit/(?:(?P\d+)/)?' => 'AlmanacDeviceEditController', + 'view/(?P[^/]+)/' => 'AlmanacDeviceViewController', + ), + 'interface/' => array( + 'edit/(?:(?P\d+)/)?' => 'AlmanacInterfaceEditController', + ), + 'binding/' => array( + 'edit/(?:(?P\d+)/)?' => 'AlmanacBindingEditController', + '(?P\d+)/' => 'AlmanacBindingViewController', + ), + 'network/' => array( + '(?:query/(?P[^/]+)/)?' => 'AlmanacNetworkListController', + 'edit/(?:(?P\d+)/)?' => 'AlmanacNetworkEditController', + '(?P\d+)/' => 'AlmanacNetworkViewController', + ), + ), + ); } - public function getRoutes() { - return array(); + protected function getCustomCapabilities() { + return array( + AlmanacCreateServicesCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + AlmanacCreateDevicesCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + AlmanacCreateNetworksCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + ); } } diff --git a/src/applications/almanac/capability/AlmanacCreateDevicesCapability.php b/src/applications/almanac/capability/AlmanacCreateDevicesCapability.php new file mode 100644 index 0000000000..f5f279fc89 --- /dev/null +++ b/src/applications/almanac/capability/AlmanacCreateDevicesCapability.php @@ -0,0 +1,16 @@ +getViewer(); + + $id = $request->getURIData('id'); + if ($id) { + $binding = id(new AlmanacBindingQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$binding) { + return new Aphront404Response(); + } + + $service = $binding->getService(); + $is_new = false; + + $service_uri = $service->getURI(); + $cancel_uri = $binding->getURI(); + $title = pht('Edit Binding'); + $save_button = pht('Save Changes'); + } else { + $service = id(new AlmanacServiceQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getStr('serviceID'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $binding = AlmanacBinding::initializeNewBinding($service); + $is_new = true; + + $service_uri = $service->getURI(); + $cancel_uri = $service_uri; + $title = pht('Create Binding'); + $save_button = pht('Create Binding'); + } + + $v_interface = array(); + if ($binding->getInterfacePHID()) { + $v_interface = array($binding->getInterfacePHID()); + } + $e_interface = true; + + $validation_exception = null; + if ($request->isFormPost()) { + $v_interface = $request->getArr('interfacePHIDs'); + + $type_interface = AlmanacBindingTransaction::TYPE_INTERFACE; + + $xactions = array(); + + $xactions[] = id(new AlmanacBindingTransaction()) + ->setTransactionType($type_interface) + ->setNewValue(head($v_interface)); + + $editor = id(new AlmanacBindingEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($binding, $xactions); + + $binding_uri = $binding->getURI(); + return id(new AphrontRedirectResponse())->setURI($binding_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_interface = $ex->getShortMessage($type_interface); + } + } + + $interface_handles = array(); + if ($v_interface) { + $interface_handles = $this->loadViewerHandles($v_interface); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setName('interfacePHIDs') + ->setLabel('Interface') + ->setLimit(1) + ->setDatasource(new AlmanacInterfaceDatasource()) + ->setValue($interface_handles) + ->setError($e_interface)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue($save_button)); + + $box = id(new PHUIObjectBoxView()) + ->setValidationException($validation_exception) + ->setHeaderText($title) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($service->getName(), $service_uri); + if ($is_new) { + $crumbs->addTextCrumb(pht('Create Binding')); + } else { + $crumbs->addTextCrumb(pht('Edit Binding')); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/almanac/controller/AlmanacBindingViewController.php b/src/applications/almanac/controller/AlmanacBindingViewController.php new file mode 100644 index 0000000000..b7ff42ce03 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacBindingViewController.php @@ -0,0 +1,122 @@ +getViewer(); + + $id = $request->getURIData('id'); + + $binding = id(new AlmanacBindingQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$binding) { + return new Aphront404Response(); + } + + $service = $binding->getService(); + $service_uri = $service->getURI(); + + $title = pht('Binding %s', $binding->getID()); + + $property_list = $this->buildPropertyList($binding); + $action_list = $this->buildActionList($binding); + $property_list->setActionList($action_list); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($title) + ->setPolicyObject($binding); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($property_list); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($service->getName(), $service_uri); + $crumbs->addTextCrumb($title); + + $xactions = id(new AlmanacBindingTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($binding->getPHID())) + ->execute(); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setObjectPHID($binding->getPHID()) + ->setTransactions($xactions) + ->setShouldTerminate(true); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $xaction_view, + ), + array( + 'title' => $title, + )); + } + + private function buildPropertyList(AlmanacBinding $binding) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $handles = $this->loadViewerHandles( + array( + $binding->getServicePHID(), + $binding->getDevicePHID(), + $binding->getInterface()->getNetworkPHID(), + )); + + $properties->addProperty( + pht('Service'), + $handles[$binding->getServicePHID()]->renderLink()); + + $properties->addProperty( + pht('Device'), + $handles[$binding->getDevicePHID()]->renderLink()); + + $properties->addProperty( + pht('Network'), + $handles[$binding->getInterface()->getNetworkPHID()]->renderLink()); + + $properties->addProperty( + pht('Interface'), + $binding->getInterface()->renderDisplayAddress()); + + return $properties; + } + + private function buildActionList(AlmanacBinding $binding) { + $viewer = $this->getViewer(); + $id = $binding->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $binding, + PhabricatorPolicyCapability::CAN_EDIT); + + $actions = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Binding')) + ->setHref($this->getApplicationURI("binding/edit/{$id}/")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + return $actions; + } + +} diff --git a/src/applications/almanac/controller/AlmanacConsoleController.php b/src/applications/almanac/controller/AlmanacConsoleController.php new file mode 100644 index 0000000000..bb95d3a541 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacConsoleController.php @@ -0,0 +1,52 @@ +getViewer(); + + $menu = id(new PHUIObjectItemListView()) + ->setUser($viewer); + + $menu->addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Services')) + ->setHref($this->getApplicationURI('service/')) + ->addAttribute( + pht( + 'Manage Almanac services.'))); + + $menu->addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Devices')) + ->setHref($this->getApplicationURI('device/')) + ->addAttribute( + pht( + 'Manage Almanac devices.'))); + + $menu->addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Networks')) + ->setHref($this->getApplicationURI('network/')) + ->addAttribute( + pht( + 'Manage Almanac networks.'))); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Console')); + + return $this->buildApplicationPage( + array( + $crumbs, + $menu, + ), + array( + 'title' => pht('Almanac Console'), + )); + } + +} diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php new file mode 100644 index 0000000000..5a95566d75 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacController.php @@ -0,0 +1,4 @@ +getApplicationURI('device/'); + $crumbs->addTextCrumb(pht('Devices'), $list_uri); + + return $crumbs; + } + +} diff --git a/src/applications/almanac/controller/AlmanacDeviceEditController.php b/src/applications/almanac/controller/AlmanacDeviceEditController.php new file mode 100644 index 0000000000..b8bd075d23 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacDeviceEditController.php @@ -0,0 +1,142 @@ +getViewer(); + + $list_uri = $this->getApplicationURI('device/'); + + $id = $request->getURIData('id'); + if ($id) { + $device = id(new AlmanacDeviceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$device) { + return new Aphront404Response(); + } + + $is_new = false; + $device_uri = $device->getURI(); + $cancel_uri = $device_uri; + $title = pht('Edit Device'); + $save_button = pht('Save Changes'); + } else { + $this->requireApplicationCapability( + AlmanacCreateDevicesCapability::CAPABILITY); + + $device = AlmanacDevice::initializeNewDevice(); + $is_new = true; + + $cancel_uri = $list_uri; + $title = pht('Create Device'); + $save_button = pht('Create Device'); + } + + $v_name = $device->getName(); + $e_name = true; + $validation_exception = null; + + if ($request->isFormPost()) { + $v_name = $request->getStr('name'); + $v_view = $request->getStr('viewPolicy'); + $v_edit = $request->getStr('editPolicy'); + + $type_name = AlmanacDeviceTransaction::TYPE_NAME; + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $xactions = array(); + + $xactions[] = id(new AlmanacDeviceTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $xactions[] = id(new AlmanacDeviceTransaction()) + ->setTransactionType($type_view) + ->setNewValue($v_view); + + $xactions[] = id(new AlmanacDeviceTransaction()) + ->setTransactionType($type_edit) + ->setNewValue($v_edit); + + $editor = id(new AlmanacDeviceEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($device, $xactions); + + $device_uri = $device->getURI(); + return id(new AphrontRedirectResponse())->setURI($device_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_name = $ex->getShortMessage($type_name); + + $device->setViewPolicy($v_view); + $device->setEditPolicy($v_edit); + } + } + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($device) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($v_name) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('viewPolicy') + ->setPolicyObject($device) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('editPolicy') + ->setPolicyObject($device) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue($save_button)); + + $box = id(new PHUIObjectBoxView()) + ->setValidationException($validation_exception) + ->setHeaderText($title) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + if ($is_new) { + $crumbs->addTextCrumb(pht('Create Device')); + } else { + $crumbs->addTextCrumb($device->getName(), $device_uri); + $crumbs->addTextCrumb(pht('Edit')); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/almanac/controller/AlmanacDeviceListController.php b/src/applications/almanac/controller/AlmanacDeviceListController.php new file mode 100644 index 0000000000..3487676aba --- /dev/null +++ b/src/applications/almanac/controller/AlmanacDeviceListController.php @@ -0,0 +1,52 @@ +setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new AlmanacDeviceSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + AlmanacCreateDevicesCapability::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Device')) + ->setHref($this->getApplicationURI('device/edit/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + + public function buildSideNavView() { + $viewer = $this->getViewer(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new AlmanacDeviceSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + +} diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php new file mode 100644 index 0000000000..15c9102487 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -0,0 +1,142 @@ +getViewer(); + + $name = $request->getURIData('name'); + + $device = id(new AlmanacDeviceQuery()) + ->setViewer($viewer) + ->withNames(array($name)) + ->executeOne(); + if (!$device) { + return new Aphront404Response(); + } + + $title = pht('Device %s', $device->getName()); + + $property_list = $this->buildPropertyList($device); + $action_list = $this->buildActionList($device); + $property_list->setActionList($action_list); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($device->getName()) + ->setPolicyObject($device); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($property_list); + + $interfaces = $this->buildInterfaceList($device); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($device->getName()); + + $xactions = id(new AlmanacDeviceTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($device->getPHID())) + ->execute(); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setObjectPHID($device->getPHID()) + ->setTransactions($xactions) + ->setShouldTerminate(true); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $interfaces, + $xaction_view, + ), + array( + 'title' => $title, + )); + } + + private function buildPropertyList(AlmanacDevice $device) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + return $properties; + } + + private function buildActionList(AlmanacDevice $device) { + $viewer = $this->getViewer(); + $id = $device->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $device, + PhabricatorPolicyCapability::CAN_EDIT); + + $actions = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Device')) + ->setHref($this->getApplicationURI("device/edit/{$id}/")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + return $actions; + } + + private function buildInterfaceList(AlmanacDevice $device) { + $viewer = $this->getViewer(); + $id = $device->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $device, + PhabricatorPolicyCapability::CAN_EDIT); + + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($viewer) + ->withDevicePHIDs(array($device->getPHID())) + ->execute(); + + $phids = array(); + foreach ($interfaces as $interface) { + $phids[] = $interface->getNetworkPHID(); + $phids[] = $interface->getDevicePHID(); + } + $handles = $this->loadViewerHandles($phids); + + $table = id(new AlmanacInterfaceTableView()) + ->setUser($viewer) + ->setInterfaces($interfaces) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Device Interfaces')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($this->getApplicationURI("interface/edit/?deviceID={$id}")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit) + ->setText(pht('Add Interface')) + ->setIcon( + id(new PHUIIconView()) + ->setIconFont('fa-plus'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + } + +} diff --git a/src/applications/almanac/controller/AlmanacInterfaceEditController.php b/src/applications/almanac/controller/AlmanacInterfaceEditController.php new file mode 100644 index 0000000000..9d9cb652e2 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacInterfaceEditController.php @@ -0,0 +1,155 @@ +getViewer(); + + $id = $request->getURIData('id'); + if ($id) { + $interface = id(new AlmanacInterfaceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$interface) { + return new Aphront404Response(); + } + + $device = $interface->getDevice(); + + $is_new = false; + $title = pht('Edit Interface'); + $save_button = pht('Save Changes'); + } else { + $device = id(new AlmanacDeviceQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getStr('deviceID'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$device) { + return new Aphront404Response(); + } + + $interface = AlmanacInterface::initializeNewInterface(); + $is_new = true; + + $title = pht('Create Interface'); + $save_button = pht('Create Interface'); + } + + $device_uri = $device->getURI(); + $cancel_uri = $device_uri; + + $v_network = $interface->getNetworkPHID(); + + $v_address = $interface->getAddress(); + $e_address = true; + + $v_port = $interface->getPort(); + + $validation_exception = null; + + if ($request->isFormPost()) { + $v_network = $request->getStr('networkPHID'); + $v_address = $request->getStr('address'); + $v_port = $request->getStr('port'); + + $type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE; + + $address = AlmanacAddress::newFromParts($v_network, $v_address, $v_port); + + $xaction = id(new AlmanacDeviceTransaction()) + ->setTransactionType($type_interface) + ->setNewValue($address->toDictionary()); + + if ($interface->getID()) { + $xaction->setOldValue(array( + 'id' => $interface->getID(), + ) + $interface->toAddress()->toDictionary()); + } else { + $xaction->setOldValue(array()); + } + + $xactions = array(); + $xactions[] = $xaction; + + $editor = id(new AlmanacDeviceEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + try { + $editor->applyTransactions($device, $xactions); + + $device_uri = $device->getURI(); + return id(new AphrontRedirectResponse())->setURI($device_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_address = $ex->getShortMessage($type_interface); + } + } + + $networks = id(new AlmanacNetworkQuery()) + ->setViewer($viewer) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Network')) + ->setName('networkPHID') + ->setValue($v_network) + ->setOptions(mpull($networks, 'getName', 'getPHID'))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Address')) + ->setName('address') + ->setValue($v_address) + ->setError($e_address)) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Port')) + ->setName('port') + ->setValue($v_port) + ->setError($e_address)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue($save_button)); + + $box = id(new PHUIObjectBoxView()) + ->setValidationException($validation_exception) + ->setHeaderText($title) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($device->getName(), $device_uri); + if ($is_new) { + $crumbs->addTextCrumb(pht('Create Interface')); + } else { + $crumbs->addTextCrumb(pht('Edit Interface')); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/almanac/controller/AlmanacNetworkController.php b/src/applications/almanac/controller/AlmanacNetworkController.php new file mode 100644 index 0000000000..6313969b3b --- /dev/null +++ b/src/applications/almanac/controller/AlmanacNetworkController.php @@ -0,0 +1,14 @@ +getApplicationURI('network/'); + $crumbs->addTextCrumb(pht('Networks'), $list_uri); + + return $crumbs; + } + +} diff --git a/src/applications/almanac/controller/AlmanacNetworkEditController.php b/src/applications/almanac/controller/AlmanacNetworkEditController.php new file mode 100644 index 0000000000..558577fd7a --- /dev/null +++ b/src/applications/almanac/controller/AlmanacNetworkEditController.php @@ -0,0 +1,143 @@ +getViewer(); + + $list_uri = $this->getApplicationURI('network/'); + + $id = $request->getURIData('id'); + if ($id) { + $network = id(new AlmanacNetworkQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$network) { + return new Aphront404Response(); + } + + $is_new = false; + $network_uri = $this->getApplicationURI('network/'.$network->getID().'/'); + $cancel_uri = $network_uri; + $title = pht('Edit Network'); + $save_button = pht('Save Changes'); + } else { + $this->requireApplicationCapability( + AlmanacCreateNetworksCapability::CAPABILITY); + + $network = AlmanacNetwork::initializeNewNetwork(); + $is_new = true; + + $cancel_uri = $list_uri; + $title = pht('Create Network'); + $save_button = pht('Create Network'); + } + + $v_name = $network->getName(); + $e_name = true; + $validation_exception = null; + + if ($request->isFormPost()) { + $v_name = $request->getStr('name'); + $v_view = $request->getStr('viewPolicy'); + $v_edit = $request->getStr('editPolicy'); + + $type_name = AlmanacNetworkTransaction::TYPE_NAME; + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $xactions = array(); + + $xactions[] = id(new AlmanacNetworkTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $xactions[] = id(new AlmanacNetworkTransaction()) + ->setTransactionType($type_view) + ->setNewValue($v_view); + + $xactions[] = id(new AlmanacNetworkTransaction()) + ->setTransactionType($type_edit) + ->setNewValue($v_edit); + + $editor = id(new AlmanacNetworkEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($network, $xactions); + + $id = $network->getID(); + $network_uri = $this->getApplicationURI("network/{$id}/"); + return id(new AphrontRedirectResponse())->setURI($network_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_name = $ex->getShortMessage($type_name); + + $network->setViewPolicy($v_view); + $network->setEditPolicy($v_edit); + } + } + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($network) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($v_name) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('viewPolicy') + ->setPolicyObject($network) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('editPolicy') + ->setPolicyObject($network) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue($save_button)); + + $box = id(new PHUIObjectBoxView()) + ->setValidationException($validation_exception) + ->setHeaderText($title) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + if ($is_new) { + $crumbs->addTextCrumb(pht('Create Network')); + } else { + $crumbs->addTextCrumb($network->getName(), $network_uri); + $crumbs->addTextCrumb(pht('Edit')); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/almanac/controller/AlmanacNetworkListController.php b/src/applications/almanac/controller/AlmanacNetworkListController.php new file mode 100644 index 0000000000..96cc7f5ba3 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacNetworkListController.php @@ -0,0 +1,52 @@ +setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new AlmanacNetworkSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + AlmanacCreateNetworksCapability::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Network')) + ->setHref($this->getApplicationURI('network/edit/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + + public function buildSideNavView() { + $viewer = $this->getViewer(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new AlmanacNetworkSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + +} diff --git a/src/applications/almanac/controller/AlmanacNetworkViewController.php b/src/applications/almanac/controller/AlmanacNetworkViewController.php new file mode 100644 index 0000000000..1aee1a790e --- /dev/null +++ b/src/applications/almanac/controller/AlmanacNetworkViewController.php @@ -0,0 +1,95 @@ +getViewer(); + + $id = $request->getURIData('id'); + + $network = id(new AlmanacNetworkQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$network) { + return new Aphront404Response(); + } + + $title = pht('Network %s', $network->getName()); + + $property_list = $this->buildPropertyList($network); + $action_list = $this->buildActionList($network); + $property_list->setActionList($action_list); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($network->getName()) + ->setPolicyObject($network); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($property_list); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($network->getName()); + + $xactions = id(new AlmanacNetworkTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($network->getPHID())) + ->execute(); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setObjectPHID($network->getPHID()) + ->setTransactions($xactions) + ->setShouldTerminate(true); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $xaction_view, + ), + array( + 'title' => $title, + )); + } + + private function buildPropertyList(AlmanacNetwork $network) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + return $properties; + } + + private function buildActionList(AlmanacNetwork $network) { + $viewer = $this->getViewer(); + $id = $network->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $network, + PhabricatorPolicyCapability::CAN_EDIT); + + $actions = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Network')) + ->setHref($this->getApplicationURI("network/edit/{$id}/")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + return $actions; + } + +} diff --git a/src/applications/almanac/controller/AlmanacServiceController.php b/src/applications/almanac/controller/AlmanacServiceController.php new file mode 100644 index 0000000000..5ab1b886d6 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacServiceController.php @@ -0,0 +1,14 @@ +getApplicationURI('service/'); + $crumbs->addTextCrumb(pht('Services'), $list_uri); + + return $crumbs; + } + +} diff --git a/src/applications/almanac/controller/AlmanacServiceEditController.php b/src/applications/almanac/controller/AlmanacServiceEditController.php new file mode 100644 index 0000000000..e8a642656c --- /dev/null +++ b/src/applications/almanac/controller/AlmanacServiceEditController.php @@ -0,0 +1,142 @@ +getViewer(); + + $list_uri = $this->getApplicationURI('service/'); + + $id = $request->getURIData('id'); + if ($id) { + $service = id(new AlmanacServiceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$service) { + return new Aphront404Response(); + } + + $is_new = false; + $service_uri = $service->getURI(); + $cancel_uri = $service_uri; + $title = pht('Edit Service'); + $save_button = pht('Save Changes'); + } else { + $this->requireApplicationCapability( + AlmanacCreateServicesCapability::CAPABILITY); + + $service = AlmanacService::initializeNewService(); + $is_new = true; + + $cancel_uri = $list_uri; + $title = pht('Create Service'); + $save_button = pht('Create Service'); + } + + $v_name = $service->getName(); + $e_name = true; + $validation_exception = null; + + if ($request->isFormPost()) { + $v_name = $request->getStr('name'); + $v_view = $request->getStr('viewPolicy'); + $v_edit = $request->getStr('editPolicy'); + + $type_name = AlmanacServiceTransaction::TYPE_NAME; + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $xactions = array(); + + $xactions[] = id(new AlmanacServiceTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $xactions[] = id(new AlmanacServiceTransaction()) + ->setTransactionType($type_view) + ->setNewValue($v_view); + + $xactions[] = id(new AlmanacServiceTransaction()) + ->setTransactionType($type_edit) + ->setNewValue($v_edit); + + $editor = id(new AlmanacServiceEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($service, $xactions); + + $service_uri = $service->getURI(); + return id(new AphrontRedirectResponse())->setURI($service_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_name = $ex->getShortMessage($type_name); + + $service->setViewPolicy($v_view); + $service->setEditPolicy($v_edit); + } + } + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($service) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($v_name) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('viewPolicy') + ->setPolicyObject($service) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setName('editPolicy') + ->setPolicyObject($service) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) + ->setPolicies($policies)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue($save_button)); + + $box = id(new PHUIObjectBoxView()) + ->setValidationException($validation_exception) + ->setHeaderText($title) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + if ($is_new) { + $crumbs->addTextCrumb(pht('Create Service')); + } else { + $crumbs->addTextCrumb($service->getName(), $service_uri); + $crumbs->addTextCrumb(pht('Edit')); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/almanac/controller/AlmanacServiceListController.php b/src/applications/almanac/controller/AlmanacServiceListController.php new file mode 100644 index 0000000000..abd38e9871 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacServiceListController.php @@ -0,0 +1,52 @@ +setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new AlmanacServiceSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + AlmanacCreateServicesCapability::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Service')) + ->setHref($this->getApplicationURI('service/edit/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + + public function buildSideNavView() { + $viewer = $this->getViewer(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new AlmanacServiceSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + +} diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php new file mode 100644 index 0000000000..8218e3eafe --- /dev/null +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -0,0 +1,145 @@ +getViewer(); + + $name = $request->getURIData('name'); + + $service = id(new AlmanacServiceQuery()) + ->setViewer($viewer) + ->withNames(array($name)) + ->executeOne(); + if (!$service) { + return new Aphront404Response(); + } + + $title = pht('Service %s', $service->getName()); + + $property_list = $this->buildPropertyList($service); + $action_list = $this->buildActionList($service); + $property_list->setActionList($action_list); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($service->getName()) + ->setPolicyObject($service); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($property_list); + + $bindings = $this->buildBindingList($service); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($service->getName()); + + $xactions = id(new AlmanacServiceTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($service->getPHID())) + ->execute(); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setObjectPHID($service->getPHID()) + ->setTransactions($xactions) + ->setShouldTerminate(true); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $bindings, + $xaction_view, + ), + array( + 'title' => $title, + )); + } + + private function buildPropertyList(AlmanacService $service) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + return $properties; + } + + private function buildActionList(AlmanacService $service) { + $viewer = $this->getViewer(); + $id = $service->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $service, + PhabricatorPolicyCapability::CAN_EDIT); + + $actions = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Service')) + ->setHref($this->getApplicationURI("service/edit/{$id}/")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + return $actions; + } + + private function buildBindingList(AlmanacService $service) { + $viewer = $this->getViewer(); + $id = $service->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $service, + PhabricatorPolicyCapability::CAN_EDIT); + + $bindings = id(new AlmanacBindingQuery()) + ->setViewer($viewer) + ->withServicePHIDs(array($service->getPHID())) + ->execute(); + + $phids = array(); + foreach ($bindings as $binding) { + $phids[] = $binding->getServicePHID(); + $phids[] = $binding->getDevicePHID(); + $phids[] = $binding->getInterface()->getNetworkPHID(); + } + $handles = $this->loadViewerHandles($phids); + + $table = id(new AlmanacBindingTableView()) + ->setNoDataString( + pht('This service has not been bound to any device interfaces yet.')) + ->setUser($viewer) + ->setBindings($bindings) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Service Bindings')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($this->getApplicationURI("binding/edit/?serviceID={$id}")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit) + ->setText(pht('Add Binding')) + ->setIcon( + id(new PHUIIconView()) + ->setIconFont('fa-plus'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + } + +} diff --git a/src/applications/almanac/editor/AlmanacBindingEditor.php b/src/applications/almanac/editor/AlmanacBindingEditor.php new file mode 100644 index 0000000000..28eb233c6f --- /dev/null +++ b/src/applications/almanac/editor/AlmanacBindingEditor.php @@ -0,0 +1,139 @@ +getTransactionType()) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + return $object->getInterfacePHID(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + $interface = id(new AlmanacInterfaceQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs(array($xaction->getNewValue())) + ->executeOne(); + $object->setDevicePHID($interface->getDevicePHID()); + $object->setInterfacePHID($interface->getPHID()); + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + $missing = $this->validateIsEmptyTextField( + $object->getInterfacePHID(), + $xactions); + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Bindings must specify an interface.'), + nonempty(last($xactions), null)); + $error->setIsMissingFieldError(true); + $errors[] = $error; + } else if ($xactions) { + foreach ($xactions as $xaction) { + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs(array($xaction->getNewValue())) + ->execute(); + if (!$interfaces) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can not bind a service to an invalid or restricted '. + 'interface.'), + $xaction); + $errors[] = $error; + } + } + + $final_value = last($xactions)->getNewValue(); + + $binding = id(new AlmanacBindingQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withServicePHIDs(array($object->getServicePHID())) + ->withInterfacePHIDs(array($final_value)) + ->executeOne(); + if ($binding && ($binding->getID() != $object->getID())) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Already Bound'), + pht( + 'You can not bind a service to the same interface multiple '. + 'times.'), + last($xactions)); + $errors[] = $error; + } + } + break; + } + + return $errors; + } + + + +} diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php new file mode 100644 index 0000000000..fbf8c29bbb --- /dev/null +++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php @@ -0,0 +1,292 @@ +getTransactionType()) { + case AlmanacDeviceTransaction::TYPE_NAME: + return $object->getName(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacDeviceTransaction::TYPE_NAME: + case AlmanacDeviceTransaction::TYPE_INTERFACE: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacDeviceTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + case AlmanacDeviceTransaction::TYPE_INTERFACE: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacDeviceTransaction::TYPE_NAME: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return; + case AlmanacDeviceTransaction::TYPE_INTERFACE: + $old = $xaction->getOldValue(); + if ($old) { + $interface = id(new AlmanacInterfaceQuery()) + ->setViewer($this->requireActor()) + ->withIDs(array($old['id'])) + ->executeOne(); + if (!$interface) { + throw new Exception(pht('Unable to load interface!')); + } + } else { + $interface = AlmanacInterface::initializeNewInterface() + ->setDevicePHID($object->getPHID()); + } + + $new = $xaction->getNewValue(); + if ($new) { + $interface + ->setNetworkPHID($new['networkPHID']) + ->setAddress($new['address']) + ->setPort((int)$new['port']) + ->save(); + } else { + $interface->delete(); + } + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case AlmanacDeviceTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Device name is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } else { + foreach ($xactions as $xaction) { + $message = null; + $name = $xaction->getNewValue(); + + try { + AlmanacNames::validateServiceOrDeviceName($name); + } catch (Exception $ex) { + $message = $ex->getMessage(); + } + + if ($message !== null) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + $message, + $xaction); + $errors[] = $error; + } + } + } + + if ($xactions) { + $duplicate = id(new AlmanacDeviceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withNames(array(last($xactions)->getNewValue())) + ->executeOne(); + if ($duplicate && ($duplicate->getID() != $object->getID())) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Not Unique'), + pht('Almanac devices must have unique names.'), + last($xactions)); + $errors[] = $error; + } + } + + break; + case AlmanacDeviceTransaction::TYPE_INTERFACE: + // We want to make sure that all the affected networks are visible to + // the actor, any edited interfaces exist, and that the actual address + // components are valid. + + $network_phids = array(); + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + if ($old) { + $network_phids[] = $old['networkPHID']; + } + if ($new) { + $network_phids[] = $new['networkPHID']; + + $address = $new['address']; + if (!strlen($address)) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('Interfaces must have an address.'), + $xaction); + $errors[] = $error; + } else { + // TODO: Validate addresses, but IPv6 addresses are not trival + // to validate. + } + + $port = $new['port']; + if (!strlen($port)) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('Interfaces must have a port.'), + $xaction); + $errors[] = $error; + } else if ((int)$port < 1 || (int)$port > 65535) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Port numbers must be between 1 and 65535, inclusive.'), + $xaction); + $errors[] = $error; + } + } + } + + if ($network_phids) { + $networks = id(new AlmanacNetworkQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs($network_phids) + ->execute(); + $networks = mpull($networks, null, 'getPHID'); + } else { + $networks = array(); + } + + $addresses = array(); + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + if ($old) { + $network = idx($networks, $old['networkPHID']); + if (!$network) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can not edit an interface which belongs to a '. + 'nonexistent or restricted network.'), + $xaction); + $errors[] = $error; + } + + $addresses[] = $old['id']; + } + + $new = $xaction->getNewValue(); + if ($new) { + $network = idx($networks, $new['networkPHID']); + if (!$network) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can not add an interface on a nonexistent or '. + 'restricted network.'), + $xaction); + $errors[] = $error; + } + } + } + + if ($addresses) { + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($this->requireActor()) + ->withDevicePHIDs(array($object->getPHID())) + ->withIDs($addresses) + ->execute(); + $interfaces = mpull($interfaces, null, 'getID'); + } else { + $interfaces = array(); + } + + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + if ($old) { + $interface = idx($interfaces, $old['id']); + if (!$interface) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You can not edit an invalid or restricted interface.'), + $xaction); + $errors[] = $error; + } + } + } + break; + } + + return $errors; + } + + + +} diff --git a/src/applications/almanac/editor/AlmanacNetworkEditor.php b/src/applications/almanac/editor/AlmanacNetworkEditor.php new file mode 100644 index 0000000000..779a0c7bda --- /dev/null +++ b/src/applications/almanac/editor/AlmanacNetworkEditor.php @@ -0,0 +1,108 @@ +getTransactionType()) { + case AlmanacNetworkTransaction::TYPE_NAME: + return $object->getName(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacNetworkTransaction::TYPE_NAME: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacNetworkTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacNetworkTransaction::TYPE_NAME: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case AlmanacServiceTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Network name is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + + + +} diff --git a/src/applications/almanac/editor/AlmanacServiceEditor.php b/src/applications/almanac/editor/AlmanacServiceEditor.php new file mode 100644 index 0000000000..1a3b273751 --- /dev/null +++ b/src/applications/almanac/editor/AlmanacServiceEditor.php @@ -0,0 +1,145 @@ +getTransactionType()) { + case AlmanacServiceTransaction::TYPE_NAME: + return $object->getName(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacServiceTransaction::TYPE_NAME: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacServiceTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacServiceTransaction::TYPE_NAME: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case AlmanacServiceTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Service name is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } else { + foreach ($xactions as $xaction) { + $message = null; + + $name = $xaction->getNewValue(); + + try { + AlmanacNames::validateServiceOrDeviceName($name); + } catch (Exception $ex) { + $message = $ex->getMessage(); + } + + if ($message !== null) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + $message, + $xaction); + $errors[] = $error; + } + } + } + + if ($xactions) { + $duplicate = id(new AlmanacServiceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withNames(array(last($xactions)->getNewValue())) + ->executeOne(); + if ($duplicate && ($duplicate->getID() != $object->getID())) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Not Unique'), + pht('Almanac services must have unique names.'), + last($xactions)); + $errors[] = $error; + } + } + + break; + } + + return $errors; + } + + + +} diff --git a/src/applications/almanac/phid/AlmanacBindingPHIDType.php b/src/applications/almanac/phid/AlmanacBindingPHIDType.php new file mode 100644 index 0000000000..d8fcb510fb --- /dev/null +++ b/src/applications/almanac/phid/AlmanacBindingPHIDType.php @@ -0,0 +1,38 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $binding = $objects[$phid]; + + $id = $binding->getID(); + + $handle->setObjectName(pht('Binding %d', $id)); + $handle->setName(pht('Binding %d', $id)); + } + } + +} diff --git a/src/applications/almanac/phid/AlmanacDevicePHIDType.php b/src/applications/almanac/phid/AlmanacDevicePHIDType.php index 56dc8a5d2f..8a1bb36a90 100644 --- a/src/applications/almanac/phid/AlmanacDevicePHIDType.php +++ b/src/applications/almanac/phid/AlmanacDevicePHIDType.php @@ -33,6 +33,7 @@ final class AlmanacDevicePHIDType extends PhabricatorPHIDType { $handle->setObjectName(pht('Device %d', $id)); $handle->setName($name); + $handle->setURI($device->getURI()); } } diff --git a/src/applications/almanac/phid/AlmanacInterfacePHIDType.php b/src/applications/almanac/phid/AlmanacInterfacePHIDType.php new file mode 100644 index 0000000000..67f9b1664a --- /dev/null +++ b/src/applications/almanac/phid/AlmanacInterfacePHIDType.php @@ -0,0 +1,50 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $interface = $objects[$phid]; + + $id = $interface->getID(); + + $device_name = $interface->getDevice()->getName(); + $address = $interface->getAddress(); + $port = $interface->getPort(); + $network = $interface->getNetwork()->getName(); + + $name = pht( + '%s:%s (%s on %s)', + $device_name, + $port, + $address, + $network); + + $handle->setObjectName(pht('Interface %d', $id)); + $handle->setName($name); + } + } + +} diff --git a/src/applications/almanac/phid/AlmanacNetworkPHIDType.php b/src/applications/almanac/phid/AlmanacNetworkPHIDType.php new file mode 100644 index 0000000000..e27efa5cd8 --- /dev/null +++ b/src/applications/almanac/phid/AlmanacNetworkPHIDType.php @@ -0,0 +1,40 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $network = $objects[$phid]; + + $id = $network->getID(); + $name = $network->getName(); + + $handle->setObjectName(pht('Network %d', $id)); + $handle->setName($name); + $handle->setURI($network->getURI()); + } + } + +} diff --git a/src/applications/almanac/phid/AlmanacServicePHIDType.php b/src/applications/almanac/phid/AlmanacServicePHIDType.php new file mode 100644 index 0000000000..c64e089ce6 --- /dev/null +++ b/src/applications/almanac/phid/AlmanacServicePHIDType.php @@ -0,0 +1,40 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $service = $objects[$phid]; + + $id = $service->getID(); + $name = $service->getName(); + + $handle->setObjectName(pht('Service %d', $id)); + $handle->setName($name); + $handle->setURI($service->getURI()); + } + } + +} diff --git a/src/applications/almanac/query/AlmanacBindingQuery.php b/src/applications/almanac/query/AlmanacBindingQuery.php new file mode 100644 index 0000000000..28bb25f598 --- /dev/null +++ b/src/applications/almanac/query/AlmanacBindingQuery.php @@ -0,0 +1,143 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withServicePHIDs(array $phids) { + $this->servicePHIDs = $phids; + return $this; + } + + public function withDevicePHIDs(array $phids) { + $this->devicePHIDs = $phids; + return $this; + } + + public function withInterfacePHIDs(array $phids) { + $this->interfacePHIDs = $phids; + return $this; + } + + protected function loadPage() { + $table = new AlmanacBinding(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function willFilterPage(array $bindings) { + $service_phids = mpull($bindings, 'getServicePHID'); + $device_phids = mpull($bindings, 'getDevicePHID'); + $interface_phids = mpull($bindings, 'getInterfacePHID'); + + $services = id(new AlmanacServiceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($service_phids) + ->execute(); + $services = mpull($services, null, 'getPHID'); + + $devices = id(new AlmanacDeviceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($device_phids) + ->execute(); + $devices = mpull($devices, null, 'getPHID'); + + $interfaces = id(new AlmanacInterfaceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($interface_phids) + ->execute(); + $interfaces = mpull($interfaces, null, 'getPHID'); + + foreach ($bindings as $key => $binding) { + $service = idx($services, $binding->getServicePHID()); + $device = idx($devices, $binding->getDevicePHID()); + $interface = idx($interfaces, $binding->getInterfacePHID()); + if (!$service || !$device || !$interface) { + $this->didRejectResult($binding); + unset($bindings[$key]); + continue; + } + + $binding->attachService($service); + $binding->attachDevice($device); + $binding->attachInterface($interface); + } + + return $bindings; + } + + protected function buildWhereClause($conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->servicePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'servicePHID IN (%Ls)', + $this->servicePHIDs); + } + + if ($this->devicePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'devicePHID IN (%Ls)', + $this->devicePHIDs); + } + + if ($this->interfacePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'interfacePHID IN (%Ls)', + $this->interfacePHIDs); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/query/AlmanacBindingTransactionQuery.php b/src/applications/almanac/query/AlmanacBindingTransactionQuery.php new file mode 100644 index 0000000000..2b002e4f4a --- /dev/null +++ b/src/applications/almanac/query/AlmanacBindingTransactionQuery.php @@ -0,0 +1,10 @@ +ids = $ids; @@ -16,6 +18,16 @@ final class AlmanacDeviceQuery return $this; } + public function withNames(array $names) { + $this->names = $names; + return $this; + } + + public function withDatasourceQuery($query) { + $this->datasourceQuery = $query; + return $this; + } + protected function loadPage() { $table = new AlmanacDevice(); $conn_r = $table->establishConnection('r'); @@ -48,6 +60,24 @@ final class AlmanacDeviceQuery $this->phids); } + if ($this->names !== null) { + $hashes = array(); + foreach ($this->names as $name) { + $hashes[] = PhabricatorHash::digestForIndex($name); + } + $where[] = qsprintf( + $conn_r, + 'nameIndex IN (%Ls)', + $hashes); + } + + if ($this->datasourceQuery !== null) { + $where[] = qsprintf( + $conn_r, + 'name LIKE %>', + $this->datasourceQuery); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); diff --git a/src/applications/almanac/query/AlmanacDeviceSearchEngine.php b/src/applications/almanac/query/AlmanacDeviceSearchEngine.php new file mode 100644 index 0000000000..b23d0ecb8c --- /dev/null +++ b/src/applications/almanac/query/AlmanacDeviceSearchEngine.php @@ -0,0 +1,84 @@ + pht('All Devices'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function getRequiredHandlePHIDsForResultList( + array $devices, + PhabricatorSavedQuery $query) { + return array(); + } + + protected function renderResultList( + array $devices, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($devices, 'AlmanacDevice'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($devices as $device) { + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Device %d', $device->getID())) + ->setHeader($device->getName()) + ->setHref($device->getURI()) + ->setObject($device); + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/almanac/query/AlmanacDeviceTransactionQuery.php b/src/applications/almanac/query/AlmanacDeviceTransactionQuery.php new file mode 100644 index 0000000000..8b50fa85e5 --- /dev/null +++ b/src/applications/almanac/query/AlmanacDeviceTransactionQuery.php @@ -0,0 +1,10 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withNetworkPHIDs(array $phids) { + $this->networkPHIDs = $phids; + return $this; + } + + public function withDevicePHIDs(array $phids) { + $this->devicePHIDs = $phids; + return $this; + } + + public function withAddresses(array $addresses) { + $this->addresses = $addresses; + return $this; + } + + protected function loadPage() { + $table = new AlmanacInterface(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function willFilterPage(array $interfaces) { + $network_phids = mpull($interfaces, 'getNetworkPHID'); + $device_phids = mpull($interfaces, 'getDevicePHID'); + + $networks = id(new AlmanacNetworkQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($network_phids) + ->execute(); + $networks = mpull($networks, null, 'getPHID'); + + $devices = id(new AlmanacDeviceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($device_phids) + ->execute(); + $devices = mpull($devices, null, 'getPHID'); + + foreach ($interfaces as $key => $interface) { + $network = idx($networks, $interface->getNetworkPHID()); + $device = idx($devices, $interface->getDevicePHID()); + if (!$network || !$device) { + $this->didRejectResult($interface); + unset($interfaces[$key]); + continue; + } + + $interface->attachNetwork($network); + $interface->attachDevice($device); + } + + return $interfaces; + } + + protected function buildWhereClause($conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->networkPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'networkPHID IN (%Ls)', + $this->networkPHIDs); + } + + if ($this->devicePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'devicePHID IN (%Ls)', + $this->devicePHIDs); + } + + if ($this->addresses !== null) { + $parts = array(); + foreach ($this->addresses as $address) { + $parts[] = qsprintf( + $conn_r, + '(networkPHID = %s AND address = %s AND port = %d)', + $address->getNetworkPHID(), + $address->getAddress(), + $address->getPort()); + } + $where[] = implode(' OR ', $parts); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/query/AlmanacNetworkQuery.php b/src/applications/almanac/query/AlmanacNetworkQuery.php new file mode 100644 index 0000000000..1eb43fa828 --- /dev/null +++ b/src/applications/almanac/query/AlmanacNetworkQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new AlmanacNetwork(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function buildWhereClause($conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/query/AlmanacNetworkSearchEngine.php b/src/applications/almanac/query/AlmanacNetworkSearchEngine.php new file mode 100644 index 0000000000..955d162096 --- /dev/null +++ b/src/applications/almanac/query/AlmanacNetworkSearchEngine.php @@ -0,0 +1,85 @@ + pht('All Networks'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function getRequiredHandlePHIDsForResultList( + array $networks, + PhabricatorSavedQuery $query) { + return array(); + } + + protected function renderResultList( + array $networks, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($networks, 'AlmanacNetwork'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($networks as $network) { + $id = $network->getID(); + + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Network %d', $id)) + ->setHeader($network->getName()) + ->setHref($this->getApplicationURI("network/{$id}/")) + ->setObject($network); + + $list->addItem($item); + } + + return $list; + } +} diff --git a/src/applications/almanac/query/AlmanacNetworkTransactionQuery.php b/src/applications/almanac/query/AlmanacNetworkTransactionQuery.php new file mode 100644 index 0000000000..7be51efec0 --- /dev/null +++ b/src/applications/almanac/query/AlmanacNetworkTransactionQuery.php @@ -0,0 +1,10 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withNames(array $names) { + $this->names = $names; + return $this; + } + + protected function loadPage() { + $table = new AlmanacService(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function buildWhereClause($conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->names !== null) { + $hashes = array(); + foreach ($this->names as $name) { + $hashes[] = PhabricatorHash::digestForIndex($name); + } + + $where[] = qsprintf( + $conn_r, + 'nameIndex IN (%Ls)', + $hashes); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/query/AlmanacServiceSearchEngine.php b/src/applications/almanac/query/AlmanacServiceSearchEngine.php new file mode 100644 index 0000000000..22384c871e --- /dev/null +++ b/src/applications/almanac/query/AlmanacServiceSearchEngine.php @@ -0,0 +1,83 @@ + pht('All Services'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function getRequiredHandlePHIDsForResultList( + array $services, + PhabricatorSavedQuery $query) { + return array(); + } + + protected function renderResultList( + array $services, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($services, 'AlmanacService'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($services as $service) { + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Service %d', $service->getID())) + ->setHeader($service->getName()) + ->setHref($service->getURI()) + ->setObject($service); + + $list->addItem($item); + } + + return $list; + } +} diff --git a/src/applications/almanac/query/AlmanacServiceTransactionQuery.php b/src/applications/almanac/query/AlmanacServiceTransactionQuery.php new file mode 100644 index 0000000000..96bf93377b --- /dev/null +++ b/src/applications/almanac/query/AlmanacServiceTransactionQuery.php @@ -0,0 +1,10 @@ +setServicePHID($service->getPHID()); + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'mailKey' => 'bytes20', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_service' => array( + 'columns' => array('servicePHID', 'interfacePHID'), + 'unique' => true, + ), + 'key_device' => array( + 'columns' => array('devicePHID'), + ), + 'key_interface' => array( + 'columns' => array('interfacePHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID(AlmanacBindingPHIDType::TYPECONST); + } + + public function save() { + if (!$this->mailKey) { + $this->mailKey = Filesystem::readRandomCharacters(20); + } + return parent::save(); + } + + public function getURI() { + return '/almanac/binding/'.$this->getID().'/'; + } + + public function getService() { + return $this->assertAttached($this->service); + } + + public function attachService(AlmanacService $service) { + $this->service = $service; + return $this; + } + + public function getDevice() { + return $this->assertAttached($this->device); + } + + public function attachDevice(AlmanacDevice $device) { + $this->device = $device; + return $this; + } + + public function getInterface() { + return $this->assertAttached($this->interface); + } + + public function attachInterface(AlmanacInterface $interface) { + $this->interface = $interface; + return $this; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return $this->getService()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getService()->hasAutomaticCapability($capability, $viewer); + } + + public function describeAutomaticCapability($capability) { + return array( + pht('A binding inherits the policies of its service.'), + pht( + 'To view a binding, you must also be able to view its device and '. + 'interface.'), + ); + } + +} diff --git a/src/applications/almanac/storage/AlmanacBindingTransaction.php b/src/applications/almanac/storage/AlmanacBindingTransaction.php new file mode 100644 index 0000000000..4ea4909bb6 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacBindingTransaction.php @@ -0,0 +1,65 @@ +getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_INTERFACE: + if ($old) { + $phids[] = $old; + } + if ($new) { + $phids[] = $new; + } + break; + } + + return $phids; + } + + public function getTitle() { + $author_phid = $this->getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_INTERFACE: + if ($old === null) { + return pht( + '%s created this binding.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s changed this binding from %s to %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + } + break; + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/almanac/storage/AlmanacDevice.php b/src/applications/almanac/storage/AlmanacDevice.php index d5d41a6195..52d7c453e8 100644 --- a/src/applications/almanac/storage/AlmanacDevice.php +++ b/src/applications/almanac/storage/AlmanacDevice.php @@ -5,12 +5,33 @@ final class AlmanacDevice implements PhabricatorPolicyInterface { protected $name; + protected $nameIndex; + protected $mailKey; + protected $viewPolicy; + protected $editPolicy; + + public static function initializeNewDevice() { + return id(new AlmanacDevice()) + ->setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN); + } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text255', + 'name' => 'text128', + 'nameIndex' => 'bytes12', + 'mailKey' => 'bytes20', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_name' => array( + 'columns' => array('nameIndex'), + 'unique' => true, + ), + 'key_nametext' => array( + 'columns' => array('name'), + ), ), ) + parent::getConfiguration(); } @@ -19,6 +40,22 @@ final class AlmanacDevice return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST); } + public function save() { + AlmanacNames::validateServiceOrDeviceName($this->getName()); + + $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); + + if (!$this->mailKey) { + $this->mailKey = Filesystem::readRandomCharacters(20); + } + + return parent::save(); + } + + public function getURI() { + return '/almanac/device/view/'.$this->getName().'/'; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -26,16 +63,16 @@ final class AlmanacDevice public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: - // Until we get a clearer idea on what's going to be stored in this - // table, don't allow anyone (other than the omnipotent user) to find - // these objects. - return PhabricatorPolicies::POLICY_NOONE; + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); } } diff --git a/src/applications/almanac/storage/AlmanacDeviceTransaction.php b/src/applications/almanac/storage/AlmanacDeviceTransaction.php new file mode 100644 index 0000000000..b80b74af18 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacDeviceTransaction.php @@ -0,0 +1,100 @@ +getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_INTERFACE: + if ($old) { + $phids[] = $old['networkPHID']; + } + if ($new) { + $phids[] = $new['networkPHID']; + } + break; + } + + return $phids; + } + + public function getTitle() { + $author_phid = $this->getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this device.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this device from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + break; + case self::TYPE_INTERFACE: + if ($old && $new) { + return pht( + '%s changed interface %s on this device to %s.', + $this->renderHandleLink($author_phid), + $this->describeInterface($old), + $this->describeInterface($new)); + } else if ($old) { + return pht( + '%s removed the interface %s from this device.', + $this->renderHandleLink($author_phid), + $this->describeInterface($new)); + } else if ($new) { + return pht( + '%s added the interface %s to this device.', + $this->renderHandleLink($author_phid), + $this->describeInterface($new)); + } + } + + return parent::getTitle(); + } + + public function shouldGenerateOldValue() { + switch ($this->getTransactionType()) { + case self::TYPE_INTERFACE: + return false; + } + return parent::shouldGenerateOldValue(); + } + + private function describeInterface(array $info) { + return pht( + '%s:%s (%s)', + $info['address'], + $info['port'], + $this->renderHandleLink($info['networkPHID'])); + } + +} diff --git a/src/applications/almanac/storage/AlmanacInterface.php b/src/applications/almanac/storage/AlmanacInterface.php new file mode 100644 index 0000000000..aa95cb4236 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacInterface.php @@ -0,0 +1,103 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'address' => 'text64', + 'port' => 'uint32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_location' => array( + 'columns' => array('networkPHID', 'address', 'port'), + ), + 'key_device' => array( + 'columns' => array('devicePHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + AlmanacInterfacePHIDType::TYPECONST); + } + + public function getDevice() { + return $this->assertAttached($this->device); + } + + public function attachDevice(AlmanacDevice $device) { + $this->device = $device; + return $this; + } + + public function getNetwork() { + return $this->assertAttached($this->network); + } + + public function attachNetwork(AlmanacNetwork $network) { + $this->network = $network; + return $this; + } + + public function toAddress() { + return AlmanacAddress::newFromParts( + $this->getNetworkPHID(), + $this->getAddress(), + $this->getPort()); + } + + public function getAddressHash() { + return $this->toAddress()->toHash(); + } + + public function renderDisplayAddress() { + return $this->getAddress().':'.$this->getPort(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return $this->getDevice()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getDevice()->hasAutomaticCapability($capability, $viewer); + } + + public function describeAutomaticCapability($capability) { + return array( + pht('An interface inherits the policies of the device it belongs to.'), + pht( + 'You must be able to view the network an interface resides on to '. + 'view the interface.'), + ); + } + +} diff --git a/src/applications/almanac/storage/AlmanacNetwork.php b/src/applications/almanac/storage/AlmanacNetwork.php new file mode 100644 index 0000000000..c21d8c792c --- /dev/null +++ b/src/applications/almanac/storage/AlmanacNetwork.php @@ -0,0 +1,72 @@ +setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN); + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text128', + 'mailKey' => 'bytes20', + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID(AlmanacNetworkPHIDType::TYPECONST); + } + + public function save() { + if (!$this->mailKey) { + $this->mailKey = Filesystem::readRandomCharacters(20); + } + + return parent::save(); + } + + public function getURI() { + return '/almanac/network/view/'.$this->getName().'/'; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/almanac/storage/AlmanacNetworkTransaction.php b/src/applications/almanac/storage/AlmanacNetworkTransaction.php new file mode 100644 index 0000000000..7d2f81c984 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacNetworkTransaction.php @@ -0,0 +1,45 @@ +getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this network.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this network from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + break; + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php new file mode 100644 index 0000000000..4ea48a5729 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacService.php @@ -0,0 +1,86 @@ +setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN); + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text128', + 'nameIndex' => 'bytes12', + 'mailKey' => 'bytes20', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_name' => array( + 'columns' => array('nameIndex'), + 'unique' => true, + ), + 'key_nametext' => array( + 'columns' => array('name'), + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID(AlmanacServicePHIDType::TYPECONST); + } + + public function save() { + AlmanacNames::validateServiceOrDeviceName($this->getName()); + + $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); + + if (!$this->mailKey) { + $this->mailKey = Filesystem::readRandomCharacters(20); + } + + return parent::save(); + } + + public function getURI() { + return '/almanac/service/view/'.$this->getName().'/'; + } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/almanac/storage/AlmanacServiceTransaction.php b/src/applications/almanac/storage/AlmanacServiceTransaction.php new file mode 100644 index 0000000000..6f2a9aa52b --- /dev/null +++ b/src/applications/almanac/storage/AlmanacServiceTransaction.php @@ -0,0 +1,45 @@ +getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this service.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this service from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + break; + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php b/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php new file mode 100644 index 0000000000..4a3053d357 --- /dev/null +++ b/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php @@ -0,0 +1,51 @@ +getViewer(); + $raw_query = $this->getRawQuery(); + + $devices = id(new AlmanacDeviceQuery()) + ->setViewer($viewer) + ->withDatasourceQuery($raw_query) + ->execute(); + + if ($devices) { + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($viewer) + ->withDevicePHIDs(mpull($devices, 'getPHID')) + ->execute(); + } else { + $interfaces = array(); + } + + if ($interfaces) { + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs(mpull($interfaces, 'getPHID')) + ->execute(); + } else { + $handles = array(); + } + + $results = array(); + foreach ($handles as $handle) { + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($handle->getName()) + ->setPHID($handle->getPHID()); + } + + return $results; + } + +} diff --git a/src/applications/almanac/util/AlmanacAddress.php b/src/applications/almanac/util/AlmanacAddress.php new file mode 100644 index 0000000000..2d33579a32 --- /dev/null +++ b/src/applications/almanac/util/AlmanacAddress.php @@ -0,0 +1,54 @@ + + } + + public function getNetworkPHID() { + return $this->networkPHID; + } + + public function getAddress() { + return $this->address; + } + + public function getPort() { + return $this->port; + } + + public static function newFromDictionary(array $dictionary) { + return self::newFromParts( + $dictionary['networkPHID'], + $dictionary['address'], + $dictionary['port']); + } + + public static function newFromParts($network_phid, $address, $port) { + $addr = new AlmanacAddress(); + + $addr->networkPHID = $network_phid; + $addr->address = $address; + $addr->port = (int)$port; + + return $addr; + } + + public function toDictionary() { + return array( + 'networkPHID' => $this->getNetworkPHID(), + 'address' => $this->getAddress(), + 'port' => $this->getPort(), + ); + } + + public function toHash() { + return PhabricatorHash::digestForIndex(json_encode($this->toDictionary())); + } + +} diff --git a/src/applications/almanac/util/AlmanacNames.php b/src/applications/almanac/util/AlmanacNames.php new file mode 100644 index 0000000000..d65779264d --- /dev/null +++ b/src/applications/almanac/util/AlmanacNames.php @@ -0,0 +1,56 @@ + false, + 'a' => false, + 'ab' => false, + '...' => false, + 'ab.' => false, + '.ab' => false, + 'A-B' => false, + 'A!B' => false, + 'A.B' => false, + 'a..b' => false, + '1.2' => false, + '127.0.0.1' => false, + '1.b' => false, + 'a.1' => false, + 'a.1.b' => false, + '-.a' => false, + '-a.b' => false, + 'a-.b' => false, + 'a.-' => false, + 'a.-b' => false, + 'a.b-' => false, + '-.-' => false, + 'a--b' => false, + + 'abc' => true, + 'a.b' => true, + 'db.phacility.instance' => true, + 'web002.useast.example.com' => true, + 'master.example-corp.com' => true, + ); + + foreach ($map as $input => $expect) { + $caught = null; + try { + AlmanacNames::validateServiceOrDeviceName($input); + } catch (Exception $ex) { + $caught = $ex; + } + $this->assertEqual( + $expect, + !($caught instanceof Exception), + $input); + } + } +} diff --git a/src/applications/almanac/view/AlmanacBindingTableView.php b/src/applications/almanac/view/AlmanacBindingTableView.php new file mode 100644 index 0000000000..d6c27f62c9 --- /dev/null +++ b/src/applications/almanac/view/AlmanacBindingTableView.php @@ -0,0 +1,86 @@ +noDataString = $no_data_string; + return $this; + } + + public function getNoDataString() { + return $this->noDataString; + } + + public function setHandles(array $handles) { + $this->handles = $handles; + return $this; + } + + public function getHandles() { + return $this->handles; + } + + public function setBindings(array $bindings) { + $this->bindings = $bindings; + return $this; + } + + public function getBindings() { + return $this->bindings; + } + + public function render() { + $bindings = $this->getBindings(); + $handles = $this->getHandles(); + $viewer = $this->getUser(); + + $rows = array(); + foreach ($bindings as $binding) { + $addr = $binding->getInterface()->getAddress(); + $port = $binding->getInterface()->getPort(); + + $rows[] = array( + $binding->getID(), + $handles[$binding->getServicePHID()]->renderLink(), + $handles[$binding->getDevicePHID()]->renderLink(), + $handles[$binding->getInterface()->getNetworkPHID()]->renderLink(), + $binding->getInterface()->renderDisplayAddress(), + phutil_tag( + 'a', + array( + 'class' => 'small grey button', + 'href' => '/almanac/binding/'.$binding->getID().'/', + ), + pht('Details')), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setNoDataString($this->getNoDataString()) + ->setHeaders( + array( + pht('ID'), + pht('Service'), + pht('Device'), + pht('Network'), + pht('Interface'), + null, + )) + ->setColumnClasses( + array( + '', + '', + '', + '', + 'wide', + 'action', + )); + + return $table; + } + +} diff --git a/src/applications/almanac/view/AlmanacInterfaceTableView.php b/src/applications/almanac/view/AlmanacInterfaceTableView.php new file mode 100644 index 0000000000..68822677b0 --- /dev/null +++ b/src/applications/almanac/view/AlmanacInterfaceTableView.php @@ -0,0 +1,69 @@ +handles = $handles; + return $this; + } + + public function getHandles() { + return $this->handles; + } + + public function setInterfaces(array $interfaces) { + $this->interfaces = $interfaces; + return $this; + } + + public function getInterfaces() { + return $this->interfaces; + } + + public function render() { + $interfaces = $this->getInterfaces(); + $handles = $this->getHandles(); + $viewer = $this->getUser(); + + $rows = array(); + foreach ($interfaces as $interface) { + $rows[] = array( + $interface->getID(), + $handles[$interface->getNetworkPHID()]->renderLink(), + $interface->getAddress(), + $interface->getPort(), + phutil_tag( + 'a', + array( + 'class' => 'small grey button', + 'href' => '/almanac/interface/edit/'.$interface->getID().'/', + ), + pht('Edit')), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('ID'), + pht('Network'), + pht('Address'), + pht('Port'), + null, + )) + ->setColumnClasses( + array( + '', + 'wide', + '', + '', + 'action', + )); + + return $table; + } + +} diff --git a/src/applications/audit/constants/PhabricatorAuditStatusConstants.php b/src/applications/audit/constants/PhabricatorAuditStatusConstants.php index f80760d614..e1a4378ab3 100644 --- a/src/applications/audit/constants/PhabricatorAuditStatusConstants.php +++ b/src/applications/audit/constants/PhabricatorAuditStatusConstants.php @@ -38,18 +38,50 @@ final class PhabricatorAuditStatusConstants { $color = 'red'; break; case self::AUDIT_REQUIRED: + case self::AUDIT_REQUESTED: $color = 'orange'; break; case self::ACCEPTED: $color = 'green'; break; + case self::AUDIT_NOT_REQUIRED: + $color = 'blue'; + break; + case self::RESIGNED: + case self::CLOSED: + $color = 'dark'; + break; default: - $color = null; + $color = 'bluegrey'; break; } return $color; } + public static function getStatusIcon($code) { + switch ($code) { + case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED: + case PhabricatorAuditStatusConstants::RESIGNED: + $icon = PHUIStatusItemView::ICON_OPEN; + break; + case PhabricatorAuditStatusConstants::AUDIT_REQUIRED: + case PhabricatorAuditStatusConstants::AUDIT_REQUESTED: + $icon = PHUIStatusItemView::ICON_WARNING; + break; + case PhabricatorAuditStatusConstants::CONCERNED: + $icon = PHUIStatusItemView::ICON_REJECT; + break; + case PhabricatorAuditStatusConstants::ACCEPTED: + case PhabricatorAuditStatusConstants::CLOSED: + $icon = PHUIStatusItemView::ICON_ACCEPT; + break; + default: + $icon = PHUIStatusItemView::ICON_QUESTION; + break; + } + return $icon; + } + public static function getOpenStatusConstants() { return array( self::AUDIT_REQUIRED, diff --git a/src/applications/audit/controller/PhabricatorAuditListController.php b/src/applications/audit/controller/PhabricatorAuditListController.php index f68862fa3c..4d2d7bf37e 100644 --- a/src/applications/audit/controller/PhabricatorAuditListController.php +++ b/src/applications/audit/controller/PhabricatorAuditListController.php @@ -3,22 +3,13 @@ final class PhabricatorAuditListController extends PhabricatorAuditController { - private $queryKey; - private $name; - private $filterStatus; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } - - public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) - ->setQueryKey($this->queryKey) + public function handleRequest(AphrontRequest $request) { + $controller = id(new PhabricatorApplicationSearchController()) + ->setQueryKey($request->getURIData('queryKey')) ->setSearchEngine(new PhabricatorCommitSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 7b66ddc843..74e460afdf 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -3,6 +3,42 @@ final class PhabricatorAuditEditor extends PhabricatorApplicationTransactionEditor { + const MAX_FILES_SHOWN_IN_EMAIL = 1000; + + private $auditReasonMap = array(); + private $heraldEmailPHIDs = array(); + private $affectedFiles; + private $rawPatch; + + public function addAuditReason($phid, $reason) { + if (!isset($this->auditReasonMap[$phid])) { + $this->auditReasonMap[$phid] = array(); + } + $this->auditReasonMap[$phid][] = $reason; + return $this; + } + + private function getAuditReasons($phid) { + if (isset($this->auditReasonMap[$phid])) { + return $this->auditReasonMap[$phid]; + } + if ($this->getIsHeraldEditor()) { + $name = 'herald'; + } else { + $name = $this->getActor()->getUsername(); + } + return array('Added by '.$name.'.'); + } + + public function setRawPatch($patch) { + $this->rawPatch = $patch; + return $this; + } + + public function getRawPatch() { + return $this->rawPatch; + } + public function getEditorApplicationClass() { return 'PhabricatorAuditApplication'; } @@ -17,6 +53,8 @@ final class PhabricatorAuditEditor $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_EDGE; + $types[] = PhabricatorAuditTransaction::TYPE_COMMIT; + // TODO: These will get modernized eventually, but that can happen one // at a time later on. $types[] = PhabricatorAuditActionConstants::ACTION; @@ -44,6 +82,7 @@ final class PhabricatorAuditEditor switch ($xaction->getTransactionType()) { case PhabricatorAuditActionConstants::ACTION: case PhabricatorAuditActionConstants::INLINE: + case PhabricatorAuditTransaction::TYPE_COMMIT: return null; case PhabricatorAuditActionConstants::ADD_AUDITORS: // TODO: For now, just record the added PHIDs. Eventually, turn these @@ -62,6 +101,7 @@ final class PhabricatorAuditEditor case PhabricatorAuditActionConstants::ACTION: case PhabricatorAuditActionConstants::INLINE: case PhabricatorAuditActionConstants::ADD_AUDITORS: + case PhabricatorAuditTransaction::TYPE_COMMIT: return $xaction->getNewValue(); } @@ -79,6 +119,7 @@ final class PhabricatorAuditEditor case PhabricatorAuditActionConstants::ACTION: case PhabricatorAuditActionConstants::INLINE: case PhabricatorAuditActionConstants::ADD_AUDITORS: + case PhabricatorAuditTransaction::TYPE_COMMIT: return; } @@ -95,6 +136,7 @@ final class PhabricatorAuditEditor case PhabricatorTransactions::TYPE_EDGE: case PhabricatorAuditActionConstants::ACTION: case PhabricatorAuditActionConstants::INLINE: + case PhabricatorAuditTransaction::TYPE_COMMIT: return; case PhabricatorAuditActionConstants::ADD_AUDITORS: $new = $xaction->getNewValue(); @@ -118,15 +160,19 @@ final class PhabricatorAuditEditor continue; } - $audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED; + if ($this->getIsHeraldEditor()) { + $audit_requested = $xaction->getMetadataValue('auditStatus'); + $audit_reason_map = $xaction->getMetadataValue('auditReasonMap'); + $audit_reason = $audit_reason_map[$phid]; + } else { + $audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED; + $audit_reason = $this->getAuditReasons($phid); + } $requests[] = id (new PhabricatorRepositoryAuditRequest()) ->setCommitPHID($object->getPHID()) ->setAuditorPHID($phid) ->setAuditStatus($audit_requested) - ->setAuditReasons( - array( - 'Added by '.$actor->getUsername(), - )) + ->setAuditReasons($audit_reason) ->save(); } @@ -165,8 +211,12 @@ final class PhabricatorAuditEditor $actor_is_author = ($object->getAuthorPHID()) && ($actor_phid == $object->getAuthorPHID()); + $import_status_flag = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorAuditTransaction::TYPE_COMMIT: + $import_status_flag = PhabricatorRepositoryCommit::IMPORTED_HERALD; + break; case PhabricatorAuditActionConstants::ACTION: $new = $xaction->getNewValue(); switch ($new) { @@ -265,9 +315,67 @@ final class PhabricatorAuditEditor $object->updateAuditStatus($requests); $object->save(); + if ($import_status_flag) { + $object->writeImportStatusFlag($import_status_flag); + } + return $xactions; } + protected function expandTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + $xactions = parent::expandTransaction($object, $xaction); + switch ($xaction->getTransactionType()) { + case PhabricatorAuditTransaction::TYPE_COMMIT: + $request = $this->createAuditRequestTransactionFromCommitMessage( + $object); + if ($request) { + $xactions[] = $request; + $this->setUnmentionablePHIDMap($request->getNewValue()); + } + break; + default: + break; + } + return $xactions; + } + + private function createAuditRequestTransactionFromCommitMessage( + PhabricatorRepositoryCommit $commit) { + + $data = $commit->getCommitData(); + $message = $data->getCommitMessage(); + + $matches = null; + if (!preg_match('/^Auditors:\s*(.*)$/im', $message, $matches)) { + return array(); + } + + $phids = id(new PhabricatorObjectListQuery()) + ->setViewer($this->getActor()) + ->setAllowPartialResults(true) + ->setAllowedTypes( + array( + PhabricatorPeopleUserPHIDType::TYPECONST, + PhabricatorProjectProjectPHIDType::TYPECONST, + )) + ->setObjectList($matches[1]) + ->execute(); + + if (!$phids) { + return array(); + } + + foreach ($phids as $phid) { + $this->addAuditReason($phid, 'Requested by Author'); + } + return id(new PhabricatorAuditTransaction()) + ->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS) + ->setNewValue(array_fuse($phids)); + } + protected function sortTransactions(array $xactions) { $xactions = parent::sortTransactions($xactions); @@ -353,9 +461,75 @@ final class PhabricatorAuditEditor return true; } + protected function expandCustomRemarkupBlockTransactions( + PhabricatorLiskDAO $object, + array $xactions, + $blocks, + PhutilMarkupEngine $engine) { + + // we are only really trying to find unmentionable phids here... + // don't bother with this outside initial commit (i.e. create) + // transaction + $is_commit = false; + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorAuditTransaction::TYPE_COMMIT: + $is_commit = true; + break; + } + } + + // "result" is always an array.... + $result = array(); + if (!$is_commit) { + return $result; + } + + $flat_blocks = array_mergev($blocks); + $huge_block = implode("\n\n", $flat_blocks); + $phid_map = array(); + $phid_map[] = $this->getUnmentionablePHIDMap(); + $monograms = array(); + + $task_refs = id(new ManiphestCustomFieldStatusParser()) + ->parseCorpus($huge_block); + foreach ($task_refs as $match) { + foreach ($match['monograms'] as $monogram) { + $monograms[] = $monogram; + } + } + + $rev_refs = id(new DifferentialCustomFieldDependsOnParser()) + ->parseCorpus($huge_block); + foreach ($rev_refs as $match) { + foreach ($match['monograms'] as $monogram) { + $monograms[] = $monogram; + } + } + + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($this->getActor()) + ->withNames($monograms) + ->execute(); + $phid_map[] = mpull($objects, 'getPHID', 'getPHID'); + $phid_map = array_mergev($phid_map); + $this->setUnmentionablePHIDMap($phid_map); + + return $result; + } + + protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { + + // not every code path loads the repository so tread carefully + if ($object->getRepository($assert_attached = false)) { + $repository = $object->getRepository(); + if ($repository->isImporting()) { + return false; + } + } return $this->isCommitMostlyImported($object); } @@ -386,13 +560,23 @@ final class PhabricatorAuditEditor $subject = "{$name}: {$summary}"; $thread_topic = "Commit {$monogram}{$identifier}"; - return id(new PhabricatorMetaMTAMail()) + $template = id(new PhabricatorMetaMTAMail()) ->setSubject($subject) ->addHeader('Thread-Topic', $thread_topic); + + $this->attachPatch( + $template, + $object); + + return $template; } protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); + if ($this->heraldEmailPHIDs) { + $phids = $this->heraldEmailPHIDs; + } + if ($object->getAuthorPHID()) { $phids[] = $object->getAuthorPHID(); } @@ -414,12 +598,17 @@ final class PhabricatorAuditEditor $body = parent::buildMailBody($object, $xactions); $type_inline = PhabricatorAuditActionConstants::INLINE; + $type_push = PhabricatorAuditTransaction::TYPE_COMMIT; + $is_commit = false; $inlines = array(); foreach ($xactions as $xaction) { if ($xaction->getTransactionType() == $type_inline) { $inlines[] = $xaction; } + if ($xaction->getTransactionType() == $type_push) { + $is_commit = true; + } } if ($inlines) { @@ -428,6 +617,14 @@ final class PhabricatorAuditEditor $this->renderInlineCommentsForMail($object, $inlines)); } + if ($is_commit) { + $data = $object->getCommitData(); + $body->addTextSection(pht('AFFECTED FILES'), $this->affectedFiles); + $this->inlinePatch( + $body, + $object); + } + // Reload the commit to pull commit data. $commit = id(new DiffusionCommitQuery()) ->setViewer($this->requireActor()) @@ -440,7 +637,7 @@ final class PhabricatorAuditEditor $author_phid = $commit->getAuthorPHID(); if ($author_phid) { - $user_phids[$commit->getAuthorPHID()][] = pht('Author'); + $user_phids[$author_phid][] = pht('Author'); } $committer_phid = $data->getCommitDetail('committerPHID'); @@ -448,6 +645,13 @@ final class PhabricatorAuditEditor $user_phids[$committer_phid][] = pht('Committer'); } + // we loaded this in applyFinalEffects + $audit_requests = $object->getAudits(); + $auditor_phids = mpull($audit_requests, 'getAuditorPHID'); + foreach ($auditor_phids as $auditor_phid) { + $user_phids[$auditor_phid][] = pht('Auditor'); + } + // TODO: It would be nice to show pusher here too, but that information // is a little tricky to get at right now. @@ -474,13 +678,75 @@ final class PhabricatorAuditEditor $monogram = $object->getRepository()->formatCommitName( $object->getCommitIdentifier()); - $body->addTextSection( + $body->addLinkSection( pht('COMMIT'), PhabricatorEnv::getProductionURI('/'.$monogram)); return $body; } + private function attachPatch( + PhabricatorMetaMTAMail $template, + PhabricatorRepositoryCommit $commit) { + + if (!$this->getRawPatch()) { + return; + } + + $attach_key = 'metamta.diffusion.attach-patches'; + $attach_patches = PhabricatorEnv::getEnvConfig($attach_key); + if (!$attach_patches) { + return; + } + + $repository = $commit->getRepository(); + $encoding = $repository->getDetail('encoding', 'UTF-8'); + + $raw_patch = $this->getRawPatch(); + $commit_name = $repository->formatCommitName( + $commit->getCommitIdentifier()); + + $template->addAttachment( + new PhabricatorMetaMTAAttachment( + $raw_patch, + $commit_name.'.patch', + 'text/x-patch; charset='.$encoding)); + } + + private function inlinePatch( + PhabricatorMetaMTAMailBody $body, + PhabricatorRepositoryCommit $commit) { + + if (!$this->getRawPatch()) { + return; + } + + $inline_key = 'metamta.diffusion.inline-patches'; + $inline_patches = PhabricatorEnv::getEnvConfig($inline_key); + if (!$inline_patches) { + return; + } + + $repository = $commit->getRepository(); + $raw_patch = $this->getRawPatch(); + $result = null; + $len = substr_count($raw_patch, "\n"); + if ($len <= $inline_patches) { + // We send email as utf8, so we need to convert the text to utf8 if + // we can. + $encoding = $repository->getDetail('encoding', 'UTF-8'); + if ($encoding) { + $raw_patch = phutil_utf8_convert($raw_patch, 'UTF-8', $encoding); + } + $result = phutil_utf8ize($raw_patch); + } + + if ($result) { + $result = "PATCH\n\n{$result}\n"; + } + $body->addRawSection($result); + } + private function renderInlineCommentsForMail( PhabricatorLiskDAO $object, array $inline_xactions) { @@ -515,10 +781,127 @@ final class PhabricatorAuditEditor return implode("\n", $block); } + public function getMailTagsMap() { + return array( + PhabricatorAuditTransaction::MAILTAG_COMMIT => + pht('A commit is created.'), + PhabricatorAuditTransaction::MAILTAG_ACTION_CONCERN => + pht('A commit has a concerned raised against it.'), + PhabricatorAuditTransaction::MAILTAG_ACTION_ACCEPT => + pht('A commit is accepted.'), + PhabricatorAuditTransaction::MAILTAG_ACTION_RESIGN => + pht('A commit has an auditor resign.'), + PhabricatorAuditTransaction::MAILTAG_ACTION_CLOSE => + pht('A commit is closed.'), + PhabricatorAuditTransaction::MAILTAG_ADD_AUDITORS => + pht('A commit has auditors added.'), + PhabricatorAuditTransaction::MAILTAG_ADD_CCS => + pht("A commit's subscribers change."), + PhabricatorAuditTransaction::MAILTAG_PROJECTS => + pht("A commit's projects change."), + PhabricatorAuditTransaction::MAILTAG_COMMENT => + pht('Someone comments on a commit.'), + PhabricatorAuditTransaction::MAILTAG_OTHER => + pht('Other commit activity not listed above occurs.'), + ); + } + + + protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { - return $this->isCommitMostlyImported($object); + return $this->shouldSendMail($object, $xactions); + } + + protected function shouldApplyHeraldRules( + PhabricatorLiskDAO $object, + array $xactions) { + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorAuditTransaction::TYPE_COMMIT: + $repository = $object->getRepository(); + if ($repository->isImporting()) { + return false; + } + if ($repository->getDetail('herald-disabled')) { + return false; + } + return true; + default: + break; + } + } + return parent::shouldApplyHeraldRules($object, $xactions); + } + + protected function buildHeraldAdapter( + PhabricatorLiskDAO $object, + array $xactions) { + + return id(new HeraldCommitAdapter()) + ->setCommit($object); + } + + protected function didApplyHeraldRules( + PhabricatorLiskDAO $object, + 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); + } + + $cc_phids = $adapter->getAddCCMap(); + $add_ccs = array('+' => array()); + foreach ($cc_phids as $phid => $rule_ids) { + $add_ccs['+'][$phid] = $phid; + } + $xactions[] = id(new PhabricatorAuditTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) + ->setNewValue($add_ccs); + + $this->heraldEmailPHIDs = $adapter->getEmailPHIDs(); + + HarbormasterBuildable::applyBuildPlans( + $object->getPHID(), + $object->getRepository()->getPHID(), + $adapter->getBuildPlans()); + + $limit = self::MAX_FILES_SHOWN_IN_EMAIL; + $files = $adapter->loadAffectedPaths(); + sort($files); + if (count($files) > $limit) { + array_splice($files, $limit); + $files[] = pht( + '(This commit affected more than %d files. Only %d are shown here '. + 'and additional ones are truncated.)', + $limit, + $limit); + } + $this->affectedFiles = implode("\n", $files); + + return $xactions; } private function isCommitMostlyImported(PhabricatorLiskDAO $object) { diff --git a/src/applications/audit/storage/PhabricatorAuditTransaction.php b/src/applications/audit/storage/PhabricatorAuditTransaction.php index 8224d889d4..2932364350 100644 --- a/src/applications/audit/storage/PhabricatorAuditTransaction.php +++ b/src/applications/audit/storage/PhabricatorAuditTransaction.php @@ -3,6 +3,19 @@ final class PhabricatorAuditTransaction extends PhabricatorApplicationTransaction { + const TYPE_COMMIT = 'audit:commit'; + + const MAILTAG_ACTION_CONCERN = 'audit-action-concern'; + const MAILTAG_ACTION_ACCEPT = 'audit-action-accept'; + const MAILTAG_ACTION_RESIGN = 'audit-action-resign'; + const MAILTAG_ACTION_CLOSE = 'audit-action-close'; + const MAILTAG_ADD_AUDITORS = 'audit-add-auditors'; + const MAILTAG_ADD_CCS = 'audit-add-ccs'; + const MAILTAG_COMMENT = 'audit-comment'; + const MAILTAG_COMMIT = 'audit-commit'; + const MAILTAG_PROJECTS = 'audit-projects'; + const MAILTAG_OTHER = 'audit-other'; + public function getApplicationName() { return 'audit'; } @@ -15,12 +28,35 @@ final class PhabricatorAuditTransaction return new PhabricatorAuditTransactionComment(); } + public function getRemarkupBlocks() { + $blocks = parent::getRemarkupBlocks(); + + switch ($this->getTransactionType()) { + case self::TYPE_COMMIT: + $data = $this->getNewValue(); + $blocks[] = $data['description']; + break; + } + + return $blocks; + } + public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); $type = $this->getTransactionType(); switch ($type) { + case self::TYPE_COMMIT: + $phids[] = $this->getObjectPHID(); + $data = $this->getNewValue(); + if ($data['authorPHID']) { + $phids[] = $data['authorPHID']; + } + if ($data['committerPHID']) { + $phids[] = $data['committerPHID']; + } + break; case PhabricatorAuditActionConstants::ADD_CCS: case PhabricatorAuditActionConstants::ADD_AUDITORS: $old = $this->getOldValue(); @@ -59,6 +95,8 @@ final class PhabricatorAuditTransaction break; case PhabricatorAuditActionConstants::ADD_AUDITORS: return pht('Added Auditors'); + case self::TYPE_COMMIT: + return pht('Committed'); } return parent::getActionName(); @@ -104,10 +142,47 @@ final class PhabricatorAuditTransaction } switch ($type) { + case self::TYPE_COMMIT: + $author = null; + if ($new['authorPHID']) { + $author = $this->renderHandleLink($new['authorPHID']); + } else { + $author = $new['authorName']; + } + + $committer = null; + if ($new['committerPHID']) { + $committer = $this->renderHandleLink($new['committerPHID']); + } else if ($new['committerName']) { + $committer = $new['committerName']; + } + + $commit = $this->renderHandleLink($this->getObjectPHID()); + + if (!$committer) { + $committer = $author; + $author = null; + } + + if ($author) { + $title = pht( + '%s committed %s (authored by %s).', + $committer, + $commit, + $author); + } else { + $title = pht( + '%s committed %s.', + $committer, + $commit); + } + return $title; + case PhabricatorAuditActionConstants::INLINE: return pht( '%s added inline comments.', $author_handle); + case PhabricatorAuditActionConstants::ADD_CCS: if ($add && $rem) { return pht( @@ -203,6 +278,40 @@ final class PhabricatorAuditTransaction } switch ($type) { + case self::TYPE_COMMIT: + $author = null; + if ($new['authorPHID']) { + $author = $this->renderHandleLink($new['authorPHID']); + } else { + $author = $new['authorName']; + } + + $committer = null; + if ($new['committerPHID']) { + $committer = $this->renderHandleLink($new['committerPHID']); + } else if ($new['committerName']) { + $committer = $new['committerName']; + } + + if (!$committer) { + $committer = $author; + $author = null; + } + + if ($author) { + $title = pht( + '%s committed %s (authored by %s).', + $committer, + $object_handle, + $author); + } else { + $title = pht( + '%s committed %s.', + $committer, + $object_handle); + } + return $title; + case PhabricatorAuditActionConstants::INLINE: return pht( '%s added inline comments to %s.', @@ -265,6 +374,15 @@ final class PhabricatorAuditTransaction return parent::getTitleForFeed($story); } + public function getBodyForFeed(PhabricatorFeedStory $story) { + switch ($this->getTransactionType()) { + case self::TYPE_COMMIT: + $data = $this->getNewValue(); + return $story->renderSummary($data['summary']); + } + return parent::getBodyForFeed($story); + } + // TODO: These two mail methods can likely be abstracted by introducing a // formal concept of "inline comment" transactions. @@ -288,9 +406,63 @@ final class PhabricatorAuditTransaction switch ($this->getTransactionType()) { case PhabricatorAuditActionConstants::INLINE: return null; + case self::TYPE_COMMIT: + $data = $this->getNewValue(); + return $data['description']; } return parent::getBodyForMail(); } + public function getMailTags() { + $tags = array(); + switch ($this->getTransactionType()) { + case PhabricatorAuditActionConstants::ACTION: + switch ($this->getNewValue()) { + case PhabricatorAuditActionConstants::CONCERN: + $tags[] = self::MAILTAG_ACTION_CONCERN; + break; + case PhabricatorAuditActionConstants::ACCEPT: + $tags[] = self::MAILTAG_ACTION_ACCEPT; + break; + case PhabricatorAuditActionConstants::RESIGN: + $tags[] = self::MAILTAG_ACTION_RESIGN; + break; + case PhabricatorAuditActionConstants::CLOSE: + $tags[] = self::MAILTAG_ACTION_CLOSE; + break; + } + break; + case PhabricatorAuditActionConstants::ADD_AUDITORS: + $tags[] = self::MAILTAG_ADD_AUDITORS; + break; + case PhabricatorAuditActionConstants::ADD_CCS: + $tags[] = self::MAILTAG_ADD_CCS; + break; + case PhabricatorAuditActionConstants::INLINE: + case PhabricatorTransactions::TYPE_COMMENT: + $tags[] = self::MAILTAG_COMMENT; + break; + case self::TYPE_COMMIT: + $tags[] = self::MAILTAG_COMMIT; + break; + case PhabricatorTransactions::TYPE_EDGE: + switch ($this->getMetadataValue('edge:type')) { + case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST: + $tags[] = self::MAILTAG_PROJECTS; + break; + case PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER: + $tags[] = self::MAILTAG_ADD_CCS; + break; + default: + $tags[] = self::MAILTAG_OTHER; + break; + } + break; + default: + $tags[] = self::MAILTAG_OTHER; + break; + } + return $tags; + } } diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 04bca56c79..a8d8f32cb8 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -137,11 +137,11 @@ abstract class PhabricatorController extends AphrontController { if ($this->shouldRequireEnabledUser()) { if ($user->isLoggedIn() && !$user->getIsApproved()) { - $controller = new PhabricatorAuthNeedsApprovalController($request); + $controller = new PhabricatorAuthNeedsApprovalController(); return $this->delegateToController($controller); } if ($user->getIsDisabled()) { - $controller = new PhabricatorDisabledUserController($request); + $controller = new PhabricatorDisabledUserController(); return $this->delegateToController($controller); } } @@ -166,7 +166,7 @@ abstract class PhabricatorController extends AphrontController { if (!$this->shouldAllowPartialSessions()) { if ($user->hasSession() && $user->getSession()->getIsPartial()) { - $login_controller = new PhabricatorAuthFinishController($request); + $login_controller = new PhabricatorAuthFinishController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } @@ -180,8 +180,7 @@ abstract class PhabricatorController extends AphrontController { // and require MFA enrollment. $user->updateMultiFactorEnrollment(); if (!$user->getIsEnrolledInMultiFactor()) { - $mfa_controller = new PhabricatorAuthNeedsMultiFactorController( - $request); + $mfa_controller = new PhabricatorAuthNeedsMultiFactorController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($mfa_controller); } @@ -198,7 +197,7 @@ abstract class PhabricatorController extends AphrontController { // If this controller isn't public, and the user isn't logged in, require // login. if (!$allow_public && !$user->isLoggedIn()) { - $login_controller = new PhabricatorAuthStartController($request); + $login_controller = new PhabricatorAuthStartController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } @@ -206,7 +205,7 @@ abstract class PhabricatorController extends AphrontController { if ($user->isLoggedIn()) { if ($this->shouldRequireEmailVerification()) { if (!$user->getIsEmailVerified()) { - $controller = new PhabricatorMustVerifyEmailController($request); + $controller = new PhabricatorMustVerifyEmailController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($controller); } diff --git a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php index 94249f502d..2e71dca34a 100644 --- a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php +++ b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php @@ -22,7 +22,8 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase { ->setApplicationConfiguration($application_configuration) ->setRequestData(array()); - $controller = new PhabricatorTestController($request); + $controller = new PhabricatorTestController(); + $controller->setRequest($request); $u_public = id(new PhabricatorUser()) ->setUsername('public'); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php index d7b1019925..1d82160a59 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -14,8 +14,7 @@ final class PhabricatorCalendarEventListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorCalendarEventSearchEngine()) ->setNavigation($this->buildSideNav()); diff --git a/src/infrastructure/celerity/CelerityAPI.php b/src/applications/celerity/CelerityAPI.php similarity index 100% rename from src/infrastructure/celerity/CelerityAPI.php rename to src/applications/celerity/CelerityAPI.php diff --git a/src/infrastructure/celerity/CelerityResourceGraph.php b/src/applications/celerity/CelerityResourceGraph.php similarity index 100% rename from src/infrastructure/celerity/CelerityResourceGraph.php rename to src/applications/celerity/CelerityResourceGraph.php diff --git a/src/infrastructure/celerity/CelerityResourceMap.php b/src/applications/celerity/CelerityResourceMap.php similarity index 100% rename from src/infrastructure/celerity/CelerityResourceMap.php rename to src/applications/celerity/CelerityResourceMap.php diff --git a/src/infrastructure/celerity/CelerityResourceMapGenerator.php b/src/applications/celerity/CelerityResourceMapGenerator.php similarity index 100% rename from src/infrastructure/celerity/CelerityResourceMapGenerator.php rename to src/applications/celerity/CelerityResourceMapGenerator.php diff --git a/src/infrastructure/celerity/CelerityResourceTransformer.php b/src/applications/celerity/CelerityResourceTransformer.php similarity index 98% rename from src/infrastructure/celerity/CelerityResourceTransformer.php rename to src/applications/celerity/CelerityResourceTransformer.php index 48fc457c55..3b4c9f493d 100644 --- a/src/infrastructure/celerity/CelerityResourceTransformer.php +++ b/src/applications/celerity/CelerityResourceTransformer.php @@ -184,8 +184,10 @@ final class CelerityResourceTransformer { 'lightblue' => '#daeaf3', 'sky' => '#3498db', 'lightsky' => '#ddeef9', - 'indigo' => '#c6539d', - 'lightindigo' => '#f5e2ef', + 'indigo' => '#6e5cb6', + 'lightindigo' => '#eae6f7', + 'pink' => '#da49be', + 'lightpink' => '#fbeaf8', 'violet' => '#8e44ad', 'lightviolet' => '#ecdff1', 'charcoal' => '#4b4d51', diff --git a/src/infrastructure/celerity/CeleritySpriteGenerator.php b/src/applications/celerity/CeleritySpriteGenerator.php similarity index 100% rename from src/infrastructure/celerity/CeleritySpriteGenerator.php rename to src/applications/celerity/CeleritySpriteGenerator.php diff --git a/src/infrastructure/celerity/CelerityStaticResourceResponse.php b/src/applications/celerity/CelerityStaticResourceResponse.php similarity index 100% rename from src/infrastructure/celerity/CelerityStaticResourceResponse.php rename to src/applications/celerity/CelerityStaticResourceResponse.php diff --git a/src/infrastructure/celerity/__tests__/CelerityResourceTransformerTestCase.php b/src/applications/celerity/__tests__/CelerityResourceTransformerTestCase.php similarity index 100% rename from src/infrastructure/celerity/__tests__/CelerityResourceTransformerTestCase.php rename to src/applications/celerity/__tests__/CelerityResourceTransformerTestCase.php diff --git a/src/infrastructure/celerity/__tests__/transformer/min.css b/src/applications/celerity/__tests__/transformer/min.css similarity index 100% rename from src/infrastructure/celerity/__tests__/transformer/min.css rename to src/applications/celerity/__tests__/transformer/min.css diff --git a/src/infrastructure/celerity/__tests__/transformer/print.css b/src/applications/celerity/__tests__/transformer/print.css similarity index 100% rename from src/infrastructure/celerity/__tests__/transformer/print.css rename to src/applications/celerity/__tests__/transformer/print.css diff --git a/src/infrastructure/celerity/__tests__/transformer/xform.css b/src/applications/celerity/__tests__/transformer/xform.css similarity index 100% rename from src/infrastructure/celerity/__tests__/transformer/xform.css rename to src/applications/celerity/__tests__/transformer/xform.css diff --git a/src/infrastructure/celerity/api.php b/src/applications/celerity/api.php similarity index 100% rename from src/infrastructure/celerity/api.php rename to src/applications/celerity/api.php diff --git a/src/applications/celerity/application/PhabricatorCelerityApplication.php b/src/applications/celerity/application/PhabricatorCelerityApplication.php new file mode 100644 index 0000000000..9a361efed6 --- /dev/null +++ b/src/applications/celerity/application/PhabricatorCelerityApplication.php @@ -0,0 +1,33 @@ + array( + '(?:(?P[0-9]+)T/)?'. + '(?P[^/]+)/'. + '(?P[a-f0-9]{8})/'. + '(?P.+\.(?:'.$extensions.'))' + => 'CelerityPhabricatorResourceController', + ), + ); + } + +} diff --git a/src/infrastructure/celerity/CelerityPhabricatorResourceController.php b/src/applications/celerity/controller/CelerityPhabricatorResourceController.php similarity index 100% rename from src/infrastructure/celerity/CelerityPhabricatorResourceController.php rename to src/applications/celerity/controller/CelerityPhabricatorResourceController.php diff --git a/src/infrastructure/celerity/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php similarity index 100% rename from src/infrastructure/celerity/CelerityResourceController.php rename to src/applications/celerity/controller/CelerityResourceController.php diff --git a/src/infrastructure/celerity/management/CelerityManagementMapWorkflow.php b/src/applications/celerity/management/CelerityManagementMapWorkflow.php similarity index 100% rename from src/infrastructure/celerity/management/CelerityManagementMapWorkflow.php rename to src/applications/celerity/management/CelerityManagementMapWorkflow.php diff --git a/src/infrastructure/celerity/management/CelerityManagementWorkflow.php b/src/applications/celerity/management/CelerityManagementWorkflow.php similarity index 100% rename from src/infrastructure/celerity/management/CelerityManagementWorkflow.php rename to src/applications/celerity/management/CelerityManagementWorkflow.php diff --git a/src/infrastructure/celerity/resources/CelerityPhabricatorResources.php b/src/applications/celerity/resources/CelerityPhabricatorResources.php similarity index 100% rename from src/infrastructure/celerity/resources/CelerityPhabricatorResources.php rename to src/applications/celerity/resources/CelerityPhabricatorResources.php diff --git a/src/infrastructure/celerity/resources/CelerityPhysicalResources.php b/src/applications/celerity/resources/CelerityPhysicalResources.php similarity index 100% rename from src/infrastructure/celerity/resources/CelerityPhysicalResources.php rename to src/applications/celerity/resources/CelerityPhysicalResources.php diff --git a/src/infrastructure/celerity/resources/CelerityResources.php b/src/applications/celerity/resources/CelerityResources.php similarity index 100% rename from src/infrastructure/celerity/resources/CelerityResources.php rename to src/applications/celerity/resources/CelerityResources.php diff --git a/src/infrastructure/celerity/resources/CelerityResourcesOnDisk.php b/src/applications/celerity/resources/CelerityResourcesOnDisk.php similarity index 100% rename from src/infrastructure/celerity/resources/CelerityResourcesOnDisk.php rename to src/applications/celerity/resources/CelerityResourcesOnDisk.php diff --git a/src/applications/conduit/controller/PhabricatorConduitListController.php b/src/applications/conduit/controller/PhabricatorConduitListController.php index 5bc17bdc44..17ea5a970a 100644 --- a/src/applications/conduit/controller/PhabricatorConduitListController.php +++ b/src/applications/conduit/controller/PhabricatorConduitListController.php @@ -14,8 +14,7 @@ final class PhabricatorConduitListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorConduitSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/config/controller/PhabricatorConfigIgnoreController.php b/src/applications/config/controller/PhabricatorConfigIgnoreController.php index a39685a6b5..a136035536 100644 --- a/src/applications/config/controller/PhabricatorConfigIgnoreController.php +++ b/src/applications/config/controller/PhabricatorConfigIgnoreController.php @@ -1,7 +1,7 @@ getViewer(), $option, - PhabricatorContentSource::newFromConsole()); + PhabricatorContentSource::newConsoleSource()); $key_count++; $console->writeOut(pht( 'Migrated option "%s" from file to local config.', $key)."\n"); diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php index 60292b4a74..7a11d64684 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php @@ -153,11 +153,7 @@ final class PhabricatorConfigSchemaQuery extends Phobject { public function loadExpectedSchema() { $databases = $this->getDatabaseNames(); - - $api = $this->getAPI(); - - $charset_info = $api->getCharsetInfo(); - list($charset, $collate_text, $collate_sort) = $charset_info; + $info = $this->getAPI()->getCharsetInfo(); $specs = id(new PhutilSymbolLoader()) ->setAncestorClass('PhabricatorConfigSchemaSpec') @@ -166,9 +162,12 @@ final class PhabricatorConfigSchemaQuery extends Phobject { $server_schema = new PhabricatorConfigServerSchema(); foreach ($specs as $spec) { $spec - ->setUTF8Charset($charset) - ->setUTF8BinaryCollation($collate_text) - ->setUTF8SortingCollation($collate_sort) + ->setUTF8Charset( + $info[PhabricatorStorageManagementAPI::CHARSET_DEFAULT]) + ->setUTF8BinaryCollation( + $info[PhabricatorStorageManagementAPI::COLLATE_TEXT]) + ->setUTF8SortingCollation( + $info[PhabricatorStorageManagementAPI::COLLATE_SORT]) ->setServer($server_schema) ->buildSchemata($server_schema); } diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 16ef622c79..d680472c9e 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -405,7 +405,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { array $xactions) { $body = parent::buildMailBody($object, $xactions); - $body->addTextSection( + $body->addLinkSection( pht('CONPHERENCE DETAIL'), PhabricatorEnv::getProductionURI('/conpherence/'.$object->getID().'/')); diff --git a/src/applications/console/application/PhabricatorConsoleApplication.php b/src/applications/console/application/PhabricatorConsoleApplication.php new file mode 100644 index 0000000000..8e4398af32 --- /dev/null +++ b/src/applications/console/application/PhabricatorConsoleApplication.php @@ -0,0 +1,26 @@ + array( + '' => 'DarkConsoleController', + 'data/(?P[^/]+)/' => 'DarkConsoleDataController', + ), + ); + } + +} diff --git a/src/aphront/console/DarkConsoleController.php b/src/applications/console/controller/DarkConsoleController.php similarity index 100% rename from src/aphront/console/DarkConsoleController.php rename to src/applications/console/controller/DarkConsoleController.php diff --git a/src/aphront/console/DarkConsoleDataController.php b/src/applications/console/controller/DarkConsoleDataController.php similarity index 100% rename from src/aphront/console/DarkConsoleDataController.php rename to src/applications/console/controller/DarkConsoleDataController.php diff --git a/src/aphront/console/DarkConsoleCore.php b/src/applications/console/core/DarkConsoleCore.php similarity index 100% rename from src/aphront/console/DarkConsoleCore.php rename to src/applications/console/core/DarkConsoleCore.php diff --git a/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php b/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php similarity index 100% rename from src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php rename to src/applications/console/plugin/DarkConsoleErrorLogPlugin.php diff --git a/src/aphront/console/plugin/DarkConsoleEventPlugin.php b/src/applications/console/plugin/DarkConsoleEventPlugin.php similarity index 100% rename from src/aphront/console/plugin/DarkConsoleEventPlugin.php rename to src/applications/console/plugin/DarkConsoleEventPlugin.php diff --git a/src/aphront/console/plugin/DarkConsolePlugin.php b/src/applications/console/plugin/DarkConsolePlugin.php similarity index 100% rename from src/aphront/console/plugin/DarkConsolePlugin.php rename to src/applications/console/plugin/DarkConsolePlugin.php diff --git a/src/aphront/console/plugin/DarkConsoleRequestPlugin.php b/src/applications/console/plugin/DarkConsoleRequestPlugin.php similarity index 100% rename from src/aphront/console/plugin/DarkConsoleRequestPlugin.php rename to src/applications/console/plugin/DarkConsoleRequestPlugin.php diff --git a/src/aphront/console/plugin/DarkConsoleServicesPlugin.php b/src/applications/console/plugin/DarkConsoleServicesPlugin.php similarity index 100% rename from src/aphront/console/plugin/DarkConsoleServicesPlugin.php rename to src/applications/console/plugin/DarkConsoleServicesPlugin.php diff --git a/src/aphront/console/plugin/DarkConsoleXHProfPlugin.php b/src/applications/console/plugin/DarkConsoleXHProfPlugin.php similarity index 100% rename from src/aphront/console/plugin/DarkConsoleXHProfPlugin.php rename to src/applications/console/plugin/DarkConsoleXHProfPlugin.php diff --git a/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php b/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php similarity index 100% rename from src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php rename to src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php diff --git a/src/aphront/console/plugin/event/DarkConsoleEventPluginAPI.php b/src/applications/console/plugin/event/DarkConsoleEventPluginAPI.php similarity index 100% rename from src/aphront/console/plugin/event/DarkConsoleEventPluginAPI.php rename to src/applications/console/plugin/event/DarkConsoleEventPluginAPI.php diff --git a/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php b/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php similarity index 100% rename from src/aphront/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php rename to src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php index 2085a6884f..f3c34d23aa 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownListController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php @@ -14,8 +14,7 @@ final class PhabricatorCountdownListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorCountdownSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php index e7ac2b992c..f51d558de1 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php @@ -126,58 +126,21 @@ final class PhabricatorDaemonConsoleController $daemon_table->setUser($user); $daemon_table->setDaemonLogs($logs); - $tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( - 'leaseOwner IS NOT NULL'); - - $rows = array(); - foreach ($tasks as $task) { - $rows[] = array( - $task->getID(), - $task->getTaskClass(), - $task->getLeaseOwner(), - $task->getLeaseExpires() - time(), - $task->getPriority(), - $task->getFailureCount(), - phutil_tag( - 'a', - array( - 'href' => '/daemon/task/'.$task->getID().'/', - 'class' => 'button small grey', - ), - pht('View Task')), - ); - } - $daemon_panel = new PHUIObjectBoxView(); $daemon_panel->setHeaderText(pht('Active Daemons')); $daemon_panel->appendChild($daemon_table); - $leased_table = new AphrontTableView($rows); - $leased_table->setHeaders( - array( - pht('ID'), - pht('Class'), - pht('Owner'), - pht('Expires'), - pht('Priority'), - pht('Failures'), - '', - )); - $leased_table->setColumnClasses( - array( - 'n', - 'wide', - '', - '', - 'n', - 'n', - 'action', - )); - $leased_table->setNoDataString(pht('No tasks are leased by workers.')); - $leased_panel = new PHUIObjectBoxView(); - $leased_panel->setHeaderText(pht('Leased Tasks')); - $leased_panel->appendChild($leased_table); + $tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( + 'leaseOwner IS NOT NULL'); + + $tasks_table = $this->renderTasksTable( + $tasks, + pht('No tasks are leased by workers.')); + + $leased_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Leased Tasks')) + ->appendChild($tasks_table); $task_table = new PhabricatorWorkerActiveTask(); $queued = queryfx_all( @@ -211,6 +174,16 @@ final class PhabricatorDaemonConsoleController $queued_panel->setHeaderText(pht('Queued Tasks')); $queued_panel->appendChild($queued_table); + $upcoming = id(new PhabricatorWorkerLeaseQuery()) + ->setLimit(10) + ->setSkipLease(true) + ->execute(); + + $upcoming_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Next In Queue')) + ->appendChild( + $this->renderTasksTable($upcoming, pht('Task queue is empty.'))); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); @@ -223,6 +196,7 @@ final class PhabricatorDaemonConsoleController $daemon_panel, $queued_panel, $leased_panel, + $upcoming_panel, )); return $this->buildApplicationPage( @@ -233,4 +207,52 @@ final class PhabricatorDaemonConsoleController )); } + private function renderTasksTable(array $tasks, $nodata) { + $rows = array(); + foreach ($tasks as $task) { + $rows[] = array( + $task->getID(), + $task->getTaskClass(), + $task->getLeaseOwner(), + $task->getLeaseExpires() + ? phutil_format_relative_time($task->getLeaseExpires() - time()) + : '-', + $task->getPriority(), + $task->getFailureCount(), + phutil_tag( + 'a', + array( + 'href' => '/daemon/task/'.$task->getID().'/', + 'class' => 'button small grey', + ), + pht('View Task')), + ); + } + + $table = new AphrontTableView($rows); + $table->setHeaders( + array( + pht('ID'), + pht('Class'), + pht('Owner'), + pht('Expires'), + pht('Priority'), + pht('Failures'), + '', + )); + $table->setColumnClasses( + array( + 'n', + 'wide', + '', + '', + 'n', + 'n', + 'action', + )); + $table->setNoDataString($nodata); + + return $table; + } + } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardListController.php b/src/applications/dashboard/controller/PhabricatorDashboardListController.php index fce5ab69f4..9f0b64de79 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardListController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardListController.php @@ -14,8 +14,7 @@ final class PhabricatorDashboardListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorDashboardSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php index 8df9b20506..5f5df692cc 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php @@ -14,8 +14,7 @@ final class PhabricatorDashboardPanelListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorDashboardPanelSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php index 6f76e2618e..4b1ddde899 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php @@ -68,6 +68,8 @@ final class PhabricatorDashboardPanelTransactionEditor case PhabricatorTransactions::TYPE_EDIT_POLICY: $object->setEditPolicy($xaction->getNewValue()); return; + case PhabricatorTransactions::TYPE_EDGE: + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -82,6 +84,7 @@ final class PhabricatorDashboardPanelTransactionEditor case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: + case PhabricatorTransactions::TYPE_EDGE: return; } diff --git a/src/applications/differential/application/PhabricatorDifferentialApplication.php b/src/applications/differential/application/PhabricatorDifferentialApplication.php index 1b7495a13c..43cb463e5c 100644 --- a/src/applications/differential/application/PhabricatorDifferentialApplication.php +++ b/src/applications/differential/application/PhabricatorDifferentialApplication.php @@ -67,6 +67,8 @@ EOTEXT => 'DifferentialRevisionEditController', 'revision/land/(?:(?P[1-9]\d*))/(?P[^/]+)/' => 'DifferentialRevisionLandController', + 'revision/closedetails/(?P[^/]+)/' + => 'DifferentialRevisionCloseDetailsController', 'comment/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialCommentPreviewController', 'save/(?P[1-9]\d*)/' => 'DifferentialCommentSaveController', diff --git a/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php index e116a939b4..388fa364e0 100644 --- a/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php @@ -81,9 +81,20 @@ final class DifferentialParseCommitMessageConduitAPIMethod } } + // grab some extra information about the Differential Revision: field... + $revision_id_field = new DifferentialRevisionIDField(); + $revision_id_value = idx( + $corpus_map, + $revision_id_field->getFieldKeyForConduit()); + $revision_id_valid_domain = PhabricatorEnv::getProductionURI(''); + return array( 'errors' => $this->errors, 'fields' => $values, + 'revisionIDFieldInfo' => array( + 'value' => $revision_id_value, + 'validDomain' => $revision_id_valid_domain, + ), ); } diff --git a/src/applications/differential/controller/DifferentialRevisionCloseDetailsController.php b/src/applications/differential/controller/DifferentialRevisionCloseDetailsController.php new file mode 100644 index 0000000000..8b02ff2382 --- /dev/null +++ b/src/applications/differential/controller/DifferentialRevisionCloseDetailsController.php @@ -0,0 +1,113 @@ +phid = idx($data, 'phid'); + } + + public function processRequest() { + $request = $this->getRequest(); + + $viewer = $request->getUser(); + $xaction_phid = $this->phid; + + $xaction = id(new PhabricatorObjectQuery()) + ->withPHIDs(array($xaction_phid)) + ->setViewer($viewer) + ->executeOne(); + if (!$xaction) { + return new Aphront404Response(); + } + + $obj_phid = $xaction->getObjectPHID(); + $obj_handle = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($obj_phid)) + ->executeOne(); + + $body = $this->getRevisionMatchExplanation( + $xaction->getMetadataValue('revisionMatchData'), + $obj_handle); + + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('Commit Close Explanation')) + ->appendParagraph($body) + ->addCancelButton($obj_handle->getURI()); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + private function getRevisionMatchExplanation( + $revision_match_data, + PhabricatorObjectHandle $obj_handle) { + + if (!$revision_match_data) { + return pht( + 'This commit was made before this feature was built and thus this '. + 'information is unavailable.'); + } + + $body_why = array(); + if ($revision_match_data['usedURI']) { + return pht( + 'We found a "Differential Revision" field with value "%s" in the '. + 'commit message, and the domain on the URI matches this install, so '. + 'we linked this commit to %s.', + $revision_match_data['foundURI'], + phutil_tag( + 'a', + array( + 'href' => $obj_handle->getURI(),), + $obj_handle->getName())); + } else if ($revision_match_data['foundURI']) { + $body_why[] = pht( + 'We found a "Differential Revision" field with value "%s" in the '. + 'commit message, but the domain on this URI did not match the '. + 'configured domain for this install, "%s", so we ignored it under '. + 'the assumption that it refers to some third-party revision.', + $revision_match_data['foundURI'], + $revision_match_data['validDomain']); + } else { + $body_why[] = pht( + 'We didn\'t find a "Differential Revision" field in the commit '. + 'message.'); + } + + switch ($revision_match_data['matchHashType']) { + case ArcanistDifferentialRevisionHash::HASH_GIT_TREE: + $hash_info = true; + $hash_type = 'tree'; + break; + case ArcanistDifferentialRevisionHash::HASH_GIT_COMMIT: + case ArcanistDifferentialRevisionHash::HASH_MERCURIAL_COMMIT: + $hash_info = true; + $hash_type = 'commit'; + break; + default: + $hash_info = false; + break; + } + if ($hash_info) { + $diff_link = phutil_tag( + 'a', + array( + 'href' => $obj_handle->getURI(),), + $obj_handle->getName()); + $body_why = pht( + 'This commit and the active diff of %s had the same %s hash '. + '(%s) so we linked this commit to %s.', + $diff_link, + $hash_type, + $revision_match_data['matchHashValue'], + $diff_link); + } + + return phutil_implode_html("\n", $body_why); + + } +} diff --git a/src/applications/differential/controller/DifferentialRevisionListController.php b/src/applications/differential/controller/DifferentialRevisionListController.php index b2ac03b7b2..bb6f13a5df 100644 --- a/src/applications/differential/controller/DifferentialRevisionListController.php +++ b/src/applications/differential/controller/DifferentialRevisionListController.php @@ -13,8 +13,7 @@ final class DifferentialRevisionListController extends DifferentialController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new DifferentialRevisionSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php b/src/applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php index e2eef1788a..a7e3654d0d 100644 --- a/src/applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php +++ b/src/applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php @@ -54,7 +54,7 @@ final class DifferentialChangesSinceLastUpdateField $uri = '/'.$revision->getMonogram().'?vs='.$old_id.'&id='.$new_id; $uri = PhabricatorEnv::getProductionURI($uri); - $body->addTextSection(pht('CHANGES SINCE LAST UPDATE'), $uri); + $body->addLinkSection(pht('CHANGES SINCE LAST UPDATE'), $uri); } } diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index 2819a6401e..e46f7ca1a4 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -63,7 +63,11 @@ final class DifferentialReviewersField } public function getRequiredHandlePHIDsForEdit() { - return mpull($this->getValue(), 'getReviewerPHID'); + $phids = array(); + if ($this->getValue()) { + $phids = mpull($this->getValue(), 'getReviewerPHID'); + } + return $phids; } public function renderEditControl(array $handles) { diff --git a/src/applications/differential/customfield/DifferentialRevisionIDField.php b/src/applications/differential/customfield/DifferentialRevisionIDField.php index 74c38767d9..3741c0126f 100644 --- a/src/applications/differential/customfield/DifferentialRevisionIDField.php +++ b/src/applications/differential/customfield/DifferentialRevisionIDField.php @@ -47,22 +47,26 @@ final class DifferentialRevisionIDField $this->revisionID = $value; } - private static function parseRevisionIDFromURI($uri) { - $path = id(new PhutilURI($uri))->getPath(); + private static function parseRevisionIDFromURI($uri_string) { + $uri = new PhutilURI($uri_string); + $path = $uri->getPath(); $matches = null; if (preg_match('#^/D(\d+)$#', $path, $matches)) { $id = (int)$matches[1]; + + $prod_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/D'.$id)); + // Make sure the URI is the same as our URI. Basically, we want to ignore // commits from other Phabricator installs. - if ($uri == PhabricatorEnv::getProductionURI('/D'.$id)) { + if ($uri->getDomain() == $prod_uri->getDomain()) { return $id; } $allowed_uris = PhabricatorEnv::getAllowedURIs('/D'.$id); foreach ($allowed_uris as $allowed_uri) { - if ($uri == $allowed_uri) { + if ($uri_string == $allowed_uri) { return $id; } } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 85f68768df..dc0058b0f3 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1140,7 +1140,7 @@ final class DifferentialTransactionEditor $changed_uri = $this->getChangedPriorToCommitURI(); if ($changed_uri) { - $body->addTextSection( + $body->addLinkSection( pht('CHANGED PRIOR TO COMMIT'), $changed_uri); } @@ -1151,7 +1151,7 @@ final class DifferentialTransactionEditor $this->renderInlineCommentsForMail($object, $inlines)); } - $body->addTextSection( + $body->addLinkSection( pht('REVISION DETAIL'), PhabricatorEnv::getProductionURI('/D'.$object->getID())); diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php index 4cc8c4e803..b6df7cf4f5 100644 --- a/src/applications/differential/storage/DifferentialTransaction.php +++ b/src/applications/differential/storage/DifferentialTransaction.php @@ -306,6 +306,22 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction { return parent::getTitle(); } + public function renderExtraInformationLink() { + if ($this->getMetadataValue('revisionMatchData')) { + $details_href = + '/differential/revision/closedetails/'.$this->getPHID().'/'; + $details_link = javelin_tag( + 'a', + array( + 'href' => $details_href, + 'sigil' => 'workflow', + ), + pht('Explain Why')); + return $details_link; + } + return parent::renderExtraInformationLink(); + } + public function getTitleForFeed(PhabricatorFeedStory $story) { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index 03e777a668..5dd7c09d73 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -95,7 +95,7 @@ final class DifferentialReviewersView extends AphrontView { if ($is_current) { $item->setIcon( PHUIStatusItemView::ICON_INFO, - 'bluegrey', + 'blue', pht('Commented')); } else { $item->setIcon( diff --git a/src/applications/diffusion/controller/DiffusionBrowseMainController.php b/src/applications/diffusion/controller/DiffusionBrowseMainController.php index f0f32d7823..8604909f09 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseMainController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseMainController.php @@ -12,7 +12,7 @@ final class DiffusionBrowseMainController extends DiffusionBrowseController { $grep = $request->getStr('grep'); $find = $request->getStr('find'); if (strlen($grep) || strlen($find)) { - $controller = new DiffusionBrowseSearchController($request); + $controller = new DiffusionBrowseSearchController(); } else { $results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index b225a35e99..3e7308a56a 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -1055,58 +1055,12 @@ final class DiffusionCommitController extends DiffusionController { $view = new PHUIStatusListView(); foreach ($audit_requests as $request) { + $code = $request->getAuditStatus(); $item = new PHUIStatusItemView(); - - switch ($request->getAuditStatus()) { - case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED: - $item->setIcon( - PHUIStatusItemView::ICON_OPEN, - 'blue', - pht('Commented')); - break; - case PhabricatorAuditStatusConstants::AUDIT_REQUIRED: - $item->setIcon( - PHUIStatusItemView::ICON_WARNING, - 'blue', - pht('Audit Required')); - break; - case PhabricatorAuditStatusConstants::CONCERNED: - $item->setIcon( - PHUIStatusItemView::ICON_REJECT, - 'red', - pht('Concern Raised')); - break; - case PhabricatorAuditStatusConstants::ACCEPTED: - $item->setIcon( - PHUIStatusItemView::ICON_ACCEPT, - 'green', - pht('Accepted')); - break; - case PhabricatorAuditStatusConstants::AUDIT_REQUESTED: - $item->setIcon( - PHUIStatusItemView::ICON_WARNING, - 'dark', - pht('Audit Requested')); - break; - case PhabricatorAuditStatusConstants::RESIGNED: - $item->setIcon( - PHUIStatusItemView::ICON_OPEN, - 'dark', - pht('Resigned')); - break; - case PhabricatorAuditStatusConstants::CLOSED: - $item->setIcon( - PHUIStatusItemView::ICON_ACCEPT, - 'blue', - pht('Closed')); - break; - default: - $item->setIcon( - PHUIStatusItemView::ICON_QUESTION, - 'dark', - pht('%s?', $request->getAuditStatus())); - break; - } + $item->setIcon( + PhabricatorAuditStatusConstants::getStatusIcon($code), + PhabricatorAuditStatusConstants::getStatusColor($code), + PhabricatorAuditStatusConstants::getStatusName($code)); $note = array(); foreach ($request->getAuditReasons() as $reason) { diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index a50627dee0..04b40ce72f 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -23,7 +23,7 @@ abstract class DiffusionController extends PhabricatorController { // "svn checkout". If it is, we jump off into repository serving code to // process the request. if (DiffusionServeController::isVCSRequest($request)) { - $serve_controller = id(new DiffusionServeController($request)) + $serve_controller = id(new DiffusionServeController()) ->setCurrentApplication($this->getCurrentApplication()); return $this->delegateToController($serve_controller); } diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index fa8693aa62..91e4fbf709 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -12,7 +12,7 @@ final class DiffusionLintController extends DiffusionController { $drequest = $this->diffusionRequest; if ($request->getStr('lint') !== null) { - $controller = new DiffusionLintDetailsController($request); + $controller = new DiffusionLintDetailsController(); $controller->setDiffusionRequest($drequest); $controller->setCurrentApplication($this->getCurrentApplication()); return $this->delegateToController($controller); diff --git a/src/applications/diffusion/controller/DiffusionPushLogListController.php b/src/applications/diffusion/controller/DiffusionPushLogListController.php index 53d72ba386..0b56376224 100644 --- a/src/applications/diffusion/controller/DiffusionPushLogListController.php +++ b/src/applications/diffusion/controller/DiffusionPushLogListController.php @@ -14,7 +14,7 @@ final class DiffusionPushLogListController extends DiffusionPushLogController { public function processRequest() { $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorRepositoryPushLogSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryListController.php b/src/applications/diffusion/controller/DiffusionRepositoryListController.php index 4202905146..9795d15334 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryListController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryListController.php @@ -14,7 +14,7 @@ final class DiffusionRepositoryListController extends DiffusionController { public function processRequest() { $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorRepositorySearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/diffusion/data/DiffusionCommitHash.php b/src/applications/diffusion/data/DiffusionCommitHash.php index 8605a99a34..7fbf23a2bf 100644 --- a/src/applications/diffusion/data/DiffusionCommitHash.php +++ b/src/applications/diffusion/data/DiffusionCommitHash.php @@ -23,4 +23,15 @@ final class DiffusionCommitHash extends Phobject { return $this->hashType; } + public static function convertArrayToObjects(array $hashes) { + $hash_objects = array(); + foreach ($hashes as $hash) { + $type = $hash[0]; + $hash = $hash[1]; + $hash_objects[] = id(new DiffusionCommitHash()) + ->setHashType($type) + ->setHashValue($hash); + } + return $hash_objects; + } } diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php index 64d541f151..a288d6a70b 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php @@ -4,12 +4,28 @@ final class DiffusionLowLevelCommitFieldsQuery extends DiffusionLowLevelQuery { private $ref; + private $revisionMatchData = array( + 'usedURI' => null, + 'foundURI' => null, + 'validDomain' => null, + 'matchHashType' => null, + 'matchHashValue' => null, + ); public function withCommitRef(DiffusionCommitRef $ref) { $this->ref = $ref; return $this; } + public function getRevisionMatchData() { + return $this->revisionMatchData; + } + + private function setRevisionMatchData($key, $value) { + $this->revisionMatchData[$key] = $value; + return $this; + } + public function executeQuery() { $ref = $this->ref; $message = $ref->getMessage(); @@ -25,10 +41,21 @@ final class DiffusionLowLevelCommitFieldsQuery ->execute(); $fields = $result['fields']; + $revision_id = idx($fields, 'revisionID'); + if ($revision_id) { + $this->setRevisionMatchData('usedURI', true); + } else { + $this->setRevisionMatchData('usedURI', false); + } + $revision_id_info = $result['revisionIDFieldInfo']; + $this->setRevisionMatchData('foundURI', $revision_id_info['value']); + $this->setRevisionMatchData( + 'validDomain', + $revision_id_info['validDomain']); + // If there is no "Differential Revision:" field in the message, try to // identify the revision by doing a hash lookup. - $revision_id = idx($fields, 'revisionID'); if (!$revision_id && $hashes) { $hash_list = array(); foreach ($hashes as $hash) { @@ -36,12 +63,38 @@ final class DiffusionLowLevelCommitFieldsQuery } $revisions = id(new DifferentialRevisionQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->needHashes(true) ->withCommitHashes($hash_list) ->execute(); if (!empty($revisions)) { $revision = $this->pickBestRevision($revisions); $fields['revisionID'] = $revision->getID(); + $revision_hashes = $revision->getHashes(); + $revision_hashes = DiffusionCommitHash::convertArrayToObjects( + $revision_hashes); + $revision_hashes = mpull($revision_hashes, 'getHashType'); + // sort the hashes in the order the mighty + // @{class:ArcanstDifferentialRevisionHash} does; probably unnecessary + // but should future proof things nicely. + $revision_hashes = array_select_keys( + $revision_hashes, + ArcanistDifferentialRevisionHash::getTypes()); + foreach ($hashes as $hash) { + $revision_hash = idx($revision_hashes, $hash->getHashType()); + if (!$revision_hash) { + continue; + } + if ($revision_hash->getHashValue() == $hash->getHashValue()) { + $this->setRevisionMatchData( + 'matchHashType', + $hash->getHashType()); + $this->setRevisionMatchData( + 'matchHashValue', + $hash->getHashValue()); + break; + } + } } } diff --git a/src/applications/diviner/controller/DivinerAtomListController.php b/src/applications/diviner/controller/DivinerAtomListController.php index 8681e41293..68d1f6b652 100644 --- a/src/applications/diviner/controller/DivinerAtomListController.php +++ b/src/applications/diviner/controller/DivinerAtomListController.php @@ -14,7 +14,7 @@ final class DivinerAtomListController extends DivinerController { public function processRequest() { $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->key) ->setSearchEngine(new DivinerAtomSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/drydock/controller/DrydockBlueprintListController.php b/src/applications/drydock/controller/DrydockBlueprintListController.php index 53231d0eac..86e5be8824 100644 --- a/src/applications/drydock/controller/DrydockBlueprintListController.php +++ b/src/applications/drydock/controller/DrydockBlueprintListController.php @@ -14,7 +14,7 @@ final class DrydockBlueprintListController extends DrydockBlueprintController { public function processRequest() { $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new DrydockBlueprintSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/drydock/controller/DrydockLeaseListController.php b/src/applications/drydock/controller/DrydockLeaseListController.php index 6d8835770d..f5c0897717 100644 --- a/src/applications/drydock/controller/DrydockLeaseListController.php +++ b/src/applications/drydock/controller/DrydockLeaseListController.php @@ -13,8 +13,7 @@ final class DrydockLeaseListController extends DrydockLeaseController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new DrydockLeaseSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/drydock/controller/DrydockLogListController.php b/src/applications/drydock/controller/DrydockLogListController.php index 68047922f2..eaeb411d28 100644 --- a/src/applications/drydock/controller/DrydockLogListController.php +++ b/src/applications/drydock/controller/DrydockLogListController.php @@ -13,8 +13,7 @@ final class DrydockLogListController extends DrydockLogController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new DrydockLogSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/drydock/controller/DrydockResourceListController.php b/src/applications/drydock/controller/DrydockResourceListController.php index 3da5841e21..53ed62a743 100644 --- a/src/applications/drydock/controller/DrydockResourceListController.php +++ b/src/applications/drydock/controller/DrydockResourceListController.php @@ -13,8 +13,7 @@ final class DrydockResourceListController extends DrydockResourceController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new DrydockResourceSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/feed/PhabricatorFeedStoryPublisher.php b/src/applications/feed/PhabricatorFeedStoryPublisher.php index 7cb3d33884..5c6940fc8b 100644 --- a/src/applications/feed/PhabricatorFeedStoryPublisher.php +++ b/src/applications/feed/PhabricatorFeedStoryPublisher.php @@ -121,8 +121,8 @@ final class PhabricatorFeedStoryPublisher { } $subscribed_phids = $this->subscribedPHIDs; - $subscribed_phids = $this->filterSubscribedPHIDs($subscribed_phids); if ($subscribed_phids) { + $subscribed_phids = $this->filterSubscribedPHIDs($subscribed_phids); $this->insertNotifications($chrono_key, $subscribed_phids); $this->sendNotification($chrono_key, $subscribed_phids); } @@ -164,12 +164,15 @@ final class PhabricatorFeedStoryPublisher { $mark_read); } - queryfx( - $conn, - 'INSERT INTO %T (primaryObjectPHID, userPHID, chronologicalKey, hasViewed) - VALUES %Q', - $notif->getTableName(), - implode(', ', $sql)); + if ($sql) { + queryfx( + $conn, + 'INSERT INTO %T '. + '(primaryObjectPHID, userPHID, chronologicalKey, hasViewed) '. + 'VALUES %Q', + $notif->getTableName(), + implode(', ', $sql)); + } } private function sendNotification($chrono_key, array $subscribed_phids) { diff --git a/src/applications/feed/controller/PhabricatorFeedListController.php b/src/applications/feed/controller/PhabricatorFeedListController.php index d6a81cd23a..f317b477be 100644 --- a/src/applications/feed/controller/PhabricatorFeedListController.php +++ b/src/applications/feed/controller/PhabricatorFeedListController.php @@ -13,8 +13,7 @@ final class PhabricatorFeedListController extends PhabricatorFeedController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorFeedSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php index 26bcbca024..a115985704 100644 --- a/src/applications/feed/story/PhabricatorFeedStory.php +++ b/src/applications/feed/story/PhabricatorFeedStory.php @@ -387,7 +387,7 @@ abstract class PhabricatorFeedStory } } - final protected function renderSummary($text, $len = 128) { + final public function renderSummary($text, $len = 128) { if ($len) { $text = id(new PhutilUTF8StringTruncator()) ->setMaximumGlyphs($len) diff --git a/src/applications/files/controller/PhabricatorFileListController.php b/src/applications/files/controller/PhabricatorFileListController.php index feea6db3a6..bf7f0a955c 100644 --- a/src/applications/files/controller/PhabricatorFileListController.php +++ b/src/applications/files/controller/PhabricatorFileListController.php @@ -13,8 +13,7 @@ final class PhabricatorFileListController extends PhabricatorFileController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->key) ->setSearchEngine(new PhabricatorFileSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/flag/controller/PhabricatorFlagListController.php b/src/applications/flag/controller/PhabricatorFlagListController.php index f70d100a55..9029a35a7e 100644 --- a/src/applications/flag/controller/PhabricatorFlagListController.php +++ b/src/applications/flag/controller/PhabricatorFlagListController.php @@ -13,8 +13,7 @@ final class PhabricatorFlagListController extends PhabricatorFlagController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorFlagSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/fund/controller/FundBackerListController.php b/src/applications/fund/controller/FundBackerListController.php index 157e1be4b0..0cc0f5a0f5 100644 --- a/src/applications/fund/controller/FundBackerListController.php +++ b/src/applications/fund/controller/FundBackerListController.php @@ -7,6 +7,10 @@ final class FundBackerListController private $queryKey; private $initiative; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->id = idx($data, 'id'); $this->queryKey = idx($data, 'queryKey'); @@ -25,7 +29,7 @@ final class FundBackerListController } } - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine($this->getEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/fund/controller/FundInitiativeListController.php b/src/applications/fund/controller/FundInitiativeListController.php index ad7fd1cf97..490a676390 100644 --- a/src/applications/fund/controller/FundInitiativeListController.php +++ b/src/applications/fund/controller/FundInitiativeListController.php @@ -5,13 +5,16 @@ final class FundInitiativeListController private $queryKey; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->queryKey = idx($data, 'queryKey'); } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new FundInitiativeSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index 24d5dbe083..c3c64b7556 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -5,6 +5,10 @@ final class FundInitiativeViewController private $id; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->id = $data['id']; } @@ -50,6 +54,7 @@ final class FundInitiativeViewController $properties = $this->buildPropertyListView($initiative); $actions = $this->buildActionListView($initiative); $properties->setActionList($actions); + $crumbs->setActionList($actions); $box = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -74,6 +79,7 @@ final class FundInitiativeViewController ), array( 'title' => $title, + 'pageObjects' => array($initiative->getPHID()), )); } diff --git a/src/applications/fund/editor/FundInitiativeEditor.php b/src/applications/fund/editor/FundInitiativeEditor.php index b1e32d4db8..05041ba119 100644 --- a/src/applications/fund/editor/FundInitiativeEditor.php +++ b/src/applications/fund/editor/FundInitiativeEditor.php @@ -104,6 +104,8 @@ final class FundInitiativeEditor return; case PhabricatorTransactions::TYPE_SUBSCRIBERS: case PhabricatorTransactions::TYPE_EDGE: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: return; } @@ -156,6 +158,8 @@ final class FundInitiativeEditor return; case PhabricatorTransactions::TYPE_SUBSCRIBERS: case PhabricatorTransactions::TYPE_EDGE: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: return; } @@ -257,6 +261,18 @@ final class FundInitiativeEditor ->addHeader('Thread-Topic', $monogram); } + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $body = parent::buildMailBody($object, $xactions); + + $body->addLinkSection( + pht('INITIATIVE DETAIL'), + PhabricatorEnv::getProductionURI('/'.$object->getMonogram())); + + return $body; + } protected function getMailTo(PhabricatorLiskDAO $object) { return array($object->getOwnerPHID()); @@ -277,6 +293,8 @@ final class FundInitiativeEditor return true; } - + protected function supportsSearch() { + return true; + } } diff --git a/src/applications/fund/search/FundInitiativeIndexer.php b/src/applications/fund/search/FundInitiativeIndexer.php new file mode 100644 index 0000000000..a5be183980 --- /dev/null +++ b/src/applications/fund/search/FundInitiativeIndexer.php @@ -0,0 +1,58 @@ +setViewer($this->getViewer()) + ->withPHIDs(array($phid)) + ->executeOne(); + if (!$object) { + throw new Exception("Unable to load object by phid '{$phid}'!"); + } + return $object; + } + + protected function buildAbstractDocumentByPHID($phid) { + $initiative = $this->loadDocumentByPHID($phid); + + $doc = id(new PhabricatorSearchAbstractDocument()) + ->setPHID($initiative->getPHID()) + ->setDocumentType(FundInitiativePHIDType::TYPECONST) + ->setDocumentTitle($initiative->getName()) + ->setDocumentCreated($initiative->getDateCreated()) + ->setDocumentModified($initiative->getDateModified()); + + $doc->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, + $initiative->getOwnerPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $initiative->getDateCreated()); + + $doc->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_OWNER, + $initiative->getOwnerPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $initiative->getDateCreated()); + + $doc->addRelationship( + $initiative->isClosed() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $initiative->getPHID(), + FundInitiativePHIDType::TYPECONST, + time()); + + $this->indexTransactions( + $doc, + new FundInitiativeTransactionQuery(), + array($initiative->getPHID())); + + return $doc; + } +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php index 2f83d30f0b..aac41dc57b 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php @@ -13,8 +13,7 @@ final class HarbormasterBuildableListController extends HarbormasterController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new HarbormasterBuildableSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/harbormaster/controller/HarbormasterPlanListController.php b/src/applications/harbormaster/controller/HarbormasterPlanListController.php index 0d94f0bb69..0e2cb3e233 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanListController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanListController.php @@ -13,8 +13,7 @@ final class HarbormasterPlanListController extends HarbormasterPlanController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new HarbormasterBuildPlanSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/herald/controller/HeraldRuleListController.php b/src/applications/herald/controller/HeraldRuleListController.php index 7fe16c26bf..8847b5d06b 100644 --- a/src/applications/herald/controller/HeraldRuleListController.php +++ b/src/applications/herald/controller/HeraldRuleListController.php @@ -13,8 +13,7 @@ final class HeraldRuleListController extends HeraldController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new HeraldRuleSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/herald/controller/HeraldTranscriptListController.php b/src/applications/herald/controller/HeraldTranscriptListController.php index dfa79e252e..fc531c6aa9 100644 --- a/src/applications/herald/controller/HeraldTranscriptListController.php +++ b/src/applications/herald/controller/HeraldTranscriptListController.php @@ -37,8 +37,7 @@ final class HeraldTranscriptListController extends HeraldController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new HeraldTranscriptSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/legalpad/controller/LegalpadDocumentListController.php b/src/applications/legalpad/controller/LegalpadDocumentListController.php index 28221c643f..483ccde581 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentListController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentListController.php @@ -13,8 +13,7 @@ final class LegalpadDocumentListController extends LegalpadController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new LegalpadDocumentSearchEngine()) ->setNavigation($this->buildSideNav()); diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php index 1c0591c36b..17d5cdc92c 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php @@ -38,7 +38,7 @@ final class LegalpadDocumentSignatureListController extends LegalpadController { $engine->setDocument($this->document); } - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine($engine) ->setNavigation($this->buildSideNav()); diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php index 80dd262716..98104690d0 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -194,7 +194,7 @@ final class LegalpadDocumentEditor $body = parent::buildMailBody($object, $xactions); - $body->addTextSection( + $body->addLinkSection( pht('DOCUMENT DETAIL'), PhabricatorEnv::getProductionURI('/legalpad/view/'.$object->getID().'/')); diff --git a/src/applications/macro/controller/PhabricatorMacroListController.php b/src/applications/macro/controller/PhabricatorMacroListController.php index 57ab3c891e..730f8de3b1 100644 --- a/src/applications/macro/controller/PhabricatorMacroListController.php +++ b/src/applications/macro/controller/PhabricatorMacroListController.php @@ -13,8 +13,7 @@ final class PhabricatorMacroListController extends PhabricatorMacroController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->key) ->setSearchEngine(new PhabricatorMacroSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php index 09d4c99fb4..fe5259a1d2 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditor.php +++ b/src/applications/macro/editor/PhabricatorMacroEditor.php @@ -173,7 +173,7 @@ final class PhabricatorMacroEditor array $xactions) { $body = parent::buildMailBody($object, $xactions); - $body->addTextSection( + $body->addLinkSection( pht('MACRO DETAIL'), PhabricatorEnv::getProductionURI('/macro/view/'.$object->getID().'/')); diff --git a/src/applications/mailinglists/controller/PhabricatorMailingListsListController.php b/src/applications/mailinglists/controller/PhabricatorMailingListsListController.php index f4285c77ec..4d993e9f8e 100644 --- a/src/applications/mailinglists/controller/PhabricatorMailingListsListController.php +++ b/src/applications/mailinglists/controller/PhabricatorMailingListsListController.php @@ -14,8 +14,7 @@ final class PhabricatorMailingListsListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorMailingListSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/maniphest/controller/ManiphestTaskListController.php b/src/applications/maniphest/controller/ManiphestTaskListController.php index 79926d9574..f3aa046457 100644 --- a/src/applications/maniphest/controller/ManiphestTaskListController.php +++ b/src/applications/maniphest/controller/ManiphestTaskListController.php @@ -15,10 +15,6 @@ final class ManiphestTaskListController } public function processRequest() { - $request = $this->getRequest(); - $searchEngine = new ManiphestTaskSearchEngine(); - $searchEngine->setProjectKey($this->projectKey); - $searchEngine->setTaskTypeKey($this->taskTypeKey); $controller = id(new ManiphestSearchController($request)) ->setQueryKey($this->queryKey) ->setProjectKey($this->projectKey) diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index c2cd4455c8..1fb7d2ec52 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -497,7 +497,7 @@ final class ManiphestTransactionEditor $object->getDescription()); } - $body->addTextSection( + $body->addLinkSection( pht('TASK DETAIL'), PhabricatorEnv::getProductionURI('/T'.$object->getID())); diff --git a/src/applications/meta/controller/PhabricatorApplicationsListController.php b/src/applications/meta/controller/PhabricatorApplicationsListController.php index 9bcb845768..f6aa650334 100644 --- a/src/applications/meta/controller/PhabricatorApplicationsListController.php +++ b/src/applications/meta/controller/PhabricatorApplicationsListController.php @@ -14,8 +14,7 @@ final class PhabricatorApplicationsListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorAppSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php index 9f62049444..234438698b 100644 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php @@ -75,7 +75,7 @@ final class PhabricatorMailImplementationTestAdapter } public function supportsMessageIDHeader() { - return $this->config['supportsMessageIDHeader']; + return idx($this->config, 'supportsMessageIDHeader', true); } public function send() { diff --git a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php index e52b5c52c0..389549da7b 100644 --- a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php +++ b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php @@ -82,7 +82,13 @@ final class PhabricatorMetaMTAMailBody { phutil_tag('div', array('style' => 'font-weight:800;'), $header), $html_fragment, ); + return $this; + } + public function addLinkSection($header, $link) { + $html = phutil_tag('a', array('href' => $link), $link); + $this->addPlaintextSection($header, $link); + $this->addHTMLSection($header, $html); return $this; } @@ -98,7 +104,7 @@ final class PhabricatorMetaMTAMailBody { return $this; } - $this->addTextSection( + $this->addLinkSection( pht('WHY DID I GET THIS EMAIL?'), PhabricatorEnv::getProductionURI($xscript_uri)); diff --git a/src/applications/metamta/view/PhabricatorMetaMTAMailSection.php b/src/applications/metamta/view/PhabricatorMetaMTAMailSection.php index 811b4c3127..701511dda2 100644 --- a/src/applications/metamta/view/PhabricatorMetaMTAMailSection.php +++ b/src/applications/metamta/view/PhabricatorMetaMTAMailSection.php @@ -22,7 +22,6 @@ final class PhabricatorMetaMTAMailSection { public function addHTMLFragment($fragment) { $this->htmlFragments[] = $fragment; return $this; - } public function addPlaintextFragment($fragment) { diff --git a/src/applications/notification/controller/PhabricatorNotificationListController.php b/src/applications/notification/controller/PhabricatorNotificationListController.php index 771a032cd3..b7ab15df90 100644 --- a/src/applications/notification/controller/PhabricatorNotificationListController.php +++ b/src/applications/notification/controller/PhabricatorNotificationListController.php @@ -10,8 +10,7 @@ final class PhabricatorNotificationListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorNotificationSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php index ee5d85661a..d4105c6047 100644 --- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php +++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php @@ -14,8 +14,7 @@ final class PhabricatorOAuthClientListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorOAuthServerClientSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/passphrase/controller/PassphraseCredentialListController.php b/src/applications/passphrase/controller/PassphraseCredentialListController.php index dae53bc463..db36a4a6ba 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialListController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialListController.php @@ -13,8 +13,7 @@ final class PassphraseCredentialListController extends PassphraseController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PassphraseCredentialSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/paste/controller/PhabricatorPasteListController.php b/src/applications/paste/controller/PhabricatorPasteListController.php index c4134972ca..3d8cf7fc11 100644 --- a/src/applications/paste/controller/PhabricatorPasteListController.php +++ b/src/applications/paste/controller/PhabricatorPasteListController.php @@ -13,8 +13,7 @@ final class PhabricatorPasteListController extends PhabricatorPasteController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorPasteSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/paste/editor/PhabricatorPasteEditor.php b/src/applications/paste/editor/PhabricatorPasteEditor.php index 26c79147f1..6e038fe11d 100644 --- a/src/applications/paste/editor/PhabricatorPasteEditor.php +++ b/src/applications/paste/editor/PhabricatorPasteEditor.php @@ -167,7 +167,7 @@ final class PhabricatorPasteEditor $body = parent::buildMailBody($object, $xactions); - $body->addTextSection( + $body->addLinkSection( pht('PASTE DETAIL'), PhabricatorEnv::getProductionURI('/P'.$object->getID())); diff --git a/src/applications/people/controller/PhabricatorPeopleListController.php b/src/applications/people/controller/PhabricatorPeopleListController.php index ae1d772eff..3888ddfa8a 100644 --- a/src/applications/people/controller/PhabricatorPeopleListController.php +++ b/src/applications/people/controller/PhabricatorPeopleListController.php @@ -18,13 +18,10 @@ final class PhabricatorPeopleListController } public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - $this->requireApplicationCapability( PeopleBrowseUserDirectoryCapability::CAPABILITY); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->key) ->setSearchEngine(new PhabricatorPeopleSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/people/controller/PhabricatorPeopleLogsController.php b/src/applications/people/controller/PhabricatorPeopleLogsController.php index be05cc3481..95bfc14cd7 100644 --- a/src/applications/people/controller/PhabricatorPeopleLogsController.php +++ b/src/applications/people/controller/PhabricatorPeopleLogsController.php @@ -10,8 +10,7 @@ final class PhabricatorPeopleLogsController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorPeopleLogSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index 190d3f1c62..3dab246787 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -156,51 +156,6 @@ final class PhabricatorPeopleProfilePictureController } } - // Try to add Gravatar images for any email addresses associated with the - // account. - if (PhabricatorEnv::getEnvConfig('security.allow-outbound-http')) { - $emails = id(new PhabricatorUserEmail())->loadAllWhere( - 'userPHID = %s ORDER BY address', - $user->getPHID()); - - $futures = array(); - foreach ($emails as $email_object) { - $email = $email_object->getAddress(); - - $hash = md5(strtolower(trim($email))); - $uri = id(new PhutilURI("https://secure.gravatar.com/avatar/{$hash}")) - ->setQueryParams( - array( - 'size' => 200, - 'default' => '404', - 'rating' => 'x', - )); - $futures[$email] = new HTTPSFuture($uri); - } - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - foreach (Futures($futures) as $email => $future) { - try { - list($body) = $future->resolvex(); - $file = PhabricatorFile::newFromFileData( - $body, - array( - 'name' => 'profile-gravatar', - 'ttl' => (60 * 60 * 4), - )); - if ($file->isTransformableImage()) { - $images[$file->getPHID()] = array( - 'uri' => $file->getBestURI(), - 'tip' => pht('Gravatar for %s', $email), - ); - } - } catch (Exception $ex) { - // Just continue. - } - } - unset($unguarded); - } - $images[PhabricatorPHIDConstants::PHID_VOID] = array( 'uri' => $default_image->getBestURI(), 'tip' => pht('Default Picture'), diff --git a/src/applications/people/storage/PhabricatorUserSchemaSpec.php b/src/applications/people/storage/PhabricatorUserSchemaSpec.php index fa01b8c210..4d9bab1b4f 100644 --- a/src/applications/people/storage/PhabricatorUserSchemaSpec.php +++ b/src/applications/people/storage/PhabricatorUserSchemaSpec.php @@ -9,7 +9,7 @@ final class PhabricatorUserSchemaSpec extends PhabricatorConfigSchemaSpec { id(new PhabricatorUser())->getApplicationName(), PhabricatorUser::NAMETOKEN_TABLE, array( - 'token' => 'text255', + 'token' => 'sort255', 'userID' => 'id', ), array( diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index 033b98d7e2..fb74ae1644 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -72,7 +72,8 @@ final class PhameBlog extends PhameDAO 'load.'); } - $skin = newv($spec->getSkinClass(), array($request)); + $skin = newv($spec->getSkinClass(), array()); + $skin->setRequest($request); $skin->setSpecification($spec); return $skin; diff --git a/src/applications/pholio/controller/PholioMockListController.php b/src/applications/pholio/controller/PholioMockListController.php index 6667457bff..c4d7fb4117 100644 --- a/src/applications/pholio/controller/PholioMockListController.php +++ b/src/applications/pholio/controller/PholioMockListController.php @@ -13,8 +13,7 @@ final class PholioMockListController extends PholioController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PholioMockSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index fb12b8fc6a..7ba95455b5 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -409,7 +409,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } } - $body->addTextSection( + $body->addLinkSection( pht('MOCK DETAIL'), PhabricatorEnv::getProductionURI('/M'.$object->getID())); @@ -462,18 +462,17 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { HeraldAdapter $adapter, HeraldTranscript $transcript) { - // TODO: Convert this to be transaction-based. + $xactions = array(); $cc_phids = $adapter->getCcPHIDs(); if ($cc_phids) { - id(new PhabricatorSubscriptionsEditor()) - ->setObject($object) - ->setActor($this->requireActor()) - ->subscribeImplicit($cc_phids) - ->save(); + $value = array_fuse($cc_phids); + $xactions[] = id(new PholioTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) + ->setNewValue(array('+' => $value)); } - return array(); + return $xactions; } protected function sortTransactions(array $xactions) { diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index f3928d7a7a..656b304662 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -39,7 +39,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { 'card/' => array( 'new/' => 'PhortunePaymentMethodCreateController', ), - 'buy/(?P\d+)/' => 'PhortuneProductPurchaseController', + 'order/(?:query/(?P[^/]+)/)?' + => 'PhortuneCartListController', + 'charge/(?:query/(?P[^/]+)/)?' + => 'PhortuneChargeListController', ), 'card/(?P\d+)/' => array( 'edit/' => 'PhortunePaymentMethodEditController', diff --git a/src/applications/phortune/controller/PhortuneAccountListController.php b/src/applications/phortune/controller/PhortuneAccountListController.php index 9e928f9c46..eff139af7e 100644 --- a/src/applications/phortune/controller/PhortuneAccountListController.php +++ b/src/applications/phortune/controller/PhortuneAccountListController.php @@ -18,11 +18,7 @@ final class PhortuneAccountListController extends PhortuneController { $merchants = id(new PhortuneMerchantQuery()) ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) + ->withMemberPHIDs(array($viewer->getPHID())) ->execute(); $title = pht('Accounts'); @@ -87,8 +83,8 @@ final class PhortuneAccountListController extends PhortuneController { ->setHref($this->getApplicationURI('merchant/')) ->setIcon( id(new PHUIIconView()) - ->setIconFont('fa-folder-open')) - ->setText(pht('Browse Merchants'))); + ->setIconFont('fa-list')) + ->setText(pht('View All Merchants'))); $merchant_box = id(new PHUIObjectBoxView()) ->setHeader($merchant_header) diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php index e3189fe5b9..4d6198cf5b 100644 --- a/src/applications/phortune/controller/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -179,6 +179,7 @@ final class PhortuneAccountViewController extends PhortuneController { PhortuneCart::STATUS_REVIEW, PhortuneCart::STATUS_PURCHASED, )) + ->setLimit(10) ->execute(); $phids = array(); @@ -190,73 +191,23 @@ final class PhortuneAccountViewController extends PhortuneController { } $handles = $this->loadViewerHandles($phids); - $rows = array(); - $rowc = array(); - foreach ($carts as $cart) { - $cart_link = $handles[$cart->getPHID()]->renderLink(); - $purchases = $cart->getPurchases(); + $orders_uri = $this->getApplicationURI($account->getID().'/order/'); - if (count($purchases) == 1) { - $purchase_name = $handles[$purchase->getPHID()]->renderLink(); - $purchases = array(); - } else { - $purchase_name = ''; - } - - $rowc[] = ''; - $rows[] = array( - $cart->getID(), - phutil_tag( - 'strong', - array(), - $cart_link), - $purchase_name, - phutil_tag( - 'strong', - array(), - $cart->getTotalPriceAsCurrency()->formatForDisplay()), - PhortuneCart::getNameForStatus($cart->getStatus()), - phabricator_datetime($cart->getDateModified(), $viewer), - ); - foreach ($purchases as $purchase) { - $id = $purchase->getID(); - - $price = $purchase->getTotalPriceAsCurrency()->formatForDisplay(); - - $rowc[] = ''; - $rows[] = array( - '', - $handles[$purchase->getPHID()]->renderLink(), - $price, - '', - '', - ); - } - } - - $table = id(new AphrontTableView($rows)) - ->setRowClasses($rowc) - ->setHeaders( - array( - pht('ID'), - pht('Order'), - pht('Purchase'), - pht('Amount'), - pht('Status'), - pht('Updated'), - )) - ->setColumnClasses( - array( - '', - '', - 'wide', - 'right', - '', - 'right', - )); + $table = id(new PhortuneOrderTableView()) + ->setUser($viewer) + ->setCarts($carts) + ->setHandles($handles); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Order History')); + ->setHeader(pht('Recent Orders')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon( + id(new PHUIIconView()) + ->setIconFont('fa-list')) + ->setHref($orders_uri) + ->setText(pht('View All Orders'))); return id(new PHUIObjectBoxView()) ->setHeader($header) @@ -271,9 +222,40 @@ final class PhortuneAccountViewController extends PhortuneController { ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) ->needCarts(true) + ->setLimit(10) ->execute(); - return $this->buildChargesTable($charges); + $phids = array(); + foreach ($charges as $charge) { + $phids[] = $charge->getProviderPHID(); + $phids[] = $charge->getCartPHID(); + $phids[] = $charge->getMerchantPHID(); + $phids[] = $charge->getPaymentMethodPHID(); + } + + $handles = $this->loadViewerHandles($phids); + + $charges_uri = $this->getApplicationURI($account->getID().'/charge/'); + + $table = id(new PhortuneChargeTableView()) + ->setUser($viewer) + ->setCharges($charges) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Recent Charges')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon( + id(new PHUIIconView()) + ->setIconFont('fa-list')) + ->setHref($charges_uri) + ->setText(pht('View All Charges'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); } private function buildAccountHistorySection(PhortuneAccount $account) { @@ -285,14 +267,10 @@ final class PhortuneAccountViewController extends PhortuneController { ->withObjectPHIDs(array($account->getPHID())) ->execute(); - $engine = id(new PhabricatorMarkupEngine()) - ->setViewer($user); - $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setObjectPHID($account->getPHID()) - ->setTransactions($xactions) - ->setMarkupEngine($engine); + ->setTransactions($xactions); return $xaction_view; } @@ -309,5 +287,4 @@ final class PhortuneAccountViewController extends PhortuneController { return $crumbs; } - } diff --git a/src/applications/phortune/controller/PhortuneCartListController.php b/src/applications/phortune/controller/PhortuneCartListController.php index 57f486b6c3..524152e8d3 100644 --- a/src/applications/phortune/controller/PhortuneCartListController.php +++ b/src/applications/phortune/controller/PhortuneCartListController.php @@ -3,13 +3,16 @@ final class PhortuneCartListController 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'); } @@ -34,9 +37,26 @@ final class PhortuneCartListController } $this->merchant = $merchant; $engine->setMerchant($merchant); + } else if ($this->accountID) { + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($this->accountID)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + $this->account = $account; + $engine->setAccount($account); + } else { + return new Aphront404Response(); } - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); @@ -73,6 +93,17 @@ final class PhortuneCartListController $this->getApplicationURI("merchant/orders/{$id}/")); } + $account = $this->account; + if ($account) { + $id = $account->getID(); + $crumbs->addTextCrumb( + $account->getName(), + $this->getApplicationURI("{$id}/")); + $crumbs->addTextCrumb( + pht('Orders'), + $this->getApplicationURI("{$id}/order/")); + } + return $crumbs; } diff --git a/src/applications/phortune/controller/PhortuneCartViewController.php b/src/applications/phortune/controller/PhortuneCartViewController.php index 84d74db8ce..d02182ac36 100644 --- a/src/applications/phortune/controller/PhortuneCartViewController.php +++ b/src/applications/phortune/controller/PhortuneCartViewController.php @@ -135,19 +135,49 @@ final class PhortuneCartViewController ->needCarts(true) ->execute(); - $charges_table = $this->buildChargesTable($charges, false); + $phids = array(); + foreach ($charges as $charge) { + $phids[] = $charge->getProviderPHID(); + $phids[] = $charge->getCartPHID(); + $phids[] = $charge->getMerchantPHID(); + $phids[] = $charge->getPaymentMethodPHID(); + } + $handles = $this->loadViewerHandles($phids); + + $charges_table = id(new PhortuneChargeTableView()) + ->setUser($viewer) + ->setHandles($handles) + ->setCharges($charges) + ->setShowOrder(false); + + $charges = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Charges')) + ->appendChild($charges_table); $account = $cart->getAccount(); $crumbs = $this->buildApplicationCrumbs(); $this->addAccountCrumb($crumbs, $cart->getAccount()); $crumbs->addTextCrumb(pht('Cart %d', $cart->getID())); + $crumbs->setActionList($actions); + + $xactions = id(new PhortuneCartTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($cart->getPHID())) + ->execute(); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setObjectPHID($cart->getPHID()) + ->setTransactions($xactions) + ->setShouldTerminate(true); return $this->buildApplicationPage( array( $crumbs, $cart_box, - $charges_table, + $charges, + $xaction_view, ), array( 'title' => pht('Cart'), diff --git a/src/applications/phortune/controller/PhortuneChargeListController.php b/src/applications/phortune/controller/PhortuneChargeListController.php new file mode 100644 index 0000000000..73424dee5f --- /dev/null +++ b/src/applications/phortune/controller/PhortuneChargeListController.php @@ -0,0 +1,81 @@ +accountID = idx($data, 'accountID'); + $this->queryKey = idx($data, 'queryKey'); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $engine = new PhortuneChargeSearchEngine(); + + if ($this->accountID) { + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($this->accountID)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + $this->account = $account; + $engine->setAccount($account); + } else { + return new Aphront404Response(); + } + + $controller = id(new PhabricatorApplicationSearchController()) + ->setQueryKey($this->queryKey) + ->setSearchEngine($engine) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildSideNavView() { + $viewer = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new PhortuneChargeSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $account = $this->account; + if ($account) { + $id = $account->getID(); + $crumbs->addTextCrumb( + $account->getName(), + $this->getApplicationURI("{$id}/")); + $crumbs->addTextCrumb( + pht('Charges'), + $this->getApplicationURI("{$id}/charge/")); + } + + return $crumbs; + } + +} diff --git a/src/applications/phortune/controller/PhortuneController.php b/src/applications/phortune/controller/PhortuneController.php index ae15fb495e..f8f85b4d83 100644 --- a/src/applications/phortune/controller/PhortuneController.php +++ b/src/applications/phortune/controller/PhortuneController.php @@ -2,79 +2,6 @@ abstract class PhortuneController extends PhabricatorController { - protected function loadActiveAccount(PhabricatorUser $user) { - return PhortuneAccountQuery::loadActiveAccountForUser( - $user, - PhabricatorContentSource::newFromRequest($this->getRequest())); - } - - protected function buildChargesTable(array $charges, $show_cart = true) { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $phids = array(); - foreach ($charges as $charge) { - $phids[] = $charge->getProviderPHID(); - $phids[] = $charge->getCartPHID(); - $phids[] = $charge->getMerchantPHID(); - $phids[] = $charge->getPaymentMethodPHID(); - } - - $handles = $this->loadViewerHandles($phids); - - $rows = array(); - foreach ($charges as $charge) { - $rows[] = array( - $charge->getID(), - $handles[$charge->getCartPHID()]->renderLink(), - $handles[$charge->getProviderPHID()]->renderLink(), - $charge->getPaymentMethodPHID() - ? $handles[$charge->getPaymentMethodPHID()]->renderLink() - : null, - $handles[$charge->getMerchantPHID()]->renderLink(), - $charge->getAmountAsCurrency()->formatForDisplay(), - $charge->getStatusForDisplay(), - phabricator_datetime($charge->getDateCreated(), $viewer), - ); - } - - $charge_table = id(new AphrontTableView($rows)) - ->setHeaders( - array( - pht('ID'), - pht('Cart'), - pht('Provider'), - pht('Method'), - pht('Merchant'), - pht('Amount'), - pht('Status'), - pht('Created'), - )) - ->setColumnClasses( - array( - '', - '', - '', - '', - '', - 'wide right', - '', - '', - )) - ->setColumnVisibility( - array( - true, - $show_cart, - )); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Charge History')); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->appendChild($charge_table); - } - protected function addAccountCrumb( $crumbs, PhortuneAccount $account, diff --git a/src/applications/phortune/controller/PhortuneMerchantEditController.php b/src/applications/phortune/controller/PhortuneMerchantEditController.php index 3ed850bcb2..fa7b5391d1 100644 --- a/src/applications/phortune/controller/PhortuneMerchantEditController.php +++ b/src/applications/phortune/controller/PhortuneMerchantEditController.php @@ -32,6 +32,7 @@ final class PhortuneMerchantEditController PhortuneMerchantCapability::CAPABILITY); $merchant = PhortuneMerchant::initializeNewMerchant($viewer); + $merchant->attachMemberPHIDs(array($viewer->getPHID())); $is_new = true; } @@ -52,6 +53,8 @@ final class PhortuneMerchantEditController $e_name = true; $v_name = $merchant->getName(); $v_desc = $merchant->getDescription(); + $v_members = $merchant->getMemberPHIDs(); + $e_members = null; $validation_exception = null; if ($request->isFormPost()) { @@ -59,11 +62,14 @@ final class PhortuneMerchantEditController $v_desc = $request->getStr('desc'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); + $v_members = $request->getArr('memberPHIDs'); $type_name = PhortuneMerchantTransaction::TYPE_NAME; $type_desc = PhortuneMerchantTransaction::TYPE_DESCRIPTION; + $type_edge = PhabricatorTransactions::TYPE_EDGE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $edge_members = PhortuneMerchantHasMemberEdgeType::EDGECONST; $xactions = array(); @@ -80,8 +86,12 @@ final class PhortuneMerchantEditController ->setNewValue($v_view); $xactions[] = id(new PhortuneMerchantTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); + ->setTransactionType($type_edge) + ->setMetadataValue('edge:type', $edge_members) + ->setNewValue( + array( + '=' => array_fuse($v_members), + )); $editor = id(new PhortuneMerchantEditor()) ->setActor($viewer) @@ -98,9 +108,9 @@ final class PhortuneMerchantEditController $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); + $e_mbmers = $ex->getShortMessage($type_edge); $merchant->setViewPolicy($v_view); - $merchant->setEditPolicy($v_edit); } } @@ -109,6 +119,8 @@ final class PhortuneMerchantEditController ->setObject($merchant) ->execute(); + $member_handles = $this->loadViewerHandles($v_members); + $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( @@ -122,18 +134,19 @@ final class PhortuneMerchantEditController ->setName('desc') ->setLabel(pht('Description')) ->setValue($v_desc)) + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorPeopleDatasource()) + ->setLabel(pht('Members')) + ->setName('memberPHIDs') + ->setValue($member_handles) + ->setError($e_members)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') ->setPolicyObject($merchant) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($merchant) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($button_text) diff --git a/src/applications/phortune/controller/PhortuneMerchantListController.php b/src/applications/phortune/controller/PhortuneMerchantListController.php index 7947de2d25..2290d09d3d 100644 --- a/src/applications/phortune/controller/PhortuneMerchantListController.php +++ b/src/applications/phortune/controller/PhortuneMerchantListController.php @@ -14,8 +14,7 @@ final class PhortuneMerchantListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhortuneMerchantSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php index 809e5d7ca5..2fe2b76716 100644 --- a/src/applications/phortune/controller/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php @@ -43,6 +43,7 @@ final class PhortuneMerchantViewController $properties = $this->buildPropertyListView($merchant, $providers); $actions = $this->buildActionListView($merchant); $properties->setActionList($actions); + $crumbs->setActionList($actions); $provider_list = $this->buildProviderList( $merchant, @@ -140,6 +141,12 @@ final class PhortuneMerchantViewController $view->addProperty(pht('Status'), $status_view); + $this->loadHandles($merchant->getMemberPHIDs()); + + $view->addProperty( + pht('Members'), + $this->renderHandlesForPHIDs($merchant->getMemberPHIDs())); + $view->invokeWillRenderEvent(); $description = $merchant->getDescription(); diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php index a62d802a56..584ba241f9 100644 --- a/src/applications/phortune/controller/PhortuneProductViewController.php +++ b/src/applications/phortune/controller/PhortuneProductViewController.php @@ -16,7 +16,6 @@ final class PhortuneProductViewController extends PhortuneController { ->setViewer($user) ->withIDs(array($this->productID)) ->executeOne(); - if (!$product) { return new Aphront404Response(); } @@ -26,11 +25,7 @@ final class PhortuneProductViewController extends PhortuneController { $header = id(new PHUIHeaderView()) ->setHeader($product->getProductName()); - $account = $this->loadActiveAccount($user); - $edit_uri = $this->getApplicationURI('product/edit/'.$product->getID().'/'); - $cart_uri = $this->getApplicationURI( - $account->getID().'/buy/'.$product->getID().'/'); $actions = id(new PhabricatorActionListView()) ->setUser($user) diff --git a/src/applications/phortune/controller/PhortunePurchaseViewController.php b/src/applications/phortune/controller/PhortunePurchaseViewController.php deleted file mode 100644 index 01a5f5c43a..0000000000 --- a/src/applications/phortune/controller/PhortunePurchaseViewController.php +++ /dev/null @@ -1,58 +0,0 @@ -purchaseID = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $account = $this->loadActiveAccount($viewer); - - $purchase = id(new PhortunePurchaseQuery()) - ->setViewer($viewer) - ->withIDs(array($this->purchaseID)) - ->executeOne(); - if (!$purchase) { - return new Aphront404Response(); - } - $cart = $purchase->getCart(); - - $title = pht('Purchase: %s', $purchase->getFullDisplayName()); - - $header = id(new PHUIHeaderView()) - ->setHeader($purchase->getFullDisplayName()); - - $crumbs = $this->buildApplicationCrumbs(); - $this->addAccountCrumb($crumbs, $account); - $crumbs->addTextCrumb( - pht('Cart %d', $cart->getID()), - $this->getApplicationURI('cart/'.$cart->getID().'/')); - $crumbs->addTextCrumb( - pht('Purchase %d', $purchase->getID()), - $this->getApplicationURI('purchase/'.$purchase->getID().'/')); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->addProperty(pht('Status'), $purchase->getStatus()); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); - } - -} diff --git a/src/applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php b/src/applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php new file mode 100644 index 0000000000..784228222a --- /dev/null +++ b/src/applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php @@ -0,0 +1,12 @@ +getTransactionType()) { + case PhortuneCartTransaction::TYPE_CREATED: + case PhortuneCartTransaction::TYPE_PURCHASED: + case PhortuneCartTransaction::TYPE_HOLD: + case PhortuneCartTransaction::TYPE_REVIEW: + case PhortuneCartTransaction::TYPE_CANCEL: + case PhortuneCartTransaction::TYPE_REFUND: + return null; + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhortuneCartTransaction::TYPE_CREATED: + case PhortuneCartTransaction::TYPE_PURCHASED: + case PhortuneCartTransaction::TYPE_HOLD: + case PhortuneCartTransaction::TYPE_REVIEW: + case PhortuneCartTransaction::TYPE_CANCEL: + case PhortuneCartTransaction::TYPE_REFUND: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhortuneCartTransaction::TYPE_CREATED: + case PhortuneCartTransaction::TYPE_PURCHASED: + case PhortuneCartTransaction::TYPE_HOLD: + case PhortuneCartTransaction::TYPE_REVIEW: + case PhortuneCartTransaction::TYPE_CANCEL: + case PhortuneCartTransaction::TYPE_REFUND: + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhortuneCartTransaction::TYPE_CREATED: + case PhortuneCartTransaction::TYPE_PURCHASED: + case PhortuneCartTransaction::TYPE_HOLD: + case PhortuneCartTransaction::TYPE_REVIEW: + case PhortuneCartTransaction::TYPE_CANCEL: + case PhortuneCartTransaction::TYPE_REFUND: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function shouldSendMail( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $name = $object->getName(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject(pht('Order %d: %s', $id, $name)) + ->addHeader('Thread-Topic', pht('Order %s', $id)); + } + + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $body = parent::buildMailBody($object, $xactions); + + $items = array(); + foreach ($object->getPurchases() as $purchase) { + $name = $purchase->getFullDisplayName(); + $price = $purchase->getTotalPriceAsCurrency()->formatForDisplay(); + + $items[] = "{$name} {$price}"; + } + + $body->addTextSection(pht('ORDER CONTENTS'), implode("\n", $items)); + + $body->addLinkSection( + pht('ORDER DETAIL'), + PhabricatorEnv::getProductionURI('/phortune/cart/'.$object->getID().'/')); + + return $body; + } + + protected function getMailTo(PhabricatorLiskDAO $object) { + $phids = array(); + + // Relaod the cart to pull merchant and account information, in case we + // just created the object. + $cart = id(new PhortuneCartQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs(array($object->getPHID())) + ->executeOne(); + + foreach ($cart->getAccount()->getMemberPHIDs() as $account_member) { + $phids[] = $account_member; + } + + foreach ($cart->getMerchant()->getMemberPHIDs() as $merchant_member) { + $phids[] = $merchant_member; + } + + return $phids; + } + + protected function getMailCC(PhabricatorLiskDAO $object) { + return array(); + } + + protected function getMailSubjectPrefix() { + return 'Order'; + } + + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new PhortuneCartReplyHandler()) + ->setMailReceiver($object); + } + +} diff --git a/src/applications/phortune/editor/PhortuneMerchantEditor.php b/src/applications/phortune/editor/PhortuneMerchantEditor.php index ddf65a9630..432efe0886 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditor.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditor.php @@ -17,7 +17,7 @@ final class PhortuneMerchantEditor $types[] = PhortuneMerchantTransaction::TYPE_NAME; $types[] = PhortuneMerchantTransaction::TYPE_DESCRIPTION; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; - $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + $types[] = PhabricatorTransactions::TYPE_EDGE; return $types; } @@ -59,6 +59,9 @@ final class PhortuneMerchantEditor case PhortuneMerchantTransaction::TYPE_DESCRIPTION: $object->setDescription($xaction->getNewValue()); return; + case PhabricatorTransactions::TYPE_EDGE: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -71,6 +74,8 @@ final class PhortuneMerchantEditor switch ($xaction->getTransactionType()) { case PhortuneMerchantTransaction::TYPE_NAME: case PhortuneMerchantTransaction::TYPE_DESCRIPTION: + case PhabricatorTransactions::TYPE_EDGE: + case PhabricatorTransactions::TYPE_VIEW_POLICY: return; } @@ -106,5 +111,4 @@ final class PhortuneMerchantEditor return $errors; } - } diff --git a/src/applications/phortune/mail/PhortuneCartReplyHandler.php b/src/applications/phortune/mail/PhortuneCartReplyHandler.php new file mode 100644 index 0000000000..92e3022d43 --- /dev/null +++ b/src/applications/phortune/mail/PhortuneCartReplyHandler.php @@ -0,0 +1,38 @@ +getDefaultPrivateReplyHandlerEmailAddress($handle, 'CART'); + } + + public function getPublicReplyHandlerEmailAddress() { + return $this->getDefaultPublicReplyHandlerEmailAddress('CART'); + } + + public function getReplyHandlerDomain() { + return PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain'); + } + + public function getReplyHandlerInstructions() { + if ($this->supportsReplies()) { + // TODO: Implement. + return null; + } else { + return null; + } + } + + protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { + // TODO: Implement. + return null; + } + +} diff --git a/src/applications/phortune/query/PhortuneAccountQuery.php b/src/applications/phortune/query/PhortuneAccountQuery.php index 51a07bc876..676badcb0c 100644 --- a/src/applications/phortune/query/PhortuneAccountQuery.php +++ b/src/applications/phortune/query/PhortuneAccountQuery.php @@ -27,24 +27,6 @@ final class PhortuneAccountQuery return $accounts; } - public static function loadActiveAccountForUser( - PhabricatorUser $user, - PhabricatorContentSource $content_source) { - - $accounts = id(new PhortuneAccountQuery()) - ->setViewer($user) - ->withMemberPHIDs(array($user->getPHID())) - ->execute(); - - if (!$accounts) { - return PhortuneAccount::createNewAccount($user, $content_source); - } else if (count($accounts) == 1) { - return head($accounts); - } else { - throw new Exception('TODO: No account selection yet.'); - } - } - public function withIDs(array $ids) { $this->ids = $ids; return $this; diff --git a/src/applications/phortune/query/PhortuneCartSearchEngine.php b/src/applications/phortune/query/PhortuneCartSearchEngine.php index 304f311144..88e1777a5e 100644 --- a/src/applications/phortune/query/PhortuneCartSearchEngine.php +++ b/src/applications/phortune/query/PhortuneCartSearchEngine.php @@ -4,6 +4,16 @@ final class PhortuneCartSearchEngine extends PhabricatorApplicationSearchEngine { private $merchant; + private $account; + + public function setAccount(PhortuneAccount $account) { + $this->account = $account; + return $this; + } + + public function getAccount() { + return $this->account; + } public function setMerchant(PhortuneMerchant $merchant) { $this->merchant = $merchant; @@ -39,6 +49,7 @@ final class PhortuneCartSearchEngine $viewer = $this->requireViewer(); $merchant = $this->getMerchant(); + $account = $this->getAccount(); if ($merchant) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -49,9 +60,21 @@ final class PhortuneCartSearchEngine pht('You can not query orders for a merchant you do not control.')); } $query->withMerchantPHIDs(array($merchant->getPHID())); + } else if ($account) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + throw new Exception( + pht( + 'You can not query orders for an account you are not '. + 'a member of.')); + } + $query->withAccountPHIDs(array($account->getPHID())); } else { $accounts = id(new PhortuneAccountQuery()) - ->withMemberPHIDs($viewer->getPHID()) + ->withMemberPHIDs(array($viewer->getPHID())) ->execute(); if ($accounts) { $query->withAccountPHIDs(mpull($accounts, 'getPHID')); @@ -69,8 +92,11 @@ final class PhortuneCartSearchEngine protected function getURI($path) { $merchant = $this->getMerchant(); + $account = $this->getAccount(); if ($merchant) { return '/phortune/merchant/'.$merchant->getID().'/order/'.$path; + } else if ($account) { + return '/phortune/'.$account->getID().'/order/'; } else { return '/phortune/order/'.$path; } diff --git a/src/applications/phortune/query/PhortuneCartTransactionQuery.php b/src/applications/phortune/query/PhortuneCartTransactionQuery.php new file mode 100644 index 0000000000..a72b74814f --- /dev/null +++ b/src/applications/phortune/query/PhortuneCartTransactionQuery.php @@ -0,0 +1,10 @@ +account = $account; + return $this; + } + + public function getAccount() { + return $this->account; + } + + public function getResultTypeDescription() { + return pht('Phortune Charges'); + } + + public function buildSavedQueryFromRequest(AphrontRequest $request) { + $saved = new PhabricatorSavedQuery(); + + return $saved; + } + + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $query = id(new PhortuneChargeQuery()); + + $viewer = $this->requireViewer(); + + $account = $this->getAccount(); + if ($account) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + throw new Exception( + pht( + 'You can not query charges for an account you are not '. + 'a member of.')); + } + $query->withAccountPHIDs(array($account->getPHID())); + } else { + $accounts = id(new PhortuneAccountQuery()) + ->withMemberPHIDs(array($viewer->getPHID())) + ->execute(); + if ($accounts) { + $query->withAccountPHIDs(mpull($accounts, 'getPHID')); + } else { + throw new Exception(pht('You have no accounts!')); + } + } + + return $query; + } + + public function buildSearchForm( + AphrontFormView $form, + PhabricatorSavedQuery $saved_query) {} + + protected function getURI($path) { + $account = $this->getAccount(); + if ($account) { + return '/phortune/'.$account->getID().'/charge/'; + } else { + return '/phortune/charge/'.$path; + } + } + + public function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Charges'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function getRequiredHandlePHIDsForResultList( + array $charges, + PhabricatorSavedQuery $query) { + + $phids = array(); + foreach ($charges as $charge) { + $phids[] = $charge->getProviderPHID(); + $phids[] = $charge->getCartPHID(); + $phids[] = $charge->getMerchantPHID(); + $phids[] = $charge->getPaymentMethodPHID(); + } + + return $phids; + } + + protected function renderResultList( + array $charges, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($charges, 'PhortuneCharge'); + + $viewer = $this->requireViewer(); + + $table = id(new PhortuneChargeTableView()) + ->setUser($viewer) + ->setCharges($charges) + ->setHandles($handles); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Charges')) + ->appendChild($table); + } +} diff --git a/src/applications/phortune/query/PhortuneMerchantQuery.php b/src/applications/phortune/query/PhortuneMerchantQuery.php index a246f43a31..1632797421 100644 --- a/src/applications/phortune/query/PhortuneMerchantQuery.php +++ b/src/applications/phortune/query/PhortuneMerchantQuery.php @@ -5,6 +5,7 @@ final class PhortuneMerchantQuery private $ids; private $phids; + private $memberPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -16,14 +17,20 @@ final class PhortuneMerchantQuery return $this; } + public function withMemberPHIDs(array $member_phids) { + $this->memberPHIDs = $member_phids; + return $this; + } + protected function loadPage() { $table = new PhortuneMerchant(); $conn = $table->establishConnection('r'); $rows = queryfx_all( $conn, - 'SELECT * FROM %T %Q %Q %Q', + 'SELECT m.* FROM %T m %Q %Q %Q %Q', $table->getTableName(), + $this->buildJoinClause($conn), $this->buildWhereClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); @@ -31,6 +38,21 @@ final class PhortuneMerchantQuery return $table->loadAllFromArray($rows); } + protected function willFilterPage(array $merchants) { + $query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($merchants, 'getPHID')) + ->withEdgeTypes(array(PhortuneMerchantHasMemberEdgeType::EDGECONST)); + $query->execute(); + + foreach ($merchants as $merchant) { + $member_phids = $query->getDestinationPHIDs(array($merchant->getPHID())); + $member_phids = array_reverse($member_phids); + $merchant->attachMemberPHIDs($member_phids); + } + + return $merchants; + } + private function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); @@ -48,11 +70,32 @@ final class PhortuneMerchantQuery $this->phids); } + if ($this->memberPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'e.dst IN (%Ls)', + $this->memberPHIDs); + } + $where[] = $this->buildPagingClause($conn); return $this->formatWhereClause($where); } + private function buildJoinClause(AphrontDatabaseConnection $conn) { + $joins = array(); + + if ($this->memberPHIDs !== null) { + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T e ON m.phid = e.src AND e.type = %d', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + PhortuneMerchantHasMemberEdgeType::EDGECONST); + } + + return implode(' ', $joins); + } + public function getQueryApplicationClass() { return 'PhabricatorPhortuneApplication'; } diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php index e0b1990711..4edd523a5e 100644 --- a/src/applications/phortune/storage/PhortuneCart.php +++ b/src/applications/phortune/storage/PhortuneCart.php @@ -17,6 +17,7 @@ final class PhortuneCart extends PhortuneDAO protected $cartClass; protected $status; protected $metadata = array(); + protected $mailKey; private $account = self::ATTACHABLE; private $purchases = self::ATTACHABLE; @@ -70,7 +71,26 @@ final class PhortuneCart extends PhortuneDAO } public function activateCart() { - $this->setStatus(self::STATUS_READY)->save(); + $this->openTransaction(); + $this->beginReadLocking(); + + $copy = clone $this; + $copy->reload(); + + if ($copy->getStatus() !== self::STATUS_BUILDING) { + throw new Exception( + pht( + 'Cart has wrong status ("%s") to call willApplyCharge().', + $copy->getStatus())); + } + + $this->setStatus(self::STATUS_READY)->save(); + + $this->endReadLocking(); + $this->saveTransaction(); + + $this->recordCartTransaction(PhortuneCartTransaction::TYPE_CREATED); + return $this; } @@ -140,6 +160,8 @@ final class PhortuneCart extends PhortuneDAO $this->endReadLocking(); $this->saveTransaction(); + + $this->recordCartTransaction(PhortuneCartTransaction::TYPE_HOLD); } public function didApplyCharge(PhortuneCharge $charge) { @@ -199,7 +221,7 @@ final class PhortuneCart extends PhortuneDAO $this->endReadLocking(); $this->saveTransaction(); - // TODO: Notify merchant to review order. + $this->recordCartTransaction(PhortuneCartTransaction::TYPE_REVIEW); return $this; } @@ -228,6 +250,8 @@ final class PhortuneCart extends PhortuneDAO $this->endReadLocking(); $this->saveTransaction(); + $this->recordCartTransaction(PhortuneCartTransaction::TYPE_PURCHASED); + return $this; } @@ -384,6 +408,30 @@ final class PhortuneCart extends PhortuneDAO $this->saveTransaction(); } + private function recordCartTransaction($type) { + $omnipotent_user = PhabricatorUser::getOmnipotentUser(); + $phortune_phid = id(new PhabricatorPhortuneApplication())->getPHID(); + + $xactions = array(); + + $xactions[] = id(new PhortuneCartTransaction()) + ->setTransactionType($type) + ->setNewValue(true); + + $content_source = PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_PHORTUNE, + array()); + + $editor = id(new PhortuneCartEditor()) + ->setActor($omnipotent_user) + ->setActingAsPHID($phortune_phid) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($this, $xactions); + } + public function getName() { return $this->getImplementation()->getName($this); } @@ -467,6 +515,7 @@ final class PhortuneCart extends PhortuneDAO self::CONFIG_COLUMN_SCHEMA => array( 'status' => 'text32', 'cartClass' => 'text128', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_account' => array( @@ -484,6 +533,13 @@ final class PhortuneCart extends PhortuneDAO PhortuneCartPHIDType::TYPECONST); } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + public function attachPurchases(array $purchases) { assert_instances_of($purchases, 'PhortunePurchase'); $this->purchases = $purchases; diff --git a/src/applications/phortune/storage/PhortuneCartTransaction.php b/src/applications/phortune/storage/PhortuneCartTransaction.php new file mode 100644 index 0000000000..5638031118 --- /dev/null +++ b/src/applications/phortune/storage/PhortuneCartTransaction.php @@ -0,0 +1,57 @@ +getTransactionType()) { + case self::TYPE_CREATED: + return true; + } + + return parent::shouldHideForMail($xactions); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_CREATED: + return pht('This order was created.'); + case self::TYPE_HOLD: + return pht('This order was put on hold until payment clears.'); + case self::TYPE_REVIEW: + return pht( + 'This order was flagged for manual processing by the merchant.'); + case self::TYPE_CANCEL: + return pht('This order was cancelled.'); + case self::TYPE_REFUND: + return pht('This order was refunded.'); + case self::TYPE_PURCHASED: + return pht('Payment for this order was completed.'); + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/phortune/storage/PhortuneMerchant.php b/src/applications/phortune/storage/PhortuneMerchant.php index 8778bcd084..a54fd4dcf0 100644 --- a/src/applications/phortune/storage/PhortuneMerchant.php +++ b/src/applications/phortune/storage/PhortuneMerchant.php @@ -5,13 +5,14 @@ final class PhortuneMerchant extends PhortuneDAO protected $name; protected $viewPolicy; - protected $editPolicy; protected $description; + private $memberPHIDs = self::ATTACHABLE; + public static function initializeNewMerchant(PhabricatorUser $actor) { return id(new PhortuneMerchant()) ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) - ->setEditPolicy($actor->getPHID()); + ->attachMemberPHIDs(array()); } public function getConfiguration() { @@ -29,6 +30,15 @@ final class PhortuneMerchant extends PhortuneDAO PhortuneMerchantPHIDType::TYPECONST); } + public function getMemberPHIDs() { + return $this->assertAttached($this->memberPHIDs); + } + + public function attachMemberPHIDs(array $member_phids) { + $this->memberPHIDs = $member_phids; + return $this; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -45,16 +55,21 @@ final class PhortuneMerchant extends PhortuneDAO case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: - return $this->getEditPolicy(); + return PhabricatorPolicies::POLICY_NOONE; } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + $members = array_fuse($this->getMemberPHIDs()); + if (isset($members[$viewer->getPHID()])) { + return true; + } + return false; } public function describeAutomaticCapability($capability) { - return null; + return pht("A merchant's members an always view and edit it."); } } diff --git a/src/applications/phortune/view/PhortuneChargeTableView.php b/src/applications/phortune/view/PhortuneChargeTableView.php new file mode 100644 index 0000000000..4e82404cc6 --- /dev/null +++ b/src/applications/phortune/view/PhortuneChargeTableView.php @@ -0,0 +1,89 @@ +showOrder = $show_order; + return $this; + } + + public function getShowOrder() { + return $this->showOrder; + } + + public function setHandles(array $handles) { + $this->handles = $handles; + return $this; + } + + public function getHandles() { + return $this->handles; + } + + public function setCharges(array $charges) { + $this->charges = $charges; + return $this; + } + + public function getCharges() { + return $this->charges; + } + + public function render() { + $charges = $this->getCharges(); + $handles = $this->getHandles(); + $viewer = $this->getUser(); + + $rows = array(); + foreach ($charges as $charge) { + $rows[] = array( + $charge->getID(), + $handles[$charge->getCartPHID()]->renderLink(), + $handles[$charge->getProviderPHID()]->renderLink(), + $charge->getPaymentMethodPHID() + ? $handles[$charge->getPaymentMethodPHID()]->renderLink() + : null, + $handles[$charge->getMerchantPHID()]->renderLink(), + $charge->getAmountAsCurrency()->formatForDisplay(), + $charge->getStatusForDisplay(), + phabricator_datetime($charge->getDateCreated(), $viewer), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('ID'), + pht('Cart'), + pht('Provider'), + pht('Method'), + pht('Merchant'), + pht('Amount'), + pht('Status'), + pht('Created'), + )) + ->setColumnClasses( + array( + '', + '', + '', + '', + '', + 'wide right', + '', + '', + )) + ->setColumnVisibility( + array( + true, + $this->getShowOrder(), + )); + + return $table; + } + +} diff --git a/src/applications/phortune/view/PhortuneOrderTableView.php b/src/applications/phortune/view/PhortuneOrderTableView.php new file mode 100644 index 0000000000..9691d1fb0b --- /dev/null +++ b/src/applications/phortune/view/PhortuneOrderTableView.php @@ -0,0 +1,100 @@ +handles = $handles; + return $this; + } + + public function getHandles() { + return $this->handles; + } + + public function setCarts(array $carts) { + $this->carts = $carts; + return $this; + } + + public function getCarts() { + return $this->carts; + } + + public function render() { + $carts = $this->getCarts(); + $handles = $this->getHandles(); + $viewer = $this->getUser(); + + $rows = array(); + $rowc = array(); + foreach ($carts as $cart) { + $cart_link = $handles[$cart->getPHID()]->renderLink(); + $purchases = $cart->getPurchases(); + + if (count($purchases) == 1) { + $purchase = head($purchases); + $purchase_name = $handles[$purchase->getPHID()]->renderLink(); + $purchases = array(); + } else { + $purchase_name = ''; + } + + $rowc[] = ''; + $rows[] = array( + $cart->getID(), + phutil_tag( + 'strong', + array(), + $cart_link), + $purchase_name, + phutil_tag( + 'strong', + array(), + $cart->getTotalPriceAsCurrency()->formatForDisplay()), + PhortuneCart::getNameForStatus($cart->getStatus()), + phabricator_datetime($cart->getDateModified(), $viewer), + ); + foreach ($purchases as $purchase) { + $id = $purchase->getID(); + + $price = $purchase->getTotalPriceAsCurrency()->formatForDisplay(); + + $rowc[] = ''; + $rows[] = array( + '', + $handles[$purchase->getPHID()]->renderLink(), + $price, + '', + '', + ); + } + } + + $table = id(new AphrontTableView($rows)) + ->setRowClasses($rowc) + ->setHeaders( + array( + pht('ID'), + pht('Order'), + pht('Purchase'), + pht('Amount'), + pht('Status'), + pht('Updated'), + )) + ->setColumnClasses( + array( + '', + '', + 'wide', + 'right', + '', + 'right', + )); + + return $table; + } + +} diff --git a/src/applications/phrequent/controller/PhrequentListController.php b/src/applications/phrequent/controller/PhrequentListController.php index fec1ccff02..af66e708b9 100644 --- a/src/applications/phrequent/controller/PhrequentListController.php +++ b/src/applications/phrequent/controller/PhrequentListController.php @@ -13,8 +13,7 @@ final class PhrequentListController extends PhrequentController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhrequentSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index b5475e7940..850e5f47fc 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -73,14 +73,8 @@ final class PhrictionEditController return new Aphront404Response(); } } - $document = new PhrictionDocument(); - $document->setSlug($slug); - - $content = new PhrictionContent(); - $content->setSlug($slug); - - $default_title = PhabricatorSlug::getDefaultTitle($slug); - $content->setTitle($default_title); + $document = PhrictionDocument::initializeNewDocument($user, $slug); + $content = $document->getContent(); } } @@ -174,13 +168,21 @@ final class PhrictionEditController } if (!count($errors)) { - $editor = id(PhrictionDocumentEditor::newForSlug($document->getSlug())) - ->setActor($user) - ->setTitle($title) - ->setContent($request->getStr('content')) - ->setDescription($notes); - $editor->save(); + $xactions = array(); + $xactions[] = id(new PhrictionTransaction()) + ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setNewValue($title); + $xactions[] = id(new PhrictionTransaction()) + ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setNewValue($request->getStr('content')); + + $editor = id(new PhrictionTransactionEditor()) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setDescription($notes) + ->applyTransactions($document, $xactions); if ($draft) { $draft->delete(); diff --git a/src/applications/phriction/controller/PhrictionListController.php b/src/applications/phriction/controller/PhrictionListController.php index b3377041bd..8946d7c1b7 100644 --- a/src/applications/phriction/controller/PhrictionListController.php +++ b/src/applications/phriction/controller/PhrictionListController.php @@ -14,8 +14,7 @@ final class PhrictionListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhrictionSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/phriction/editor/PhrictionDocumentEditor.php b/src/applications/phriction/editor/PhrictionDocumentEditor.php index e3f900844e..84b0e137ce 100644 --- a/src/applications/phriction/editor/PhrictionDocumentEditor.php +++ b/src/applications/phriction/editor/PhrictionDocumentEditor.php @@ -106,7 +106,7 @@ final class PhrictionDocumentEditor extends PhabricatorEditor { return $this->execute(PhrictionChangeType::CHANGE_DELETE, true); } - private function stub() { + public function stub() { return $this->execute(PhrictionChangeType::CHANGE_STUB, true); } diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php new file mode 100644 index 0000000000..546abc3979 --- /dev/null +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -0,0 +1,298 @@ +description = $description; + return $this; + } + + private function getDescription() { + return $this->description; + } + + private function setOldContent(PhrictionContent $content) { + $this->oldContent = $content; + return $this; + } + + private function getOldContent() { + return $this->oldContent; + } + + private function setNewContent(PhrictionContent $content) { + $this->newContent = $content; + return $this; + } + + private function getNewContent() { + return $this->newContent; + } + + public function getEditorApplicationClass() { + return 'PhabricatorPhrictionApplication'; + } + + public function getEditorObjectsDescription() { + return pht('Phriction Documents'); + } + + public function getTransactionTypes() { + $types = parent::getTransactionTypes(); + + $types[] = PhabricatorTransactions::TYPE_COMMENT; + $types[] = PhrictionTransaction::TYPE_TITLE; + $types[] = PhrictionTransaction::TYPE_CONTENT; + + /* TODO + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + */ + + return $types; + } + + protected function getCustomTransactionOldValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhrictionTransaction::TYPE_TITLE: + if ($this->getIsNewObject()) { + return null; + } + return $this->getOldContent()->getTitle(); + case PhrictionTransaction::TYPE_CONTENT: + if ($this->getIsNewObject()) { + return null; + } + return $this->getOldContent()->getContent(); + } + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhrictionTransaction::TYPE_TITLE: + case PhrictionTransaction::TYPE_CONTENT: + return $xaction->getNewValue(); + } + } + + protected function shouldApplyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhrictionTransaction::TYPE_TITLE: + case PhrictionTransaction::TYPE_CONTENT: + return true; + } + } + return parent::shouldApplyInitialEffects($object, $xactions); + } + + protected function applyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + $this->setOldContent($object->getContent()); + $this->setNewContent($this->buildNewContentTemplate($object)); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhrictionTransaction::TYPE_TITLE: + case PhrictionTransaction::TYPE_CONTENT: + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + return; + } + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhrictionTransaction::TYPE_TITLE: + $this->getNewContent()->setTitle($xaction->getNewValue()); + break; + case PhrictionTransaction::TYPE_CONTENT: + $this->getNewContent()->setContent($xaction->getNewValue()); + break; + default: + break; + } + } + + protected function applyFinalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + $save_content = false; + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhrictionTransaction::TYPE_TITLE: + case PhrictionTransaction::TYPE_CONTENT: + $save_content = true; + break; + default: + break; + } + } + + if ($save_content) { + $content = $this->getNewContent(); + $content->setDocumentID($object->getID()); + $content->save(); + + $object->setContentID($content->getID()); + $object->save(); + $object->attachContent($content); + } + + if ($this->getIsNewObject()) { + // Stub out empty parent documents if they don't exist + $ancestral_slugs = PhabricatorSlug::getAncestry($object->getSlug()); + if ($ancestral_slugs) { + $ancestors = id(new PhrictionDocument())->loadAllWhere( + 'slug IN (%Ls)', + $ancestral_slugs); + $ancestors = mpull($ancestors, null, 'getSlug'); + foreach ($ancestral_slugs as $slug) { + // We check for change type to prevent near-infinite recursion + if (!isset($ancestors[$slug]) && + $content->getChangeType() != + PhrictionChangeType::CHANGE_STUB) { + id(PhrictionDocumentEditor::newForSlug($slug)) + ->setActor($this->getActor()) + ->setTitle(PhabricatorSlug::getDefaultTitle($slug)) + ->setContent('') + ->setDescription(pht('Empty Parent Document')) + ->stub(); + } + } + } + } + return $xactions; + } + + protected function shouldSendMail( + PhabricatorLiskDAO $object, + array $xactions) { + + $xactions = mfilter($xactions, 'shouldHide', true); + return $xactions; + } + + protected function getMailSubjectPrefix() { + return '[Phriction]'; + } + + protected function getMailTo(PhabricatorLiskDAO $object) { + return array( + $object->getContent()->getAuthorPHID(), + $this->getActingAsPHID(), + ); + } + + public function getMailTagsMap() { + return array( + PhrictionTransaction::MAILTAG_TITLE => + pht("A document's title changes."), + PhrictionTransaction::MAILTAG_CONTENT => + pht("A document's content changes."), + ); + } + + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new PhrictionReplyHandler()) + ->setMailReceiver($object); + } + + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $title = $object->getContent()->getTitle(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject($title) + ->addHeader('Thread-Topic', $object->getPHID()); + } + + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $body = parent::buildMailBody($object, $xactions); + + if ($this->getIsNewObject()) { + $body->addTextSection( + pht('DOCUMENT CONTENT'), + $object->getContent()->getContent()); + } + + $body->addLinkSection( + pht('DOCUMENT DETAIL'), + PhabricatorEnv::getProductionURI( + PhrictionDocument::getSlugURI($object->getSlug()))); + + return $body; + } + + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + return $this->shouldSendMail($object, $xactions); + } + + protected function getFeedRelatedPHIDs( + PhabricatorLiskDAO $object, + array $xactions) { + + $phids = parent::getFeedRelatedPHIDs($object, $xactions); + // TODO - once the editor supports moves, we'll need to surface the + // "from document phid" to related phids. + return $phids; + } + + protected function supportsSearch() { + return true; + } + + protected function shouldApplyHeraldRules( + PhabricatorLiskDAO $object, + array $xactions) { + return false; + } + + private function buildNewContentTemplate( + PhrictionDocument $document) { + + $new_content = new PhrictionContent(); + $new_content->setSlug($document->getSlug()); + $new_content->setAuthorPHID($this->getActor()->getPHID()); + $new_content->setChangeType(PhrictionChangeType::CHANGE_EDIT); + + $new_content->setTitle($this->getOldContent()->getTitle()); + $new_content->setContent($this->getOldContent()->getContent()); + + if (strlen($this->getDescription())) { + $new_content->setDescription($this->getDescription()); + } + $new_content->setVersion($this->getOldContent()->getVersion() + 1); + + return $new_content; + } + +} diff --git a/src/applications/phriction/mail/PhrictionReplyHandler.php b/src/applications/phriction/mail/PhrictionReplyHandler.php new file mode 100644 index 0000000000..1f6564dbd7 --- /dev/null +++ b/src/applications/phriction/mail/PhrictionReplyHandler.php @@ -0,0 +1,41 @@ +getDefaultPrivateReplyHandlerEmailAddress( + $handle, + PhrictionDocumentPHIDType::TYPECONST); + } + + public function getPublicReplyHandlerEmailAddress() { + return $this->getDefaultPublicReplyHandlerEmailAddress( + PhrictionDocumentPHIDType::TYPECONST); + } + + public function getReplyHandlerDomain() { + return PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain'); + } + + public function getReplyHandlerInstructions() { + if ($this->supportsReplies()) { + // TODO: Implement. + return null; + } else { + return null; + } + } + + protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { + // TODO: Implement. + return null; + } + +} diff --git a/src/applications/phriction/query/PhrictionTransactionQuery.php b/src/applications/phriction/query/PhrictionTransactionQuery.php new file mode 100644 index 0000000000..c43006364b --- /dev/null +++ b/src/applications/phriction/query/PhrictionTransactionQuery.php @@ -0,0 +1,10 @@ + 'uint32', 'contentID' => 'id?', 'status' => 'uint32', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -53,6 +55,27 @@ final class PhrictionDocument extends PhrictionDAO PhrictionDocumentPHIDType::TYPECONST); } + public static function initializeNewDocument(PhabricatorUser $actor, $slug) { + $document = new PhrictionDocument(); + $document->setSlug($slug); + + $content = new PhrictionContent(); + $content->setSlug($slug); + + $default_title = PhabricatorSlug::getDefaultTitle($slug); + $content->setTitle($default_title); + $document->attachContent($content); + + return $document; + } + + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + public static function getSlugURI($slug, $type = 'document') { static $types = array( 'document' => '/w/', diff --git a/src/applications/phriction/storage/PhrictionTransaction.php b/src/applications/phriction/storage/PhrictionTransaction.php new file mode 100644 index 0000000000..afbd1e2f98 --- /dev/null +++ b/src/applications/phriction/storage/PhrictionTransaction.php @@ -0,0 +1,186 @@ +getTransactionType()) { + case self::TYPE_CONTENT: + $blocks[] = $this->getNewValue(); + break; + } + + return $blocks; + } + + public function shouldHide() { + switch ($this->getTransactionType()) { + case self::TYPE_CONTENT: + if ($this->getOldValue() === null) { + return true; + } else { + return false; + } + break; + } + + return parent::shouldHide(); + } + + public function getActionStrength() { + switch ($this->getTransactionType()) { + case self::TYPE_TITLE: + return 1.4; + case self::TYPE_CONTENT: + return 1.3; + } + + return parent::getActionStrength(); + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_TITLE: + if ($old === null) { + return pht('Created'); + } + + return pht('Retitled'); + + case self::TYPE_CONTENT: + return pht('Edited'); + + } + + return parent::getActionName(); + } + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_TITLE: + case self::TYPE_CONTENT: + return 'fa-pencil'; + } + + return parent::getIcon(); + } + + + + public function getTitle() { + $author_phid = $this->getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_TITLE: + if ($old === null) { + return pht( + '%s created this document.', + $this->renderHandleLink($author_phid)); + } + return pht( + '%s changed the title from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + + case self::TYPE_CONTENT: + return pht( + '%s edited the document content.', + $this->renderHandleLink($author_phid)); + + } + + return parent::getTitle(); + } + + public function getTitleForFeed(PhabricatorFeedStory $story) { + $author_phid = $this->getAuthorPHID(); + $object_phid = $this->getObjectPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_TITLE: + if ($old === null) { + return pht( + '%s created %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } + + return pht( + '%s renamed %s from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + $old, + $new); + + case self::TYPE_CONTENT: + return pht( + '%s edited the content of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + + } + return parent::getTitleForFeed($story); + } + + public function hasChangeDetails() { + switch ($this->getTransactionType()) { + case self::TYPE_CONTENT: + return true; + } + return parent::hasChangeDetails(); + } + + public function renderChangeDetails(PhabricatorUser $viewer) { + return $this->renderTextCorpusChangeDetails( + $viewer, + $this->getOldValue(), + $this->getNewValue()); + } + + public function getMailTags() { + $tags = array(); + switch ($this->getTransactionType()) { + case self::TYPE_TITLE: + $tags[] = self::MAILTAG_TITLE; + break; + case self::TYPE_CONTENT: + $tags[] = self::MAILTAG_CONTENT; + break; + } + return $tags; + } + +} diff --git a/src/applications/phriction/storage/PhrictionTransactionComment.php b/src/applications/phriction/storage/PhrictionTransactionComment.php new file mode 100644 index 0000000000..6f0aee04c5 --- /dev/null +++ b/src/applications/phriction/storage/PhrictionTransactionComment.php @@ -0,0 +1,10 @@ +getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PonderQuestionSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php index 9c2792efbf..69ee10f270 100644 --- a/src/applications/ponder/editor/PonderAnswerEditor.php +++ b/src/applications/ponder/editor/PonderAnswerEditor.php @@ -98,7 +98,7 @@ final class PonderAnswerEditor extends PonderEditor { } } - $body->addTextSection( + $body->addLinkSection( pht('ANSWER DETAIL'), PhabricatorEnv::getProductionURI($object->getURI())); diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index f7030dedce..5505e751de 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -243,7 +243,7 @@ final class PonderQuestionEditor } } - $body->addTextSection( + $body->addLinkSection( $header, PhabricatorEnv::getProductionURI($uri)); diff --git a/src/applications/project/config/PhabricatorProjectConfigOptions.php b/src/applications/project/config/PhabricatorProjectConfigOptions.php index 149ba13dc0..5ee179a1d9 100644 --- a/src/applications/project/config/PhabricatorProjectConfigOptions.php +++ b/src/applications/project/config/PhabricatorProjectConfigOptions.php @@ -32,7 +32,7 @@ final class PhabricatorProjectConfigOptions 'Array of custom fields for Projects.')) ->addExample( '{"mycompany:motto": {"name": "Project Motto", '. - '"type": "string"}}', + '"type": "text"}}', pht('Valid Setting')), $this->newOption('projects.fields', $custom_field_type, $default_fields) ->setCustomData(id(new PhabricatorProject())->getCustomFieldBaseClass()) diff --git a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php b/src/applications/project/controller/PhabricatorProjectEditDetailsController.php index ebba9eaa9a..a496f25e3c 100644 --- a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php +++ b/src/applications/project/controller/PhabricatorProjectEditDetailsController.php @@ -46,9 +46,6 @@ final class PhabricatorProjectEditDetailsController ->setViewer($viewer) ->readFieldsFromStorage($project); - $view_uri = $this->getApplicationURI('view/'.$project->getID().'/'); - $edit_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); - $e_name = true; $e_slugs = false; $e_edit = null; @@ -151,9 +148,11 @@ final class PhabricatorProjectEditDetailsController } if ($is_new) { - $redirect_uri = $view_uri; + $redirect_uri = + $this->getApplicationURI('view/'.$project->getID().'/'); } else { - $redirect_uri = $edit_uri; + $redirect_uri = + $this->getApplicationURI('edit/'.$project->getID().'/'); } return id(new AphrontRedirectResponse())->setURI($redirect_uri); @@ -288,7 +287,7 @@ final class PhabricatorProjectEditDetailsController $form->appendChild( id(new AphrontFormSubmitControl()) - ->addCancelButton($edit_uri) + ->addCancelButton($this->getApplicationURI()) ->setValue(pht('Save'))); $form_box = id(new PHUIObjectBoxView()) @@ -301,8 +300,10 @@ final class PhabricatorProjectEditDetailsController $crumbs->addTextCrumb($title); } else { $crumbs - ->addTextCrumb($project->getName(), $view_uri) - ->addTextCrumb(pht('Edit'), $edit_uri) + ->addTextCrumb($project->getName(), + $this->getApplicationURI('view/'.$project->getID().'/')) + ->addTextCrumb(pht('Edit'), + $this->getApplicationURI('edit/'.$project->getID().'/')) ->addTextCrumb(pht('Details')); } diff --git a/src/applications/project/controller/PhabricatorProjectListController.php b/src/applications/project/controller/PhabricatorProjectListController.php index e07820f599..ad00fbc08f 100644 --- a/src/applications/project/controller/PhabricatorProjectListController.php +++ b/src/applications/project/controller/PhabricatorProjectListController.php @@ -14,8 +14,7 @@ final class PhabricatorProjectListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorProjectSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index edd175cba1..9feb64ebbc 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -66,7 +66,7 @@ final class PhabricatorProjectProfileController ->setIconFont('fa-columns'); $board_btn = id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Workboards')) + ->setText(pht('Workboard')) ->setHref($this->getApplicationURI("board/{$id}/")) ->setIcon($icon); diff --git a/src/applications/project/customfield/PhabricatorProjectDescriptionField.php b/src/applications/project/customfield/PhabricatorProjectDescriptionField.php index 9f178ad907..7b5fdf4357 100644 --- a/src/applications/project/customfield/PhabricatorProjectDescriptionField.php +++ b/src/applications/project/customfield/PhabricatorProjectDescriptionField.php @@ -31,6 +31,7 @@ final class PhabricatorProjectDescriptionField 'name' => pht('Description'), 'type' => 'remarkup', 'description' => pht('Short project description.'), + 'fulltext' => PhabricatorSearchField::FIELD_BODY, ), )); } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 4592560a00..d055e532fc 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -222,7 +222,7 @@ final class PhabricatorProjectSearchEngine array( 'href' => $workboards_uri, ), - pht('Workboards')); + pht('Workboard')); $members_url = phutil_tag( 'a', diff --git a/src/applications/project/remarkup/ProjectRemarkupRule.php b/src/applications/project/remarkup/ProjectRemarkupRule.php index 3657fb087b..2f93a88e95 100644 --- a/src/applications/project/remarkup/ProjectRemarkupRule.php +++ b/src/applications/project/remarkup/ProjectRemarkupRule.php @@ -30,7 +30,7 @@ final class ProjectRemarkupRule extends PhabricatorObjectRemarkupRule { // In other contexts, the PhabricatorProjectProjectPHIDType pattern is // controlling and these names should parse correctly. - return '[^\s.!,:;{}#]*[^\s\d!,:;{}#]+(?:[^\s.!,:;{}#][^\s!,:;{}#]*)*'; + return '[^\s.\d!,:;{}#]+(?:[^\s!,:;{}#][^\s.!,:;{}#]+)*'; } protected function loadObjects(array $ids) { diff --git a/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php b/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php new file mode 100644 index 0000000000..4e72514b26 --- /dev/null +++ b/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php @@ -0,0 +1,57 @@ + array( + 'embed' => array(), + 'ref' => array( + array( + 'offset' => 8, + 'id' => 'ducks', + ), + ), + ), + 'We should make a post on #blog.example.com tomorrow.' => array( + 'embed' => array(), + 'ref' => array( + array( + 'offset' => 26, + 'id' => 'blog.example.com', + ), + ), + ), + 'We should make a post on #blog.example.com.' => array( + 'embed' => array(), + 'ref' => array( + array( + 'offset' => 26, + 'id' => 'blog.example.com', + ), + ), + ), + '#123' => array( + 'embed' => array(), + 'ref' => array(), + ), + '#security#123' => array( + 'embed' => array(), + 'ref' => array( + array( + 'offset' => 1, + 'id' => 'security', + 'tail' => '123', + ), + ), + ), + ); + + foreach ($cases as $input => $expect) { + $rule = new ProjectRemarkupRule(); + $matches = $rule->extractReferences($input); + $this->assertEqual($expect, $matches, $input); + } + } + +} diff --git a/src/applications/releeph/controller/branch/ReleephBranchViewController.php b/src/applications/releeph/controller/branch/ReleephBranchViewController.php index f282bc9883..9612c4f547 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchViewController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchViewController.php @@ -28,7 +28,7 @@ final class ReleephBranchViewController extends ReleephBranchController } $this->setBranch($branch); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setPreface($this->renderPreface()) ->setQueryKey($this->queryKey) ->setSearchEngine($this->getSearchEngine()) diff --git a/src/applications/releeph/controller/product/ReleephProductListController.php b/src/applications/releeph/controller/product/ReleephProductListController.php index 1149a88724..0c5e52f3e9 100644 --- a/src/applications/releeph/controller/product/ReleephProductListController.php +++ b/src/applications/releeph/controller/product/ReleephProductListController.php @@ -13,8 +13,7 @@ final class ReleephProductListController extends ReleephController { } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new ReleephProductSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/releeph/controller/product/ReleephProductViewController.php b/src/applications/releeph/controller/product/ReleephProductViewController.php index 2d72268a71..bb2f188a5a 100644 --- a/src/applications/releeph/controller/product/ReleephProductViewController.php +++ b/src/applications/releeph/controller/product/ReleephProductViewController.php @@ -28,7 +28,7 @@ final class ReleephProductViewController extends ReleephProductController } $this->setProduct($product); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setPreface($this->renderPreface()) ->setSearchEngine( diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 159177c8e9..782bc018d3 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -40,8 +40,11 @@ final class PhabricatorRepositoryCommit return $this; } - public function getRepository() { - return $this->assertAttached($this->repository); + public function getRepository($assert_attached = true) { + if ($assert_attached) { + return $this->assertAttached($this->repository); + } + return $this->repository; } public function isPartiallyImported($mask) { @@ -59,6 +62,7 @@ final class PhabricatorRepositoryCommit $this->getTableName(), $flag, $this->getID()); + $this->setImportStatus($this->getImportStatus() | $flag); return $this; } diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php index 9ad358ee89..c7af5215dd 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php @@ -3,7 +3,6 @@ final class PhabricatorRepositoryCommitHeraldWorker extends PhabricatorRepositoryCommitParserWorker { - const MAX_FILES_SHOWN_IN_EMAIL = 1000; public function getRequiredLeaseTime() { // Herald rules may take a long time to process. @@ -14,34 +13,14 @@ final class PhabricatorRepositoryCommitHeraldWorker PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { - $result = $this->applyHeraldRules($repository, $commit); - - $commit->writeImportStatusFlag( - PhabricatorRepositoryCommit::IMPORTED_HERALD); - - return $result; - } - - private function applyHeraldRules( - PhabricatorRepository $repository, - PhabricatorRepositoryCommit $commit) { - - $commit->attachRepository($repository); - - // Don't take any actions on an importing repository. Principally, this - // avoids generating thousands of audits or emails when you import an - // established repository on an existing install. - if ($repository->isImporting()) { - return; - } - - if ($repository->getDetail('herald-disabled')) { - return; - } - - $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( - 'commitID = %d', - $commit->getID()); + // Reload the commit to pull commit data and audit requests. + $commit = id(new DiffusionCommitQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIDs(array($commit->getID())) + ->needCommitData(true) + ->needAuditRequests(true) + ->executeOne(); + $data = $commit->getCommitData(); if (!$data) { throw new PhabricatorWorkerPermanentFailureException( @@ -50,402 +29,43 @@ final class PhabricatorRepositoryCommitHeraldWorker 'or no longer exists.')); } - $adapter = id(new HeraldCommitAdapter()) - ->setCommit($commit); + $commit->attachRepository($repository); - $rules = id(new HeraldRuleQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withContentTypes(array($adapter->getAdapterContentType())) - ->withDisabled(false) - ->needConditionsAndActions(true) - ->needAppliedToPHIDs(array($adapter->getPHID())) - ->needValidateAuthors(true) - ->execute(); - - $engine = new HeraldEngine(); - - $effects = $engine->applyRules($rules, $adapter); - $engine->applyEffects($effects, $adapter, $rules); - $xscript = $engine->getTranscript(); - - $audit_phids = $adapter->getAuditMap(); - $cc_phids = $adapter->getAddCCMap(); - if ($audit_phids || $cc_phids) { - $this->createAudits($commit, $audit_phids, $cc_phids, $rules); - } - - HarbormasterBuildable::applyBuildPlans( - $commit->getPHID(), - $repository->getPHID(), - $adapter->getBuildPlans()); - - $explicit_auditors = $this->createAuditsFromCommitMessage($commit, $data); - - $this->publishFeedStory($repository, $commit, $data); - - $herald_targets = $adapter->getEmailPHIDs(); - - $email_phids = array_unique( - array_merge( - $explicit_auditors, - array_keys($cc_phids), - $herald_targets)); - if (!$email_phids) { - return; - } - - $revision = $adapter->loadDifferentialRevision(); - if ($revision) { - $name = $revision->getTitle(); - } else { - $name = $data->getSummary(); - } - - $author_phid = $data->getCommitDetail('authorPHID'); - $reviewer_phid = $data->getCommitDetail('reviewerPHID'); - - $phids = array_filter( - array( - $author_phid, - $reviewer_phid, - $commit->getPHID(), - )); - - $handles = id(new PhabricatorHandleQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($phids) - ->execute(); - - $commit_handle = $handles[$commit->getPHID()]; - $commit_name = $commit_handle->getName(); - - if ($author_phid) { - $author_name = $handles[$author_phid]->getName(); - } else { - $author_name = $data->getAuthorName(); - } - - if ($reviewer_phid) { - $reviewer_name = $handles[$reviewer_phid]->getName(); - } else { - $reviewer_name = null; - } - - $who = implode(', ', array_filter(array($author_name, $reviewer_name))); - - $description = $data->getCommitMessage(); - - $commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI()); - $differential = $revision - ? PhabricatorEnv::getProductionURI('/D'.$revision->getID()) - : 'No revision.'; - - $limit = self::MAX_FILES_SHOWN_IN_EMAIL; - $files = $adapter->loadAffectedPaths(); - sort($files); - if (count($files) > $limit) { - array_splice($files, $limit); - $files[] = '(This commit affected more than '.$limit.' files. '. - 'Only '.$limit.' are shown here and additional ones are truncated.)'; - } - $files = implode("\n", $files); - - $xscript_id = $xscript->getID(); - - $why_uri = '/herald/transcript/'.$xscript_id.'/'; - - $reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit( - $commit); - - $template = new PhabricatorMetaMTAMail(); - - $inline_patch_text = $this->buildPatch($template, $repository, $commit); - - $body = new PhabricatorMetaMTAMailBody(); - $body->addRawSection($description); - $body->addTextSection(pht('DETAILS'), $commit_uri); - - // TODO: This should be integrated properly once we move to - // ApplicationTransactions. - $field_list = PhabricatorCustomField::getObjectFields( - $commit, - PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS); - $field_list - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->readFieldsFromStorage($commit); - foreach ($field_list->getFields() as $field) { - try { - $field->buildApplicationTransactionMailBody( - new DifferentialTransaction(), // Bogus object to satisfy typehint. - $body); - } catch (Exception $ex) { - // Log the exception and continue. - phlog($ex); - } - } - - $body->addTextSection(pht('DIFFERENTIAL REVISION'), $differential); - $body->addTextSection(pht('AFFECTED FILES'), $files); - $body->addReplySection($reply_handler->getReplyHandlerInstructions()); - $body->addHeraldSection($why_uri); - $body->addRawSection($inline_patch_text); - $body = $body->render(); - - $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix'); - - $threading = PhabricatorAuditCommentEditor::getMailThreading( - $repository, - $commit); - list($thread_id, $thread_topic) = $threading; - - $template->setRelatedPHID($commit->getPHID()); - $template->setSubject("{$commit_name}: {$name}"); - $template->setSubjectPrefix($prefix); - $template->setVarySubjectPrefix('[Commit]'); - $template->setBody($body); - $template->setThreadID($thread_id, $is_new = true); - $template->addHeader('Thread-Topic', $thread_topic); - $template->setIsBulk(true); - - $template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader()); - if ($author_phid) { - $template->setFrom($author_phid); - } - - // TODO: We should verify that each recipient can actually see the - // commit before sending them email (T603). - - $mails = $reply_handler->multiplexMail( - $template, - id(new PhabricatorHandleQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($email_phids) - ->execute(), + $content_source = PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_DAEMON, array()); - foreach ($mails as $mail) { - $mail->saveAndSend(); - } - } - - private function createAudits( - PhabricatorRepositoryCommit $commit, - array $map, - array $ccmap, - array $rules) { - assert_instances_of($rules, 'HeraldRule'); - - $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere( - 'commitPHID = %s', - $commit->getPHID()); - $requests = mpull($requests, null, 'getAuditorPHID'); - - $rules = mpull($rules, null, 'getID'); - - $maps = array( - PhabricatorAuditStatusConstants::AUDIT_REQUIRED => $map, - ); - - foreach ($maps as $status => $map) { - foreach ($map as $phid => $rule_ids) { - $request = idx($requests, $phid); - if ($request) { - continue; - } - $reasons = array(); - foreach ($rule_ids as $id) { - $rule_name = '?'; - if ($rules[$id]) { - $rule_name = $rules[$id]->getName(); - } - if ($status == PhabricatorAuditStatusConstants::AUDIT_REQUIRED) { - $reasons[] = pht( - '%s Triggered Audit', - "H{$id} {$rule_name}"); - } else { - $reasons[] = pht( - '%s Triggered CC', - "H{$id} {$rule_name}"); - } - } - - $request = new PhabricatorRepositoryAuditRequest(); - $request->setCommitPHID($commit->getPHID()); - $request->setAuditorPHID($phid); - $request->setAuditStatus($status); - $request->setAuditReasons($reasons); - $request->save(); - } - } - - $commit->updateAuditStatus($requests); - $commit->save(); - - if ($ccmap) { - id(new PhabricatorSubscriptionsEditor()) - ->setActor(PhabricatorUser::getOmnipotentUser()) - ->setObject($commit) - ->subscribeExplicit(array_keys($ccmap)) - ->save(); - } - } - - - /** - * Find audit requests in the "Auditors" field if it is present and trigger - * explicit audit requests. - */ - private function createAuditsFromCommitMessage( - PhabricatorRepositoryCommit $commit, - PhabricatorRepositoryCommitData $data) { - - $message = $data->getCommitMessage(); - - $matches = null; - if (!preg_match('/^Auditors:\s*(.*)$/im', $message, $matches)) { - return array(); - } - - $phids = id(new PhabricatorObjectListQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->setAllowPartialResults(true) - ->setAllowedTypes( - array( - PhabricatorPeopleUserPHIDType::TYPECONST, - PhabricatorProjectProjectPHIDType::TYPECONST, - )) - ->setObjectList($matches[1]) - ->execute(); - - if (!$phids) { - return array(); - } - - $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere( - 'commitPHID = %s', - $commit->getPHID()); - $requests = mpull($requests, null, 'getAuditorPHID'); - - foreach ($phids as $phid) { - if (isset($requests[$phid])) { - continue; - } - - $request = new PhabricatorRepositoryAuditRequest(); - $request->setCommitPHID($commit->getPHID()); - $request->setAuditorPHID($phid); - $request->setAuditStatus( - PhabricatorAuditStatusConstants::AUDIT_REQUESTED); - $request->setAuditReasons( - array( - 'Requested by Author', - )); - $request->save(); - - $requests[$phid] = $request; - } - - $commit->updateAuditStatus($requests); - $commit->save(); - - return $phids; - } - - private function publishFeedStory( - PhabricatorRepository $repository, - PhabricatorRepositoryCommit $commit, - PhabricatorRepositoryCommitData $data) { - - if (time() > $commit->getEpoch() + (24 * 60 * 60)) { - // Don't publish stories that are more than 24 hours old, to avoid - // ridiculous levels of feed spam if a repository is imported without - // disabling feed publishing. - return; - } - - $author_phid = $commit->getAuthorPHID(); $committer_phid = $data->getCommitDetail('committerPHID'); + $author_phid = $data->getCommitDetail('authorPHID'); + $acting_as_phid = nonempty( + $committer_phid, + $author_phid, + id(new PhabricatorDiffusionApplication())->getPHID()); - $publisher = new PhabricatorFeedStoryPublisher(); - $publisher->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_COMMIT); - $publisher->setStoryData( - array( - 'commitPHID' => $commit->getPHID(), + $editor = id(new PhabricatorAuditEditor()) + ->setActor(PhabricatorUser::getOmnipotentUser()) + ->setActingAsPHID($acting_as_phid) + ->setContentSource($content_source); + + $xactions = array(); + $xactions[] = id(new PhabricatorAuditTransaction()) + ->setTransactionType(PhabricatorAuditTransaction::TYPE_COMMIT) + ->setDateCreated($commit->getEpoch()) + ->setNewValue(array( + 'description' => $data->getCommitMessage(), 'summary' => $data->getSummary(), 'authorName' => $data->getAuthorName(), - 'authorPHID' => $author_phid, + 'authorPHID' => $commit->getAuthorPHID(), 'committerName' => $data->getCommitDetail('committer'), - 'committerPHID' => $committer_phid, + 'committerPHID' => $data->getCommitDetail('committerPHID'), )); - $publisher->setStoryTime($commit->getEpoch()); - $publisher->setRelatedPHIDs( - array_filter( - array( - $author_phid, - $committer_phid, - ))); - if ($author_phid) { - $publisher->setStoryAuthorPHID($author_phid); - } - $publisher->publish(); - } - - private function buildPatch( - PhabricatorMetaMTAMail $template, - PhabricatorRepository $repository, - PhabricatorRepositoryCommit $commit) { - - $attach_key = 'metamta.diffusion.attach-patches'; - $inline_key = 'metamta.diffusion.inline-patches'; - - $attach_patches = PhabricatorEnv::getEnvConfig($attach_key); - $inline_patches = PhabricatorEnv::getEnvConfig($inline_key); - - if (!$attach_patches && !$inline_patches) { - return; - } - - $encoding = $repository->getDetail('encoding', 'UTF-8'); - - $result = null; - $patch_error = null; - try { $raw_patch = $this->loadRawPatchText($repository, $commit); - if ($attach_patches) { - $commit_name = $repository->formatCommitName( - $commit->getCommitIdentifier()); - - $template->addAttachment( - new PhabricatorMetaMTAAttachment( - $raw_patch, - $commit_name.'.patch', - 'text/x-patch; charset='.$encoding)); - } } catch (Exception $ex) { - phlog($ex); - $patch_error = 'Unable to generate: '.$ex->getMessage(); + $raw_patch = pht('Unable to generate patch: %s', $ex->getMessage()); } - - if ($patch_error) { - $result = $patch_error; - } else if ($inline_patches) { - $len = substr_count($raw_patch, "\n"); - if ($len <= $inline_patches) { - // We send email as utf8, so we need to convert the text to utf8 if - // we can. - if ($encoding) { - $raw_patch = phutil_utf8_convert($raw_patch, 'UTF-8', $encoding); - } - $result = phutil_utf8ize($raw_patch); - } - } - - if ($result) { - $result = "PATCH\n\n{$result}\n"; - } - - return $result; + $editor->setRawPatch($raw_patch); + return $editor->applyTransactions($commit, $xactions); } private function loadRawPatchText( @@ -478,9 +98,11 @@ final class PhabricatorRepositoryCommitHeraldWorker if ($byte_limit && $size > $byte_limit) { $pretty_size = phutil_format_bytes($size); $pretty_limit = phutil_format_bytes($byte_limit); - throw new Exception( - "Patch size of {$pretty_size} exceeds configured byte size limit of ". - "{$pretty_limit}."); + throw new Exception(pht( + 'Patch size of %s exceeds configured byte size limit (%s) of %s.', + $pretty_size, + $byte_key, + $pretty_limit)); } return $raw_diff; diff --git a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php index 883e547e58..23582d63eb 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php @@ -70,7 +70,7 @@ final class PhabricatorRepositoryPushMailWorker $body = new PhabricatorMetaMTAMailBody(); $body->addRawSection($overview); - $body->addTextSection(pht('DETAILS'), $details_uri); + $body->addLinkSection(pht('DETAILS'), $details_uri); if ($commit_lines) { $body->addTextSection(pht('COMMITS'), implode("\n", $commit_lines)); diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index ef25a2ef34..7c56251dea 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -17,7 +17,9 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $data = new PhabricatorRepositoryCommitData(); } $data->setCommitID($commit->getID()); - $data->setAuthorName((string)$author); + $data->setAuthorName(id(new PhutilUTF8StringTruncator()) + ->setMaximumCodepoints(255) + ->truncateString((string)$author)); $data->setCommitDetail('authorName', $ref->getAuthorName()); $data->setCommitDetail('authorEmail', $ref->getAuthorEmail()); @@ -53,11 +55,12 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $differential_app = 'PhabricatorDifferentialApplication'; $revision_id = null; + $low_level_query = null; if (PhabricatorApplication::isClassInstalled($differential_app)) { - $field_values = id(new DiffusionLowLevelCommitFieldsQuery()) + $low_level_query = id(new DiffusionLowLevelCommitFieldsQuery()) ->setRepository($repository) - ->withCommitRef($ref) - ->execute(); + ->withCommitRef($ref); + $field_values = $low_level_query->execute(); $revision_id = idx($field_values, 'revisionID'); if (!empty($field_values['reviewedByPHIDs'])) { @@ -160,9 +163,15 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $commit_close_xaction->setMetadataValue( 'authorName', $data->getAuthorName()); - $commit_close_xaction->setMetadataValue( - 'commitHashes', - $hashes); + + if ($low_level_query) { + $commit_close_xaction->setMetadataValue( + 'revisionMatchData', + $low_level_query->getRevisionMatchData()); + $data->setCommitDetail( + 'revisionMatchData', + $low_level_query->getRevisionMatchData()); + } $diff = $this->generateFinalDiff($revision, $acting_as_phid); diff --git a/src/applications/search/controller/PhabricatorSearchController.php b/src/applications/search/controller/PhabricatorSearchController.php index bad363c36d..9680e33d5e 100644 --- a/src/applications/search/controller/PhabricatorSearchController.php +++ b/src/applications/search/controller/PhabricatorSearchController.php @@ -72,7 +72,7 @@ final class PhabricatorSearchController } } - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/search/controller/PhabricatorSearchHovercardController.php b/src/applications/search/controller/PhabricatorSearchHovercardController.php index 237373c6c7..06422f9e79 100644 --- a/src/applications/search/controller/PhabricatorSearchHovercardController.php +++ b/src/applications/search/controller/PhabricatorSearchHovercardController.php @@ -3,6 +3,10 @@ final class PhabricatorSearchHovercardController extends PhabricatorSearchBaseController { + public function shouldAllowPublic() { + return true; + } + public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php index 866b25c76c..8062fc4098 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php @@ -14,8 +14,7 @@ final class PhabricatorSlowvoteListController } public function processRequest() { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) + $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorSlowvoteSearchEngine()) ->setNavigation($this->buildSideNavView()); diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php index eec7585b7e..ea4bc5d77b 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php @@ -5,6 +5,10 @@ final class PhabricatorApplicationTransactionDetailController private $phid; + public function shouldAllowPublic() { + return true; + } + public function willProcessRequest(array $data) { $this->phid = $data['phid']; } diff --git a/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php b/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php index 923ac77e24..57e65f8bbc 100644 --- a/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php +++ b/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php @@ -110,7 +110,9 @@ class PhabricatorApplicationTransactionFeedStory $xactions = array(); $xaction_phids = $this->getValue('transactionPHIDs'); foreach ($xaction_phids as $xaction_phid) { - $xactions[] = $this->getObject($xaction_phid); + $xaction = $this->getObject($xaction_phid); + $xaction->setHandles($this->getHandles()); + $xactions[] = $xaction; } $primary = $this->getPrimaryTransaction(); diff --git a/src/applications/uiexample/examples/PHUIColorPalletteExample.php b/src/applications/uiexample/examples/PHUIColorPalletteExample.php index c13e230959..08459d60b5 100644 --- a/src/applications/uiexample/examples/PHUIColorPalletteExample.php +++ b/src/applications/uiexample/examples/PHUIColorPalletteExample.php @@ -25,8 +25,10 @@ final class PHUIColorPalletteExample extends PhabricatorUIExample { 'daeaf3' => '83% Blue {$lightblue}', '3498db' => 'Sky Base {$sky}', 'ddeef9' => '83% Sky {$lightsky}', - 'c6539d' => 'Base Indigo {$indigo}', - 'f5e2ef' => '83% Indigo {$lightindigo}', + '6e5cb6' => 'Base Indigo {$indigo}', + 'eae6f7' => '83% Indigo {$lightindigo}', + 'da49be' => 'Base Pink {$pink}', + 'fbeaf8' => '83% Pink {$lightpink}', '8e44ad' => 'Base Violet {$violet}', 'ecdff1' => '83% Violet {$lightviolet}', ); diff --git a/src/docs/contributor/phabricator_code_layout.diviner b/src/docs/contributor/phabricator_code_layout.diviner index 10ce6338e8..0b021d667b 100644 --- a/src/docs/contributor/phabricator_code_layout.diviner +++ b/src/docs/contributor/phabricator_code_layout.diviner @@ -8,9 +8,6 @@ application class and subdirectory organization best practices. When a user visits a Phabricator URI, the Phabricator infrastructure parses that URI with a regular expression to determine what controller class to load. -For now, that regular expression is hard-coded inside the -@{class:AphrontDefaultApplicationConfiguration} within the ##getURIMap## -method. Use the existing entries as examples for adding your own entries. The Phabricator infrastructure knows where a given controller class lives on disk from a cache file the Arcanist phutil mapper generates. This mapping diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner index cc878ef007..ff430e4290 100644 --- a/src/docs/user/configuration/custom_fields.diviner +++ b/src/docs/user/configuration/custom_fields.diviner @@ -99,9 +99,12 @@ When defining custom fields using a configuration option like - **edit**: Show this field on the application's edit interface (this defaults to `true`). - **view**: Show this field on the application's view interface (this - defaults to `true`). + defaults to `true`). (Note: Empty fields are not shown.) - **search**: Show this field on the application's search interface, allowing users to filter objects by the field value. + - **fulltext**: Index the text in this field as part of the object's global + full-text index. This allows users to find the object by searching for + the field's contents using global search. - **caption**: A caption to display underneath the field (optional). - **required**: True if the user should be required to provide a value. - **options**: If type is set to **select**, provide options for the dropdown diff --git a/src/docs/user/userguide/remarkup.diviner b/src/docs/user/userguide/remarkup.diviner index d6e4c1163d..d128861217 100644 --- a/src/docs/user/userguide/remarkup.diviner +++ b/src/docs/user/userguide/remarkup.diviner @@ -48,7 +48,7 @@ empty lines: > Quoted Text - Use "- " or "* " for bulleted lists, and "# " for numbered lists. + Use `- ` or `* ` for bulleted lists, and `# ` for numbered lists. Use ``` or indent two spaces for code. Use %%% for a literal block. Use | ... | ... for tables. @@ -92,7 +92,7 @@ You can optionally omit the trailing `=` signs -- that is, these are the same: This produces headers like the ones in this document. Make sure you have an empty line before and after the header. -Make **lists** by beginning each item with a "-" or a "*": +Make **lists** by beginning each item with a `-` or a `*`: lang=text - milk @@ -109,9 +109,9 @@ This produces a list like this: - eggs - bread -(Note that you need to put a space after the "-" or "*".) +(Note that you need to put a space after the `-` or `*`.) -You can make numbered lists with a "#" instead of "-" or "*": +You can make numbered lists with a `#` instead of `-` or `*`: # Articuno # Zapdos @@ -184,7 +184,7 @@ You can also use three backticks to enclose the code block: ```f(x, y); g(f);``` -You can specify a language for syntax highlighting with "lang=xxx": +You can specify a language for syntax highlighting with `lang=xxx`: lang=text lang=html @@ -196,7 +196,7 @@ available (in most cases, this means you need to configure Pygments): lang=html ... -You can also use a "COUNTEREXAMPLE" header to show that a block of code is +You can also use a `COUNTEREXAMPLE` header to show that a block of code is bad and shouldn't be copied: lang=text @@ -258,7 +258,7 @@ You can use `lines=N` to limit the vertical size of a chunk of code, and

Mangostine

Melon

-You can also use "NOTE:", "WARNING:", or "IMPORTANT:" to call out an important +You can also use `NOTE:`, `WARNING:`, or `IMPORTANT:` to call out an important idea. NOTE: Best practices in proton pack operation include not crossing the streams. @@ -267,8 +267,8 @@ WARNING: Crossing the streams can result in total protonic reversal! IMPORTANT: Don't cross the streams! -In addition, you can use "(NOTE)", "(WARNING)", or "(IMPORTANT)" to get the -same effect but without "(NOTE)", "(WARNING)", or "(IMPORTANT)" appearing in +In addition, you can use `(NOTE)`, `(WARNING)`, or `(IMPORTANT)` to get the +same effect but without `(NOTE)`, `(WARNING)`, or `(IMPORTANT)` appearing in the rendered result. For example (NOTE) Dr. Egon Spengler is the best resource for additional proton pack @@ -325,7 +325,7 @@ task or revision is closed). Some types of objects support rich embedding. == Linking to Project Tags -Projects can be linked to with the use of a hashtag (#). This works by default +Projects can be linked to with the use of a hashtag `#`. This works by default using the name of the Project (lowercase, underscored). Additionally you can set multiple additional hashtags by editing the Project details. @@ -398,7 +398,7 @@ You can embed a countdown by using braces: = Quoting Text = -To quote text, preface it with an ">": +To quote text, preface it with an `>`: > This is quoted text. @@ -427,7 +427,7 @@ of a dancing banana. = Memes = You can also use image macros in the context of memes. For example, if you -have an image macro named "grumpy", you can create a meme by doing the +have an image macro named `grumpy`, you can create a meme by doing the following: {meme, src = grumpy, above = toptextgoeshere, below = bottomtextgoeshere} @@ -480,7 +480,7 @@ opponents: = Literal Blocks = -To place text in a literal block use "%%%": +To place text in a literal block use `%%%`: %%%Text that won't be processed by remarkup [[http://www.example.com | example]] diff --git a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php index d7ff2a3585..15b83928e1 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php @@ -339,8 +339,6 @@ final class PhabricatorCustomFieldList extends Phobject { } $field->updateAbstractDocument($document); } - - return $document; } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php index 332243804d..d7d76e46b4 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php @@ -425,4 +425,25 @@ abstract class PhabricatorStandardCustomField return 'std:control:'.$key; } + public function shouldAppearInGlobalSearch() { + return $this->getFieldConfigValue('fulltext', false); + } + + public function updateAbstractDocument( + PhabricatorSearchAbstractDocument $document) { + + $field_key = $this->getFieldConfigValue('fulltext'); + + // If the caller or configuration didn't specify a valid field key, + // generate one automatically from the field index. + if (!is_string($field_key) || (strlen($field_key) != 4)) { + $field_key = '!'.substr($this->getFieldIndex(), 0, 3); + } + + $field_value = $this->getFieldValue(); + if (strlen($field_value)) { + $document->addField($field_key, $field_value); + } + } + } diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php index a5766fde93..003e2fe7b2 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php +++ b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php @@ -35,7 +35,7 @@ final class PhabricatorBotObjectNameHandler extends PhabricatorBotHandler { foreach ($matches as $match) { switch ($match[1]) { case 'R2D2': - $output[$match[1]] = pht('beep hoop bop'); + $output[$match[1]] = pht('beep boop bop'); break; } } diff --git a/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php b/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php index 47e229ec62..659a83821e 100644 --- a/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php +++ b/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php @@ -161,17 +161,17 @@ final class PhabricatorWorkerTestCase extends PhabricatorTestCase { } public function testLeasedIsHighestPriority() { - $task1 = $this->scheduleTask(array(), 2); + $task1 = $this->scheduleTask(array(), 1); $task2 = $this->scheduleTask(array(), 1); - $task3 = $this->scheduleTask(array(), 1); + $task3 = $this->scheduleTask(array(), 2); $this->expectNextLease( - $task1, + $task3, 'Tasks with a higher priority should be scheduled first.'); $this->expectNextLease( - $task2, + $task1, 'Tasks with the same priority should be FIFO.'); - $this->expectNextLease($task3); + $this->expectNextLease($task2); } private function expectNextLease($task, $message = null) { diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php index bc2dd22cc8..fff5e85fa2 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php @@ -10,6 +10,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { private $ids; private $limit; + private $skipLease; public static function getDefaultWaitBeforeRetry() { return phutil_units('5 minutes in seconds'); @@ -19,6 +20,20 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { return phutil_units('2 hours in seconds'); } + /** + * Set this flag to select tasks from the top of the queue without leasing + * them. + * + * This can be used to show which tasks are coming up next without altering + * the queue's behavior. + * + * @param bool True to skip the lease acquisition step. + */ + public function setSkipLease($skip) { + $this->skipLease = $skip; + return $this; + } + public function withIDs(array $ids) { $this->ids = $ids; return $this; @@ -51,6 +66,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { $limit = $this->limit; $leased = 0; + $task_ids = array(); foreach ($phases as $phase) { // NOTE: If we issue `UPDATE ... WHERE ... ORDER BY id ASC`, the query // goes very, very slowly. The `ORDER BY` triggers this, although we get @@ -74,17 +90,23 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { // total runtime, so keep it simple for the moment. if ($rows) { - queryfx( - $conn_w, - 'UPDATE %T task - SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + %d - %Q', - $task_table->getTableName(), - $lease_ownership_name, - self::getDefaultLeaseDuration(), - $this->buildUpdateWhereClause($conn_w, $phase, $rows)); + if ($this->skipLease) { + $leased += count($rows); + $task_ids += array_fuse(ipull($rows, 'id')); + } else { + queryfx( + $conn_w, + 'UPDATE %T task + SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + %d + %Q', + $task_table->getTableName(), + $lease_ownership_name, + self::getDefaultLeaseDuration(), + $this->buildUpdateWhereClause($conn_w, $phase, $rows)); + + $leased += $conn_w->getAffectedRows(); + } - $leased += $conn_w->getAffectedRows(); if ($leased == $limit) { break; } @@ -95,16 +117,27 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { return array(); } + if ($this->skipLease) { + $selection_condition = qsprintf( + $conn_w, + 'task.id IN (%Ld)', + $task_ids); + } else { + $selection_condition = qsprintf( + $conn_w, + 'task.leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP()', + $lease_ownership_name); + } + $data = queryfx_all( $conn_w, 'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime FROM %T task LEFT JOIN %T taskdata ON taskdata.id = task.dataID - WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP() - %Q %Q', + WHERE %Q %Q %Q', $task_table->getTableName(), $taskdata_table->getTableName(), - $lease_ownership_name, + $selection_condition, $this->buildOrderClause($conn_w, $phase), $this->buildLimitClause($conn_w, $limit)); @@ -183,7 +216,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { case self::PHASE_UNLEASED: // When selecting new tasks, we want to consume them in order of // decreasing priority (and then FIFO). - return qsprintf($conn_w, 'ORDER BY id ASC'); + return qsprintf($conn_w, 'ORDER BY priority DESC, id ASC'); case self::PHASE_EXPIRED: // When selecting failed tasks, we want to consume them in roughly // FIFO order of their failures, which is not necessarily their original diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php index fb5571dcbe..1e60bde7dc 100644 --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -95,15 +95,33 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { } public function apply($text) { - $prefix = $this->getObjectNamePrefix(); - $prefix = preg_quote($prefix, '@'); - $id = $this->getObjectIDPattern(); - $text = preg_replace_callback( - '@\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B@u', + $this->getObjectEmbedPattern(), array($this, 'markupObjectEmbed'), $text); + $text = preg_replace_callback( + $this->getObjectReferencePattern(), + array($this, 'markupObjectReference'), + $text); + + return $text; + } + + private function getObjectEmbedPattern() { + $prefix = $this->getObjectNamePrefix(); + $prefix = preg_quote($prefix); + $id = $this->getObjectIDPattern(); + + return '(\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B)u'; + } + + private function getObjectReferencePattern() { + $prefix = $this->getObjectNamePrefix(); + $prefix = preg_quote($prefix); + + $id = $this->getObjectIDPattern(); + // If the prefix starts with a word character (like "D"), we want to // require a word boundary so that we don't match "XD1" as "D1". If the // prefix does not start with a word character, we want to require no word @@ -121,12 +139,55 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { // The "\b" allows us to link "(abcdef)" or similar without linking things // in the middle of words. - $text = preg_replace_callback( - '((?getObjectEmbedPattern(), + $text, + $embed_matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + $ref_matches = null; + preg_match_all( + $this->getObjectReferencePattern(), + $text, + $ref_matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + $results = array(); + $sets = array( + 'embed' => $embed_matches, + 'ref' => $ref_matches, + ); + foreach ($sets as $type => $matches) { + $formatted = array(); + foreach ($matches as $match) { + $format = array( + 'offset' => $match[1][1], + 'id' => $match[1][0], + ); + if (isset($match[2][0])) { + $format['tail'] = $match[2][0]; + } + $formatted[] = $format; + } + $results[$type] = $formatted; + } + + return $results; } public function markupObjectEmbed($matches) { diff --git a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php index 70ee920169..16d4c5674d 100644 --- a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php +++ b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php @@ -8,6 +8,22 @@ final class PhabricatorStorageManagementAPI { private $password; private $namespace; private $conns = array(); + private $disableUTF8MB4; + + const CHARSET_DEFAULT = 'CHARSET'; + const CHARSET_FULLTEXT = 'CHARSET_FULLTEXT'; + const COLLATE_TEXT = 'COLLATE_TEXT'; + const COLLATE_SORT = 'COLLATE_SORT'; + const COLLATE_FULLTEXT = 'COLLATE_FULLTEXT'; + + public function setDisableUTF8MB4($disable_utf8_mb4) { + $this->disableUTF8MB4 = $disable_utf8_mb4; + return $this; + } + + public function getDisableUTF8MB4() { + return $this->disableUTF8MB4; + } public function setNamespace($namespace) { $this->namespace = $namespace; @@ -110,13 +126,12 @@ final class PhabricatorStorageManagementAPI { public function createDatabase($fragment) { $info = $this->getCharsetInfo(); - list($charset, $collate_text, $collate_sort) = $info; queryfx( $this->getConn(null), 'CREATE DATABASE IF NOT EXISTS %T COLLATE %T', $this->getDatabaseName($fragment), - $collate_text); + $info[self::COLLATE_TEXT]); } public function createTable($fragment, $table, array $cols) { @@ -187,15 +202,17 @@ final class PhabricatorStorageManagementAPI { $conn = $this->getConn(null); $charset_info = $this->getCharsetInfo(); - list($charset, $collate_text, $collate_sort) = $charset_info; + foreach ($charset_info as $key => $value) { + $charset_info[$key] = qsprintf($conn, '%T', $value); + } foreach ($queries as $query) { $query = str_replace('{$NAMESPACE}', $this->namespace, $query); - $query = str_replace('{$CHARSET}', $charset, $query); - $escaped_text = qsprintf($conn, '%T', $collate_text); - $query = str_replace('{$COLLATE_TEXT}', $escaped_text, $query); - $escaped_text = qsprintf($conn, '%T', $collate_sort); - $query = str_replace('{$COLLATE_SORT}', $escaped_text, $query); + + foreach ($charset_info as $key => $value) { + $query = str_replace('{$'.$key.'}', $value, $query); + } + queryfx( $conn, '%Q', @@ -209,6 +226,12 @@ final class PhabricatorStorageManagementAPI { } public function isCharacterSetAvailable($character_set) { + if ($character_set == 'utf8mb4') { + if ($this->getDisableUTF8MB4()) { + return false; + } + } + $conn = $this->getConn(null); $result = queryfx_one( @@ -226,8 +249,10 @@ final class PhabricatorStorageManagementAPI { // collation. This is most correct, and will sort properly. $charset = 'utf8mb4'; + $charset_full = 'utf8mb4'; $collate_text = 'utf8mb4_bin'; $collate_sort = 'utf8mb4_unicode_ci'; + $collate_full = 'utf8mb4_unicode_ci'; } else { // If utf8mb4 is not available, we use binary. This allows us to store // 4-byte unicode characters. This has some tradeoffs: @@ -238,13 +263,25 @@ final class PhabricatorStorageManagementAPI { // It's possible that strings will be truncated in the middle of a // character on insert. We encourage users to set STRICT_ALL_TABLES // to prevent this. + // + // There's no valid collation we can use to get a fulltext index on + // 4-byte unicode characters: we can't add a fulltext key to a binary + // column. $charset = 'binary'; + $charset_full = 'utf8'; $collate_text = 'binary'; $collate_sort = 'binary'; + $collate_full = 'utf8_general_ci'; } - return array($charset, $collate_text, $collate_sort); + return array( + self::CHARSET_DEFAULT => $charset, + self::CHARSET_FULLTEXT => $charset_full, + self::COLLATE_TEXT => $collate_text, + self::COLLATE_SORT => $collate_sort, + self::COLLATE_FULLTEXT => $collate_full, + ); } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php index 61a799deed..81d1c21df1 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php @@ -10,14 +10,26 @@ final class PhabricatorStorageManagementAdjustWorkflow ->setSynopsis( pht( 'Make schemata adjustments to correct issues with characters sets, '. - 'collations, and keys.')); + 'collations, and keys.')) + ->setArguments( + array( + array( + 'name' => 'unsafe', + 'help' => pht( + 'Permit adjustments which truncate data. This option may '. + 'destroy some data, but the lost data is usually not '. + 'important (most commonly, the ends of very long object '. + 'titles).'), + ), + )); } public function execute(PhutilArgumentParser $args) { $force = $args->getArg('force'); + $unsafe = $args->getArg('unsafe'); $this->requireAllPatchesApplied(); - return $this->adjustSchemata($force); + return $this->adjustSchemata($force, $unsafe); } private function requireAllPatchesApplied() { @@ -59,7 +71,7 @@ final class PhabricatorStorageManagementAdjustWorkflow return array($comp, $expect, $actual); } - private function adjustSchemata($force) { + private function adjustSchemata($force, $unsafe) { $console = PhutilConsole::getConsole(); $console->writeOut( @@ -138,6 +150,12 @@ final class PhabricatorStorageManagementAdjustWorkflow $api = $this->getAPI(); $conn = $api->getConn(null); + if ($unsafe) { + queryfx($conn, 'SET SESSION sql_mode = %s', ''); + } else { + queryfx($conn, 'SET SESSION sql_mode = %s', 'STRICT_ALL_TABLES'); + } + $failed = array(); // We make changes in several phases. @@ -327,6 +345,15 @@ final class PhabricatorStorageManagementAdjustWorkflow "\n%s\n", pht('Failed to make some schema adjustments, detailed above.')); + if (!$unsafe) { + $console->writeOut( + "%s\n", + pht( + 'Migrations which fail with certain types of errors (including '. + '"#1406 Data Too Long" and "#1366 Incorrect String Value") can be '. + 'forced to complete by running again with `--unsafe`.')); + } + return 1; } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php index 6abeedf9bc..419fca8340 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php @@ -33,6 +33,14 @@ final class PhabricatorStorageManagementQuickstartWorkflow $bin = dirname(phutil_get_library_root('phabricator')).'/bin/storage'; + if (!$this->getAPI()->isCharacterSetAvailable('utf8mb4')) { + throw new PhutilArgumentUsageException( + pht( + 'You can only generate a new quickstart file if MySQL supports '. + 'the utf8mb4 character set (available in MySQL 5.5 and newer). The '. + 'configured server does not support utf8mb4.')); + } + $err = phutil_passthru( '%s upgrade --force --no-quickstart --namespace %s', $bin, @@ -74,6 +82,21 @@ final class PhabricatorStorageManagementQuickstartWorkflow '{$NAMESPACE}', $dump); + // NOTE: This is a hack. We can not use `binary` for this column, because + // it is part of a fulltext index. + $old = $dump; + $dump = preg_replace( + '/`corpus` longtext CHARACTER SET .* COLLATE .*,/mi', + '`corpus` longtext CHARACTER SET {$CHARSET_FULLTEXT} '. + 'COLLATE {$COLLATE_FULLTEXT},', + $dump); + if ($dump == $old) { + // If we didn't make any changes, yell about it. We'll produce an invalid + // dump otherwise. + throw new PhutilArgumentUsageException( + pht('Failed to apply hack to adjust FULLTEXT search column!')); + } + $dump = str_replace( 'utf8mb4_bin', '{$COLLATE_TEXT}', diff --git a/src/view/phui/PHUIIconView.php b/src/view/phui/PHUIIconView.php index fdaa380051..0559e633e2 100644 --- a/src/view/phui/PHUIIconView.php +++ b/src/view/phui/PHUIIconView.php @@ -580,6 +580,7 @@ final class PHUIIconView extends AphrontTagView { 'sky', 'indigo', 'violet', + 'pink', 'lightgreytext', 'lightbluetext', ); diff --git a/webroot/index.php b/webroot/index.php index 09a62b5e4a..b29f4627ac 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -74,6 +74,8 @@ try { $application->setRequest($request); list($controller, $uri_data) = $application->buildController(); + $request->setURIMap($uri_data); + $controller->setRequest($request); $access_log->setData( array( @@ -98,7 +100,7 @@ try { if (!$response) { $controller->willProcessRequest($uri_data); - $response = $controller->processRequest(); + $response = $controller->handleRequest($request); } } catch (Exception $ex) { $original_exception = $ex; diff --git a/webroot/rsrc/css/application/maniphest/task-summary.css b/webroot/rsrc/css/application/maniphest/task-summary.css index 75554c1f3c..913319b4bf 100644 --- a/webroot/rsrc/css/application/maniphest/task-summary.css +++ b/webroot/rsrc/css/application/maniphest/task-summary.css @@ -101,5 +101,6 @@ } .dashboard-panel .maniphest-task-group-header { - display: none; + border-left: 1px solid {$lightblueborder}; + border-right: 1px solid {$lightblueborder}; } diff --git a/webroot/rsrc/css/application/pholio/pholio.css b/webroot/rsrc/css/application/pholio/pholio.css index 6dc47960d0..1cd8e604a1 100644 --- a/webroot/rsrc/css/application/pholio/pholio.css +++ b/webroot/rsrc/css/application/pholio/pholio.css @@ -32,7 +32,7 @@ } .device-desktop .pholio-mock-thumb-grid-item:hover { - border-color: {$indigo}; + border-color: {$pink}; } .pholio-mock-thumb-grid-current { @@ -89,7 +89,7 @@ } .device-desktop .pholio-transaction-inline-image-anchor:hover .phui-image-mask { - border-color: {$indigo}; + border-color: {$pink}; } .pholio-transaction-inline-comment { @@ -138,7 +138,7 @@ .pholio-mock-reticle-final .pholio-mock-comment-icon { font-size: 2.2em; - color: {$indigo}; + color: {$pink}; text-shadow: 0 3px 8px rgba(0, 0, 0, 0.35); -webkit-text-stroke: 1px white; } @@ -198,7 +198,7 @@ border-radius: 2px; color: #fff; text-decoration: none; - background: {$indigo}; + background: {$pink}; } .pholio-image-button { diff --git a/webroot/rsrc/css/core/syntax.css b/webroot/rsrc/css/core/syntax.css index db41a3c169..4669c18647 100644 --- a/webroot/rsrc/css/core/syntax.css +++ b/webroot/rsrc/css/core/syntax.css @@ -10,8 +10,9 @@ margin-right: 1px; } -.remarkup-code td > span { - padding: 1px 0 3px; +.remarkup-code td > span, +.remarkup-code td > span > span { + padding: 1px 0 3px; } .remarkup-code span { diff --git a/webroot/rsrc/css/font/phui-font-icon-base.css b/webroot/rsrc/css/font/phui-font-icon-base.css index df1250b974..2ce692e57c 100644 --- a/webroot/rsrc/css/font/phui-font-icon-base.css +++ b/webroot/rsrc/css/font/phui-font-icon-base.css @@ -134,6 +134,9 @@ .phui-icon-view.indigo { color: {$indigo}; } +.phui-icon-view.pink { + color: {$pink}; +} .phui-icon-view.violet { color: {$violet}; } diff --git a/webroot/rsrc/css/phui/phui-fontkit.css b/webroot/rsrc/css/phui/phui-fontkit.css index 004de8606c..f696412150 100644 --- a/webroot/rsrc/css/phui/phui-fontkit.css +++ b/webroot/rsrc/css/phui/phui-fontkit.css @@ -64,3 +64,9 @@ body .phui-font-source-sans font: 13px/1.231 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: normal; } + +.phui-font-source-sans .phui-tag-shade .phui-icon-view { + font-size: 13px; + top: 4px; + left: 6px; +} 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 37fd6f4fb9..5bb9b09c51 100644 --- a/webroot/rsrc/css/phui/phui-object-item-list-view.css +++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css @@ -430,13 +430,13 @@ */ -.phui-object-item-highlighted { +.phui-object-item.phui-object-item-highlighted { background: {$lightyellow}; + border-left-color: {$yellow}; } -.phui-object-item.phui-object-item-highlighted { - background-image: linear-gradient(to bottom, rgb(253, 255, 221), rgb(243, 245, 206)); - background-image: -webkit-linear-gradient(top, rgb(253, 255, 221), rgb(243, 245, 206)); +.phui-object-item-highlighted .phui-object-item-frame { + border-color: {$yellow}; } .phui-object-item-selected { @@ -681,7 +681,8 @@ border-radius: 0; } -.dashboard-panel .phui-object-item-list-header { +.dashboard-panel .phui-object-item-list-header, +.dashboard-panel .maniphest-task-group-header { font-size: 13px; color: {$bluetext}; background: {$lightgreybackground}; diff --git a/webroot/rsrc/css/phui/phui-status.css b/webroot/rsrc/css/phui/phui-status.css index 4d8fbfd910..9dc446d536 100644 --- a/webroot/rsrc/css/phui/phui-status.css +++ b/webroot/rsrc/css/phui/phui-status.css @@ -6,10 +6,6 @@ width: 100%; } -.phui-property-list-value .phui-status-list-view { - margin-left: -4px; -} - .phui-status-list-view .phui-icon-view { display: block; width: 14px; diff --git a/webroot/rsrc/image/sprite-login-X2.png b/webroot/rsrc/image/sprite-login-X2.png index dbfa87790a..292a4c58f6 100644 Binary files a/webroot/rsrc/image/sprite-login-X2.png and b/webroot/rsrc/image/sprite-login-X2.png differ diff --git a/webroot/rsrc/image/sprite-login.png b/webroot/rsrc/image/sprite-login.png index 5102cf8440..79e1387940 100644 Binary files a/webroot/rsrc/image/sprite-login.png and b/webroot/rsrc/image/sprite-login.png differ