diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1f36bf03b4..da4a85231e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,8 +9,8 @@ return array( 'names' => array( 'conpherence.pkg.css' => '3c8a0668', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '7e6e954b', - 'core.pkg.js' => 'a747b035', + 'core.pkg.css' => '3dc188c0', + 'core.pkg.js' => '9ac8af68', 'differential.pkg.css' => '8d8360fb', 'differential.pkg.js' => '67e02996', 'diffusion.pkg.css' => '42c75c37', @@ -30,14 +30,14 @@ return array( 'rsrc/css/aphront/notification.css' => '30240bd2', 'rsrc/css/aphront/panel-view.css' => '46923d46', 'rsrc/css/aphront/phabricator-nav-view.css' => 'f8a0c1bf', - 'rsrc/css/aphront/table-view.css' => '7dc3a9c2', + 'rsrc/css/aphront/table-view.css' => '5f13a9e4', 'rsrc/css/aphront/tokenizer.css' => 'b52d0668', 'rsrc/css/aphront/tooltip.css' => 'e3f2412f', 'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2', 'rsrc/css/aphront/typeahead.css' => '8779483d', 'rsrc/css/application/almanac/almanac.css' => '2e050f4f', 'rsrc/css/application/auth/auth.css' => 'add92fd8', - 'rsrc/css/application/base/main-menu-view.css' => '8e2d9a28', + 'rsrc/css/application/base/main-menu-view.css' => '17b71bbc', 'rsrc/css/application/base/notification-menu.css' => '4df1ee30', 'rsrc/css/application/base/phui-theme.css' => '35883b37', 'rsrc/css/application/base/standard-page-view.css' => '8a295cb9', @@ -58,7 +58,7 @@ return array( 'rsrc/css/application/contentsource/content-source-view.css' => 'cdf0d579', 'rsrc/css/application/countdown/timer.css' => 'bff8012f', 'rsrc/css/application/daemon/bulk-job.css' => '73af99f5', - 'rsrc/css/application/dashboard/dashboard.css' => '4267d6c6', + 'rsrc/css/application/dashboard/dashboard.css' => '5a205b9d', 'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d', 'rsrc/css/application/differential/add-comment.css' => '7e5900d9', 'rsrc/css/application/differential/changeset-view.css' => 'bde53589', @@ -86,7 +86,7 @@ return array( 'rsrc/css/application/paste/paste.css' => 'b37bcd38', 'rsrc/css/application/people/people-picture-menu-item.css' => 'fe8e07cf', 'rsrc/css/application/people/people-profile.css' => '2ea2daa1', - 'rsrc/css/application/phame/phame.css' => '799febf9', + 'rsrc/css/application/phame/phame.css' => 'bb442327', 'rsrc/css/application/pholio/pholio-edit.css' => '4df55b3b', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '722b48c2', 'rsrc/css/application/pholio/pholio.css' => '88ef5ef1', @@ -100,7 +100,7 @@ return array( 'rsrc/css/application/policy/policy.css' => 'ceb56a08', 'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a', 'rsrc/css/application/project/project-card-view.css' => '4e7371cd', - 'rsrc/css/application/project/project-triggers.css' => 'cb866c2d', + 'rsrc/css/application/project/project-triggers.css' => 'cd9c8bb9', 'rsrc/css/application/project/project-view.css' => '567858b3', 'rsrc/css/application/releeph/releeph-core.css' => 'f81ff2db', 'rsrc/css/application/releeph/releeph-preview-branch.css' => '22db5c07', @@ -128,13 +128,13 @@ return array( 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2', 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42', 'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa', - 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '534f1757', + 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => 'fa74cc35', 'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'a65865a7', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'd7723ecc', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46', - 'rsrc/css/phui/phui-action-list.css' => 'c4972757', + 'rsrc/css/phui/phui-action-list.css' => 'e820263c', 'rsrc/css/phui/phui-action-panel.css' => '6c386cbf', 'rsrc/css/phui/phui-badge.css' => '666e25ad', 'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d', @@ -160,11 +160,11 @@ return array( 'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec', 'rsrc/css/phui/phui-icon.css' => '4cbc684a', 'rsrc/css/phui/phui-image-mask.css' => '62c7f4d2', - 'rsrc/css/phui/phui-info-view.css' => '37b8d9ce', + 'rsrc/css/phui/phui-info-view.css' => 'a10a909b', 'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4', 'rsrc/css/phui/phui-left-right.css' => '68513c34', 'rsrc/css/phui/phui-lightbox.css' => '4ebf22da', - 'rsrc/css/phui/phui-list.css' => '470b1adb', + 'rsrc/css/phui/phui-list.css' => 'b05144dd', 'rsrc/css/phui/phui-object-box.css' => 'f434b6be', 'rsrc/css/phui/phui-pager.css' => 'd022c7ad', 'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8', @@ -173,7 +173,7 @@ return array( 'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370', 'rsrc/css/phui/phui-spacing.css' => 'b05cadc3', 'rsrc/css/phui/phui-status.css' => 'e5ff8be0', - 'rsrc/css/phui/phui-tag-view.css' => '29409667', + 'rsrc/css/phui/phui-tag-view.css' => '8519160a', 'rsrc/css/phui/phui-timeline-view.css' => '1e348e4b', 'rsrc/css/phui/phui-two-column-view.css' => '01e6991e', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308', @@ -217,7 +217,7 @@ return array( 'rsrc/externals/javelin/core/init.js' => '98e6504a', 'rsrc/externals/javelin/core/init_node.js' => '16961339', 'rsrc/externals/javelin/core/install.js' => '5902260c', - 'rsrc/externals/javelin/core/util.js' => '22ae1776', + 'rsrc/externals/javelin/core/util.js' => 'edb4d8c9', 'rsrc/externals/javelin/docs/Base.js' => '5a401d7d', 'rsrc/externals/javelin/docs/onload.js' => 'ee58fb62', 'rsrc/externals/javelin/ext/fx/Color.js' => '78f811c9', @@ -259,7 +259,7 @@ return array( 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae', 'rsrc/externals/javelin/lib/__tests__/URI.js' => '6fff0c2b', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => '8426ebeb', - 'rsrc/externals/javelin/lib/behavior.js' => 'fce5d170', + 'rsrc/externals/javelin/lib/behavior.js' => '1b6acc2a', 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '89a1ae3a', 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'a4356cde', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'a241536a', @@ -371,10 +371,10 @@ return array( 'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '8f959ad0', 'rsrc/js/application/countdown/timer.js' => '6a162524', 'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => '3829a3cf', - 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '09ecf50c', - 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '076bd092', + 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '9c01e364', + 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9', - 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '9b1cbd76', + 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8', 'rsrc/js/application/diff/DiffChangeset.js' => 'd0a85a85', 'rsrc/js/application/diff/DiffChangesetList.js' => '04023d82', 'rsrc/js/application/diff/DiffInline.js' => 'a4a14a94', @@ -433,7 +433,7 @@ return array( 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '600f440c', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '2bdadf1a', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '9cec214e', - 'rsrc/js/application/trigger/TriggerRule.js' => '1c60c3fc', + 'rsrc/js/application/trigger/TriggerRule.js' => '41b7b4f6', 'rsrc/js/application/trigger/TriggerRuleControl.js' => '5faf27b9', 'rsrc/js/application/trigger/TriggerRuleEditor.js' => 'b49fd60c', 'rsrc/js/application/trigger/TriggerRuleType.js' => '4feea7d3', @@ -516,9 +516,9 @@ return array( 'rsrc/js/phui/behavior-phui-timer-control.js' => 'f84bcbf4', 'rsrc/js/phuix/PHUIXActionListView.js' => 'c68f183f', 'rsrc/js/phuix/PHUIXActionView.js' => 'aaa08f3b', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => '8f139ef0', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '2fbe234d', 'rsrc/js/phuix/PHUIXButtonView.js' => '55a24e84', - 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bdce4d78', + 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '7acfd98b', 'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7', 'rsrc/js/phuix/PHUIXFormControl.js' => '38c1f3fb', 'rsrc/js/phuix/PHUIXIconView.js' => 'a5257c4e', @@ -531,7 +531,7 @@ return array( 'aphront-list-filter-view-css' => 'feb64255', 'aphront-multi-column-view-css' => 'fbc00ba3', 'aphront-panel-view-css' => '46923d46', - 'aphront-table-view-css' => '7dc3a9c2', + 'aphront-table-view-css' => '5f13a9e4', 'aphront-tokenizer-control-css' => 'b52d0668', 'aphront-tooltip-css' => 'e3f2412f', 'aphront-typeahead-control-css' => '8779483d', @@ -571,7 +571,7 @@ return array( 'herald-test-css' => 'e004176f', 'inline-comment-summary-css' => '81eb368d', 'javelin-aphlict' => '022516b4', - 'javelin-behavior' => 'fce5d170', + 'javelin-behavior' => '1b6acc2a', 'javelin-behavior-aphlict-dropdown' => 'e9a2940f', 'javelin-behavior-aphlict-listen' => '4e61fa88', 'javelin-behavior-aphlict-status' => 'c3703a16', @@ -594,10 +594,10 @@ return array( 'javelin-behavior-conpherence-search' => '91befbcc', 'javelin-behavior-countdown-timer' => '6a162524', 'javelin-behavior-dark-console' => 'f39d968b', - 'javelin-behavior-dashboard-async-panel' => '09ecf50c', - 'javelin-behavior-dashboard-move-panels' => '076bd092', + 'javelin-behavior-dashboard-async-panel' => '9c01e364', + 'javelin-behavior-dashboard-move-panels' => 'a2ab19be', 'javelin-behavior-dashboard-query-panel-select' => '1e413dc9', - 'javelin-behavior-dashboard-tab-panel' => '9b1cbd76', + 'javelin-behavior-dashboard-tab-panel' => '0116d3e8', 'javelin-behavior-day-view' => '727a5a61', 'javelin-behavior-desktop-notifications-control' => '070679fe', 'javelin-behavior-detect-timezone' => '78bc5d94', @@ -729,7 +729,7 @@ return array( 'javelin-typeahead-source' => '8badee71', 'javelin-typeahead-static-source' => '80bff3af', 'javelin-uri' => '2e255291', - 'javelin-util' => '22ae1776', + 'javelin-util' => 'edb4d8c9', 'javelin-vector' => 'e9c80beb', 'javelin-view' => '289bf236', 'javelin-view-html' => 'f8c4e135', @@ -757,7 +757,7 @@ return array( 'path-typeahead' => 'ad486db3', 'people-picture-menu-item-css' => 'fe8e07cf', 'people-profile-css' => '2ea2daa1', - 'phabricator-action-list-view-css' => 'c4972757', + 'phabricator-action-list-view-css' => 'e820263c', 'phabricator-busy' => '5202e831', 'phabricator-chatlog-css' => 'abdc76ee', 'phabricator-content-source-view-css' => 'cdf0d579', @@ -765,7 +765,7 @@ return array( 'phabricator-countdown-css' => 'bff8012f', 'phabricator-darklog' => '3b869402', 'phabricator-darkmessage' => '26cd4b73', - 'phabricator-dashboard-css' => '4267d6c6', + 'phabricator-dashboard-css' => '5a205b9d', 'phabricator-diff-changeset' => 'd0a85a85', 'phabricator-diff-changeset-list' => '04023d82', 'phabricator-diff-inline' => 'a4a14a94', @@ -779,7 +779,7 @@ return array( 'phabricator-flag-css' => '2b77be8d', 'phabricator-keyboard-shortcut' => 'c9749dcd', 'phabricator-keyboard-shortcut-manager' => '37b8a04a', - 'phabricator-main-menu-view' => '8e2d9a28', + 'phabricator-main-menu-view' => '17b71bbc', 'phabricator-nav-view-css' => 'f8a0c1bf', 'phabricator-notification' => 'a9b91e3f', 'phabricator-notification-css' => '30240bd2', @@ -798,7 +798,7 @@ return array( 'phabricator-tooltip' => '83754533', 'phabricator-ui-example-css' => 'b4795059', 'phabricator-zindex-css' => '99c0f5eb', - 'phame-css' => '799febf9', + 'phame-css' => 'bb442327', 'pholio-css' => '88ef5ef1', 'pholio-edit-css' => '4df55b3b', 'pholio-inline-comments-css' => '722b48c2', @@ -842,18 +842,18 @@ return array( 'phui-icon-set-selector-css' => '7aa5f3ec', 'phui-icon-view-css' => '4cbc684a', 'phui-image-mask-css' => '62c7f4d2', - 'phui-info-view-css' => '37b8d9ce', + 'phui-info-view-css' => 'a10a909b', 'phui-inline-comment-view-css' => '48acce5b', 'phui-invisible-character-view-css' => 'c694c4a4', 'phui-left-right-css' => '68513c34', 'phui-lightbox-css' => '4ebf22da', - 'phui-list-view-css' => '470b1adb', + 'phui-list-view-css' => 'b05144dd', 'phui-object-box-css' => 'f434b6be', - 'phui-oi-big-ui-css' => '534f1757', + 'phui-oi-big-ui-css' => 'fa74cc35', 'phui-oi-color-css' => 'b517bfa0', 'phui-oi-drag-ui-css' => 'da15d3dc', 'phui-oi-flush-ui-css' => '490e2e2e', - 'phui-oi-list-view-css' => 'a65865a7', + 'phui-oi-list-view-css' => 'd7723ecc', 'phui-oi-simple-ui-css' => '6a30fa46', 'phui-pager-css' => 'd022c7ad', 'phui-pinboard-view-css' => '1f08f5d8', @@ -862,7 +862,7 @@ return array( 'phui-segment-bar-view-css' => '5166b370', 'phui-spacing-css' => 'b05cadc3', 'phui-status-list-view-css' => 'e5ff8be0', - 'phui-tag-view-css' => '29409667', + 'phui-tag-view-css' => '8519160a', 'phui-theme-css' => '35883b37', 'phui-timeline-view-css' => '1e348e4b', 'phui-two-column-view-css' => '01e6991e', @@ -872,9 +872,9 @@ return array( 'phui-workpanel-view-css' => '3ae89b20', 'phuix-action-list-view' => 'c68f183f', 'phuix-action-view' => 'aaa08f3b', - 'phuix-autocomplete' => '8f139ef0', + 'phuix-autocomplete' => '2fbe234d', 'phuix-button-view' => '55a24e84', - 'phuix-dropdown-menu' => 'bdce4d78', + 'phuix-dropdown-menu' => '7acfd98b', 'phuix-form-control-view' => '38c1f3fb', 'phuix-icon-view' => 'a5257c4e', 'policy-css' => 'ceb56a08', @@ -882,7 +882,7 @@ return array( 'policy-transaction-detail-css' => 'c02b8384', 'ponder-view-css' => '05a09d0a', 'project-card-view-css' => '4e7371cd', - 'project-triggers-css' => 'cb866c2d', + 'project-triggers-css' => 'cd9c8bb9', 'project-view-css' => '567858b3', 'releeph-core' => 'f81ff2db', 'releeph-preview-branch' => '22db5c07', @@ -894,7 +894,7 @@ return array( 'syntax-default-css' => '055fc231', 'syntax-highlighting-css' => '4234f572', 'tokens-css' => 'ce5a50bd', - 'trigger-rule' => '1c60c3fc', + 'trigger-rule' => '41b7b4f6', 'trigger-rule-control' => '5faf27b9', 'trigger-rule-editor' => 'b49fd60c', 'trigger-rule-type' => '4feea7d3', @@ -902,6 +902,11 @@ return array( 'unhandled-exception-css' => '9ecfc00d', ), 'requires' => array( + '0116d3e8' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '01384686' => array( 'javelin-behavior', 'javelin-uri', @@ -959,14 +964,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '076bd092' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), '0889b835' => array( 'javelin-install', 'javelin-event', @@ -977,11 +974,6 @@ return array( 'herald-rule-editor', 'javelin-behavior', ), - '09ecf50c' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-workflow', - ), '0ad8d31f' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1029,6 +1021,13 @@ return array( 'javelin-stratcom', 'javelin-util', ), + '17b71bbc' => array( + 'phui-theme-css', + ), + '1b6acc2a' => array( + 'javelin-magical-init', + 'javelin-util', + ), '1c850a26' => array( 'javelin-install', 'javelin-util', @@ -1173,6 +1172,12 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), + '2fbe234d' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), '308f9fe4' => array( 'javelin-install', 'javelin-util', @@ -1369,9 +1374,6 @@ return array( 'javelin-dom', 'javelin-fx', ), - '534f1757' => array( - 'phui-oi-list-view-css', - ), '541f81c3' => array( 'javelin-install', ), @@ -1556,6 +1558,13 @@ return array( 'javelin-install', 'javelin-dom', ), + '7acfd98b' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + 'javelin-stratcom', + ), '7ad020a5' => array( 'javelin-behavior', 'javelin-dom', @@ -1631,15 +1640,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '8e2d9a28' => array( - 'phui-theme-css', - ), - '8f139ef0' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), '8f959ad0' => array( 'javelin-behavior', 'javelin-dom', @@ -1728,10 +1728,10 @@ return array( 'javelin-install', 'javelin-util', ), - '9b1cbd76' => array( + '9c01e364' => array( 'javelin-behavior', 'javelin-dom', - 'javelin-stratcom', + 'javelin-workflow', ), '9cec214e' => array( 'javelin-behavior', @@ -1757,6 +1757,14 @@ return array( 'a241536a' => array( 'javelin-install', ), + 'a2ab19be' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), 'a4356cde' => array( 'javelin-install', 'javelin-dom', @@ -1921,13 +1929,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - 'bdce4d78' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-vector', - 'javelin-stratcom', - ), 'bde53589' => array( 'phui-inline-comment-view-css', ), @@ -2175,9 +2176,8 @@ return array( 'phabricator-keyboard-shortcut', 'conpherence-thread-manager', ), - 'fce5d170' => array( - 'javelin-magical-init', - 'javelin-util', + 'fa74cc35' => array( + 'phui-oi-list-view-css', ), 'fdc13e4e' => array( 'javelin-install', diff --git a/resources/sql/autopatches/20190329.portals.01.create.sql b/resources/sql/autopatches/20190329.portals.01.create.sql new file mode 100644 index 0000000000..d7d1e6138f --- /dev/null +++ b/resources/sql/autopatches/20190329.portals.01.create.sql @@ -0,0 +1,11 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190329.portals.02.xaction.sql b/resources/sql/autopatches/20190329.portals.02.xaction.sql new file mode 100644 index 0000000000..057df69e2d --- /dev/null +++ b/resources/sql/autopatches/20190329.portals.02.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portaltransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL, + oldValue LONGTEXT NOT NULL, + newValue LONGTEXT NOT NULL, + contentSource LONGTEXT NOT NULL, + metadata LONGTEXT NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190410.portals.01.ferret.doc.sql b/resources/sql/autopatches/20190410.portals.01.ferret.doc.sql new file mode 100644 index 0000000000..984f3196f9 --- /dev/null +++ b/resources/sql/autopatches/20190410.portals.01.ferret.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190410.portals.02.ferret.field.sql b/resources/sql/autopatches/20190410.portals.02.ferret.field.sql new file mode 100644 index 0000000000..af02b8f0d6 --- /dev/null +++ b/resources/sql/autopatches/20190410.portals.02.ferret.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190410.portals.03.ferret.ngrams.sql b/resources/sql/autopatches/20190410.portals.03.ferret.ngrams.sql new file mode 100644 index 0000000000..37a5fc80ab --- /dev/null +++ b/resources/sql/autopatches/20190410.portals.03.ferret.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190410.portals.04.ferret.cngrams.sql b/resources/sql/autopatches/20190410.portals.04.ferret.cngrams.sql new file mode 100644 index 0000000000..678af664bf --- /dev/null +++ b/resources/sql/autopatches/20190410.portals.04.ferret.cngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.01.panels.php b/resources/sql/autopatches/20190412.dashboard.01.panels.php new file mode 100644 index 0000000000..dad132356b --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.01.panels.php @@ -0,0 +1,81 @@ +establishConnection('r'); +$table_name = $dashboard_table->getTableName(); + +$rows = new LiskRawMigrationIterator($conn, $table_name); +foreach ($rows as $row) { + $config = $row['layoutConfig']; + + try { + $config = phutil_json_decode($config); + } catch (Exception $ex) { + $config = array(); + } + + if (!is_array($config)) { + $config = array(); + } + + $panels = idx($config, 'panelLocations'); + if (!is_array($panels)) { + $panels = array(); + } + + if (idx($config, 'layoutMode') === 'layout-mode-full') { + $column_map = array( + 0 => 'main', + ); + } else { + $column_map = array( + 0 => 'left', + 1 => 'right', + ); + } + + $panel_list = array(); + foreach ($panels as $column_idx => $panel_phids) { + $column_key = idx($column_map, $column_idx, 'unknown'); + foreach ($panel_phids as $panel_phid) { + $panel_list[] = array( + 'panelKey' => Filesystem::readRandomCharacters(8), + 'columnKey' => $column_key, + 'panelPHID' => $panel_phid, + ); + } + } + unset($config['panelLocations']); + $config['panels'] = $panel_list; + + queryfx( + $conn, + 'UPDATE %R SET layoutConfig = %s WHERE id = %d', + $dashboard_table, + phutil_json_encode($config), + $row['id']); +} diff --git a/resources/sql/autopatches/20190412.dashboard.02.install.sql b/resources/sql/autopatches/20190412.dashboard.02.install.sql new file mode 100644 index 0000000000..be3363d5c3 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.02.install.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_dashboard.dashboard_install; diff --git a/resources/sql/autopatches/20190412.dashboard.03.dashngrams.sql b/resources/sql/autopatches/20190412.dashboard.03.dashngrams.sql new file mode 100644 index 0000000000..2f1b572a8b --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.03.dashngrams.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_dashboard.dashboard_dashboard_ngrams; diff --git a/resources/sql/autopatches/20190412.dashboard.04.panelngrams.sql b/resources/sql/autopatches/20190412.dashboard.04.panelngrams.sql new file mode 100644 index 0000000000..e06d817c6e --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.04.panelngrams.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_dashboard.dashboard_dashboardpanel_ngrams; diff --git a/resources/sql/autopatches/20190412.dashboard.05.dferret.doc.sql b/resources/sql/autopatches/20190412.dashboard.05.dferret.doc.sql new file mode 100644 index 0000000000..2073a5b578 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.05.dferret.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_dashboard_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.06.dferret.field.sql b/resources/sql/autopatches/20190412.dashboard.06.dferret.field.sql new file mode 100644 index 0000000000..b8845f5686 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.06.dferret.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_dashboard_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.07.dferret.ngrams.sql b/resources/sql/autopatches/20190412.dashboard.07.dferret.ngrams.sql new file mode 100644 index 0000000000..3279e7dc27 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.07.dferret.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_dashboard_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.08.dferret.cngrams.sql b/resources/sql/autopatches/20190412.dashboard.08.dferret.cngrams.sql new file mode 100644 index 0000000000..0ee815d175 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.08.dferret.cngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_dashboard_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.09.pferret.doc.sql b/resources/sql/autopatches/20190412.dashboard.09.pferret.doc.sql new file mode 100644 index 0000000000..827a4245ed --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.09.pferret.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_panel_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.10.pferret.field.sql b/resources/sql/autopatches/20190412.dashboard.10.pferret.field.sql new file mode 100644 index 0000000000..f63521d87b --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.10.pferret.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_panel_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.11.pferret.ngrams.sql b/resources/sql/autopatches/20190412.dashboard.11.pferret.ngrams.sql new file mode 100644 index 0000000000..a197ec0272 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.11.pferret.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_panel_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.12.pferret.cngrams.sql b/resources/sql/autopatches/20190412.dashboard.12.pferret.cngrams.sql new file mode 100644 index 0000000000..95426fcf1d --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.12.pferret.cngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_panel_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.13.rebuild.php b/resources/sql/autopatches/20190412.dashboard.13.rebuild.php new file mode 100644 index 0000000000..ed32a9137f --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.13.rebuild.php @@ -0,0 +1,7 @@ + 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php', 'DifferentialRevisionInlineTransaction' => 'applications/differential/xaction/DifferentialRevisionInlineTransaction.php', 'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php', + 'DifferentialRevisionJIRAIssueURIsHeraldField' => 'applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php', 'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php', 'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php', 'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php', @@ -2305,12 +2306,14 @@ phutil_register_library_map(array( 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', 'PhabricatorAuthManagementListMFAProvidersWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListMFAProvidersWorkflow.php', + 'PhabricatorAuthManagementLockWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLockWorkflow.php', 'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php', 'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php', 'PhabricatorAuthManagementRevokeWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php', 'PhabricatorAuthManagementStripWorkflow' => 'applications/auth/management/PhabricatorAuthManagementStripWorkflow.php', 'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php', 'PhabricatorAuthManagementUnlimitWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUnlimitWorkflow.php', + 'PhabricatorAuthManagementUnlockWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php', 'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php', 'PhabricatorAuthManagementVerifyWorkflow' => 'applications/auth/management/PhabricatorAuthManagementVerifyWorkflow.php', 'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php', @@ -2903,65 +2906,112 @@ phutil_register_library_map(array( 'PhabricatorDarkConsoleTabSetting' => 'applications/settings/setting/PhabricatorDarkConsoleTabSetting.php', 'PhabricatorDarkConsoleVisibleSetting' => 'applications/settings/setting/PhabricatorDarkConsoleVisibleSetting.php', 'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php', - 'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php', + 'PhabricatorDashboardAdjustController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php', 'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php', - 'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardArchiveController.php', - 'PhabricatorDashboardArrangeController' => 'applications/dashboard/controller/PhabricatorDashboardArrangeController.php', + 'PhabricatorDashboardApplicationInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php', + 'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php', + 'PhabricatorDashboardColumn' => 'applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php', + 'PhabricatorDashboardConsoleController' => 'applications/dashboard/controller/PhabricatorDashboardConsoleController.php', 'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php', 'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php', - 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php', 'PhabricatorDashboardDashboardPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardDashboardPHIDType.php', 'PhabricatorDashboardDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardDatasource.php', - 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php', + 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php', + 'PhabricatorDashboardEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardEditEngine.php', + 'PhabricatorDashboardFavoritesInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php', + 'PhabricatorDashboardFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardFerretEngine.php', + 'PhabricatorDashboardFullLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php', + 'PhabricatorDashboardFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardFulltextEngine.php', + 'PhabricatorDashboardHalfLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php', + 'PhabricatorDashboardHomeInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php', 'PhabricatorDashboardIconSet' => 'applications/dashboard/icon/PhabricatorDashboardIconSet.php', - 'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php', - 'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/PhabricatorDashboardInstallController.php', - 'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php', + 'PhabricatorDashboardIconTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php', + 'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php', + 'PhabricatorDashboardInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php', + 'PhabricatorDashboardLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php', + 'PhabricatorDashboardLayoutTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php', 'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php', - 'PhabricatorDashboardManageController' => 'applications/dashboard/controller/PhabricatorDashboardManageController.php', - 'PhabricatorDashboardMovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardMovePanelController.php', - 'PhabricatorDashboardNgrams' => 'applications/dashboard/storage/PhabricatorDashboardNgrams.php', + 'PhabricatorDashboardNameTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php', + 'PhabricatorDashboardObjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php', + 'PhabricatorDashboardOneThirdLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php', 'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php', - 'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php', - 'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php', - 'PhabricatorDashboardPanelCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php', + 'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php', + 'PhabricatorDashboardPanelContainerIndexEngineExtension' => 'applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php', + 'PhabricatorDashboardPanelContainerInterface' => 'applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php', 'PhabricatorDashboardPanelDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php', 'PhabricatorDashboardPanelEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php', - 'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditController.php', + 'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php', 'PhabricatorDashboardPanelEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php', - 'PhabricatorDashboardPanelEditproController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php', - 'PhabricatorDashboardPanelHasDashboardEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardPanelHasDashboardEdgeType.php', - 'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/PhabricatorDashboardPanelListController.php', - 'PhabricatorDashboardPanelNgrams' => 'applications/dashboard/storage/PhabricatorDashboardPanelNgrams.php', + 'PhabricatorDashboardPanelFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php', + 'PhabricatorDashboardPanelFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelFulltextEngine.php', + 'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php', + 'PhabricatorDashboardPanelNameTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelNameTransaction.php', 'PhabricatorDashboardPanelPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php', + 'PhabricatorDashboardPanelPropertyTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php', 'PhabricatorDashboardPanelQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelQuery.php', - 'PhabricatorDashboardPanelRenderController' => 'applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php', + 'PhabricatorDashboardPanelRef' => 'applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php', + 'PhabricatorDashboardPanelRefList' => 'applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php', + 'PhabricatorDashboardPanelRenderController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php', 'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php', - 'PhabricatorDashboardPanelSearchApplicationCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php', 'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php', - 'PhabricatorDashboardPanelSearchQueryCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php', - 'PhabricatorDashboardPanelTabsCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelTabsCustomField.php', + 'PhabricatorDashboardPanelStatusTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php', + 'PhabricatorDashboardPanelTabsController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php', 'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php', 'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php', 'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php', + 'PhabricatorDashboardPanelTransactionType' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php', 'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php', - 'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/PhabricatorDashboardPanelViewController.php', + 'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php', + 'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php', + 'PhabricatorDashboardPanelsTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php', + 'PhabricatorDashboardPortal' => 'applications/dashboard/storage/PhabricatorDashboardPortal.php', + 'PhabricatorDashboardPortalController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php', + 'PhabricatorDashboardPortalDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php', + 'PhabricatorDashboardPortalEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalEditConduitAPIMethod.php', + 'PhabricatorDashboardPortalEditController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalEditController.php', + 'PhabricatorDashboardPortalEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php', + 'PhabricatorDashboardPortalEditor' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditor.php', + 'PhabricatorDashboardPortalFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php', + 'PhabricatorDashboardPortalFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalFulltextEngine.php', + 'PhabricatorDashboardPortalInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php', + 'PhabricatorDashboardPortalListController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php', + 'PhabricatorDashboardPortalMenuItem' => 'applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php', + 'PhabricatorDashboardPortalNameTransaction' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalNameTransaction.php', + 'PhabricatorDashboardPortalPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php', + 'PhabricatorDashboardPortalProfileMenuEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php', + 'PhabricatorDashboardPortalQuery' => 'applications/dashboard/query/PhabricatorDashboardPortalQuery.php', + 'PhabricatorDashboardPortalSearchConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalSearchConduitAPIMethod.php', + 'PhabricatorDashboardPortalSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPortalSearchEngine.php', + 'PhabricatorDashboardPortalStatus' => 'applications/dashboard/constants/PhabricatorDashboardPortalStatus.php', + 'PhabricatorDashboardPortalTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPortalTransaction.php', + 'PhabricatorDashboardPortalTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPortalTransactionQuery.php', + 'PhabricatorDashboardPortalTransactionType' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalTransactionType.php', + 'PhabricatorDashboardPortalViewController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php', 'PhabricatorDashboardProfileController' => 'applications/dashboard/controller/PhabricatorDashboardProfileController.php', 'PhabricatorDashboardProfileMenuItem' => 'applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php', + 'PhabricatorDashboardProjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php', 'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php', + 'PhabricatorDashboardQueryPanelApplicationEditField' => 'applications/dashboard/editfield/PhabricatorDashboardQueryPanelApplicationEditField.php', + 'PhabricatorDashboardQueryPanelApplicationTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelApplicationTransaction.php', 'PhabricatorDashboardQueryPanelInstallController' => 'applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php', + 'PhabricatorDashboardQueryPanelQueryEditField' => 'applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php', + 'PhabricatorDashboardQueryPanelQueryTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php', 'PhabricatorDashboardQueryPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php', 'PhabricatorDashboardRemarkupRule' => 'applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php', - 'PhabricatorDashboardRemovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php', 'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php', 'PhabricatorDashboardSchemaSpec' => 'applications/dashboard/storage/PhabricatorDashboardSchemaSpec.php', 'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php', + 'PhabricatorDashboardStatusTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php', + 'PhabricatorDashboardTabsPanelTabsTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTabsPanelTabsTransaction.php', 'PhabricatorDashboardTabsPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php', + 'PhabricatorDashboardTextPanelTextTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTextPanelTextTransaction.php', 'PhabricatorDashboardTextPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php', 'PhabricatorDashboardTransaction' => 'applications/dashboard/storage/PhabricatorDashboardTransaction.php', 'PhabricatorDashboardTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php', 'PhabricatorDashboardTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardTransactionQuery.php', - 'PhabricatorDashboardViewController' => 'applications/dashboard/controller/PhabricatorDashboardViewController.php', + 'PhabricatorDashboardTransactionType' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php', + 'PhabricatorDashboardTwoThirdsLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php', + 'PhabricatorDashboardViewController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php', 'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php', 'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php', 'PhabricatorDatabaseRef' => 'infrastructure/cluster/PhabricatorDatabaseRef.php', @@ -3028,6 +3078,7 @@ phutil_register_library_map(array( 'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php', 'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php', 'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php', + 'PhabricatorEdgeIndexEngineExtension' => 'applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php', 'PhabricatorEdgeObject' => 'infrastructure/edges/conduit/PhabricatorEdgeObject.php', 'PhabricatorEdgeObjectQuery' => 'infrastructure/edges/query/PhabricatorEdgeObjectQuery.php', 'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php', @@ -3687,6 +3738,7 @@ phutil_register_library_map(array( 'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php', 'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php', 'PhabricatorObjectStatus' => 'infrastructure/status/PhabricatorObjectStatus.php', + 'PhabricatorObjectUsesDashboardPanelEdgeType' => 'applications/search/edge/PhabricatorObjectUsesDashboardPanelEdgeType.php', 'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php', 'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php', 'PhabricatorOlderInlinesSetting' => 'applications/settings/setting/PhabricatorOlderInlinesSetting.php', @@ -4031,12 +4083,16 @@ phutil_register_library_map(array( 'PhabricatorProfileMenuEditor' => 'applications/search/editor/PhabricatorProfileMenuEditor.php', 'PhabricatorProfileMenuEngine' => 'applications/search/engine/PhabricatorProfileMenuEngine.php', 'PhabricatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorProfileMenuItem.php', + 'PhabricatorProfileMenuItemAffectsObjectEdgeType' => 'applications/search/edge/PhabricatorProfileMenuItemAffectsObjectEdgeType.php', 'PhabricatorProfileMenuItemConfiguration' => 'applications/search/storage/PhabricatorProfileMenuItemConfiguration.php', 'PhabricatorProfileMenuItemConfigurationQuery' => 'applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php', 'PhabricatorProfileMenuItemConfigurationTransaction' => 'applications/search/storage/PhabricatorProfileMenuItemConfigurationTransaction.php', 'PhabricatorProfileMenuItemConfigurationTransactionQuery' => 'applications/search/query/PhabricatorProfileMenuItemConfigurationTransactionQuery.php', 'PhabricatorProfileMenuItemIconSet' => 'applications/search/menuitem/PhabricatorProfileMenuItemIconSet.php', + 'PhabricatorProfileMenuItemIndexEngineExtension' => 'applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php', 'PhabricatorProfileMenuItemPHIDType' => 'applications/search/phidtype/PhabricatorProfileMenuItemPHIDType.php', + 'PhabricatorProfileMenuItemView' => 'applications/search/engine/PhabricatorProfileMenuItemView.php', + 'PhabricatorProfileMenuItemViewList' => 'applications/search/engine/PhabricatorProfileMenuItemViewList.php', 'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php', 'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php', 'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php', @@ -4174,18 +4230,21 @@ phutil_register_library_map(array( 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', 'PhabricatorProjectTrigger' => 'applications/project/storage/PhabricatorProjectTrigger.php', + 'PhabricatorProjectTriggerAddProjectsRule' => 'applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php', 'PhabricatorProjectTriggerController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerController.php', 'PhabricatorProjectTriggerCorruptionException' => 'applications/project/exception/PhabricatorProjectTriggerCorruptionException.php', 'PhabricatorProjectTriggerEditController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php', 'PhabricatorProjectTriggerEditor' => 'applications/project/editor/PhabricatorProjectTriggerEditor.php', 'PhabricatorProjectTriggerInvalidRule' => 'applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php', 'PhabricatorProjectTriggerListController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerListController.php', + 'PhabricatorProjectTriggerManiphestOwnerRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php', 'PhabricatorProjectTriggerManiphestPriorityRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php', 'PhabricatorProjectTriggerManiphestStatusRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php', 'PhabricatorProjectTriggerNameTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerNameTransaction.php', 'PhabricatorProjectTriggerPHIDType' => 'applications/project/phid/PhabricatorProjectTriggerPHIDType.php', 'PhabricatorProjectTriggerPlaySoundRule' => 'applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php', 'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php', + 'PhabricatorProjectTriggerRemoveProjectsRule' => 'applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php', 'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php', 'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php', 'PhabricatorProjectTriggerRulesetTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php', @@ -4235,6 +4294,7 @@ phutil_register_library_map(array( 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', 'PhabricatorQuickSearchEngineExtension' => 'applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', + 'PhabricatorRebuildIndexesWorker' => 'applications/search/worker/PhabricatorRebuildIndexesWorker.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', @@ -6192,6 +6252,7 @@ phutil_register_library_map(array( 'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialRevisionInlineTransaction' => 'PhabricatorModularTransactionType', 'DifferentialRevisionInlinesController' => 'DifferentialController', + 'DifferentialRevisionJIRAIssueURIsHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionListView' => 'AphrontView', 'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver', @@ -7250,7 +7311,7 @@ phutil_register_library_map(array( 'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor', 'HeraldRuleField' => 'HeraldField', 'HeraldRuleFieldGroup' => 'HeraldFieldGroup', - 'HeraldRuleIndexEngineExtension' => 'PhabricatorIndexEngineExtension', + 'HeraldRuleIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension', 'HeraldRuleListController' => 'HeraldController', 'HeraldRuleListView' => 'AphrontView', 'HeraldRuleNameTransaction' => 'HeraldRuleTransactionType', @@ -8134,12 +8195,14 @@ phutil_register_library_map(array( 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListMFAProvidersWorkflow' => 'PhabricatorAuthManagementWorkflow', + 'PhabricatorAuthManagementLockWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRevokeWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementStripWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementUnlimitWorkflow' => 'PhabricatorAuthManagementWorkflow', + 'PhabricatorAuthManagementUnlockWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementVerifyWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementWorkflow' => 'PhabricatorManagementWorkflow', @@ -8840,77 +8903,131 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorProjectInterface', - 'PhabricatorNgramsInterface', + 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', + 'PhabricatorDashboardPanelContainerInterface', ), - 'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardAdjustController' => 'PhabricatorDashboardController', 'PhabricatorDashboardApplication' => 'PhabricatorApplication', + 'PhabricatorDashboardApplicationInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow', 'PhabricatorDashboardArchiveController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardArrangeController' => 'PhabricatorDashboardProfileController', + 'PhabricatorDashboardColumn' => 'Phobject', + 'PhabricatorDashboardConsoleController' => 'PhabricatorDashboardController', 'PhabricatorDashboardController' => 'PhabricatorController', 'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO', - 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType', 'PhabricatorDashboardDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorDashboardEditController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorDashboardFavoritesInstallWorkflow' => 'PhabricatorDashboardApplicationInstallWorkflow', + 'PhabricatorDashboardFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorDashboardFullLayoutMode' => 'PhabricatorDashboardLayoutMode', + 'PhabricatorDashboardFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhabricatorDashboardHalfLayoutMode' => 'PhabricatorDashboardLayoutMode', + 'PhabricatorDashboardHomeInstallWorkflow' => 'PhabricatorDashboardApplicationInstallWorkflow', 'PhabricatorDashboardIconSet' => 'PhabricatorIconSet', - 'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO', + 'PhabricatorDashboardIconTransaction' => 'PhabricatorDashboardTransactionType', 'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardLayoutConfig' => 'Phobject', + 'PhabricatorDashboardInstallWorkflow' => 'Phobject', + 'PhabricatorDashboardLayoutMode' => 'Phobject', + 'PhabricatorDashboardLayoutTransaction' => 'PhabricatorDashboardTransactionType', 'PhabricatorDashboardListController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardManageController' => 'PhabricatorDashboardProfileController', - 'PhabricatorDashboardMovePanelController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardNgrams' => 'PhabricatorSearchNgrams', + 'PhabricatorDashboardNameTransaction' => 'PhabricatorDashboardTransactionType', + 'PhabricatorDashboardObjectInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow', + 'PhabricatorDashboardOneThirdLayoutMode' => 'PhabricatorDashboardLayoutMode', 'PhabricatorDashboardPanel' => array( 'PhabricatorDashboardDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', - 'PhabricatorCustomFieldInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', - 'PhabricatorNgramsInterface', + 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', + 'PhabricatorDashboardPanelContainerInterface', ), 'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardPanelCoreCustomField' => array( - 'PhabricatorDashboardPanelCustomField', - 'PhabricatorStandardCustomFieldInterface', - ), - 'PhabricatorDashboardPanelCustomField' => 'PhabricatorCustomField', + 'PhabricatorDashboardPanelContainerIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension', 'PhabricatorDashboardPanelDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorDashboardPanelEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelEditEngine' => 'PhabricatorEditEngine', - 'PhabricatorDashboardPanelEditproController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardPanelHasDashboardEdgeType' => 'PhabricatorEdgeType', + 'PhabricatorDashboardPanelFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorDashboardPanelFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardPanelNgrams' => 'PhabricatorSearchNgrams', + 'PhabricatorDashboardPanelNameTransaction' => 'PhabricatorDashboardPanelTransactionType', 'PhabricatorDashboardPanelPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorDashboardPanelPropertyTransaction' => 'PhabricatorDashboardPanelTransactionType', 'PhabricatorDashboardPanelQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorDashboardPanelRef' => 'Phobject', + 'PhabricatorDashboardPanelRefList' => 'Phobject', 'PhabricatorDashboardPanelRenderController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelRenderingEngine' => 'Phobject', - 'PhabricatorDashboardPanelSearchApplicationCustomField' => 'PhabricatorStandardCustomField', 'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorDashboardPanelSearchQueryCustomField' => 'PhabricatorStandardCustomField', - 'PhabricatorDashboardPanelTabsCustomField' => 'PhabricatorStandardCustomField', - 'PhabricatorDashboardPanelTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorDashboardPanelStatusTransaction' => 'PhabricatorDashboardPanelTransactionType', + 'PhabricatorDashboardPanelTabsController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardPanelTransaction' => 'PhabricatorModularTransaction', 'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorDashboardPanelTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorDashboardPanelType' => 'Phobject', + 'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardPanelsTransaction' => 'PhabricatorDashboardTransactionType', + 'PhabricatorDashboardPortal' => array( + 'PhabricatorDashboardDAO', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', + 'PhabricatorProjectInterface', + 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', + ), + 'PhabricatorDashboardPortalController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardPortalDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorDashboardPortalEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'PhabricatorDashboardPortalEditController' => 'PhabricatorDashboardPortalController', + 'PhabricatorDashboardPortalEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorDashboardPortalEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorDashboardPortalFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorDashboardPortalFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhabricatorDashboardPortalInstallWorkflow' => 'PhabricatorDashboardObjectInstallWorkflow', + 'PhabricatorDashboardPortalListController' => 'PhabricatorDashboardPortalController', + 'PhabricatorDashboardPortalMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorDashboardPortalNameTransaction' => 'PhabricatorDashboardPortalTransactionType', + 'PhabricatorDashboardPortalPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorDashboardPortalProfileMenuEngine' => 'PhabricatorProfileMenuEngine', + 'PhabricatorDashboardPortalQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorDashboardPortalSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhabricatorDashboardPortalSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorDashboardPortalStatus' => 'Phobject', + 'PhabricatorDashboardPortalTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorDashboardPortalTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorDashboardPortalTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorDashboardPortalViewController' => 'PhabricatorDashboardPortalController', 'PhabricatorDashboardProfileController' => 'PhabricatorController', 'PhabricatorDashboardProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorDashboardProjectInstallWorkflow' => 'PhabricatorDashboardObjectInstallWorkflow', 'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorDashboardQueryPanelApplicationEditField' => 'PhabricatorEditField', + 'PhabricatorDashboardQueryPanelApplicationTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 'PhabricatorDashboardQueryPanelInstallController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardQueryPanelQueryEditField' => 'PhabricatorEditField', + 'PhabricatorDashboardQueryPanelQueryTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 'PhabricatorDashboardQueryPanelType' => 'PhabricatorDashboardPanelType', 'PhabricatorDashboardRemarkupRule' => 'PhabricatorObjectRemarkupRule', - 'PhabricatorDashboardRemovePanelController' => 'PhabricatorDashboardController', 'PhabricatorDashboardRenderingEngine' => 'Phobject', 'PhabricatorDashboardSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorDashboardStatusTransaction' => 'PhabricatorDashboardTransactionType', + 'PhabricatorDashboardTabsPanelTabsTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 'PhabricatorDashboardTabsPanelType' => 'PhabricatorDashboardPanelType', + 'PhabricatorDashboardTextPanelTextTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 'PhabricatorDashboardTextPanelType' => 'PhabricatorDashboardPanelType', - 'PhabricatorDashboardTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorDashboardTransaction' => 'PhabricatorModularTransaction', 'PhabricatorDashboardTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorDashboardTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorDashboardTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorDashboardTwoThirdsLayoutMode' => 'PhabricatorDashboardLayoutMode', 'PhabricatorDashboardViewController' => 'PhabricatorDashboardProfileController', 'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec', 'PhabricatorDataNotAttachedException' => 'Exception', @@ -8975,6 +9092,7 @@ phutil_register_library_map(array( 'PhabricatorEdgeEditType' => 'PhabricatorPHIDListEditType', 'PhabricatorEdgeEditor' => 'Phobject', 'PhabricatorEdgeGraph' => 'AbstractDirectedGraph', + 'PhabricatorEdgeIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'PhabricatorEdgeObject' => array( 'Phobject', 'PhabricatorPolicyInterface', @@ -9714,6 +9832,7 @@ phutil_register_library_map(array( 'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorObjectSelectorDialog' => 'Phobject', 'PhabricatorObjectStatus' => 'Phobject', + 'PhabricatorObjectUsesDashboardPanelEdgeType' => 'PhabricatorEdgeType', 'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery', 'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource', 'PhabricatorOlderInlinesSetting' => 'PhabricatorSelectSetting', @@ -10130,17 +10249,22 @@ phutil_register_library_map(array( 'PhabricatorProfileMenuEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProfileMenuEngine' => 'Phobject', 'PhabricatorProfileMenuItem' => 'Phobject', + 'PhabricatorProfileMenuItemAffectsObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProfileMenuItemConfiguration' => array( 'PhabricatorSearchDAO', 'PhabricatorPolicyInterface', 'PhabricatorExtendedPolicyInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorIndexableInterface', ), 'PhabricatorProfileMenuItemConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorProfileMenuItemConfigurationTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProfileMenuItemConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorProfileMenuItemIconSet' => 'PhabricatorIconSet', + 'PhabricatorProfileMenuItemIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension', 'PhabricatorProfileMenuItemPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorProfileMenuItemView' => 'Phobject', + 'PhabricatorProfileMenuItemViewList' => 'Phobject', 'PhabricatorProject' => array( 'PhabricatorProjectDAO', 'PhabricatorApplicationTransactionInterface', @@ -10313,18 +10437,21 @@ phutil_register_library_map(array( 'PhabricatorIndexableInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorProjectTriggerAddProjectsRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerController' => 'PhabricatorProjectController', 'PhabricatorProjectTriggerCorruptionException' => 'Exception', 'PhabricatorProjectTriggerEditController' => 'PhabricatorProjectTriggerController', 'PhabricatorProjectTriggerEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTriggerInvalidRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerListController' => 'PhabricatorProjectTriggerController', + 'PhabricatorProjectTriggerManiphestOwnerRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerManiphestPriorityRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerManiphestStatusRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerNameTransaction' => 'PhabricatorProjectTriggerTransactionType', 'PhabricatorProjectTriggerPHIDType' => 'PhabricatorPHIDType', 'PhabricatorProjectTriggerPlaySoundRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorProjectTriggerRemoveProjectsRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerRule' => 'Phobject', 'PhabricatorProjectTriggerRuleRecord' => 'Phobject', 'PhabricatorProjectTriggerRulesetTransaction' => 'PhabricatorProjectTriggerTransactionType', @@ -10377,6 +10504,7 @@ phutil_register_library_map(array( ), 'PhabricatorQuickSearchEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', + 'PhabricatorRebuildIndexesWorker' => 'PhabricatorWorker', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php index f4b05e8adf..b6ba91e7cd 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthListController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php @@ -13,6 +13,7 @@ final class PhabricatorAuthListController $list = new PHUIObjectItemListView(); $can_manage = $this->hasApplicationCapability( AuthManageProvidersCapability::CAPABILITY); + $is_locked = PhabricatorEnv::getEnvConfig('auth.lock-config'); foreach ($configs as $config) { $item = new PHUIObjectItemView(); @@ -69,7 +70,8 @@ final class PhabricatorAuthListController $crumbs->addTextCrumb(pht('Login and Registration')); $crumbs->setBorder(true); - $guidance_context = new PhabricatorAuthProvidersGuidanceContext(); + $guidance_context = id(new PhabricatorAuthProvidersGuidanceContext()) + ->setCanManage($can_manage); $guidance = id(new PhabricatorGuidanceEngine()) ->setViewer($viewer) @@ -81,7 +83,7 @@ final class PhabricatorAuthListController ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setHref($this->getApplicationURI('config/new/')) ->setIcon('fa-plus') - ->setDisabled(!$can_manage) + ->setDisabled(!$can_manage || $is_locked) ->setText(pht('Add Provider')); $list->setFlush(true); diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 38ae2201b8..e4956335a0 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -389,7 +389,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { * appropriate for one-time checks. * * @param PhabricatorUser User whose session needs to be in high security. - * @param AphrontReqeust Current request. + * @param AphrontRequest Current request. * @param string URI to return the user to if they cancel. * @return PhabricatorAuthHighSecurityToken Security token. * @task hisec @@ -421,7 +421,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { * use @{method:requireHighSecurityToken}. * * @param PhabricatorUser User whose session needs to be in high security. - * @param AphrontReqeust Current request. + * @param AphrontRequest Current request. * @param string URI to return the user to if they cancel. * @param bool True to jump partial sessions directly into high * security instead of just upgrading them to full diff --git a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php index 1302846ec3..12c8300811 100644 --- a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php +++ b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php @@ -1,4 +1,17 @@ canManage = $can_manage; + return $this; + } + + public function getCanManage() { + return $this->canManage; + } + +} diff --git a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php index d1f67393ca..32482a582a 100644 --- a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php +++ b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php @@ -92,6 +92,25 @@ final class PhabricatorAuthProvidersGuidanceEngineExtension ->setMessage($message); } + $locked_config_key = 'auth.lock-config'; + $is_locked = PhabricatorEnv::getEnvConfig($locked_config_key); + if ($is_locked) { + $message = pht( + 'Authentication provider configuration is locked, and can not be '. + 'changed without being unlocked. See the configuration setting %s '. + 'for details.', + phutil_tag( + 'a', + array( + 'href' => '/config/edit/'.$locked_config_key, + ), + $locked_config_key)); + + $results[] = $this->newWarning('auth.locked-config') + ->setPriority(500) + ->setMessage($message); + } + return $results; } diff --git a/src/applications/auth/management/PhabricatorAuthManagementLockWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementLockWorkflow.php new file mode 100644 index 0000000000..e15069f775 --- /dev/null +++ b/src/applications/auth/management/PhabricatorAuthManagementLockWorkflow.php @@ -0,0 +1,32 @@ +setName('lock') + ->setExamples('**lock**') + ->setSynopsis( + pht( + 'Lock authentication provider config, to prevent changes to '. + 'the config without doing **bin/auth unlock**.')); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + $key = 'auth.lock-config'; + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); + $config_entry->setValue(true); + + // If the entry has been deleted, resurrect it. + $config_entry->setIsDeleted(0); + + $config_entry->save(); + + echo tsprintf( + "%s\n", + pht('Locked the authentication provider configuration.')); + } +} diff --git a/src/applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php new file mode 100644 index 0000000000..bcca83f65e --- /dev/null +++ b/src/applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php @@ -0,0 +1,33 @@ +setName('unlock') + ->setExamples('**unlock**') + ->setSynopsis( + pht( + 'Unlock the authentication provider config, to make it possible '. + 'to edit the config using the web UI. Make sure to do '. + '**bin/auth lock** when done editing the configuration.')); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + $key = 'auth.lock-config'; + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); + $config_entry->setValue(false); + + // If the entry has been deleted, resurrect it. + $config_entry->setIsDeleted(0); + + $config_entry->save(); + + echo tsprintf( + "%s\n", + pht('Unlocked the authentication provider configuration.')); + } +} diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 61f6176f15..8cbea36d07 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -238,6 +238,7 @@ final class CelerityDefaultPostprocessor 'grey.button.gradient' => 'linear-gradient(to bottom, #ffffff, #f1f0f1)', 'grey.button.hover' => 'linear-gradient(to bottom, #ffffff, #eeebec)', + 'document.border' => '#dedee1', ); } diff --git a/src/applications/config/check/PhabricatorAuthSetupCheck.php b/src/applications/config/check/PhabricatorAuthSetupCheck.php index 43fab77eb7..59bc17ecce 100644 --- a/src/applications/config/check/PhabricatorAuthSetupCheck.php +++ b/src/applications/config/check/PhabricatorAuthSetupCheck.php @@ -22,6 +22,7 @@ final class PhabricatorAuthSetupCheck extends PhabricatorSetupCheck { ->setViewer(PhabricatorUser::getOmnipotentUser()) ->execute(); + $did_warn = false; if (!$configs) { $message = pht( 'You have not configured any authentication providers yet. You '. @@ -35,6 +36,42 @@ final class PhabricatorAuthSetupCheck extends PhabricatorSetupCheck { ->setName(pht('No Authentication Providers Configured')) ->setMessage($message) ->addLink('/auth/', pht('Auth Application')); + + $did_warn = true; + } + + // This check is meant for new administrators, but we don't want to + // show both this warning and the "No Auth Providers" warning. Also, + // show this as a reminder to go back and do a `bin/auth lock` after + // they make their desired changes. + $is_locked = PhabricatorEnv::getEnvConfig('auth.lock-config'); + if (!$is_locked && !$did_warn) { + $message = pht( + 'Your authentication provider configuration is unlocked. Once you '. + 'finish setting up or modifying authentication, you should lock the '. + 'configuration to prevent unauthorized changes.'. + "\n\n". + 'Leaving your authentication provider configuration unlocked '. + 'increases the damage that a compromised administrator account can '. + 'do to your install, by, for example, changing the authentication '. + 'provider to a server they control and intercepting usernames and '. + 'passwords.'. + "\n\n". + 'To prevent this attack, you should configure your authentication '. + 'providers, and then lock the configuration by doing `%s` '. + 'from the command line. This will prevent changing the '. + 'authentication provider config without first doing `%s`.', + 'bin/auth lock', + 'bin/auth unlock'); + $this + ->newIssue('auth.config-unlocked') + ->setShortName(pht('Auth Config Unlocked')) + ->setName(pht('Authenticaton Provider Configuration Unlocked')) + ->setMessage($message) + ->addRelatedPhabricatorConfig('auth.lock-config') + ->addCommand( + hsprintf( + 'phabricator/ $ ./bin/auth lock')); } } } diff --git a/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php b/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php index 1440714bf7..8058ecafcb 100644 --- a/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php +++ b/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php @@ -73,6 +73,26 @@ final class PhabricatorAuthenticationConfigOptions ->addExample( "yourcompany.com\nmail.yourcompany.com", pht('Valid Setting')), + $this->newOption('auth.lock-config', 'bool', false) + ->setBoolOptions( + array( + pht('Auth provider config must be unlocked before editing'), + pht('Auth provider config can be edited without unlocking'), + )) + ->setSummary( + pht( + 'Require administrators to unlock the authentication provider '. + 'configuration from the CLI before it can be edited.')) + ->setDescription( + pht( + 'When set to `true`, the authentication provider configuration '. + 'for this instance can not be modified without first running '. + '`bin/auth unlock` from the command line. This is to reduce '. + 'the security impact of a compromised administrator account. '. + "\n\n". + 'After running `bin/auth unlock` and making your changes to the '. + 'authentication provider config, you should run `bin/auth lock`.')) + ->setLocked(true), $this->newOption('account.editable', 'bool', true) ->setBoolOptions( array( diff --git a/src/applications/config/option/PhabricatorSecurityConfigOptions.php b/src/applications/config/option/PhabricatorSecurityConfigOptions.php index 50fc24a85d..d5557e3688 100644 --- a/src/applications/config/option/PhabricatorSecurityConfigOptions.php +++ b/src/applications/config/option/PhabricatorSecurityConfigOptions.php @@ -163,14 +163,26 @@ EOTEXT 'mailto' => true, )) ->setSummary( - pht('Determines which URI protocols are auto-linked.')) + pht( + 'Determines which URI protocols are valid for links and '. + 'redirects.')) ->setDescription( pht( - "When users write comments which have URIs, they'll be ". - "automatically linked if the protocol appears in this set. This ". - "whitelist is primarily to prevent security issues like ". - "%s URIs.", - 'javascript://')) + 'When users write comments which have URIs, they will be '. + 'automatically turned into clickable links if the URI protocol '. + 'appears in this set.'. + "\n\n". + 'This set of allowed protocols is primarily intended to prevent '. + 'security issues with "javascript:" and other potentially '. + 'dangerous URI handlers.'. + "\n\n". + 'This set is also used to enforce valid redirect URIs. '. + 'Phabricator will refuse to issue a HTTP "Location" redirect to a '. + 'URI with a protocol not on this set.'. + "\n\n". + 'Usually, "http" and "https" should be present in this set. If '. + 'you remove one or both protocols, some Phabricator features '. + 'which rely on links or redirects may not work.')) ->addExample("http\nhttps", pht('Valid Setting')) ->setLocked(true), $this->newOption( diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 9c6682a8a7..99fd18878e 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -136,7 +136,7 @@ final class ConpherenceThreadQuery protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { if ($this->participantPHIDs !== null || strlen($this->fulltext)) { - return 'GROUP BY thread.id'; + return qsprintf($conn_r, 'GROUP BY thread.id'); } else { return $this->buildApplicationSearchGroupClause($conn_r); } @@ -192,18 +192,24 @@ final class ConpherenceThreadQuery if ($can_optimize) { $members_policy = id(new ConpherenceThreadMembersPolicyRule()) ->getObjectPolicyFullKey(); + $policies = array( + $members_policy, + PhabricatorPolicies::POLICY_USER, + PhabricatorPolicies::POLICY_ADMIN, + PhabricatorPolicies::POLICY_NOONE, + ); if ($viewer->isLoggedIn()) { $where[] = qsprintf( $conn, - 'thread.viewPolicy != %s OR vp.participantPHID = %s', - $members_policy, + 'thread.viewPolicy NOT IN (%Ls) OR vp.participantPHID = %s', + $policies, $viewer->getPHID()); } else { $where[] = qsprintf( $conn, - 'thread.viewPolicy != %s', - $members_policy); + 'thread.viewPolicy NOT IN (%Ls)', + $policies); } } diff --git a/src/applications/conpherence/view/ConpherenceLayoutView.php b/src/applications/conpherence/view/ConpherenceLayoutView.php index 7dbc00a325..3382a9ba87 100644 --- a/src/applications/conpherence/view/ConpherenceLayoutView.php +++ b/src/applications/conpherence/view/ConpherenceLayoutView.php @@ -224,12 +224,12 @@ final class ConpherenceLayoutView extends AphrontTagView { private function buildNUXView() { $viewer = $this->getViewer(); - $engine = new ConpherenceThreadSearchEngine(); - $engine->setViewer($viewer); + $engine = id(new ConpherenceThreadSearchEngine()) + ->setViewer($viewer); $saved = $engine->buildSavedQueryFromBuiltin('all'); $query = $engine->buildQueryFromSavedQuery($saved); - $pager = $engine->newPagerForSavedQuery($saved); - $pager->setPageSize(10); + $pager = $engine->newPagerForSavedQuery($saved) + ->setPageSize(10); $results = $engine->executeQuery($query, $pager); $view = $engine->renderResults($results, $saved); diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php index d8e2701727..9beac5ae53 100644 --- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php +++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php @@ -10,6 +10,10 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { return '/dashboard/'; } + public function getTypeaheadURI() { + return '/dashboard/console/'; + } + public function getShortDescription() { return pht('Create Custom Pages'); } @@ -27,6 +31,9 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { } public function getRoutes() { + $menu_rules = $this->getProfileMenuRouting( + 'PhabricatorDashboardPortalViewController'); + return array( '/W(?P\d+)' => 'PhabricatorDashboardPanelViewController', '/dashboard/' => array( @@ -34,29 +41,39 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { => 'PhabricatorDashboardListController', 'view/(?P\d+)/' => 'PhabricatorDashboardViewController', 'archive/(?P\d+)/' => 'PhabricatorDashboardArchiveController', - 'manage/(?P\d+)/' => 'PhabricatorDashboardManageController', - 'arrange/(?P\d+)/' => 'PhabricatorDashboardArrangeController', - 'create/' => 'PhabricatorDashboardEditController', - 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardEditController', - 'install/(?:(?P\d+)/)?' => 'PhabricatorDashboardInstallController', - 'addpanel/(?P\d+)/' => 'PhabricatorDashboardAddPanelController', - 'movepanel/(?P\d+)/' => 'PhabricatorDashboardMovePanelController', - 'removepanel/(?P\d+)/' - => 'PhabricatorDashboardRemovePanelController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorDashboardEditController', + 'install/(?P\d+)/'. + '(?:(?P[^/]+)/'. + '(?:(?P[^/]+)/)?)?' => + 'PhabricatorDashboardInstallController', + 'console/' => 'PhabricatorDashboardConsoleController', + 'adjust/(?Premove|add|move)/' + => 'PhabricatorDashboardAdjustController', 'panel/' => array( 'install/(?P[^/]+)/(?:(?P[^/]+)/)?' => 'PhabricatorDashboardQueryPanelInstallController', '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardPanelListController', - 'create/' => 'PhabricatorDashboardPanelEditController', - $this->getEditRoutePattern('editpro/') - => 'PhabricatorDashboardPanelEditproController', - 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardPanelEditController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorDashboardPanelEditController', 'render/(?P\d+)/' => 'PhabricatorDashboardPanelRenderController', 'archive/(?P\d+)/' => 'PhabricatorDashboardPanelArchiveController', + 'tabs/(?P\d+)/(?Padd|move|remove|rename)/' + => 'PhabricatorDashboardPanelTabsController', ), ), + '/portal/' => array( + $this->getQueryRoutePattern() => + 'PhabricatorDashboardPortalListController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorDashboardPortalEditController', + 'view/(?P\d)/' => array( + '' => 'PhabricatorDashboardPortalViewController', + ) + $menu_rules, + + ), ); } diff --git a/src/applications/dashboard/conduit/PhabricatorDashboardPortalEditConduitAPIMethod.php b/src/applications/dashboard/conduit/PhabricatorDashboardPortalEditConduitAPIMethod.php new file mode 100644 index 0000000000..489bb21cab --- /dev/null +++ b/src/applications/dashboard/conduit/PhabricatorDashboardPortalEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - $redirect_uri = $this->getApplicationURI( - 'arrange/'.$dashboard->getID().'/'); - - $v_panel = head($request->getArr('panel')); - $e_panel = true; - $errors = array(); - if ($request->isFormPost()) { - if (strlen($v_panel)) { - $panel = id(new PhabricatorDashboardPanelQuery()) - ->setViewer($viewer) - ->withIDs(array($v_panel)) - ->executeOne(); - if (!$panel) { - $errors[] = pht('Not a valid panel.'); - $e_panel = pht('Invalid'); - } - - $on_dashboard = $dashboard->getPanels(); - $on_ids = mpull($on_dashboard, null, 'getID'); - if (array_key_exists($v_panel, $on_ids)) { - $p_name = $panel->getName(); - $errors[] = pht('Panel "%s" already exists on dashboard.', $p_name); - $e_panel = pht('Invalid'); - } - - } else { - $errors[] = pht('Select a panel to add.'); - $e_panel = pht('Required'); - } - - if (!$errors) { - PhabricatorDashboardTransactionEditor::addPanelToDashboard( - $viewer, - PhabricatorContentSource::newFromRequest($request), - $panel, - $dashboard, - $request->getInt('column', 0)); - - return id(new AphrontRedirectResponse())->setURI($redirect_uri); - } - } - - $panels = id(new PhabricatorDashboardPanelQuery()) - ->setViewer($viewer) - ->withArchived(false) - ->execute(); - - if (!$panels) { - return $this->newDialog() - ->setTitle(pht('No Panels Exist Yet')) - ->appendParagraph( - pht( - 'You have not created any dashboard panels yet, so you can not '. - 'add an existing panel.')) - ->appendParagraph( - pht('Instead, add a new panel.')) - ->addCancelButton($redirect_uri); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('column', $request->getInt('column')) - ->appendRemarkupInstructions( - pht('Choose a panel to add to this dashboard:')) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setUser($this->getViewer()) - ->setDatasource(new PhabricatorDashboardPanelDatasource()) - ->setLimit(1) - ->setName('panel') - ->setLabel(pht('Panel'))); - - return $this->newDialog() - ->setTitle(pht('Add Panel')) - ->setErrors($errors) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($redirect_uri) - ->addSubmitButton(pht('Add Panel')); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php b/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php deleted file mode 100644 index 1ef482b7f4..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php +++ /dev/null @@ -1,79 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $this->setDashboard($dashboard); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $dashboard, - PhabricatorPolicyCapability::CAN_EDIT); - - $title = $dashboard->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Arrange')); - $header = $this->buildHeaderView(); - - $info_view = null; - if (!$can_edit) { - $no_edit = pht( - 'You do not have permission to edit this dashboard.'); - - $info_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->setErrors(array($no_edit)); - } - - $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) - ->setViewer($viewer) - ->setDashboard($dashboard) - ->setArrangeMode($can_edit) - ->renderDashboard(); - - $dashboard_box = id(new PHUIBoxView()) - ->addClass('dashboard-preview-box') - ->appendChild($rendered_dashboard); - - $install_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText('Install Dashboard') - ->setIcon('fa-plus') - ->setWorkflow(true) - ->setHref($this->getApplicationURI("/install/{$id}/")); - $header->addActionLink($install_button); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $info_view, - $dashboard_box, - )); - - $navigation = $this->buildSideNavView('arrange'); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setNavigation($navigation) - ->appendChild($view); - - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardConsoleController.php b/src/applications/dashboard/controller/PhabricatorDashboardConsoleController.php new file mode 100644 index 0000000000..1caceec1e4 --- /dev/null +++ b/src/applications/dashboard/controller/PhabricatorDashboardConsoleController.php @@ -0,0 +1,72 @@ +getViewer(); + + $menu = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setBig(true); + + $menu->addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Portals')) + ->setImageIcon('fa-compass') + ->setHref('/portal/') + ->setClickable(true) + ->addAttribute( + pht( + 'Portals are collections of dashboards, links, and other '. + 'resources that can provide a high-level overview of a '. + 'project.'))); + + $menu->addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Dashboards')) + ->setImageIcon('fa-dashboard') + ->setHref($this->getApplicationURI('/')) + ->setClickable(true) + ->addAttribute( + pht( + 'Dashboards organize panels, creating a cohesive page for '. + 'analysis or action.'))); + + $menu->addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Panels')) + ->setImageIcon('fa-line-chart') + ->setHref($this->getApplicationURI('panel/')) + ->setClickable(true) + ->addAttribute( + pht( + 'Panels show queries, charts, and other information to provide '. + 'insight on a particular topic.'))); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Console')); + $crumbs->setBorder(true); + + $title = pht('Dashboard Console'); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) + ->setObjectList($menu); + + $view = id(new PHUITwoColumnView()) + ->setFixed(true) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php deleted file mode 100644 index 6b3a96d80c..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php +++ /dev/null @@ -1,382 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $dashboard->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - $is_new = false; - } else { - if (!$request->getStr('edit')) { - if ($request->isFormPost()) { - switch ($request->getStr('template')) { - case 'empty': - break; - default: - return $this->processBuildTemplateRequest($request); - } - } else { - return $this->processTemplateRequest($request); - } - } - - $dashboard = PhabricatorDashboard::initializeNewDashboard($viewer); - $v_projects = array(); - $is_new = true; - } - - $crumbs = $this->buildApplicationCrumbs(); - - if ($is_new) { - $title = pht('Create Dashboard'); - $header_icon = 'fa-plus-square'; - $button = pht('Create Dashboard'); - $cancel_uri = $this->getApplicationURI(); - - $crumbs->addTextCrumb(pht('Create Dashboard')); - } else { - $id = $dashboard->getID(); - $cancel_uri = $this->getApplicationURI('manage/'.$id.'/'); - - $title = pht('Edit Dashboard: %s', $dashboard->getName()); - $header_icon = 'fa-pencil'; - $button = pht('Save Changes'); - - $crumbs->addTextCrumb($dashboard->getName(), $cancel_uri); - $crumbs->addTextCrumb(pht('Edit')); - } - - $v_name = $dashboard->getName(); - $v_icon = $dashboard->getIcon(); - $v_layout_mode = $dashboard->getLayoutConfigObject()->getLayoutMode(); - $e_name = true; - - $validation_exception = null; - if ($request->isFormPost() && $request->getStr('edit')) { - $v_name = $request->getStr('name'); - $v_icon = $request->getStr('icon'); - $v_layout_mode = $request->getStr('layout_mode'); - $v_view_policy = $request->getStr('viewPolicy'); - $v_edit_policy = $request->getStr('editPolicy'); - $v_projects = $request->getArr('projects'); - - $xactions = array(); - - $type_name = PhabricatorDashboardTransaction::TYPE_NAME; - $type_icon = PhabricatorDashboardTransaction::TYPE_ICON; - $type_layout_mode = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE; - $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_layout_mode) - ->setNewValue($v_layout_mode); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_icon) - ->setNewValue($v_icon); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_view_policy) - ->setNewValue($v_view_policy); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_edit_policy) - ->setNewValue($v_edit_policy); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - try { - $editor = id(new PhabricatorDashboardTransactionEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->applyTransactions($dashboard, $xactions); - - $uri = $this->getApplicationURI('arrange/'.$dashboard->getID().'/'); - - return id(new AphrontRedirectResponse())->setURI($uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $validation_exception->getShortMessage($type_name); - - $dashboard->setViewPolicy($v_view_policy); - $dashboard->setEditPolicy($v_edit_policy); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($dashboard) - ->execute(); - - $layout_mode_options = - PhabricatorDashboardLayoutConfig::getLayoutModeSelectOptions(); - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('edit', true) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Layout Mode')) - ->setName('layout_mode') - ->setValue($v_layout_mode) - ->setOptions($layout_mode_options)) - ->appendChild( - id(new PHUIFormIconSetControl()) - ->setLabel(pht('Icon')) - ->setName('icon') - ->setIconSet(new PhabricatorDashboardIconSet()) - ->setValue($v_icon)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($dashboard) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($dashboard) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)); - - $form->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())); - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($button) - ->addCancelButton($cancel_uri)); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setForm($form) - ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) - ->setValidationException($validation_exception); - - $crumbs->setBorder(true); - - $view = id(new PHUITwoColumnView()) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function processTemplateRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - - $template_control = id(new AphrontFormRadioButtonControl()) - ->setName(pht('template')) - ->setValue($request->getStr('template', 'empty')) - ->addButton( - 'empty', - pht('Empty'), - pht('Start with a blank canvas.')) - ->addButton( - 'simple', - pht('Simple Template'), - pht( - 'Start with a simple dashboard with a welcome message, a feed of '. - 'recent events, and a few starter panels.')); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht('Choose a dashboard template to start with.')) - ->appendChild($template_control); - - return $this->newDialog() - ->setTitle(pht('Create Dashboard')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form->buildLayoutView()) - ->addCancelButton('/dashboard/') - ->addSubmitButton(pht('Continue')); - } - - private function processBuildTemplateRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $template = $request->getStr('template'); - - $bare_panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); - $panel_phids = array(); - - switch ($template) { - case 'simple': - $v_name = pht("%s's Dashboard", $viewer->getUsername()); - - $welcome_panel = $this->newPanel( - $request, - $viewer, - 'text', - pht('Welcome'), - array( - 'text' => pht( - "This is a simple template dashboard. You can edit this panel ". - "to change this text and replace it with a welcome message, or ". - "leave this placeholder text as-is to give your dashboard a ". - "rustic, authentic feel.\n\n". - "You can drag, remove, add, and edit panels to customize the ". - "rest of this dashboard to show the information you want.\n\n". - "To install this dashboard on the home page, edit your personal ". - "or global menu on the homepage and click Dashboard under ". - "New Menu Item on the right."), - )); - $panel_phids[] = $welcome_panel->getPHID(); - - $feed_panel = $this->newPanel( - $request, - $viewer, - 'query', - pht('Recent Activity'), - array( - 'class' => 'PhabricatorFeedSearchEngine', - 'key' => 'all', - )); - $panel_phids[] = $feed_panel->getPHID(); - - $revision_panel = $this->newPanel( - $request, - $viewer, - 'query', - pht('Active Revisions'), - array( - 'class' => 'DifferentialRevisionSearchEngine', - 'key' => 'active', - )); - $panel_phids[] = $revision_panel->getPHID(); - - $task_panel = $this->newPanel( - $request, - $viewer, - 'query', - pht('Assigned Tasks'), - array( - 'class' => 'ManiphestTaskSearchEngine', - 'key' => 'assigned', - )); - $panel_phids[] = $task_panel->getPHID(); - - $commit_panel = $this->newPanel( - $request, - $viewer, - 'query', - pht('Recent Commits'), - array( - 'class' => 'PhabricatorCommitSearchEngine', - 'key' => 'all', - )); - $panel_phids[] = $commit_panel->getPHID(); - - $mode_2_and_1 = PhabricatorDashboardLayoutConfig::MODE_THIRDS_AND_THIRD; - $layout = id(new PhabricatorDashboardLayoutConfig()) - ->setLayoutMode($mode_2_and_1) - ->setPanelLocation(0, $welcome_panel->getPHID()) - ->setPanelLocation(0, $revision_panel->getPHID()) - ->setPanelLocation(0, $task_panel->getPHID()) - ->setPanelLocation(0, $commit_panel->getPHID()) - ->setPanelLocation(1, $feed_panel->getPHID()); - - break; - default: - throw new Exception(pht('Unknown dashboard template %s!', $template)); - } - - // Create the dashboard. - - $dashboard = PhabricatorDashboard::initializeNewDashboard($viewer) - ->setLayoutConfigFromObject($layout); - - $xactions = array(); - - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorDashboardTransaction::TYPE_NAME) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST) - ->setNewValue( - array( - '+' => array_fuse($panel_phids), - )); - - $editor = id(new PhabricatorDashboardTransactionEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->applyTransactions($dashboard, $xactions); - - $manage_uri = $this->getApplicationURI('arrange/'.$dashboard->getID().'/'); - - return id(new AphrontRedirectResponse()) - ->setURI($manage_uri); - } - - private function newPanel( - AphrontRequest $request, - PhabricatorUser $viewer, - $type, - $name, - array $properties) { - - $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer) - ->setPanelType($type) - ->setProperties($properties); - - $xactions = array(); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME) - ->setNewValue($name); - - $editor = id(new PhabricatorDashboardPanelTransactionEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->applyTransactions($panel, $xactions); - - return $panel; - } - - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardInstallController.php b/src/applications/dashboard/controller/PhabricatorDashboardInstallController.php deleted file mode 100644 index 145c6f530e..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardInstallController.php +++ /dev/null @@ -1,141 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - $cancel_uri = $this->getApplicationURI( - 'view/'.$dashboard->getID().'/'); - - $home_app = new PhabricatorHomeApplication(); - - $options = array(); - $options['home'] = array( - 'personal' => - array( - 'capability' => PhabricatorPolicyCapability::CAN_VIEW, - 'application' => $home_app, - 'name' => pht('Personal Dashboard'), - 'value' => 'personal', - 'description' => pht('Places this dashboard as a menu item on home '. - 'as a personal menu item. It will only be on your personal '. - 'home.'), - ), - 'global' => - array( - 'capability' => PhabricatorPolicyCapability::CAN_EDIT, - 'application' => $home_app, - 'name' => pht('Global Dashboard'), - 'value' => 'global', - 'description' => pht('Places this dashboard as a menu item on home '. - 'as a global menu item. It will be available to all users.'), - ), - ); - - - $errors = array(); - $v_name = null; - if ($request->isFormPost()) { - $menuitem = new PhabricatorDashboardProfileMenuItem(); - $dashboard_phid = $dashboard->getPHID(); - $home = new PhabricatorHomeApplication(); - $v_name = $request->getStr('name'); - $v_home = $request->getStr('home'); - - if ($v_home) { - $application = $options['home'][$v_home]['application']; - $capability = $options['home'][$v_home]['capability']; - - $can_edit_home = PhabricatorPolicyFilter::hasCapability( - $viewer, - $application, - $capability); - - if (!$can_edit_home) { - $errors[] = pht( - 'You do not have permission to install a dashboard on home.'); - } - } else { - $errors[] = pht( - 'You must select a destination to install this dashboard.'); - } - - $v_phid = $viewer->getPHID(); - if ($v_home == 'global') { - $v_phid = null; - } - - if (!$errors) { - $install = PhabricatorProfileMenuItemConfiguration::initializeNewItem( - $home, - $menuitem, - $v_phid); - - $install->setMenuItemProperty('dashboardPHID', $dashboard_phid); - $install->setMenuItemProperty('name', $v_name); - $install->setMenuItemOrder(1); - - $xactions = array(); - - $editor = id(new PhabricatorProfileMenuEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true) - ->setContentSourceFromRequest($request); - - $editor->applyTransactions($install, $xactions); - - $view_uri = '/home/menu/view/'.$install->getID().'/'; - - return id(new AphrontRedirectResponse())->setURI($view_uri); - } - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Menu Label')) - ->setName('name') - ->setValue($v_name)); - - $radio = id(new AphrontFormRadioButtonControl()) - ->setLabel(pht('Home Menu')) - ->setName('home'); - - foreach ($options['home'] as $type => $option) { - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $option['application'], - $option['capability']); - if ($can_edit) { - $radio->addButton( - $option['value'], - $option['name'], - $option['description']); - } - } - - $form->appendChild($radio); - - return $this->newDialog() - ->setTitle(pht('Install Dashboard')) - ->setErrors($errors) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($cancel_uri) - ->addSubmitButton(pht('Install Dashboard')); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardListController.php b/src/applications/dashboard/controller/PhabricatorDashboardListController.php index 60e6d83ab4..de33056115 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardListController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardListController.php @@ -28,9 +28,6 @@ final class PhabricatorDashboardListController ->setViewer($user) ->addNavigationItems($nav->getMenu()); - $nav->addLabel(pht('Panels')); - $nav->addFilter('panel/', pht('Manage Panels')); - $nav->selectFilter(null); return $nav; @@ -39,11 +36,9 @@ final class PhabricatorDashboardListController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-plus-square') - ->setName(pht('Create Dashboard')) - ->setHref($this->getApplicationURI().'create/')); + id(new PhabricatorDashboardEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardManageController.php b/src/applications/dashboard/controller/PhabricatorDashboardManageController.php deleted file mode 100644 index 39a7e46841..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardManageController.php +++ /dev/null @@ -1,139 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - // TODO: This UI should drop a lot of capabilities if the user can't - // edit the dashboard, but we should still let them in for "Install" and - // "View History". - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $this->setDashboard($dashboard); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $dashboard, - PhabricatorPolicyCapability::CAN_EDIT); - - $title = $dashboard->getName(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Manage')); - - $header = $this->buildHeaderView(); - $curtain = $this->buildCurtainView($dashboard); - $properties = $this->buildPropertyView($dashboard); - - $timeline = $this->buildTransactionTimeline( - $dashboard, - new PhabricatorDashboardTransactionQuery()); - $timeline->setShouldTerminate(true); - - $info_view = null; - if (!$can_edit) { - $no_edit = pht( - 'You do not have permission to edit this dashboard.'); - - $info_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->setErrors(array($no_edit)); - } - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $info_view, - $properties, - $timeline, - )); - - $navigation = $this->buildSideNavView('manage'); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setNavigation($navigation) - ->appendChild($view); - - } - - private function buildCurtainView(PhabricatorDashboard $dashboard) { - $viewer = $this->getViewer(); - $id = $dashboard->getID(); - - $curtain = $this->newCurtainView($dashboard); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $dashboard, - PhabricatorPolicyCapability::CAN_EDIT); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Dashboard')) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI("edit/{$id}/")) - ->setDisabled(!$can_edit)); - - if ($dashboard->isArchived()) { - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Activate Dashboard')) - ->setIcon('fa-check') - ->setHref($this->getApplicationURI("archive/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit)); - } else { - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Archive Dashboard')) - ->setIcon('fa-ban') - ->setHref($this->getApplicationURI("archive/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit)); - } - - return $curtain; - } - - private function buildPropertyView(PhabricatorDashboard $dashboard) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $dashboard); - - $properties->addProperty( - pht('Editable By'), - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); - - $properties->addProperty( - pht('Panels'), - $viewer->renderHandleList($dashboard->getPanelPHIDs())); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php deleted file mode 100644 index d1962517f9..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php +++ /dev/null @@ -1,71 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $column_id = $request->getStr('columnID'); - $panel_phid = $request->getStr('objectPHID'); - $after_phid = $request->getStr('afterPHID'); - $before_phid = $request->getStr('beforePHID'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $panels = mpull($dashboard->getPanels(), null, 'getPHID'); - $panel = idx($panels, $panel_phid); - if (!$panel) { - return new Aphront404Response(); - } - - $layout_config = $dashboard->getLayoutConfigObject(); - $layout_config->removePanel($panel_phid); - $panel_location_grid = $layout_config->getPanelLocations(); - - $column_phids = idx($panel_location_grid, $column_id, array()); - $column_phids = array_values($column_phids); - if ($column_phids) { - $insert_at = 0; - foreach ($column_phids as $index => $phid) { - if ($phid === $before_phid) { - $insert_at = $index; - break; - } - if ($phid === $after_phid) { - $insert_at = $index + 1; - break; - } - } - - $new_column_phids = $column_phids; - array_splice( - $new_column_phids, - $insert_at, - 0, - array($panel_phid)); - } else { - $new_column_phids = array(0 => $panel_phid); - } - - $panel_location_grid[$column_id] = $new_column_phids; - $layout_config->setPanelLocations($panel_location_grid); - $dashboard->setLayoutConfigFromObject($layout_config); - $dashboard->save(); - - return id(new AphrontAjaxResponse())->setContent(''); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php deleted file mode 100644 index 7aece8c603..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php +++ /dev/null @@ -1,354 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - // If the user is trying to create a panel directly on a dashboard, make - // sure they have permission to see and edit the dashboard. - - $dashboard_id = $request->getInt('dashboardID'); - $dashboard = null; - if ($dashboard_id) { - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($dashboard_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - $manage_uri = $this->getApplicationURI('arrange/'.$dashboard_id.'/'); - } - - if ($id) { - $is_create = false; - - if ($dashboard) { - $capabilities = array( - PhabricatorPolicyCapability::CAN_VIEW, - ); - } else { - $capabilities = array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - ); - } - - $panel = id(new PhabricatorDashboardPanelQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities($capabilities) - ->executeOne(); - if (!$panel) { - return new Aphront404Response(); - } - - } else { - $is_create = true; - - $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); - $types = PhabricatorDashboardPanelType::getAllPanelTypes(); - $type = $request->getStr('type'); - if (empty($types[$type])) { - return $this->processPanelTypeRequest($request); - } - - $panel->setPanelType($type); - } - - if ($is_create) { - $title = pht('Create New Panel'); - $button = pht('Create Panel'); - $header_icon = 'fa-plus-square'; - if ($dashboard) { - $cancel_uri = $manage_uri; - } else { - $cancel_uri = $this->getApplicationURI('panel/'); - } - } else { - $title = pht('Edit Panel: %s', $panel->getName()); - $button = pht('Save Panel'); - $header_icon = 'fa-pencil'; - if ($dashboard) { - $cancel_uri = $manage_uri; - } else { - $cancel_uri = '/'.$panel->getMonogram(); - } - } - - $v_name = $panel->getName(); - $e_name = true; - - $field_list = PhabricatorCustomField::getObjectFields( - $panel, - PhabricatorCustomField::ROLE_EDIT); - $field_list - ->setViewer($viewer) - ->readFieldsFromStorage($panel); - - if ($is_create && !$request->isFormPost()) { - $panel->requireImplementation()->initializeFieldsFromRequest( - $panel, - $field_list, - $request); - } - - $validation_exception = null; - - // NOTE: We require 'edit' to distinguish between the "Choose a Type" - // and "Create a Panel" dialogs. - - if ($request->isFormPost() && $request->getBool('edit')) { - $v_name = $request->getStr('name'); - $v_view_policy = $request->getStr('viewPolicy'); - $v_edit_policy = $request->getStr('editPolicy'); - - $type_name = PhabricatorDashboardPanelTransaction::TYPE_NAME; - $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType($type_view_policy) - ->setNewValue($v_view_policy); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType($type_edit_policy) - ->setNewValue($v_edit_policy); - - $field_xactions = $field_list->buildFieldTransactionsFromRequest( - new PhabricatorDashboardPanelTransaction(), - $request); - $xactions = array_merge($xactions, $field_xactions); - - try { - $editor = id(new PhabricatorDashboardPanelTransactionEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->applyTransactions($panel, $xactions); - - // If we're creating a panel directly on a dashboard, add it now. - if ($dashboard && $is_create) { - PhabricatorDashboardTransactionEditor::addPanelToDashboard( - $viewer, - PhabricatorContentSource::newFromRequest($request), - $panel, - $dashboard, - $request->getInt('column', 0)); - } - - if ($dashboard) { - $done_uri = $manage_uri; - } else { - $done_uri = '/'.$panel->getMonogram(); - } - - return id(new AphrontRedirectResponse())->setURI($done_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $validation_exception->getShortMessage($type_name); - - $panel->setViewPolicy($v_view_policy); - $panel->setEditPolicy($v_edit_policy); - } - } - - // NOTE: We're setting the submit URI explicitly because we need to edit - // a different panel if we just cloned the original panel. - if ($is_create) { - $submit_uri = $this->getApplicationURI('panel/edit/'); - } else { - $submit_uri = $this->getApplicationURI('panel/edit/'.$panel->getID().'/'); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($panel) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->setAction($submit_uri) - ->addHiddenInput('edit', true) - ->addHiddenInput('dashboardID', $request->getInt('dashboardID')) - ->addHiddenInput('column', $request->getInt('column')) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)); - - if (!$request->isAjax() || !$is_create) { - $form - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($panel) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($panel) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)); - } - - $field_list->appendFieldsToForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Panels'), - $this->getApplicationURI('panel/')); - if ($is_create) { - $crumbs->addTextCrumb(pht('New Panel')); - $form->addHiddenInput('type', $panel->getPanelType()); - } else { - $crumbs->addTextCrumb( - $panel->getMonogram(), - '/'.$panel->getMonogram()); - $crumbs->addTextCrumb(pht('Edit')); - } - $crumbs->setBorder(true); - - if ($request->isAjax()) { - return $this->newDialog() - ->setTitle($title) - ->setSubmitURI($submit_uri) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->setValidationException($validation_exception) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($cancel_uri) - ->addSubmitButton($button); - } else { - $form - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($button) - ->addCancelButton($cancel_uri)); - } - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Panel')) - ->setValidationException($validation_exception) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function processPanelTypeRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - - $types = PhabricatorDashboardPanelType::getAllPanelTypes(); - - $v_type = null; - $errors = array(); - if ($request->isFormPost()) { - $v_type = $request->getStr('type'); - if (!isset($types[$v_type])) { - $errors[] = pht('You must select a type of panel to create.'); - } - } - - $cancel_uri = $this->getApplicationURI('panel/'); - - if (!$v_type) { - $v_type = key($types); - } - - $panel_types = id(new AphrontFormRadioButtonControl()) - ->setName('type') - ->setValue($v_type); - - foreach ($types as $key => $type) { - $panel_types->addButton( - $key, - $type->getPanelTypeName(), - $type->getPanelTypeDescription()); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('dashboardID', $request->getInt('dashboardID')) - ->addHiddenInput('column', $request->getInt('column')) - ->appendRemarkupInstructions( - pht( - 'Choose the type of dashboard panel to create:')) - ->appendChild($panel_types); - - if ($request->isAjax()) { - return $this->newDialog() - ->setTitle(pht('Add New Panel')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->setErrors($errors) - ->appendChild($form->buildLayoutView()) - ->addCancelbutton($cancel_uri) - ->addSubmitButton(pht('Continue')); - } else { - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Continue')) - ->addCancelButton($cancel_uri)); - } - - $title = pht('Create Dashboard Panel'); - $header_icon = 'fa-plus-square'; - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Panels'), - $this->getApplicationURI('panel/')); - $crumbs->addTextCrumb(pht('New Panel')); - $crumbs->setBorder(true); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Panel')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php deleted file mode 100644 index b40dfd85b3..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php +++ /dev/null @@ -1,105 +0,0 @@ -setController($this); - - $id = $request->getURIData('id'); - if (!$id) { - $list_uri = $this->getApplicationURI('panel/'); - - $panel_type = $request->getStr('panelType'); - $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); - if (empty($panel_types[$panel_type])) { - return $this->buildPanelTypeResponse($list_uri); - } - - $engine - ->addContextParameter('panelType', $panel_type) - ->setPanelType($panel_type); - } - - return $engine->buildResponse(); - } - - private function buildPanelTypeResponse($cancel_uri) { - $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); - - $viewer = $this->getViewer(); - $request = $this->getRequest(); - - $e_type = null; - $errors = array(); - if ($request->isFormPost()) { - $e_type = pht('Required'); - $errors[] = pht( - 'To create a new dashboard panel, you must select a panel type.'); - } - - $type_control = id(new AphrontFormRadioButtonControl()) - ->setLabel(pht('Panel Type')) - ->setName('panelType') - ->setError($e_type); - - foreach ($panel_types as $key => $type) { - $type_control->addButton( - $key, - $type->getPanelTypeName(), - $type->getPanelTypeDescription()); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht('Choose the type of dashboard panel to create:')) - ->appendChild($type_control); - - if ($request->isAjax()) { - return $this->newDialog() - ->setTitle(pht('Add New Panel')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->setErrors($errors) - ->appendForm($form) - ->addCancelButton($cancel_uri) - ->addSubmitButton(pht('Continue')); - } - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Continue')) - ->addCancelButton($cancel_uri)); - - $title = pht('Create Dashboard Panel'); - $header_icon = 'fa-plus-square'; - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Panels'), - $this->getApplicationURI('panel/')); - $crumbs->addTextCrumb(pht('New Panel')); - $crumbs->setBorder(true); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Panel')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardProfileController.php b/src/applications/dashboard/controller/PhabricatorDashboardProfileController.php index adc85efdf3..ec441658c2 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardProfileController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardProfileController.php @@ -14,10 +14,6 @@ abstract class PhabricatorDashboardProfileController return $this->dashboard; } - public function buildApplicationMenu() { - return $this->buildSideNavView()->getMenu(); - } - protected function buildHeaderView() { $viewer = $this->getViewer(); $dashboard = $this->getDashboard(); @@ -49,50 +45,10 @@ abstract class PhabricatorDashboardProfileController $dashboard = $this->getDashboard(); if ($dashboard) { - $id = $dashboard->getID(); - $dashboard_uri = $this->getApplicationURI("/view/{$id}/"); - $crumbs->addTextCrumb($dashboard->getName(), $dashboard_uri); + $crumbs->addTextCrumb($dashboard->getName(), $dashboard->getURI()); } return $crumbs; } - protected function buildSideNavView($filter = null) { - $viewer = $this->getViewer(); - $dashboard = $this->getDashboard(); - $id = $dashboard->getID(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $dashboard, - PhabricatorPolicyCapability::CAN_EDIT); - - $nav = id(new AphrontSideNavFilterView()) - ->setBaseURI(new PhutilURI($this->getApplicationURI())); - - $nav->addLabel(pht('Dashboard')); - - $nav->addFilter( - 'view', - pht('View Dashboard'), - $this->getApplicationURI("/view/{$id}/"), - 'fa-dashboard'); - - $nav->addFilter( - 'arrange', - pht('Arrange Panels'), - $this->getApplicationURI("/arrange/{$id}/"), - 'fa-columns'); - - $nav->addFilter( - 'manage', - pht('Manage Dashboard'), - $this->getApplicationURI("/manage/{$id}/"), - 'fa-gears'); - - $nav->selectFilter($filter); - - return $nav; - } - } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php b/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php index 77068531a9..a229fcb41c 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php @@ -7,185 +7,160 @@ final class PhabricatorDashboardQueryPanelInstallController $viewer = $request->getViewer(); $v_dashboard = null; - $v_name = null; - $v_column = 0; - $v_engine = $request->getURIData('engineKey'); - $v_query = $request->getURIData('queryKey'); + $e_dashboard = null; + $v_name = null; $e_name = true; - // Validate Engines + $v_engine = $request->getStr('engine'); + if (!strlen($v_engine)) { + $v_engine = $request->getURIData('engineKey'); + } + + $v_query = $request->getStr('query'); + if (!strlen($v_query)) { + $v_query = $request->getURIData('queryKey'); + } + $engines = PhabricatorApplicationSearchEngine::getAllEngines(); - foreach ($engines as $name => $engine) { - if (!$engine->canUseInPanelContext()) { - unset($engines[$name]); - } - } - if (!in_array($v_engine, array_keys($engines))) { - return new Aphront404Response(); - } + $engine = idx($engines, $v_engine); + if ($engine) { + $engine = id(clone $engine) + ->setViewer($viewer); - // Validate Queries - $engine = $engines[$v_engine]; - $engine->setViewer($viewer); - $good_query = false; - if ($engine->isBuiltinQuery($v_query)) { - $good_query = true; + $redirect_uri = $engine->getQueryResultsPageURI($v_query); + + $named_query = idx($engine->loadEnabledNamedQueries(), $v_query); + if ($named_query) { + $v_name = $named_query->getQueryName(); + } } else { - $saved_query = id(new PhabricatorSavedQueryQuery()) - ->setViewer($viewer) - ->withEngineClassNames(array($v_engine)) - ->withQueryKeys(array($v_query)) - ->executeOne(); - if ($saved_query) { - $good_query = true; - } - } - if (!$good_query) { - return new Aphront404Response(); - } - - $named_query = idx($engine->loadEnabledNamedQueries(), $v_query); - if ($named_query) { - $v_name = $named_query->getQueryName(); + $redirect_uri = '/'; } $errors = array(); + $xaction_name = PhabricatorDashboardPanelNameTransaction::TRANSACTIONTYPE; + $xaction_engine = + PhabricatorDashboardQueryPanelApplicationTransaction::TRANSACTIONTYPE; + $xaction_query = + PhabricatorDashboardQueryPanelQueryTransaction::TRANSACTIONTYPE; + if ($request->isFormPost()) { - $v_dashboard = $request->getInt('dashboardID'); $v_name = $request->getStr('name'); if (!$v_name) { $errors[] = pht('You must provide a name for this panel.'); $e_name = pht('Required'); } - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($v_dashboard)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); + $v_dashboard = head($request->getArr('dashboardPHIDs')); + if (!$v_dashboard) { + $errors[] = pht('You must select a dashboard.'); + $e_dashboard = pht('Required'); + } else { + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withPHIDs(array($v_dashboard)) + ->executeOne(); + if (!$dashboard) { + $errors[] = pht('You must select a valid dashboard.'); + $e_dashboard = pht('Invalid'); + } - if (!$dashboard) { - $errors[] = pht('Please select a valid dashboard.'); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $dashboard, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $errors[] = pht( + 'You must select a dashboard you have permission to edit.'); + } } if (!$errors) { - $redirect_uri = "/dashboard/arrange/{$v_dashboard}/"; + $done_uri = $dashboard->getURI(); + + // First, create a new panel. $panel_type = id(new PhabricatorDashboardQueryPanelType()) ->getPanelTypeKey(); - $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); - $panel->setPanelType($panel_type); - $field_list = PhabricatorCustomField::getObjectFields( - $panel, - PhabricatorCustomField::ROLE_EDIT); - - $field_list - ->setViewer($viewer) - ->readFieldsFromStorage($panel); - - $panel->requireImplementation()->initializeFieldsFromRequest( - $panel, - $field_list, - $request); + $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer) + ->setPanelType($panel_type); $xactions = array(); - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) - ->setMetadataValue('customfield:key', 'std:dashboard:core:class') - ->setOldValue(null) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_engine) ->setNewValue($v_engine); - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) - ->setMetadataValue('customfield:key', 'std:dashboard:core:key') - ->setOldValue(null) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_query) ->setNewValue($v_query); - $editor = id(new PhabricatorDashboardPanelTransactionEditor()) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_name) + ->setNewValue($v_name); + + $editor = $panel->getApplicationTransactionEditor() ->setActor($viewer) - ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->applyTransactions($panel, $xactions); - PhabricatorDashboardTransactionEditor::addPanelToDashboard( - $viewer, - PhabricatorContentSource::newFromRequest($request), - $panel, - $dashboard, - $request->getInt('column', 0)); + // Now that we've created a panel, add it to the dashboard. - return id(new AphrontRedirectResponse())->setURI($redirect_uri); + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->newPanelRef($panel); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($dashboard, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); } } - // Make this a select for now, as we don't expect someone to have - // edit access to a vast number of dashboards. - // Can add optiongroup if needed down the road. - $dashboards = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withStatuses(array( - PhabricatorDashboard::STATUS_ACTIVE, - )) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - $options = mpull($dashboards, 'getName', 'getID'); - asort($options); - - $redirect_uri = $engine->getQueryResultsPageURI($v_query); - - if (!$options) { - $notice = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('You do not have access to any dashboards. To '. - 'continue, please create a dashboard first.')); - - return $this->newDialog() - ->setTitle(pht('No Dashboards')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($notice) - ->addCancelButton($redirect_uri); + if ($v_dashboard) { + $dashboard_phids = array($v_dashboard); + } else { + $dashboard_phids = array(); } $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('engine', $v_engine) - ->addHiddenInput('query', $v_query) - ->addHiddenInput('column', $v_column) - ->appendChild( + ->setViewer($viewer) + ->appendControl( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($v_name) ->setError($e_name)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setUser($this->getViewer()) - ->setValue($v_dashboard) - ->setName('dashboardID') - ->setOptions($options) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setValue($dashboard_phids) + ->setError($e_dashboard) + ->setName('dashboardPHIDs') + ->setLimit(1) + ->setDatasource(new PhabricatorDashboardDatasource()) ->setLabel(pht('Dashboard'))); return $this->newDialog() ->setTitle(pht('Add Panel to Dashboard')) ->setErrors($errors) ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form->buildLayoutView()) + ->addHiddenInput('engine', $v_engine) + ->addHiddenInput('query', $v_query) + ->appendForm($form) ->addCancelButton($redirect_uri) ->addSubmitButton(pht('Add Panel')); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php deleted file mode 100644 index d62b163db2..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php +++ /dev/null @@ -1,89 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - // NOTE: If you can edit a dashboard, you can remove panels from it even - // if you don't have permission to see them or they aren't valid. We only - // require that the panel be present on the dashboard. - - $v_panel = $request->getStr('panelPHID'); - - $panel_on_dashboard = false; - $layout = $dashboard->getLayoutConfigObject(); - $columns = $layout->getPanelLocations(); - foreach ($columns as $column) { - foreach ($column as $column_panel_phid) { - if ($column_panel_phid == $v_panel) { - $panel_on_dashboard = true; - break; - } - } - } - - if (!$panel_on_dashboard) { - return new Aphront404Response(); - } - - $redirect_uri = $this->getApplicationURI( - 'arrange/'.$dashboard->getID().'/'); - $layout_config = $dashboard->getLayoutConfigObject(); - - if ($request->isFormPost()) { - $xactions = array(); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST) - ->setNewValue( - array( - '-' => array( - $v_panel => $v_panel, - ), - )); - - $layout_config->removePanel($v_panel); - $dashboard->setLayoutConfigFromObject($layout_config); - - $editor = id(new PhabricatorDashboardTransactionEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true) - ->applyTransactions($dashboard, $xactions); - - return id(new AphrontRedirectResponse())->setURI($redirect_uri); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('confirm', true) - ->addHiddenInput('panelPHID', $v_panel) - ->appendChild(pht('Are you sure you want to remove this panel?')); - - return $this->newDialog() - ->setTitle(pht('Remove Panel')) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($redirect_uri) - ->addSubmitButton(pht('Remove Panel')); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php deleted file mode 100644 index 41441f09f4..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php +++ /dev/null @@ -1,68 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $this->setDashboard($dashboard); - - $dashboard_uri = $this->getApplicationURI("view/{$id}/"); - $title = $dashboard->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('View')); - - if ($dashboard->getPanelPHIDs()) { - $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) - ->setViewer($viewer) - ->setDashboard($dashboard) - ->renderDashboard(); - $content = id(new PHUIBoxView()) - ->addClass('dashboard-preview-box') - ->appendChild($rendered_dashboard); - } else { - $content = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('This dashboard has no panels yet.')); - } - - $navigation = $this->buildSideNavView('view'); - $header = $this->buildHeaderView(); - - $install_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText('Install Dashboard') - ->setIcon('fa-plus') - ->setColor(PHUIButtonView::GREEN) - ->setWorkflow(true) - ->setHref($this->getApplicationURI("/install/{$id}/")); - $header->addActionLink($install_button); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $content, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setNavigation($navigation) - ->appendChild($view); - } - -} diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php new file mode 100644 index 0000000000..86a3ff5805 --- /dev/null +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php @@ -0,0 +1,243 @@ +getViewer(); + + $context_phid = $request->getStr('contextPHID'); + + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$dashboard) { + return new Aphront404Response(); + } + + $this->contextPHID = $context_phid; + + $done_uri = $dashboard->getURI(); + $ref_list = $dashboard->getPanelRefList(); + + $panel_ref = null; + $panel_key = $request->getStr('panelKey'); + if (strlen($panel_key)) { + $panel_ref = $ref_list->getPanelRef($panel_key); + if (!$panel_ref) { + return new Aphront404Response(); + } + + $this->panelKey = $panel_key; + } + + $column_key = $request->getStr('columnKey'); + if (strlen($column_key)) { + $columns = $ref_list->getColumns(); + if (!isset($columns[$column_key])) { + return new Aphront404Response(); + } + $this->columnKey = $column_key; + } + + $after_ref = null; + $after_key = $request->getStr('afterKey'); + if (strlen($after_key)) { + $after_ref = $ref_list->getPanelRef($after_key); + if (!$after_ref) { + return new Aphront404Response(); + } + } + + switch ($request->getURIData('op')) { + case 'add': + return $this->handleAddRequest($dashboard, $done_uri); + case 'remove': + if (!$panel_ref) { + return new Aphront404Response(); + } + return $this->handleRemoveRequest($dashboard, $panel_ref, $done_uri); + case 'move': + return $this->handleMoveRequest($dashboard, $panel_ref, $after_ref); + } + } + + private function handleAddRequest( + PhabricatorDashboard $dashboard, + $done_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $errors = array(); + + $panel_phid = null; + $e_panel = true; + if ($request->isFormPost()) { + $panel_phid = head($request->getArr('panelPHIDs')); + + if (!$panel_phid) { + $errors[] = pht('You must choose a panel to add to the dashboard.'); + $e_panel = pht('Required'); + } else { + $panel = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withPHIDs(array($panel_phid)) + ->executeOne(); + if (!$panel) { + $errors[] = pht('You must choose a valid panel.'); + $e_panel = pht('Invalid'); + } + } + + if (!$errors) { + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->newPanelRef($panel, $this->columnKey); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($dashboard, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + } + + if ($panel_phid) { + $panel_phids = array($panel_phid); + } else { + $panel_phids = array(); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendRemarkupInstructions( + pht('Choose a panel to add to this dashboard:')) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorDashboardPanelDatasource()) + ->setLimit(1) + ->setName('panelPHIDs') + ->setLabel(pht('Panel')) + ->setError($e_panel) + ->setValue($panel_phids)); + + return $this->newEditDialog() + ->setTitle(pht('Add Panel')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->setErrors($errors) + ->appendForm($form) + ->addCancelButton($done_uri) + ->addSubmitButton(pht('Add Panel')); + } + + private function handleRemoveRequest( + PhabricatorDashboard $dashboard, + PhabricatorDashboardPanelRef $panel_ref, + $done_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + // NOTE: If you can edit a dashboard, you can remove panels from it even + // if you don't have permission to see them or they aren't valid. We only + // require that the panel be present on the dashboard. + + if ($request->isFormPost()) { + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->removePanelRef($panel_ref); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($dashboard, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + + $panel_phid = $panel_ref->getPanelPHID(); + $handles = $viewer->loadHandles(array($panel_phid)); + $handle = $handles[$panel_phid]; + + $message = pht( + 'Remove panel %s from dashboard %s?', + phutil_tag('strong', array(), $handle->getFullName()), + phutil_tag('strong', array(), $dashboard->getName())); + + return $this->newEditDialog() + ->setTitle(pht('Remove Dashboard Panel')) + ->appendParagraph($message) + ->addCancelButton($done_uri) + ->addSubmitButton(pht('Remove Panel')); + } + + private function handleMoveRequest( + PhabricatorDashboard $dashboard, + PhabricatorDashboardPanelRef $panel_ref, + PhabricatorDashboardPanelRef $after_ref = null) { + + $request = $this->getRequest(); + $request->validateCSRF(); + $viewer = $this->getViewer(); + + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->movePanelRef($panel_ref, $this->columnKey, $after_ref); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($dashboard, $xactions); + + return id(new AphrontAjaxResponse())->setContent(array()); + } + + + private function newEditDialog() { + return $this->newDialog() + ->addHiddenInput('contextPHID', $this->contextPHID) + ->addHiddenInput('panelKey', $this->panelKey) + ->addHiddenInput('columnKey', $this->columnKey); + } + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardArchiveController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php similarity index 92% rename from src/applications/dashboard/controller/PhabricatorDashboardArchiveController.php rename to src/applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php index 709e03fdf0..fb69c477d7 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardArchiveController.php +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php @@ -20,7 +20,7 @@ final class PhabricatorDashboardArchiveController return new Aphront404Response(); } - $view_uri = $this->getApplicationURI('manage/'.$dashboard->getID().'/'); + $view_uri = $dashboard->getURI(); if ($request->isFormPost()) { if ($dashboard->isArchived()) { @@ -32,7 +32,8 @@ final class PhabricatorDashboardArchiveController $xactions = array(); $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorDashboardTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorDashboardStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorDashboardTransactionEditor()) diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php new file mode 100644 index 0000000000..00e03dd286 --- /dev/null +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php new file mode 100644 index 0000000000..417086b405 --- /dev/null +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php @@ -0,0 +1,75 @@ +dashboard = $dashboard; + return $this; + } + + public function getDashboard() { + return $this->dashboard; + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$dashboard) { + return new Aphront404Response(); + } + + $this->setDashboard($dashboard); + $cancel_uri = $dashboard->getURI(); + + $workflow_key = $request->getURIData('workflowKey'); + + $workflows = PhabricatorDashboardInstallWorkflow::getAllWorkflows(); + if (!isset($workflows[$workflow_key])) { + return $this->newWorkflowDialog($dashboard, $workflows); + } + + return id(clone $workflows[$workflow_key]) + ->setRequest($request) + ->setViewer($viewer) + ->setDashboard($dashboard) + ->setMode($request->getURIData('modeKey')) + ->handleRequest($request); + } + + private function newWorkflowDialog( + PhabricatorDashboard $dashboard, + array $workflows) { + $viewer = $this->getViewer(); + $cancel_uri = $dashboard->getURI(); + + $menu = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setBig(true); + + foreach ($workflows as $key => $workflow) { + $item = $workflow->getWorkflowMenuItem(); + + $item_href = urisprintf('install/%d/%s/', $dashboard->getID(), $key); + $item_href = $this->getApplicationURI($item_href); + $item->setHref($item_href); + + $menu->addItem($item); + } + + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Menu')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendChild($menu) + ->addCancelButton($cancel_uri); + } + +} diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php new file mode 100644 index 0000000000..f43afd7e57 --- /dev/null +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php @@ -0,0 +1,202 @@ +getViewer(); + $id = $request->getURIData('id'); + + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$dashboard) { + return new Aphront404Response(); + } + $this->setDashboard($dashboard); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $dashboard, + PhabricatorPolicyCapability::CAN_EDIT); + + $title = $dashboard->getName(); + $crumbs = $this->buildApplicationCrumbs(); + $header = $this->buildHeaderView(); + + $curtain = $this->buildCurtainView($dashboard); + + $usage_box = $this->newUsageView($dashboard); + + $timeline = $this->buildTransactionTimeline( + $dashboard, + new PhabricatorDashboardTransactionQuery()); + $timeline->setShouldTerminate(true); + + $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) + ->setViewer($viewer) + ->setDashboard($dashboard) + ->setArrangeMode($can_edit) + ->renderDashboard(); + + $dashboard_box = id(new PHUIBoxView()) + ->addClass('dashboard-preview-box') + ->appendChild($rendered_dashboard); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn( + array( + $dashboard_box, + $usage_box, + $timeline, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + + } + + private function buildCurtainView(PhabricatorDashboard $dashboard) { + $viewer = $this->getViewer(); + $id = $dashboard->getID(); + + $curtain = $this->newCurtainView($dashboard); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $dashboard, + PhabricatorPolicyCapability::CAN_EDIT); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Dashboard')) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI("edit/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Add Dashboard to Menu')) + ->setIcon('fa-wrench') + ->setHref($this->getApplicationURI("/install/{$id}/")) + ->setWorkflow(true)); + + if ($dashboard->isArchived()) { + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Activate Dashboard')) + ->setIcon('fa-check') + ->setHref($this->getApplicationURI("archive/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + } else { + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Archive Dashboard')) + ->setIcon('fa-ban') + ->setHref($this->getApplicationURI("archive/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + } + + return $curtain; + } + + private function newUsageView(PhabricatorDashboard $dashboard) { + $viewer = $this->getViewer(); + + $custom_phids = array(); + if ($viewer->getPHID()) { + $custom_phids[] = $viewer->getPHID(); + } + + $items = id(new PhabricatorProfileMenuItemConfigurationQuery()) + ->setViewer($viewer) + ->withAffectedObjectPHIDs( + array( + $dashboard->getPHID(), + )) + ->withCustomPHIDs($custom_phids, $include_global = true) + ->execute(); + + $handle_phids = array(); + foreach ($items as $item) { + $handle_phids[] = $item->getProfilePHID(); + $custom_phid = $item->getCustomPHID(); + if ($custom_phid) { + $handle_phids[] = $custom_phid; + } + } + + if ($handle_phids) { + $handles = $viewer->loadHandles($handle_phids); + } else { + $handles = array(); + } + + $items = msortv($items, 'newUsageSortVector'); + + $rows = array(); + foreach ($items as $item) { + $profile_phid = $item->getProfilePHID(); + $custom_phid = $item->getCustomPHID(); + + $profile = $handles[$profile_phid]->renderLink(); + $profile_icon = $handles[$profile_phid]->getIcon(); + + if ($custom_phid) { + $custom = $handles[$custom_phid]->renderLink(); + } else { + $custom = pht('Global'); + } + + $type = $item->getProfileMenuTypeDescription(); + + $rows[] = array( + id(new PHUIIconView())->setIcon($profile_icon), + $type, + $profile, + $custom, + ); + } + + $usage_table = id(new AphrontTableView($rows)) + ->setNoDataString( + pht('This dashboard has not been added to any menus.')) + ->setHeaders( + array( + null, + pht('Type'), + pht('Menu'), + pht('Global/Personal'), + )) + ->setColumnClasses( + array( + 'center', + null, + 'pri', + 'wide', + )); + + $header_view = id(new PHUIHeaderView()) + ->setHeader(pht('Dashboard Used By')); + + $usage_box = id(new PHUIObjectBoxView()) + ->setTable($usage_table) + ->setHeader($header_view); + + return $usage_box; + } + + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php similarity index 94% rename from src/applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php rename to src/applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php index 8710539c49..05171e086d 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php @@ -25,7 +25,8 @@ final class PhabricatorDashboardPanelArchiveController if ($request->isFormPost()) { $xactions = array(); $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE) + ->setTransactionType( + PhabricatorDashboardPanelStatusTransaction::TRANSACTIONTYPE) ->setNewValue((int)!$panel->getIsArchived()); id(new PhabricatorDashboardPanelTransactionEditor()) diff --git a/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php new file mode 100644 index 0000000000..4ab76d18b5 --- /dev/null +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php @@ -0,0 +1,104 @@ +getViewer(); + + $engine = id(new PhabricatorDashboardPanelEditEngine()) + ->setController($this); + + // We can create or edit a panel in the context of a dashboard or + // container panel, like a tab panel. If we started this flow on some + // container object, we want to return to that container when we're done + // editing. + + $context_phid = $request->getStr('contextPHID'); + if (strlen($context_phid)) { + $context = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$context) { + return new Aphront404Response(); + } + + if (!($context instanceof PhabricatorDashboardPanelContainerInterface)) { + return new Aphront404Response(); + } + + $engine + ->setContextObject($context) + ->addContextParameter('contextPHID', $context_phid); + } else { + $context = null; + } + + $id = $request->getURIData('id'); + if (!$id) { + $column_key = $request->getStr('columnKey'); + + if ($context) { + $cancel_uri = $context->getURI(); + } else { + $cancel_uri = $this->getApplicationURI('panel/'); + } + + $panel_type = $request->getStr('panelType'); + $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); + if (empty($panel_types[$panel_type])) { + return $this->buildPanelTypeResponse($cancel_uri); + } + + $engine + ->addContextParameter('panelType', $panel_type) + ->addContextParameter('columnKey', $column_key) + ->setPanelType($panel_type) + ->setColumnKey($column_key); + } + + return $engine->buildResponse(); + } + + private function buildPanelTypeResponse($cancel_uri) { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $base_uri = $request->getRequestURI(); + $base_uri = new PhutilURI($base_uri); + + $menu = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setBig(true); + + $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); + foreach ($panel_types as $panel_type) { + $item = id(new PHUIObjectItemView()) + ->setClickable(true) + ->setImageIcon($panel_type->getIcon()) + ->setHeader($panel_type->getPanelTypeName()) + ->addAttribute($panel_type->getPanelTypeDescription()); + + $type_uri = id(clone $base_uri) + ->replaceQueryParam('panelType', $panel_type->getPanelTypeKey()); + + $item->setHref($type_uri); + + $menu->addItem($item); + } + + return $this->newDialog() + ->setTitle(pht('Choose Panel Type')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendChild($menu) + ->addCancelButton($cancel_uri); + } + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php similarity index 94% rename from src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php rename to src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php index eaffdaa0d5..1eab148261 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php @@ -43,7 +43,7 @@ final class PhabricatorDashboardPanelListController id(new PHUIListItemView()) ->setIcon('fa-plus-square') ->setName(pht('Create Panel')) - ->setHref($this->getApplicationURI().'panel/create/')); + ->setHref($this->getApplicationURI().'panel/edit/')); return $crumbs; } @@ -52,7 +52,7 @@ final class PhabricatorDashboardPanelListController $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Panel')) - ->setHref('/dashboard/panel/create/') + ->setHref('/dashboard/panel/edit/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getIcon(); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php similarity index 75% rename from src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php rename to src/applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php index 8f25a0f9bb..fee7ef6de4 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php @@ -31,14 +31,28 @@ final class PhabricatorDashboardPanelRenderController $parent_phids = array(); } - $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) + $engine = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setPanel($panel) ->setPanelPHID($panel->getPHID()) ->setParentPanelPHIDs($parent_phids) + ->setMovable($request->getBool('movable')) ->setHeaderMode($request->getStr('headerMode')) - ->setDashboardID($request->getInt('dashboardID')) - ->renderPanel(); + ->setPanelKey($request->getStr('panelKey')); + + $context_phid = $request->getStr('contextPHID'); + if ($context_phid) { + $context = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->executeOne(); + if (!$context) { + return new Aphront404Response(); + } + $engine->setContextObject($context); + } + + $rendered_panel = $engine->renderPanel(); if ($request->isAjax()) { return id(new AphrontAjaxResponse()) diff --git a/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php new file mode 100644 index 0000000000..0703332eac --- /dev/null +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php @@ -0,0 +1,354 @@ +contextObject = $context_object; + return $this; + } + + private function getContextObject() { + return $this->contextObject; + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + + $panel = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$panel) { + return new Aphront404Response(); + } + + $tabs_type = id(new PhabricatorDashboardTabsPanelType()) + ->getPanelTypeKey(); + + // This controller may only be used to edit tab panels. + $panel_type = $panel->getPanelType(); + if ($panel_type !== $tabs_type) { + return new Aphront404Response(); + } + + $op = $request->getURIData('op'); + $after = $request->getStr('after'); + if (!strlen($after)) { + $after = null; + } + + $target = $request->getStr('target'); + if (!strlen($target)) { + $target = null; + } + + $impl = $panel->getImplementation(); + $config = $impl->getPanelConfiguration($panel); + + $cancel_uri = $panel->getURI(); + + if ($after !== null) { + $found = false; + foreach ($config as $key => $spec) { + if ((string)$key === $after) { + $found = true; + break; + } + } + + if (!$found) { + return $this->newDialog() + ->setTitle(pht('Adjacent Tab Not Found')) + ->appendParagraph( + pht( + 'Adjacent tab ("%s") was not found on this panel. It may have '. + 'been removed.', + $after)) + ->addCancelButton($cancel_uri); + } + } + + if ($target !== null) { + $found = false; + foreach ($config as $key => $spec) { + if ((string)$key === $target) { + $found = true; + break; + } + } + + if (!$found) { + return $this->newDialog() + ->setTitle(pht('Target Tab Not Found')) + ->appendParagraph( + pht( + 'Target tab ("%s") was not found on this panel. It may have '. + 'been removed.', + $target)) + ->addCancelButton($cancel_uri); + } + } + + // Tab panels may be edited from the panel page, or from the context of + // a dashboard. If we're editing from a dashboard, we want to redirect + // back to the dashboard after making changes. + + $context_phid = $request->getStr('contextPHID'); + $context = null; + if (strlen($context_phid)) { + $context = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->executeOne(); + if (!$context) { + return new Aphront404Response(); + } + + switch (phid_get_type($context_phid)) { + case PhabricatorDashboardDashboardPHIDType::TYPECONST: + $cancel_uri = $context->getURI(); + break; + case PhabricatorDashboardPanelPHIDType::TYPECONST: + $cancel_uri = $context->getURI(); + break; + default: + return $this->newDialog() + ->setTitle(pht('Context Object Unsupported')) + ->appendParagraph( + pht( + 'Context object ("%s") has unsupported type. Panels should '. + 'be rendered from the context of a dashboard or another '. + 'panel.', + $context_phid)) + ->addCancelButton($cancel_uri); + } + + $this->setContextObject($context); + } + + switch ($op) { + case 'add': + return $this->handleAddOperation($panel, $after, $cancel_uri); + case 'remove': + return $this->handleRemoveOperation($panel, $target, $cancel_uri); + case 'move': + break; + case 'rename': + return $this->handleRenameOperation($panel, $target, $cancel_uri); + } + } + + private function handleAddOperation( + PhabricatorDashboardPanel $panel, + $after, + $cancel_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $panel_phid = null; + $errors = array(); + if ($request->isFormPost()) { + $panel_phid = $request->getArr('panelPHID'); + $panel_phid = head($panel_phid); + + $add_panel = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withPHIDs(array($panel_phid)) + ->executeOne(); + if (!$add_panel) { + $errors[] = pht('You must select a valid panel.'); + } + + if (!$errors) { + $add_panel_config = array( + 'name' => null, + 'panelID' => $add_panel->getID(), + ); + $add_panel_key = Filesystem::readRandomCharacters(12); + + $impl = $panel->getImplementation(); + $old_config = $impl->getPanelConfiguration($panel); + $new_config = array(); + if ($after === null) { + $new_config = $old_config; + $new_config[] = $add_panel_config; + } else { + foreach ($old_config as $key => $value) { + $new_config[$key] = $value; + if ((string)$key === $after) { + $new_config[$add_panel_key] = $add_panel_config; + } + } + } + + $xactions = array(); + + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardTabsPanelTabsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_config); + + $editor = id(new PhabricatorDashboardPanelTransactionEditor()) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($panel, $xactions); + + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + } + + if ($panel_phid) { + $v_panel = array($panel_phid); + } else { + $v_panel = array(); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorDashboardPanelDatasource()) + ->setLimit(1) + ->setName('panelPHID') + ->setLabel(pht('Panel')) + ->setValue($v_panel)); + + return $this->newEditDialog() + ->setTitle(pht('Choose Dashboard Panel')) + ->setErrors($errors) + ->addHiddenInput('after', $after) + ->appendForm($form) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Add Panel')); + } + + private function handleRemoveOperation( + PhabricatorDashboardPanel $panel, + $target, + $cancel_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $panel_phid = null; + $errors = array(); + if ($request->isFormPost()) { + $impl = $panel->getImplementation(); + $old_config = $impl->getPanelConfiguration($panel); + + $new_config = $this->removePanel($old_config, $target); + $this->writePanelConfig($panel, $new_config); + + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + + return $this->newEditDialog() + ->setTitle(pht('Remove tab?')) + ->addHiddenInput('target', $target) + ->appendParagraph(pht('Really remove this tab?')) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Remove Tab')); + } + + private function handleRenameOperation( + PhabricatorDashboardPanel $panel, + $target, + $cancel_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $impl = $panel->getImplementation(); + $old_config = $impl->getPanelConfiguration($panel); + + $spec = $old_config[$target]; + $name = idx($spec, 'name'); + + if ($request->isFormPost()) { + $name = $request->getStr('name'); + + $new_config = $this->renamePanel($old_config, $target, $name); + $this->writePanelConfig($panel, $new_config); + + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendControl( + id(new AphrontFormTextControl()) + ->setValue($name) + ->setName('name') + ->setLabel(pht('Tab Name'))); + + return $this->newEditDialog() + ->setTitle(pht('Rename Panel')) + ->addHiddenInput('target', $target) + ->appendForm($form) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Rename Tab')); + } + + + private function writePanelConfig( + PhabricatorDashboardPanel $panel, + array $config) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $xactions = array(); + + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardTabsPanelTabsTransaction::TRANSACTIONTYPE) + ->setNewValue($config); + + $editor = id(new PhabricatorDashboardPanelTransactionEditor()) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + return $editor->applyTransactions($panel, $xactions); + } + + private function removePanel(array $config, $target) { + $result = array(); + + foreach ($config as $key => $panel_spec) { + if ((string)$key === $target) { + continue; + } + $result[$key] = $panel_spec; + } + + return $result; + } + + private function renamePanel(array $config, $target, $name) { + $config[$target]['name'] = $name; + return $config; + } + + protected function newEditDialog() { + $dialog = $this->newDialog() + ->setWidth(AphrontDialogView::WIDTH_FORM); + + $context = $this->getContextObject(); + if ($context) { + $dialog->addHiddenInput('contextPHID', $context->getPHID()); + } + + return $dialog; + } + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php similarity index 66% rename from src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php rename to src/applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php index 4b5f1b45be..56f9821fcd 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php @@ -19,6 +19,11 @@ final class PhabricatorDashboardPanelViewController return new Aphront404Response(); } + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $panel, + PhabricatorPolicyCapability::CAN_EDIT); + $title = $panel->getMonogram().' '.$panel->getName(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( @@ -29,17 +34,21 @@ final class PhabricatorDashboardPanelViewController $header = $this->buildHeaderView($panel); $curtain = $this->buildCurtainView($panel); - $properties = $this->buildPropertyView($panel); + + $usage_box = $this->newUsageView($panel); $timeline = $this->buildTransactionTimeline( $panel, new PhabricatorDashboardPanelTransactionQuery()); + $timeline->setShouldTerminate(true); $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setPanel($panel) + ->setContextObject($panel) ->setPanelPHID($panel->getPHID()) ->setParentPanelPHIDs(array()) + ->setEditMode(true) ->renderPanel(); $preview = id(new PHUIBoxView()) @@ -50,10 +59,10 @@ final class PhabricatorDashboardPanelViewController ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( - $properties, + $rendered_panel, + $usage_box, $timeline, - )) - ->setFooter($rendered_panel); + )); return $this->newPage() ->setTitle($title) @@ -65,18 +74,11 @@ final class PhabricatorDashboardPanelViewController $viewer = $this->getViewer(); $id = $panel->getID(); - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('View Panel')) - ->setIcon('fa-columns') - ->setHref($this->getApplicationURI("panel/render/{$id}/")); - $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($panel->getName()) ->setPolicyObject($panel) - ->setHeaderIcon('fa-columns') - ->addActionLink($button); + ->setHeaderIcon('fa-window-maximize'); if (!$panel->getIsArchived()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); @@ -124,51 +126,51 @@ final class PhabricatorDashboardPanelViewController return $curtain; } - private function buildPropertyView(PhabricatorDashboardPanel $panel) { + private function newUsageView(PhabricatorDashboardPanel $panel) { $viewer = $this->getViewer(); - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); + $object_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $panel->getPHID(), + PhabricatorDashboardPanelUsedByObjectEdgeType::EDGECONST); - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $panel); - - $panel_type = $panel->getImplementation(); - if ($panel_type) { - $type_name = $panel_type->getPanelTypeName(); + if ($object_phids) { + $handles = $viewer->loadHandles($object_phids); } else { - $type_name = phutil_tag( - 'em', - array(), - nonempty($panel->getPanelType(), pht('null'))); + $handles = array(); } - $properties->addProperty( - pht('Panel Type'), - $type_name); + $rows = array(); + foreach ($object_phids as $object_phid) { + $handle = $handles[$object_phid]; - $properties->addProperty( - pht('Editable By'), - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); + $icon = $handle->getIcon(); - $dashboard_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $panel->getPHID(), - PhabricatorDashboardPanelHasDashboardEdgeType::EDGECONST); + $rows[] = array( + id(new PHUIIconView())->setIcon($icon), + $handle->getTypeName(), + $handle->renderLink(), + ); + } - $does_not_appear = pht( - 'This panel does not appear on any dashboards.'); + $usage_table = id(new AphrontTableView($rows)) + ->setNoDataString( + pht( + 'This panel is not used on any dashboard or inside any other '. + 'panel container.')) + ->setColumnClasses( + array( + 'center', + '', + 'pri wide', + )); - $properties->addProperty( - pht('Appears On'), - $dashboard_phids - ? $viewer->renderHandleList($dashboard_phids) - : phutil_tag('em', array(), $does_not_appear)); + $header_view = id(new PHUIHeaderView()) + ->setHeader(pht('Panel Used By')); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); + $usage_box = id(new PHUIObjectBoxView()) + ->setTable($usage_table) + ->setHeader($header_view); + + return $usage_box; } - } diff --git a/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php b/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php new file mode 100644 index 0000000000..98ac9a7846 --- /dev/null +++ b/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php @@ -0,0 +1,18 @@ +addCrumb( + id(new PHUICrumbView()) + ->setHref('/portal/') + ->setName(pht('Portals')) + ->setIcon('fa-compass')); + + return $crumbs; + } + +} diff --git a/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalEditController.php b/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalEditController.php new file mode 100644 index 0000000000..327d969f34 --- /dev/null +++ b/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalEditController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php b/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php new file mode 100644 index 0000000000..3eba0179b3 --- /dev/null +++ b/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php @@ -0,0 +1,26 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorDashboardPortalEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + +} diff --git a/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php b/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php new file mode 100644 index 0000000000..259a06451b --- /dev/null +++ b/src/applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php @@ -0,0 +1,59 @@ +portal = $portal; + return $this; + } + + public function getPortal() { + return $this->portal; + } + + public function shouldAllowPublic() { + return true; + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('portalID'); + + $portal = id(new PhabricatorDashboardPortalQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$portal) { + return new Aphront404Response(); + } + + $this->setPortal($portal); + + $engine = id(new PhabricatorDashboardPortalProfileMenuEngine()) + ->setProfileObject($portal) + ->setController($this); + + return $engine->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $portal = $this->getPortal(); + if ($portal) { + $crumbs->addTextCrumb($portal->getName(), $portal->getURI()); + } + + return $crumbs; + } + + public function newTimelineView() { + return $this->buildTransactionTimeline( + $this->getPortal(), + new PhabricatorDashboardPortalTransactionQuery()); + } + +} diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php deleted file mode 100644 index 6657e9cc41..0000000000 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php +++ /dev/null @@ -1,47 +0,0 @@ -getPanelType()) { - return array(); - } - - $impl = $object->requireImplementation(); - $specs = $impl->getFieldSpecifications(); - return PhabricatorStandardCustomField::buildStandardFields($this, $specs); - } - - public function shouldUseStorage() { - return false; - } - - public function readValueFromObject(PhabricatorCustomFieldInterface $object) { - $key = $this->getProxy()->getRawStandardFieldKey(); - $this->setValueFromStorage($object->getProperty($key)); - $this->didSetValueFromStorage(); - } - - public function applyApplicationTransactionInternalEffects( - PhabricatorApplicationTransaction $xaction) { - $object = $this->getObject(); - $key = $this->getProxy()->getRawStandardFieldKey(); - - $this->setValueFromApplicationTransactions($xaction->getNewValue()); - $value = $this->getValueForStorage(); - - $object->setProperty($key, $value); - } - - public function applyApplicationTransactionExternalEffects( - PhabricatorApplicationTransaction $xaction) { - return; - } - -} diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php deleted file mode 100644 index 43ece18e7b..0000000000 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php +++ /dev/null @@ -1,4 +0,0 @@ -getArr($this->getFieldKey().'_name'); - $panel_ids = $request->getArr($this->getFieldKey().'_panelID'); - $panels = array(); - foreach ($panel_ids as $panel_id) { - $panels[] = $panel_id[0]; - } - foreach ($names as $idx => $name) { - $panel_id = idx($panels, $idx); - if (strlen($name) && $panel_id) { - $value[] = array( - 'name' => $name, - 'panelID' => $panel_id, - ); - } - } - - $this->setFieldValue($value); - } - - public function getApplicationTransactionTitle( - PhabricatorApplicationTransaction $xaction) { - $author_phid = $xaction->getAuthorPHID(); - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $new_tabs = array(); - if ($new) { - foreach ($new as $new_tab) { - $new_tabs[] = $new_tab['name']; - } - $new_tabs = implode(' | ', $new_tabs); - } - - $old_tabs = array(); - if ($old) { - foreach ($old as $old_tab) { - $old_tabs[] = $old_tab['name']; - } - $old_tabs = implode(' | ', $old_tabs); - } - - if (!$old) { - // In case someone makes a tab panel with no tabs. - if ($new) { - return pht( - '%s set the tabs to "%s".', - $xaction->renderHandleLink($author_phid), - $new_tabs); - } - } else if (!$new) { - return pht( - '%s removed tabs.', - $xaction->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the tabs from "%s" to "%s".', - $xaction->renderHandleLink($author_phid), - $old_tabs, - $new_tabs); - } - } - - public function renderEditControl(array $handles) { - // NOTE: This includes archived panels so we don't mutate the tabs - // when saving a tab panel that includes archived panels. This whole UI is - // hopefully temporary anyway. - - $value = $this->getFieldValue(); - if (!is_array($value)) { - $value = array(); - } - - $out = array(); - for ($ii = 1; $ii <= 6; $ii++) { - $tab = idx($value, ($ii - 1), array()); - $panel = idx($tab, 'panelID', null); - $panel_id = array(); - if ($panel) { - $panel_id[] = $panel; - } - $out[] = id(new AphrontFormTextControl()) - ->setName($this->getFieldKey().'_name[]') - ->setValue(idx($tab, 'name')) - ->setLabel(pht('Tab %d Name', $ii)); - - $out[] = id(new AphrontFormTokenizerControl()) - ->setUser($this->getViewer()) - ->setDatasource(new PhabricatorDashboardPanelDatasource()) - ->setName($this->getFieldKey().'_panelID[]') - ->setValue($panel_id) - ->setLimit(1) - ->setLabel(pht('Tab %d Panel', $ii)); - } - - return $out; - } - -} diff --git a/src/applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php b/src/applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php deleted file mode 100644 index 8b31ea7476..0000000000 --- a/src/applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php +++ /dev/null @@ -1,103 +0,0 @@ -setAncestorClass('PhabricatorApplicationSearchEngine') ->setFilterMethod('canUseInPanelContext') @@ -31,7 +25,7 @@ final class PhabricatorDashboardPanelSearchApplicationCustomField $options = array(); - $value = $this->getFieldValue(); + $value = $this->getValueForControl(); if (strlen($value) && empty($engines[$value])) { $options[$value] = $value; } @@ -42,12 +36,24 @@ final class PhabricatorDashboardPanelSearchApplicationCustomField } return id(new AphrontFormSelectControl()) - ->setID($this->getFieldControlID()) - ->setLabel($this->getFieldName()) - ->setCaption($this->getCaption()) - ->setName($this->getFieldKey()) - ->setValue($this->getFieldValue()) + ->setID($this->getControlID()) ->setOptions($options); } + protected function newHTTPParameterType() { + return new AphrontSelectHTTPParameterType(); + } + + public function getControlID() { + if (!$this->controlID) { + $this->controlID = celerity_generate_unique_node_id(); + } + + return $this->controlID; + } + + protected function newConduitParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php b/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php similarity index 59% rename from src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php rename to src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php index 87870bf6ff..58192c0eee 100644 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php +++ b/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php @@ -1,23 +1,26 @@ applicationControlID = $id; + return $this; } - public function shouldAppearInApplicationSearch() { - return false; + public function getApplicationControlID() { + return $this->applicationControlID; } - public function renderEditControl(array $handles) { + protected function newControl() { $engines = id(new PhutilClassMapQuery()) ->setAncestorClass('PhabricatorApplicationSearchEngine') ->setFilterMethod('canUseInPanelContext') ->execute(); - $value = $this->getFieldValue(); + $value = $this->getValueForControl(); $queries = array(); $seen = false; @@ -43,12 +46,14 @@ final class PhabricatorDashboardPanelSearchQueryCustomField $options = array($value => $name); - $app_control_key = $this->getFieldConfigValue('control.application'); + $application_id = $this->getApplicationControlID(); + $control_id = celerity_generate_unique_node_id(); + Javelin::initBehavior( 'dashboard-query-panel-select', array( - 'applicationID' => $this->getFieldControlID($app_control_key), - 'queryID' => $this->getFieldControlID(), + 'applicationID' => $application_id, + 'queryID' => $control_id, 'options' => $queries, 'value' => array( 'key' => strlen($value) ? $value : null, @@ -57,12 +62,16 @@ final class PhabricatorDashboardPanelSearchQueryCustomField )); return id(new AphrontFormSelectControl()) - ->setID($this->getFieldControlID()) - ->setLabel($this->getFieldName()) - ->setCaption($this->getCaption()) - ->setName($this->getFieldKey()) - ->setValue($this->getFieldValue()) + ->setID($control_id) ->setOptions($options); } + protected function newHTTPParameterType() { + return new AphrontSelectHTTPParameterType(); + } + + protected function newConduitParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/applications/dashboard/editor/PhabricatorDashboardEditEngine.php b/src/applications/dashboard/editor/PhabricatorDashboardEditEngine.php new file mode 100644 index 0000000000..06b0a3a6fb --- /dev/null +++ b/src/applications/dashboard/editor/PhabricatorDashboardEditEngine.php @@ -0,0 +1,108 @@ +getViewer(); + return PhabricatorDashboard::initializeNewDashboard($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorDashboardQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Dashboard'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Dashboard'); + } + + protected function getObjectCreateCancelURI($object) { + return '/dashboard/'; + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Dashboard: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Dashboard'); + } + + protected function getObjectCreateShortText() { + return pht('Create Dashboard'); + } + + protected function getObjectName() { + return pht('Dashboard'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $layout_options = PhabricatorDashboardLayoutMode::getLayoutModeMap(); + + $fields = array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the dashboard.')) + ->setConduitDescription(pht('Rename the dashboard.')) + ->setConduitTypeDescription(pht('New dashboard name.')) + ->setTransactionType( + PhabricatorDashboardNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()), + id(new PhabricatorIconSetEditField()) + ->setKey('icon') + ->setLabel(pht('Icon')) + ->setTransactionType( + PhabricatorDashboardIconTransaction::TRANSACTIONTYPE) + ->setIconSet(new PhabricatorDashboardIconSet()) + ->setDescription(pht('Dashboard icon.')) + ->setConduitDescription(pht('Change the dashboard icon.')) + ->setConduitTypeDescription(pht('New dashboard icon.')) + ->setValue($object->getIcon()), + id(new PhabricatorSelectEditField()) + ->setKey('layout') + ->setLabel(pht('Layout')) + ->setDescription(pht('Dashboard layout mode.')) + ->setConduitDescription(pht('Change the dashboard layout mode.')) + ->setConduitTypeDescription(pht('New dashboard layout mode.')) + ->setTransactionType( + PhabricatorDashboardLayoutTransaction::TRANSACTIONTYPE) + ->setOptions($layout_options) + ->setValue($object->getRawLayoutMode()), + ); + + return $fields; + } + +} diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php index c9be8bf013..da891ebd5d 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php @@ -6,6 +6,8 @@ final class PhabricatorDashboardPanelEditEngine const ENGINECONST = 'dashboard.panel'; private $panelType; + private $contextObject; + private $columnKey; public function setPanelType($panel_type) { $this->panelType = $panel_type; @@ -16,6 +18,24 @@ final class PhabricatorDashboardPanelEditEngine return $this->panelType; } + public function setContextObject($context) { + $this->contextObject = $context; + return $this; + } + + public function getContextObject() { + return $this->contextObject; + } + + public function setColumnKey($column_key) { + $this->columnKey = $column_key; + return $this; + } + + public function getColumnKey() { + return $this->columnKey; + } + public function isEngineConfigurable() { return false; } @@ -63,6 +83,33 @@ final class PhabricatorDashboardPanelEditEngine return pht('Create Panel'); } + protected function getObjectCreateCancelURI($object) { + $context = $this->getContextObject(); + if ($context) { + return $context->getURI(); + } + + return parent::getObjectCreateCancelURI($object); + } + + public function getEffectiveObjectEditDoneURI($object) { + $context = $this->getContextObject(); + if ($context) { + return $context->getURI(); + } + + return parent::getEffectiveObjectEditDoneURI($object); + } + + protected function getObjectEditCancelURI($object) { + $context = $this->getContextObject(); + if ($context) { + return $context->getURI(); + } + + return parent::getObjectEditCancelURI($object); + } + protected function getObjectEditTitleText($object) { return pht('Edit Panel: %s', $object->getName()); } @@ -83,18 +130,58 @@ final class PhabricatorDashboardPanelEditEngine return $object->getURI(); } + protected function didApplyTransactions($object, array $xactions) { + $context = $this->getContextObject(); + + if ($context instanceof PhabricatorDashboard) { + $viewer = $this->getViewer(); + $controller = $this->getController(); + $request = $controller->getRequest(); + + $dashboard = $context; + + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + + $ref_list->newPanelRef($object, $this->getColumnKey()); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($dashboard, $xactions); + } + } + protected function buildCustomEditFields($object) { - return array( + $fields = array( id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the panel.')) ->setConduitDescription(pht('Rename the panel.')) ->setConduitTypeDescription(pht('New panel name.')) - ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME) + ->setTransactionType( + PhabricatorDashboardPanelNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), ); + + $panel_fields = $object->getEditEngineFields(); + foreach ($panel_fields as $panel_field) { + $fields[] = $panel_field; + } + + return $fields; } } diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php index 2a56721442..ea03c1ac7d 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php @@ -18,96 +18,11 @@ final class PhabricatorDashboardPanelTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorDashboardPanelTransaction::TYPE_NAME; - $types[] = PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - if ($this->getIsNewObject()) { - return null; - } - return $object->getName(); - case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: - return (int)$object->getIsArchived(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); + protected function supportsSearch() { + return true; } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - return $xaction->getNewValue(); - case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: - return (int)$xaction->getNewValue(); - } - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: - $object->setIsArchived((int)$xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Panel name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; - } - - } diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php b/src/applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php new file mode 100644 index 0000000000..9945ea9d28 --- /dev/null +++ b/src/applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php @@ -0,0 +1,87 @@ +getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Portal'); + } + + protected function getObjectCreateShortText() { + return pht('Create Portal'); + } + + protected function getObjectName() { + return pht('Portal'); + } + + protected function getObjectViewURI($object) { + if ($this->getIsCreate()) { + return $object->getURI(); + } else { + return '/portal/view/'.$object->getID().'/view/manage/'; + } + } + + protected function getEditorURI() { + return '/portal/edit/'; + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the portal.')) + ->setConduitDescription(pht('Rename the portal.')) + ->setConduitTypeDescription(pht('New portal name.')) + ->setTransactionType( + PhabricatorDashboardPortalNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()), + ); + } + +} diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPortalEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardPortalEditor.php new file mode 100644 index 0000000000..2c8a3ccfac --- /dev/null +++ b/src/applications/dashboard/editor/PhabricatorDashboardPortalEditor.php @@ -0,0 +1,35 @@ +setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST) - ->setNewValue( - array( - '+' => array( - $panel->getPHID() => $panel->getPHID(), - ), - )); - - $layout_config = $dashboard->getLayoutConfigObject(); - $layout_config->setPanelLocation($column, $panel->getPHID()); - $dashboard->setLayoutConfigFromObject($layout_config); - - $editor = id(new PhabricatorDashboardTransactionEditor()) - ->setActor($actor) - ->setContentSource($content_source) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true) - ->applyTransactions($dashboard, $xactions); - } - public function getTransactionTypes() { $types = parent::getTransactionTypes(); @@ -50,133 +18,11 @@ final class PhabricatorDashboardTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorDashboardTransaction::TYPE_NAME; - $types[] = PhabricatorDashboardTransaction::TYPE_ICON; - $types[] = PhabricatorDashboardTransaction::TYPE_STATUS; - $types[] = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardTransaction::TYPE_NAME: - if ($this->getIsNewObject()) { - return null; - } - return $object->getName(); - case PhabricatorDashboardTransaction::TYPE_ICON: - if ($this->getIsNewObject()) { - return null; - } - return $object->getIcon(); - case PhabricatorDashboardTransaction::TYPE_STATUS: - if ($this->getIsNewObject()) { - return null; - } - return $object->getStatus(); - case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE: - if ($this->getIsNewObject()) { - return null; - } - $layout_config = $object->getLayoutConfigObject(); - return $layout_config->getLayoutMode(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); + protected function supportsSearch() { + return true; } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardTransaction::TYPE_NAME: - case PhabricatorDashboardTransaction::TYPE_ICON: - case PhabricatorDashboardTransaction::TYPE_STATUS: - case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE: - return $xaction->getNewValue(); - } - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PhabricatorDashboardTransaction::TYPE_ICON: - $object->setIcon($xaction->getNewValue()); - return; - case PhabricatorDashboardTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE: - $old_layout = $object->getLayoutConfigObject(); - $new_layout = clone $old_layout; - $new_layout->setLayoutMode($xaction->getNewValue()); - if ($old_layout->isMultiColumnLayout() != - $new_layout->isMultiColumnLayout()) { - $panel_phids = $object->getPanelPHIDs(); - $new_locations = $new_layout->getDefaultPanelLocations(); - foreach ($panel_phids as $panel_phid) { - $new_locations[0][] = $panel_phid; - } - $new_layout->setPanelLocations($new_locations); - } - $object->setLayoutConfigFromObject($new_layout); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardTransaction::TYPE_NAME: - case PhabricatorDashboardTransaction::TYPE_ICON: - case PhabricatorDashboardTransaction::TYPE_STATUS: - case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorDashboardTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Dashboard name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; - } - - } diff --git a/src/applications/dashboard/engine/PhabricatorDashboardFerretEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardFerretEngine.php new file mode 100644 index 0000000000..0150827c31 --- /dev/null +++ b/src/applications/dashboard/engine/PhabricatorDashboardFerretEngine.php @@ -0,0 +1,18 @@ +setDocumentTitle($dashboard->getName()); + + $document->addRelationship( + $dashboard->isArchived() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $dashboard->getPHID(), + PhabricatorDashboardDashboardPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php new file mode 100644 index 0000000000..b8bd0c31ab --- /dev/null +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php @@ -0,0 +1,18 @@ +setDocumentTitle($panel->getName()); + + $document->addRelationship( + $panel->getIsArchived() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $panel->getPHID(), + PhabricatorDashboardPanelPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php index fc62c4d5cb..1866815610 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php @@ -12,16 +12,28 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { private $enableAsyncRendering; private $parentPanelPHIDs; private $headerMode = self::HEADER_MODE_NORMAL; - private $dashboardID; - private $movable = true; + private $movable; + private $panelHandle; + private $editMode; + private $contextObject; + private $panelKey; - public function setDashboardID($id) { - $this->dashboardID = $id; + public function setContextObject($object) { + $this->contextObject = $object; return $this; } - public function getDashboardID() { - return $this->dashboardID; + public function getContextObject() { + return $this->contextObject; + } + + public function setPanelKey($panel_key) { + $this->panelKey = $panel_key; + return $this; + } + + public function getPanelKey() { + return $this->panelKey; } public function setHeaderMode($header_mode) { @@ -33,6 +45,24 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { return $this->headerMode; } + public function setPanelHandle(PhabricatorObjectHandle $panel_handle) { + $this->panelHandle = $panel_handle; + return $this; + } + + public function getPanelHandle() { + return $this->panelHandle; + } + + public function isEditMode() { + return $this->editMode; + } + + public function setEditMode($mode) { + $this->editMode = $mode; + return $this; + } + /** * Allow the engine to render the panel via Ajax. */ @@ -90,11 +120,19 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { $panel = $this->getPanel(); if (!$panel) { - return $this->renderErrorPanel( - pht('Missing or Restricted Panel'), - pht( - 'This panel does not exist, or you do not have permission '. - 'to see it.')); + $handle = $this->getPanelHandle(); + if ($handle->getPolicyFiltered()) { + return $this->renderErrorPanel( + pht('Restricted Panel'), + pht( + 'You do not have permission to see this panel.')); + } else { + return $this->renderErrorPanel( + pht('Invalid Panel'), + pht( + 'This panel is invalid or does not exist. It may have been '. + 'deleted.')); + } } $panel_type = $panel->getImplementation(); @@ -144,10 +182,10 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { private function renderAsyncPanel() { + $context_phid = $this->getContextPHID(); $panel = $this->getPanel(); $panel_id = celerity_generate_unique_node_id(); - $dashboard_id = $this->getDashboardID(); Javelin::initBehavior( 'dashboard-async-panel', @@ -155,7 +193,9 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { 'panelID' => $panel_id, 'parentPanelPHIDs' => $this->getParentPanelPHIDs(), 'headerMode' => $this->getHeaderMode(), - 'dashboardID' => $dashboard_id, + 'contextPHID' => $context_phid, + 'panelKey' => $this->getPanelKey(), + 'movable' => $this->getMovable(), 'uri' => '/dashboard/panel/render/'.$panel->getID().'/', )); @@ -185,12 +225,13 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { ->setHeader($title); break; } + $icon = id(new PHUIIconView()) ->setIcon('fa-warning red msr'); $content = id(new PHUIBoxView()) ->addClass('dashboard-box') - ->addMargin(PHUI::MARGIN_MEDIUM) + ->addMargin(PHUI::MARGIN_LARGE) ->appendChild($icon) ->appendChild($body); @@ -240,11 +281,11 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { if ($panel) { $box->setMetadata( array( - 'objectPHID' => $panel->getPHID(), + 'panelKey' => $this->getPanelKey(), )); } - return phutil_tag_div('dashboard-pane', $box); + return $box; } @@ -256,8 +297,11 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { $header = null; break; case self::HEADER_MODE_EDIT: + // In edit mode, include the panel monogram to make managing boards + // a little easier. + $header_text = pht('%s %s', $panel->getMonogram(), $panel->getName()); $header = id(new PHUIHeaderView()) - ->setHeader($panel->getName()); + ->setHeader($header_text); $header = $this->addPanelHeaderActions($header); break; case self::HEADER_MODE_NORMAL: @@ -277,42 +321,65 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { private function addPanelHeaderActions( PHUIHeaderView $header) { - $panel = $this->getPanel(); - $dashboard_id = $this->getDashboardID(); + $viewer = $this->getViewer(); + $panel = $this->getPanel(); + $context_phid = $this->getContextPHID(); + + $actions = array(); if ($panel) { $panel_id = $panel->getID(); $edit_uri = "/dashboard/panel/edit/{$panel_id}/"; - $edit_uri = new PhutilURI($edit_uri); - if ($dashboard_id) { - $edit_uri->replaceQueryParam('dashboardID', $dashboard_id); - } + $params = array( + 'contextPHID' => $context_phid, + ); + $edit_uri = new PhutilURI($edit_uri, $params); - $action_edit = id(new PHUIIconView()) + $actions[] = id(new PhabricatorActionView()) ->setIcon('fa-pencil') - ->setWorkflow(true) - ->setHref((string)$edit_uri); + ->setName(pht('Edit Panel')) + ->setHref($edit_uri); - $header->addActionItem($action_edit); + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-window-maximize') + ->setName(pht('View Panel Details')) + ->setHref($panel->getURI()); } - if ($dashboard_id) { + if ($context_phid) { $panel_phid = $this->getPanelPHID(); - $remove_uri = "/dashboard/removepanel/{$dashboard_id}/"; - $remove_uri = id(new PhutilURI($remove_uri)) - ->replaceQueryParam('panelPHID', $panel_phid); + $remove_uri = urisprintf('/dashboard/adjust/remove/'); + $params = array( + 'contextPHID' => $context_phid, + 'panelKey' => $this->getPanelKey(), + ); + $remove_uri = new PhutilURI($remove_uri, $params); - $action_remove = id(new PHUIIconView()) - ->setIcon('fa-trash-o') - ->setHref((string)$remove_uri) + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-times') + ->setHref($remove_uri) + ->setName(pht('Remove Panel')) ->setWorkflow(true); - - $header->addActionItem($action_remove); } + $dropdown_menu = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + foreach ($actions as $action) { + $dropdown_menu->addAction($action); + } + + $action_menu = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-cog') + ->setText(pht('Manage Panel')) + ->setDropdownMenu($dropdown_menu); + + $header->addActionLink($action_menu); + return $header; } @@ -353,5 +420,14 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { } } + private function getContextPHID() { + $context = $this->getContextObject(); + + if ($context) { + return $context->getPHID(); + } + + return null; + } } diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php new file mode 100644 index 0000000000..b6cbe857fc --- /dev/null +++ b/src/applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php @@ -0,0 +1,18 @@ +setDocumentTitle($portal->getName()); + + $document->addRelationship( + $portal->isArchived() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $portal->getPHID(), + PhabricatorDashboardPortalPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php new file mode 100644 index 0000000000..9f8afc74dc --- /dev/null +++ b/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php @@ -0,0 +1,52 @@ +getProfileObject(); + + return $portal->getURI().$path; + } + + protected function getBuiltinProfileItems($object) { + $items = array(); + + $items[] = $this->newDividerItem('tail'); + + $items[] = $this->newManageItem(); + + $items[] = $this->newItem() + ->setMenuItemKey(PhabricatorDashboardPortalMenuItem::MENUITEMKEY) + ->setBuiltinKey('manage') + ->setIsTailItem(true); + + return $items; + } + + protected function newNoMenuItemsView(array $items) { + $object = $this->getProfileObject(); + $builtins = $this->getBuiltinProfileItems($object); + + if (count($items) <= count($builtins)) { + return $this->newEmptyView( + pht('New Portal'), + pht('Use "Edit Menu" to add menu items to this portal.')); + } else { + return $this->newEmptyValue( + pht('No Portal Content'), + pht( + 'None of the visible menu items in this portal can render any '. + 'content.')); + } + } + +} diff --git a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php index 9f6481c05b..9b63fcac55 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php @@ -11,11 +11,19 @@ final class PhabricatorDashboardRenderingEngine extends Phobject { return $this; } + public function getViewer() { + return $this->viewer; + } + public function setDashboard(PhabricatorDashboard $dashboard) { $this->dashboard = $dashboard; return $this; } + public function getDashboard() { + return $this->dashboard; + } + public function setArrangeMode($mode) { $this->arrangeMode = $mode; return $this; @@ -23,83 +31,121 @@ final class PhabricatorDashboardRenderingEngine extends Phobject { public function renderDashboard() { require_celerity_resource('phabricator-dashboard-css'); - $dashboard = $this->dashboard; - $viewer = $this->viewer; + $dashboard = $this->getDashboard(); + $viewer = $this->getViewer(); - $layout_config = $dashboard->getLayoutConfigObject(); - $panel_grid_locations = $layout_config->getPanelLocations(); - $panels = mpull($dashboard->getPanels(), null, 'getPHID'); - $dashboard_id = celerity_generate_unique_node_id(); - $result = id(new AphrontMultiColumnView()) - ->setID($dashboard_id) - ->setFluidLayout(true) - ->setGutter(AphrontMultiColumnView::GUTTER_LARGE); + $is_editable = $this->arrangeMode; - if ($this->arrangeMode) { + if ($is_editable) { $h_mode = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_EDIT; } else { $h_mode = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NORMAL; } - foreach ($panel_grid_locations as $column => $panel_column_locations) { - $panel_phids = $panel_column_locations; + $panel_phids = $dashboard->getPanelPHIDs(); + if ($panel_phids) { + $panels = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withPHIDs($panel_phids) + ->execute(); + $panels = mpull($panels, null, 'getPHID'); - // TODO: This list may contain duplicates when the dashboard itself - // does not? Perhaps this is related to T10612. For now, just unique - // the list before moving on. - $panel_phids = array_unique($panel_phids); + $handles = $viewer->loadHandles($panel_phids); + } else { + $panels = array(); + $handles = array(); + } + + $ref_list = $dashboard->getPanelRefList(); + $columns = $ref_list->getColumns(); + + $dashboard_id = celerity_generate_unique_node_id(); + + $result = id(new AphrontMultiColumnView()) + ->setID($dashboard_id) + ->setFluidLayout(true) + ->setGutter(AphrontMultiColumnView::GUTTER_LARGE); + + foreach ($columns as $column) { + $column_views = array(); + foreach ($column->getPanelRefs() as $panel_ref) { + $panel_phid = $panel_ref->getPanelPHID(); - $column_result = array(); - foreach ($panel_phids as $panel_phid) { $panel_engine = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) - ->setDashboardID($dashboard->getID()) ->setEnableAsyncRendering(true) + ->setContextObject($dashboard) + ->setPanelKey($panel_ref->getPanelKey()) ->setPanelPHID($panel_phid) ->setParentPanelPHIDs(array()) - ->setHeaderMode($h_mode); + ->setHeaderMode($h_mode) + ->setEditMode($is_editable) + ->setMovable(true) + ->setPanelHandle($handles[$panel_phid]); $panel = idx($panels, $panel_phid); if ($panel) { $panel_engine->setPanel($panel); } - $column_result[] = $panel_engine->renderPanel(); + $column_views[] = $panel_engine->renderPanel(); } - $column_class = $layout_config->getColumnClass( - $column, - $this->arrangeMode); - if ($this->arrangeMode) { - $column_result[] = $this->renderAddPanelPlaceHolder($column); - $column_result[] = $this->renderAddPanelUI($column); + + $column_classes = $column->getClasses(); + + $column_tail = array(); + if ($is_editable) { + $column_tail[] = $this->renderAddPanelPlaceHolder(); + $column_tail[] = $this->renderAddPanelUI($column); } + + $sigil = 'dashboard-column'; + + $metadata = array( + 'columnKey' => $column->getColumnKey(), + ); + + $column_view = javelin_tag( + 'div', + array( + 'sigil' => $sigil, + 'meta' => $metadata, + ), + $column_views); + $result->addColumn( - $column_result, - $column_class, - $sigil = 'dashboard-column', - $metadata = array('columnID' => $column)); + array( + $column_view, + $column_tail, + ), + implode(' ', $column_classes)); } - if ($this->arrangeMode) { + if ($is_editable) { + $params = array( + 'contextPHID' => $dashboard->getPHID(), + ); + $move_uri = new PhutilURI('/dashboard/adjust/move/', $params); + Javelin::initBehavior( 'dashboard-move-panels', array( - 'dashboardID' => $dashboard_id, - 'moveURI' => '/dashboard/movepanel/'.$dashboard->getID().'/', + 'dashboardNodeID' => $dashboard_id, + 'moveURI' => (string)$move_uri, )); } $view = id(new PHUIBoxView()) ->addClass('dashboard-view') - ->appendChild($result); + ->appendChild( + array( + $result, + )); return $view; } - private function renderAddPanelPlaceHolder($column) { - $dashboard = $this->dashboard; - $panels = $dashboard->getPanels(); - + private function renderAddPanelPlaceHolder() { return javelin_tag( 'span', array( @@ -109,19 +155,23 @@ final class PhabricatorDashboardRenderingEngine extends Phobject { pht('This column does not have any panels yet.')); } - private function renderAddPanelUI($column) { - $dashboard_id = $this->dashboard->getID(); + private function renderAddPanelUI(PhabricatorDashboardColumn $column) { + $dashboard = $this->getDashboard(); + $column_key = $column->getColumnKey(); - $create_uri = id(new PhutilURI('/dashboard/panel/create/')) - ->replaceQueryParam('dashboardID', $dashboard_id) - ->replaceQueryParam('column', $column); + $create_uri = id(new PhutilURI('/dashboard/panel/edit/')) + ->replaceQueryParam('contextPHID', $dashboard->getPHID()) + ->replaceQueryParam('columnKey', $column_key); - $add_uri = id(new PhutilURI('/dashboard/addpanel/'.$dashboard_id.'/')) - ->replaceQueryParam('column', $column); + $add_uri = id(new PhutilURI('/dashboard/adjust/add/')) + ->replaceQueryParam('contextPHID', $dashboard->getPHID()) + ->replaceQueryParam('columnKey', $column_key); $create_button = id(new PHUIButtonView()) ->setTag('a') ->setHref($create_uri) + ->setIcon('fa-plus') + ->setColor(PHUIButtonView::GREY) ->setWorkflow(true) ->setText(pht('Create Panel')) ->addClass(PHUI::MARGIN_MEDIUM); @@ -129,6 +179,8 @@ final class PhabricatorDashboardRenderingEngine extends Phobject { $add_button = id(new PHUIButtonView()) ->setTag('a') ->setHref($add_uri) + ->setIcon('fa-window-maximize') + ->setColor(PHUIButtonView::GREY) ->setWorkflow(true) ->setText(pht('Add Existing Panel')) ->addClass(PHUI::MARGIN_MEDIUM); diff --git a/src/applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php b/src/applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php new file mode 100644 index 0000000000..8fa3cffb53 --- /dev/null +++ b/src/applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php @@ -0,0 +1,28 @@ +getDashboardPanelContainerPanelPHIDs(); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php new file mode 100644 index 0000000000..5c8ad5743f --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php @@ -0,0 +1,58 @@ +getViewer(), + $this->newApplication(), + PhabricatorPolicyCapability::CAN_EDIT); + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $application = $this->newApplication(); + $can_global = $this->canInstallToGlobalMenu(); + + switch ($this->getMode()) { + case 'global': + if (!$can_global) { + return $this->newGlobalPermissionDialog(); + } else if ($request->isFormPost()) { + return $this->installDashboard($application, null); + } else { + return $this->newGlobalConfirmDialog(); + } + case 'personal': + if ($request->isFormPost()) { + return $this->installDashboard($application, $viewer->getPHID()); + } else { + return $this->newPersonalConfirmDialog(); + } + } + + $global_item = $this->newGlobalMenuItem() + ->setDisabled(!$can_global); + + $menu = $this->newMenuFromItemMap( + array( + 'personal' => $this->newPersonalMenuItem(), + 'global' => $global_item, + )); + + return $this->newApplicationModeDialog() + ->appendChild($menu); + } + + abstract protected function newGlobalPermissionDialog(); + abstract protected function newGlobalConfirmDialog(); + abstract protected function newPersonalConfirmDialog(); + + abstract protected function newPersonalMenuItem(); + abstract protected function newGlobalMenuItem(); + abstract protected function newApplicationModeDialog(); + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php new file mode 100644 index 0000000000..51cd45883b --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php @@ -0,0 +1,85 @@ +newMenuItem() + ->setHeader(pht('Add to Favorites Menu')) + ->setImageIcon('fa-bookmark') + ->addAttribute( + pht( + 'Add this dashboard to the favorites menu in the main '. + 'menu bar.')); + } + + protected function newProfileEngine() { + return new PhabricatorFavoritesProfileMenuEngine(); + } + + protected function newApplication() { + return new PhabricatorFavoritesApplication(); + } + + protected function newApplicationModeDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Favorites Menu')); + } + + protected function newPersonalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Personal Favorites')) + ->setImageIcon('fa-user') + ->addAttribute( + pht( + 'Add this dashboard to your list of personal favorite menu items, '. + 'visible to only you.')); + } + + protected function newGlobalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Global Favorites')) + ->setImageIcon('fa-globe') + ->addAttribute( + pht( + 'Add this dashboard to the global favorites menu, visible to all '. + 'users.')); + } + + protected function newGlobalPermissionDialog() { + return $this->newDialog() + ->setTitle(pht('No Permission')) + ->appendParagraph( + pht( + 'You do not have permission to install items on the global '. + 'favorites menu.')); + } + + protected function newGlobalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Global Favorites')) + ->appendParagraph( + pht( + 'Add dashboard %s as a global menu item in the favorites menu?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Favorites')); + } + + protected function newPersonalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Personal Favorites')) + ->appendParagraph( + pht( + 'Add dashboard %s as a personal menu item in the favorites menu?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Favorites')); + } + + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php new file mode 100644 index 0000000000..28731a601e --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php @@ -0,0 +1,83 @@ +newMenuItem() + ->setHeader(pht('Add to Home Page Menu')) + ->setImageIcon('fa-home') + ->addAttribute( + pht( + 'Add this dashboard to the menu on the home page.')); + } + + protected function newProfileEngine() { + return new PhabricatorHomeProfileMenuEngine(); + } + + protected function newApplication() { + return new PhabricatorHomeApplication(); + } + + protected function newApplicationModeDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Home Menu')); + } + + protected function newPersonalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Personal Home Menu')) + ->setImageIcon('fa-user') + ->addAttribute( + pht( + 'Add this dashboard to your list of personal home menu items, '. + 'visible to only you.')); + } + + protected function newGlobalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Global Home Menu')) + ->setImageIcon('fa-globe') + ->addAttribute( + pht( + 'Add this dashboard to the global home menu, visible to all '. + 'users.')); + } + + protected function newGlobalPermissionDialog() { + return $this->newDialog() + ->setTitle(pht('No Permission')) + ->appendParagraph( + pht( + 'You do not have permission to install items on the global home '. + 'menu.')); + } + + protected function newGlobalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Global Home Page')) + ->appendParagraph( + pht( + 'Add dashboard %s as a global menu item on the home page?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Home')); + } + + protected function newPersonalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Personal Home Page')) + ->appendParagraph( + pht( + 'Add dashboard %s as a personal menu item on your home page?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Home')); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php new file mode 100644 index 0000000000..fb77cdebe3 --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php @@ -0,0 +1,143 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function setDashboard(PhabricatorDashboard $dashboard) { + $this->dashboard = $dashboard; + return $this; + } + + final public function getDashboard() { + return $this->dashboard; + } + + final public function setMode($mode) { + $this->mode = $mode; + return $this; + } + + final public function getMode() { + return $this->mode; + } + + final public function setRequest(AphrontRequest $request) { + $this->request = $request; + return $this; + } + + final public function getRequest() { + return $this->request; + } + + final public function getWorkflowKey() { + return $this->getPhobjectClassConstant('WORKFLOWKEY', 32); + } + + final public static function getAllWorkflows() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getWorkflowKey') + ->setSortMethod('getOrder') + ->execute(); + } + + final public function getWorkflowMenuItem() { + return $this->newWorkflowMenuItem(); + } + + abstract public function getOrder(); + abstract protected function newWorkflowMenuItem(); + + final protected function newMenuItem() { + return id(new PHUIObjectItemView()) + ->setClickable(true); + } + + abstract public function handleRequest(AphrontRequest $request); + + final protected function newDialog() { + $dashboard = $this->getDashboard(); + + return id(new AphrontDialogView()) + ->setViewer($this->getViewer()) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($dashboard->getURI()); + } + + final protected function newMenuFromItemMap(array $map) { + $viewer = $this->getViewer(); + $dashboard = $this->getDashboard(); + + $menu = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setBig(true); + + foreach ($map as $key => $item) { + $item->setHref( + urisprintf( + '/dashboard/install/%d/%s/%s/', + $dashboard->getID(), + $this->getWorkflowKey(), + $key)); + + $menu->addItem($item); + } + + return $menu; + } + + abstract protected function newProfileEngine(); + + final protected function installDashboard($profile_object, $custom_phid) { + $dashboard = $this->getDashboard(); + $engine = $this->newProfileEngine() + ->setProfileObject($profile_object); + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $config = PhabricatorProfileMenuItemConfiguration::initializeNewItem( + $profile_object, + new PhabricatorDashboardProfileMenuItem(), + $custom_phid); + + $config->setMenuItemProperty('dashboardPHID', $dashboard->getPHID()); + + $xactions = array(); + + $editor = id(new PhabricatorProfileMenuEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($config, $xactions); + + $done_uri = $engine->getItemURI(urisprintf('view/%d/', $config->getID())); + + return id(new AphrontRedirectResponse()) + ->setURI($done_uri); + } + + final protected function getDashboardDisplayName() { + $dashboard = $this->getDashboard(); + return phutil_tag('strong', array(), $dashboard->getName()); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php new file mode 100644 index 0000000000..eb1be21954 --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php @@ -0,0 +1,99 @@ +getViewer(); + + $target_identifier = null; + + $target_tokens = $request->getArr('target'); + if ($target_tokens) { + $target_identifier = head($target_tokens); + } + + if (!strlen($target_identifier)) { + $target_identifier = $request->getStr('target'); + } + + if (!strlen($target_identifier)) { + $target_identifier = $this->getMode(); + } + + $target = null; + if (strlen($target_identifier)) { + $targets = array(); + + if (ctype_digit($target_identifier)) { + $targets = $this->newQuery() + ->setViewer($viewer) + ->withIDs(array((int)$target_identifier)) + ->execute(); + } + + if (!$targets) { + $targets = $this->newQuery() + ->setViewer($viewer) + ->withPHIDs(array($target_identifier)) + ->execute(); + } + + if ($targets) { + $target = head($targets); + } + } + + if ($target) { + $target_phid = $target->getPHID(); + } else { + $target_phid = null; + } + + if ($target) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $target, + PhabricatorPolicyCapability::CAN_EDIT); + } else { + $can_edit = null; + } + + if ($request->isFormPost() && $target && $can_edit) { + if ($request->getBool('confirm')) { + return $this->installDashboard($target, null); + } else { + return $this->newConfirmDialog($target) + ->addHiddenInput('confirm', 1) + ->addHiddenInput('target', $target_phid); + } + } + + $errors = array(); + if (strlen($target_identifier)) { + if (!$target) { + $errors[] = pht('Choose a valid object.'); + } else if (!$can_edit) { + $errors[] = pht( + 'You do not have permission to edit the selected object. '. + 'You can only install dashboards on objects you can edit.'); + } + } else if ($request->getBool('pick')) { + $errors[] = pht( + 'Choose an object to install this dashboard on.'); + } + + $form = $this->newObjectSelectionForm($target) + ->addHiddenInput('pick', 1); + + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Project Menu')) + ->setErrors($errors) + ->appendForm($form) + ->addSubmitButton(pht('Continue')); + } +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php new file mode 100644 index 0000000000..b5f06684ec --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php @@ -0,0 +1,62 @@ +newMenuItem() + ->setHeader(pht('Add to Portal Menu')) + ->setImageIcon('fa-compass') + ->addAttribute( + pht('Add this dashboard to the menu on a portal.')); + } + + protected function newProfileEngine() { + return new PhabricatorDashboardPortalProfileMenuEngine(); + } + + protected function newQuery() { + return new PhabricatorDashboardPortalQuery(); + } + + protected function newConfirmDialog($object) { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Portal Menu')) + ->appendParagraph( + pht( + 'Add the dashboard %s to portal %s?', + $this->getDashboardDisplayName(), + phutil_tag('strong', array(), $object->getName()))) + ->addSubmitButton(pht('Add to Portal')); + } + protected function newObjectSelectionForm($object) { + $viewer = $this->getViewer(); + + if ($object) { + $tokenizer_value = array($object->getPHID()); + } else { + $tokenizer_value = array(); + } + + return id(new AphrontFormView()) + ->setViewer($viewer) + ->appendInstructions( + pht( + 'Select which portal you want to add the dashboard %s to.', + $this->getDashboardDisplayName())) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('target') + ->setLimit(1) + ->setLabel(pht('Add to Portal')) + ->setValue($tokenizer_value) + ->setDatasource(new PhabricatorDashboardPortalDatasource())); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php new file mode 100644 index 0000000000..aab70af2cb --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php @@ -0,0 +1,63 @@ +newMenuItem() + ->setHeader(pht('Add to Project Menu')) + ->setImageIcon('fa-briefcase') + ->addAttribute( + pht('Add this dashboard to the menu for a project.')); + } + + protected function newProfileEngine() { + return new PhabricatorProjectProfileMenuEngine(); + } + + protected function newQuery() { + return new PhabricatorProjectQuery(); + } + + protected function newConfirmDialog($object) { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Project Menu')) + ->appendParagraph( + pht( + 'Add the dashboard %s to the menu for project %s?', + $this->getDashboardDisplayName(), + phutil_tag('strong', array(), $object->getName()))) + ->addSubmitButton(pht('Add to Project')); + } + + protected function newObjectSelectionForm($object) { + $viewer = $this->getViewer(); + + if ($object) { + $tokenizer_value = array($object->getPHID()); + } else { + $tokenizer_value = array(); + } + + return id(new AphrontFormView()) + ->setViewer($viewer) + ->appendInstructions( + pht( + 'Select which project menu you want to add the dashboard %s to.', + $this->getDashboardDisplayName())) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('target') + ->setLimit(1) + ->setLabel(pht('Add to Project')) + ->setValue($tokenizer_value) + ->setDatasource(new PhabricatorProjectDatasource())); + } + +} diff --git a/src/applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php b/src/applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php new file mode 100644 index 0000000000..02ad2c2740 --- /dev/null +++ b/src/applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php @@ -0,0 +1,12 @@ + + */ + public function getDashboardPanelContainerPanelPHIDs(); + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php new file mode 100644 index 0000000000..3bf17d1a7c --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php @@ -0,0 +1,43 @@ +columnKey = $column_key; + return $this; + } + + public function getColumnKey() { + return $this->columnKey; + } + + public function addClass($class) { + $this->classes[] = $class; + return $this; + } + + public function getClasses() { + return $this->classes; + } + + public function setPanelRefs(array $refs) { + assert_instances_of($refs, 'PhabricatorDashboardPanelRef'); + $this->refs = $refs; + return $this; + } + + public function addPanelRef(PhabricatorDashboardPanelRef $ref) { + $this->refs[] = $ref; + return $this; + } + + public function getPanelRefs() { + return $this->refs; + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php new file mode 100644 index 0000000000..a2a05ea633 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php @@ -0,0 +1,23 @@ +newColumn() + ->setColumnKey('main'), + ); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php new file mode 100644 index 0000000000..b586d5b8b0 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php @@ -0,0 +1,27 @@ +newColumn() + ->setColumnKey('left') + ->addClass('half'), + $this->newColumn() + ->setColumnKey('right') + ->addClass('half'), + ); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php deleted file mode 100644 index 0913be81d5..0000000000 --- a/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php +++ /dev/null @@ -1,164 +0,0 @@ -layoutMode = $mode; - return $this; - } - public function getLayoutMode() { - return $this->layoutMode; - } - - public function setPanelLocation($which_column, $panel_phid) { - $this->panelLocations[$which_column][] = $panel_phid; - return $this; - } - - public function setPanelLocations(array $locations) { - $this->panelLocations = $locations; - return $this; - } - - public function getPanelLocations() { - return $this->panelLocations; - } - - public function replacePanel($old_phid, $new_phid) { - $locations = $this->getPanelLocations(); - foreach ($locations as $column => $panel_phids) { - foreach ($panel_phids as $key => $panel_phid) { - if ($panel_phid == $old_phid) { - $locations[$column][$key] = $new_phid; - } - } - } - return $this->setPanelLocations($locations); - } - - public function removePanel($panel_phid) { - $panel_location_grid = $this->getPanelLocations(); - foreach ($panel_location_grid as $column => $panel_columns) { - $found_old_column = array_search($panel_phid, $panel_columns); - if ($found_old_column !== false) { - $new_panel_columns = $panel_columns; - array_splice( - $new_panel_columns, - $found_old_column, - 1, - array()); - $panel_location_grid[$column] = $new_panel_columns; - break; - } - } - $this->setPanelLocations($panel_location_grid); - } - - public function getDefaultPanelLocations() { - switch ($this->getLayoutMode()) { - case self::MODE_HALF_AND_HALF: - case self::MODE_THIRD_AND_THIRDS: - case self::MODE_THIRDS_AND_THIRD: - $locations = array(array(), array()); - break; - case self::MODE_FULL: - default: - $locations = array(array()); - break; - } - return $locations; - } - - public function getColumnClass($column_index, $grippable = false) { - switch ($this->getLayoutMode()) { - case self::MODE_HALF_AND_HALF: - $class = 'half'; - break; - case self::MODE_THIRD_AND_THIRDS: - if ($column_index) { - $class = 'thirds'; - } else { - $class = 'third'; - } - break; - case self::MODE_THIRDS_AND_THIRD: - if ($column_index) { - $class = 'third'; - } else { - $class = 'thirds'; - } - break; - case self::MODE_FULL: - default: - $class = null; - break; - } - if ($grippable) { - $class .= ' grippable'; - } - return $class; - } - - public function isMultiColumnLayout() { - return $this->getLayoutMode() != self::MODE_FULL; - } - - public function getColumnSelectOptions() { - $options = array(); - - switch ($this->getLayoutMode()) { - case self::MODE_HALF_AND_HALF: - case self::MODE_THIRD_AND_THIRDS: - case self::MODE_THIRDS_AND_THIRD: - return array( - 0 => pht('Left'), - 1 => pht('Right'), - ); - break; - case self::MODE_FULL: - throw new Exception(pht('There is only one column in mode full.')); - break; - default: - throw new Exception(pht('Unknown layout mode!')); - break; - } - - return $options; - } - - public static function getLayoutModeSelectOptions() { - return array( - self::MODE_FULL => pht('One full-width column'), - self::MODE_HALF_AND_HALF => pht('Two columns, 1/2 and 1/2'), - self::MODE_THIRD_AND_THIRDS => pht('Two columns, 1/3 and 2/3'), - self::MODE_THIRDS_AND_THIRD => pht('Two columns, 2/3 and 1/3'), - ); - } - - public static function newFromDictionary(array $dict) { - $layout_config = id(new PhabricatorDashboardLayoutConfig()) - ->setLayoutMode(idx($dict, 'layoutMode', self::MODE_FULL)); - $layout_config->setPanelLocations(idx( - $dict, - 'panelLocations', - $layout_config->getDefaultPanelLocations())); - - return $layout_config; - } - - public function toDictionary() { - return array( - 'layoutMode' => $this->getLayoutMode(), - 'panelLocations' => $this->getPanelLocations(), - ); - } - -} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php new file mode 100644 index 0000000000..965adbe1d7 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php @@ -0,0 +1,34 @@ +getPhobjectClassConstant('LAYOUTMODE', 32); + } + + public function getLayoutModeOrder() { + return 1000; + } + + abstract public function getLayoutModeName(); + abstract public function getLayoutModeColumns(); + + final protected function newColumn() { + return new PhabricatorDashboardColumn(); + } + + final public static function getAllLayoutModes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getLayoutModeKey') + ->setSortMethod('getLayoutModeOrder') + ->execute(); + } + + final public static function getLayoutModeMap() { + $modes = self::getAllLayoutModes(); + return mpull($modes, 'getLayoutModeName', 'getLayoutModeKey'); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php new file mode 100644 index 0000000000..7e02bcd6aa --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php @@ -0,0 +1,27 @@ +newColumn() + ->setColumnKey('left') + ->addClass('third'), + $this->newColumn() + ->setColumnKey('right') + ->addClass('thirds'), + ); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php new file mode 100644 index 0000000000..96a912e30a --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php @@ -0,0 +1,45 @@ +panelPHID = $panel_phid; + return $this; + } + + public function getPanelPHID() { + return $this->panelPHID; + } + + public function setColumnKey($column_key) { + $this->columnKey = $column_key; + return $this; + } + + public function getColumnKey() { + return $this->columnKey; + } + + public function setPanelKey($panel_key) { + $this->panelKey = $panel_key; + return $this; + } + + public function getPanelKey() { + return $this->panelKey; + } + + public function toDictionary() { + return array( + 'panelKey' => $this->getPanelKey(), + 'panelPHID' => $this->getPanelPHID(), + 'columnKey' => $this->getColumnKey(), + ); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php new file mode 100644 index 0000000000..2f74d0a937 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php @@ -0,0 +1,163 @@ +getLayoutModeColumns(); + $columns = mpull($columns, null, 'getColumnKey'); + $default_column = head($columns); + + $panels = idx($config, 'panels'); + if (!is_array($panels)) { + $panels = array(); + } + + $seen_panels = array(); + $refs = array(); + foreach ($panels as $panel) { + $panel_phid = idx($panel, 'panelPHID'); + if (!strlen($panel_phid)) { + continue; + } + + $panel_key = idx($panel, 'panelKey'); + if (!strlen($panel_key)) { + continue; + } + + if (isset($seen_panels[$panel_key])) { + continue; + } + $seen_panels[$panel_key] = true; + + $column_key = idx($panel, 'columnKey'); + $column = idx($columns, $column_key, $default_column); + + $ref = id(new PhabricatorDashboardPanelRef()) + ->setPanelPHID($panel_phid) + ->setPanelKey($panel_key) + ->setColumnKey($column->getColumnKey()); + + $column->addPanelRef($ref); + $refs[] = $ref; + } + + $list = new self(); + + $list->columns = $columns; + $list->refs = $refs; + + return $list; + } + + public function getColumns() { + return $this->columns; + } + + public function getPanelRefs() { + return $this->refs; + } + + public function getPanelRef($panel_key) { + foreach ($this->getPanelRefs() as $ref) { + if ($ref->getPanelKey() === $panel_key) { + return $ref; + } + } + + return null; + } + + public function toDictionary() { + return array_values(mpull($this->getPanelRefs(), 'toDictionary')); + } + + public function newPanelRef( + PhabricatorDashboardPanel $panel, + $column_key = null) { + + if ($column_key === null) { + $column_key = head_key($this->columns); + } + + $ref = id(new PhabricatorDashboardPanelRef()) + ->setPanelKey($this->newPanelKey()) + ->setPanelPHID($panel->getPHID()) + ->setColumnKey($column_key); + + $this->refs[] = $ref; + + return $ref; + } + + public function removePanelRef(PhabricatorDashboardPanelRef $target) { + foreach ($this->refs as $key => $ref) { + if ($ref->getPanelKey() !== $target->getPanelKey()) { + continue; + } + + unset($this->refs[$key]); + return $ref; + } + + return null; + } + + public function movePanelRef( + PhabricatorDashboardPanelRef $target, + $column_key, + PhabricatorDashboardPanelRef $after = null) { + + $target->setColumnKey($column_key); + + $results = array(); + + if (!$after) { + $results[] = $target; + } + + foreach ($this->refs as $ref) { + if ($ref->getPanelKey() === $target->getPanelKey()) { + continue; + } + + $results[] = $ref; + + if ($after) { + if ($ref->getPanelKey() === $after->getPanelKey()) { + $results[] = $target; + } + } + } + + $this->refs = $results; + + $column_map = mgroup($results, 'getColumnKey'); + foreach ($this->columns as $column_key => $column) { + $column->setPanelRefs(idx($column_map, $column_key, array())); + } + + return $ref; + } + + private function newPanelKey() { + return Filesystem::readRandomCharacters(8); + } + + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php new file mode 100644 index 0000000000..91b07276d4 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php @@ -0,0 +1,27 @@ +newColumn() + ->setColumnKey('left') + ->addClass('thirds'), + $this->newColumn() + ->setColumnKey('right') + ->addClass('third'), + ); + } + +} diff --git a/src/applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php b/src/applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php new file mode 100644 index 0000000000..6d47a4c219 --- /dev/null +++ b/src/applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php @@ -0,0 +1,117 @@ +getMenuItemProperty('name'); + + if (strlen($name)) { + return $name; + } + + return $this->getDefaultName(); + } + + public function buildEditEngineFields( + PhabricatorProfileMenuItemConfiguration $config) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setPlaceholder($this->getDefaultName()) + ->setValue($config->getMenuItemProperty('name')), + ); + } + + protected function newMenuItemViewList( + PhabricatorProfileMenuItemConfiguration $config) { + $viewer = $this->getViewer(); + + if (!$viewer->isLoggedIn()) { + return array(); + } + + $uri = $this->getItemViewURI($config); + $name = $this->getDisplayName($config); + $icon = 'fa-pencil'; + + $item = $this->newItemView() + ->setURI($uri) + ->setName($name) + ->setIcon($icon); + + return array( + $item, + ); + } + + public function newPageContent( + PhabricatorProfileMenuItemConfiguration $config) { + $viewer = $this->getViewer(); + $engine = $this->getEngine(); + $portal = $engine->getProfileObject(); + $controller = $engine->getController(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Manage Portal')); + + $edit_uri = urisprintf( + '/portal/edit/%d/', + $portal->getID()); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $portal, + PhabricatorPolicyCapability::CAN_EDIT); + + $curtain = $controller->newCurtainView($portal) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Portal')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)); + + $timeline = $controller->newTimelineView() + ->setShouldTerminate(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn( + array( + $timeline, + )); + + return $view; + } + + +} diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php index 34c7b1c3ee..8f9134e642 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php @@ -5,7 +5,6 @@ abstract class PhabricatorDashboardPanelType extends Phobject { abstract public function getPanelTypeKey(); abstract public function getPanelTypeName(); abstract public function getPanelTypeDescription(); - abstract public function getFieldSpecifications(); abstract public function getIcon(); abstract public function renderPanelContent( @@ -53,4 +52,15 @@ abstract class PhabricatorDashboardPanelType extends Phobject { ->execute(); } + final public function getEditEngineFields(PhabricatorDashboardPanel $panel) { + return $this->newEditEngineFields($panel); + } + + abstract protected function newEditEngineFields( + PhabricatorDashboardPanel $panel); + + public function getSubpanelPHIDs(PhabricatorDashboardPanel $panel) { + return array(); + } + } diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php index 90fe93e2b4..2964d1ccac 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php @@ -21,22 +21,29 @@ final class PhabricatorDashboardQueryPanelType 'revisions you need to review.'); } - public function getFieldSpecifications() { + protected function newEditEngineFields(PhabricatorDashboardPanel $panel) { + $application_field = + id(new PhabricatorDashboardQueryPanelApplicationEditField()) + ->setKey('class') + ->setLabel(pht('Search For')) + ->setTransactionType( + PhabricatorDashboardQueryPanelApplicationTransaction::TRANSACTIONTYPE) + ->setValue($panel->getProperty('class', '')); + + $application_id = $application_field->getControlID(); + + $query_field = + id(new PhabricatorDashboardQueryPanelQueryEditField()) + ->setKey('key') + ->setLabel(pht('Query')) + ->setApplicationControlID($application_id) + ->setTransactionType( + PhabricatorDashboardQueryPanelQueryTransaction::TRANSACTIONTYPE) + ->setValue($panel->getProperty('key', '')); + return array( - 'class' => array( - 'name' => pht('Search For'), - 'type' => 'search.application', - ), - 'key' => array( - 'name' => pht('Query'), - 'type' => 'search.query', - 'control.application' => 'class', - ), - 'limit' => array( - 'name' => pht('Limit'), - 'caption' => pht('Leave this blank for the default number of items.'), - 'type' => 'text', - ), + $application_field, + $query_field, ); } diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php index b4772ba83f..682cc11d78 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php @@ -12,20 +12,15 @@ final class PhabricatorDashboardTabsPanelType } public function getIcon() { - return 'fa-window-maximize'; + return 'fa-columns'; } public function getPanelTypeDescription() { return pht('Use tabs to switch between several other panels.'); } - public function getFieldSpecifications() { - return array( - 'config' => array( - 'name' => pht('Tabs'), - 'type' => 'dashboard.tabs', - ), - ); + protected function newEditEngineFields(PhabricatorDashboardPanel $panel) { + return array(); } public function shouldRenderAsync() { @@ -33,37 +28,39 @@ final class PhabricatorDashboardTabsPanelType return false; } + public function getPanelConfiguration(PhabricatorDashboardPanel $panel) { + $config = $panel->getProperty('config'); + + if (!is_array($config)) { + // NOTE: The older version of this panel stored raw JSON. + try { + $config = phutil_json_decode($config); + } catch (PhutilJSONParserException $ex) { + $config = array(); + } + } + + return $config; + } + public function renderPanelContent( PhabricatorUser $viewer, PhabricatorDashboardPanel $panel, PhabricatorDashboardPanelRenderingEngine $engine) { - $config = $panel->getProperty('config'); - if (!is_array($config)) { - // NOTE: The older version of this panel stored raw JSON. - $config = phutil_json_decode($config); + $is_edit = $engine->isEditMode(); + $config = $this->getPanelConfiguration($panel); + + $context_object = $engine->getContextObject(); + if (!$context_object) { + $context_object = $panel; } + $context_phid = $context_object->getPHID(); + $list = id(new PHUIListView()) ->setType(PHUIListView::NAVBAR_LIST); - $selected = 0; - - $node_ids = array(); - foreach ($config as $idx => $tab_spec) { - $node_ids[$idx] = celerity_generate_unique_node_id(); - } - - foreach ($config as $idx => $tab_spec) { - $list->addMenuItem( - id(new PHUIListItemView()) - ->setHref('#') - ->setSelected($idx == $selected) - ->addSigil('dashboard-tab-panel-tab') - ->setMetadata(array('idx' => $idx)) - ->setName(idx($tab_spec, 'name', pht('Nameless Tab')))); - } - $ids = ipull($config, 'panelID'); if ($ids) { $panels = id(new PhabricatorDashboardPanelQuery()) @@ -74,6 +71,140 @@ final class PhabricatorDashboardTabsPanelType $panels = array(); } + $id = $panel->getID(); + + $add_uri = urisprintf('/dashboard/panel/tabs/%d/add/', $id); + $add_uri = id(new PhutilURI($add_uri)) + ->replaceQueryParam('contextPHID', $context_phid); + + $remove_uri = urisprintf('/dashboard/panel/tabs/%d/remove/', $id); + $remove_uri = id(new PhutilURI($remove_uri)) + ->replaceQueryParam('contextPHID', $context_phid); + + $rename_uri = urisprintf('/dashboard/panel/tabs/%d/rename/', $id); + $rename_uri = id(new PhutilURI($rename_uri)) + ->replaceQueryParam('contextPHID', $context_phid); + + $selected = 0; + + $last_idx = null; + foreach ($config as $idx => $tab_spec) { + $panel_id = idx($tab_spec, 'panelID'); + $subpanel = idx($panels, $panel_id); + + $name = idx($tab_spec, 'name'); + if (!strlen($name)) { + if ($subpanel) { + $name = $subpanel->getName(); + } + } + + if (!strlen($name)) { + $name = pht('Unnamed Tab'); + } + + $tab_view = id(new PHUIListItemView()) + ->setHref('#') + ->setSelected((string)$idx === (string)$selected) + ->addSigil('dashboard-tab-panel-tab') + ->setMetadata(array('panelKey' => $idx)) + ->setName($name); + + if ($is_edit) { + $dropdown_menu = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + $remove_tab_uri = id(clone $remove_uri) + ->replaceQueryParam('target', $idx); + + $rename_tab_uri = id(clone $rename_uri) + ->replaceQueryParam('target', $idx); + + if ($subpanel) { + $details_uri = $subpanel->getURI(); + } else { + $details_uri = null; + } + + $edit_uri = urisprintf( + '/dashboard/panel/edit/%d/', + $panel_id); + if ($subpanel) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $subpanel, + PhabricatorPolicyCapability::CAN_EDIT); + } else { + $can_edit = false; + } + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Rename Tab')) + ->setIcon('fa-pencil') + ->setHref($rename_tab_uri) + ->setWorkflow(true)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Remove Tab')) + ->setIcon('fa-times') + ->setHref($remove_tab_uri) + ->setWorkflow(true)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Panel')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setWorkflow(true) + ->setDisabled(!$can_edit)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Panel Details')) + ->setIcon('fa-window-maximize') + ->setHref($details_uri) + ->setDisabled(!$subpanel)); + + $tab_view + ->setActionIcon('fa-caret-down', '#') + ->setDropdownMenu($dropdown_menu); + } + + $list->addMenuItem($tab_view); + + $last_idx = $idx; + } + + if ($is_edit) { + $actions = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + $add_last_uri = clone $add_uri; + if ($last_idx) { + $add_last_uri->replaceQueryParam('after', $last_idx); + } + + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Add Existing Panel')) + ->setIcon('fa-window-maximize') + ->setHref($add_last_uri) + ->setWorkflow(true)); + + $list->addMenuItem( + id(new PHUIListItemView()) + ->setHref('#') + ->setSelected(false) + ->setName(pht('Add Tab...')) + ->setDropdownMenu($actions)); + } + $parent_phids = $engine->getParentPanelPHIDs(); $parent_phids[] = $panel->getPHID(); @@ -84,32 +215,62 @@ final class PhabricatorDashboardTabsPanelType // remains selected across page loads. $content = array(); + $panel_list = array(); $no_headers = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NONE; foreach ($config as $idx => $tab_spec) { $panel_id = idx($tab_spec, 'panelID'); - $panel = idx($panels, $panel_id); + $subpanel = idx($panels, $panel_id); - if ($panel) { + if ($subpanel) { $panel_content = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setEnableAsyncRendering(true) + ->setContextObject($context_object) ->setParentPanelPHIDs($parent_phids) - ->setPanel($panel) - ->setPanelPHID($panel->getPHID()) + ->setPanel($subpanel) + ->setPanelPHID($subpanel->getPHID()) ->setHeaderMode($no_headers) - ->setMovable(false) ->renderPanel(); } else { $panel_content = pht('(Invalid Panel)'); } + $content_id = celerity_generate_unique_node_id(); + $content[] = phutil_tag( 'div', array( - 'id' => $node_ids[$idx], + 'id' => $content_id, 'style' => ($idx == $selected) ? null : 'display: none', ), $panel_content); + + $panel_list[] = array( + 'panelKey' => (string)$idx, + 'panelContentID' => $content_id, + ); + } + + if (!$content) { + if ($is_edit) { + $message = pht( + 'This tab panel does not have any tabs yet. Use "Add Tab..." to '. + 'create or place a tab.'); + } else { + $message = pht( + 'This tab panel does not have any tabs yet.'); + } + + $content = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->setErrors( + array( + $message, + )); + + $content = id(new PHUIBoxView()) + ->addClass('mlt mlb') + ->appendChild($content); } Javelin::initBehavior('dashboard-tab-panel'); @@ -119,7 +280,7 @@ final class PhabricatorDashboardTabsPanelType array( 'sigil' => 'dashboard-tab-panel-container', 'meta' => array( - 'panels' => $node_ids, + 'panels' => $panel_list, ), ), array( @@ -128,4 +289,24 @@ final class PhabricatorDashboardTabsPanelType )); } + public function getSubpanelPHIDs(PhabricatorDashboardPanel $panel) { + $config = $this->getPanelConfiguration($panel); + + $panel_ids = array(); + foreach ($config as $tab_key => $tab_spec) { + $panel_ids[] = $tab_spec['panelID']; + } + + if ($panel_ids) { + $panels = id(new PhabricatorDashboardPanelQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIDs($panel_ids) + ->execute(); + } else { + $panels = array(); + } + + return mpull($panels, 'getPHID'); + } + } diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php index 5ecde8501f..8f135d106d 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php @@ -12,21 +12,23 @@ final class PhabricatorDashboardTextPanelType } public function getIcon() { - return 'fa-paragraph'; + return 'fa-file-text-o'; } public function getPanelTypeDescription() { return pht( - 'Add some static text to the dashboard. This can be used to '. - 'provide instructions or context.'); + 'Add a text panel to the dashboard to provide instructions or '. + 'context.'); } - public function getFieldSpecifications() { + protected function newEditEngineFields(PhabricatorDashboardPanel $panel) { return array( - 'text' => array( - 'name' => pht('Text'), - 'type' => 'remarkup', - ), + id(new PhabricatorRemarkupEditField()) + ->setKey('text') + ->setLabel(pht('Text')) + ->setTransactionType( + PhabricatorDashboardTextPanelTextTransaction::TRANSACTIONTYPE) + ->setValue($panel->getProperty('text', '')), ); } diff --git a/src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php b/src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php index 49a5091412..d84db72a25 100644 --- a/src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php +++ b/src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php @@ -35,9 +35,11 @@ final class PhabricatorDashboardPanelPHIDType extends PhabricatorPHIDType { $name = $panel->getName(); $monogram = $panel->getMonogram(); - $handle->setName($panel->getMonogram()); - $handle->setFullName("{$monogram} {$name}"); - $handle->setURI("/{$monogram}"); + $handle + ->setIcon('fa-window-maximize') + ->setName($name) + ->setFullName("{$monogram} {$name}") + ->setURI($panel->getURI()); if ($panel->getIsArchived()) { $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); diff --git a/src/applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php b/src/applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php new file mode 100644 index 0000000000..378748d96b --- /dev/null +++ b/src/applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php @@ -0,0 +1,43 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $portal = $objects[$phid]; + + $handle + ->setIcon('fa-compass') + ->setName($portal->getName()) + ->setURI($portal->getURI()); + } + } + +} diff --git a/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php b/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php index ac72c9ce6f..dd32d1c9dc 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php +++ b/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php @@ -34,12 +34,6 @@ final class PhabricatorDashboardPanelQuery return $this; } - public function withNameNgrams($ngrams) { - return $this->withNgramsConstraint( - id(new PhabricatorDashboardPanelNgrams()), - $ngrams); - } - protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } @@ -62,35 +56,35 @@ final class PhabricatorDashboardPanelQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'panel.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'panel.phid IN (%Ls)', $this->phids); } if ($this->archived !== null) { $where[] = qsprintf( $conn, - 'isArchived = %d', + 'panel.isArchived = %d', (int)$this->archived); } if ($this->panelTypes !== null) { $where[] = qsprintf( $conn, - 'panelType IN (%Ls)', + 'panel.panelType IN (%Ls)', $this->panelTypes); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, - 'authorPHID IN (%Ls)', + 'panel.authorPHID IN (%Ls)', $this->authorPHIDs); } @@ -102,7 +96,7 @@ final class PhabricatorDashboardPanelQuery } protected function getPrimaryTableAlias() { - return 'dashboard_panel'; + return 'panel'; } } diff --git a/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php index 87e908b9c2..dc50fa5d66 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php +++ b/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php @@ -42,20 +42,12 @@ final class PhabricatorDashboardPanelSearchEngine $query->withAuthorPHIDs($map['authorPHIDs']); } - if ($map['name'] !== null) { - $query->withNameNgrams($map['name']); - } - return $query; } protected function buildCustomSearchFields() { return array( - id(new PhabricatorSearchTextField()) - ->setLabel(pht('Name Contains')) - ->setKey('name') - ->setDescription(pht('Search for panels by name substring.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Authored By')) ->setKey('authorPHIDs') diff --git a/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php b/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php new file mode 100644 index 0000000000..857c4dc215 --- /dev/null +++ b/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php @@ -0,0 +1,68 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + + public function newResultObject() { + return new PhabricatorDashboardPortal(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'portal.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'portal.phid IN (%Ls)', + $this->phids); + } + + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'portal.status IN (%Ls)', + $this->statuses); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorDashboardApplication'; + } + + protected function getPrimaryTableAlias() { + return 'portal'; + } + +} diff --git a/src/applications/dashboard/query/PhabricatorDashboardPortalSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardPortalSearchEngine.php new file mode 100644 index 0000000000..c1633f2e97 --- /dev/null +++ b/src/applications/dashboard/query/PhabricatorDashboardPortalSearchEngine.php @@ -0,0 +1,78 @@ +newQuery(); + return $query; + } + + protected function buildCustomSearchFields() { + return array(); + } + + protected function getURI($path) { + return '/portal/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array(); + + $names['all'] = pht('All Portals'); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + $viewer = $this->requireViewer(); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $portals, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($portals, 'PhabricatorDashboardPortal'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($portals as $portal) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($portal->getObjectName()) + ->setHeader($portal->getName()) + ->setHref($portal->getURI()) + ->setObject($portal); + + $list->addItem($item); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No portals found.')); + } + +} diff --git a/src/applications/dashboard/query/PhabricatorDashboardPortalTransactionQuery.php b/src/applications/dashboard/query/PhabricatorDashboardPortalTransactionQuery.php new file mode 100644 index 0000000000..f4dff94088 --- /dev/null +++ b/src/applications/dashboard/query/PhabricatorDashboardPortalTransactionQuery.php @@ -0,0 +1,10 @@ +ids = $ids; return $this; @@ -32,27 +29,11 @@ final class PhabricatorDashboardQuery return $this; } - public function needPanels($need_panels) { - $this->needPanels = $need_panels; - return $this; - } - - public function needProjects($need_projects) { - $this->needProjects = $need_projects; - return $this; - } - public function withCanEdit($can_edit) { $this->canEdit = $can_edit; return $this; } - public function withNameNgrams($ngrams) { - return $this->withNgramsConstraint( - id(new PhabricatorDashboardNgrams()), - $ngrams); - } - protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } @@ -74,58 +55,6 @@ final class PhabricatorDashboardQuery ->apply($dashboards); } - if ($this->needPanels) { - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($phids) - ->withEdgeTypes( - array( - PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST, - )); - $edge_query->execute(); - - $panel_phids = $edge_query->getDestinationPHIDs(); - if ($panel_phids) { - // NOTE: We explicitly disable policy exceptions when loading panels. - // If a particular panel is invalid or not visible to the viewer, - // we'll still render the dashboard, just not that panel. - - $panels = id(new PhabricatorDashboardPanelQuery()) - ->setParentQuery($this) - ->setRaisePolicyExceptions(false) - ->setViewer($this->getViewer()) - ->withPHIDs($panel_phids) - ->execute(); - $panels = mpull($panels, null, 'getPHID'); - } else { - $panels = array(); - } - - foreach ($dashboards as $dashboard) { - $dashboard_phids = $edge_query->getDestinationPHIDs( - array($dashboard->getPHID())); - $dashboard_panels = array_select_keys($panels, $dashboard_phids); - - $dashboard->attachPanelPHIDs($dashboard_phids); - $dashboard->attachPanels($dashboard_panels); - } - } - - if ($this->needProjects) { - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($phids) - ->withEdgeTypes( - array( - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, - )); - $edge_query->execute(); - - foreach ($dashboards as $dashboard) { - $project_phids = $edge_query->getDestinationPHIDs( - array($dashboard->getPHID())); - $dashboard->attachProjectPHIDs($project_phids); - } - } - return $dashboards; } @@ -135,28 +64,28 @@ final class PhabricatorDashboardQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'dashboard.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'dashboard.phid IN (%Ls)', $this->phids); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ls)', + 'dashboard.status IN (%Ls)', $this->statuses); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, - 'authorPHID IN (%Ls)', + 'dashboard.authorPHID IN (%Ls)', $this->authorPHIDs); } diff --git a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php index a05d1c4121..ea3f69faab 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php +++ b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php @@ -12,8 +12,7 @@ final class PhabricatorDashboardSearchEngine } public function newQuery() { - return id(new PhabricatorDashboardQuery()) - ->needPanels(true); + return id(new PhabricatorDashboardQuery()); } public function canUseInPanelContext() { @@ -22,10 +21,6 @@ final class PhabricatorDashboardSearchEngine protected function buildCustomSearchFields() { return array( - id(new PhabricatorSearchTextField()) - ->setLabel(pht('Name Contains')) - ->setKey('name') - ->setDescription(pht('Search for dashboards by name substring.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Authored By')) ->setKey('authorPHIDs') @@ -94,10 +89,6 @@ final class PhabricatorDashboardSearchEngine $query->withAuthorPHIDs($map['authorPHIDs']); } - if ($map['name'] !== null) { - $query->withNameNgrams($map['name']); - } - if ($map['editable'] !== null) { $query->withCanEdit($map['editable']); } @@ -122,32 +113,33 @@ final class PhabricatorDashboardSearchEngine $handles = $viewer->loadHandles($phids); + if ($dashboards) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($dashboards, 'getPHID')) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + + $edge_query->execute(); + } + $list = id(new PHUIObjectItemListView()) - ->setUser($viewer); + ->setViewer($viewer); foreach ($dashboards as $dashboard) { - $id = $dashboard->getID(); - $item = id(new PHUIObjectItemView()) - ->setUser($viewer) + ->setViewer($viewer) + ->setObjectName($dashboard->getObjectName()) ->setHeader($dashboard->getName()) - ->setHref($this->getApplicationURI("view/{$id}/")) + ->setHref($dashboard->getURI()) ->setObject($dashboard); - $bg_color = 'bg-dark'; if ($dashboard->isArchived()) { $item->setDisabled(true); $bg_color = 'bg-grey'; - } - - $panels = $dashboard->getPanels(); - foreach ($panels as $panel) { - $item->addAttribute($panel->getName()); - } - - if (empty($panels)) { - $empty = phutil_tag('em', array(), pht('No panels.')); - $item->addAttribute($empty); + } else { + $bg_color = 'bg-dark'; } $icon = id(new PHUIIconView()) @@ -160,6 +152,17 @@ final class PhabricatorDashboardSearchEngine $author_name = $handles[$author_phid]->renderLink(); $item->addByline(pht('Author: %s', $author_name)); + $phid = $dashboard->getPHID(); + $project_phids = $edge_query->getDestinationPHIDs(array($phid)); + $project_handles = $viewer->loadHandles($project_phids); + + $item->addAttribute( + id(new PHUIHandleTagListView()) + ->setLimit(4) + ->setNoDataString(pht('No Tags')) + ->setSlim(true) + ->setHandles($project_handles)); + $list->addItem($item); } @@ -170,24 +173,4 @@ final class PhabricatorDashboardSearchEngine return $result; } - protected function getNewUserBody() { - $create_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Create a Dashboard')) - ->setHref('/dashboard/create/') - ->setColor(PHUIButtonView::GREEN); - - $icon = $this->getApplication()->getIcon(); - $app_name = $this->getApplication()->getName(); - $view = id(new PHUIBigInfoView()) - ->setIcon($icon) - ->setTitle(pht('Welcome to %s', $app_name)) - ->setDescription( - pht('Customize your homepage with different panels and '. - 'search queries.')) - ->addAction($create_button); - - return $view; - } - } diff --git a/src/applications/dashboard/storage/PhabricatorDashboard.php b/src/applications/dashboard/storage/PhabricatorDashboard.php index 53bc2f857d..4081a40caf 100644 --- a/src/applications/dashboard/storage/PhabricatorDashboard.php +++ b/src/applications/dashboard/storage/PhabricatorDashboard.php @@ -10,7 +10,9 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO PhabricatorFlaggableInterface, PhabricatorDestructibleInterface, PhabricatorProjectInterface, - PhabricatorNgramsInterface { + PhabricatorFulltextInterface, + PhabricatorFerretInterface, + PhabricatorDashboardPanelContainerInterface { protected $name; protected $authorPHID; @@ -23,10 +25,7 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO const STATUS_ACTIVE = 'active'; const STATUS_ARCHIVED = 'archived'; - private $panelPHIDs = self::ATTACHABLE; - private $panels = self::ATTACHABLE; - private $edgeProjectPHIDs = self::ATTACHABLE; - + private $panelRefList; public static function initializeNewDashboard(PhabricatorUser $actor) { return id(new PhabricatorDashboard()) @@ -35,9 +34,7 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->setEditPolicy($actor->getPHID()) ->setStatus(self::STATUS_ACTIVE) - ->setAuthorPHID($actor->getPHID()) - ->attachPanels(array()) - ->attachPanelPHIDs(array()); + ->setAuthorPHID($actor->getPHID()); } public static function getStatusNameMap() { @@ -62,66 +59,78 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO ) + parent::getConfiguration(); } - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - PhabricatorDashboardDashboardPHIDType::TYPECONST); + public function getPHIDType() { + return PhabricatorDashboardDashboardPHIDType::TYPECONST; } - public function getLayoutConfigObject() { - return PhabricatorDashboardLayoutConfig::newFromDictionary( - $this->getLayoutConfig()); + public function getRawLayoutMode() { + $config = $this->getRawLayoutConfig(); + return idx($config, 'layoutMode'); } - public function setLayoutConfigFromObject( - PhabricatorDashboardLayoutConfig $object) { - - $this->setLayoutConfig($object->toDictionary()); - - // See PHI385. Dashboard panel mutations rely on changes to the Dashboard - // object persisting when transactions are applied, but this assumption is - // no longer valid after T13054. For now, just save the dashboard - // explicitly. - $this->save(); - - return $this; + public function setRawLayoutMode($mode) { + $config = $this->getRawLayoutConfig(); + $config['layoutMode'] = $mode; + return $this->setRawLayoutConfig($config); } - public function getProjectPHIDs() { - return $this->assertAttached($this->edgeProjectPHIDs); + public function getRawPanels() { + $config = $this->getRawLayoutConfig(); + return idx($config, 'panels'); } - public function attachProjectPHIDs(array $phids) { - $this->edgeProjectPHIDs = $phids; - return $this; + public function setRawPanels(array $panels) { + $config = $this->getRawLayoutConfig(); + $config['panels'] = $panels; + return $this->setRawLayoutConfig($config); } - public function attachPanelPHIDs(array $phids) { - $this->panelPHIDs = $phids; - return $this; + private function getRawLayoutConfig() { + $config = $this->getLayoutConfig(); + + if (!is_array($config)) { + $config = array(); + } + + return $config; } - public function getPanelPHIDs() { - return $this->assertAttached($this->panelPHIDs); - } + private function setRawLayoutConfig(array $config) { + // If a cached panel ref list exists, clear it. + $this->panelRefList = null; - public function attachPanels(array $panels) { - assert_instances_of($panels, 'PhabricatorDashboardPanel'); - $this->panels = $panels; - return $this; - } - - public function getPanels() { - return $this->assertAttached($this->panels); + return $this->setLayoutConfig($config); } public function isArchived() { return ($this->getStatus() == self::STATUS_ARCHIVED); } - public function getViewURI() { - return '/dashboard/view/'.$this->getID().'/'; + public function getURI() { + return urisprintf('/dashboard/view/%d/', $this->getID()); } + public function getObjectName() { + return pht('Dashboard %d', $this->getID()); + } + + public function getPanelRefList() { + if (!$this->panelRefList) { + $this->panelRefList = $this->newPanelRefList(); + } + return $this->panelRefList; + } + + private function newPanelRefList() { + $raw_config = $this->getLayoutConfig(); + return PhabricatorDashboardPanelRefList::newFromDictionary($raw_config); + } + + public function getPanelPHIDs() { + $ref_list = $this->getPanelRefList(); + $phids = mpull($ref_list->getPanelRefs(), 'getPanelPHID'); + return array_unique($phids); + } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -164,28 +173,25 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { - - $this->openTransaction(); - $installs = id(new PhabricatorDashboardInstall())->loadAllWhere( - 'dashboardPHID = %s', - $this->getPHID()); - foreach ($installs as $install) { - $install->delete(); - } - - $this->delete(); - $this->saveTransaction(); + $this->delete(); } +/* -( PhabricatorDashboardPanelContainerInterface )------------------------ */ -/* -( PhabricatorNgramInterface )------------------------------------------ */ + public function getDashboardPanelContainerPanelPHIDs() { + return $this->getPanelPHIDs(); + } +/* -( PhabricatorFulltextInterface )--------------------------------------- */ - public function newNgrams() { - return array( - id(new PhabricatorDashboardNgrams()) - ->setValue($this->getName()), - ); + public function newFulltextEngine() { + return new PhabricatorDashboardFulltextEngine(); + } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + public function newFerretEngine() { + return new PhabricatorDashboardFerretEngine(); } } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardInstall.php b/src/applications/dashboard/storage/PhabricatorDashboardInstall.php deleted file mode 100644 index 318c064808..0000000000 --- a/src/applications/dashboard/storage/PhabricatorDashboardInstall.php +++ /dev/null @@ -1,52 +0,0 @@ - array( - 'applicationClass' => 'text64', - ), - self::CONFIG_KEY_SCHEMA => array( - 'objectPHID' => array( - 'columns' => array('objectPHID', 'applicationClass'), - 'unique' => true, - ), - ), - ) + parent::getConfiguration(); - } - - public static function getDashboard( - PhabricatorUser $viewer, - $object_phid, - $application_class) { - - $dashboard = null; - $dashboard_install = id(new PhabricatorDashboardInstall()) - ->loadOneWhere( - 'objectPHID = %s AND applicationClass = %s', - $object_phid, - $application_class); - if ($dashboard_install) { - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withPHIDs(array($dashboard_install->getDashboardPHID())) - ->needPanels(true) - ->executeOne(); - } - - return $dashboard; - } -} diff --git a/src/applications/dashboard/storage/PhabricatorDashboardNgrams.php b/src/applications/dashboard/storage/PhabricatorDashboardNgrams.php deleted file mode 100644 index 7884ec43f9..0000000000 --- a/src/applications/dashboard/storage/PhabricatorDashboardNgrams.php +++ /dev/null @@ -1,18 +0,0 @@ -setName('') @@ -46,9 +45,8 @@ final class PhabricatorDashboardPanel ) + parent::getConfiguration(); } - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - PhabricatorDashboardPanelPHIDType::TYPECONST); + public function getPHIDType() { + return PhabricatorDashboardPanelPHIDType::TYPECONST; } public function getProperty($key, $default = null) { @@ -105,6 +103,10 @@ final class PhabricatorDashboardPanel return $impl; } + public function getEditEngineFields() { + return $this->requireImplementation()->getEditEngineFields($this); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -142,27 +144,6 @@ final class PhabricatorDashboardPanel } -/* -( PhabricatorCustomFieldInterface )------------------------------------ */ - - - public function getCustomFieldSpecificationForRole($role) { - return array(); - } - - public function getCustomFieldBaseClass() { - return 'PhabricatorDashboardPanelCustomField'; - } - - public function getCustomFields() { - return $this->assertAttached($this->customFields); - } - - public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { - $this->customFields = $fields; - return $this; - } - - /* -( PhabricatorDestructibleInterface )----------------------------------- */ @@ -174,15 +155,22 @@ final class PhabricatorDashboardPanel $this->saveTransaction(); } +/* -( PhabricatorDashboardPanelContainerInterface )------------------------ */ -/* -( PhabricatorNgramInterface )------------------------------------------ */ + public function getDashboardPanelContainerPanelPHIDs() { + return $this->requireImplementation()->getSubpanelPHIDs($this); + } +/* -( PhabricatorFulltextInterface )--------------------------------------- */ - public function newNgrams() { - return array( - id(new PhabricatorDashboardPanelNgrams()) - ->setValue($this->getName()), - ); + public function newFulltextEngine() { + return new PhabricatorDashboardPanelFulltextEngine(); + } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + public function newFerretEngine() { + return new PhabricatorDashboardPanelFerretEngine(); } } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPanelNgrams.php b/src/applications/dashboard/storage/PhabricatorDashboardPanelNgrams.php deleted file mode 100644 index 536da8b751..0000000000 --- a/src/applications/dashboard/storage/PhabricatorDashboardPanelNgrams.php +++ /dev/null @@ -1,18 +0,0 @@ -getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_link = $this->renderHandleLink($author_phid); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created this panel.', - $author_link); - } else { - return pht( - '%s renamed this panel from "%s" to "%s".', - $author_link, - $old, - $new); - } - case self::TYPE_ARCHIVE: - if ($new) { - return pht( - '%s archived this panel.', - $author_link); - } else { - return pht( - '%s activated this panel.', - $author_link); - } - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PhabricatorDashboardPanelTransactionType'; } - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_link = $this->renderHandleLink($author_phid); - $object_link = $this->renderHandleLink($object_phid); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created dashboard panel %s.', - $author_link, - $object_link); - } else { - return pht( - '%s renamed dashboard panel %s from "%s" to "%s".', - $author_link, - $object_link, - $old, - $new); - } - case self::TYPE_ARCHIVE: - if ($new) { - return pht( - '%s archived dashboard panel %s.', - $author_link, - $object_link); - } else { - return pht( - '%s activated dashboard panel %s.', - $author_link, - $object_link); - } - } - - return parent::getTitleForFeed(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if (!strlen($old)) { - return PhabricatorTransactions::COLOR_GREEN; - } - break; - } - - return parent::getColor(); - } } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPortal.php b/src/applications/dashboard/storage/PhabricatorDashboardPortal.php new file mode 100644 index 0000000000..d04f8282fb --- /dev/null +++ b/src/applications/dashboard/storage/PhabricatorDashboardPortal.php @@ -0,0 +1,123 @@ +setName('') + ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) + ->setEditPolicy(PhabricatorPolicies::POLICY_USER) + ->setStatus(PhabricatorDashboardPortalStatus::STATUS_ACTIVE); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text255', + 'status' => 'text32', + ), + ) + parent::getConfiguration(); + } + + public function getPHIDType() { + return PhabricatorDashboardPortalPHIDType::TYPECONST; + } + + public function getPortalProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setPortalProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getObjectName() { + return pht('Portal %d', $this->getID()); + } + + public function getURI() { + return '/portal/view/'.$this->getID().'/'; + } + + public function isArchived() { + $status_archived = PhabricatorDashboardPortalStatus::STATUS_ARCHIVED; + return ($this->getStatus() === $status_archived); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorDashboardPortalEditor(); + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorDashboardPortalTransaction(); + } + + +/* -( 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; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + public function newFulltextEngine() { + return new PhabricatorDashboardPortalFulltextEngine(); + } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + public function newFerretEngine() { + return new PhabricatorDashboardPortalFerretEngine(); + } + +} diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPortalTransaction.php b/src/applications/dashboard/storage/PhabricatorDashboardPortalTransaction.php new file mode 100644 index 0000000000..7861394b98 --- /dev/null +++ b/src/applications/dashboard/storage/PhabricatorDashboardPortalTransaction.php @@ -0,0 +1,18 @@ +getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_link = $this->renderHandleLink($author_phid); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created this dashboard.', - $author_link); - } else { - return pht( - '%s renamed this dashboard from "%s" to "%s".', - $author_link, - $old, - $new); - } - break; - case self::TYPE_ICON: - if (!strlen($old)) { - return pht( - '%s set the dashboard icon.', - $author_link); - } else { - return pht( - '%s changed this dashboard icon from "%s" to "%s".', - $author_link, - $old, - $new); - } - break; - case self::TYPE_STATUS: - if ($new == PhabricatorDashboard::STATUS_ACTIVE) { - return pht( - '%s activated this dashboard.', - $author_link); - } else { - return pht( - '%s archived this dashboard.', - $author_link); - } - break; - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PhabricatorDashboardTransactionType'; } - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_link = $this->renderHandleLink($author_phid); - $object_link = $this->renderHandleLink($object_phid); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created dashboard %s.', - $author_link, - $object_link); - } else { - return pht( - '%s renamed dashboard %s from "%s" to "%s".', - $author_link, - $object_link, - $old, - $new); - } - break; - case self::TYPE_ICON: - if (!strlen($old)) { - return pht( - '%s set dashboard icon for %s.', - $author_link, - $object_link); - } else { - return pht( - '%s set the dashboard icon on %s from "%s" to "%s".', - $author_link, - $object_link, - $old, - $new); - } - break; - case self::TYPE_STATUS: - if ($new == PhabricatorDashboard::STATUS_ACTIVE) { - return pht( - '%s activated dashboard %s.', - $author_link, - $object_link); - } else { - return pht( - '%s archived dashboard %s.', - $author_link, - $object_link); - } - break; - } - - return parent::getTitleForFeed(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if (!strlen($old)) { - return PhabricatorTransactions::COLOR_GREEN; - } - break; - case self::TYPE_STATUS: - if ($new == PhabricatorDashboard::STATUS_ACTIVE) { - return PhabricatorTransactions::COLOR_GREEN; - } else { - return PhabricatorTransactions::COLOR_INDIGO; - } - break; - } - - return parent::getColor(); - } - - public function getIcon() { - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return 'fa-pencil'; - break; - case self::TYPE_STATUS: - if ($new == PhabricatorDashboard::STATUS_ACTIVE) { - return 'fa-check'; - } else { - return 'fa-ban'; - } - break; - case self::TYPE_LAYOUT_MODE: - return 'fa-columns'; - break; - } - return parent::getIcon(); - } - - public function shouldHide() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_LAYOUT_MODE: - return true; - } - return parent::shouldHide(); - } } diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardDatasource.php index a1c0399624..ff3376bdf6 100644 --- a/src/applications/dashboard/typeahead/PhabricatorDashboardDatasource.php +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardDatasource.php @@ -18,6 +18,12 @@ final class PhabricatorDashboardDatasource public function loadResults() { $query = id(new PhabricatorDashboardQuery()); + $this->applyFerretConstraints( + $query, + id(new PhabricatorDashboard())->newFerretEngine(), + 'title', + $this->getRawQuery()); + $dashboards = $this->executeQuery($query); $results = array(); foreach ($dashboards as $dashboard) { diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php index 883b6d32bb..9ad4901bbf 100644 --- a/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php @@ -33,7 +33,11 @@ final class PhabricatorDashboardPanelDatasource $id = (int)$id; $query->withIDs(array($id)); } else { - $query->withNameNgrams($raw_query); + $this->applyFerretConstraints( + $query, + id(new PhabricatorDashboardPanel())->newFerretEngine(), + 'title', + $this->getRawQuery()); } $panels = $this->executeQuery($query); @@ -48,13 +52,13 @@ final class PhabricatorDashboardPanelDatasource $type_text = nonempty($panel->getPanelType(), pht('Unknown Type')); $icon = 'fa-question'; } - $id = $panel->getID(); + $phid = $panel->getPHID(); $monogram = $panel->getMonogram(); $properties = $panel->getProperties(); $result = id(new PhabricatorTypeaheadResult()) ->setName($monogram.' '.$panel->getName()) - ->setPHID($id) + ->setPHID($phid) ->setIcon($icon) ->addAttribute($type_text); @@ -66,7 +70,7 @@ final class PhabricatorDashboardPanelDatasource $result->setClosed(pht('Archived')); } - $results[$id] = $result; + $results[$phid] = $result; } return $results; diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php new file mode 100644 index 0000000000..008bb542ab --- /dev/null +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php @@ -0,0 +1,51 @@ +buildResults(); + return $this->filterResultsAgainstTokens($results); + } + + protected function renderSpecialTokens(array $values) { + return $this->renderTokensFromResults($this->buildResults(), $values); + } + + public function buildResults() { + $query = new PhabricatorDashboardPortalQuery(); + + $this->applyFerretConstraints( + $query, + id(new PhabricatorDashboardPortal())->newFerretEngine(), + 'title', + $this->getRawQuery()); + + $portals = $this->executeQuery($query); + + $results = array(); + foreach ($portals as $portal) { + $result = id(new PhabricatorTypeaheadResult()) + ->setName($portal->getObjectName().' '.$portal->getName()) + ->setPHID($portal->getPHID()) + ->setIcon('fa-compass'); + + $results[] = $result; + } + + return $results; + } + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php new file mode 100644 index 0000000000..cd90d3e6b4 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php @@ -0,0 +1,27 @@ +getIcon(); + } + + public function applyInternalEffects($object, $value) { + $object->setIcon($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s changed the icon for this dashboard from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php new file mode 100644 index 0000000000..441bb4fd36 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php @@ -0,0 +1,55 @@ +getRawLayoutMode(); + } + + public function applyInternalEffects($object, $value) { + $object->setRawLayoutMode($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s changed the layout mode for this dashboard from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $mode_map = PhabricatorDashboardLayoutMode::getLayoutModeMap(); + + $old_value = $object->getRawLayoutMode(); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + if ($new_value === $old_value) { + continue; + } + + if (!isset($mode_map[$new_value])) { + $errors[] = $this->newInvalidError( + pht( + 'Layout mode "%s" is not valid. Supported layout modes '. + 'are: %s.', + $new_value, + implode(', ', array_keys($mode_map))), + $xaction); + continue; + } + } + + return $errors; + } + + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php new file mode 100644 index 0000000000..af76d66f47 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php @@ -0,0 +1,70 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s renamed this dashboard from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (!strlen($new)) { + $errors[] = $this->newInvalidError( + pht('Dashboards must have a name.'), + $xaction); + continue; + } + + if (strlen($new) > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Dashboard names must not be longer than %s characters.', + $max_length)); + continue; + } + } + + if (!$errors) { + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Dashboards must have a name.')); + } + } + + return $errors; + } + + public function getTransactionTypeForConduit($xaction) { + return 'name'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php new file mode 100644 index 0000000000..35248159bd --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php @@ -0,0 +1,153 @@ +getRawPanels(); + } + + public function applyInternalEffects($object, $value) { + $object->setRawPanels($value); + } + + public function getTitle() { + return pht( + '%s changed the panels on this dashboard.', + $this->renderAuthor()); + } + + public function validateTransactions($object, array $xactions) { + $actor = $this->getActor(); + $errors = array(); + + $ref_list = $object->getPanelRefList(); + $columns = $ref_list->getColumns(); + + $old_phids = $object->getPanelPHIDs(); + $old_phids = array_fuse($old_phids); + + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + if (!is_array($new_value)) { + $errors[] = $this->newInvalidError( + pht('Panels must be a list of panel specifications.'), + $xaction); + continue; + } + + if (!phutil_is_natural_list($new_value)) { + $errors[] = $this->newInvalidError( + pht('Panels must be a list, not a map.'), + $xaction); + continue; + } + + $new_phids = array(); + $seen_keys = array(); + foreach ($new_value as $idx => $spec) { + if (!is_array($spec)) { + $errors[] = $this->newInvalidError( + pht( + 'Each panel specification must be a map of panel attributes. '. + 'Panel specification at index "%s" is "%s".', + $idx, + phutil_describe_type($spec)), + $xaction); + continue; + } + + try { + PhutilTypeSpec::checkMap( + $spec, + array( + 'panelPHID' => 'string', + 'columnKey' => 'string', + 'panelKey' => 'string', + )); + } catch (PhutilTypeCheckException $ex) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification at index "%s" is invalid: %s', + $idx, + $ex->getMessage()), + $xaction); + continue; + } + + $panel_key = $spec['panelKey']; + + if (!strlen($panel_key)) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification at index "%s" has bad panel key "%s". '. + 'Panel keys must be nonempty.', + $idx, + $panel_key), + $xaction); + continue; + } + + if (isset($seen_keys[$panel_key])) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification at index "%s" has duplicate panel key '. + '"%s". Each panel must have a unique panel key.', + $idx, + $panel_key), + $xaction); + continue; + } + + $seen_keys[$panel_key] = true; + + $panel_phid = $spec['panelPHID']; + $new_phids[] = $panel_phid; + + $column_key = $spec['columnKey']; + + if (!isset($columns[$column_key])) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification at index "%s" has bad column key "%s", '. + 'valid column keys are: %s.', + $idx, + $column_key, + implode(', ', array_keys($columns))), + $xaction); + continue; + } + } + + $new_phids = array_fuse($new_phids); + $add_phids = array_diff_key($new_phids, $old_phids); + + if ($add_phids) { + $panels = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($actor) + ->withPHIDs($add_phids) + ->execute(); + $panels = mpull($panels, null, 'getPHID'); + + foreach ($add_phids as $add_phid) { + $panel = idx($panels, $add_phid); + + if (!$panel) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification adds panel "%s", but this is not a '. + 'valid panel or not a visible panel. You can only add '. + 'valid panels which you have permission to see to a dashboard.', + $add_phid)); + continue; + } + } + } + } + + return $errors; + } + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php new file mode 100644 index 0000000000..4012fe0cc8 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php @@ -0,0 +1,59 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + switch ($new) { + case PhabricatorDashboard::STATUS_ACTIVE: + return pht( + '%s activated this dashboard.', + $this->renderAuthor()); + case PhabricatorDashboard::STATUS_ARCHIVED: + return pht( + '%s archived this dashboard.', + $this->renderAuthor()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $valid_statuses = PhabricatorDashboard::getStatusNameMap(); + + $old_value = $object->getStatus(); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + if ($new_value === $old_value) { + continue; + } + + if (!isset($valid_statuses[$new_value])) { + $errors[] = $this->newInvalidError( + pht( + 'Status "%s" is not valid. Supported status constants are: %s.', + $new_value, + implode(', ', array_keys($valid_statuses))), + $xaction); + continue; + } + } + + return $errors; + } + + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php new file mode 100644 index 0000000000..d0d87405a3 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php @@ -0,0 +1,4 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s renamed this panel from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (!strlen($new)) { + $errors[] = $this->newInvalidError( + pht('Panels must have a title.'), + $xaction); + continue; + } + + if (strlen($new) > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Panel names must not be longer than %s characters.', + $max_length)); + continue; + } + } + + if (!$errors) { + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Panels must have a title.')); + } + } + + return $errors; + } + + public function getTransactionTypeForConduit($xaction) { + return 'name'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + +} diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php new file mode 100644 index 0000000000..f30add6836 --- /dev/null +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php @@ -0,0 +1,18 @@ +getPropertyKey(); + return $object->getProperty($property_key); + } + + public function applyInternalEffects($object, $value) { + $property_key = $this->getPropertyKey(); + $object->setProperty($property_key, $value); + } + +} diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php new file mode 100644 index 0000000000..da2e736f98 --- /dev/null +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php @@ -0,0 +1,33 @@ +getIsArchived(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsArchived((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s archived this panel.', + $this->renderAuthor()); + } else { + return pht( + '%s activated this panel.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php new file mode 100644 index 0000000000..cfe829f7f5 --- /dev/null +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php @@ -0,0 +1,4 @@ +getProperty($this->getPropertyKey()); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + if ($new_value === $old_value) { + continue; + } + + if (!isset($engines[$new_value])) { + $errors[] = $this->newInvalidError( + pht( + 'Application search engine class "%s" is unknown. Query panels '. + 'must use a known search engine class.', + $new_value), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php new file mode 100644 index 0000000000..bfbfd996f6 --- /dev/null +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php @@ -0,0 +1,12 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s renamed this portal from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (!strlen($new)) { + $errors[] = $this->newInvalidError( + pht('Portals must have a title.'), + $xaction); + continue; + } + + if (strlen($new) > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Portal names must not be longer than %s characters.', + $max_length)); + continue; + } + } + + if (!$errors) { + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Portals must have a title.')); + } + } + + return $errors; + } + + public function getTransactionTypeForConduit($xaction) { + return 'name'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + +} diff --git a/src/applications/dashboard/xaction/portal/PhabricatorDashboardPortalTransactionType.php b/src/applications/dashboard/xaction/portal/PhabricatorDashboardPortalTransactionType.php new file mode 100644 index 0000000000..1855312f26 --- /dev/null +++ b/src/applications/dashboard/xaction/portal/PhabricatorDashboardPortalTransactionType.php @@ -0,0 +1,4 @@ +getMonogram(); } + public function getCreateURI($form_key) { + return '/differential/diff/create/'; + } + protected function getObjectCreateShortText() { return pht('Create Revision'); } diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index 7b94b1958b..08e0a344e0 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -243,8 +243,6 @@ final class DifferentialDiffExtractionEngine extends Phobject { PhabricatorContentSource $content_source) { $viewer = $this->getViewer(); - $result_data = array(); - $new_diff = $this->newDiffFromCommit($commit); $old_diff = $revision->getActiveDiff(); @@ -261,8 +259,6 @@ final class DifferentialDiffExtractionEngine extends Phobject { $old_diff, $new_diff); if ($has_changed) { - $result_data['vsDiff'] = $old_diff->getID(); - $revision_monogram = $revision->getMonogram(); $old_id = $old_diff->getID(); $new_id = $new_diff->getID(); @@ -336,8 +332,6 @@ final class DifferentialDiffExtractionEngine extends Phobject { // lost a race to close the revision. That's perfectly fine, we can // just continue normally. } - - return $result_data; } private function loadConcerningBuilds(DifferentialRevision $revision) { diff --git a/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php index 16d07e5240..c920253dae 100644 --- a/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php @@ -29,4 +29,8 @@ final class DifferentialReviewersAddBlockingReviewersHeraldAction return pht('Add blocking reviewers: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php index d39a4370c5..60e64c750d 100644 --- a/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php @@ -29,4 +29,8 @@ final class DifferentialReviewersAddReviewersHeraldAction return pht('Add reviewers: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php b/src/applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php new file mode 100644 index 0000000000..83b3cec39e --- /dev/null +++ b/src/applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php @@ -0,0 +1,44 @@ +getAdapter(); + $viewer = $adapter->getViewer(); + + $jira_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object->getPHID(), + PhabricatorJiraIssueHasObjectEdgeType::EDGECONST); + if (!$jira_phids) { + return array(); + } + + $xobjs = id(new DoorkeeperExternalObjectQuery()) + ->setViewer($viewer) + ->withPHIDs($jira_phids) + ->execute(); + + return mpull($xobjs, 'getObjectURI'); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_TEXT_LIST; + } + +} diff --git a/src/applications/differential/render/DifferentialChangesetRenderer.php b/src/applications/differential/render/DifferentialChangesetRenderer.php index 26de5cb53b..4ed77bf041 100644 --- a/src/applications/differential/render/DifferentialChangesetRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetRenderer.php @@ -684,7 +684,12 @@ abstract class DifferentialChangesetRenderer extends Phobject { // If this change is missing context, don't try to identify scopes, since // we won't really be able to get anywhere. $has_multiple_hunks = (count($hunk_starts) > 1); - $has_offset_hunks = (head_key($hunk_starts) != 1); + + $has_offset_hunks = false; + if ($hunk_starts) { + $has_offset_hunks = (head_key($hunk_starts) != 1); + } + $missing_context = ($has_multiple_hunks || $has_offset_hunks); if ($missing_context) { diff --git a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php index f20ba7a011..857bcc870b 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php @@ -83,49 +83,41 @@ final class DifferentialRevisionCloseTransaction } public function getTitle() { - if (!$this->getMetadataValue('isCommitClose')) { + $commit_phid = $this->getMetadataValue('commitPHID'); + if ($commit_phid) { + $commit = id(new DiffusionCommitQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($commit_phid)) + ->needIdentities(true) + ->executeOne(); + } else { + $commit = null; + } + + if (!$commit) { return pht( '%s closed this revision.', $this->renderAuthor()); } - $commit_phid = $this->getMetadataValue('commitPHID'); - $committer_phid = $this->getMetadataValue('committerPHID'); - $author_phid = $this->getMetadataValue('authorPHID'); + $author_phid = $commit->getAuthorDisplayPHID(); + $committer_phid = $commit->getCommitterDisplayPHID(); - if ($committer_phid) { - $committer_name = $this->renderHandle($committer_phid); - } else { - $committer_name = $this->getMetadataValue('committerName'); - } - - if ($author_phid) { - $author_name = $this->renderHandle($author_phid); - } else { - $author_name = $this->getMetadatavalue('authorName'); - } - - $same_phid = - strlen($committer_phid) && - strlen($author_phid) && - ($committer_phid == $author_phid); - - $same_name = - !strlen($committer_phid) && - !strlen($author_phid) && - ($committer_name == $author_name); - - if ($same_name || $same_phid) { + if (!$author_phid) { + return pht( + 'Closed by commit %s.', + $this->renderHandle($commit_phid)); + } else if (!$committer_phid || ($committer_phid === $author_phid)) { return pht( 'Closed by commit %s (authored by %s).', $this->renderHandle($commit_phid), - $author_name); + $this->renderHandle($author_phid)); } else { return pht( 'Closed by commit %s (authored by %s, committed by %s).', $this->renderHandle($commit_phid), - $author_name, - $committer_name); + $this->renderHandle($author_phid), + $this->renderHandle($committer_phid)); } } diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index 8907a22ce5..4e02ee99a0 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -236,6 +236,22 @@ final class DiffusionRepositoryEditEngine 'you can set a path in **Import Only**. Phabricator will ignore '. 'commits which do not affect this path.'); + $filesize_warning = null; + if ($object->isGit()) { + $git_binary = PhutilBinaryAnalyzer::getForBinary('git'); + $git_version = $git_binary->getBinaryVersion(); + $filesize_version = '1.8.4'; + if (version_compare($git_version, $filesize_version, '<')) { + $filesize_warning = pht( + '(WARNING) {icon exclamation-triangle} The version of "git" ("%s") '. + 'installed on this server does not support '. + '"--batch-check=", a feature required to enforce filesize '. + 'limits. Upgrade to "git" %s or newer to use this feature.', + $git_version, + $filesize_version); + } + } + return array( id(new PhabricatorSelectEditField()) ->setKey('vcs') @@ -477,6 +493,7 @@ final class DiffusionRepositoryEditEngine ->setDescription(pht('Maximum permitted file size.')) ->setConduitDescription(pht('Change the filesize limit.')) ->setConduitTypeDescription(pht('New repository filesize limit.')) + ->setControlInstructions($filesize_warning) ->setValue($object->getFilesizeLimit()), id(new PhabricatorTextEditField()) ->setKey('copyTimeLimit') diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index 6856b8676d..6ebaf6bdc7 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -459,10 +459,7 @@ final class DiffusionCommitHookEngine extends Phobject { $ref_type = PhabricatorRepositoryPushLog::REFTYPE_TAG; $ref_raw = substr($ref_raw, strlen('refs/tags/')); } else { - throw new Exception( - pht( - "Unable to identify the reftype of '%s'. Rejecting push.", - $ref_raw)); + $ref_type = PhabricatorRepositoryPushLog::REFTYPE_REF; } $ref_update = $this->newPushLog() diff --git a/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php b/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php index 36b01b96f8..f895847d4a 100644 --- a/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php +++ b/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php @@ -30,4 +30,8 @@ final class DiffusionAuditorsAddAuditorsHeraldAction return pht('Add auditors: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php index ff71ae8713..0c18f58712 100644 --- a/src/applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php @@ -24,6 +24,7 @@ final class DiffusionPreCommitRefTypeHeraldField $types = array( PhabricatorRepositoryPushLog::REFTYPE_BRANCH => pht('branch (git/hg)'), PhabricatorRepositoryPushLog::REFTYPE_TAG => pht('tag (git)'), + PhabricatorRepositoryPushLog::REFTYPE_REF => pht('ref (git)'), PhabricatorRepositoryPushLog::REFTYPE_BOOKMARK => pht('bookmark (hg)'), ); diff --git a/src/applications/favorites/application/PhabricatorFavoritesApplication.php b/src/applications/favorites/application/PhabricatorFavoritesApplication.php index 3a6acd4ebc..50f56c5224 100644 --- a/src/applications/favorites/application/PhabricatorFavoritesApplication.php +++ b/src/applications/favorites/application/PhabricatorFavoritesApplication.php @@ -15,12 +15,13 @@ final class PhabricatorFavoritesApplication extends PhabricatorApplication { } public function getIcon() { - return 'fa-star'; + return 'fa-bookmark'; } public function getRoutes() { return array( '/favorites/' => array( + '' => 'PhabricatorFavoritesMenuItemController', 'menu/' => $this->getProfileMenuRouting( 'PhabricatorFavoritesMenuItemController'), ), diff --git a/src/applications/favorites/controller/PhabricatorFavoritesMenuItemController.php b/src/applications/favorites/controller/PhabricatorFavoritesMenuItemController.php index dd4bf77cf0..54af8908a6 100644 --- a/src/applications/favorites/controller/PhabricatorFavoritesMenuItemController.php +++ b/src/applications/favorites/controller/PhabricatorFavoritesMenuItemController.php @@ -16,8 +16,7 @@ final class PhabricatorFavoritesMenuItemController $engine = id(new PhabricatorFavoritesProfileMenuEngine()) ->setProfileObject($favorites) ->setCustomPHID($viewer->getPHID()) - ->setController($this) - ->setShowNavigation(false); + ->setController($this); return $engine->buildResponse(); } diff --git a/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php b/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php index 0ac31e7de8..a17c60226b 100644 --- a/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php +++ b/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php @@ -35,6 +35,10 @@ final class PhabricatorFavoritesProfileMenuEngine } } + $items[] = $this->newDividerItem('tail'); + $items[] = $this->newManageItem() + ->setMenuItemProperty('name', pht('Edit Favorites')); + return $items; } diff --git a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php index 7b5d6d0720..a6cd4fc069 100644 --- a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php +++ b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php @@ -20,13 +20,13 @@ final class PhabricatorFavoritesMainMenuBarExtension $dropdown = $this->newDropdown($viewer); if (!$dropdown) { - return null; + return array(); } $favorites_menu = id(new PHUIButtonView()) ->setTag('a') ->setHref('#') - ->setIcon('fa-star') + ->setIcon('fa-bookmark') ->addClass('phabricator-core-user-menu') ->setNoCSS(true) ->setDropdown(true) @@ -54,7 +54,13 @@ final class PhabricatorFavoritesMainMenuBarExtension ->setProfileObject($favorites) ->setCustomPHID($viewer->getPHID()); - $filter_view = $menu_engine->buildNavigation(); + $controller = $this->getController(); + if ($controller) { + $menu_engine->setController($controller); + } + + $filter_view = $menu_engine->newProfileMenuItemViewList() + ->newNavigationView(); $menu_view = $filter_view->getMenu(); $item_views = $menu_view->getItems(); @@ -65,20 +71,11 @@ final class PhabricatorFavoritesMainMenuBarExtension $action = id(new PhabricatorActionView()) ->setName($item->getName()) ->setHref($item->getHref()) + ->setIcon($item->getIcon()) ->setType($item->getType()); $view->addAction($action); } - if ($viewer->isLoggedIn()) { - $view->addAction( - id(new PhabricatorActionView()) - ->setType(PhabricatorActionView::TYPE_DIVIDER)); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Favorites')) - ->setHref('/favorites/menu/configure/')); - } - return $view; } diff --git a/src/applications/guides/module/PhabricatorGuideQuickStartModule.php b/src/applications/guides/module/PhabricatorGuideQuickStartModule.php index 8c4b4c2606..65b07ffe2c 100644 --- a/src/applications/guides/module/PhabricatorGuideQuickStartModule.php +++ b/src/applications/guides/module/PhabricatorGuideQuickStartModule.php @@ -103,33 +103,6 @@ final class PhabricatorGuideQuickStartModule extends PhabricatorGuideModule { ->setDescription($description); $guide_items->addItem($item); - $title = pht('Build a Dashboard'); - $have_dashboard = (bool)PhabricatorDashboardInstall::getDashboard( - $viewer, - PhabricatorHomeApplication::DASHBOARD_DEFAULT, - 'PhabricatorHomeApplication'); - $href = PhabricatorEnv::getURI('/dashboard/'); - if ($have_dashboard) { - $icon = 'fa-check'; - $icon_bg = 'bg-green'; - $description = pht( - "You've created at least one dashboard."); - } else { - $icon = 'fa-dashboard'; - $icon_bg = 'bg-sky'; - $description = - pht('Customize the default homepage layout and items.'); - } - - $item = id(new PhabricatorGuideItemView()) - ->setTitle($title) - ->setHref($href) - ->setIcon($icon) - ->setIconBackground($icon_bg) - ->setDescription($description); - $guide_items->addItem($item); - - $title = pht('Personalize your Install'); $wordmark = PhabricatorEnv::getEnvConfig('ui.logo'); $href = PhabricatorEnv::getURI('/config/edit/ui.logo/'); diff --git a/src/applications/herald/engineextension/HeraldRuleIndexEngineExtension.php b/src/applications/herald/engineextension/HeraldRuleIndexEngineExtension.php index 7b7b2fb529..5df229ccd6 100644 --- a/src/applications/herald/engineextension/HeraldRuleIndexEngineExtension.php +++ b/src/applications/herald/engineextension/HeraldRuleIndexEngineExtension.php @@ -1,7 +1,7 @@ getPHID(), - $edge_type); - $old_edges = array_fuse($old_edges); - - $new_edges = $this->getPHIDsAffectedByActions($object); - $new_edges = array_fuse($new_edges); - - $add_edges = array_diff_key($new_edges, $old_edges); - $rem_edges = array_diff_key($old_edges, $new_edges); - - if (!$add_edges && !$rem_edges) { - return; - } - - $editor = new PhabricatorEdgeEditor(); - - foreach ($add_edges as $phid) { - $editor->addEdge($object->getPHID(), $edge_type, $phid); - } - - foreach ($rem_edges as $phid) { - $editor->removeEdge($object->getPHID(), $edge_type, $phid); - } - - $editor->save(); + protected function getIndexEdgeType() { + return HeraldRuleActionAffectsObjectEdgeType::EDGECONST; } - public function getIndexVersion($object) { - $phids = $this->getPHIDsAffectedByActions($object); - sort($phids); - $phids = implode(':', $phids); - return PhabricatorHash::digestForIndex($phids); - } + protected function getIndexDestinationPHIDs($object) { + $rule = $object; - private function getPHIDsAffectedByActions(HeraldRule $rule) { $viewer = $this->getViewer(); $rule = id(new HeraldRuleQuery()) diff --git a/src/applications/home/controller/PhabricatorHomeController.php b/src/applications/home/controller/PhabricatorHomeController.php index 7c46525ee0..e47d513946 100644 --- a/src/applications/home/controller/PhabricatorHomeController.php +++ b/src/applications/home/controller/PhabricatorHomeController.php @@ -1,43 +1,4 @@ newApplicationMenu(); - - $profile_menu = $this->getProfileMenu(); - if ($profile_menu) { - $menu->setProfileMenu($profile_menu); - } - - return $menu; - } - - protected function getProfileMenu() { - if (!$this->profileMenu) { - $viewer = $this->getViewer(); - $applications = id(new PhabricatorApplicationQuery()) - ->setViewer($viewer) - ->withClasses(array('PhabricatorHomeApplication')) - ->withInstalled(true) - ->execute(); - $home = head($applications); - if (!$home) { - return null; - } - - $engine = id(new PhabricatorHomeProfileMenuEngine()) - ->setViewer($viewer) - ->setProfileObject($home) - ->setCustomPHID($viewer->getPHID()); - - $this->profileMenu = $engine->buildNavigation(); - } - - return $this->profileMenu; - } - -} +abstract class PhabricatorHomeController + extends PhabricatorController {} diff --git a/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php b/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php index e380af2b6a..d3e338ef26 100644 --- a/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php +++ b/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php @@ -67,11 +67,12 @@ final class PhabricatorHomeProfileMenuEngine ->setMenuItemProperties($properties); } - // Hotlink to More Applications Launcher... $items[] = $this->newItem() ->setBuiltinKey(PhabricatorHomeConstants::ITEM_LAUNCHER) ->setMenuItemKey(PhabricatorHomeLauncherProfileMenuItem::MENUITEMKEY); + $items[] = $this->newDividerItem('tail'); + $items[] = $this->newManageItem(); return $items; diff --git a/src/applications/home/menuitem/PhabricatorHomeLauncherProfileMenuItem.php b/src/applications/home/menuitem/PhabricatorHomeLauncherProfileMenuItem.php index a727fbced6..dbf1586366 100644 --- a/src/applications/home/menuitem/PhabricatorHomeLauncherProfileMenuItem.php +++ b/src/applications/home/menuitem/PhabricatorHomeLauncherProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorHomeLauncherProfileMenuItem return pht('More Applications'); } + public function getMenuItemTypeIcon() { + return 'fa-ellipsis-h'; + } + public function canHideMenuItem( PhabricatorProfileMenuItemConfiguration $config) { return false; @@ -45,16 +49,16 @@ final class PhabricatorHomeLauncherProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $viewer = $this->getViewer(); $name = $this->getDisplayName($config); - $icon = 'fa-globe'; - $href = '/applications/'; + $icon = 'fa-ellipsis-h'; + $uri = '/applications/'; - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon); diff --git a/src/applications/home/menuitem/PhabricatorHomeProfileMenuItem.php b/src/applications/home/menuitem/PhabricatorHomeProfileMenuItem.php index 8b3eb4fe2d..a002b59da5 100644 --- a/src/applications/home/menuitem/PhabricatorHomeProfileMenuItem.php +++ b/src/applications/home/menuitem/PhabricatorHomeProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorHomeProfileMenuItem return pht('Home'); } + public function getMenuItemTypeIcon() { + return 'fa-home'; + } + public function canMakeDefault( PhabricatorProfileMenuItemConfiguration $config) { return true; @@ -48,16 +52,16 @@ final class PhabricatorHomeProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $viewer = $this->getViewer(); $name = $this->getDisplayName($config); $icon = 'fa-home'; - $href = $this->getItemViewURI($config); + $uri = $this->getItemViewURI($config); - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon); diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index c458e4dbd1..3aace69074 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -144,6 +144,8 @@ final class PhabricatorOwnersDetailController $crumbs->addTextCrumb($package->getMonogram()); $crumbs->setBorder(true); + $rules_view = $this->newRulesView($package); + $timeline = $this->buildTransactionTimeline( $package, new PhabricatorOwnersPackageTransactionQuery()); @@ -154,6 +156,7 @@ final class PhabricatorOwnersDetailController ->setCurtain($curtain) ->setMainColumn(array( $this->renderPathsTable($paths, $repositories), + $rules_view, $commit_panels, $timeline, )) @@ -345,4 +348,55 @@ final class PhabricatorOwnersDetailController return $box; } + private function newRulesView(PhabricatorOwnersPackage $package) { + $viewer = $this->getViewer(); + + $limit = 10; + $rules = id(new HeraldRuleQuery()) + ->setViewer($viewer) + ->withDisabled(false) + ->withAffectedObjectPHIDs(array($package->getPHID())) + ->needValidateAuthors(true) + ->setLimit($limit + 1) + ->execute(); + + $more_results = (count($rules) > $limit); + $rules = array_slice($rules, 0, $limit); + + $list = id(new HeraldRuleListView()) + ->setViewer($viewer) + ->setRules($rules) + ->newObjectList(); + + $list->setNoDataString( + pht( + 'No active Herald rules add this package as an auditor, reviewer, '. + 'or subscriber.')); + + $more_href = new PhutilURI( + '/herald/', + array('affectedPHID' => $package->getPHID())); + + if ($more_results) { + $list->newTailButton() + ->setHref($more_href); + } + + $more_link = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list-ul') + ->setText(pht('View All Rules')) + ->setHref($more_href); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Affected By Herald Rules')) + ->setHeaderIcon(id(new PhabricatorHeraldApplication())->getIcon()) + ->addActionLink($more_link); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($list); + } + } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php b/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php index f3e95eeb66..f98970ef73 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php @@ -30,8 +30,9 @@ final class PhabricatorPeopleProfileBadgesController $crumbs->addTextCrumb(pht('Badges')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_BADGES); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_BADGES); // Best option? $badges = id(new PhabricatorBadgesQuery()) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php index c18c5f4d96..430e11311e 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php @@ -32,8 +32,9 @@ final class PhabricatorPeopleProfileCommitsController $crumbs->addTextCrumb(pht('Recent Commits')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_COMMITS); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_COMMITS); $view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php index 91afda123b..1d6f0fc74c 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php @@ -4,7 +4,6 @@ abstract class PhabricatorPeopleProfileController extends PhabricatorPeopleController { private $user; - private $profileMenu; public function shouldRequireAdmin() { return false; @@ -19,34 +18,6 @@ abstract class PhabricatorPeopleProfileController return $this->user; } - public function buildApplicationMenu() { - $menu = $this->newApplicationMenu(); - - $profile_menu = $this->getProfileMenu(); - if ($profile_menu) { - $menu->setProfileMenu($profile_menu); - } - - return $menu; - } - - protected function getProfileMenu() { - if (!$this->profileMenu) { - $user = $this->getUser(); - if ($user) { - $viewer = $this->getViewer(); - - $engine = id(new PhabricatorPeopleProfileMenuEngine()) - ->setViewer($viewer) - ->setProfileObject($user); - - $this->profileMenu = $engine->buildNavigation(); - } - } - - return $this->profileMenu; - } - protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); @@ -138,4 +109,24 @@ abstract class PhabricatorPeopleProfileController return $header; } + final protected function newNavigation( + PhabricatorUser $user, + $item_identifier) { + + $viewer = $this->getViewer(); + + $engine = id(new PhabricatorPeopleProfileMenuEngine()) + ->setViewer($viewer) + ->setController($this) + ->setProfileObject($user); + + $view_list = $engine->newProfileMenuItemViewList(); + + $view_list->setSelectedViewWithItemIdentifier($item_identifier); + + $navigation = $view_list->newNavigationView(); + + return $navigation; + } + } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php index 4876f4495d..3af95462a9 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php @@ -83,8 +83,9 @@ final class PhabricatorPeopleProfileEditController $crumbs->addTextCrumb(pht('Edit Profile')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); $header = id(new PHUIHeaderView()) ->setHeader(pht('Edit Profile: %s', $user->getFullName())) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php index 835935f775..5db38adafb 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php @@ -29,8 +29,9 @@ final class PhabricatorPeopleProfileManageController $properties = $this->buildPropertyView($user); $name = $user->getUsername(); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Manage')); diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index 92bb2e0b86..d50eccff13 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -266,8 +266,9 @@ final class PhabricatorPeopleProfilePictureController $crumbs->addTextCrumb(pht('Edit Profile Picture')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); $header = $this->buildProfileHeader(); diff --git a/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php b/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php index 55baf0140f..0c7b6f6a1f 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php @@ -32,8 +32,9 @@ final class PhabricatorPeopleProfileRevisionsController $crumbs->addTextCrumb(pht('Recent Revisions')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_REVISIONS); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_REVISIONS); $view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php index b843af8fc7..bc4e1432f1 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php @@ -32,8 +32,9 @@ final class PhabricatorPeopleProfileTasksController $crumbs->addTextCrumb(pht('Assigned Tasks')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_TASKS); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_TASKS); $view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 6a4d68d6aa..b5c0e2b816 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -64,15 +64,16 @@ final class PhabricatorPeopleProfileViewController $calendar, )); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_PROFILE); + $navigation = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_PROFILE); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); return $this->newPage() ->setTitle($user->getUsername()) - ->setNavigation($nav) + ->setNavigation($navigation) ->setCrumbs($crumbs) ->setPageObjectPHIDs( array( diff --git a/src/applications/people/menuitem/PhabricatorPeopleBadgesProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeopleBadgesProfileMenuItem.php index 0e4da29b61..71f3aa1392 100644 --- a/src/applications/people/menuitem/PhabricatorPeopleBadgesProfileMenuItem.php +++ b/src/applications/people/menuitem/PhabricatorPeopleBadgesProfileMenuItem.php @@ -40,14 +40,14 @@ final class PhabricatorPeopleBadgesProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $user = $config->getProfileObject(); $id = $user->getID(); - $item = $this->newItem() - ->setHref("/people/badges/{$id}/") + $item = $this->newItemView() + ->setURI("/people/badges/{$id}/") ->setName($this->getDisplayName($config)) ->setIcon('fa-trophy'); diff --git a/src/applications/people/menuitem/PhabricatorPeopleCommitsProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeopleCommitsProfileMenuItem.php index f1d8be1828..b6c1c446cc 100644 --- a/src/applications/people/menuitem/PhabricatorPeopleCommitsProfileMenuItem.php +++ b/src/applications/people/menuitem/PhabricatorPeopleCommitsProfileMenuItem.php @@ -40,14 +40,14 @@ final class PhabricatorPeopleCommitsProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $user = $config->getProfileObject(); $id = $user->getID(); - $item = $this->newItem() - ->setHref("/people/commits/{$id}/") + $item = $this->newItemView() + ->setURI("/people/commits/{$id}/") ->setName($this->getDisplayName($config)) ->setIcon('fa-code'); diff --git a/src/applications/people/menuitem/PhabricatorPeopleDetailsProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeopleDetailsProfileMenuItem.php index d7d36b4ed5..61508ff515 100644 --- a/src/applications/people/menuitem/PhabricatorPeopleDetailsProfileMenuItem.php +++ b/src/applications/people/menuitem/PhabricatorPeopleDetailsProfileMenuItem.php @@ -35,16 +35,16 @@ final class PhabricatorPeopleDetailsProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $user = $config->getProfileObject(); - $href = urisprintf( + $uri = urisprintf( '/p/%s/', $user->getUsername()); - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName(pht('Profile')) ->setIcon('fa-user'); diff --git a/src/applications/people/menuitem/PhabricatorPeopleManageProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeopleManageProfileMenuItem.php index 78d3dca49d..43d2271a79 100644 --- a/src/applications/people/menuitem/PhabricatorPeopleManageProfileMenuItem.php +++ b/src/applications/people/menuitem/PhabricatorPeopleManageProfileMenuItem.php @@ -40,14 +40,14 @@ final class PhabricatorPeopleManageProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $user = $config->getProfileObject(); $id = $user->getID(); - $item = $this->newItem() - ->setHref("/people/manage/{$id}/") + $item = $this->newItemView() + ->setURI("/people/manage/{$id}/") ->setName($this->getDisplayName($config)) ->setIcon('fa-gears'); diff --git a/src/applications/people/menuitem/PhabricatorPeoplePictureProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeoplePictureProfileMenuItem.php index 938b7cf60a..3e3fc62bf0 100644 --- a/src/applications/people/menuitem/PhabricatorPeoplePictureProfileMenuItem.php +++ b/src/applications/people/menuitem/PhabricatorPeoplePictureProfileMenuItem.php @@ -28,52 +28,18 @@ final class PhabricatorPeoplePictureProfileMenuItem return array(); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $user = $config->getProfileObject(); - require_celerity_resource('people-picture-menu-item-css'); $picture = $user->getProfileImageURI(); $name = $user->getUsername(); - $classes = array(); - $classes[] = 'people-menu-image'; - if ($user->getIsDisabled()) { - $classes[] = 'phui-image-disabled'; - } + $item = $this->newItemView() + ->setDisabled($user->getIsDisabled()); - $href = urisprintf( - '/p/%s/', - $user->getUsername()); - - $photo = phutil_tag( - 'img', - array( - 'src' => $picture, - 'class' => implode(' ', $classes), - )); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $this->getViewer(), - $user, - PhabricatorPolicyCapability::CAN_EDIT); - - if ($can_edit) { - $id = $user->getID(); - $href = "/people/picture/{$id}/"; - } - - $view = phutil_tag_div('people-menu-image-container', $photo); - $view = phutil_tag( - 'a', - array( - 'href' => $href, - ), - $view); - - $item = $this->newItem() - ->appendChild($view); + $item->newProfileImage($picture); return array( $item, diff --git a/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php index 499fc1d7f4..cfa760fcd6 100644 --- a/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php +++ b/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php @@ -40,14 +40,14 @@ final class PhabricatorPeopleRevisionsProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $user = $config->getProfileObject(); $id = $user->getID(); - $item = $this->newItem() - ->setHref("/people/revisions/{$id}/") + $item = $this->newItemView() + ->setURI("/people/revisions/{$id}/") ->setName($this->getDisplayName($config)) ->setIcon('fa-gear'); diff --git a/src/applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php index c2a5036521..5dea58cb29 100644 --- a/src/applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php +++ b/src/applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php @@ -40,14 +40,14 @@ final class PhabricatorPeopleTasksProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $user = $config->getProfileObject(); $id = $user->getID(); - $item = $this->newItem() - ->setHref("/people/tasks/{$id}/") + $item = $this->newItemView() + ->setURI("/people/tasks/{$id}/") ->setName($this->getDisplayName($config)) ->setIcon('fa-anchor'); diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 4fb01c4def..7bef0e6751 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -24,7 +24,7 @@ final class PhamePostViewController $hero = $this->buildPhamePostHeader($post); - if (!$is_external) { + if (!$is_external && $viewer->isLoggedIn()) { $actions = $this->renderActions($post); $header->setPolicyObject($post); $header->setActionList($actions); @@ -136,7 +136,7 @@ final class PhamePostViewController ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); $timeline->setQuoteRef($monogram); - if ($is_external) { + if ($is_external || !$viewer->isLoggedIn()) { $add_comment = null; } else { $add_comment = $this->buildCommentForm($post, $timeline); diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php index abae359a16..18c53e9b91 100644 --- a/src/applications/phid/view/PHUIHandleTagListView.php +++ b/src/applications/phid/view/PHUIHandleTagListView.php @@ -122,7 +122,7 @@ final class PHUIHandleTagListView extends AphrontTagView { private function newPlaceholderTag() { return id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setColor(PHUITagView::COLOR_DISABLED) + ->setColor(PHUITagView::COLOR_PLACEHOLDER) ->setSlimShady($this->slim); } diff --git a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php index bb9608edb8..09056fe761 100644 --- a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php +++ b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php @@ -18,41 +18,17 @@ final class PhrictionDocumentDatasource public function loadResults() { $viewer = $this->getViewer(); - $raw_query = $this->getRawQuery(); - - $engine = id(new PhrictionDocument()) - ->newFerretEngine(); - - $compiler = id(new PhutilSearchQueryCompiler()) - ->setEnableFunctions(true); - - $raw_tokens = $compiler->newTokens($raw_query); - - $fulltext_tokens = array(); - foreach ($raw_tokens as $raw_token) { - - // This is a little hacky and could maybe be cleaner. We're treating - // every search term as though the user had entered "title:dog" insead - // of "dog". - - $alternate_token = PhutilSearchQueryToken::newFromDictionary( - array( - 'quoted' => $raw_token->isQuoted(), - 'value' => $raw_token->getValue(), - 'operator' => PhutilSearchQueryCompiler::OPERATOR_SUBSTRING, - 'function' => 'title', - )); - - $fulltext_token = id(new PhabricatorFulltextToken()) - ->setToken($alternate_token); - $fulltext_tokens[] = $fulltext_token; - } - - $documents = id(new PhrictionDocumentQuery()) + $query = id(new PhrictionDocumentQuery()) ->setViewer($viewer) - ->withFerretConstraint($engine, $fulltext_tokens) - ->needContent(true) - ->execute(); + ->needContent(true); + + $this->applyFerretConstraints( + $query, + id(new PhrictionDocument())->newFerretEngine(), + 'title', + $this->getRawQuery()); + + $documents = $query->execute(); $results = array(); foreach ($documents as $document) { diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index 20c52dac8e..d297e6408a 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -78,7 +78,7 @@ final class PonderAddAnswerView extends AphrontView { $box = id(new PHUIObjectBoxView()) ->appendChild($form) - ->setHeaderText('Answer') + ->setHeaderText(pht('Answer')) ->addClass('ponder-add-answer-view'); if ($info_panel) { diff --git a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php index c70c211398..7859091de6 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php @@ -52,7 +52,9 @@ final class PhabricatorProjectBoardBackgroundController ->setURI($view_uri); } - $nav = $this->getProfileMenu(); + $nav = $this->newNavigation( + $board, + PhabricatorProject::ITEM_WORKBOARD); $crumbs = id($this->buildApplicationCrumbs()) ->addTextCrumb(pht('Workboard'), $board->getWorkboardURI()) diff --git a/src/applications/project/controller/PhabricatorProjectBoardController.php b/src/applications/project/controller/PhabricatorProjectBoardController.php index d0c6abf882..b889bc75da 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardController.php @@ -1,14 +1,4 @@ selectFilter(PhabricatorProject::ITEM_WORKBOARD); - $menu->addClass('project-board-nav'); - - return $menu; - } -} + extends PhabricatorProjectController {} diff --git a/src/applications/project/controller/PhabricatorProjectBoardManageController.php b/src/applications/project/controller/PhabricatorProjectBoardManageController.php index 21daf2e654..b2b6dfdf6d 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardManageController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardManageController.php @@ -38,7 +38,9 @@ final class PhabricatorProjectBoardManageController $crumbs->addTextCrumb(pht('Manage')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); + $nav = $this->newNavigation( + $board, + PhabricatorProject::ITEM_WORKBOARD); $columns_list = $this->buildColumnsList($board, $columns); require_celerity_resource('project-view-css'); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 775ff1b61a..8ceb576b6f 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -172,8 +172,9 @@ final class PhabricatorProjectBoardViewController return $content; } - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::ITEM_WORKBOARD); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_WORKBOARD); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard')); @@ -576,6 +577,11 @@ final class PhabricatorProjectBoardViewController $panel->addHeaderAction($column_menu); if ($column->canHaveTrigger()) { + $trigger = $column->getTrigger(); + if ($trigger) { + $trigger->setViewer($viewer); + } + $trigger_menu = $this->buildTriggerMenu($column); $panel->addHeaderAction($trigger_menu); } @@ -720,7 +726,9 @@ final class PhabricatorProjectBoardViewController ->appendChild($board) ->addClass('project-board-wrapper'); - $nav = $this->getProfileMenu(); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_WORKBOARD); $divider = id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_DIVIDER); diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php index 781461a812..016999bbe6 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -51,7 +51,9 @@ final class PhabricatorProjectColumnDetailController $crumbs->addTextCrumb(pht('Column: %s', $title)); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_WORKBOARD); require_celerity_resource('project-view-css'); $view = id(new PHUITwoColumnView()) diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index 63494bf442..7781f4c73b 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -84,30 +84,6 @@ abstract class PhabricatorProjectController extends PhabricatorController { return null; } - public function buildApplicationMenu() { - $menu = $this->newApplicationMenu(); - - $profile_menu = $this->getProfileMenu(); - if ($profile_menu) { - $menu->setProfileMenu($profile_menu); - } - - $menu->setSearchEngine(new PhabricatorProjectSearchEngine()); - - return $menu; - } - - protected function getProfileMenu() { - if (!$this->profileMenu) { - $engine = $this->getProfileMenuEngine(); - if ($engine) { - $this->profileMenu = $engine->buildNavigation(); - } - } - - return $this->profileMenu; - } - protected function buildApplicationCrumbs() { return $this->newApplicationCrumbs('profile'); } @@ -207,4 +183,23 @@ abstract class PhabricatorProjectController extends PhabricatorController { return implode(', ', $result); } + final protected function newNavigation( + PhabricatorProject $project, + $item_identifier) { + + $engine = $this->getProfileMenuEngine(); + + $view_list = $engine->newProfileMenuItemViewList(); + + $view_list->setSelectedViewWithItemIdentifier($item_identifier); + + $navigation = $view_list->newNavigationView(); + + if ($item_identifier === PhabricatorProject::ITEM_WORKBOARD) { + $navigation->addClass('project-board-nav'); + } + + return $navigation; + } + } diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index e601d744c0..d141dc07e2 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -273,8 +273,9 @@ final class PhabricatorProjectEditPictureController ->setHeaderText(pht('Upload New Picture')) ->setForm($upload_form); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::ITEM_MANAGE); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_MANAGE); return $this->newPage() ->setTitle($title) diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index 2c76c63606..eadda5bf86 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -37,8 +37,9 @@ final class PhabricatorProjectManageController new PhabricatorProjectTransactionQuery()); $timeline->setShouldTerminate(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::ITEM_MANAGE); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_MANAGE); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Manage')); diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php index ad553eb664..ee0b71cefa 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersViewController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php @@ -36,8 +36,9 @@ final class PhabricatorProjectMembersViewController ->setUserPHIDs($project->getWatcherPHIDs()) ->setShowNote(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::ITEM_MEMBERS); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_MEMBERS); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Members')); diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 6e1e7677e6..67b94f7fa9 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -74,8 +74,9 @@ final class PhabricatorProjectProfileController ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUserPHIDs($project->getWatcherPHIDs()); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::ITEM_PROFILE); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_PROFILE); $stories = id(new PhabricatorFeedQuery()) ->setViewer($viewer) diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index 36736c78ba..be787e7a4f 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -77,8 +77,9 @@ final class PhabricatorProjectSubprojectsController $milestones, $subprojects); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::ITEM_SUBPROJECTS); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_SUBPROJECTS); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Subprojects')); diff --git a/src/applications/project/controller/PhabricatorProjectViewController.php b/src/applications/project/controller/PhabricatorProjectViewController.php index 2e53fe7276..beb622ea50 100644 --- a/src/applications/project/controller/PhabricatorProjectViewController.php +++ b/src/applications/project/controller/PhabricatorProjectViewController.php @@ -18,7 +18,7 @@ final class PhabricatorProjectViewController $project = $this->getProject(); $engine = $this->getProfileMenuEngine(); - $default = $engine->getDefaultItem(); + $default = $engine->getDefaultMenuItemConfiguration(); // If defaults are broken somehow, serve the manage page. See T13033 for // discussion. @@ -28,7 +28,7 @@ final class PhabricatorProjectViewController $default_key = PhabricatorProject::ITEM_MANAGE; } - switch ($default->getBuiltinKey()) { + switch ($default_key) { case PhabricatorProject::ITEM_WORKBOARD: $controller_object = new PhabricatorProjectBoardViewController(); break; diff --git a/src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php b/src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php index df362efb61..f8fbbf7356 100644 --- a/src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php +++ b/src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php @@ -25,6 +25,8 @@ final class PhabricatorProjectTriggerEditController $trigger = PhabricatorProjectTrigger::initializeNewTrigger(); } + $trigger->setViewer($viewer); + $column_phid = $request->getStr('columnPHID'); if ($column_phid) { $column = id(new PhabricatorProjectColumnQuery()) @@ -272,6 +274,10 @@ final class PhabricatorProjectTriggerEditController $rule_list = array_values($rule_list); $type_list = PhabricatorProjectTriggerRule::getAllTriggerRules(); + + foreach ($type_list as $rule) { + $rule->setViewer($this->getViewer()); + } $type_list = mpull($type_list, 'newTemplate'); $type_list = array_values($type_list); diff --git a/src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php b/src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php index d148c0a421..b5d22f4d72 100644 --- a/src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php +++ b/src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php @@ -20,6 +20,7 @@ final class PhabricatorProjectTriggerViewController if (!$trigger) { return new Aphront404Response(); } + $trigger->setViewer($viewer); $rules_view = $this->newRulesView($trigger); $columns_view = $this->newColumnsView($trigger); diff --git a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php index 77b69443da..813cd01781 100644 --- a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php +++ b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php @@ -22,7 +22,8 @@ final class PhabricatorProjectProfileMenuEngine $items[] = $this->newItem() ->setBuiltinKey(PhabricatorProject::ITEM_PICTURE) - ->setMenuItemKey(PhabricatorProjectPictureProfileMenuItem::MENUITEMKEY); + ->setMenuItemKey(PhabricatorProjectPictureProfileMenuItem::MENUITEMKEY) + ->setIsHeadItem(true); $items[] = $this->newItem() ->setBuiltinKey(PhabricatorProject::ITEM_PROFILE) @@ -47,7 +48,8 @@ final class PhabricatorProjectProfileMenuEngine $items[] = $this->newItem() ->setBuiltinKey(PhabricatorProject::ITEM_MANAGE) - ->setMenuItemKey(PhabricatorProjectManageProfileMenuItem::MENUITEMKEY); + ->setMenuItemKey(PhabricatorProjectManageProfileMenuItem::MENUITEMKEY) + ->setIsTailItem(true); return $items; } diff --git a/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php index b779f4be90..a3021e0239 100644 --- a/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorProjectDetailsProfileMenuItem return pht('Project Details'); } + public function getMenuItemTypeIcon() { + return 'fa-file-text-o'; + } + public function canHideMenuItem( PhabricatorProfileMenuItemConfiguration $config) { return false; @@ -45,7 +49,7 @@ final class PhabricatorProjectDetailsProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $project = $config->getProfileObject(); @@ -54,10 +58,10 @@ final class PhabricatorProjectDetailsProfileMenuItem $name = $project->getName(); $icon = $project->getDisplayIconIcon(); - $href = "/project/profile/{$id}/"; + $uri = "/project/profile/{$id}/"; - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon); diff --git a/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php index 1bd7e796dc..9b8a769318 100644 --- a/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorProjectManageProfileMenuItem return pht('Manage'); } + public function getMenuItemTypeIcon() { + return 'fa-cog'; + } + public function canHideMenuItem( PhabricatorProfileMenuItemConfiguration $config) { return false; @@ -45,7 +49,7 @@ final class PhabricatorProjectManageProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $project = $config->getProfileObject(); @@ -54,10 +58,10 @@ final class PhabricatorProjectManageProfileMenuItem $name = $this->getDisplayName($config); $icon = 'fa-gears'; - $href = "/project/manage/{$id}/"; + $uri = "/project/manage/{$id}/"; - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon); diff --git a/src/applications/project/menuitem/PhabricatorProjectMembersProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectMembersProfileMenuItem.php index 580aacb635..11a57d3a5b 100644 --- a/src/applications/project/menuitem/PhabricatorProjectMembersProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectMembersProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorProjectMembersProfileMenuItem return pht('Members'); } + public function getMenuItemTypeIcon() { + return 'fa-users'; + } + public function getDisplayName( PhabricatorProfileMenuItemConfiguration $config) { $name = $config->getMenuItemProperty('name'); @@ -35,7 +39,7 @@ final class PhabricatorProjectMembersProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $project = $config->getProfileObject(); @@ -44,10 +48,10 @@ final class PhabricatorProjectMembersProfileMenuItem $name = $this->getDisplayName($config); $icon = 'fa-group'; - $href = "/project/members/{$id}/"; + $uri = "/project/members/{$id}/"; - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon); diff --git a/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php index 2201687c81..5a58b3af41 100644 --- a/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorProjectPictureProfileMenuItem return pht('Project Picture'); } + public function getMenuItemTypeIcon() { + return 'fa-image'; + } + public function canHideMenuItem( PhabricatorProfileMenuItemConfiguration $config) { return false; @@ -28,38 +32,16 @@ final class PhabricatorProjectPictureProfileMenuItem return array(); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $project = $config->getProfileObject(); - require_celerity_resource('people-picture-menu-item-css'); - $picture = $project->getProfileImageURI(); - $href = $project->getProfileURI(); - $classes = array(); - $classes[] = 'people-menu-image'; - if ($project->isArchived()) { - $classes[] = 'phui-image-disabled'; - } + $item = $this->newItemView() + ->setDisabled($project->isArchived()); - $photo = phutil_tag( - 'img', - array( - 'src' => $picture, - 'class' => implode(' ', $classes), - )); - - $view = phutil_tag_div('people-menu-image-container', $photo); - $view = phutil_tag( - 'a', - array( - 'href' => $href, - ), - $view); - - $item = $this->newItem() - ->appendChild($view); + $item->newProfileImage($picture); return array( $item, diff --git a/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php index b64b4bb7a0..20c8d2985b 100644 --- a/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php @@ -52,7 +52,7 @@ final class PhabricatorProjectPointsProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $viewer = $this->getViewer(); $project = $config->getProfileObject(); @@ -165,8 +165,9 @@ final class PhabricatorProjectPointsProfileMenuItem ), $bar); - $item = $this->newItem() - ->appendChild($bar); + $item = $this->newItemView(); + + $item->newProgressBar($bar); return array( $item, diff --git a/src/applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php index 6b43210274..b1782e8f1c 100644 --- a/src/applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorProjectSubprojectsProfileMenuItem return pht('Subprojects'); } + public function getMenuItemTypeIcon() { + return 'fa-sitemap'; + } + public function shouldEnableForObject($object) { if ($object->isMilestone()) { return false; @@ -43,7 +47,7 @@ final class PhabricatorProjectSubprojectsProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $project = $config->getProfileObject(); @@ -51,10 +55,10 @@ final class PhabricatorProjectSubprojectsProfileMenuItem $name = $this->getDisplayName($config); $icon = 'fa-sitemap'; - $href = "/project/subprojects/{$id}/"; + $uri = "/project/subprojects/{$id}/"; - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon); diff --git a/src/applications/project/menuitem/PhabricatorProjectWorkboardProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectWorkboardProfileMenuItem.php index 38b9632d93..34152f85e7 100644 --- a/src/applications/project/menuitem/PhabricatorProjectWorkboardProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectWorkboardProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorProjectWorkboardProfileMenuItem return pht('Workboard'); } + public function getMenuItemTypeIcon() { + return 'fa-columns'; + } + public function canMakeDefault( PhabricatorProfileMenuItemConfiguration $config) { return true; @@ -52,16 +56,16 @@ final class PhabricatorProjectWorkboardProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $project = $config->getProfileObject(); $id = $project->getID(); - $href = $project->getWorkboardURI(); + $uri = $project->getWorkboardURI(); $name = $this->getDisplayName($config); - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon('fa-columns'); diff --git a/src/applications/project/storage/PhabricatorProjectTrigger.php b/src/applications/project/storage/PhabricatorProjectTrigger.php index 625dc7ffd8..d7892dd7a1 100644 --- a/src/applications/project/storage/PhabricatorProjectTrigger.php +++ b/src/applications/project/storage/PhabricatorProjectTrigger.php @@ -13,6 +13,7 @@ final class PhabricatorProjectTrigger protected $editPolicy; private $triggerRules; + private $viewer; private $usage = self::ATTACHABLE; public static function initializeNewTrigger() { @@ -41,6 +42,15 @@ final class PhabricatorProjectTrigger return PhabricatorProjectTriggerPHIDType::TYPECONST; } + public function getViewer() { + return $this->viewer; + } + + public function setViewer(PhabricatorUser $user) { + $this->viewer = $user; + return $this; + } + public function getDisplayName() { $name = $this->getName(); if (strlen($name)) { @@ -72,11 +82,16 @@ final class PhabricatorProjectTrigger parent::setRuleset($ruleset); } - public function getTriggerRules() { + public function getTriggerRules($viewer = null) { if ($this->triggerRules === null) { + if (!$viewer) { + $viewer = $this->getViewer(); + } + $trigger_rules = self::newTriggerRulesFromRuleSpecifications( $this->getRuleset(), - $allow_invalid = true); + $allow_invalid = true, + $viewer); $this->triggerRules = $trigger_rules; } @@ -86,11 +101,12 @@ final class PhabricatorProjectTrigger public static function newTriggerRulesFromRuleSpecifications( array $list, - $allow_invalid) { + $allow_invalid, + PhabricatorUser $viewer) { - // NOTE: With "$allow_invalid" set, we're trying to preserve the database + // NOTE: With "$allow_invalid" set, we're trying to preserve the database // state in the rule structure, even if it includes rule types we don't - // ha ve implementations for, or rules with invalid rule values. + // have implementations for, or rules with invalid rule values. // If an administrator adds or removes extensions which add rules, or // an upgrade affects rule validity, existing rules may become invalid. @@ -124,7 +140,7 @@ final class PhabricatorProjectTrigger if (!is_array($rule)) { throw new PhabricatorProjectTriggerCorruptionException( pht( - 'Trigger ruleset is corrupt: rule (with key "%s") should be a '. + 'Trigger ruleset is corrupt: rule (at index "%s") should be a '. 'rule specification, but is actually "%s".', $key, phutil_describe_type($rule))); @@ -140,7 +156,7 @@ final class PhabricatorProjectTrigger } catch (PhutilTypeCheckException $ex) { throw new PhabricatorProjectTriggerCorruptionException( pht( - 'Trigger ruleset is corrupt: rule (with key "%s") is not a '. + 'Trigger ruleset is corrupt: rule (at index "%s") is not a '. 'valid rule specification: %s', $key, $ex->getMessage())); @@ -179,6 +195,7 @@ final class PhabricatorProjectTrigger ->setRecord($record) ->setException($ex); } + $rule->setViewer($viewer); $trigger_rules[] = $rule; } @@ -206,9 +223,8 @@ final class PhabricatorProjectTrigger $object) { $trigger_xactions = array(); - foreach ($this->getTriggerRules() as $rule) { + foreach ($this->getTriggerRules($viewer) as $rule) { $rule - ->setViewer($viewer) ->setTrigger($this) ->setColumn($column) ->setObject($object); diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php new file mode 100644 index 0000000000..42e13d9a66 --- /dev/null +++ b/src/applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php @@ -0,0 +1,111 @@ +getDatasource()->getWireTokens($this->getValue()); + } + + protected function assertValidRuleRecordFormat($value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Add project rule value should be a list, but is not '. + '(value is "%s").', + phutil_describe_type($value))); + } + } + + protected function assertValidRuleRecordValue($value) { + if (!$value) { + throw new Exception( + pht( + 'You must select at least one project tag to add.')); + } + } + + protected function newDropTransactions($object, $value) { + $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + + $xaction = $object->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $project_edge_type) + ->setNewValue( + array( + '+' => array_fuse($value), + )); + + return array($xaction); + } + + protected function newDropEffects($value) { + return array( + $this->newEffect() + ->setIcon('fa-briefcase') + ->setContent($this->getRuleViewDescription($value)), + ); + } + + protected function getDefaultValue() { + return null; + } + + protected function getPHUIXControlType() { + return 'tokenizer'; + } + + private function getDatasource() { + return id(new PhabricatorProjectDatasource()) + ->setViewer($this->getViewer()); + } + + protected function getPHUIXControlSpecification() { + $template = id(new AphrontTokenizerTemplateView()) + ->setViewer($this->getViewer()); + + $template_markup = $template->render(); + $datasource = $this->getDatasource(); + + return array( + 'markup' => (string)hsprintf('%s', $template_markup), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + 'limit' => $datasource->getLimit(), + ), + 'value' => null, + ); + } + + public function getRuleViewLabel() { + return pht('Add Project Tags'); + } + + public function getRuleViewDescription($value) { + return pht( + 'Add project tags: %s.', + phutil_tag( + 'strong', + array(), + $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render())); + } + + public function getRuleViewIcon($value) { + return id(new PHUIIconView()) + ->setIcon('fa-briefcase', 'green'); + } + + + +} diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php index ba53b77e75..07aa6e0792 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php @@ -24,7 +24,7 @@ final class PhabricatorProjectTriggerInvalidRule return false; } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { return; } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php new file mode 100644 index 0000000000..1648f1bc8b --- /dev/null +++ b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php @@ -0,0 +1,148 @@ +getDatasource()->getWireTokens($this->getValue()); + } + + private function convertTokenizerValueToOwner($value) { + $value = head($value); + if ($value === PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN) { + $value = null; + } + return $value; + } + + protected function assertValidRuleRecordFormat($value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Owner rule value should be a list, but is not (value is "%s").', + phutil_describe_type($value))); + } + } + + protected function assertValidRuleRecordValue($value) { + if (!$value) { + throw new Exception( + pht( + 'Owner rule value is required. Specify a user to assign tasks '. + 'to, or the token "none()" to unassign tasks.')); + } + + if (count($value) > 1) { + throw new Exception( + pht( + 'Owner rule value must have only one elmement (value is "%s").', + implode(', ', $value))); + } + + $owner_phid = $this->convertTokenizerValueToOwner($value); + if ($owner_phid !== null) { + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($owner_phid)) + ->executeOne(); + if (!$user) { + throw new Exception( + pht( + 'User PHID ("%s") is not a valid user.', + $owner_phid)); + } + } + } + + protected function newDropTransactions($object, $value) { + $value = $this->convertTokenizerValueToOwner($value); + return array( + $this->newTransaction() + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) + ->setNewValue($value), + ); + } + + protected function newDropEffects($value) { + $owner_value = $this->convertTokenizerValueToOwner($value); + + return array( + $this->newEffect() + ->setIcon('fa-user') + ->setContent($this->getRuleViewDescription($value)) + ->addCondition('owner', '!=', $owner_value), + ); + } + + protected function getDefaultValue() { + return null; + } + + protected function getPHUIXControlType() { + return 'tokenizer'; + } + + private function getDatasource() { + $datasource = id(new ManiphestAssigneeDatasource()) + ->setLimit(1); + + if ($this->getViewer()) { + $datasource->setViewer($this->getViewer()); + } + + return $datasource; + } + + protected function getPHUIXControlSpecification() { + $template = id(new AphrontTokenizerTemplateView()) + ->setViewer($this->getViewer()); + + $template_markup = $template->render(); + $datasource = $this->getDatasource(); + + return array( + 'markup' => (string)hsprintf('%s', $template_markup), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + 'limit' => $datasource->getLimit(), + ), + 'value' => null, + ); + } + + public function getRuleViewLabel() { + return pht('Change Owner'); + } + + public function getRuleViewDescription($value) { + $value = $this->convertTokenizerValueToOwner($value); + + if (!$value) { + return pht('Unassign task.'); + } else { + return pht( + 'Assign task to %s.', + phutil_tag( + 'strong', + array(), + $this->getViewer() + ->renderHandle($value) + ->render())); + } + } + + public function getRuleViewIcon($value) { + return id(new PHUIIconView()) + ->setIcon('fa-user', 'green'); + } + + +} diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php index 98a03a1393..56b0fc9b1f 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php @@ -9,20 +9,24 @@ final class PhabricatorProjectTriggerManiphestPriorityRule return pht('Change priority to'); } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { if (!is_string($value)) { throw new Exception( pht( 'Priority rule value should be a string, but is not (value is "%s").', phutil_describe_type($value))); } + } + protected function assertValidRuleRecordValue($value) { $map = ManiphestTaskPriority::getTaskPriorityMap(); if (!isset($map[$value])) { throw new Exception( pht( - 'Rule value ("%s") is not a valid task priority.', - $value)); + 'Task priority value ("%s") is not a valid task priority. '. + 'Valid priorities are: %s.', + $value, + implode(', ', array_keys($map)))); } } @@ -54,7 +58,7 @@ final class PhabricatorProjectTriggerManiphestPriorityRule } protected function getDefaultValue() { - return head_key(ManiphestTaskPriority::getTaskPriorityMap()); + return ManiphestTaskPriority::getDefaultPriority(); } protected function getPHUIXControlType() { diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php index b11d7567de..85d32134e4 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php @@ -9,20 +9,24 @@ final class PhabricatorProjectTriggerManiphestStatusRule return pht('Change status to'); } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { if (!is_string($value)) { throw new Exception( pht( 'Status rule value should be a string, but is not (value is "%s").', phutil_describe_type($value))); } + } + protected function assertValidRuleRecordValue($value) { $map = ManiphestTaskStatus::getTaskStatusMap(); if (!isset($map[$value])) { throw new Exception( pht( - 'Rule value ("%s") is not a valid task status.', - $value)); + 'Task status value ("%s") is not a valid task status. '. + 'Valid statues are: %s.', + $value, + implode(', ', array_keys($map)))); } } @@ -53,7 +57,7 @@ final class PhabricatorProjectTriggerManiphestStatusRule } protected function getDefaultValue() { - return head_key(ManiphestTaskStatus::getTaskStatusMap()); + return ManiphestTaskStatus::getDefaultClosedStatus(); } protected function getPHUIXControlType() { diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php index ef19b504ef..9e99eb070f 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php @@ -9,20 +9,21 @@ final class PhabricatorProjectTriggerPlaySoundRule return pht('Play sound'); } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { if (!is_string($value)) { throw new Exception( pht( 'Status rule value should be a string, but is not (value is "%s").', phutil_describe_type($value))); } + } + protected function assertValidRuleRecordValue($value) { $map = self::getSoundMap(); - if (!isset($map[$value])) { throw new Exception( pht( - 'Rule value ("%s") is not a valid sound.', + 'Sound ("%s") is not a valid sound.', $value)); } } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php new file mode 100644 index 0000000000..42b6263ef6 --- /dev/null +++ b/src/applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php @@ -0,0 +1,111 @@ +getDatasource()->getWireTokens($this->getValue()); + } + + protected function assertValidRuleRecordFormat($value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Remove project rule value should be a list, but is not '. + '(value is "%s").', + phutil_describe_type($value))); + } + } + + protected function assertValidRuleRecordValue($value) { + if (!$value) { + throw new Exception( + pht( + 'You must select at least one project tag to remove.')); + } + } + + protected function newDropTransactions($object, $value) { + $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + + $xaction = $object->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $project_edge_type) + ->setNewValue( + array( + '-' => array_fuse($value), + )); + + return array($xaction); + } + + protected function newDropEffects($value) { + return array( + $this->newEffect() + ->setIcon('fa-briefcase', 'red') + ->setContent($this->getRuleViewDescription($value)), + ); + } + + protected function getDefaultValue() { + return null; + } + + protected function getPHUIXControlType() { + return 'tokenizer'; + } + + private function getDatasource() { + return id(new PhabricatorProjectDatasource()) + ->setViewer($this->getViewer()); + } + + protected function getPHUIXControlSpecification() { + $template = id(new AphrontTokenizerTemplateView()) + ->setViewer($this->getViewer()); + + $template_markup = $template->render(); + $datasource = $this->getDatasource(); + + return array( + 'markup' => (string)hsprintf('%s', $template_markup), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + 'limit' => $datasource->getLimit(), + ), + 'value' => null, + ); + } + + public function getRuleViewLabel() { + return pht('Remove Project Tags'); + } + + public function getRuleViewDescription($value) { + return pht( + 'Remove project tags: %s.', + phutil_tag( + 'strong', + array(), + $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render())); + } + + public function getRuleViewIcon($value) { + return id(new PHUIIconView()) + ->setIcon('fa-briefcase', 'red'); + } + + + +} diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php index ae2b3ee092..58c7cd1f1a 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php @@ -23,7 +23,7 @@ abstract class PhabricatorProjectTriggerRule final public function setRecord(PhabricatorProjectTriggerRuleRecord $record) { $value = $record->getValue(); - $this->assertValidRuleValue($value); + $this->assertValidRuleRecordFormat($value); $this->record = $record; return $this; @@ -37,11 +37,30 @@ abstract class PhabricatorProjectTriggerRule return $this->getRecord()->getValue(); } + protected function getValueForEditorField() { + return $this->getValue(); + } + abstract public function getSelectControlName(); abstract public function getRuleViewLabel(); abstract public function getRuleViewDescription($value); abstract public function getRuleViewIcon($value); - abstract protected function assertValidRuleValue($value); + abstract protected function assertValidRuleRecordFormat($value); + + final public function getRuleRecordValueValidationException() { + try { + $this->assertValidRuleRecordValue($this->getRecord()->getValue()); + } catch (Exception $ex) { + return $ex; + } + + return null; + } + + protected function assertValidRuleRecordValue($value) { + return; + } + abstract protected function newDropTransactions($object, $value); abstract protected function newDropEffects($value); abstract protected function getDefaultValue(); @@ -130,7 +149,7 @@ abstract class PhabricatorProjectTriggerRule return array( 'type' => $record->getType(), - 'value' => $record->getValue(), + 'value' => $this->getValueForEditorField(), 'isValidRule' => $is_valid, 'invalidView' => $invalid_view, ); diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php index 925a369bae..d354b01eaf 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php @@ -13,7 +13,7 @@ final class PhabricatorProjectTriggerUnknownRule return false; } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { return; } diff --git a/src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php b/src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php index 59c846becf..0f3c1233c6 100644 --- a/src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php +++ b/src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php @@ -20,15 +20,18 @@ final class PhabricatorProjectTriggerRulesetTransaction } public function validateTransactions($object, array $xactions) { + $actor = $this->getActor(); $errors = array(); foreach ($xactions as $xaction) { $ruleset = $xaction->getNewValue(); try { - PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications( - $ruleset, - $allow_invalid = false); + $rules = + PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications( + $ruleset, + $allow_invalid = false, + $actor); } catch (PhabricatorProjectTriggerCorruptionException $ex) { $errors[] = $this->newInvalidError( pht( @@ -37,6 +40,19 @@ final class PhabricatorProjectTriggerRulesetTransaction $xaction); continue; } + + foreach ($rules as $rule) { + $exception = $rule->getRuleRecordValueValidationException(); + if ($exception) { + $errors[] = $this->newInvalidError( + pht( + 'Value for "%s" rule is invalid: %s', + $rule->getSelectControlName(), + $exception->getMessage()), + $xaction); + continue; + } + } } return $errors; diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php index 5a6afb8c6e..418030864d 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php @@ -241,15 +241,13 @@ final class PhabricatorRepositoryManagementUnpublishWorkflow if ($xactions) { foreach ($xactions as $xaction) { $metadata = $xaction->getMetadata(); - if (idx($metadata, 'isCommitClose')) { - if (idx($metadata, 'commitPHID') === $src->getPHID()) { - echo tsprintf( - "%s\n", - pht( - 'MANUAL Revision "%s" was likely closed improperly by "%s".', - $dst->getMonogram(), - $src->getMonogram())); - } + if (idx($metadata, 'commitPHID') === $src->getPHID()) { + echo tsprintf( + "%s\n", + pht( + 'MANUAL Revision "%s" was likely closed improperly by "%s".', + $dst->getMonogram(), + $src->getMonogram())); } } } diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php index cfb5402c2a..095c227994 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php @@ -17,6 +17,7 @@ final class PhabricatorRepositoryPushLog const REFTYPE_TAG = 'tag'; const REFTYPE_BOOKMARK = 'bookmark'; const REFTYPE_COMMIT = 'commit'; + const REFTYPE_REF = 'ref'; const CHANGEFLAG_ADD = 1; const CHANGEFLAG_DELETE = 2; diff --git a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php index 7459073ca8..7119ab42d7 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php @@ -149,6 +149,10 @@ final class PhabricatorRepositoryPushMailWorker $type_name = pht('bookmark'); $type_prefix = pht('bookmark:'); break; + case PhabricatorRepositoryPushLog::REFTYPE_REF: + $type_name = pht('ref'); + $type_prefix = pht('ref:'); + break; case PhabricatorRepositoryPushLog::REFTYPE_COMMIT: default: break; diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index 8a1b5e5dd1..e6c9f499b1 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -244,24 +244,11 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $commit_close_xaction = id(new DifferentialTransaction()) ->setTransactionType($type_close) - ->setNewValue(true) - ->setMetadataValue('isCommitClose', true); + ->setNewValue(true); $commit_close_xaction->setMetadataValue( 'commitPHID', $commit->getPHID()); - $commit_close_xaction->setMetadataValue( - 'committerPHID', - $committer_phid); - $commit_close_xaction->setMetadataValue( - 'committerName', - $data->getCommitDetail('committer')); - $commit_close_xaction->setMetadataValue( - 'authorPHID', - $author_phid); - $commit_close_xaction->setMetadataValue( - 'authorName', - $data->getAuthorName()); if ($low_level_query) { $commit_close_xaction->setMetadataValue( @@ -278,17 +265,13 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $content_source = $this->newContentSource(); - $update_data = $extraction_engine->updateRevisionWithCommit( + $extraction_engine->updateRevisionWithCommit( $revision, $commit, array( $commit_close_xaction, ), $content_source); - - foreach ($update_data as $key => $value) { - $data->setCommitDetail($key, $value); - } } } } diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 4bf4929f4b..183aea50dc 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -196,9 +196,6 @@ final class PhabricatorApplicationSearchController $submit->addButton($save_button); } - // TODO: A "Create Dashboard Panel" action goes here somewhere once - // we sort out T5307. - $form->appendChild($submit); $body = array(); @@ -387,7 +384,6 @@ final class PhabricatorApplicationSearchController require_celerity_resource('application-search-view-css'); return $this->newPage() - ->setApplicationMenu($this->buildApplicationMenu()) ->setTitle(pht('Query: %s', $title)) ->setCrumbs($crumbs) ->setNavigation($nav) @@ -611,7 +607,6 @@ final class PhabricatorApplicationSearchController ->setFooter($lists); return $this->newPage() - ->setApplicationMenu($this->buildApplicationMenu()) ->setTitle(pht('Saved Queries')) ->setCrumbs($crumbs) ->setNavigation($nav) diff --git a/src/applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php b/src/applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php new file mode 100644 index 0000000000..f811b8b15d --- /dev/null +++ b/src/applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php @@ -0,0 +1,16 @@ +willBuildNavigationItems(array($object)); + $object->willGetMenuItemViewList(array($object)); return pht('Edit Menu Item: %s', $object->getDisplayName()); } diff --git a/src/applications/search/editor/PhabricatorProfileMenuEditor.php b/src/applications/search/editor/PhabricatorProfileMenuEditor.php index 308cde0db5..71f8c32e94 100644 --- a/src/applications/search/editor/PhabricatorProfileMenuEditor.php +++ b/src/applications/search/editor/PhabricatorProfileMenuEditor.php @@ -121,5 +121,8 @@ final class PhabricatorProfileMenuEditor return $errors; } + protected function supportsSearch() { + return true; + } } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index a89a017e85..4f5308fa28 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1142,6 +1142,12 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } $constraints = $request->getValue('constraints', array()); + if (!is_array($constraints)) { + throw new Exception( + pht( + 'Parameter "constraints" must be a map of constraints, got "%s".', + phutil_describe_type($constraints))); + } $fields = $this->getSearchFieldsForConduit(); diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index d5cb9ee43b..aedbcc787f 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -6,10 +6,8 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { private $profileObject; private $customPHID; private $items; - private $defaultItem; private $controller; private $navigation; - private $showNavigation = true; private $editMode; private $pageClasses = array(); private $showContentCrumbs = true; @@ -72,26 +70,6 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { return $this->controller; } - private function setDefaultItem( - PhabricatorProfileMenuItemConfiguration $default_item) { - $this->defaultItem = $default_item; - return $this; - } - - public function getDefaultItem() { - $this->getItems(); - return $this->defaultItem; - } - - public function setShowNavigation($show) { - $this->showNavigation = $show; - return $this; - } - - public function getShowNavigation() { - return $this->showNavigation; - } - public function addContentPageClass($class) { $this->pageClasses[] = $class; return $this; @@ -152,35 +130,23 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $item_id = $request->getURIData('id'); } - $item_list = $this->getItems(); + $view_list = $this->newProfileMenuItemViewList(); - $selected_item = null; - if (strlen($item_id)) { - $item_id_int = (int)$item_id; - foreach ($item_list as $item) { - if ($item_id_int) { - if ((int)$item->getID() === $item_id_int) { - $selected_item = $item; - break; - } - } - - $builtin_key = $item->getBuiltinKey(); - if ($builtin_key === (string)$item_id) { - $selected_item = $item; - break; - } - } - } - - if (!$selected_item) { - if ($is_view) { - $selected_item = $this->getDefaultItem(); + if ($is_view) { + $selected_item = $this->selectViewItem($view_list, $item_id); + } else { + if (!strlen($item_id)) { + $item_id = self::ITEM_MANAGE; } + $selected_item = $this->selectEditItem($view_list, $item_id); } switch ($item_action) { case 'view': + // If we were not able to select an item, we're still going to render + // a page state. For example, this happens when you create a new + // portal for the first time. + break; case 'info': case 'hide': case 'default': @@ -201,20 +167,23 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { break; } - $navigation = $this->buildNavigation(); - + $navigation = $view_list->newNavigationView(); $crumbs = $controller->buildApplicationCrumbsForEditEngine(); if (!$is_view) { - $navigation->selectFilter(self::ITEM_MANAGE); + $edit_mode = null; if ($selected_item) { - if ($selected_item->getCustomPHID()) { - $edit_mode = 'custom'; - } else { - $edit_mode = 'global'; + if ($selected_item->getBuiltinKey() !== self::ITEM_MANAGE) { + if ($selected_item->getCustomPHID()) { + $edit_mode = 'custom'; + } else { + $edit_mode = 'global'; + } } - } else { + } + + if ($edit_mode === null) { $edit_mode = $request->getURIData('itemEditMode'); } @@ -231,24 +200,33 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { } $page_title = pht('Configure Menu'); } else { - $page_title = $selected_item->getDisplayName(); + if ($selected_item) { + $page_title = $selected_item->getDisplayName(); + } else { + $page_title = pht('Empty'); + } } switch ($item_action) { case 'view': - $navigation->selectFilter($selected_item->getDefaultMenuItemKey()); + if ($selected_item) { + try { + $content = $this->buildItemViewContent($selected_item); + } catch (Exception $ex) { + $content = id(new PHUIInfoView()) + ->setTitle(pht('Unable to Render Dashboard')) + ->setErrors(array($ex->getMessage())); + } - try { - $content = $this->buildItemViewContent($selected_item); - } catch (Exception $ex) { - $content = id(new PHUIInfoView()) - ->setTitle(pht('Unable to Render Dashboard')) - ->setErrors(array($ex->getMessage())); + $crumbs->addTextCrumb($selected_item->getDisplayName()); + } else { + $content = $this->newNoContentView($this->getItems()); } - $crumbs->addTextCrumb($selected_item->getDisplayName()); if (!$content) { - return new Aphront404Response(); + $content = $this->newEmptyView( + pht('Empty'), + pht('There is nothing here.')); } break; case 'configure': @@ -297,9 +275,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { if (!$this->isMenuEnginePinnable()) { return new Aphront404Response(); } - $content = $this->buildItemDefaultContent( - $selected_item, - $item_list); + $content = $this->buildItemDefaultContent($selected_item); break; case 'edit': $content = $this->buildItemEditContent(); @@ -329,9 +305,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $page->setCrumbs($crumbs); } - if ($this->getShowNavigation()) { - $page->setNavigation($navigation); - } + $page->setNavigation($navigation); if ($is_view) { foreach ($this->pageClasses as $class) { @@ -342,62 +316,6 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { return $page; } - public function buildNavigation() { - if ($this->navigation) { - return $this->navigation; - } - $nav = id(new AphrontSideNavFilterView()) - ->setIsProfileMenu(true) - ->setBaseURI(new PhutilURI($this->getItemURI(''))); - - $menu_items = $this->getItems(); - - $filtered_items = array(); - foreach ($menu_items as $menu_item) { - if ($menu_item->isDisabled()) { - continue; - } - $filtered_items[] = $menu_item; - } - $filtered_groups = mgroup($filtered_items, 'getMenuItemKey'); - foreach ($filtered_groups as $group) { - $first_item = head($group); - $first_item->willBuildNavigationItems($group); - } - - foreach ($menu_items as $menu_item) { - if ($menu_item->isDisabled()) { - continue; - } - - $items = $menu_item->buildNavigationMenuItems(); - foreach ($items as $item) { - $this->validateNavigationMenuItem($item); - } - - // If the item produced only a single item which does not otherwise - // have a key, try to automatically assign it a reasonable key. This - // makes selecting the correct item simpler. - - if (count($items) == 1) { - $item = head($items); - if ($item->getKey() === null) { - $default_key = $menu_item->getDefaultMenuItemKey(); - $item->setKey($default_key); - } - } - - foreach ($items as $item) { - $nav->addMenuItem($item); - } - } - - $nav->selectFilter(null); - - $this->navigation = $nav; - return $this->navigation; - } - private function getItems() { if ($this->items === null) { $this->items = $this->loadItems(self::MODE_COMBINED); @@ -450,6 +368,12 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { // stored config: it corresponds to an out-of-date or uninstalled // item. if (isset($items[$builtin_key])) { + $builtin_item = $items[$builtin_key]; + + // Copy runtime properties from the builtin item to the stored item. + $stored_item->setIsHeadItem($builtin_item->getIsHeadItem()); + $stored_item->setIsTailItem($builtin_item->getIsTailItem()); + $items[$builtin_key] = $stored_item; } else { continue; @@ -459,39 +383,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { } } - $items = $this->arrangeItems($items, $mode); - - // Make sure exactly one valid item is marked as default. - $default = null; - $first = null; - foreach ($items as $item) { - if (!$item->canMakeDefault() || $item->isDisabled()) { - continue; - } - - // If this engine doesn't support pinning items, don't respect any - // setting which might be present in the database. - if ($this->isMenuEnginePinnable()) { - if ($item->isDefault()) { - $default = $item; - break; - } - } - - if ($first === null) { - $first = $item; - } - } - - if (!$default) { - $default = $first; - } - - if ($default) { - $this->setDefaultItem($default); - } - - return $items; + return $this->arrangeItems($items, $mode); } private function loadBuiltinProfileItems($mode) { @@ -731,7 +623,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { * * @return bool True if items may be pinned as default items. */ - protected function isMenuEnginePinnable() { + public function isMenuEnginePinnable() { return !$this->isMenuEnginePersonalizable(); } @@ -795,7 +687,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $filtered_groups = mgroup($items, 'getMenuItemKey'); foreach ($filtered_groups as $group) { $first_item = head($group); - $first_item->willBuildNavigationItems($group); + $first_item->willGetMenuItemViewList($group); } // Users only need to be able to edit the object which this menu appears @@ -824,6 +716,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { ->setID($list_id) ->setNoDataString(pht('This menu currently has no items.')); + $any_draggable = false; foreach ($items as $item) { $id = $item->getID(); $builtin_key = $item->getBuiltinKey(); @@ -844,14 +737,25 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $view->setHeader($name); $view->addAttribute($type); + $icon = $item->getMenuItem()->getMenuItemTypeIcon(); + if ($icon !== null) { + $view->setStatusIcon($icon); + } + if ($can_edit) { - $view - ->setGrippable(true) - ->addSigil('profile-menu-item') - ->setMetadata( - array( - 'key' => nonempty($id, $builtin_key), - )); + $can_move = (!$item->getIsHeadItem() && !$item->getIsTailItem()); + if ($can_move) { + $view + ->setGrippable(true) + ->addSigil('profile-menu-item') + ->setMetadata( + array( + 'key' => nonempty($id, $builtin_key), + )); + $any_draggable = true; + } else { + $view->setGrippable(false); + } if ($id) { $default_uri = $this->getItemURI("default/{$id}/"); @@ -966,8 +870,16 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { ->setHeader(pht('Menu Items')) ->setHeaderIcon('fa-list'); + $list_header = id(new PHUIHeaderView()) + ->setHeader(pht('Current Menu Items')); + + if ($any_draggable) { + $list_header->setSubheader( + pht('Drag items in this list to reorder them.')); + } + $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Current Menu Items')) + ->setHeader($list_header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($list); @@ -1149,8 +1061,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { } private function buildItemDefaultContent( - PhabricatorProfileMenuItemConfiguration $configuration, - array $items) { + PhabricatorProfileMenuItemConfiguration $configuration) { $controller = $this->getController(); $request = $controller->getRequest(); @@ -1212,7 +1123,26 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { protected function newManageItem() { return $this->newItem() ->setBuiltinKey(self::ITEM_MANAGE) - ->setMenuItemKey(PhabricatorManageProfileMenuItem::MENUITEMKEY); + ->setMenuItemKey(PhabricatorManageProfileMenuItem::MENUITEMKEY) + ->setIsTailItem(true); + } + + protected function newDividerItem($key) { + return $this->newItem() + ->setBuiltinKey($key) + ->setMenuItemKey(PhabricatorDividerProfileMenuItem::MENUITEMKEY) + ->setIsTailItem(true); + } + + public function getDefaultMenuItemConfiguration() { + $configs = $this->getItems(); + foreach ($configs as $config) { + if ($config->isDefault()) { + return $config; + } + } + + return null; } public function adjustDefault($key) { @@ -1319,5 +1249,113 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { return $items; } + final protected function newEmptyView($title, $message) { + return id(new PHUIInfoView()) + ->setTitle($title) + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->setErrors( + array( + $message, + )); + } + + protected function newNoContentView(array $items) { + return $this->newEmptyView( + pht('No Content'), + pht('No visible menu items can render content.')); + } + + + final public function newProfileMenuItemViewList() { + $items = $this->getItems(); + + // Throw away disabled items: they are not allowed to build any views for + // the menu. + foreach ($items as $key => $item) { + if ($item->isDisabled()) { + unset($items[$key]); + continue; + } + } + + // Give each item group a callback so it can load data it needs to render + // views. + $groups = mgroup($items, 'getMenuItemKey'); + foreach ($groups as $group) { + $item = head($group); + $item->willGetMenuItemViewList($group); + } + + $view_list = id(new PhabricatorProfileMenuItemViewList()) + ->setProfileMenuEngine($this); + + foreach ($items as $item) { + $views = $item->getMenuItemViewList(); + foreach ($views as $view) { + $view_list->addItemView($view); + } + } + + return $view_list; + } + + private function selectViewItem( + PhabricatorProfileMenuItemViewList $view_list, + $item_id) { + + // Figure out which view's content we're going to render. In most cases, + // the URI tells us. If we don't have an identifier in the URI, we'll + // render the default view instead. + + $selected_view = null; + if (strlen($item_id)) { + $item_views = $view_list->getViewsWithItemIdentifier($item_id); + if ($item_views) { + $selected_view = head($item_views); + } + } else { + $default_views = $view_list->getDefaultViews(); + if ($default_views) { + $selected_view = head($default_views); + } + } + + if ($selected_view) { + $view_list->setSelectedView($selected_view); + $selected_item = $selected_view->getMenuItemConfiguration(); + } else { + $selected_item = null; + } + + return $selected_item; + } + + private function selectEditItem( + PhabricatorProfileMenuItemViewList $view_list, + $item_id) { + + // First, try to select a visible item using the normal view selection + // pathway. If this works, it also highlights the menu properly. + + if ($item_id) { + $selected_item = $this->selectViewItem($view_list, $item_id); + if ($selected_item) { + return $selected_item; + } + } + + // If we didn't find an item in the view list, we may be enabling an item + // which is currently disabled or editing an item which is not generating + // any actual items in the menu. + + foreach ($this->getItems() as $item) { + if ($item->matchesIdentifier($item_id)) { + return $item; + } + } + + return null; + } + } diff --git a/src/applications/search/engine/PhabricatorProfileMenuItemView.php b/src/applications/search/engine/PhabricatorProfileMenuItemView.php new file mode 100644 index 0000000000..d947afcba6 --- /dev/null +++ b/src/applications/search/engine/PhabricatorProfileMenuItemView.php @@ -0,0 +1,232 @@ +config = $config; + return $this; + } + + public function getMenuItemConfiguration() { + return $this->config; + } + + public function setURI($uri) { + $this->uri = $uri; + return $this; + } + + public function getURI() { + return $this->uri; + } + + public function setName($name) { + $this->name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function setIcon($icon) { + $this->icon = $icon; + return $this; + } + + public function getIcon() { + return $this->icon; + } + + public function setIconImage($icon_image) { + $this->iconImage = $icon_image; + return $this; + } + + public function getIconImage() { + return $this->iconImage; + } + + public function setDisabled($disabled) { + $this->disabled = $disabled; + return $this; + } + + public function getDisabled() { + return $this->disabled; + } + + public function setTooltip($tooltip) { + $this->tooltip = $tooltip; + return $this; + } + + public function getTooltip() { + return $this->tooltip; + } + + public function newAction($uri) { + $this->actions[] = $uri; + return null; + } + + public function newCount($count) { + $this->counts[] = $count; + return null; + } + + public function newProfileImage($src) { + $this->images[] = $src; + return null; + } + + public function newProgressBar($bar) { + $this->progressBars[] = $bar; + return null; + } + + public function setIsExternalLink($is_external) { + $this->isExternalLink = $is_external; + return $this; + } + + public function getIsExternalLink() { + return $this->isExternalLink; + } + + public function setIsLabel($is_label) { + return $this->setSpecialType('label'); + } + + public function getIsLabel() { + return $this->isSpecialType('label'); + } + + public function setIsDivider($is_divider) { + return $this->setSpecialType('divider'); + } + + public function getIsDivider() { + return $this->isSpecialType('divider'); + } + + private function setSpecialType($type) { + $this->specialType = $type; + return $this; + } + + private function isSpecialType($type) { + return ($this->specialType === $type); + } + + public function newListItemView() { + $view = id(new PHUIListItemView()) + ->setName($this->getName()); + + $uri = $this->getURI(); + if (strlen($uri)) { + if ($this->getIsExternalLink()) { + if (!PhabricatorEnv::isValidURIForLink($uri)) { + $uri = '#'; + } + $view->setRel('noreferrer'); + } + + $view->setHref($uri); + } + + $icon = $this->getIcon(); + if ($icon) { + $view->setIcon($icon); + } + + $icon_image = $this->getIconImage(); + if ($icon_image) { + $view->setProfileImage($icon_image); + } + + if ($this->getDisabled()) { + $view->setDisabled(true); + } + + if ($this->getIsLabel()) { + $view->setType(PHUIListItemView::TYPE_LABEL); + } + + if ($this->getIsDivider()) { + $view + ->setType(PHUIListItemView::TYPE_DIVIDER) + ->addClass('phui-divider'); + } + + $tooltip = $this->getTooltip(); + if (strlen($tooltip)) { + $view->setTooltip($tooltip); + } + + if ($this->images) { + require_celerity_resource('people-picture-menu-item-css'); + foreach ($this->images as $image_src) { + $classes = array(); + $classes[] = 'people-menu-image'; + + if ($this->getDisabled()) { + $classes[] = 'phui-image-disabled'; + } + + $image = phutil_tag( + 'img', + array( + 'src' => $image_src, + 'class' => implode(' ', $classes), + )); + + $image = phutil_tag( + 'div', + array( + 'class' => 'people-menu-image-container', + ), + $image); + + $view->appendChild($image); + } + } + + foreach ($this->counts as $count) { + $view->appendChild( + phutil_tag( + 'span', + array( + 'class' => 'phui-list-item-count', + ), + $count)); + } + + foreach ($this->actions as $action) { + $view->setActionIcon('fa-pencil', $action); + } + + foreach ($this->progressBars as $bar) { + $view->appendChild($bar); + } + + return $view; + } + +} diff --git a/src/applications/search/engine/PhabricatorProfileMenuItemViewList.php b/src/applications/search/engine/PhabricatorProfileMenuItemViewList.php new file mode 100644 index 0000000000..e5a3fd3d38 --- /dev/null +++ b/src/applications/search/engine/PhabricatorProfileMenuItemViewList.php @@ -0,0 +1,266 @@ +engine = $engine; + return $this; + } + + public function getProfileMenuEngine() { + return $this->engine; + } + + public function addItemView(PhabricatorProfileMenuItemView $view) { + $this->views[] = $view; + return $this; + } + + public function getItemViews() { + return $this->views; + } + + public function setSelectedView(PhabricatorProfileMenuItemView $view) { + $found = false; + foreach ($this->getItemViews() as $item_view) { + if ($view === $item_view) { + $found = true; + break; + } + } + + if (!$found) { + throw new Exception( + pht( + 'Provided view is not one of the views in the list: you can only '. + 'select a view which appears in the list.')); + } + + $this->selectedView = $view; + + return $this; + } + + public function setSelectedViewWithItemIdentifier($identifier) { + $views = $this->getViewsWithItemIdentifier($identifier); + + if (!$views) { + throw new Exception( + pht( + 'No views match identifier "%s"!', + $identifier)); + } + + return $this->setSelectedView(head($views)); + } + + public function getViewsWithItemIdentifier($identifier) { + $views = $this->getItemViews(); + + $results = array(); + foreach ($views as $view) { + $config = $view->getMenuItemConfiguration(); + + if (!$config->matchesIdentifier($identifier)) { + continue; + } + + $results[] = $view; + } + + return $results; + } + + public function getDefaultViews() { + $engine = $this->getProfileMenuEngine(); + $can_pin = $engine->isMenuEnginePinnable(); + + $views = $this->getItemViews(); + + // Remove all the views which were built by an item that can not be the + // default item. + foreach ($views as $key => $view) { + $config = $view->getMenuItemConfiguration(); + + if (!$config->canMakeDefault()) { + unset($views[$key]); + continue; + } + } + + // Remove disabled views. + foreach ($views as $key => $view) { + if ($view->getDisabled()) { + unset($views[$key]); + } + } + + // If this engine supports pinning items and we have candidate views from a + // valid pinned item, they are the default views. + if ($can_pin) { + $pinned = array(); + + foreach ($views as $key => $view) { + $config = $view->getMenuItemConfiguration(); + + if ($config->isDefault()) { + $pinned[] = $view; + continue; + } + } + + if ($pinned) { + return $pinned; + } + } + + // Return whatever remains that's still valid. + return $views; + } + + public function newNavigationView() { + $engine = $this->getProfileMenuEngine(); + + $base_uri = $engine->getItemURI(''); + $base_uri = new PhutilURI($base_uri); + + $navigation = id(new AphrontSideNavFilterView()) + ->setIsProfileMenu(true) + ->setBaseURI($base_uri); + + $views = $this->getItemViews(); + $selected_item = null; + $item_key = 0; + $items = array(); + foreach ($views as $view) { + $list_item = $view->newListItemView(); + + // Assign unique keys to the list items. These keys are purely internal. + $list_item->setKey(sprintf('item(%d)', $item_key++)); + + if ($this->selectedView) { + if ($this->selectedView === $view) { + $selected_item = $list_item; + } + } + + $navigation->addMenuItem($list_item); + $items[] = $list_item; + } + + if (!$views) { + // If the navigation menu has no items, add an empty label item to + // force it to render something. + $empty_item = id(new PHUIListItemView()) + ->setType(PHUIListItemView::TYPE_LABEL); + $navigation->addMenuItem($empty_item); + } + + $highlight_key = $this->getHighlightedItemKey( + $items, + $selected_item); + $navigation->selectFilter($highlight_key); + + return $navigation; + } + + private function getHighlightedItemKey( + array $items, + PHUIListItemView $selected_item = null) { + + assert_instances_of($items, 'PHUIListItemView'); + + $default_key = null; + if ($selected_item) { + $default_key = $selected_item->getKey(); + } + + $engine = $this->getProfileMenuEngine(); + $controller = $engine->getController(); + + // In some rare cases, when like building the "Favorites" menu on a + // 404 page, we may not have a controller. Just accept whatever default + // behavior we'd otherwise end up with. + if (!$controller) { + return $default_key; + } + + $request = $controller->getRequest(); + + // See T12949. If one of the menu items is a link to the same URI that + // the page was accessed with, we want to highlight that item. For example, + // this allows you to add links to a menu that apply filters to a + // workboard. + + $matches = array(); + foreach ($items as $item) { + $href = $item->getHref(); + if ($this->isMatchForRequestURI($request, $href)) { + $matches[] = $item; + } + } + + foreach ($matches as $match) { + if ($match->getKey() === $default_key) { + return $default_key; + } + } + + if ($matches) { + return head($matches)->getKey(); + } + + return $default_key; + } + + private function isMatchForRequestURI(AphrontRequest $request, $item_uri) { + $request_uri = $request->getAbsoluteRequestURI(); + $item_uri = new PhutilURI($item_uri); + + // If the request URI and item URI don't have matching paths, they + // do not match. + if ($request_uri->getPath() !== $item_uri->getPath()) { + return false; + } + + // If the request URI and item URI don't have matching parameters, they + // also do not match. We're specifically trying to let "?filter=X" work + // on Workboards, among other use cases, so this is important. + $request_params = $request_uri->getQueryParamsAsPairList(); + $item_params = $item_uri->getQueryParamsAsPairList(); + if ($request_params !== $item_params) { + return false; + } + + // If the paths and parameters match, the item domain must be: empty; or + // match the request domain; or match the production domain. + + $request_domain = $request_uri->getDomain(); + + $production_uri = PhabricatorEnv::getProductionURI('/'); + $production_domain = id(new PhutilURI($production_uri)) + ->getDomain(); + + $allowed_domains = array( + '', + $request_domain, + $production_domain, + ); + $allowed_domains = array_fuse($allowed_domains); + + $item_domain = $item_uri->getDomain(); + $item_domain = (string)$item_domain; + + if (isset($allowed_domains[$item_domain])) { + return true; + } + + return false; + } + +} diff --git a/src/applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php b/src/applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php new file mode 100644 index 0000000000..528285c17f --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php @@ -0,0 +1,50 @@ +getIndexEdgeType(); + + $old_edges = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object->getPHID(), + $edge_type); + $old_edges = array_fuse($old_edges); + + $new_edges = $this->getIndexDestinationPHIDs($object); + $new_edges = array_fuse($new_edges); + + $add_edges = array_diff_key($new_edges, $old_edges); + $rem_edges = array_diff_key($old_edges, $new_edges); + + if (!$add_edges && !$rem_edges) { + return; + } + + $editor = new PhabricatorEdgeEditor(); + + foreach ($add_edges as $phid) { + $editor->addEdge($object->getPHID(), $edge_type, $phid); + } + + foreach ($rem_edges as $phid) { + $editor->removeEdge($object->getPHID(), $edge_type, $phid); + } + + $editor->save(); + } + + final public function getIndexVersion($object) { + $phids = $this->getIndexDestinationPHIDs($object); + sort($phids); + $phids = implode(':', $phids); + return PhabricatorHash::digestForIndex($phids); + } + +} diff --git a/src/applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php b/src/applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php new file mode 100644 index 0000000000..078e51058c --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php @@ -0,0 +1,28 @@ +getAffectedObjectPHIDs(); + } + +} diff --git a/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php index aa42d56cfb..040b877368 100644 --- a/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php @@ -68,7 +68,7 @@ final class PhabricatorApplicationProfileMenuItem return head($apps); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $viewer = $this->getViewer(); $app = $this->getApplication($config); @@ -83,8 +83,8 @@ final class PhabricatorApplicationProfileMenuItem return array(); } - $item = $this->newItem() - ->setHref($app->getApplicationURI()) + $item = $this->newItemView() + ->setURI($app->getApplicationURI()) ->setName($this->getDisplayName($config)) ->setIcon($app->getIcon()); diff --git a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php index 542c634958..591dee8604 100644 --- a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php @@ -41,7 +41,7 @@ final class PhabricatorConpherenceProfileMenuItem return $conpherence; } - public function willBuildNavigationItems(array $items) { + public function willGetMenuItemViewList(array $items) { $viewer = $this->getViewer(); $room_phids = array(); foreach ($items as $item) { @@ -98,7 +98,7 @@ final class PhabricatorConpherenceProfileMenuItem return $config->getMenuItemProperty('name'); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $viewer = $this->getViewer(); $room = $this->getConpherence($config); @@ -114,21 +114,14 @@ final class PhabricatorConpherenceProfileMenuItem $unread_count = $data['unread_count']; } - $count = null; - if ($unread_count) { - $count = phutil_tag( - 'span', - array( - 'class' => 'phui-list-item-count', - ), - $unread_count); - } - - $item = $this->newItem() - ->setHref('/'.$room->getMonogram()) + $item = $this->newItemView() + ->setURI('/'.$room->getMonogram()) ->setName($this->getDisplayName($config)) - ->setIcon('fa-comments') - ->appendChild($count); + ->setIcon('fa-comments'); + + if ($unread_count) { + $item->newCount($unread_count); + } return array( $item, diff --git a/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php index 7d4f319b61..9d89c52ff8 100644 --- a/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php @@ -8,6 +8,7 @@ final class PhabricatorDashboardProfileMenuItem const FIELD_DASHBOARD = 'dashboardPHID'; private $dashboard; + private $dashboardHandle; public function getMenuItemTypeIcon() { return 'fa-dashboard'; @@ -26,37 +27,44 @@ final class PhabricatorDashboardProfileMenuItem return true; } - public function attachDashboard($dashboard) { + private function attachDashboard(PhabricatorDashboard $dashboard = null) { $this->dashboard = $dashboard; return $this; } - public function getDashboard() { - $dashboard = $this->dashboard; - - if (!$dashboard) { - return null; - } else if ($dashboard->isArchived()) { - return null; - } - - return $dashboard; + private function getDashboard() { + return $this->dashboard; } + public function getAffectedObjectPHIDs( + PhabricatorProfileMenuItemConfiguration $config) { + return array( + $this->getDashboardPHID($config), + ); + } + + public function newPageContent( PhabricatorProfileMenuItemConfiguration $config) { $viewer = $this->getViewer(); - $dashboard_phid = $config->getMenuItemProperty('dashboardPHID'); + $dashboard_phid = $this->getDashboardPHID($config); // Reload the dashboard to attach panels, which we need for rendering. $dashboard = id(new PhabricatorDashboardQuery()) ->setViewer($viewer) ->withPHIDs(array($dashboard_phid)) - ->needPanels(true) ->executeOne(); if (!$dashboard) { - return null; + return $this->newEmptyView( + pht('Invalid Dashboard'), + pht('This dashboard is invalid and could not be loaded.')); + } + + if ($dashboard->isArchived()) { + return $this->newEmptyView( + pht('Archived Dashboard'), + pht('This dashboard has been archived.')); } $engine = id(new PhabricatorDashboardRenderingEngine()) @@ -66,11 +74,11 @@ final class PhabricatorDashboardProfileMenuItem return $engine->renderDashboard(); } - public function willBuildNavigationItems(array $items) { + public function willGetMenuItemViewList(array $items) { $viewer = $this->getViewer(); $dashboard_phids = array(); foreach ($items as $item) { - $dashboard_phids[] = $item->getMenuItemProperty('dashboardPHID'); + $dashboard_phids[] = $this->getDashboardPHID($item); } $dashboards = id(new PhabricatorDashboardQuery()) @@ -78,11 +86,18 @@ final class PhabricatorDashboardProfileMenuItem ->withPHIDs($dashboard_phids) ->execute(); + $handles = $viewer->loadHandles($dashboard_phids); + $dashboards = mpull($dashboards, null, 'getPHID'); foreach ($items as $item) { - $dashboard_phid = $item->getMenuItemProperty('dashboardPHID'); + $dashboard_phid = $this->getDashboardPHID($item); $dashboard = idx($dashboards, $dashboard_phid, null); - $item->getMenuItem()->attachDashboard($dashboard); + + $menu_item = $item->getMenuItem(); + + $menu_item + ->attachDashboard($dashboard) + ->setDashboardHandle($handles[$dashboard_phid]); } } @@ -91,7 +106,15 @@ final class PhabricatorDashboardProfileMenuItem $dashboard = $this->getDashboard(); if (!$dashboard) { - return pht('(Restricted/Invalid Dashboard)'); + if ($this->getDashboardHandle()->getPolicyFiltered()) { + return pht('Restricted Dashboard'); + } else { + return pht('Invalid Dashboard'); + } + } + + if ($dashboard->isArchived()) { + return pht('Archived Dashboard'); } if (strlen($this->getName($config))) { @@ -109,7 +132,7 @@ final class PhabricatorDashboardProfileMenuItem ->setLabel(pht('Dashboard')) ->setIsRequired(true) ->setDatasource(new PhabricatorDashboardDatasource()) - ->setSingleValue($config->getMenuItemProperty('dashboardPHID')), + ->setSingleValue($this->getDashboardPHID($config)), id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) @@ -122,24 +145,43 @@ final class PhabricatorDashboardProfileMenuItem return $config->getMenuItemProperty('name'); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { + $is_disabled = true; + $action_uri = null; + $dashboard = $this->getDashboard(); - if (!$dashboard) { - return array(); + if ($dashboard) { + if ($dashboard->isArchived()) { + $icon = 'fa-ban'; + $name = $this->getDisplayName($config); + } else { + $icon = $dashboard->getIcon(); + $name = $this->getDisplayName($config); + $is_disabled = false; + $action_uri = $dashboard->getURI(); + } + } else { + $icon = 'fa-ban'; + if ($this->getDashboardHandle()->getPolicyFiltered()) { + $name = pht('Restricted Dashboard'); + } else { + $name = pht('Invalid Dashboard'); + } } - $icon = $dashboard->getIcon(); - $name = $this->getDisplayName($config); - $href = $this->getItemViewURI($config); - $action_href = '/dashboard/arrange/'.$dashboard->getID().'/'; + $uri = $this->getItemViewURI($config); - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon) - ->setActionIcon('fa-pencil', $action_href); + ->setDisabled($is_disabled); + + if ($action_uri) { + $item->newAction($action_uri); + } return array( $item, @@ -191,4 +233,18 @@ final class PhabricatorDashboardProfileMenuItem return $errors; } + private function getDashboardPHID( + PhabricatorProfileMenuItemConfiguration $config) { + return $config->getMenuItemProperty('dashboardPHID'); + } + + private function getDashboardHandle() { + return $this->dashboardHandle; + } + + private function setDashboardHandle(PhabricatorObjectHandle $handle) { + $this->dashboardHandle = $handle; + return $this; + } + } diff --git a/src/applications/search/menuitem/PhabricatorDividerProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorDividerProfileMenuItem.php index e6a6e608e6..8510418fab 100644 --- a/src/applications/search/menuitem/PhabricatorDividerProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorDividerProfileMenuItem.php @@ -34,12 +34,11 @@ final class PhabricatorDividerProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { - $item = $this->newItem() - ->setType(PHUIListItemView::TYPE_DIVIDER) - ->addClass('phui-divider'); + $item = $this->newItemView() + ->setIsDivider(true); return array( $item, diff --git a/src/applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php index 88749d247f..71e3d7e8a5 100644 --- a/src/applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php @@ -34,7 +34,7 @@ final class PhabricatorEditEngineProfileMenuItem return $form; } - public function willBuildNavigationItems(array $items) { + public function willGetMenuItemViewList(array $items) { $viewer = $this->getViewer(); $engines = PhabricatorEditEngine::getAllEditEngines(); $engine_keys = array_keys($engines); @@ -99,7 +99,7 @@ final class PhabricatorEditEngineProfileMenuItem return $config->getMenuItemProperty('name'); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $form = $this->getForm(); @@ -110,13 +110,13 @@ final class PhabricatorEditEngineProfileMenuItem $icon = $form->getIcon(); $name = $this->getDisplayName($config); - $href = $form->getCreateURI(); - if ($href === null) { + $uri = $form->getCreateURI(); + if ($uri === null) { return array(); } - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon); diff --git a/src/applications/search/menuitem/PhabricatorLabelProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorLabelProfileMenuItem.php index 1f769905d7..a152da5898 100644 --- a/src/applications/search/menuitem/PhabricatorLabelProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorLabelProfileMenuItem.php @@ -7,7 +7,7 @@ final class PhabricatorLabelProfileMenuItem const FIELD_NAME = 'name'; public function getMenuItemTypeIcon() { - return 'fa-map-signs'; + return 'fa-tag'; } public function getMenuItemTypeName() { @@ -39,14 +39,14 @@ final class PhabricatorLabelProfileMenuItem return $config->getMenuItemProperty('name'); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $name = $this->getLabelName($config); - $item = $this->newItem() + $item = $this->newItemView() ->setName($name) - ->setType(PHUIListItemView::TYPE_LABEL); + ->setIsLabel(true); return array( $item, diff --git a/src/applications/search/menuitem/PhabricatorLinkProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorLinkProfileMenuItem.php index 0b6a2f330e..bba3b01060 100644 --- a/src/applications/search/menuitem/PhabricatorLinkProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorLinkProfileMenuItem.php @@ -71,22 +71,14 @@ final class PhabricatorLinkProfileMenuItem return $config->getMenuItemProperty('tooltip'); } - private function isValidLinkURI($uri) { - return PhabricatorEnv::isValidURIForLink($uri); - } - - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $icon = $this->getLinkIcon($config); $name = $this->getLinkName($config); - $href = $this->getLinkURI($config); + $uri = $this->getLinkURI($config); $tooltip = $this->getLinkTooltip($config); - if (!$this->isValidLinkURI($href)) { - $href = '#'; - } - $icon_object = id(new PhabricatorProfileMenuItemIconSet()) ->getIcon($icon); if ($icon_object) { @@ -95,12 +87,12 @@ final class PhabricatorLinkProfileMenuItem $icon_class = 'fa-link'; } - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon_class) ->setTooltip($tooltip) - ->setRel('noreferrer'); + ->setIsExternalLink(true); return array( $item, @@ -142,7 +134,7 @@ final class PhabricatorLinkProfileMenuItem continue; } - if (!$this->isValidLinkURI($new)) { + if (!PhabricatorEnv::isValidURIForLink($new)) { $errors[] = $this->newInvalidError( pht( 'URI "%s" is not a valid link URI. It should be a full, valid '. diff --git a/src/applications/search/menuitem/PhabricatorManageProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorManageProfileMenuItem.php index d5de555975..89ac4a5633 100644 --- a/src/applications/search/menuitem/PhabricatorManageProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorManageProfileMenuItem.php @@ -13,6 +13,10 @@ final class PhabricatorManageProfileMenuItem return pht('Edit Menu'); } + public function getMenuItemTypeIcon() { + return 'fa-pencil'; + } + public function canHideMenuItem( PhabricatorProfileMenuItemConfiguration $config) { return false; @@ -45,7 +49,7 @@ final class PhabricatorManageProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $viewer = $this->getViewer(); @@ -54,13 +58,13 @@ final class PhabricatorManageProfileMenuItem } $engine = $this->getEngine(); - $href = $engine->getItemURI('configure/'); + $uri = $engine->getItemURI('configure/'); $name = $this->getDisplayName($config); $icon = 'fa-pencil'; - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) ->setIcon($icon); diff --git a/src/applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php index 071979d391..d85b02b3a2 100644 --- a/src/applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php @@ -50,7 +50,7 @@ final class PhabricatorMotivatorProfileMenuItem ); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $source = $config->getMenuItemProperty('source'); @@ -66,11 +66,11 @@ final class PhabricatorMotivatorProfileMenuItem $fact_text = $this->selectFact($facts); - $item = $this->newItem() + $item = $this->newItemView() ->setName($fact_name) ->setIcon($fact_icon) ->setTooltip($fact_text) - ->setHref('#'); + ->setURI('#'); return array( $item, diff --git a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php index 061afc7fad..773a0f09ac 100644 --- a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php @@ -5,16 +5,6 @@ abstract class PhabricatorProfileMenuItem extends Phobject { private $viewer; private $engine; - final public function buildNavigationMenuItems( - PhabricatorProfileMenuItemConfiguration $config) { - return $this->newNavigationMenuItems($config); - } - - abstract protected function newNavigationMenuItems( - PhabricatorProfileMenuItemConfiguration $config); - - public function willBuildNavigationItems(array $items) {} - public function getMenuItemTypeIcon() { return null; } @@ -76,10 +66,38 @@ abstract class PhabricatorProfileMenuItem extends Phobject { ->execute(); } - protected function newItem() { - return new PHUIListItemView(); + final protected function newItemView() { + return new PhabricatorProfileMenuItemView(); } + public function willGetMenuItemViewList(array $items) {} + + final public function getMenuItemViewList( + PhabricatorProfileMenuItemConfiguration $config) { + $list = $this->newMenuItemViewList($config); + + if (!is_array($list)) { + throw new Exception( + pht( + 'Expected "newMenuItemViewList()" to return a list (in class "%s"), '. + 'but it returned something else ("%s").', + get_class($this), + phutil_describe_type($list))); + } + + assert_instances_of($list, 'PhabricatorProfileMenuItemView'); + + foreach ($list as $view) { + $view->setMenuItemConfiguration($config); + } + + return $list; + } + + abstract protected function newMenuItemViewList( + PhabricatorProfileMenuItemConfiguration $config); + + public function newPageContent( PhabricatorProfileMenuItemConfiguration $config) { return null; @@ -131,4 +149,19 @@ abstract class PhabricatorProfileMenuItem extends Phobject { return $this->newError(pht('Invalid'), $message, $xaction); } + final protected function newEmptyView($title, $message) { + return id(new PHUIInfoView()) + ->setTitle($title) + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->setErrors( + array( + $message, + )); + } + + public function getAffectedObjectPHIDs( + PhabricatorProfileMenuItemConfiguration $config) { + return array(); + } + } diff --git a/src/applications/search/menuitem/PhabricatorProjectProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorProjectProfileMenuItem.php index aadabd179a..efb61f06a1 100644 --- a/src/applications/search/menuitem/PhabricatorProjectProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorProjectProfileMenuItem.php @@ -35,7 +35,7 @@ final class PhabricatorProjectProfileMenuItem return $project; } - public function willBuildNavigationItems(array $items) { + public function willGetMenuItemViewList(array $items) { $viewer = $this->getViewer(); $project_phids = array(); foreach ($items as $item) { @@ -90,7 +90,7 @@ final class PhabricatorProjectProfileMenuItem return $config->getMenuItemProperty('name'); } - protected function newNavigationMenuItems( + protected function newMenuItemViewList( PhabricatorProfileMenuItemConfiguration $config) { $project = $this->getProject(); @@ -100,12 +100,12 @@ final class PhabricatorProjectProfileMenuItem $picture = $project->getProfileImageURI(); $name = $this->getDisplayName($config); - $href = $project->getURI(); + $uri = $project->getURI(); - $item = $this->newItem() - ->setHref($href) + $item = $this->newItemView() + ->setURI($uri) ->setName($name) - ->setProfileImage($picture); + ->setIconImage($picture); return array( $item, diff --git a/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php b/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php index 230989e88e..16b5d793a4 100644 --- a/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php +++ b/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php @@ -8,6 +8,7 @@ final class PhabricatorProfileMenuItemConfigurationQuery private $profilePHIDs; private $customPHIDs; private $includeGlobal; + private $affectedObjectPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -30,6 +31,11 @@ final class PhabricatorProfileMenuItemConfigurationQuery return $this; } + public function withAffectedObjectPHIDs(array $phids) { + $this->affectedObjectPHIDs = $phids; + return $this; + } + public function newResultObject() { return new PhabricatorProfileMenuItemConfiguration(); } @@ -44,21 +50,21 @@ final class PhabricatorProfileMenuItemConfigurationQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'config.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'config.phid IN (%Ls)', $this->phids); } if ($this->profilePHIDs !== null) { $where[] = qsprintf( $conn, - 'profilePHID IN (%Ls)', + 'config.profilePHID IN (%Ls)', $this->profilePHIDs); } @@ -66,23 +72,45 @@ final class PhabricatorProfileMenuItemConfigurationQuery if ($this->customPHIDs && $this->includeGlobal) { $where[] = qsprintf( $conn, - 'customPHID IN (%Ls) OR customPHID IS NULL', + 'config.customPHID IN (%Ls) OR config.customPHID IS NULL', $this->customPHIDs); } else if ($this->customPHIDs) { $where[] = qsprintf( $conn, - 'customPHID IN (%Ls)', + 'config.customPHID IN (%Ls)', $this->customPHIDs); } else { $where[] = qsprintf( $conn, - 'customPHID IS NULL'); + 'config.customPHID IS NULL'); } } + if ($this->affectedObjectPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'affected.dst IN (%Ls)', + $this->affectedObjectPHIDs); + } + return $where; } + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + if ($this->affectedObjectPHIDs !== null) { + $joins[] = qsprintf( + $conn, + 'JOIN %T affected ON affected.src = config.phid + AND affected.type = %d', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + PhabricatorProfileMenuItemAffectsObjectEdgeType::EDGECONST); + } + + return $joins; + } + protected function willFilterPage(array $page) { $items = PhabricatorProfileMenuItem::getAllMenuItems(); foreach ($page as $key => $item) { @@ -128,4 +156,8 @@ final class PhabricatorProfileMenuItemConfigurationQuery return 'PhabricatorSearchApplication'; } + protected function getPrimaryTableAlias() { + return 'config'; + } + } diff --git a/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php b/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php index 8520cac1bd..4307b19732 100644 --- a/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php +++ b/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php @@ -5,7 +5,8 @@ final class PhabricatorProfileMenuItemConfiguration implements PhabricatorPolicyInterface, PhabricatorExtendedPolicyInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorIndexableInterface { protected $profilePHID; protected $menuItemKey; @@ -17,6 +18,8 @@ final class PhabricatorProfileMenuItemConfiguration private $profileObject = self::ATTACHABLE; private $menuItem = self::ATTACHABLE; + private $isHeadItem = false; + private $isTailItem = false; const VISIBILITY_DEFAULT = 'default'; const VISIBILITY_VISIBLE = 'visible'; @@ -98,10 +101,6 @@ final class PhabricatorProfileMenuItemConfiguration return idx($this->menuItemProperties, $key, $default); } - public function buildNavigationMenuItems() { - return $this->getMenuItem()->buildNavigationMenuItems($this); - } - public function getMenuItemTypeName() { return $this->getMenuItem()->getMenuItemTypeName(); } @@ -122,8 +121,12 @@ final class PhabricatorProfileMenuItemConfiguration return $this->getMenuItem()->shouldEnableForObject($object); } - public function willBuildNavigationItems(array $items) { - return $this->getMenuItem()->willBuildNavigationItems($items); + public function willGetMenuItemViewList(array $items) { + return $this->getMenuItem()->willGetMenuItemViewList($items); + } + + public function getMenuItemViewList() { + return $this->getMenuItem()->getMenuItemViewList($this); } public function validateTransactions(array $map) { @@ -158,6 +161,15 @@ final class PhabricatorProfileMenuItemConfiguration $is_global = 1; } + // Sort "head" items above other items and "tail" items after other items. + if ($this->getIsHeadItem()) { + $force_position = 0; + } else if ($this->getIsTailItem()) { + $force_position = 2; + } else { + $force_position = 1; + } + // Sort items with an explicit order above items without an explicit order, // so any newly created builtins go to the bottom. $order = $this->getMenuItemOrder(); @@ -169,6 +181,7 @@ final class PhabricatorProfileMenuItemConfiguration return id(new PhutilSortVector()) ->addInt($is_global) + ->addInt($force_position) ->addInt($has_order) ->addInt((int)$order) ->addInt((int)$this->getID()); @@ -207,6 +220,86 @@ final class PhabricatorProfileMenuItemConfiguration return $this->getMenuItem()->newPageContent($this); } + public function setIsHeadItem($is_head_item) { + $this->isHeadItem = $is_head_item; + return $this; + } + + public function getIsHeadItem() { + return $this->isHeadItem; + } + + public function setIsTailItem($is_tail_item) { + $this->isTailItem = $is_tail_item; + return $this; + } + + public function getIsTailItem() { + return $this->isTailItem; + } + + public function matchesIdentifier($identifier) { + if (!strlen($identifier)) { + return false; + } + + if (ctype_digit($identifier)) { + if ((int)$this->getID() === (int)$identifier) { + return true; + } + } + + if ((string)$this->getBuiltinKey() === (string)$identifier) { + return true; + } + + return false; + } + + public function getAffectedObjectPHIDs() { + return $this->getMenuItem()->getAffectedObjectPHIDs($this); + } + + public function getProfileMenuTypeDescription() { + $profile_phid = $this->getProfilePHID(); + + $home_phid = id(new PhabricatorHomeApplication())->getPHID(); + if ($profile_phid === $home_phid) { + return pht('Home Menu'); + } + + $favorites_phid = id(new PhabricatorFavoritesApplication())->getPHID(); + if ($profile_phid === $favorites_phid) { + return pht('Favorites Menu'); + } + + switch (phid_get_type($profile_phid)) { + case PhabricatorProjectProjectPHIDType::TYPECONST: + return pht('Project Menu'); + case PhabricatorDashboardPortalPHIDType::TYPECONST: + return pht('Portal Menu'); + } + + return pht('Profile Menu'); + } + + public function newUsageSortVector() { + // Used to sort items in contexts where we're showing the usage of an + // object in menus, like "Dashboard Used By" on Dashboard pages. + + // Sort usage as a custom item after usage as a global item. + if ($this->getCustomPHID()) { + $is_personal = 1; + } else { + $is_personal = 0; + } + + return id(new PhutilSortVector()) + ->addInt($is_personal) + ->addInt($this->getID()); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/search/worker/PhabricatorRebuildIndexesWorker.php b/src/applications/search/worker/PhabricatorRebuildIndexesWorker.php new file mode 100644 index 0000000000..7d5cc64b0b --- /dev/null +++ b/src/applications/search/worker/PhabricatorRebuildIndexesWorker.php @@ -0,0 +1,44 @@ + $query_class, + ), + array( + 'priority' => parent::PRIORITY_INDEX, + )); + } + + protected function doWork() { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $data = $this->getTaskData(); + $query_class = idx($data, 'queryClass'); + + try { + $query = newv($query_class, array()); + } catch (Exception $ex) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to instantiate query class "%s": %s', + $query_class, + $ex->getMessage())); + } + + $query->setViewer($viewer); + + $iterator = new PhabricatorQueryIterator($query); + foreach ($iterator as $object) { + PhabricatorSearchWorker::queueDocumentForIndexing( + $object->getPHID(), + array( + 'force' => true, + )); + } + } + +} diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php index 14f72d313a..3604d368fc 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php @@ -29,4 +29,8 @@ final class PhabricatorSubscriptionsAddSubscribersHeraldAction return pht('Add subscribers: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php index 67ece2cb72..7b55e6b5aa 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php @@ -29,4 +29,8 @@ final class PhabricatorSubscriptionsRemoveSubscribersHeraldAction return pht('Remove subscribers: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 82af45dca8..0986247454 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -566,6 +566,18 @@ abstract class PhabricatorEditEngine return $this->getObjectViewURI($object); } + /** + * @task uri + */ + public function getCreateURI($form_key) { + try { + $create_uri = $this->getEditURI(null, "form/{$form_key}/"); + } catch (Exception $ex) { + $create_uri = null; + } + + return $create_uri; + } /** * @task uri diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index d71728a01f..4b65c1b815 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -657,7 +657,6 @@ abstract class PhabricatorApplicationTransaction case PhabricatorMutedEdgeType::EDGECONST: case PhabricatorMutedByEdgeType::EDGECONST: return true; - break; case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: $record = PhabricatorEdgeChangeRecord::newFromTransaction($this); $add = $record->getAddedPHIDs(); @@ -700,6 +699,10 @@ abstract class PhabricatorApplicationTransaction switch ($edge_type) { case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: + case DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST: + case DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST: + case ManiphestTaskHasCommitEdgeType::EDGECONST: + case DiffusionCommitHasTaskEdgeType::EDGECONST: return true; case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST: // When an object is first created, we hide any corresponding @@ -755,8 +758,11 @@ abstract class PhabricatorApplicationTransaction switch ($edge_type) { case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: + case DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST: + case DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST: + case ManiphestTaskHasCommitEdgeType::EDGECONST: + case DiffusionCommitHasTaskEdgeType::EDGECONST: return true; - break; default: break; } diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php index 6c9f3a50a6..b1919a0ee0 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php @@ -227,14 +227,7 @@ final class PhabricatorEditEngineConfiguration public function getCreateURI() { $form_key = $this->getIdentifier(); $engine = $this->getEngine(); - - try { - $create_uri = $engine->getEditURI(null, "form/{$form_key}/"); - } catch (Exception $ex) { - $create_uri = null; - } - - return $create_uri; + return $engine->getCreateURI($form_key); } public function getIdentifier() { diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php index 8cf7fe5b48..71048e96c5 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php @@ -149,5 +149,19 @@ final class PhabricatorEditEngineConfigurationTransaction return parent::getIcon(); } + protected function newRemarkupChanges() { + $changes = array(); + + $type = $this->getTransactionType(); + switch ($type) { + case self::TYPE_PREAMBLE: + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + break; + } + + return $changes; + } } diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php index e077d7a7ec..29a8d4b7ba 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -604,4 +604,38 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { return mpull($tokens, 'getWireFormat', 'getPHID'); } + final protected function applyFerretConstraints( + PhabricatorCursorPagedPolicyAwareQuery $query, + PhabricatorFerretEngine $engine, + $ferret_function, + $raw_query) { + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + // This is a little hacky and could maybe be cleaner. We're treating + // every search term as though the user had entered "title:dog" instead + // of "dog". + + $alternate_token = PhutilSearchQueryToken::newFromDictionary( + array( + 'quoted' => $raw_token->isQuoted(), + 'value' => $raw_token->getValue(), + 'operator' => PhutilSearchQueryCompiler::OPERATOR_SUBSTRING, + 'function' => $ferret_function, + )); + + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($alternate_token); + $fulltext_tokens[] = $fulltext_token; + } + + $query->withFerretConstraint($engine, $fulltext_tokens); + } + + } diff --git a/src/docs/user/upgrading.diviner b/src/docs/user/upgrading.diviner index d9eca240ec..bf15aa5904 100644 --- a/src/docs/user/upgrading.diviner +++ b/src/docs/user/upgrading.diviner @@ -90,19 +90,7 @@ set -x ROOT=`pwd` # You can hard-code the path here instead. -### UPDATE WORKING COPIES ###################################################### - -cd $ROOT/libphutil -git pull - -cd $ROOT/arcanist -git pull - -cd $ROOT/phabricator -git pull - - -### CYCLE WEB SERVER AND DAEMONS ############################################### +### STOP WEB SERVER AND DAEMONS ############################################### # Stop daemons. $ROOT/phabricator/bin/phd stop @@ -117,6 +105,16 @@ $ROOT/phabricator/bin/phd stop sudo /etc/init.d/httpd stop +### UPDATE WORKING COPIES ###################################################### + +cd $ROOT/libphutil +git pull + +cd $ROOT/arcanist +git pull + +cd $ROOT/phabricator +git pull # Upgrade the database schema. You may want to add the "--force" flag to allow # this script to run noninteractively. diff --git a/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php b/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php index 83d2585e18..976f1800a9 100644 --- a/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php +++ b/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php @@ -8,6 +8,20 @@ final class PhabricatorWorkerTestCase extends PhabricatorTestCase { ); } + protected function willRunOneTest($test) { + parent::willRunOneTest($test); + + // Before we run these test cases, clear the queue. After D20412, we may + // have queued tasks from migrations. + $task_table = new PhabricatorWorkerActiveTask(); + $conn = $task_table->establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE %R', + $task_table); + } + public function testLeaseTask() { $task = $this->scheduleTask(); $this->expectNextLease($task, pht('Leasing should work.')); diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php index 99603da567..85adb4fc29 100644 --- a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php +++ b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php @@ -110,6 +110,19 @@ abstract class PhabricatorLiskDAO extends LiskDAO { $connection = $replica->newApplicationConnection($database); $connection->setReadOnly(true); if ($replica->isReachable($connection)) { + if ($master_exception) { + // If we ended up here as the result of a failover, log the + // exception. This is seriously bad news even if we are able + // to recover from it. + $proxy_exception = new PhutilProxyException( + pht( + 'Failed to connect to master database ("%s"), failing over '. + 'into read-only mode.', + $database), + $master_exception); + phlog($proxy_exception); + } + return $connection; } } diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index cfb1b4abbe..bebb13ad1e 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -32,18 +32,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView return $this->showFooter; } - public function setApplicationMenu($application_menu) { - // NOTE: For now, this can either be a PHUIListView or a - // PHUIApplicationMenuView. - - $this->applicationMenu = $application_menu; - return $this; - } - - public function getApplicationMenu() { - return $this->applicationMenu; - } - public function setApplicationName($application_name) { $this->applicationName = $application_name; return $this; @@ -316,6 +304,12 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView )); } + // If we aren't showing the page chrome, skip rendering DarkConsole and the + // main menu, since they won't be visible on the page. + if (!$this->getShowChrome()) { + return; + } + if ($console) { require_celerity_resource('aphront-dark-console-css'); @@ -345,7 +339,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $menu->setController($this->getController()); } - $application_menu = $this->getApplicationMenu(); + $application_menu = $this->applicationMenu; if ($application_menu) { if ($application_menu instanceof PHUIApplicationMenuView) { $crumbs = $this->getCrumbs(); @@ -359,6 +353,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $menu->setApplicationMenu($application_menu); } + $this->menuContent = $menu->render(); } @@ -865,13 +860,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView public function produceAphrontResponse() { $controller = $this->getController(); - if (!$this->getApplicationMenu()) { - $application_menu = $controller->buildApplicationMenu(); - if ($application_menu) { - $this->setApplicationMenu($application_menu); - } - } - $viewer = $this->getUser(); if ($viewer && $viewer->getPHID()) { $object_phids = $this->pageObjects; @@ -887,6 +875,17 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $response = id(new AphrontAjaxResponse()) ->setContent($content); } else { + // See T13247. Try to find some navigational menu items to create a + // mobile navigation menu from. + $application_menu = $controller->buildApplicationMenu(); + if (!$application_menu) { + $navigation = $this->getNavigation(); + if ($navigation) { + $application_menu = $navigation->getMenu(); + } + } + $this->applicationMenu = $application_menu; + $content = $this->render(); $response = id(new AphrontWebpageResponse()) diff --git a/src/view/phui/PHUICrumbsView.php b/src/view/phui/PHUICrumbsView.php index 00a1f1911e..fb1a0ae9b4 100644 --- a/src/view/phui/PHUICrumbsView.php +++ b/src/view/phui/PHUICrumbsView.php @@ -50,9 +50,15 @@ final class PHUICrumbsView extends AphrontView { $action_view = null; if ($this->actions) { + // TODO: This block of code takes "PHUIListItemView" objects and turns + // them into some weird abomination by reading most of their properties + // out. Some day, this workflow should render the items and CSS should + // resytle them in place without needing a wholly separate set of + // DOM nodes. + $actions = array(); foreach ($this->actions as $action) { - if ($action->getType() == PHUIListItemView::TYPE_DIVIDER) { + if ($action->getType() == PHUIListItemView::TYPE_DIVIDER) { $actions[] = phutil_tag( 'span', array( diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index 8e53024826..5de58b5492 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -35,6 +35,7 @@ final class PHUIListItemView extends AphrontTagView { private $actionIconHref; private $count; private $rel; + private $dropdownMenu; public function setOpenInNewWindow($open_in_new_window) { $this->openInNewWindow = $open_in_new_window; @@ -64,10 +65,19 @@ final class PHUIListItemView extends AphrontTagView { } public function setDropdownMenu(PhabricatorActionListView $actions) { - Javelin::initBehavior('phui-dropdown-menu'); - $this->addSigil('phui-dropdown-menu'); - $this->setMetadata($actions->getDropdownMenuMetadata()); + $this->dropdownMenu = $actions; + + // TODO: "PHUICrumbsView" currently creates a bad copy of list items + // by reading some of their properties. To survive this copy step, we + // need to mutate "$this" immediately or the "Create Object" dropdown + // when multiple create forms exist breaks. + + if (!$this->actionIcon) { + Javelin::initBehavior('phui-dropdown-menu'); + $this->addSigil('phui-dropdown-menu'); + $this->setMetadata($actions->getDropdownMenuMetadata()); + } return $this; } @@ -235,8 +245,22 @@ final class PHUIListItemView extends AphrontTagView { $classes[] = 'phui-list-item-has-action-icon'; } + $sigil = null; + $metadata = null; + if ($this->dropdownMenu) { + $classes[] = 'dropdown'; + if (!$this->actionIcon) { + $classes[] = 'dropdown-with-caret'; + Javelin::initBehavior('phui-dropdown-menu'); + $sigil = 'phui-dropdown-menu'; + $metadata = $this->dropdownMenu->getDropdownMenuMetadata(); + } + } + return array( - 'class' => implode(' ', $classes), + 'class' => $classes, + 'sigil' => $sigil, + 'meta' => $metadata, ); } @@ -339,19 +363,7 @@ final class PHUIListItemView extends AphrontTagView { $classes[] = 'phui-list-item-indented'; } - $action_link = null; - if ($this->actionIcon) { - $action_icon = id(new PHUIIconView()) - ->setIcon($this->actionIcon) - ->addClass('phui-list-item-action-icon'); - $action_link = phutil_tag( - 'a', - array( - 'href' => $this->actionIconHref, - 'class' => 'phui-list-item-action-href', - ), - $action_icon); - } + $action_link = $this->newActionIconView(); $count = null; if ($this->count) { @@ -363,6 +375,12 @@ final class PHUIListItemView extends AphrontTagView { $this->count); } + $caret = null; + if ($this->dropdownMenu && !$this->actionIcon) { + $caret = id(new PHUIIconView()) + ->setIcon('fa-caret-down'); + } + $icons = $this->getIcons(); $list_item = javelin_tag( @@ -382,9 +400,42 @@ final class PHUIListItemView extends AphrontTagView { $this->renderChildren(), $name, $count, + $caret, )); return array($list_item, $action_link); } + private function newActionIconView() { + $action_icon = $this->actionIcon; + $action_href = $this->actionIconHref; + + if ($action_icon === null) { + return null; + } + + $icon_view = id(new PHUIIconView()) + ->setIcon($action_icon) + ->addClass('phui-list-item-action-icon'); + + if ($this->dropdownMenu) { + Javelin::initBehavior('phui-dropdown-menu'); + $sigil = 'phui-dropdown-menu'; + $metadata = $this->dropdownMenu->getDropdownMenuMetadata(); + } else { + $sigil = null; + $metadata = null; + } + + return javelin_tag( + 'a', + array( + 'href' => $action_href, + 'class' => 'phui-list-item-action-href', + 'sigil' => $sigil, + 'meta' => $metadata, + ), + $icon_view); + } + } diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 463f34a2a0..05747c7ce6 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -299,6 +299,8 @@ final class PHUIObjectItemView extends AphrontTagView { if ($this->disabled) { $item_classes[] = 'phui-oi-disabled'; + } else { + $item_classes[] = 'phui-oi-enabled'; } switch ($this->effect) { @@ -330,8 +332,14 @@ final class PHUIObjectItemView extends AphrontTagView { Javelin::initBehavior('phui-selectable-list'); } - if ($this->getGrippable()) { - $item_classes[] = 'phui-oi-grippable'; + $is_grippable = $this->getGrippable(); + if ($is_grippable !== null) { + $item_classes[] = 'phui-oi-has-grip'; + if ($is_grippable) { + $item_classes[] = 'phui-oi-grippable'; + } else { + $item_classes[] = 'phui-oi-ungrippable'; + } } if ($this->getImageURI()) { @@ -580,7 +588,7 @@ final class PHUIObjectItemView extends AphrontTagView { } $grippable = null; - if ($this->getGrippable()) { + if ($this->getGrippable() !== null) { $grippable = phutil_tag( 'div', array( diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php index aa309a4bb6..b77ec5d70e 100644 --- a/src/view/phui/PHUITagView.php +++ b/src/view/phui/PHUITagView.php @@ -24,6 +24,7 @@ final class PHUITagView extends AphrontTagView { const COLOR_BLUEGREY = 'bluegrey'; const COLOR_CHECKERED = 'checkered'; const COLOR_DISABLED = 'disabled'; + const COLOR_PLACEHOLDER = 'placeholder'; const COLOR_OBJECT = 'object'; const COLOR_PERSON = 'person'; diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index fd1a918148..3736ffe841 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -160,6 +160,9 @@ th.aphront-table-view-sortable-selected { vertical-align: top; } +/* Apply this rule to both "" and "" so that the header widths + are correct if the table has no rows. */ +.aphront-table-view th.wide, .aphront-table-view td.wide { white-space: normal; width: 100%; diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index 80adda0502..944b0af125 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -650,6 +650,7 @@ ul.phabricator-core-user-profile-object .phui-oi-content, ul.phabricator-core-user-profile-object .phui-oi-subhead { padding: 0; margin: 0; + background: transparent; } ul.phabricator-core-user-profile-object.phui-oi-list-simple .phui-oi-image { diff --git a/webroot/rsrc/css/application/dashboard/dashboard.css b/webroot/rsrc/css/application/dashboard/dashboard.css index aed87b7673..daa2cfb5c7 100644 --- a/webroot/rsrc/css/application/dashboard/dashboard.css +++ b/webroot/rsrc/css/application/dashboard/dashboard.css @@ -54,7 +54,7 @@ } .grippable .aphront-multi-column-column .dashboard-box.phui-object-box:hover { - box-shadow: {$dropshadow}; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.10); } .grippable .aphront-multi-column-column .dashboard-box.phui-object-box:hover diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 4478e607e9..be9939b51f 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -284,6 +284,7 @@ text-align: center; background: {$page.content}; padding: 16px 0 24px; + border-top: 1px solid {$document.border}; } .device-phone .phame-mega-header { diff --git a/webroot/rsrc/css/application/project/project-triggers.css b/webroot/rsrc/css/application/project/project-triggers.css index 9b3ce8e462..67705df146 100644 --- a/webroot/rsrc/css/application/project/project-triggers.css +++ b/webroot/rsrc/css/application/project/project-triggers.css @@ -27,6 +27,7 @@ .trigger-rules-table td.invalid-cell { padding-left: 12px; + width: 100%; } .trigger-rules-table td.invalid-cell .phui-icon-view { diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css index 2d2163f9e9..3ad8f3a198 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css @@ -70,21 +70,29 @@ } .phui-oi-list-big .phui-oi-linked-container { - border: 1px solid {$lightblueborder}; + border-width: 1px; + border-style: solid; border-radius: 4px; - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.035); } -.phui-oi-list-big .phui-oi-disabled { - border-radius: 4px; - background: {$lightgreybackground}; +.phui-oi-list-big .phui-oi-enabled.phui-oi-linked-container { + border-color: {$lightblueborder}; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05); +} + +.phui-oi-list-big .phui-oi-disabled.phui-oi-linked-container { + border-color: {$greybackground}; +} + +.phui-oi-list-big .phui-oi-disabled .phui-oi-image-icon .phui-icon-view { + color: {$darkgreybackground}; } .device-desktop .phui-oi-linked-container { cursor: pointer; } -.device-desktop .phui-oi-linked-container:hover { +.device-desktop .phui-oi-enabled.phui-oi-linked-container:hover { background-color: {$hoverblue}; border-color: {$blueborder}; } diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 67d0682aa7..85f83802e7 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -132,11 +132,15 @@ ul.phui-oi-list-view { background: url('/rsrc/image/texture/grip.png') center center no-repeat; } +.phui-oi-ungrippable .phui-oi-grip { + opacity: 0.25; +} + .device .phui-oi-grip { display: none; } -.phui-oi-grippable .phui-oi-frame { +.phui-oi-has-grip .phui-oi-frame { padding-left: 16px; } @@ -607,11 +611,6 @@ ul.phui-oi-list-view .phui-oi-selected border-top: none; } -.dashboard-pane .phui-oi-empty .phui-info-view { - border: none; - margin: 0; -} - .device-desktop .aphront-multi-column-fluid .aphront-multi-column-2-up .aphront-multi-column-column-outer.third .phui-oi-col2 { display: none; diff --git a/webroot/rsrc/css/phui/phui-action-list.css b/webroot/rsrc/css/phui/phui-action-list.css index 3df4ff1b78..0163437b60 100644 --- a/webroot/rsrc/css/phui/phui-action-list.css +++ b/webroot/rsrc/css/phui/phui-action-list.css @@ -213,3 +213,32 @@ .phabricator-action-view-item .phui-icon-view { color: {$sky}; } + +.phui-list-navbar .phui-list-item-href { + display: inline-block; +} + +.phui-list-navbar .phui-list-item-disabled .phui-list-item-href { + color: {$lightgreytext}; +} + +.phui-list-navbar .phui-list-item-action-href { + display: inline-block; + padding: 8px 16px; + line-height: 16px; +} + +.phui-list-navbar .phui-list-item-action-href .phui-icon-view { + color: {$darkgreytext}; +} + +.device-desktop + .phui-list-navbar .phui-list-item-action-href:hover { + background-color: rgba({$alphablue}, 0.07); + color: {$sky}; +} + +.phui-list-navbar .dropdown-with-caret .phui-list-item-href + .phui-icon-view { + margin-left: 12px; +} diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index 84dc11601f..cdd790f574 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -56,7 +56,7 @@ .phui-document-container { background-color: {$page.content}; position: relative; - border-bottom: 1px solid #dedee1; + border-bottom: 1px solid {$document.border}; } .phui-document-view-pro-box, diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index b4fafc6e59..0d15d08e4b 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -74,8 +74,8 @@ h1.phui-info-view-head { } .phui-info-view-list { - margin: 0; - list-style: none; + margin-left: 30px; + list-style: disc; line-height: 1.6em; } diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css index 5fab89bcd8..6932d9f29b 100644 --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -110,10 +110,6 @@ border-right: 1px solid {$thinblueborder}; } -.phui-list-view.phui-list-navbar > li > * { - display: block; -} - .phui-list-navbar .phui-list-item-href { color: {$bluetext}; padding: 8px 16px; @@ -265,7 +261,8 @@ /* - Action Icon ----------------------------------------------------------- */ -.phui-list-item-has-action-icon .phui-list-item-action-href { +.phabricator-nav-local .phui-list-item-has-action-icon + .phui-list-item-action-href { position: absolute; width: 28px; top: 0; @@ -277,26 +274,30 @@ display: none; } -.phui-list-item-has-action-icon.phui-list-item-selected .phui-list-item-href { +.phabricator-nav-local .phui-list-item-has-action-icon.phui-list-item-selected + .phui-list-item-href { padding-right: 32px; } -.phui-list-item-has-action-icon.phui-list-item-selected +.phabricator-nav-local .phui-list-item-has-action-icon.phui-list-item-selected .phui-list-item-action-href { display: block; } -.phui-list-item-has-action-icon .phui-list-item-action-href:hover { +.phabricator-nav-local .phui-list-item-has-action-icon + .phui-list-item-action-href:hover { background-color: rgba({$alphablack},.05); } -.phui-list-item-has-action-icon .phui-list-item-action-icon { +.phabricator-nav-local .phui-list-item-has-action-icon + .phui-list-item-action-icon { opacity: 0.5; } -.phui-list-item-has-action-icon .phui-list-item-action-href:hover +.phabricator-nav-local .phui-list-item-has-action-icon + .phui-list-item-action-href:hover .phui-list-item-action-icon { - opacity: 1; + opacity: 1; } /* - Item Counts ----------------------------------------------------------- */ diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index 57529645a7..d0b2afce1d 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -441,6 +441,12 @@ a.phui-tag-view:hover.phui-tag-disabled .phui-tag-core { border-color: {$sh-disabledborder}; } +.phui-tag-placeholder .phui-tag-core { + border-color: transparent; + background-color: {$sh-disabledbackground}; + opacity: 0.5; +} + /* - Outline Tags -------------------------------------------------------------- Basic Tag with a bold border and white background diff --git a/webroot/rsrc/externals/javelin/core/util.js b/webroot/rsrc/externals/javelin/core/util.js index c0353c5f39..d8495ba4e5 100644 --- a/webroot/rsrc/externals/javelin/core/util.js +++ b/webroot/rsrc/externals/javelin/core/util.js @@ -295,9 +295,69 @@ if (!window.console || !window.console.log) { * @return void */ JX.log = function(message) { + // "JX.log()" accepts "Error" in addition to "string". Only try to + // treat the argument as a "sprintf()" pattern if it's a string. + if (typeof message === 'string') { + message = JX.sprintf.apply(null, arguments); + } window.console.log(message); }; +JX.sprintf = function(pattern) { + var argv = Array.prototype.slice.call(arguments); + argv.reverse(); + + // Pop off the pattern argument. + argv.pop(); + + var len = pattern.length; + var output = ''; + for (var ii = 0; ii < len; ii++) { + var c = pattern.charAt(ii); + + if (c !== '%') { + output += c; + continue; + } + + ii++; + + var next = pattern.charAt(ii); + if (next === '%') { + // This is "%%" (that is, an escaped "%" symbol), so just add a literal + // "%" to the result. + output += '%'; + continue; + } + + if (next === 's') { + if (!argv.length) { + throw new Error( + 'Too few arguments to "JX.sprintf(...)" for pattern: ' + pattern); + } + + output += '' + argv.pop(); + + continue; + } + + if (next === '') { + throw new Error( + 'Pattern passed to "JX.sprintf(...)" ends with "%": ' + pattern); + } + + throw new Error( + 'Unknown conversion "%' + c + '" passed to "JX.sprintf(...)" in ' + + 'pattern: ' + pattern); + } + + if (argv.length) { + throw new Error( + 'Too many arguments to "JX.sprintf()" for pattern: ' + pattern); + } + + return output; +}; if (__DEV__) { window.alert = (function(native_alert) { diff --git a/webroot/rsrc/externals/javelin/lib/behavior.js b/webroot/rsrc/externals/javelin/lib/behavior.js index ebd63bb1ac..a15f763fe3 100644 --- a/webroot/rsrc/externals/javelin/lib/behavior.js +++ b/webroot/rsrc/externals/javelin/lib/behavior.js @@ -91,7 +91,14 @@ JX.initBehaviors = function(map) { configs = [null]; } for (var ii = 0; ii < configs.length; ii++) { - JX.behavior._behaviors[name](configs[ii], JX.behavior._statics[name]); + try { + JX.behavior._behaviors[name](configs[ii], JX.behavior._statics[name]); + } catch (behavior_exception) { + JX.log( + 'JX.initBehaviors(...): behavior "%s" raised an error during setup.', + name); + JX.log(behavior_exception); + } } JX.behavior._initialized[name] = true; } diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js index 0ab1796e5b..126f2d868e 100644 --- a/webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js +++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js @@ -12,7 +12,9 @@ JX.behavior('dashboard-async-panel', function(config) { var data = { parentPanelPHIDs: config.parentPanelPHIDs.join(','), headerMode: config.headerMode, - dashboardID: config.dashboardID + contextPHID: config.contextPHID, + movable: config.movable, + panelKey: config.panelKey }; new JX.Workflow(config.uri) diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js index d8d08eca5f..c01ab6cd04 100644 --- a/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js +++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js @@ -17,7 +17,7 @@ JX.behavior('dashboard-move-panels', function(config) { } function markcolempty(col, toggle) { - JX.DOM.alterClass(col, 'dashboard-column-empty', toggle); + JX.DOM.alterClass(col.parentNode, 'dashboard-column-empty', toggle); } function onupdate(col) { @@ -33,40 +33,16 @@ JX.behavior('dashboard-move-panels', function(config) { list.lock(); JX.DOM.alterClass(item, 'drag-sending', true); - var item_phid = JX.Stratcom.getData(item).objectPHID; var data = { - objectPHID: item_phid, - columnID: JX.Stratcom.getData(list.getRootNode()).columnID + panelKey: JX.Stratcom.getData(item).panelKey, + columnKey: JX.Stratcom.getData(list.getRootNode()).columnKey }; - var after_phid = null; - var items = finditems(list.getRootNode()); if (after) { - after_phid = JX.Stratcom.getData(after).objectPHID; - data.afterPHID = after_phid; - } - var ii; - var ii_item; - var ii_item_phid; - var ii_prev_item_phid = null; - var before_phid = null; - for (ii = 0; ii < items.length; ii++) { - ii_item = items[ii]; - ii_item_phid = JX.Stratcom.getData(ii_item).objectPHID; - if (ii_item_phid == item_phid) { - // skip the item we just dropped - continue; + var after_data = JX.Stratcom.getData(after); + if (after_data.panelKey) { + data.afterKey = after_data.panelKey; } - // note this handles when there is no after phid - we are at the top of - // the list - quite nicely - if (ii_prev_item_phid == after_phid) { - before_phid = ii_item_phid; - break; - } - ii_prev_item_phid = ii_item_phid; - } - if (before_phid) { - data.beforePHID = before_phid; } var workflow = new JX.Workflow(config.moveURI, data) @@ -77,23 +53,24 @@ JX.behavior('dashboard-move-panels', function(config) { workflow.start(); } - var lists = []; - var ii; - var cols = JX.DOM.scry(JX.$(config.dashboardID), 'div', 'dashboard-column'); - var col = null; + var dashboard_node = JX.$(config.dashboardNodeID); + var lists = []; + var cols = JX.DOM.scry(dashboard_node, 'div', 'dashboard-column'); + + var ii; for (ii = 0; ii < cols.length; ii++) { - col = cols[ii]; + var col = cols[ii]; var list = new JX.DraggableList(itemSigil, col) .setFindItemsHandler(JX.bind(null, finditems, col)) .setCanDragX(true); list.listen('didSend', JX.bind(list, onupdate, col)); list.listen('didReceive', JX.bind(list, onupdate, col)); - list.listen('didDrop', JX.bind(null, ondrop, list)); lists.push(list); + markcolempty(col, finditems(col).length === 0); } diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js index b9c70d7df9..5d79c35ce3 100644 --- a/webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js +++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js @@ -8,28 +8,42 @@ JX.behavior('dashboard-tab-panel', function() { JX.Stratcom.listen('click', 'dashboard-tab-panel-tab', function(e) { + // On dashboard panels in edit mode, the user may click the dropdown caret + // within a tab to open the context menu. If they do, this click should + // just open the menu, not select the tab. For now, pass the event here + // to let the menu handler act on it. + if (JX.Stratcom.pass(e)) { + return; + } + e.kill(); - var ii; - var idx = e.getNodeData('dashboard-tab-panel-tab').idx; + var selected_key = e.getNodeData('dashboard-tab-panel-tab').panelKey; var root = e.getNode('dashboard-tab-panel-container'); var data = JX.Stratcom.getData(root); + + var ii; // Give the tab the user clicked a selected style, and remove it from // the other tabs. var tabs = JX.DOM.scry(root, 'li', 'dashboard-tab-panel-tab'); for (ii = 0; ii < tabs.length; ii++) { - JX.DOM.alterClass(tabs[ii], 'phui-list-item-selected', (ii == idx)); + var tab = tabs[ii]; + var tab_data = JX.Stratcom.getData(tab); + var is_selected = (tab_data.panelKey === selected_key); + JX.DOM.alterClass(tabs[ii], 'phui-list-item-selected', is_selected); } // Switch the visible content to correspond to whatever the user clicked. for (ii = 0; ii < data.panels.length; ii++) { - var panel = JX.$(data.panels[ii]); - if (ii == idx) { - JX.DOM.show(panel); + var panel = data.panels[ii]; + var node = JX.$(panel.panelContentID); + + if (panel.panelKey == selected_key) { + JX.DOM.show(node); } else { - JX.DOM.hide(panel); + JX.DOM.hide(node); } } diff --git a/webroot/rsrc/js/application/trigger/TriggerRule.js b/webroot/rsrc/js/application/trigger/TriggerRule.js index cf117e24d9..86f9ecb966 100644 --- a/webroot/rsrc/js/application/trigger/TriggerRule.js +++ b/webroot/rsrc/js/application/trigger/TriggerRule.js @@ -98,7 +98,24 @@ JX.install('TriggerRule', { }, _onTypeChange: function(control) { - this.setType(control.value); + var new_type = control.value; + + this.setType(new_type); + + // Before we build a new control, change the rule value to be appropriate + // for the new rule type. + + // TODO: Currently, we set the rule value to the default value for the + // type. This works most of the time, but if the user selects type "A", + // makes a change to the value, selects type "B", then changes back to + // type "A", it would be better to retain their edit. This is currently + // difficult because of tokenizers: if you save their value, you get a + // list of PHIDs which do not restore cleanly into tokens later. + + var editor = this.getEditor(); + var type = editor.getType(new_type); + this.setValue(type.getDefaultValue()); + this._rebuildValueControl(); }, diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index deb9f9d100..2eaa9bafe1 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -555,6 +555,13 @@ JX.install('PHUIXAutocomplete', { if (prefix) { var pattern = new RegExp(prefix); if (!trim.match(pattern)) { + // If the prefix pattern can not match the text, deactivate. (This + // check might need to be more careful if we have a more varied + // set of prefixes in the future, but for now they're all a single + // prefix character.) + if (trim.length) { + this._deactivate(); + } return; } trim = trim.replace(pattern, ''); diff --git a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js index d9d86bf595..b49cb5b9f0 100644 --- a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js +++ b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js @@ -198,7 +198,35 @@ JX.install('PHUIXDropdownMenu', { var v = JX.$V(this._node); var d = JX.Vector.getDim(this._node); - switch (this.getAlign()) { + var alignments = ['right', 'left']; + var disallow = {}; + var margin = 8; + + // If "right" alignment would leave us with the dropdown near or off the + // left side of the screen, disallow it. + var x_min = ((v.x + d.x) - m.x); + if (x_min < margin) { + disallow.right = true; + } + + var align = this.getAlign(); + + // If the position disallows the configured alignment, try the next + // best alignment instead. + + // If no alignment is allowed, we'll stick with the original alignment + // and accept that it isn't going to render very nicely. This can happen + // if the browser window is very, very small. + if (align in disallow) { + for (var ii = 0; ii < alignments.length; ii++) { + if (!(alignments[ii] in disallow)) { + align = alignments[ii]; + break; + } + } + } + + switch (align) { case 'right': v = v.add(d) .add(JX.$V(-m.x, 0));