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));