mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-10 06:41:04 +01:00
(stable) Promote 2016 Week 6
This commit is contained in:
commit
b3dd0fd860
121 changed files with 4039 additions and 1388 deletions
BIN
resources/builtin/image-200x200.png
Normal file
BIN
resources/builtin/image-200x200.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
resources/builtin/projects/fa-map-marker.png
Normal file
BIN
resources/builtin/projects/fa-map-marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3 KiB |
|
@ -7,8 +7,8 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => '5e4df064',
|
||||
'core.pkg.js' => 'a79eed25',
|
||||
'core.pkg.css' => 'e33b14a4',
|
||||
'core.pkg.js' => 'ef5e33db',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '2de124c9',
|
||||
'differential.pkg.js' => '5c2ba922',
|
||||
|
@ -36,7 +36,7 @@ return array(
|
|||
'rsrc/css/application/base/notification-menu.css' => 'f31c0bde',
|
||||
'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601',
|
||||
'rsrc/css/application/base/phui-theme.css' => 'ab7b848c',
|
||||
'rsrc/css/application/base/standard-page-view.css' => '7b0d68d8',
|
||||
'rsrc/css/application/base/standard-page-view.css' => 'c4467133',
|
||||
'rsrc/css/application/chatlog/chatlog.css' => 'd295b020',
|
||||
'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
|
||||
'rsrc/css/application/config/config-options.css' => '0ede4c9b',
|
||||
|
@ -81,7 +81,7 @@ return array(
|
|||
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
|
||||
'rsrc/css/application/paste/paste.css' => 'a5157c48',
|
||||
'rsrc/css/application/people/people-profile.css' => '2473d929',
|
||||
'rsrc/css/application/phame/phame.css' => '6d5b3682',
|
||||
'rsrc/css/application/phame/phame.css' => '1dbbacf9',
|
||||
'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee',
|
||||
'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
|
||||
'rsrc/css/application/pholio/pholio.css' => '95174bdd',
|
||||
|
@ -93,7 +93,8 @@ return array(
|
|||
'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43',
|
||||
'rsrc/css/application/policy/policy.css' => '957ea14c',
|
||||
'rsrc/css/application/ponder/ponder-view.css' => '7b0df4da',
|
||||
'rsrc/css/application/project/project-view.css' => '22f7ed0e',
|
||||
'rsrc/css/application/project/project-card-view.css' => '9c3631e5',
|
||||
'rsrc/css/application/project/project-view.css' => '4693497c',
|
||||
'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733',
|
||||
'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5',
|
||||
'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd',
|
||||
|
@ -105,14 +106,13 @@ return array(
|
|||
'rsrc/css/core/core.css' => '5b3563c8',
|
||||
'rsrc/css/core/remarkup.css' => 'e1c8b32f',
|
||||
'rsrc/css/core/syntax.css' => '9fd11da8',
|
||||
'rsrc/css/core/z-index.css' => 'a36a45da',
|
||||
'rsrc/css/core/z-index.css' => '5c7025bf',
|
||||
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
|
||||
'rsrc/css/font/font-aleo.css' => '8bdb2835',
|
||||
'rsrc/css/font/font-awesome.css' => 'c43323c5',
|
||||
'rsrc/css/font/font-lato.css' => 'c7ccd872',
|
||||
'rsrc/css/font/phui-font-icon-base.css' => 'ecbbb4c2',
|
||||
'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82',
|
||||
'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52',
|
||||
'rsrc/css/layout/phabricator-side-menu-view.css' => '3a3d9f41',
|
||||
'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983',
|
||||
'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93',
|
||||
|
@ -125,15 +125,17 @@ return array(
|
|||
'rsrc/css/phui/phui-big-info-view.css' => 'bd903741',
|
||||
'rsrc/css/phui/phui-box.css' => '6e8ac7fd',
|
||||
'rsrc/css/phui/phui-button.css' => 'd6ac72db',
|
||||
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
|
||||
'rsrc/css/phui/phui-crumbs-view.css' => '414406b5',
|
||||
'rsrc/css/phui/phui-document-pro.css' => '8799acf7',
|
||||
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
|
||||
'rsrc/css/phui/phui-document.css' => '9c71d2bf',
|
||||
'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23',
|
||||
'rsrc/css/phui/phui-feed-story.css' => '04aec08f',
|
||||
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
|
||||
'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e',
|
||||
'rsrc/css/phui/phui-form.css' => '0b98e572',
|
||||
'rsrc/css/phui/phui-header-view.css' => 'd53cc835',
|
||||
'rsrc/css/phui/phui-hovercard.css' => 'de1a2119',
|
||||
'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad',
|
||||
'rsrc/css/phui/phui-icon.css' => '3f33ab57',
|
||||
'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8',
|
||||
|
@ -141,7 +143,7 @@ return array(
|
|||
'rsrc/css/phui/phui-info-view.css' => '6d7c3509',
|
||||
'rsrc/css/phui/phui-list.css' => '9da2aa00',
|
||||
'rsrc/css/phui/phui-object-box.css' => '407eaf5a',
|
||||
'rsrc/css/phui/phui-object-item-list-view.css' => '0d484a97',
|
||||
'rsrc/css/phui/phui-object-item-list-view.css' => 'fe594a65',
|
||||
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
|
||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
||||
'rsrc/css/phui/phui-profile-menu.css' => 'ab4fcf5f',
|
||||
|
@ -149,15 +151,16 @@ return array(
|
|||
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
|
||||
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
||||
'rsrc/css/phui/phui-status.css' => '888cedb8',
|
||||
'rsrc/css/phui/phui-tag-view.css' => 'e60e227b',
|
||||
'rsrc/css/phui/phui-tag-view.css' => '9d5d4400',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => '2efceff8',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => 'c75bfc5b',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => 'b07a5524',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => 'ffb55371',
|
||||
'rsrc/css/phui/workboards/phui-workpanel.css' => 'e9339dc3',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => 'b4322ca7',
|
||||
'rsrc/css/phui/workboards/phui-workpanel.css' => 'e1bd8d04',
|
||||
'rsrc/css/sprite-login.css' => '60e8560e',
|
||||
'rsrc/css/sprite-menu.css' => '9dd65b92',
|
||||
'rsrc/css/sprite-tokens.css' => '4f399012',
|
||||
'rsrc/externals/d3/d3.min.js' => 'a11a5ff2',
|
||||
'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7',
|
||||
'rsrc/externals/font/aleo/aleo-bold.svg' => '45899c8e',
|
||||
'rsrc/externals/font/aleo/aleo-bold.ttf' => '4b08bef0',
|
||||
|
@ -252,9 +255,6 @@ return array(
|
|||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '1bc11c4a',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa',
|
||||
'rsrc/externals/raphael/g.raphael.js' => '40dde778',
|
||||
'rsrc/externals/raphael/g.raphael.line.js' => '40da039e',
|
||||
'rsrc/externals/raphael/raphael.js' => '51ee6b43',
|
||||
'rsrc/favicons/apple-touch-icon-120x120.png' => '43742962',
|
||||
'rsrc/favicons/apple-touch-icon-152x152.png' => '669eaec3',
|
||||
'rsrc/favicons/apple-touch-icon-76x76.png' => 'ecdef672',
|
||||
|
@ -369,7 +369,7 @@ return array(
|
|||
'rsrc/js/application/countdown/timer.js' => 'e4cc26b3',
|
||||
'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => 'edf8a145',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '019f36c4',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63',
|
||||
'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756',
|
||||
|
@ -401,7 +401,7 @@ return array(
|
|||
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
|
||||
'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7',
|
||||
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '7b98d7c5',
|
||||
'rsrc/js/application/maniphest/behavior-line-chart.js' => '88f0c5b3',
|
||||
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876',
|
||||
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2',
|
||||
'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763',
|
||||
'rsrc/js/application/owners/OwnersPathEditor.js' => 'aa1733d0',
|
||||
|
@ -412,9 +412,9 @@ return array(
|
|||
'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '3f5d6dbf',
|
||||
'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'fc91ab6c',
|
||||
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
|
||||
'rsrc/js/application/policy/behavior-policy-control.js' => 'ae45872f',
|
||||
'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5',
|
||||
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
|
||||
'rsrc/js/application/projects/behavior-project-boards.js' => 'ba4fa35c',
|
||||
'rsrc/js/application/projects/behavior-project-boards.js' => '48470f95',
|
||||
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
||||
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
|
||||
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
|
||||
|
@ -447,9 +447,9 @@ return array(
|
|||
'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5',
|
||||
'rsrc/js/core/Busy.js' => '59a7976a',
|
||||
'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac',
|
||||
'rsrc/js/core/DraggableList.js' => 'a16ec1c6',
|
||||
'rsrc/js/core/DraggableList.js' => '8905523d',
|
||||
'rsrc/js/core/FileUpload.js' => '477359c8',
|
||||
'rsrc/js/core/Hovercard.js' => 'c6f720ff',
|
||||
'rsrc/js/core/Hovercard.js' => '1bd28176',
|
||||
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
|
||||
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
|
||||
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
|
||||
|
@ -458,7 +458,7 @@ return array(
|
|||
'rsrc/js/core/ShapedRequest.js' => '7cbe244b',
|
||||
'rsrc/js/core/TextAreaUtils.js' => '9e54692d',
|
||||
'rsrc/js/core/Title.js' => 'df5e11d2',
|
||||
'rsrc/js/core/ToolTip.js' => '1d298e3a',
|
||||
'rsrc/js/core/ToolTip.js' => '6323f942',
|
||||
'rsrc/js/core/behavior-active-nav.js' => 'e379b58e',
|
||||
'rsrc/js/core/behavior-audio-source.js' => '59b251eb',
|
||||
'rsrc/js/core/behavior-autofocus.js' => '7319e029',
|
||||
|
@ -475,7 +475,7 @@ return array(
|
|||
'rsrc/js/core/behavior-global-drag-and-drop.js' => 'c8e57404',
|
||||
'rsrc/js/core/behavior-high-security-warning.js' => 'a464fe03',
|
||||
'rsrc/js/core/behavior-history-install.js' => '7ee2b591',
|
||||
'rsrc/js/core/behavior-hovercard.js' => '66dd6e9e',
|
||||
'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64',
|
||||
'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0',
|
||||
'rsrc/js/core/behavior-keyboard-shortcuts.js' => 'd75709e6',
|
||||
'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7',
|
||||
|
@ -490,7 +490,7 @@ return array(
|
|||
'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e',
|
||||
'rsrc/js/core/behavior-reveal-content.js' => '60821bc7',
|
||||
'rsrc/js/core/behavior-scrollbar.js' => '834a1173',
|
||||
'rsrc/js/core/behavior-search-typeahead.js' => '0b7a4f6e',
|
||||
'rsrc/js/core/behavior-search-typeahead.js' => '06c32383',
|
||||
'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6',
|
||||
'rsrc/js/core/behavior-time-typeahead.js' => 'f80d6bf0',
|
||||
'rsrc/js/core/behavior-toggle-class.js' => '5d7c9f33',
|
||||
|
@ -535,6 +535,7 @@ return array(
|
|||
'conpherence-transaction-css' => '85d0974c',
|
||||
'conpherence-update-css' => 'faf6be09',
|
||||
'conpherence-widget-pane-css' => '775eaaba',
|
||||
'd3' => 'a11a5ff2',
|
||||
'differential-changeset-view-css' => 'b6b0d1bb',
|
||||
'differential-core-view-css' => '7ac3cabc',
|
||||
'differential-inline-comment-editor' => '64a5550f',
|
||||
|
@ -579,7 +580,7 @@ return array(
|
|||
'javelin-behavior-countdown-timer' => 'e4cc26b3',
|
||||
'javelin-behavior-dark-console' => 'f411b6ae',
|
||||
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
|
||||
'javelin-behavior-dashboard-move-panels' => '82439934',
|
||||
'javelin-behavior-dashboard-move-panels' => '019f36c4',
|
||||
'javelin-behavior-dashboard-query-panel-select' => '453c5375',
|
||||
'javelin-behavior-dashboard-tab-panel' => 'd4eecc63',
|
||||
'javelin-behavior-day-view' => '5c46cff2',
|
||||
|
@ -615,7 +616,7 @@ return array(
|
|||
'javelin-behavior-icon-composer' => '8499b6ab',
|
||||
'javelin-behavior-launch-icon-composer' => '48086888',
|
||||
'javelin-behavior-lightbox-attachments' => 'f8ba29d7',
|
||||
'javelin-behavior-line-chart' => '88f0c5b3',
|
||||
'javelin-behavior-line-chart' => 'e4232876',
|
||||
'javelin-behavior-load-blame' => '42126667',
|
||||
'javelin-behavior-maniphest-batch-editor' => '782ab6e7',
|
||||
'javelin-behavior-maniphest-batch-selector' => '7b98d7c5',
|
||||
|
@ -630,7 +631,6 @@ return array(
|
|||
'javelin-behavior-phabricator-file-tree' => '88236f00',
|
||||
'javelin-behavior-phabricator-gesture' => '3ab51e2c',
|
||||
'javelin-behavior-phabricator-gesture-example' => '558829c2',
|
||||
'javelin-behavior-phabricator-hovercards' => '66dd6e9e',
|
||||
'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0',
|
||||
'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6',
|
||||
'javelin-behavior-phabricator-line-linker' => '1499a8cb',
|
||||
|
@ -640,7 +640,7 @@ return array(
|
|||
'javelin-behavior-phabricator-oncopy' => '2926fff2',
|
||||
'javelin-behavior-phabricator-remarkup-assist' => '340c8eff',
|
||||
'javelin-behavior-phabricator-reveal-content' => '60821bc7',
|
||||
'javelin-behavior-phabricator-search-typeahead' => '0b7a4f6e',
|
||||
'javelin-behavior-phabricator-search-typeahead' => '06c32383',
|
||||
'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6',
|
||||
'javelin-behavior-phabricator-tooltips' => '3ee3408b',
|
||||
'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6',
|
||||
|
@ -649,11 +649,12 @@ return array(
|
|||
'javelin-behavior-pholio-mock-edit' => '246dc085',
|
||||
'javelin-behavior-pholio-mock-view' => 'fbe497e7',
|
||||
'javelin-behavior-phui-dropdown-menu' => '54733475',
|
||||
'javelin-behavior-phui-hovercards' => 'bcaccd64',
|
||||
'javelin-behavior-phui-object-box-tabs' => '2bfa2836',
|
||||
'javelin-behavior-phui-profile-menu' => '12884df9',
|
||||
'javelin-behavior-policy-control' => 'ae45872f',
|
||||
'javelin-behavior-policy-control' => 'd0c516d5',
|
||||
'javelin-behavior-policy-rule-editor' => '5e9f347c',
|
||||
'javelin-behavior-project-boards' => 'ba4fa35c',
|
||||
'javelin-behavior-project-boards' => '48470f95',
|
||||
'javelin-behavior-project-create' => '065227cc',
|
||||
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
|
||||
'javelin-behavior-recurring-edit' => '5f1c4d5f',
|
||||
|
@ -741,14 +742,12 @@ return array(
|
|||
'phabricator-countdown-css' => 'e7544472',
|
||||
'phabricator-dashboard-css' => 'eb458607',
|
||||
'phabricator-drag-and-drop-file-upload' => 'ad10aeac',
|
||||
'phabricator-draggable-list' => 'a16ec1c6',
|
||||
'phabricator-draggable-list' => '8905523d',
|
||||
'phabricator-fatal-config-template-css' => '8e6c6fcd',
|
||||
'phabricator-feed-css' => 'ecd4ec57',
|
||||
'phabricator-file-upload' => '477359c8',
|
||||
'phabricator-filetree-view-css' => 'fccf9f82',
|
||||
'phabricator-flag-css' => '5337623f',
|
||||
'phabricator-hovercard' => 'c6f720ff',
|
||||
'phabricator-hovercard-view-css' => '1239cd52',
|
||||
'phabricator-keyboard-shortcut' => '1ae869f2',
|
||||
'phabricator-keyboard-shortcut-manager' => 'c1700f6f',
|
||||
'phabricator-main-menu-view' => 'd00a795a',
|
||||
|
@ -765,10 +764,10 @@ return array(
|
|||
'phabricator-side-menu-view-css' => '3a3d9f41',
|
||||
'phabricator-slowvote-css' => 'da0afb1b',
|
||||
'phabricator-source-code-view-css' => 'cbeef983',
|
||||
'phabricator-standard-page-view' => '7b0d68d8',
|
||||
'phabricator-standard-page-view' => 'c4467133',
|
||||
'phabricator-textareautils' => '9e54692d',
|
||||
'phabricator-title' => 'df5e11d2',
|
||||
'phabricator-tooltip' => '1d298e3a',
|
||||
'phabricator-tooltip' => '6323f942',
|
||||
'phabricator-ui-example-css' => '528b19de',
|
||||
'phabricator-uiexample-javelin-view' => 'd4a14807',
|
||||
'phabricator-uiexample-reactor-button' => 'd19198c8',
|
||||
|
@ -780,8 +779,8 @@ return array(
|
|||
'phabricator-uiexample-reactor-select' => 'a155550f',
|
||||
'phabricator-uiexample-reactor-sendclass' => '1def2711',
|
||||
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
|
||||
'phabricator-zindex-css' => 'a36a45da',
|
||||
'phame-css' => '6d5b3682',
|
||||
'phabricator-zindex-css' => '5c7025bf',
|
||||
'phame-css' => '1dbbacf9',
|
||||
'pholio-css' => '95174bdd',
|
||||
'pholio-edit-css' => '3ad9d1ee',
|
||||
'pholio-inline-comments-css' => '8e545e49',
|
||||
|
@ -799,16 +798,19 @@ return array(
|
|||
'phui-calendar-day-css' => 'd1cf6f93',
|
||||
'phui-calendar-list-css' => 'c1c7f338',
|
||||
'phui-calendar-month-css' => '476be7e0',
|
||||
'phui-chart-css' => '6bf6f78e',
|
||||
'phui-crumbs-view-css' => '414406b5',
|
||||
'phui-document-summary-view-css' => '9ca48bdf',
|
||||
'phui-document-view-css' => '9c71d2bf',
|
||||
'phui-document-view-pro-css' => '8799acf7',
|
||||
'phui-feed-story-css' => 'b7b26d23',
|
||||
'phui-feed-story-css' => '04aec08f',
|
||||
'phui-font-icon-base-css' => 'ecbbb4c2',
|
||||
'phui-fontkit-css' => '9cda225e',
|
||||
'phui-form-css' => '0b98e572',
|
||||
'phui-form-view-css' => '4a1a0f5e',
|
||||
'phui-header-view-css' => 'd53cc835',
|
||||
'phui-hovercard' => '1bd28176',
|
||||
'phui-hovercard-view-css' => 'de1a2119',
|
||||
'phui-icon-set-selector-css' => '1ab67aad',
|
||||
'phui-icon-view-css' => '3f33ab57',
|
||||
'phui-image-mask-css' => '5a8b09c8',
|
||||
|
@ -817,7 +819,7 @@ return array(
|
|||
'phui-inline-comment-view-css' => '0fdb3667',
|
||||
'phui-list-view-css' => '9da2aa00',
|
||||
'phui-object-box-css' => '407eaf5a',
|
||||
'phui-object-item-list-view-css' => '0d484a97',
|
||||
'phui-object-item-list-view-css' => 'fe594a65',
|
||||
'phui-pager-css' => 'bea33d23',
|
||||
'phui-pinboard-view-css' => '2495140e',
|
||||
'phui-profile-menu-css' => 'ab4fcf5f',
|
||||
|
@ -825,13 +827,13 @@ return array(
|
|||
'phui-remarkup-preview-css' => '1a8f2591',
|
||||
'phui-spacing-css' => '042804d6',
|
||||
'phui-status-list-view-css' => '888cedb8',
|
||||
'phui-tag-view-css' => 'e60e227b',
|
||||
'phui-tag-view-css' => '9d5d4400',
|
||||
'phui-theme-css' => 'ab7b848c',
|
||||
'phui-timeline-view-css' => '2efceff8',
|
||||
'phui-two-column-view-css' => 'c75bfc5b',
|
||||
'phui-workboard-view-css' => 'b07a5524',
|
||||
'phui-workcard-view-css' => 'ffb55371',
|
||||
'phui-workpanel-view-css' => 'e9339dc3',
|
||||
'phui-workcard-view-css' => 'b4322ca7',
|
||||
'phui-workpanel-view-css' => 'e1bd8d04',
|
||||
'phuix-action-list-view' => 'b5c256b8',
|
||||
'phuix-action-view' => '8cf6d262',
|
||||
'phuix-autocomplete' => '9196fb06',
|
||||
|
@ -842,10 +844,8 @@ return array(
|
|||
'policy-edit-css' => '815c66f7',
|
||||
'policy-transaction-detail-css' => '82100a43',
|
||||
'ponder-view-css' => '7b0df4da',
|
||||
'project-view-css' => '22f7ed0e',
|
||||
'raphael-core' => '51ee6b43',
|
||||
'raphael-g' => '40dde778',
|
||||
'raphael-g-line' => '40da039e',
|
||||
'project-card-view-css' => '9c3631e5',
|
||||
'project-view-css' => '4693497c',
|
||||
'releeph-core' => '9b3c5733',
|
||||
'releeph-preview-branch' => 'b7a6f4a5',
|
||||
'releeph-request-differential-create-dialog' => '8d8b92cd',
|
||||
|
@ -877,6 +877,14 @@ return array(
|
|||
'javelin-behavior-device',
|
||||
'javelin-vector',
|
||||
),
|
||||
'019f36c4' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'031cee25' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-request',
|
||||
|
@ -901,6 +909,17 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
),
|
||||
'06c32383' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-typeahead',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'phabricator-prefab',
|
||||
'phuix-icon-view',
|
||||
),
|
||||
'087e919c' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -914,16 +933,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-router',
|
||||
),
|
||||
'0b7a4f6e' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-typeahead',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'0f764c35' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -964,11 +973,12 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-typeahead-normalizer',
|
||||
),
|
||||
'1d298e3a' => array(
|
||||
'1bd28176' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
'javelin-request',
|
||||
'javelin-uri',
|
||||
),
|
||||
'1d45c74d' => array(
|
||||
'javelin-behavior',
|
||||
|
@ -1144,6 +1154,15 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-workflow',
|
||||
),
|
||||
'48470f95' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'49b73b36' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1292,6 +1311,12 @@ return array(
|
|||
'javelin-install',
|
||||
'javelin-util',
|
||||
),
|
||||
'6323f942' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'635de1ec' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1306,13 +1331,6 @@ return array(
|
|||
'javelin-request',
|
||||
'javelin-workflow',
|
||||
),
|
||||
'66dd6e9e' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-behavior-device',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
'phabricator-hovercard',
|
||||
),
|
||||
'6882e80a' => array(
|
||||
'javelin-dom',
|
||||
),
|
||||
|
@ -1432,14 +1450,6 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
82439934 => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'834a1173' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-scrollbar',
|
||||
|
@ -1477,10 +1487,13 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
),
|
||||
'88f0c5b3' => array(
|
||||
'javelin-behavior',
|
||||
'8905523d' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'8a41885b' => array(
|
||||
'javelin-install',
|
||||
|
@ -1594,14 +1607,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-reactor-dom',
|
||||
),
|
||||
'a16ec1c6' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'a2828756' => array(
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
|
@ -1677,15 +1682,6 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-file-upload',
|
||||
),
|
||||
'ae45872f' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'phuix-dropdown-menu',
|
||||
'phuix-action-list-view',
|
||||
'phuix-action-view',
|
||||
'javelin-workflow',
|
||||
),
|
||||
'b064af76' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1759,14 +1755,12 @@ return array(
|
|||
'b6b0d1bb' => array(
|
||||
'phui-inline-comment-view-css',
|
||||
),
|
||||
'ba4fa35c' => array(
|
||||
'bcaccd64' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-behavior-device',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
'javelin-vector',
|
||||
'phui-hovercard',
|
||||
),
|
||||
'bd4c8dca' => array(
|
||||
'javelin-install',
|
||||
|
@ -1792,13 +1786,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'c6f720ff' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
'javelin-request',
|
||||
'javelin-uri',
|
||||
),
|
||||
'c72aa091' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1854,6 +1841,16 @@ return array(
|
|||
'd00a795a' => array(
|
||||
'phui-theme-css',
|
||||
),
|
||||
'd0c516d5' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'phuix-dropdown-menu',
|
||||
'phuix-action-list-view',
|
||||
'phuix-action-view',
|
||||
'javelin-workflow',
|
||||
'phuix-icon-view',
|
||||
),
|
||||
'd19198c8' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1921,6 +1918,9 @@ return array(
|
|||
'javelin-dom',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'e1bd8d04' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
'e1d25dfb' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1950,6 +1950,12 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
),
|
||||
'e4232876' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
'phui-chart-css',
|
||||
),
|
||||
'e4cc26b3' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1982,9 +1988,6 @@ return array(
|
|||
'e6e25838' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'e9339dc3' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
'e9581f08' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2221,8 +2224,8 @@ return array(
|
|||
'phabricator-file-upload',
|
||||
'javelin-behavior-global-drag-and-drop',
|
||||
'javelin-behavior-phabricator-reveal-content',
|
||||
'phabricator-hovercard',
|
||||
'javelin-behavior-phabricator-hovercards',
|
||||
'phui-hovercard',
|
||||
'javelin-behavior-phui-hovercards',
|
||||
'javelin-color',
|
||||
'javelin-fx',
|
||||
'phabricator-draggable-list',
|
||||
|
|
|
@ -59,8 +59,8 @@ return array(
|
|||
'phabricator-file-upload',
|
||||
'javelin-behavior-global-drag-and-drop',
|
||||
'javelin-behavior-phabricator-reveal-content',
|
||||
'phabricator-hovercard',
|
||||
'javelin-behavior-phabricator-hovercards',
|
||||
'phui-hovercard',
|
||||
'javelin-behavior-phui-hovercards',
|
||||
'javelin-color',
|
||||
'javelin-fx',
|
||||
'phabricator-draggable-list',
|
||||
|
|
2
resources/sql/autopatches/20160202.board.1.proxy.sql
Normal file
2
resources/sql/autopatches/20160202.board.1.proxy.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_project.project_column
|
||||
ADD proxyPHID VARBINARY(64);
|
5
resources/sql/autopatches/20160202.ipv6.1.sql
Normal file
5
resources/sql/autopatches/20160202.ipv6.1.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE {$NAMESPACE}_repository.repository_pullevent
|
||||
CHANGE remoteAddress remoteAddress VARBINARY(64);
|
||||
|
||||
ALTER TABLE {$NAMESPACE}_repository.repository_pushevent
|
||||
CHANGE remoteAddress remoteAddress VARBINARY(64);
|
39
resources/sql/autopatches/20160202.ipv6.2.php
Normal file
39
resources/sql/autopatches/20160202.ipv6.2.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
$pull = new PhabricatorRepositoryPullEvent();
|
||||
$push = new PhabricatorRepositoryPushEvent();
|
||||
|
||||
$conn_w = $pull->establishConnection('w');
|
||||
|
||||
$log_types = array($pull, $push);
|
||||
foreach ($log_types as $log) {
|
||||
foreach (new LiskMigrationIterator($log) as $row) {
|
||||
$addr = $row->getRemoteAddress();
|
||||
|
||||
$addr = (string)$addr;
|
||||
if (!strlen($addr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ctype_digit($addr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(int)$addr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ip = long2ip($addr);
|
||||
if (!is_string($ip) || !strlen($ip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = $row->getID();
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET remoteAddress = %s WHERE id = %d',
|
||||
$log->getTableName(),
|
||||
$ip,
|
||||
$id);
|
||||
}
|
||||
}
|
|
@ -1493,6 +1493,8 @@ phutil_register_library_map(array(
|
|||
'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php',
|
||||
'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php',
|
||||
'PHUIHeaderView' => 'view/phui/PHUIHeaderView.php',
|
||||
'PHUIHovercardUIExample' => 'applications/uiexample/examples/PHUIHovercardUIExample.php',
|
||||
'PHUIHovercardView' => 'view/phui/PHUIHovercardView.php',
|
||||
'PHUIIconCircleView' => 'view/phui/PHUIIconCircleView.php',
|
||||
'PHUIIconExample' => 'applications/uiexample/examples/PHUIIconExample.php',
|
||||
'PHUIIconView' => 'view/phui/PHUIIconView.php',
|
||||
|
@ -1585,6 +1587,7 @@ phutil_register_library_map(array(
|
|||
'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php',
|
||||
'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php',
|
||||
'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php',
|
||||
'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php',
|
||||
'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php',
|
||||
'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
|
||||
'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php',
|
||||
|
@ -1811,6 +1814,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php',
|
||||
'PhabricatorBinariesSetupCheck' => 'applications/config/check/PhabricatorBinariesSetupCheck.php',
|
||||
'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php',
|
||||
'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php',
|
||||
'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
|
||||
'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php',
|
||||
'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
|
||||
|
@ -1885,6 +1889,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
|
||||
'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
|
||||
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
|
||||
'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php',
|
||||
'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php',
|
||||
'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php',
|
||||
'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php',
|
||||
|
@ -2388,8 +2393,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHomeQuickCreateController' => 'applications/home/controller/PhabricatorHomeQuickCreateController.php',
|
||||
'PhabricatorHovercardEngineExtension' => 'applications/search/engineextension/PhabricatorHovercardEngineExtension.php',
|
||||
'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php',
|
||||
'PhabricatorHovercardUIExample' => 'applications/uiexample/examples/PhabricatorHovercardUIExample.php',
|
||||
'PhabricatorHovercardView' => 'view/widget/hovercard/PhabricatorHovercardView.php',
|
||||
'PhabricatorHunksManagementMigrateWorkflow' => 'applications/differential/management/PhabricatorHunksManagementMigrateWorkflow.php',
|
||||
'PhabricatorHunksManagementWorkflow' => 'applications/differential/management/PhabricatorHunksManagementWorkflow.php',
|
||||
'PhabricatorIDsSearchEngineExtension' => 'applications/search/engineextension/PhabricatorIDsSearchEngineExtension.php',
|
||||
|
@ -2735,7 +2738,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php',
|
||||
'PhabricatorPeopleEmpowerController' => 'applications/people/controller/PhabricatorPeopleEmpowerController.php',
|
||||
'PhabricatorPeopleExternalPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalPHIDType.php',
|
||||
'PhabricatorPeopleHovercardEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleHovercardEngineExtension.php',
|
||||
'PhabricatorPeopleIconSet' => 'applications/people/icon/PhabricatorPeopleIconSet.php',
|
||||
'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php',
|
||||
'PhabricatorPeopleInviteListController' => 'applications/people/controller/PhabricatorPeopleInviteListController.php',
|
||||
|
@ -2860,6 +2862,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php',
|
||||
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
|
||||
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
|
||||
'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php',
|
||||
'PhabricatorProjectColorsConfigOptionType' => 'applications/project/config/PhabricatorProjectColorsConfigOptionType.php',
|
||||
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
|
||||
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
|
||||
|
@ -2913,7 +2916,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectMembersProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectMembersProfilePanel.php',
|
||||
'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php',
|
||||
'PhabricatorProjectMembersViewController' => 'applications/project/controller/PhabricatorProjectMembersViewController.php',
|
||||
'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php',
|
||||
'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php',
|
||||
'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php',
|
||||
'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php',
|
||||
|
@ -2937,7 +2939,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php',
|
||||
'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php',
|
||||
'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php',
|
||||
'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
|
||||
'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php',
|
||||
'PhabricatorProjectSubprojectsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectSubprojectsProfilePanel.php',
|
||||
'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php',
|
||||
'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php',
|
||||
'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php',
|
||||
|
@ -3813,6 +3817,7 @@ phutil_register_library_map(array(
|
|||
'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php',
|
||||
'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php',
|
||||
'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php',
|
||||
'ProjectHovercardEngineExtension' => 'applications/project/events/ProjectHovercardEngineExtension.php',
|
||||
'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php',
|
||||
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
|
||||
'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php',
|
||||
|
@ -5668,6 +5673,8 @@ phutil_register_library_map(array(
|
|||
'PHUIHandleTagListView' => 'AphrontTagView',
|
||||
'PHUIHandleView' => 'AphrontView',
|
||||
'PHUIHeaderView' => 'AphrontTagView',
|
||||
'PHUIHovercardUIExample' => 'PhabricatorUIExample',
|
||||
'PHUIHovercardView' => 'AphrontTagView',
|
||||
'PHUIIconCircleView' => 'AphrontTagView',
|
||||
'PHUIIconExample' => 'PhabricatorUIExample',
|
||||
'PHUIIconView' => 'AphrontTagView',
|
||||
|
@ -5769,6 +5776,7 @@ phutil_register_library_map(array(
|
|||
'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability',
|
||||
'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability',
|
||||
'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
|
||||
'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'Phabricator404Controller' => 'PhabricatorController',
|
||||
'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
|
@ -6034,6 +6042,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher',
|
||||
'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider',
|
||||
'PhabricatorBoardLayoutEngine' => 'Phobject',
|
||||
'PhabricatorBot' => 'PhabricatorDaemon',
|
||||
'PhabricatorBotChannel' => 'PhabricatorBotTarget',
|
||||
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
|
||||
|
@ -6716,8 +6725,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHomeQuickCreateController' => 'PhabricatorHomeController',
|
||||
'PhabricatorHovercardEngineExtension' => 'Phobject',
|
||||
'PhabricatorHovercardEngineExtensionModule' => 'PhabricatorConfigModule',
|
||||
'PhabricatorHovercardUIExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorHovercardView' => 'AphrontView',
|
||||
'PhabricatorHunksManagementMigrateWorkflow' => 'PhabricatorHunksManagementWorkflow',
|
||||
'PhabricatorHunksManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'PhabricatorIDsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
|
||||
|
@ -7108,7 +7115,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleEmpowerController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleExternalPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorPeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
|
||||
'PhabricatorPeopleIconSet' => 'PhabricatorIconSet',
|
||||
'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleInviteListController' => 'PhabricatorPeopleInviteController',
|
||||
|
@ -7259,6 +7265,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorFulltextInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
'PhabricatorColumnProxyInterface',
|
||||
),
|
||||
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
||||
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
||||
|
@ -7267,6 +7274,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectCardView' => 'AphrontTagView',
|
||||
'PhabricatorProjectColorsConfigOptionType' => 'PhabricatorConfigJSONOptionType',
|
||||
'PhabricatorProjectColumn' => array(
|
||||
'PhabricatorProjectDAO',
|
||||
|
@ -7330,7 +7338,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectMembersProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMembersViewController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMoveController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar',
|
||||
'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
|
@ -7357,7 +7364,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorStandardCustomFieldInterface',
|
||||
),
|
||||
'PhabricatorProjectStatus' => 'Phobject',
|
||||
'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectSubprojectsProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
||||
'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
|
@ -8443,6 +8452,7 @@ phutil_register_library_map(array(
|
|||
'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability',
|
||||
'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability',
|
||||
'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||
'ProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
|
||||
'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
|
||||
'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase',
|
||||
|
|
|
@ -542,8 +542,12 @@ final class AphrontRequest extends Phobject {
|
|||
return $this->isFormPost() && $this->getStr('__dialog__');
|
||||
}
|
||||
|
||||
public function getRemoteAddr() {
|
||||
return $_SERVER['REMOTE_ADDR'];
|
||||
public function getRemoteAddress() {
|
||||
$address = $_SERVER['REMOTE_ADDR'];
|
||||
if (!strlen($address)) {
|
||||
return null;
|
||||
}
|
||||
return substr($address, 0, 64);
|
||||
}
|
||||
|
||||
public function isHTTPS() {
|
||||
|
|
|
@ -66,7 +66,7 @@ final class CelerityResourceTransformer extends Phobject {
|
|||
return $data;
|
||||
}
|
||||
|
||||
// Some resources won't survive minification (like Raphael.js), and are
|
||||
// Some resources won't survive minification (like d3.min.js), and are
|
||||
// marked so as not to be minified.
|
||||
if (strpos($data, '@'.'do-not-minify') !== false) {
|
||||
return $data;
|
||||
|
|
|
@ -322,6 +322,7 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject {
|
|||
case 'phid':
|
||||
case 'policy';
|
||||
case 'hashpath64':
|
||||
case 'ipaddress':
|
||||
$column_type = 'varbinary(64)';
|
||||
break;
|
||||
case 'bytes64':
|
||||
|
|
|
@ -35,7 +35,7 @@ final class DifferentialHovercardEngineExtension
|
|||
}
|
||||
|
||||
public function renderHovercard(
|
||||
PhabricatorHovercardView $hovercard,
|
||||
PHUIHovercardView $hovercard,
|
||||
PhabricatorObjectHandle $handle,
|
||||
$object,
|
||||
$data) {
|
||||
|
|
|
@ -1180,7 +1180,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
|
||||
if ($this->coverage) {
|
||||
require_celerity_resource('differential-changeset-view-css');
|
||||
$cov_index = $line['line'] - 1;
|
||||
$cov_index = $line_index;
|
||||
|
||||
if (isset($this->coverage[$cov_index])) {
|
||||
$cov_class = $this->coverage[$cov_index];
|
||||
|
|
|
@ -76,8 +76,7 @@ final class DiffusionServeController extends DiffusionController {
|
|||
}
|
||||
|
||||
try {
|
||||
$remote_addr = $request->getRemoteAddr();
|
||||
$remote_addr = ip2long($remote_addr);
|
||||
$remote_addr = $request->getRemoteAddress();
|
||||
|
||||
$pull_event = id(new PhabricatorRepositoryPullEvent())
|
||||
->setEpoch(PhabricatorTime::getNow())
|
||||
|
@ -720,11 +719,11 @@ final class DiffusionServeController extends DiffusionController {
|
|||
}
|
||||
|
||||
private function getCommonEnvironment(PhabricatorUser $viewer) {
|
||||
$remote_addr = $this->getRequest()->getRemoteAddr();
|
||||
$remote_address = $this->getRequest()->getRemoteAddress();
|
||||
|
||||
return array(
|
||||
DiffusionCommitHookEngine::ENV_USER => $viewer->getUsername(),
|
||||
DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS => $remote_addr,
|
||||
DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS => $remote_address,
|
||||
DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL => 'http',
|
||||
);
|
||||
}
|
||||
|
|
|
@ -56,15 +56,6 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
return $this->remoteAddress;
|
||||
}
|
||||
|
||||
private function getRemoteAddressForLog() {
|
||||
// If whatever we have here isn't a valid IPv4 address, just store `null`.
|
||||
// Older versions of PHP return `-1` on failure instead of `false`.
|
||||
$remote_address = $this->getRemoteAddress();
|
||||
$remote_address = max(0, ip2long($remote_address));
|
||||
$remote_address = nonempty($remote_address, null);
|
||||
return $remote_address;
|
||||
}
|
||||
|
||||
public function setSubversionTransactionInfo($transaction, $repository) {
|
||||
$this->subversionTransaction = $transaction;
|
||||
$this->subversionRepository = $repository;
|
||||
|
@ -1078,7 +1069,7 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
$viewer = $this->getViewer();
|
||||
return PhabricatorRepositoryPushEvent::initializeNewEvent($viewer)
|
||||
->setRepositoryPHID($this->getRepository()->getPHID())
|
||||
->setRemoteAddress($this->getRemoteAddressForLog())
|
||||
->setRemoteAddress($this->getRemoteAddress())
|
||||
->setRemoteProtocol($this->getRemoteProtocol())
|
||||
->setEpoch(time());
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ final class DiffusionHovercardEngineExtension
|
|||
}
|
||||
|
||||
public function renderHovercard(
|
||||
PhabricatorHovercardView $hovercard,
|
||||
PHUIHovercardView $hovercard,
|
||||
PhabricatorObjectHandle $handle,
|
||||
$commit,
|
||||
$data) {
|
||||
|
|
|
@ -47,7 +47,10 @@ final class DiffusionSymbolDatasource
|
|||
->setPHID(md5($symbol->getURI())) // Just needs to be unique.
|
||||
->setDisplayName($name)
|
||||
->setDisplayType(strtoupper($lang).' '.ucwords($type).' ('.$repo.')')
|
||||
->setPriorityType('symb');
|
||||
->setPriorityType('symb')
|
||||
->setImageSprite(
|
||||
'phabricator-search-icon phui-font-fa phui-icon-view fa-code '.
|
||||
'lightgreytext');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,12 +42,9 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
$repository = $log->getRepository();
|
||||
|
||||
// Reveal this if it's valid and the user can edit the repository.
|
||||
$remote_addr = '-';
|
||||
$remote_address = '-';
|
||||
if (isset($editable_repos[$log->getRepositoryPHID()])) {
|
||||
$remote_long = $log->getPushEvent()->getRemoteAddress();
|
||||
if ($remote_long) {
|
||||
$remote_addr = long2ip($remote_long);
|
||||
}
|
||||
$remote_address = $log->getPushEvent()->getRemoteAddress();
|
||||
}
|
||||
|
||||
$event_id = $log->getPushEvent()->getID();
|
||||
|
@ -76,7 +73,7 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
),
|
||||
$repository->getDisplayName()),
|
||||
$handles[$log->getPusherPHID()]->renderLink(),
|
||||
$remote_addr,
|
||||
$remote_address,
|
||||
$log->getPushEvent()->getRemoteProtocol(),
|
||||
$log->getRefType(),
|
||||
$log->getRefName(),
|
||||
|
|
|
@ -30,8 +30,7 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||
}
|
||||
|
||||
if (!$points) {
|
||||
// NOTE: Raphael crashes Safari if you hand it series with no points.
|
||||
throw new Exception(pht('No data to show!'));
|
||||
throw new Exception('No data to show!');
|
||||
}
|
||||
|
||||
// Limit amount of data passed to browser.
|
||||
|
@ -56,16 +55,12 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||
'div',
|
||||
array(
|
||||
'id' => $id,
|
||||
'style' => 'border: 1px solid #6f6f6f; '.
|
||||
'margin: 1em 2em; '.
|
||||
'background: #ffffff; '.
|
||||
'height: 400px; ',
|
||||
'style' => 'background: #ffffff; '.
|
||||
'height: 480px; ',
|
||||
),
|
||||
'');
|
||||
|
||||
require_celerity_resource('raphael-core');
|
||||
require_celerity_resource('raphael-g');
|
||||
require_celerity_resource('raphael-g-line');
|
||||
require_celerity_resource('d3');
|
||||
|
||||
Javelin::initBehavior('line-chart', array(
|
||||
'hardpoint' => $id,
|
||||
|
@ -75,9 +70,9 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||
'colors' => array('#0000ff'),
|
||||
));
|
||||
|
||||
$panel = new PHUIObjectBoxView();
|
||||
$panel->setHeaderText(pht('Count of %s', $spec->getName()));
|
||||
$panel->appendChild($chart);
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Count of %s', $spec->getName()))
|
||||
->appendChild($chart);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Chart'));
|
||||
|
@ -85,7 +80,7 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$panel,
|
||||
$box,
|
||||
),
|
||||
array(
|
||||
'title' => pht('Chart'),
|
||||
|
|
|
@ -179,6 +179,7 @@ final class PhabricatorFilesComposeIconBuiltinFile
|
|||
'fa-legal' => pht('Hired Protection'),
|
||||
'fa-linux' => pht('M\'Lady'),
|
||||
'fa-lock' => pht('Policy'),
|
||||
'fa-map-marker' => pht('Destination Beacon'),
|
||||
'fa-microphone' => pht('Podcasting'),
|
||||
'fa-mobile' => pht('Tiny Pocket Cat Meme Machine'),
|
||||
'fa-money' => pht('1 of 99 Problems'),
|
||||
|
|
|
@ -92,21 +92,27 @@ abstract class PhabricatorFileUploadSource
|
|||
|
||||
$threshold = PhabricatorFileStorageEngine::getChunkThreshold();
|
||||
|
||||
// If we don't know how large the file is, we're going to read some data
|
||||
// from it until we know whether it's a small file or not. This will give
|
||||
// us enough information to make a decision about chunking.
|
||||
$length = $this->getDataLength();
|
||||
if ($length === null) {
|
||||
$rope = $this->getRope();
|
||||
while ($this->readFileData()) {
|
||||
$length = $rope->getByteLength();
|
||||
if ($length > $threshold) {
|
||||
break;
|
||||
if ($threshold === null) {
|
||||
// If there are no chunk engines available, we clearly can't chunk the
|
||||
// file.
|
||||
$this->shouldChunk = false;
|
||||
} else {
|
||||
// If we don't know how large the file is, we're going to read some data
|
||||
// from it until we know whether it's a small file or not. This will give
|
||||
// us enough information to make a decision about chunking.
|
||||
$length = $this->getDataLength();
|
||||
if ($length === null) {
|
||||
$rope = $this->getRope();
|
||||
while ($this->readFileData()) {
|
||||
$length = $rope->getByteLength();
|
||||
if ($length > $threshold) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->shouldChunk = ($length > $threshold);
|
||||
$this->shouldChunk = ($length > $threshold);
|
||||
}
|
||||
|
||||
return $this->shouldChunk;
|
||||
}
|
||||
|
|
|
@ -290,9 +290,8 @@ final class ManiphestReportController extends ManiphestController {
|
|||
|
||||
list($burn_x, $burn_y) = $this->buildSeries($data);
|
||||
|
||||
require_celerity_resource('raphael-core');
|
||||
require_celerity_resource('raphael-g');
|
||||
require_celerity_resource('raphael-g-line');
|
||||
require_celerity_resource('d3');
|
||||
require_celerity_resource('phui-chart-css');
|
||||
|
||||
Javelin::initBehavior('line-chart', array(
|
||||
'hardpoint' => $id,
|
||||
|
@ -306,7 +305,11 @@ final class ManiphestReportController extends ManiphestController {
|
|||
'yformat' => 'int',
|
||||
));
|
||||
|
||||
return array($filter, $chart, $panel);
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Burnup Rate'))
|
||||
->appendChild($chart);
|
||||
|
||||
return array($filter, $box, $panel);
|
||||
}
|
||||
|
||||
private function renderReportFilters(array $tokens, $has_window) {
|
||||
|
|
|
@ -280,20 +280,35 @@ final class ManiphestEditEngine
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
// If the workboard's project has been removed from the card's project
|
||||
// list, we are going to remove it from the board completely.
|
||||
// If the workboard's project and all descendant projects have been removed
|
||||
// from the card's project list, we are going to remove it from the board
|
||||
// completely.
|
||||
|
||||
// TODO: If the user did something sneaky and changed a subproject, we'll
|
||||
// currently leave the card where it was but should really move it to the
|
||||
// proper new column.
|
||||
|
||||
$descendant_projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs(array($column->getProjectPHID()))
|
||||
->execute();
|
||||
$board_phids = mpull($descendant_projects, 'getPHID', 'getPHID');
|
||||
$board_phids[$column->getProjectPHID()] = $column->getProjectPHID();
|
||||
|
||||
$project_map = array_fuse($task->getProjectPHIDs());
|
||||
$remove_card = empty($project_map[$column->getProjectPHID()]);
|
||||
$remove_card = !array_intersect_key($board_phids, $project_map);
|
||||
|
||||
$positions = id(new PhabricatorProjectColumnPositionQuery())
|
||||
->setViewer($viewer)
|
||||
->withColumns(array($column))
|
||||
->withBoardPHIDs(array($column->getProjectPHID()))
|
||||
->withColumnPHIDs(array($column->getPHID()))
|
||||
->execute();
|
||||
$task_phids = mpull($positions, 'getObjectPHID');
|
||||
|
||||
$column_tasks = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($task_phids)
|
||||
->needProjectPHIDs(true)
|
||||
->execute();
|
||||
|
||||
if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
|
||||
|
@ -329,13 +344,23 @@ final class ManiphestEditEngine
|
|||
->executeOne();
|
||||
}
|
||||
|
||||
$handle_phids = $task->getProjectPHIDs();
|
||||
$handle_phids = array_fuse($handle_phids);
|
||||
$handle_phids = array_diff_key($handle_phids, $board_phids);
|
||||
|
||||
$project_handles = $viewer->loadHandles($handle_phids);
|
||||
$project_handles = iterator_to_array($project_handles);
|
||||
|
||||
$tasks = id(new ProjectBoardTaskCard())
|
||||
->setViewer($viewer)
|
||||
->setTask($task)
|
||||
->setOwner($owner)
|
||||
->setProjectHandles($project_handles)
|
||||
->setCanEdit(true)
|
||||
->getItem();
|
||||
|
||||
$tasks->addClass('phui-workcard');
|
||||
|
||||
$payload = array(
|
||||
'tasks' => $tasks,
|
||||
'data' => $data,
|
||||
|
|
|
@ -200,18 +200,6 @@ final class ManiphestTransactionEditor
|
|||
'columnPHIDs'));
|
||||
}
|
||||
|
||||
$columns = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($this->requireActor())
|
||||
->withPHIDs($new_phids)
|
||||
->execute();
|
||||
$columns = mpull($columns, null, 'getPHID');
|
||||
|
||||
$positions = id(new PhabricatorProjectColumnPositionQuery())
|
||||
->setViewer($this->requireActor())
|
||||
->withObjectPHIDs(array($object->getPHID()))
|
||||
->withBoardPHIDs(array($board_phid))
|
||||
->execute();
|
||||
|
||||
$before_phid = idx($xaction->getNewValue(), 'beforePHID');
|
||||
$after_phid = idx($xaction->getNewValue(), 'afterPHID');
|
||||
|
||||
|
@ -227,111 +215,86 @@ final class ManiphestTransactionEditor
|
|||
// object's position in the "natural" ordering, so we do need to update
|
||||
// some rows.
|
||||
|
||||
$object_phid = $object->getPHID();
|
||||
|
||||
// We're doing layout with the ominpotent viewer to make sure we don't
|
||||
// remove positions in columns that exist, but which the actual actor
|
||||
// can't see.
|
||||
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$select_phids = array($board_phid);
|
||||
|
||||
$descendants = id(new PhabricatorProjectQuery())
|
||||
->setViewer($omnipotent_viewer)
|
||||
->withAncestorProjectPHIDs($select_phids)
|
||||
->execute();
|
||||
foreach ($descendants as $descendant) {
|
||||
$select_phids[] = $descendant->getPHID();
|
||||
}
|
||||
|
||||
$board_tasks = id(new ManiphestTaskQuery())
|
||||
->setViewer($omnipotent_viewer)
|
||||
->withEdgeLogicPHIDs(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
|
||||
array($select_phids))
|
||||
->execute();
|
||||
|
||||
$object_phids = mpull($board_tasks, 'getPHID');
|
||||
$object_phids[] = $object_phid;
|
||||
|
||||
$engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($omnipotent_viewer)
|
||||
->setBoardPHIDs(array($board_phid))
|
||||
->setObjectPHIDs($object_phids)
|
||||
->executeLayout();
|
||||
|
||||
// TODO: This logic needs to be revised if we legitimately support
|
||||
// multiple column positions.
|
||||
|
||||
// NOTE: When a task is newly created, it's implicitly added to the
|
||||
// backlog but we don't currently record that in the "$old_phids". Just
|
||||
// clean it up for now.
|
||||
$columns = $engine->getObjectColumns($board_phid, $object_phid);
|
||||
foreach ($columns as $column) {
|
||||
$engine->queueRemovePosition(
|
||||
$board_phid,
|
||||
$column->getPHID(),
|
||||
$object_phid);
|
||||
}
|
||||
|
||||
// Remove all existing column positions on the board.
|
||||
|
||||
foreach ($positions as $position) {
|
||||
$position->delete();
|
||||
foreach ($old_phids as $column_phid) {
|
||||
$engine->queueRemovePosition(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid);
|
||||
}
|
||||
|
||||
// Add the new column positions.
|
||||
|
||||
foreach ($new_phids as $phid) {
|
||||
$column = idx($columns, $phid);
|
||||
if (!$column) {
|
||||
throw new Exception(
|
||||
pht('No such column "%s" exists!', $phid));
|
||||
}
|
||||
|
||||
// Load the other object positions in the column. Note that we must
|
||||
// skip implicit column creation to avoid generating a new position
|
||||
// if the target column is a backlog column.
|
||||
|
||||
$other_positions = id(new PhabricatorProjectColumnPositionQuery())
|
||||
->setViewer($this->requireActor())
|
||||
->withColumns(array($column))
|
||||
->withBoardPHIDs(array($board_phid))
|
||||
->setSkipImplicitCreate(true)
|
||||
->execute();
|
||||
$other_positions = msort($other_positions, 'getOrderingKey');
|
||||
|
||||
// Set up the new position object. We're going to figure out the
|
||||
// right sequence number and then persist this object with that
|
||||
// sequence number.
|
||||
$new_position = id(new PhabricatorProjectColumnPosition())
|
||||
->setBoardPHID($board_phid)
|
||||
->setColumnPHID($column->getPHID())
|
||||
->setObjectPHID($object->getPHID());
|
||||
|
||||
$updates = array();
|
||||
$sequence = 0;
|
||||
|
||||
// If we're just dropping this into the column without any specific
|
||||
// position information, put it at the top.
|
||||
if (!$before_phid && !$after_phid) {
|
||||
$new_position->setSequence($sequence)->save();
|
||||
$sequence++;
|
||||
}
|
||||
|
||||
foreach ($other_positions as $position) {
|
||||
$object_phid = $position->getObjectPHID();
|
||||
|
||||
// If this is the object we're moving before and we haven't
|
||||
// saved yet, insert here.
|
||||
if (($before_phid == $object_phid) && !$new_position->getID()) {
|
||||
$new_position->setSequence($sequence)->save();
|
||||
$sequence++;
|
||||
}
|
||||
|
||||
// This object goes here in the sequence; we might need to update
|
||||
// the row.
|
||||
if ($sequence != $position->getSequence()) {
|
||||
$updates[$position->getID()] = $sequence;
|
||||
}
|
||||
$sequence++;
|
||||
|
||||
// If this is the object we're moving after and we haven't saved
|
||||
// yet, insert here.
|
||||
if (($after_phid == $object_phid) && !$new_position->getID()) {
|
||||
$new_position->setSequence($sequence)->save();
|
||||
$sequence++;
|
||||
}
|
||||
}
|
||||
|
||||
// We should have found a place to put it.
|
||||
if (!$new_position->getID()) {
|
||||
throw new Exception(
|
||||
pht('Unable to find a place to insert object on column!'));
|
||||
}
|
||||
|
||||
// If we changed other objects' column positions, bulk reorder them.
|
||||
|
||||
if ($updates) {
|
||||
$position = new PhabricatorProjectColumnPosition();
|
||||
$conn_w = $position->establishConnection('w');
|
||||
|
||||
$pairs = array();
|
||||
foreach ($updates as $id => $sequence) {
|
||||
// This is ugly because MySQL gets upset with us if it is
|
||||
// configured strictly and we attempt inserts which can't work.
|
||||
// We'll never actually do these inserts since they'll always
|
||||
// collide (triggering the ON DUPLICATE KEY logic), so we just
|
||||
// provide dummy values in order to get there.
|
||||
|
||||
$pairs[] = qsprintf(
|
||||
$conn_w,
|
||||
'(%d, %d, "", "", "")',
|
||||
$id,
|
||||
$sequence);
|
||||
}
|
||||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT INTO %T (id, sequence, boardPHID, columnPHID, objectPHID)
|
||||
VALUES %Q ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)',
|
||||
$position->getTableName(),
|
||||
implode(', ', $pairs));
|
||||
// Add new positions.
|
||||
foreach ($new_phids as $column_phid) {
|
||||
if ($before_phid) {
|
||||
$engine->queueAddPositionBefore(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid,
|
||||
$before_phid);
|
||||
} else if ($after_phid) {
|
||||
$engine->queueAddPositionAfter(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid,
|
||||
$after_phid);
|
||||
} else {
|
||||
$engine->queueAddPosition(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid);
|
||||
}
|
||||
}
|
||||
|
||||
$engine->applyPositionUpdates();
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -19,7 +19,7 @@ final class ManiphestHovercardEngineExtension
|
|||
}
|
||||
|
||||
public function renderHovercard(
|
||||
PhabricatorHovercardView $hovercard,
|
||||
PHUIHovercardView $hovercard,
|
||||
PhabricatorObjectHandle $handle,
|
||||
$task,
|
||||
$data) {
|
||||
|
|
|
@ -54,23 +54,21 @@ final class PhabricatorPeopleProfileViewController
|
|||
$feed = $this->buildPeopleFeed($user, $viewer);
|
||||
$feed = phutil_tag_div('project-view-feed', $feed);
|
||||
|
||||
$projects = $this->buildProjectsView($user);
|
||||
$badges = $this->buildBadgesView($user);
|
||||
|
||||
if ($badges) {
|
||||
$columns = id(new PHUITwoColumnView())
|
||||
->addClass('project-view-badges')
|
||||
->setMainColumn(
|
||||
array(
|
||||
$properties,
|
||||
$feed,
|
||||
))
|
||||
->setSideColumn(
|
||||
array(
|
||||
$badges,
|
||||
));
|
||||
} else {
|
||||
$columns = array($properties, $feed);
|
||||
}
|
||||
$columns = id(new PHUITwoColumnView())
|
||||
->addClass('project-view-badges')
|
||||
->setMainColumn(
|
||||
array(
|
||||
$properties,
|
||||
$feed,
|
||||
))
|
||||
->setSideColumn(
|
||||
array(
|
||||
$projects,
|
||||
$badges,
|
||||
));
|
||||
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_PROFILE);
|
||||
|
@ -112,7 +110,7 @@ final class PhabricatorPeopleProfileViewController
|
|||
PhabricatorCustomField::ROLE_VIEW);
|
||||
$field_list->appendFieldsToPropertyList($user, $viewer, $view);
|
||||
|
||||
if ($view->isEmpty()) {
|
||||
if (!$view->hasAnyProperties()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -124,39 +122,101 @@ final class PhabricatorPeopleProfileViewController
|
|||
return $view;
|
||||
}
|
||||
|
||||
private function buildBadgesView(
|
||||
private function buildProjectsView(
|
||||
PhabricatorUser $user) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$class = 'PhabricatorBadgesApplication';
|
||||
$box = null;
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withMemberPHIDs(array($user->getPHID()))
|
||||
->needImages(true)
|
||||
->withStatus(PhabricatorProjectQuery::STATUS_OPEN)
|
||||
->execute();
|
||||
|
||||
if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
$badge_phids = $user->getBadgePHIDs();
|
||||
if ($badge_phids) {
|
||||
$badges = id(new PhabricatorBadgesQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($badge_phids)
|
||||
->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE))
|
||||
->execute();
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Projects'));
|
||||
|
||||
$flex = new PHUIBadgeBoxView();
|
||||
foreach ($badges as $badge) {
|
||||
$item = id(new PHUIBadgeView())
|
||||
->setIcon($badge->getIcon())
|
||||
->setHeader($badge->getName())
|
||||
->setSubhead($badge->getFlavor())
|
||||
->setQuality($badge->getQuality());
|
||||
$flex->addItem($item);
|
||||
}
|
||||
if (!empty($projects)) {
|
||||
$limit = 5;
|
||||
$render_phids = array_slice($projects, 0, $limit);
|
||||
$list = id(new PhabricatorProjectListView())
|
||||
->setUser($viewer)
|
||||
->setProjects($render_phids);
|
||||
|
||||
if (count($projects) > $limit) {
|
||||
$header_text = pht(
|
||||
'Projects (%s)',
|
||||
phutil_count($projects));
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($header_text)
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-list-ul')
|
||||
->setText(pht('View All'))
|
||||
->setHref('/project/?member='.$user->getPHID()));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Badges'))
|
||||
->appendChild($flex)
|
||||
->setBackground(PHUIBoxView::GREY);
|
||||
}
|
||||
|
||||
} else {
|
||||
$error = id(new PHUIBoxView())
|
||||
->addClass('mlb')
|
||||
->appendChild(pht('User does not belong to any projects.'));
|
||||
$list = id(new PHUIInfoView())
|
||||
->setSeverity(PHUIInfoView::SEVERITY_NODATA)
|
||||
->appendChild($error);
|
||||
}
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->appendChild($list)
|
||||
->setBackground(PHUIBoxView::GREY);
|
||||
|
||||
return $box;
|
||||
}
|
||||
|
||||
private function buildBadgesView(PhabricatorUser $user) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$class = 'PhabricatorBadgesApplication';
|
||||
|
||||
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$badge_phids = $user->getBadgePHIDs();
|
||||
if ($badge_phids) {
|
||||
$badges = id(new PhabricatorBadgesQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($badge_phids)
|
||||
->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE))
|
||||
->execute();
|
||||
|
||||
$flex = new PHUIBadgeBoxView();
|
||||
foreach ($badges as $badge) {
|
||||
$item = id(new PHUIBadgeView())
|
||||
->setIcon($badge->getIcon())
|
||||
->setHeader($badge->getName())
|
||||
->setSubhead($badge->getFlavor())
|
||||
->setQuality($badge->getQuality());
|
||||
$flex->addItem($item);
|
||||
}
|
||||
|
||||
} else {
|
||||
$error = id(new PHUIBoxView())
|
||||
->addClass('mlb')
|
||||
->appendChild(pht('User does not have any badges.'));
|
||||
$flex = id(new PHUIInfoView())
|
||||
->setSeverity(PHUIInfoView::SEVERITY_NODATA)
|
||||
->appendChild($error);
|
||||
}
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Badges'))
|
||||
->appendChild($flex)
|
||||
->setBackground(PHUIBoxView::GREY);
|
||||
|
||||
return $box;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,13 @@ final class PhabricatorUserBlurbField
|
|||
->setLabel($this->getFieldName());
|
||||
}
|
||||
|
||||
public function getApplicationTransactionRemarkupBlocks(
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
return array(
|
||||
$xaction->getNewValue(),
|
||||
);
|
||||
}
|
||||
|
||||
public function renderPropertyViewLabel() {
|
||||
return null;
|
||||
}
|
||||
|
@ -67,10 +74,11 @@ final class PhabricatorUserBlurbField
|
|||
if (!strlen($blurb)) {
|
||||
return null;
|
||||
}
|
||||
return PhabricatorMarkupEngine::renderOneObject(
|
||||
id(new PhabricatorMarkupOneOff())->setContent($blurb),
|
||||
'default',
|
||||
$this->getViewer());
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$view = new PHUIRemarkupView($viewer, $blurb);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
public function getStyleForPropertyView() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPeopleHovercardEngineExtension
|
||||
final class PeopleHovercardEngineExtension
|
||||
extends PhabricatorHovercardEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'people';
|
||||
|
@ -36,7 +36,7 @@ final class PhabricatorPeopleHovercardEngineExtension
|
|||
}
|
||||
|
||||
public function renderHovercard(
|
||||
PhabricatorHovercardView $hovercard,
|
||||
PHUIHovercardView $hovercard,
|
||||
PhabricatorObjectHandle $handle,
|
||||
$object,
|
||||
$data) {
|
|
@ -74,6 +74,26 @@ final class PhabricatorPeopleIconSet
|
|||
'icon' => 'fa-heart',
|
||||
'name' => pht('Resources'),
|
||||
),
|
||||
array(
|
||||
'key' => 'camera',
|
||||
'icon' => 'fa-camera-retro',
|
||||
'name' => pht('Design'),
|
||||
),
|
||||
array(
|
||||
'key' => 'music',
|
||||
'icon' => 'fa-headphones',
|
||||
'name' => pht('Musician'),
|
||||
),
|
||||
array(
|
||||
'key' => 'spy',
|
||||
'icon' => 'fa-user-secret',
|
||||
'name' => pht('Spy'),
|
||||
),
|
||||
array(
|
||||
'key' => 'android',
|
||||
'icon' => 'fa-android',
|
||||
'name' => pht('Bot'),
|
||||
),
|
||||
array(
|
||||
'key' => 'relationships',
|
||||
'icon' => 'fa-glass',
|
||||
|
|
|
@ -93,7 +93,7 @@ final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule {
|
|||
|
||||
if ($exists) {
|
||||
$user = $actual_users[$username];
|
||||
Javelin::initBehavior('phabricator-hovercards');
|
||||
Javelin::initBehavior('phui-hovercards');
|
||||
|
||||
// Check if the user has view access to the object she was mentioned in
|
||||
if ($context_object
|
||||
|
|
|
@ -54,8 +54,10 @@ final class PhabricatorPeopleUserPHIDType extends PhabricatorPHIDType {
|
|||
$icon_icon = PhabricatorPeopleIconSet::getIconIcon($icon_key);
|
||||
$subtitle = $profile->getDisplayTitle();
|
||||
|
||||
$handle->setIcon($icon_icon);
|
||||
$handle->setSubtitle($subtitle);
|
||||
$handle
|
||||
->setIcon($icon_icon)
|
||||
->setSubtitle($subtitle)
|
||||
->setTokenIcon('fa-user');
|
||||
}
|
||||
|
||||
$availability = null;
|
||||
|
|
|
@ -41,13 +41,13 @@ final class PhabricatorUserLogView extends AphrontView {
|
|||
$ip = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $base_uri.'?ip='.$log->getRemoteAddr().'#R',
|
||||
'href' => $base_uri.'?ip='.$ip.'#R',
|
||||
),
|
||||
$ip);
|
||||
$session = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $base_uri.'?sessions='.$log->getSession().'#R',
|
||||
'href' => $base_uri.'?sessions='.$ip.'#R',
|
||||
),
|
||||
$session);
|
||||
}
|
||||
|
|
|
@ -55,20 +55,17 @@ final class PhameHomeController extends PhamePostController {
|
|||
->addAction($create_button);
|
||||
}
|
||||
|
||||
$actions = $this->renderActions($viewer);
|
||||
$action_button = id(new PHUIButtonView())
|
||||
$view_all = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setText(pht('Actions'))
|
||||
->setHref('#')
|
||||
->setIcon('fa-bars')
|
||||
->addClass('phui-mobile-menu')
|
||||
->setDropdownMenu($actions);
|
||||
->setText(pht('View All'))
|
||||
->setHref($this->getApplicationURI('post/'))
|
||||
->setIcon('fa-list-ul');
|
||||
|
||||
$title = pht('Recent Posts');
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title)
|
||||
->addActionLink($action_button);
|
||||
->addActionLink($view_all);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->setBorder(true);
|
||||
|
@ -108,39 +105,21 @@ final class PhameHomeController extends PhamePostController {
|
|||
$blog_list,
|
||||
$draft_list,
|
||||
))
|
||||
->setDisplay(PHUITwoColumnView::DISPLAY_LEFT)
|
||||
->addClass('phame-home-view');
|
||||
->addClass('phame-home-container');
|
||||
|
||||
$phame_home = phutil_tag_div('phame-home-view', $phame_view);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$phame_view,
|
||||
$phame_home,
|
||||
));
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function renderActions($viewer) {
|
||||
$actions = id(new PhabricatorActionListView())
|
||||
->setUser($viewer);
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-pencil')
|
||||
->setHref($this->getApplicationURI('post/query/draft/'))
|
||||
->setName(pht('My Drafts')));
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-pencil-square-o')
|
||||
->setHref($this->getApplicationURI('post/'))
|
||||
->setName(pht('All Posts')));
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
private function renderBlogs($viewer, $blogs) {}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
|
|
|
@ -101,9 +101,18 @@ final class PhamePostViewController
|
|||
$subtitle = pht('Written by %s on %s.', $author, $date);
|
||||
}
|
||||
|
||||
$user_icon = $blogger_profile->getIcon();
|
||||
$user_icon = PhabricatorPeopleIconSet::getIconIcon($user_icon);
|
||||
$user_icon = id(new PHUIIconView())->setIcon($user_icon);
|
||||
|
||||
$about = id(new PhameDescriptionView())
|
||||
->setTitle($subtitle)
|
||||
->setDescription($blogger_profile->getTitle())
|
||||
->setDescription(
|
||||
array(
|
||||
$user_icon,
|
||||
' ',
|
||||
$blogger_profile->getTitle(),
|
||||
))
|
||||
->setImage($blogger->getProfileImageURI())
|
||||
->setImageHref('/p/'.$blogger->getUsername());
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ final class PhabricatorObjectHandle
|
|||
private $objectName;
|
||||
private $policyFiltered;
|
||||
private $subtitle;
|
||||
private $tokenIcon;
|
||||
|
||||
public function setIcon($icon) {
|
||||
$this->icon = $icon;
|
||||
|
@ -86,6 +87,19 @@ final class PhabricatorObjectHandle
|
|||
return null;
|
||||
}
|
||||
|
||||
public function setTokenIcon($icon) {
|
||||
$this->tokenIcon = $icon;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTokenIcon() {
|
||||
if ($this->tokenIcon !== null) {
|
||||
return $this->tokenIcon;
|
||||
}
|
||||
|
||||
return $this->getIcon();
|
||||
}
|
||||
|
||||
public function getTypeIcon() {
|
||||
if ($this->getPHIDType()) {
|
||||
return $this->getPHIDType()->getTypeIcon();
|
||||
|
@ -262,7 +276,7 @@ final class PhabricatorObjectHandle
|
|||
}
|
||||
|
||||
public function renderHovercardLink($name = null) {
|
||||
Javelin::initBehavior('phabricator-hovercards');
|
||||
Javelin::initBehavior('phui-hovercards');
|
||||
|
||||
$attributes = array(
|
||||
'sigil' => 'hovercard',
|
||||
|
|
|
@ -9,7 +9,7 @@ final class PHUIHandleTagListView extends AphrontTagView {
|
|||
private $slim;
|
||||
private $showHovercards;
|
||||
|
||||
public function setHandles(array $handles) {
|
||||
public function setHandles($handles) {
|
||||
$this->handles = $handles;
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ final class PhabricatorPolicyEditController
|
|||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
|
||||
$object_phid = $request->getURIData('objectPHID');
|
||||
if ($object_phid) {
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
|
@ -32,6 +31,17 @@ final class PhabricatorPolicyEditController
|
|||
}
|
||||
}
|
||||
|
||||
$phid = $request->getURIData('phid');
|
||||
switch ($phid) {
|
||||
case AphrontFormPolicyControl::getSelectProjectKey():
|
||||
return $this->handleProjectRequest($request);
|
||||
case AphrontFormPolicyControl::getSelectCustomKey():
|
||||
$phid = null;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$action_options = array(
|
||||
PhabricatorPolicy::ACTION_ALLOW => pht('Allow'),
|
||||
PhabricatorPolicy::ACTION_DENY => pht('Deny'),
|
||||
|
@ -55,7 +65,6 @@ final class PhabricatorPolicyEditController
|
|||
'value' => null,
|
||||
);
|
||||
|
||||
$phid = $request->getURIData('phid');
|
||||
if ($phid) {
|
||||
$policies = id(new PhabricatorPolicyQuery())
|
||||
->setViewer($viewer)
|
||||
|
@ -253,4 +262,79 @@ final class PhabricatorPolicyEditController
|
|||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
private function handleProjectRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$errors = array();
|
||||
$e_project = true;
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$project_phids = $request->getArr('projectPHIDs');
|
||||
$project_phid = head($project_phids);
|
||||
|
||||
$project = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($project_phid))
|
||||
->executeOne();
|
||||
|
||||
if ($project) {
|
||||
// Save this project as one of the user's most recently used projects,
|
||||
// so we'll show it by default in future menus.
|
||||
|
||||
$pref_key = PhabricatorUserPreferences::PREFERENCE_FAVORITE_POLICIES;
|
||||
|
||||
$preferences = $viewer->loadPreferences();
|
||||
$favorites = $preferences->getPreference($pref_key);
|
||||
if (!is_array($favorites)) {
|
||||
$favorites = array();
|
||||
}
|
||||
|
||||
// Add this, or move it to the end of the list.
|
||||
unset($favorites[$project_phid]);
|
||||
$favorites[$project_phid] = true;
|
||||
|
||||
$preferences->setPreference($pref_key, $favorites);
|
||||
$preferences->save();
|
||||
|
||||
$data = array(
|
||||
'phid' => $project->getPHID(),
|
||||
'info' => array(
|
||||
'name' => $project->getName(),
|
||||
'full' => $project->getName(),
|
||||
'icon' => $project->getDisplayIconIcon(),
|
||||
),
|
||||
);
|
||||
|
||||
return id(new AphrontAjaxResponse())->setContent($data);
|
||||
} else {
|
||||
$errors[] = pht('You must choose a project.');
|
||||
$e_project = pht('Required');
|
||||
}
|
||||
}
|
||||
|
||||
$project_datasource = id(new PhabricatorProjectDatasource())
|
||||
->setParameters(
|
||||
array(
|
||||
'policy' => 1,
|
||||
));
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Members Of'))
|
||||
->setName('projectPHIDs')
|
||||
->setLimit(1)
|
||||
->setError($e_project)
|
||||
->setDatasource($project_datasource));
|
||||
|
||||
return $this->newDialog()
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setErrors($errors)
|
||||
->setTitle(pht('Select Project'))
|
||||
->appendForm($form)
|
||||
->addSubmitButton(pht('Done'))
|
||||
->addCancelButton('#');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -195,10 +195,48 @@ final class PhabricatorPolicyQuery
|
|||
$viewer = $this->getViewer();
|
||||
|
||||
if ($viewer->getPHID()) {
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withMemberPHIDs(array($viewer->getPHID()))
|
||||
->execute();
|
||||
$pref_key = PhabricatorUserPreferences::PREFERENCE_FAVORITE_POLICIES;
|
||||
|
||||
$favorite_limit = 10;
|
||||
$default_limit = 5;
|
||||
|
||||
// If possible, show the user's 10 most recently used projects.
|
||||
$preferences = $viewer->loadPreferences();
|
||||
$favorites = $preferences->getPreference($pref_key);
|
||||
if (!is_array($favorites)) {
|
||||
$favorites = array();
|
||||
}
|
||||
$favorite_phids = array_keys($favorites);
|
||||
$favorite_phids = array_slice($favorite_phids, -$favorite_limit);
|
||||
|
||||
if ($favorite_phids) {
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($favorite_phids)
|
||||
->withIsMilestone(false)
|
||||
->setLimit($favorite_limit)
|
||||
->execute();
|
||||
$projects = mpull($projects, null, 'getPHID');
|
||||
} else {
|
||||
$projects = array();
|
||||
}
|
||||
|
||||
// If we didn't find enough favorites, add some default projects. These
|
||||
// are just arbitrary projects that the viewer is a member of, but may
|
||||
// be useful on smaller installs and for new users until they can use
|
||||
// the control enough time to establish useful favorites.
|
||||
if (count($projects) < $default_limit) {
|
||||
$default_projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withMemberPHIDs(array($viewer->getPHID()))
|
||||
->withIsMilestone(false)
|
||||
->setLimit($default_limit)
|
||||
->execute();
|
||||
$default_projects = mpull($default_projects, null, 'getPHID');
|
||||
$projects = $projects + $default_projects;
|
||||
$projects = array_slice($projects, 0, $default_limit);
|
||||
}
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$phids[] = $project->getPHID();
|
||||
}
|
||||
|
|
|
@ -808,6 +808,318 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
|||
pht('Engineering + Scan'));
|
||||
}
|
||||
|
||||
public function testTagAncestryConflicts() {
|
||||
$user = $this->createUser();
|
||||
$user->save();
|
||||
|
||||
$stonework = $this->createProject($user);
|
||||
$stonework_masonry = $this->createProject($user, $stonework);
|
||||
$stonework_sculpting = $this->createProject($user, $stonework);
|
||||
|
||||
$task = $this->newTask($user, array());
|
||||
$this->assertEqual(array(), $this->getTaskProjects($task));
|
||||
|
||||
$this->addProjectTags($user, $task, array($stonework->getPHID()));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
$stonework->getPHID(),
|
||||
),
|
||||
$this->getTaskProjects($task));
|
||||
|
||||
// Adding a descendant should remove the parent.
|
||||
$this->addProjectTags($user, $task, array($stonework_masonry->getPHID()));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
$stonework_masonry->getPHID(),
|
||||
),
|
||||
$this->getTaskProjects($task));
|
||||
|
||||
// Adding an ancestor should remove the descendant.
|
||||
$this->addProjectTags($user, $task, array($stonework->getPHID()));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
$stonework->getPHID(),
|
||||
),
|
||||
$this->getTaskProjects($task));
|
||||
|
||||
// Adding two tags in the same hierarchy which are not mutual ancestors
|
||||
// should remove the ancestor but otherwise work fine.
|
||||
$this->addProjectTags(
|
||||
$user,
|
||||
$task,
|
||||
array(
|
||||
$stonework_masonry->getPHID(),
|
||||
$stonework_sculpting->getPHID(),
|
||||
));
|
||||
|
||||
$expect = array(
|
||||
$stonework_masonry->getPHID(),
|
||||
$stonework_sculpting->getPHID(),
|
||||
);
|
||||
sort($expect);
|
||||
|
||||
$this->assertEqual($expect, $this->getTaskProjects($task));
|
||||
}
|
||||
|
||||
public function testTagMilestoneConflicts() {
|
||||
$user = $this->createUser();
|
||||
$user->save();
|
||||
|
||||
$stonework = $this->createProject($user);
|
||||
$stonework_1 = $this->createProject($user, $stonework, true);
|
||||
$stonework_2 = $this->createProject($user, $stonework, true);
|
||||
|
||||
$task = $this->newTask($user, array());
|
||||
$this->assertEqual(array(), $this->getTaskProjects($task));
|
||||
|
||||
$this->addProjectTags($user, $task, array($stonework->getPHID()));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
$stonework->getPHID(),
|
||||
),
|
||||
$this->getTaskProjects($task));
|
||||
|
||||
// Adding a milesone should remove the parent.
|
||||
$this->addProjectTags($user, $task, array($stonework_1->getPHID()));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
$stonework_1->getPHID(),
|
||||
),
|
||||
$this->getTaskProjects($task));
|
||||
|
||||
// Adding the parent should remove the milestone.
|
||||
$this->addProjectTags($user, $task, array($stonework->getPHID()));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
$stonework->getPHID(),
|
||||
),
|
||||
$this->getTaskProjects($task));
|
||||
|
||||
// First, add one milestone.
|
||||
$this->addProjectTags($user, $task, array($stonework_1->getPHID()));
|
||||
// Now, adding a second milestone should remove the first milestone.
|
||||
$this->addProjectTags($user, $task, array($stonework_2->getPHID()));
|
||||
$this->assertEqual(
|
||||
array(
|
||||
$stonework_2->getPHID(),
|
||||
),
|
||||
$this->getTaskProjects($task));
|
||||
}
|
||||
|
||||
public function testBoardMoves() {
|
||||
$user = $this->createUser();
|
||||
$user->save();
|
||||
|
||||
$board = $this->createProject($user);
|
||||
|
||||
$backlog = $this->addColumn($user, $board, 0);
|
||||
$column = $this->addColumn($user, $board, 1);
|
||||
|
||||
// New tasks should appear in the backlog.
|
||||
$task1 = $this->newTask($user, array($board));
|
||||
$expect = array(
|
||||
$backlog->getPHID(),
|
||||
);
|
||||
$this->assertColumns($expect, $user, $board, $task1);
|
||||
|
||||
// Moving a task should move it to the destination column.
|
||||
$this->moveToColumn($user, $board, $task1, $backlog, $column);
|
||||
$expect = array(
|
||||
$column->getPHID(),
|
||||
);
|
||||
$this->assertColumns($expect, $user, $board, $task1);
|
||||
|
||||
// Same thing again, with a new task.
|
||||
$task2 = $this->newTask($user, array($board));
|
||||
$expect = array(
|
||||
$backlog->getPHID(),
|
||||
);
|
||||
$this->assertColumns($expect, $user, $board, $task2);
|
||||
|
||||
// Move it, too.
|
||||
$this->moveToColumn($user, $board, $task2, $backlog, $column);
|
||||
$expect = array(
|
||||
$column->getPHID(),
|
||||
);
|
||||
$this->assertColumns($expect, $user, $board, $task2);
|
||||
|
||||
// Now the stuff should be in the column, in order, with the more recently
|
||||
// moved task on top.
|
||||
$expect = array(
|
||||
$task2->getPHID(),
|
||||
$task1->getPHID(),
|
||||
);
|
||||
$this->assertTasksInColumn($expect, $user, $board, $column);
|
||||
|
||||
// Move the second task after the first task.
|
||||
$options = array(
|
||||
'afterPHID' => $task1->getPHID(),
|
||||
);
|
||||
$this->moveToColumn($user, $board, $task2, $column, $column, $options);
|
||||
$expect = array(
|
||||
$task1->getPHID(),
|
||||
$task2->getPHID(),
|
||||
);
|
||||
$this->assertTasksInColumn($expect, $user, $board, $column);
|
||||
|
||||
// Move the second task before the first task.
|
||||
$options = array(
|
||||
'beforePHID' => $task1->getPHID(),
|
||||
);
|
||||
$this->moveToColumn($user, $board, $task2, $column, $column, $options);
|
||||
$expect = array(
|
||||
$task2->getPHID(),
|
||||
$task1->getPHID(),
|
||||
);
|
||||
$this->assertTasksInColumn($expect, $user, $board, $column);
|
||||
}
|
||||
|
||||
public function testMilestoneMoves() {
|
||||
$user = $this->createUser();
|
||||
$user->save();
|
||||
|
||||
$board = $this->createProject($user);
|
||||
|
||||
$backlog = $this->addColumn($user, $board, 0);
|
||||
|
||||
// Create a task into the backlog.
|
||||
$task = $this->newTask($user, array($board));
|
||||
$expect = array(
|
||||
$backlog->getPHID(),
|
||||
);
|
||||
$this->assertColumns($expect, $user, $board, $task);
|
||||
|
||||
$milestone = $this->createProject($user, $board, true);
|
||||
|
||||
$this->addProjectTags($user, $task, array($milestone->getPHID()));
|
||||
|
||||
// We just want the side effect of looking at the board: creation of the
|
||||
// milestone column.
|
||||
$this->loadColumns($user, $board, $task);
|
||||
|
||||
$column = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($user)
|
||||
->withProjectPHIDs(array($board->getPHID()))
|
||||
->withProxyPHIDs(array($milestone->getPHID()))
|
||||
->executeOne();
|
||||
|
||||
$this->assertTrue((bool)$column);
|
||||
|
||||
// Moving the task to the milestone should have moved it to the milestone
|
||||
// column.
|
||||
$expect = array(
|
||||
$column->getPHID(),
|
||||
);
|
||||
$this->assertColumns($expect, $user, $board, $task);
|
||||
}
|
||||
|
||||
private function moveToColumn(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorProject $board,
|
||||
ManiphestTask $task,
|
||||
PhabricatorProjectColumn $src,
|
||||
PhabricatorProjectColumn $dst,
|
||||
$options = null) {
|
||||
|
||||
$xactions = array();
|
||||
|
||||
if (!$options) {
|
||||
$options = array();
|
||||
}
|
||||
|
||||
$xactions[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN)
|
||||
->setOldValue(
|
||||
array(
|
||||
'projectPHID' => $board->getPHID(),
|
||||
'columnPHIDs' => array($src->getPHID()),
|
||||
))
|
||||
->setNewValue(
|
||||
array(
|
||||
'projectPHID' => $board->getPHID(),
|
||||
'columnPHIDs' => array($dst->getPHID()),
|
||||
) + $options);
|
||||
|
||||
$editor = id(new ManiphestTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSource(PhabricatorContentSource::newConsoleSource())
|
||||
->setContinueOnNoEffect(true)
|
||||
->applyTransactions($task, $xactions);
|
||||
}
|
||||
|
||||
private function assertColumns(
|
||||
array $expect,
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorProject $board,
|
||||
ManiphestTask $task) {
|
||||
$column_phids = $this->loadColumns($viewer, $board, $task);
|
||||
$this->assertEqual($expect, $column_phids);
|
||||
}
|
||||
|
||||
private function loadColumns(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorProject $board,
|
||||
ManiphestTask $task) {
|
||||
$engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($viewer)
|
||||
->setBoardPHIDs(array($board->getPHID()))
|
||||
->setObjectPHIDs(
|
||||
array(
|
||||
$task->getPHID(),
|
||||
))
|
||||
->executeLayout();
|
||||
|
||||
$columns = $engine->getObjectColumns($board->getPHID(), $task->getPHID());
|
||||
$column_phids = mpull($columns, 'getPHID');
|
||||
$column_phids = array_values($column_phids);
|
||||
|
||||
return $column_phids;
|
||||
}
|
||||
|
||||
private function assertTasksInColumn(
|
||||
array $expect,
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorProject $board,
|
||||
PhabricatorProjectColumn $column) {
|
||||
|
||||
$engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($viewer)
|
||||
->setBoardPHIDs(array($board->getPHID()))
|
||||
->setObjectPHIDs($expect)
|
||||
->executeLayout();
|
||||
|
||||
$object_phids = $engine->getColumnObjectPHIDs(
|
||||
$board->getPHID(),
|
||||
$column->getPHID());
|
||||
$object_phids = array_values($object_phids);
|
||||
|
||||
$this->assertEqual($expect, $object_phids);
|
||||
}
|
||||
|
||||
private function addColumn(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorProject $project,
|
||||
$sequence) {
|
||||
|
||||
$project->setHasWorkboard(1)->save();
|
||||
|
||||
return PhabricatorProjectColumn::initializeNewColumn($viewer)
|
||||
->setSequence(0)
|
||||
->setProperty('isDefault', ($sequence == 0))
|
||||
->setProjectPHID($project->getPHID())
|
||||
->save();
|
||||
}
|
||||
|
||||
private function getTaskProjects(ManiphestTask $task) {
|
||||
$project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$task->getPHID(),
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
|
||||
|
||||
sort($project_phids);
|
||||
|
||||
return $project_phids;
|
||||
}
|
||||
|
||||
private function attemptProjectEdit(
|
||||
PhabricatorProject $proj,
|
||||
PhabricatorUser $user,
|
||||
|
@ -827,6 +1139,30 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
|||
}
|
||||
|
||||
|
||||
private function addProjectTags(
|
||||
PhabricatorUser $viewer,
|
||||
ManiphestTask $task,
|
||||
array $phids) {
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue(
|
||||
'edge:type',
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)
|
||||
->setNewValue(
|
||||
array(
|
||||
'+' => array_fuse($phids),
|
||||
));
|
||||
|
||||
$editor = id(new ManiphestTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSource(PhabricatorContentSource::newConsoleSource())
|
||||
->setContinueOnNoEffect(true)
|
||||
->applyTransactions($task, $xactions);
|
||||
}
|
||||
|
||||
private function newTask(
|
||||
PhabricatorUser $viewer,
|
||||
array $projects,
|
||||
|
@ -945,6 +1281,16 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
|||
|
||||
$this->applyTransactions($project, $user, $xactions);
|
||||
|
||||
// Force these values immediately; they are normally updated by the
|
||||
// index engine.
|
||||
if ($parent) {
|
||||
if ($is_milestone) {
|
||||
$parent->setHasMilestones(1)->save();
|
||||
} else {
|
||||
$parent->setHasSubprojects(1)->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
|
|
|
@ -91,6 +91,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
|||
=> 'PhabricatorProjectWatchController',
|
||||
'silence/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorProjectSilenceController',
|
||||
'warning/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorProjectSubprojectWarningController',
|
||||
),
|
||||
'/tag/' => array(
|
||||
'(?P<slug>[^/]+)/' => 'PhabricatorProjectViewController',
|
||||
|
|
|
@ -97,9 +97,20 @@ final class PhabricatorProjectBoardReorderController
|
|||
->setFlush(true);
|
||||
|
||||
foreach ($columns as $column) {
|
||||
// Don't allow milestone columns to be reordered.
|
||||
$proxy = $column->getProxy();
|
||||
if ($proxy && $proxy->isMilestone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// At least for now, don't show subproject column.
|
||||
if ($proxy) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHeader($column->getDisplayName())
|
||||
->addIcon('none', $column->getDisplayType());
|
||||
->addIcon($column->getHeaderIcon(), $column->getDisplayType());
|
||||
|
||||
if ($column->isHidden()) {
|
||||
$item->setDisabled(true);
|
||||
|
|
|
@ -28,10 +28,103 @@ final class PhabricatorProjectBoardViewController
|
|||
$project = $this->getProject();
|
||||
|
||||
$this->readRequestState();
|
||||
$columns = $this->loadColumns($project);
|
||||
|
||||
// TODO: Expand the checks here if we add the ability
|
||||
// to hide the Backlog column
|
||||
$board_uri = $this->getApplicationURI('board/'.$project->getID().'/');
|
||||
|
||||
$search_engine = id(new ManiphestTaskSearchEngine())
|
||||
->setViewer($viewer)
|
||||
->setBaseURI($board_uri)
|
||||
->setIsBoardView(true);
|
||||
|
||||
if ($request->isFormPost() && !$request->getBool('initialize')) {
|
||||
$saved = $search_engine->buildSavedQueryFromRequest($request);
|
||||
$search_engine->saveQuery($saved);
|
||||
$filter_form = id(new AphrontFormView())
|
||||
->setUser($viewer);
|
||||
$search_engine->buildSearchForm($filter_form, $saved);
|
||||
if ($search_engine->getErrors()) {
|
||||
return $this->newDialog()
|
||||
->setWidth(AphrontDialogView::WIDTH_FULL)
|
||||
->setTitle(pht('Advanced Filter'))
|
||||
->appendChild($filter_form->buildLayoutView())
|
||||
->setErrors($search_engine->getErrors())
|
||||
->setSubmitURI($board_uri)
|
||||
->addSubmitButton(pht('Apply Filter'))
|
||||
->addCancelButton($board_uri);
|
||||
}
|
||||
return id(new AphrontRedirectResponse())->setURI(
|
||||
$this->getURIWithState(
|
||||
$search_engine->getQueryResultsPageURI($saved->getQueryKey())));
|
||||
}
|
||||
|
||||
$query_key = $request->getURIData('queryKey');
|
||||
if (!$query_key) {
|
||||
$query_key = 'open';
|
||||
}
|
||||
$this->queryKey = $query_key;
|
||||
|
||||
$custom_query = null;
|
||||
if ($search_engine->isBuiltinQuery($query_key)) {
|
||||
$saved = $search_engine->buildSavedQueryFromBuiltin($query_key);
|
||||
} else {
|
||||
$saved = id(new PhabricatorSavedQueryQuery())
|
||||
->setViewer($viewer)
|
||||
->withQueryKeys(array($query_key))
|
||||
->executeOne();
|
||||
|
||||
if (!$saved) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$custom_query = $saved;
|
||||
}
|
||||
|
||||
if ($request->getURIData('filter')) {
|
||||
$filter_form = id(new AphrontFormView())
|
||||
->setUser($viewer);
|
||||
$search_engine->buildSearchForm($filter_form, $saved);
|
||||
|
||||
return $this->newDialog()
|
||||
->setWidth(AphrontDialogView::WIDTH_FULL)
|
||||
->setTitle(pht('Advanced Filter'))
|
||||
->appendChild($filter_form->buildLayoutView())
|
||||
->setSubmitURI($board_uri)
|
||||
->addSubmitButton(pht('Apply Filter'))
|
||||
->addCancelButton($board_uri);
|
||||
}
|
||||
|
||||
$task_query = $search_engine->buildQueryFromSavedQuery($saved);
|
||||
|
||||
$select_phids = array($project->getPHID());
|
||||
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
||||
$descendants = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs($select_phids)
|
||||
->execute();
|
||||
foreach ($descendants as $descendant) {
|
||||
$select_phids[] = $descendant->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$tasks = $task_query
|
||||
->withEdgeLogicPHIDs(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
|
||||
array($select_phids))
|
||||
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
|
||||
->setViewer($viewer)
|
||||
->execute();
|
||||
$tasks = mpull($tasks, null, 'getPHID');
|
||||
|
||||
$board_phid = $project->getPHID();
|
||||
|
||||
$layout_engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($viewer)
|
||||
->setBoardPHIDs(array($board_phid))
|
||||
->setObjectPHIDs(array_keys($tasks))
|
||||
->executeLayout();
|
||||
|
||||
$columns = $layout_engine->getColumns($board_phid);
|
||||
if (!$columns) {
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
|
@ -56,132 +149,14 @@ final class PhabricatorProjectBoardViewController
|
|||
return $this->newPage()
|
||||
->setTitle(
|
||||
array(
|
||||
$project->getDisplayName(),
|
||||
pht('Workboard'),
|
||||
$project->getName(),
|
||||
))
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($content);
|
||||
}
|
||||
|
||||
$board_uri = $this->getApplicationURI('board/'.$project->getID().'/');
|
||||
|
||||
$engine = id(new ManiphestTaskSearchEngine())
|
||||
->setViewer($viewer)
|
||||
->setBaseURI($board_uri)
|
||||
->setIsBoardView(true);
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$saved = $engine->buildSavedQueryFromRequest($request);
|
||||
$engine->saveQuery($saved);
|
||||
$filter_form = id(new AphrontFormView())
|
||||
->setUser($viewer);
|
||||
$engine->buildSearchForm($filter_form, $saved);
|
||||
if ($engine->getErrors()) {
|
||||
return $this->newDialog()
|
||||
->setWidth(AphrontDialogView::WIDTH_FULL)
|
||||
->setTitle(pht('Advanced Filter'))
|
||||
->appendChild($filter_form->buildLayoutView())
|
||||
->setErrors($engine->getErrors())
|
||||
->setSubmitURI($board_uri)
|
||||
->addSubmitButton(pht('Apply Filter'))
|
||||
->addCancelButton($board_uri);
|
||||
}
|
||||
return id(new AphrontRedirectResponse())->setURI(
|
||||
$this->getURIWithState(
|
||||
$engine->getQueryResultsPageURI($saved->getQueryKey())));
|
||||
}
|
||||
|
||||
$query_key = $request->getURIData('queryKey');
|
||||
if (!$query_key) {
|
||||
$query_key = 'open';
|
||||
}
|
||||
$this->queryKey = $query_key;
|
||||
|
||||
$custom_query = null;
|
||||
if ($engine->isBuiltinQuery($query_key)) {
|
||||
$saved = $engine->buildSavedQueryFromBuiltin($query_key);
|
||||
} else {
|
||||
$saved = id(new PhabricatorSavedQueryQuery())
|
||||
->setViewer($viewer)
|
||||
->withQueryKeys(array($query_key))
|
||||
->executeOne();
|
||||
|
||||
if (!$saved) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$custom_query = $saved;
|
||||
}
|
||||
|
||||
if ($request->getURIData('filter')) {
|
||||
$filter_form = id(new AphrontFormView())
|
||||
->setUser($viewer);
|
||||
$engine->buildSearchForm($filter_form, $saved);
|
||||
|
||||
return $this->newDialog()
|
||||
->setWidth(AphrontDialogView::WIDTH_FULL)
|
||||
->setTitle(pht('Advanced Filter'))
|
||||
->appendChild($filter_form->buildLayoutView())
|
||||
->setSubmitURI($board_uri)
|
||||
->addSubmitButton(pht('Apply Filter'))
|
||||
->addCancelButton($board_uri);
|
||||
}
|
||||
|
||||
$task_query = $engine->buildQueryFromSavedQuery($saved);
|
||||
|
||||
$tasks = $task_query
|
||||
->withEdgeLogicPHIDs(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
PhabricatorQueryConstraint::OPERATOR_AND,
|
||||
array($project->getPHID()))
|
||||
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
|
||||
->setViewer($viewer)
|
||||
->execute();
|
||||
$tasks = mpull($tasks, null, 'getPHID');
|
||||
|
||||
if ($tasks) {
|
||||
$positions = id(new PhabricatorProjectColumnPositionQuery())
|
||||
->setViewer($viewer)
|
||||
->withObjectPHIDs(mpull($tasks, 'getPHID'))
|
||||
->withColumns($columns)
|
||||
->execute();
|
||||
$positions = mpull($positions, null, 'getObjectPHID');
|
||||
} else {
|
||||
$positions = array();
|
||||
}
|
||||
|
||||
$task_map = array();
|
||||
foreach ($tasks as $task) {
|
||||
$task_phid = $task->getPHID();
|
||||
if (empty($positions[$task_phid])) {
|
||||
// This shouldn't normally be possible because we create positions on
|
||||
// demand, but we might have raced as an object was removed from the
|
||||
// board. Just drop the task if we don't have a position for it.
|
||||
continue;
|
||||
}
|
||||
|
||||
$position = $positions[$task_phid];
|
||||
$task_map[$position->getColumnPHID()][] = $task_phid;
|
||||
}
|
||||
|
||||
// If we're showing the board in "natural" order, sort columns by their
|
||||
// column positions.
|
||||
if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) {
|
||||
foreach ($task_map as $column_phid => $task_phids) {
|
||||
$order = array();
|
||||
foreach ($task_phids as $task_phid) {
|
||||
if (isset($positions[$task_phid])) {
|
||||
$order[$task_phid] = $positions[$task_phid]->getOrderingKey();
|
||||
} else {
|
||||
$order[$task_phid] = 0;
|
||||
}
|
||||
}
|
||||
asort($order);
|
||||
$task_map[$column_phid] = array_keys($order);
|
||||
}
|
||||
}
|
||||
|
||||
$task_can_edit_map = id(new PhabricatorPolicyFilter())
|
||||
->setViewer($viewer)
|
||||
->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
|
||||
|
@ -198,7 +173,10 @@ final class PhabricatorProjectBoardViewController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$batch_task_phids = idx($task_map, $batch_column->getPHID(), array());
|
||||
$batch_task_phids = $layout_engine->getColumnObjectPHIDs(
|
||||
$board_phid,
|
||||
$batch_column->getPHID());
|
||||
|
||||
foreach ($batch_task_phids as $key => $batch_task_phid) {
|
||||
if (empty($task_can_edit_map[$batch_task_phid])) {
|
||||
unset($batch_task_phids[$key]);
|
||||
|
@ -250,10 +228,46 @@ final class PhabricatorProjectBoardViewController
|
|||
|
||||
$this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
|
||||
|
||||
$all_project_phids = array();
|
||||
foreach ($tasks as $task) {
|
||||
foreach ($task->getProjectPHIDs() as $project_phid) {
|
||||
$all_project_phids[$project_phid] = $project_phid;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($select_phids as $phid) {
|
||||
unset($all_project_phids[$phid]);
|
||||
}
|
||||
|
||||
$all_handles = $viewer->loadHandles($all_project_phids);
|
||||
$all_handles = iterator_to_array($all_handles);
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$task_phids = idx($task_map, $column->getPHID(), array());
|
||||
if (!$this->showHidden) {
|
||||
if ($column->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$proxy = $column->getProxy();
|
||||
if ($proxy && !$proxy->isMilestone()) {
|
||||
// TODO: For now, don't show subproject columns because we can't
|
||||
// handle tasks with multiple positions yet.
|
||||
continue;
|
||||
}
|
||||
|
||||
$task_phids = $layout_engine->getColumnObjectPHIDs(
|
||||
$board_phid,
|
||||
$column->getPHID());
|
||||
|
||||
$column_tasks = array_select_keys($tasks, $task_phids);
|
||||
|
||||
// If we aren't using "natural" order, reorder the column by the original
|
||||
// query order.
|
||||
if ($this->sortKey != PhabricatorProjectColumn::ORDER_NATURAL) {
|
||||
$column_tasks = array_select_keys($column_tasks, array_keys($tasks));
|
||||
}
|
||||
|
||||
$panel = id(new PHUIWorkpanelView())
|
||||
->setHeader($column->getDisplayName())
|
||||
->setSubHeader($column->getDisplayType())
|
||||
|
@ -264,6 +278,11 @@ final class PhabricatorProjectBoardViewController
|
|||
$panel->setHeaderIcon($header_icon);
|
||||
}
|
||||
|
||||
$display_class = $column->getDisplayClass();
|
||||
if ($display_class) {
|
||||
$panel->addClass($display_class);
|
||||
}
|
||||
|
||||
if ($column->isHidden()) {
|
||||
$panel->addClass('project-panel-hidden');
|
||||
}
|
||||
|
@ -288,6 +307,7 @@ final class PhabricatorProjectBoardViewController
|
|||
->setFlush(true)
|
||||
->setAllowEmptyList(true)
|
||||
->addSigil('project-column')
|
||||
->setItemClass('phui-workcard')
|
||||
->setMetadata(
|
||||
array(
|
||||
'columnPHID' => $column->getPHID(),
|
||||
|
@ -302,8 +322,12 @@ final class PhabricatorProjectBoardViewController
|
|||
$owner = $this->handles[$task->getOwnerPHID()];
|
||||
}
|
||||
$can_edit = idx($task_can_edit_map, $task->getPHID(), false);
|
||||
|
||||
$handles = array_select_keys($all_handles, $task->getProjectPHIDs());
|
||||
|
||||
$cards->addItem(id(new ProjectBoardTaskCard())
|
||||
->setViewer($viewer)
|
||||
->setProjectHandles($handles)
|
||||
->setTask($task)
|
||||
->setOwner($owner)
|
||||
->setCanEdit($can_edit)
|
||||
|
@ -320,7 +344,7 @@ final class PhabricatorProjectBoardViewController
|
|||
$filter_menu = $this->buildFilterMenu(
|
||||
$viewer,
|
||||
$custom_query,
|
||||
$engine,
|
||||
$search_engine,
|
||||
$query_key);
|
||||
|
||||
$manage_menu = $this->buildManageMenu($project, $this->showHidden);
|
||||
|
@ -347,7 +371,11 @@ final class PhabricatorProjectBoardViewController
|
|||
$crumbs->addAction($manage_menu);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(pht('%s Board', $project->getName()))
|
||||
->setTitle(
|
||||
array(
|
||||
$project->getDisplayName(),
|
||||
pht('Workboard'),
|
||||
))
|
||||
->setPageObjectPHIDs(array($project->getPHID()))
|
||||
->setShowFooter(false)
|
||||
->setNavigation($nav)
|
||||
|
@ -381,25 +409,6 @@ final class PhabricatorProjectBoardViewController
|
|||
$this->sortKey = $sort_key;
|
||||
}
|
||||
|
||||
private function loadColumns(PhabricatorProject $project) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$column_query = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($viewer)
|
||||
->withProjectPHIDs(array($project->getPHID()));
|
||||
|
||||
if (!$this->showHidden) {
|
||||
$column_query->withStatuses(
|
||||
array(PhabricatorProjectColumn::STATUS_ACTIVE));
|
||||
}
|
||||
|
||||
$columns = $column_query->execute();
|
||||
$columns = mpull($columns, null, 'getSequence');
|
||||
ksort($columns);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
private function buildSortMenu(
|
||||
PhabricatorUser $viewer,
|
||||
$sort_key) {
|
||||
|
@ -616,6 +625,12 @@ final class PhabricatorProjectBoardViewController
|
|||
|
||||
$column_items = array();
|
||||
|
||||
if ($column->getProxyPHID()) {
|
||||
$default_phid = $column->getProxyPHID();
|
||||
} else {
|
||||
$default_phid = $column->getProjectPHID();
|
||||
}
|
||||
|
||||
$column_items[] = id(new PhabricatorActionView())
|
||||
->setIcon('fa-plus')
|
||||
->setName(pht('Create Task...'))
|
||||
|
@ -624,6 +639,7 @@ final class PhabricatorProjectBoardViewController
|
|||
->setMetadata(
|
||||
array(
|
||||
'columnPHID' => $column->getPHID(),
|
||||
'projectPHID' => $default_phid,
|
||||
));
|
||||
|
||||
$batch_edit_uri = $request->getRequestURI();
|
||||
|
@ -772,6 +788,10 @@ final class PhabricatorProjectBoardViewController
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Tailor this UI if the project is already a parent project. We
|
||||
// should not offer options for creating a parent project workboard, since
|
||||
// they can't have their own columns.
|
||||
|
||||
$new_selector = id(new AphrontFormRadioButtonControl())
|
||||
->setLabel(pht('Columns'))
|
||||
->setName('initialize-type')
|
||||
|
@ -795,6 +815,7 @@ final class PhabricatorProjectBoardViewController
|
|||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->addHiddenInput('initialize', 1)
|
||||
->appendRemarkupInstructions(
|
||||
pht('The workboard for this project has not been created yet.'))
|
||||
->appendControl($new_selector)
|
||||
|
|
|
@ -22,6 +22,8 @@ final class PhabricatorProjectColumnDetailController
|
|||
}
|
||||
$this->setProject($project);
|
||||
|
||||
$project_id = $project->getID();
|
||||
|
||||
$column = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
|
@ -45,6 +47,10 @@ final class PhabricatorProjectColumnDetailController
|
|||
$actions = $this->buildActionView($column);
|
||||
$properties = $this->buildPropertyView($column, $actions);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$project_id}/");
|
||||
$crumbs->addTextCrumb(pht('Column: %s', $title));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->addPropertyList($properties);
|
||||
|
@ -54,6 +60,7 @@ final class PhabricatorProjectColumnDetailController
|
|||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$box,
|
||||
|
|
|
@ -81,10 +81,12 @@ final class PhabricatorProjectColumnEditController
|
|||
|
||||
$xactions = array();
|
||||
|
||||
$type_name = PhabricatorProjectColumnTransaction::TYPE_NAME;
|
||||
$xactions[] = id(new PhabricatorProjectColumnTransaction())
|
||||
->setTransactionType($type_name)
|
||||
->setNewValue($v_name);
|
||||
if (!$column->getProxy()) {
|
||||
$type_name = PhabricatorProjectColumnTransaction::TYPE_NAME;
|
||||
$xactions[] = id(new PhabricatorProjectColumnTransaction())
|
||||
->setTransactionType($type_name)
|
||||
->setNewValue($v_name);
|
||||
}
|
||||
|
||||
$type_limit = PhabricatorProjectColumnTransaction::TYPE_LIMIT;
|
||||
$xactions[] = id(new PhabricatorProjectColumnTransaction())
|
||||
|
@ -105,26 +107,26 @@ final class PhabricatorProjectColumnEditController
|
|||
}
|
||||
}
|
||||
|
||||
$form = new AphrontFormView();
|
||||
$form
|
||||
->setUser($request->getUser())
|
||||
->appendChild(
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($request->getUser());
|
||||
|
||||
if (!$column->getProxy()) {
|
||||
$form->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setValue($v_name)
|
||||
->setLabel(pht('Name'))
|
||||
->setName('name')
|
||||
->setError($e_name)
|
||||
->setCaption(
|
||||
pht('This will be displayed as the header of the column.')))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setValue($v_limit)
|
||||
->setLabel(pht('Point Limit'))
|
||||
->setName('limit')
|
||||
->setError($e_limit)
|
||||
->setCaption(
|
||||
pht('Maximum number of points of tasks allowed in the column.')));
|
||||
->setError($e_name));
|
||||
}
|
||||
|
||||
$form->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setValue($v_limit)
|
||||
->setLabel(pht('Point Limit'))
|
||||
->setName('limit')
|
||||
->setError($e_limit)
|
||||
->setCaption(
|
||||
pht('Maximum number of points of tasks allowed in the column.')));
|
||||
|
||||
if ($is_new) {
|
||||
$title = pht('Create Column');
|
||||
|
|
|
@ -42,6 +42,7 @@ final class PhabricatorProjectEditController
|
|||
if ($parent_id) {
|
||||
$query = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->needImages(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
|
@ -58,7 +59,7 @@ final class PhabricatorProjectEditController
|
|||
|
||||
if ($is_milestone) {
|
||||
if (!$parent->supportsMilestones()) {
|
||||
$cancel_uri = "/project/milestones/{$parent_id}/";
|
||||
$cancel_uri = "/project/subprojects/{$parent_id}/";
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('No Milestones'))
|
||||
->appendParagraph(
|
||||
|
@ -91,20 +92,13 @@ final class PhabricatorProjectEditController
|
|||
$engine = $this->getEngine();
|
||||
if ($engine) {
|
||||
$parent = $engine->getParentProject();
|
||||
if ($parent) {
|
||||
$id = $parent->getID();
|
||||
$milestone = $engine->getMilestoneProject();
|
||||
if ($parent || $milestone) {
|
||||
$id = nonempty($parent, $milestone)->getID();
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Subprojects'),
|
||||
$this->getApplicationURI("subprojects/{$id}/"));
|
||||
}
|
||||
|
||||
$milestone = $engine->getMilestoneProject();
|
||||
if ($milestone) {
|
||||
$id = $milestone->getID();
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Milestones'),
|
||||
$this->getApplicationURI("milestones/{$id}/"));
|
||||
}
|
||||
}
|
||||
|
||||
return $crumbs;
|
||||
|
|
|
@ -51,7 +51,11 @@ final class PhabricatorProjectManageController
|
|||
return $this->newPage()
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->setTitle($project->getName())
|
||||
->setTitle(
|
||||
array(
|
||||
$project->getDisplayName(),
|
||||
pht('Manage'),
|
||||
))
|
||||
->appendChild(
|
||||
array(
|
||||
$object_box,
|
||||
|
|
|
@ -48,7 +48,7 @@ final class PhabricatorProjectMembersViewController
|
|||
return $this->newPage()
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->setTitle(array($project->getName(), $title))
|
||||
->setTitle(array($project->getDisplayName(), $title))
|
||||
->appendChild(
|
||||
array(
|
||||
$object_box,
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectMilestonesController
|
||||
extends PhabricatorProjectController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$response = $this->loadProject();
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$project = $this->getProject();
|
||||
$id = $project->getID();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$has_support = $project->supportsMilestones();
|
||||
if ($has_support) {
|
||||
$milestones = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withParentProjectPHIDs(array($project->getPHID()))
|
||||
->needImages(true)
|
||||
->withIsMilestone(true)
|
||||
->setOrder('newest')
|
||||
->execute();
|
||||
} else {
|
||||
$milestones = array();
|
||||
}
|
||||
|
||||
$can_create = $can_edit && $has_support;
|
||||
|
||||
if ($project->getHasMilestones()) {
|
||||
$button_text = pht('Create Next Milestone');
|
||||
} else {
|
||||
$button_text = pht('Add Milestones');
|
||||
}
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Milestones'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref("/project/edit/?milestone={$id}")
|
||||
->setIcon('fa-plus')
|
||||
->setDisabled(!$can_create)
|
||||
->setWorkflow(!$can_create)
|
||||
->setText($button_text));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header);
|
||||
|
||||
if (!$has_support) {
|
||||
$no_support = pht(
|
||||
'This project is a milestone. Milestones can not have their own '.
|
||||
'milestones.');
|
||||
|
||||
$info_view = id(new PHUIInfoView())
|
||||
->setErrors(array($no_support))
|
||||
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
|
||||
|
||||
$box->setInfoView($info_view);
|
||||
}
|
||||
|
||||
$box->setObjectList(
|
||||
id(new PhabricatorProjectListView())
|
||||
->setUser($viewer)
|
||||
->setProjects($milestones)
|
||||
->renderList());
|
||||
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_MILESTONES);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Milestones'));
|
||||
|
||||
return $this->newPage()
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->setTitle(array($project->getName(), pht('Milestones')))
|
||||
->appendChild($box);
|
||||
}
|
||||
|
||||
}
|
|
@ -26,9 +26,12 @@ final class PhabricatorProjectMoveController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
$board_phid = $project->getPHID();
|
||||
|
||||
$object = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->needProjectPHIDs(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
|
@ -53,11 +56,14 @@ final class PhabricatorProjectMoveController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$positions = id(new PhabricatorProjectColumnPositionQuery())
|
||||
$engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($viewer)
|
||||
->withColumns($columns)
|
||||
->withObjectPHIDs(array($object_phid))
|
||||
->execute();
|
||||
->setBoardPHIDs(array($board_phid))
|
||||
->setObjectPHIDs(array($object_phid))
|
||||
->executeLayout();
|
||||
|
||||
$columns = $engine->getObjectColumns($board_phid, $object_phid);
|
||||
$old_column_phids = mpull($columns, 'getPHID');
|
||||
|
||||
$xactions = array();
|
||||
|
||||
|
@ -79,7 +85,7 @@ final class PhabricatorProjectMoveController
|
|||
) + $order_params)
|
||||
->setOldValue(
|
||||
array(
|
||||
'columnPHIDs' => mpull($positions, 'getColumnPHID'),
|
||||
'columnPHIDs' => $old_column_phids,
|
||||
'projectPHID' => $column->getProjectPHID(),
|
||||
));
|
||||
|
||||
|
@ -95,6 +101,7 @@ final class PhabricatorProjectMoveController
|
|||
$tasks = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($task_phids)
|
||||
->needProjectPHIDs(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
|
@ -132,7 +139,33 @@ final class PhabricatorProjectMoveController
|
|||
->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
|
||||
->setNewValue($sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$proxy = $column->getProxy();
|
||||
if ($proxy) {
|
||||
// We're moving the task into a subproject or milestone column, so add
|
||||
// the subproject or milestone.
|
||||
$add_projects = array($proxy->getPHID());
|
||||
} else if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
||||
// We're moving the task into the "Backlog" column on the parent project,
|
||||
// so add the parent explicitly. This gets rid of any subproject or
|
||||
// milestone tags.
|
||||
$add_projects = array($project->getPHID());
|
||||
} else {
|
||||
$add_projects = array();
|
||||
}
|
||||
|
||||
if ($add_projects) {
|
||||
$project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
|
||||
|
||||
$xactions[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $project_type)
|
||||
->setNewValue(
|
||||
array(
|
||||
'+' => array_fuse($add_projects),
|
||||
));
|
||||
}
|
||||
|
||||
$editor = id(new ManiphestTransactionEditor())
|
||||
->setActor($viewer)
|
||||
|
@ -149,15 +182,49 @@ final class PhabricatorProjectMoveController
|
|||
->withPHIDs(array($object->getOwnerPHID()))
|
||||
->executeOne();
|
||||
}
|
||||
|
||||
// Reload the object so it reflects edits which have been applied.
|
||||
$object = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->needProjectPHIDs(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
|
||||
$except_phids = array($board_phid);
|
||||
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
||||
$descendants = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs($except_phids)
|
||||
->execute();
|
||||
foreach ($descendants as $descendant) {
|
||||
$except_phids[] = $descendant->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$except_phids = array_fuse($except_phids);
|
||||
$handle_phids = array_fuse($object->getProjectPHIDs());
|
||||
$handle_phids = array_diff_key($handle_phids, $except_phids);
|
||||
|
||||
$project_handles = $viewer->loadHandles($handle_phids);
|
||||
$project_handles = iterator_to_array($project_handles);
|
||||
|
||||
$card = id(new ProjectBoardTaskCard())
|
||||
->setViewer($viewer)
|
||||
->setTask($object)
|
||||
->setOwner($owner)
|
||||
->setCanEdit(true)
|
||||
->setProjectHandles($project_handles)
|
||||
->getItem();
|
||||
|
||||
$card->addClass('phui-workcard');
|
||||
|
||||
return id(new AphrontAjaxResponse())->setContent(
|
||||
array('task' => $card));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,9 +17,16 @@ final class PhabricatorProjectProfileController
|
|||
$project = $this->getProject();
|
||||
$id = $project->getID();
|
||||
$picture = $project->getProfileImageURI();
|
||||
$icon = $project->getDisplayIconIcon();
|
||||
$icon_name = $project->getDisplayIconName();
|
||||
$tag = id(new PHUITagView())
|
||||
->setIcon($icon)
|
||||
->setName($icon_name)
|
||||
->addClass('project-view-header-tag')
|
||||
->setType(PHUITagView::TYPE_SHADE);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($project->getName())
|
||||
->setHeader(array($project->getDisplayName(), $tag))
|
||||
->setUser($viewer)
|
||||
->setPolicyObject($project)
|
||||
->setImage($picture)
|
||||
|
@ -45,6 +52,9 @@ final class PhabricatorProjectProfileController
|
|||
$watch_action = $this->renderWatchAction($project);
|
||||
$header->addActionLink($watch_action);
|
||||
|
||||
$milestone_list = $this->buildMilestoneList($project);
|
||||
$subproject_list = $this->buildSubprojectList($project);
|
||||
|
||||
$member_list = id(new PhabricatorProjectMemberListView())
|
||||
->setUser($viewer)
|
||||
->setProject($project)
|
||||
|
@ -82,6 +92,8 @@ final class PhabricatorProjectProfileController
|
|||
))
|
||||
->setSideColumn(
|
||||
array(
|
||||
$milestone_list,
|
||||
$subproject_list,
|
||||
$member_list,
|
||||
$watcher_list,
|
||||
));
|
||||
|
@ -103,7 +115,7 @@ final class PhabricatorProjectProfileController
|
|||
return $this->newPage()
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->setTitle($project->getName())
|
||||
->setTitle($project->getDisplayName())
|
||||
->setPageObjectPHIDs(array($project->getPHID()))
|
||||
->appendChild(
|
||||
array(
|
||||
|
@ -125,7 +137,7 @@ final class PhabricatorProjectProfileController
|
|||
PhabricatorCustomField::ROLE_VIEW);
|
||||
$field_list->appendFieldsToPropertyList($project, $viewer, $view);
|
||||
|
||||
if ($view->isEmpty()) {
|
||||
if (!$view->hasAnyProperties()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -176,5 +188,90 @@ final class PhabricatorProjectProfileController
|
|||
->setHref($watch_href);
|
||||
}
|
||||
|
||||
private function buildMilestoneList(PhabricatorProject $project) {
|
||||
if (!$project->getHasMilestones()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$id = $project->getID();
|
||||
|
||||
$milestones = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withParentProjectPHIDs(array($project->getPHID()))
|
||||
->needImages(true)
|
||||
->withIsMilestone(true)
|
||||
->setOrder('newest')
|
||||
->execute();
|
||||
if (!$milestones) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$milestone_list = id(new PhabricatorProjectListView())
|
||||
->setUser($viewer)
|
||||
->setProjects($milestones)
|
||||
->renderList();
|
||||
|
||||
$view_all = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon(
|
||||
id(new PHUIIconView())
|
||||
->setIcon('fa-list-ul'))
|
||||
->setText(pht('View All'))
|
||||
->setHref("/project/subprojects/{$id}/");
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Milestones'))
|
||||
->addActionLink($view_all);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIBoxView::GREY)
|
||||
->setObjectList($milestone_list);
|
||||
}
|
||||
|
||||
private function buildSubprojectList(PhabricatorProject $project) {
|
||||
if (!$project->getHasSubprojects()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$id = $project->getID();
|
||||
|
||||
$limit = 25;
|
||||
|
||||
$subprojects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withParentProjectPHIDs(array($project->getPHID()))
|
||||
->needImages(true)
|
||||
->withIsMilestone(false)
|
||||
->setLimit($limit)
|
||||
->execute();
|
||||
if (!$subprojects) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$subproject_list = id(new PhabricatorProjectListView())
|
||||
->setUser($viewer)
|
||||
->setProjects($subprojects)
|
||||
->renderList();
|
||||
|
||||
$view_all = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon(
|
||||
id(new PHUIIconView())
|
||||
->setIcon('fa-list-ul'))
|
||||
->setText(pht('View All'))
|
||||
->setHref("/project/subprojects/{$id}/");
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Subprojects'))
|
||||
->addActionLink($view_all);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIBoxView::GREY)
|
||||
->setObjectList($subproject_list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectSubprojectWarningController
|
||||
extends PhabricatorProjectController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$response = $this->loadProject();
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$project = $this->getProject();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
if (!$can_edit) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$id = $project->getID();
|
||||
$cancel_uri = "/project/subprojects/{$id}/";
|
||||
$done_uri = "/project/edit/?parent={$id}";
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($done_uri);
|
||||
}
|
||||
|
||||
$doc_href = PhabricatorEnv::getDoclink('Projects User Guide');
|
||||
|
||||
$conversion_help = pht(
|
||||
"Creating a project's first subproject **moves all ".
|
||||
"members** to become members of the subproject instead".
|
||||
"\n\n".
|
||||
"See [[ %s | Projects User Guide ]] in the documentation for details. ".
|
||||
"This process can not be undone.",
|
||||
$doc_href);
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Convert to Parent Project'))
|
||||
->appendChild(new PHUIRemarkupView($viewer, $conversion_help))
|
||||
->addCancelButton($cancel_uri)
|
||||
->addSubmitButton(pht('Convert Project'));
|
||||
}
|
||||
|
||||
}
|
|
@ -23,9 +23,10 @@ final class PhabricatorProjectSubprojectsController
|
|||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$has_support = $project->supportsSubprojects();
|
||||
$allows_subprojects = $project->supportsSubprojects();
|
||||
$allows_milestones = $project->supportsMilestones();
|
||||
|
||||
if ($has_support) {
|
||||
if ($allows_subprojects) {
|
||||
$subprojects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withParentProjectPHIDs(array($project->getPHID()))
|
||||
|
@ -36,44 +37,57 @@ final class PhabricatorProjectSubprojectsController
|
|||
$subprojects = array();
|
||||
}
|
||||
|
||||
$can_create = $can_edit && $has_support;
|
||||
|
||||
if ($project->getHasSubprojects()) {
|
||||
$button_text = pht('Create Subproject');
|
||||
if ($allows_milestones) {
|
||||
$milestones = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withParentProjectPHIDs(array($project->getPHID()))
|
||||
->needImages(true)
|
||||
->withIsMilestone(true)
|
||||
->setOrder('newest')
|
||||
->execute();
|
||||
} else {
|
||||
$button_text = pht('Add Subprojects');
|
||||
$milestones = array();
|
||||
}
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Subprojects'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref("/project/edit/?parent={$id}")
|
||||
->setIcon('fa-plus')
|
||||
->setDisabled(!$can_create)
|
||||
->setWorkflow(!$can_create)
|
||||
->setText($button_text));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header);
|
||||
|
||||
if (!$has_support) {
|
||||
$no_support = pht(
|
||||
'This project is a milestone. Milestones can not have subprojects.');
|
||||
|
||||
$info_view = id(new PHUIInfoView())
|
||||
->setErrors(array($no_support))
|
||||
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
|
||||
|
||||
$box->setInfoView($info_view);
|
||||
if ($milestones) {
|
||||
$milestone_list = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Milestones'))
|
||||
->setObjectList(
|
||||
id(new PhabricatorProjectListView())
|
||||
->setUser($viewer)
|
||||
->setProjects($milestones)
|
||||
->renderList());
|
||||
} else {
|
||||
$milestone_list = null;
|
||||
}
|
||||
|
||||
$box->setObjectList(
|
||||
id(new PhabricatorProjectListView())
|
||||
->setUser($viewer)
|
||||
->setProjects($subprojects)
|
||||
->renderList());
|
||||
if ($subprojects) {
|
||||
$subproject_list = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Subprojects'))
|
||||
->setObjectList(
|
||||
id(new PhabricatorProjectListView())
|
||||
->setUser($viewer)
|
||||
->setProjects($subprojects)
|
||||
->renderList());
|
||||
} else {
|
||||
$subproject_list = null;
|
||||
}
|
||||
|
||||
$property_list = $this->buildPropertyList(
|
||||
$project,
|
||||
$milestones,
|
||||
$subprojects);
|
||||
|
||||
$action_list = $this->buildActionList(
|
||||
$project,
|
||||
$milestones,
|
||||
$subprojects);
|
||||
|
||||
$property_list->setActionList($action_list);
|
||||
|
||||
$header_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Subprojects and Milestones'))
|
||||
->addPropertyList($property_list);
|
||||
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_SUBPROJECTS);
|
||||
|
@ -85,7 +99,151 @@ final class PhabricatorProjectSubprojectsController
|
|||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->setTitle(array($project->getName(), pht('Subprojects')))
|
||||
->appendChild($box);
|
||||
->appendChild(
|
||||
array(
|
||||
$header_box,
|
||||
$milestone_list,
|
||||
$subproject_list,
|
||||
));
|
||||
}
|
||||
|
||||
private function buildPropertyList(
|
||||
PhabricatorProject $project,
|
||||
array $milestones,
|
||||
array $subprojects) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = id(new PHUIPropertyListView())
|
||||
->setUser($viewer);
|
||||
|
||||
$view->addProperty(
|
||||
pht('Prototype'),
|
||||
$this->renderStatus(
|
||||
'fa-exclamation-triangle red',
|
||||
pht('Warning'),
|
||||
pht('Subprojects and milestones are only partially implemented.')));
|
||||
|
||||
if (!$project->supportsMilestones()) {
|
||||
$milestone_status = $this->renderStatus(
|
||||
'fa-times grey',
|
||||
pht('Already Milestone'),
|
||||
pht(
|
||||
'This project is already a milestone, and milestones may not '.
|
||||
'have their own milestones.'));
|
||||
} else {
|
||||
if (!$milestones) {
|
||||
$milestone_status = $this->renderStatus(
|
||||
'fa-check grey',
|
||||
pht('None Created'),
|
||||
pht(
|
||||
'You can create milestones for this project.'));
|
||||
} else {
|
||||
$milestone_status = $this->renderStatus(
|
||||
'fa-check green',
|
||||
pht('Has Milestones'),
|
||||
pht('This project has milestones.'));
|
||||
}
|
||||
}
|
||||
|
||||
$view->addProperty(pht('Milestones'), $milestone_status);
|
||||
|
||||
if (!$project->supportsSubprojects()) {
|
||||
$subproject_status = $this->renderStatus(
|
||||
'fa-times grey',
|
||||
pht('Milestone'),
|
||||
pht(
|
||||
'This project is a milestone, and milestones may not have '.
|
||||
'subprojects.'));
|
||||
} else {
|
||||
if (!$subprojects) {
|
||||
$subproject_status = $this->renderStatus(
|
||||
'fa-check grey',
|
||||
pht('None Created'),
|
||||
pht('You can create subprojects for this project.'));
|
||||
} else {
|
||||
$subproject_status = $this->renderStatus(
|
||||
'fa-check green',
|
||||
pht('Has Subprojects'),
|
||||
pht(
|
||||
'This project has subprojects.'));
|
||||
}
|
||||
}
|
||||
|
||||
$view->addProperty(pht('Subprojects'), $subproject_status);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildActionList(
|
||||
PhabricatorProject $project,
|
||||
array $milestones,
|
||||
array $subprojects) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $project->getID();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$allows_milestones = $project->supportsMilestones();
|
||||
$allows_subprojects = $project->supportsSubprojects();
|
||||
|
||||
$view = id(new PhabricatorActionListView())
|
||||
->setUser($viewer);
|
||||
|
||||
if ($allows_milestones && $milestones) {
|
||||
$milestone_text = pht('Create Next Milestone');
|
||||
} else {
|
||||
$milestone_text = pht('Create Milestone');
|
||||
}
|
||||
|
||||
$can_milestone = ($can_edit && $allows_milestones);
|
||||
$milestone_href = "/project/edit/?milestone={$id}";
|
||||
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName($milestone_text)
|
||||
->setIcon('fa-plus')
|
||||
->setHref($milestone_href)
|
||||
->setDisabled(!$can_milestone)
|
||||
->setWorkflow(!$can_milestone));
|
||||
|
||||
$can_subproject = ($can_edit && $allows_subprojects);
|
||||
|
||||
// If we're offering to create the first subproject, we're going to warn
|
||||
// the user about the effects before moving forward.
|
||||
if ($can_subproject && !$subprojects) {
|
||||
$subproject_href = "/project/warning/{$id}/";
|
||||
$subproject_disabled = false;
|
||||
$subproject_workflow = true;
|
||||
} else {
|
||||
$subproject_href = "/project/edit/?parent={$id}";
|
||||
$subproject_disabled = !$can_subproject;
|
||||
$subproject_workflow = !$can_subproject;
|
||||
}
|
||||
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Create Subproject'))
|
||||
->setIcon('fa-plus')
|
||||
->setHref($subproject_href)
|
||||
->setDisabled($subproject_disabled)
|
||||
->setWorkflow($subproject_workflow));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function renderStatus($icon, $target, $note) {
|
||||
$item = id(new PHUIStatusItemView())
|
||||
->setIcon($icon)
|
||||
->setTarget(phutil_tag('strong', array(), $target))
|
||||
->setNote($note);
|
||||
|
||||
return id(new PHUIStatusListView())
|
||||
->addItem($item);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -219,7 +219,12 @@ final class PhabricatorProjectTransactionEditor
|
|||
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorProjectTransaction::TYPE_MEMBERS:
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$type = $xaction->getMetadataValue('edge:type');
|
||||
if ($type != PhabricatorProjectProjectHasMemberEdgeType::EDGECONST) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($is_parent) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$xaction->getTransactionType(),
|
||||
|
@ -792,27 +797,6 @@ final class PhabricatorProjectTransactionEditor
|
|||
|
||||
$results = parent::expandTransactions($object, $xactions);
|
||||
|
||||
// Automatically add the author as a member when they create a project
|
||||
// if they're using the web interface.
|
||||
|
||||
$content_source = $this->getContentSource();
|
||||
$source_web = PhabricatorContentSource::SOURCE_WEB;
|
||||
$is_web = ($content_source->getSource() === $source_web);
|
||||
|
||||
if ($this->getIsNewObject() && $is_web) {
|
||||
if ($actor_phid) {
|
||||
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
|
||||
|
||||
$results[] = id(new PhabricatorProjectTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $type_member)
|
||||
->setNewValue(
|
||||
array(
|
||||
'+' => array($actor_phid => $actor_phid),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$is_milestone = $object->isMilestone();
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
|
|
582
src/applications/project/engine/PhabricatorBoardLayoutEngine.php
Normal file
582
src/applications/project/engine/PhabricatorBoardLayoutEngine.php
Normal file
|
@ -0,0 +1,582 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorBoardLayoutEngine extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $boardPHIDs;
|
||||
private $objectPHIDs;
|
||||
private $boards;
|
||||
private $columnMap = array();
|
||||
private $objectColumnMap = array();
|
||||
private $boardLayout = array();
|
||||
|
||||
private $remQueue = array();
|
||||
private $addQueue = array();
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setBoardPHIDs(array $board_phids) {
|
||||
$this->boardPHIDs = array_fuse($board_phids);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBoardPHIDs() {
|
||||
return $this->boardPHIDs;
|
||||
}
|
||||
|
||||
public function setObjectPHIDs(array $object_phids) {
|
||||
$this->objectPHIDs = array_fuse($object_phids);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjectPHIDs() {
|
||||
return $this->objectPHIDs;
|
||||
}
|
||||
|
||||
public function executeLayout() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$boards = $this->loadBoards();
|
||||
if (!$boards) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$columns = $this->loadColumns($boards);
|
||||
$positions = $this->loadPositions($boards);
|
||||
|
||||
foreach ($boards as $board_phid => $board) {
|
||||
$board_columns = idx($columns, $board_phid);
|
||||
|
||||
// Don't layout boards with no columns. These boards need to be formally
|
||||
// created first.
|
||||
if (!$columns) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$board_positions = idx($positions, $board_phid, array());
|
||||
|
||||
$this->layoutBoard($board, $board_columns, $board_positions);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getColumns($board_phid) {
|
||||
$columns = idx($this->boardLayout, $board_phid, array());
|
||||
return array_select_keys($this->columnMap, array_keys($columns));
|
||||
}
|
||||
|
||||
public function getColumnObjectPHIDs($board_phid, $column_phid) {
|
||||
$columns = idx($this->boardLayout, $board_phid, array());
|
||||
$positions = idx($columns, $column_phid, array());
|
||||
return mpull($positions, 'getObjectPHID');
|
||||
}
|
||||
|
||||
public function getObjectColumns($board_phid, $object_phid) {
|
||||
$board_map = idx($this->objectColumnMap, $board_phid, array());
|
||||
|
||||
$column_phids = idx($board_map, $object_phid);
|
||||
if (!$column_phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_select_keys($this->columnMap, $column_phids);
|
||||
}
|
||||
|
||||
public function queueRemovePosition(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid) {
|
||||
|
||||
$board_layout = idx($this->boardLayout, $board_phid, array());
|
||||
$positions = idx($board_layout, $column_phid, array());
|
||||
$position = idx($positions, $object_phid);
|
||||
|
||||
if ($position) {
|
||||
$this->remQueue[] = $position;
|
||||
|
||||
// If this position hasn't been saved yet, get it out of the add queue.
|
||||
if (!$position->getID()) {
|
||||
foreach ($this->addQueue as $key => $add_position) {
|
||||
if ($add_position === $position) {
|
||||
unset($this->addQueue[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($this->boardLayout[$board_phid][$column_phid][$object_phid]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function queueAddPositionBefore(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid,
|
||||
$before_phid) {
|
||||
|
||||
return $this->queueAddPositionRelative(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid,
|
||||
$before_phid,
|
||||
true);
|
||||
}
|
||||
|
||||
public function queueAddPositionAfter(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid,
|
||||
$after_phid) {
|
||||
|
||||
return $this->queueAddPositionRelative(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid,
|
||||
$after_phid,
|
||||
false);
|
||||
}
|
||||
|
||||
public function queueAddPosition(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid) {
|
||||
return $this->queueAddPositionRelative(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid,
|
||||
null,
|
||||
true);
|
||||
}
|
||||
|
||||
private function queueAddPositionRelative(
|
||||
$board_phid,
|
||||
$column_phid,
|
||||
$object_phid,
|
||||
$relative_phid,
|
||||
$is_before) {
|
||||
|
||||
$board_layout = idx($this->boardLayout, $board_phid, array());
|
||||
$positions = idx($board_layout, $column_phid, array());
|
||||
|
||||
// Check if the object is already in the column, and remove it if it is.
|
||||
$object_position = idx($positions, $object_phid);
|
||||
unset($positions[$object_phid]);
|
||||
|
||||
if (!$object_position) {
|
||||
$object_position = id(new PhabricatorProjectColumnPosition())
|
||||
->setBoardPHID($board_phid)
|
||||
->setColumnPHID($column_phid)
|
||||
->setObjectPHID($object_phid);
|
||||
}
|
||||
|
||||
$found = false;
|
||||
if (!$positions) {
|
||||
$object_position->setSequence(0);
|
||||
} else {
|
||||
foreach ($positions as $position) {
|
||||
if (!$found) {
|
||||
if ($relative_phid === null) {
|
||||
$is_match = true;
|
||||
} else {
|
||||
$position_phid = $position->getObjectPHID();
|
||||
$is_match = ($relative_phid == $position_phid);
|
||||
}
|
||||
|
||||
if ($is_match) {
|
||||
$found = true;
|
||||
|
||||
$sequence = $position->getSequence();
|
||||
|
||||
if (!$is_before) {
|
||||
$sequence++;
|
||||
}
|
||||
|
||||
$object_position->setSequence($sequence++);
|
||||
|
||||
if (!$is_before) {
|
||||
// If we're inserting after this position, continue the loop so
|
||||
// we don't update it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
$position->setSequence($sequence++);
|
||||
$this->addQueue[] = $position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($relative_phid && !$found) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to find object "%s" in column "%s" on board "%s".',
|
||||
$relative_phid,
|
||||
$column_phid,
|
||||
$board_phid));
|
||||
}
|
||||
|
||||
$this->addQueue[] = $object_position;
|
||||
|
||||
$positions[$object_phid] = $object_position;
|
||||
$positions = msort($positions, 'getOrderingKey');
|
||||
|
||||
$this->boardLayout[$board_phid][$column_phid] = $positions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function applyPositionUpdates() {
|
||||
foreach ($this->remQueue as $position) {
|
||||
if ($position->getID()) {
|
||||
$position->delete();
|
||||
}
|
||||
}
|
||||
$this->remQueue = array();
|
||||
|
||||
$adds = array();
|
||||
$updates = array();
|
||||
|
||||
foreach ($this->addQueue as $position) {
|
||||
$id = $position->getID();
|
||||
if ($id) {
|
||||
$updates[$id] = $position;
|
||||
} else {
|
||||
$adds[] = $position;
|
||||
}
|
||||
}
|
||||
$this->addQueue = array();
|
||||
|
||||
$table = new PhabricatorProjectColumnPosition();
|
||||
$conn_w = $table->establishConnection('w');
|
||||
|
||||
$pairs = array();
|
||||
foreach ($updates as $id => $position) {
|
||||
// This is ugly because MySQL gets upset with us if it is configured
|
||||
// strictly and we attempt inserts which can't work. We'll never actually
|
||||
// do these inserts since they'll always collide (triggering the ON
|
||||
// DUPLICATE KEY logic), so we just provide dummy values in order to get
|
||||
// there.
|
||||
|
||||
$pairs[] = qsprintf(
|
||||
$conn_w,
|
||||
'(%d, %d, "", "", "")',
|
||||
$id,
|
||||
$position->getSequence());
|
||||
}
|
||||
|
||||
if ($pairs) {
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT INTO %T (id, sequence, boardPHID, columnPHID, objectPHID)
|
||||
VALUES %Q ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)',
|
||||
$table->getTableName(),
|
||||
implode(', ', $pairs));
|
||||
}
|
||||
|
||||
foreach ($adds as $position) {
|
||||
$position->save();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function loadBoards() {
|
||||
$viewer = $this->getViewer();
|
||||
$board_phids = $this->getBoardPHIDs();
|
||||
|
||||
$boards = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($board_phids)
|
||||
->execute();
|
||||
$boards = mpull($boards, null, 'getPHID');
|
||||
|
||||
foreach ($boards as $key => $board) {
|
||||
if (!$board->getHasWorkboard()) {
|
||||
unset($boards[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $boards;
|
||||
}
|
||||
|
||||
private function loadColumns(array $boards) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$columns = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($viewer)
|
||||
->withProjectPHIDs(array_keys($boards))
|
||||
->execute();
|
||||
$columns = msort($columns, 'getOrderingKey');
|
||||
$columns = mpull($columns, null, 'getPHID');
|
||||
|
||||
$need_children = array();
|
||||
foreach ($boards as $phid => $board) {
|
||||
if ($board->getHasMilestones() || $board->getHasSubprojects()) {
|
||||
$need_children[] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
if ($need_children) {
|
||||
$children = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withParentProjectPHIDs($need_children)
|
||||
->execute();
|
||||
$children = mpull($children, null, 'getPHID');
|
||||
$children = mgroup($children, 'getParentProjectPHID');
|
||||
} else {
|
||||
$children = array();
|
||||
}
|
||||
|
||||
$columns = mgroup($columns, 'getProjectPHID');
|
||||
foreach ($boards as $board_phid => $board) {
|
||||
$board_columns = idx($columns, $board_phid, array());
|
||||
|
||||
// If the project has milestones, create any missing columns.
|
||||
if ($board->getHasMilestones() || $board->getHasSubprojects()) {
|
||||
$child_projects = idx($children, $board_phid, array());
|
||||
|
||||
$next_sequence = last($board_columns)->getSequence() + 1;
|
||||
$proxy_columns = mpull($board_columns, null, 'getProxyPHID');
|
||||
foreach ($child_projects as $child_phid => $child) {
|
||||
if (isset($proxy_columns[$child_phid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_column = PhabricatorProjectColumn::initializeNewColumn($viewer)
|
||||
->attachProject($board)
|
||||
->attachProxy($child)
|
||||
->setSequence($next_sequence++)
|
||||
->setProjectPHID($board_phid)
|
||||
->setProxyPHID($child_phid);
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$new_column->save();
|
||||
unset($unguarded);
|
||||
|
||||
$board_columns[$new_column->getPHID()] = $new_column;
|
||||
}
|
||||
}
|
||||
|
||||
$board_columns = msort($board_columns, 'getOrderingKey');
|
||||
|
||||
$columns[$board_phid] = $board_columns;
|
||||
}
|
||||
|
||||
foreach ($columns as $board_phid => $board_columns) {
|
||||
foreach ($board_columns as $board_column) {
|
||||
$column_phid = $board_column->getPHID();
|
||||
$this->columnMap[$column_phid] = $board_column;
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
private function loadPositions(array $boards) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$object_phids = $this->getObjectPHIDs();
|
||||
if (!$object_phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$positions = id(new PhabricatorProjectColumnPositionQuery())
|
||||
->setViewer($viewer)
|
||||
->withBoardPHIDs(array_keys($boards))
|
||||
->withObjectPHIDs($object_phids)
|
||||
->execute();
|
||||
$positions = msort($positions, 'getOrderingKey');
|
||||
$positions = mgroup($positions, 'getBoardPHID');
|
||||
|
||||
return $positions;
|
||||
}
|
||||
|
||||
private function layoutBoard(
|
||||
$board,
|
||||
array $columns,
|
||||
array $positions) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$board_phid = $board->getPHID();
|
||||
$position_groups = mgroup($positions, 'getObjectPHID');
|
||||
|
||||
$layout = array();
|
||||
foreach ($columns as $column) {
|
||||
$column_phid = $column->getPHID();
|
||||
$layout[$column_phid] = array();
|
||||
|
||||
if ($column->isDefaultColumn()) {
|
||||
$default_phid = $column_phid;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all the columns which are proxies for other objects.
|
||||
$proxy_map = array();
|
||||
foreach ($columns as $column) {
|
||||
$proxy_phid = $column->getProxyPHID();
|
||||
if ($proxy_phid) {
|
||||
$proxy_map[$proxy_phid] = $column->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$object_phids = $this->getObjectPHIDs();
|
||||
|
||||
// If we have proxies, we need to force cards into the correct proxy
|
||||
// columns.
|
||||
if ($proxy_map) {
|
||||
$edge_query = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($object_phids)
|
||||
->withEdgeTypes(
|
||||
array(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
));
|
||||
$edge_query->execute();
|
||||
|
||||
$project_phids = $edge_query->getDestinationPHIDs();
|
||||
$project_phids = array_fuse($project_phids);
|
||||
} else {
|
||||
$project_phids = array();
|
||||
}
|
||||
|
||||
if ($project_phids) {
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($project_phids)
|
||||
->execute();
|
||||
$projects = mpull($projects, null, 'getPHID');
|
||||
} else {
|
||||
$projects = array();
|
||||
}
|
||||
|
||||
// Build a map from every project that any task is tagged with to the
|
||||
// ancestor project which has a column on this board, if one exists.
|
||||
$ancestor_map = array();
|
||||
foreach ($projects as $phid => $project) {
|
||||
if (isset($proxy_map[$phid])) {
|
||||
$ancestor_map[$phid] = $proxy_map[$phid];
|
||||
} else {
|
||||
$seen = array($phid);
|
||||
foreach ($project->getAncestorProjects() as $ancestor) {
|
||||
$ancestor_phid = $ancestor->getPHID();
|
||||
$seen[] = $ancestor_phid;
|
||||
if (isset($proxy_map[$ancestor_phid])) {
|
||||
foreach ($seen as $project_phid) {
|
||||
$ancestor_map[$project_phid] = $proxy_map[$ancestor_phid];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($object_phids as $object_phid) {
|
||||
$positions = idx($position_groups, $object_phid, array());
|
||||
|
||||
// First, check for objects that have corresponding proxy columns. We're
|
||||
// going to overwrite normal column positions if a tag belongs to a proxy
|
||||
// column, since you can't be in normal columns if you're in proxy
|
||||
// columns.
|
||||
$proxy_hits = array();
|
||||
if ($proxy_map) {
|
||||
$object_project_phids = $edge_query->getDestinationPHIDs(
|
||||
array(
|
||||
$object_phid,
|
||||
));
|
||||
|
||||
foreach ($object_project_phids as $project_phid) {
|
||||
if (isset($ancestor_map[$project_phid])) {
|
||||
$proxy_hits[] = $ancestor_map[$project_phid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($proxy_hits) {
|
||||
// TODO: For now, only one column hit is permissible.
|
||||
$proxy_hits = array_slice($proxy_hits, 0, 1);
|
||||
|
||||
$proxy_hits = array_fuse($proxy_hits);
|
||||
|
||||
// Check the object positions: we hope to find a position in each
|
||||
// column the object should be part of. We're going to drop any
|
||||
// invalid positions and create new positions where positions are
|
||||
// missing.
|
||||
foreach ($positions as $key => $position) {
|
||||
$column_phid = $position->getColumnPHID();
|
||||
if (isset($proxy_hits[$column_phid])) {
|
||||
// Valid column, mark the position as found.
|
||||
unset($proxy_hits[$column_phid]);
|
||||
} else {
|
||||
// Invalid column, ignore the position.
|
||||
unset($positions[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new positions for anything we haven't found.
|
||||
foreach ($proxy_hits as $proxy_hit) {
|
||||
$new_position = id(new PhabricatorProjectColumnPosition())
|
||||
->setBoardPHID($board_phid)
|
||||
->setColumnPHID($proxy_hit)
|
||||
->setObjectPHID($object_phid)
|
||||
->setSequence(0);
|
||||
|
||||
$this->addQueue[] = $new_position;
|
||||
|
||||
$positions[] = $new_position;
|
||||
}
|
||||
} else {
|
||||
// Ignore any positions in columns which no longer exist. We don't
|
||||
// actively destory them because the rest of the code ignores them and
|
||||
// there's no real need to destroy the data.
|
||||
foreach ($positions as $key => $position) {
|
||||
$column_phid = $position->getColumnPHID();
|
||||
if (empty($columns[$column_phid])) {
|
||||
unset($positions[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// If the object has no position, put it on the default column.
|
||||
if (!$positions) {
|
||||
$new_position = id(new PhabricatorProjectColumnPosition())
|
||||
->setBoardPHID($board_phid)
|
||||
->setColumnPHID($default_phid)
|
||||
->setObjectPHID($object_phid)
|
||||
->setSequence(0);
|
||||
|
||||
$this->addQueue[] = $new_position;
|
||||
|
||||
$positions = array(
|
||||
$new_position,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($positions as $position) {
|
||||
$column_phid = $position->getColumnPHID();
|
||||
$layout[$column_phid][$object_phid] = $position;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($layout as $column_phid => $map) {
|
||||
$map = msort($map, 'getOrderingKey');
|
||||
$layout[$column_phid] = $map;
|
||||
|
||||
foreach ($map as $object_phid => $position) {
|
||||
$this->objectColumnMap[$board_phid][$object_phid][] = $column_phid;
|
||||
}
|
||||
}
|
||||
|
||||
$this->boardLayout[$board_phid] = $layout;
|
||||
}
|
||||
|
||||
}
|
|
@ -176,7 +176,7 @@ final class PhabricatorProjectEditEngine
|
|||
$milestone_phid = null;
|
||||
}
|
||||
|
||||
return array(
|
||||
$fields = array(
|
||||
id(new PhabricatorHandlesEditField())
|
||||
->setKey('parent')
|
||||
->setLabel(pht('Parent'))
|
||||
|
@ -243,6 +243,49 @@ final class PhabricatorProjectEditEngine
|
|||
->setConduitTypeDescription(pht('New list of slugs.'))
|
||||
->setValue($slugs),
|
||||
);
|
||||
|
||||
$can_edit_members = (!$milestone) &&
|
||||
(!$object->isMilestone()) &&
|
||||
(!$object->getHasSubprojects());
|
||||
|
||||
if ($can_edit_members) {
|
||||
|
||||
// Show this on the web UI when creating a project, but not when editing
|
||||
// one. It is always available via Conduit.
|
||||
$conduit_only = !$this->getIsCreate();
|
||||
|
||||
$members_field = id(new PhabricatorUsersEditField())
|
||||
->setKey('members')
|
||||
->setAliases(array('memberPHIDs'))
|
||||
->setLabel(pht('Initial Members'))
|
||||
->setIsConduitOnly($conduit_only)
|
||||
->setUseEdgeTransactions(true)
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue(
|
||||
'edge:type',
|
||||
PhabricatorProjectProjectHasMemberEdgeType::EDGECONST)
|
||||
->setDescription(pht('Initial project members.'))
|
||||
->setConduitDescription(pht('Set project members.'))
|
||||
->setConduitTypeDescription(pht('New list of members.'))
|
||||
->setValue(array());
|
||||
|
||||
$members_field->setViewer($this->getViewer());
|
||||
|
||||
$edit_add = $members_field->getConduitEditType('members.add')
|
||||
->setConduitDescription(pht('Add members.'));
|
||||
|
||||
$edit_set = $members_field->getConduitEditType('members.set')
|
||||
->setConduitDescription(
|
||||
pht('Set members, overwriting the current value.'));
|
||||
|
||||
$edit_rem = $members_field->getConduitEditType('members.remove')
|
||||
->setConduitDescription(pht('Remove members.'));
|
||||
|
||||
$fields[] = $members_field;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,6 +28,10 @@ final class PhabricatorProjectProfilePanelEngine
|
|||
->setBuiltinKey(PhabricatorProject::PANEL_MEMBERS)
|
||||
->setPanelKey(PhabricatorProjectMembersProfilePanel::PANELKEY);
|
||||
|
||||
$panels[] = $this->newPanel()
|
||||
->setBuiltinKey(PhabricatorProject::PANEL_SUBPROJECTS)
|
||||
->setPanelKey(PhabricatorProjectSubprojectsProfilePanel::PANELKEY);
|
||||
|
||||
$panels[] = $this->newPanel()
|
||||
->setBuiltinKey(PhabricatorProject::PANEL_MANAGE)
|
||||
->setPanelKey(PhabricatorProjectManageProfilePanel::PANELKEY);
|
||||
|
|
|
@ -61,6 +61,15 @@ final class PhabricatorProjectsMembershipIndexEngineExtension
|
|||
|
||||
$conn_w = $project->establishConnection('w');
|
||||
|
||||
$any_milestone = queryfx_one(
|
||||
$conn_w,
|
||||
'SELECT id FROM %T
|
||||
WHERE parentProjectPHID = %s AND milestoneNumber IS NOT NULL
|
||||
LIMIT 1',
|
||||
$project->getTableName(),
|
||||
$project_phid);
|
||||
$has_milestones = (bool)$any_milestone;
|
||||
|
||||
$project->openTransaction();
|
||||
|
||||
// Delete any existing materialized member edges.
|
||||
|
@ -92,6 +101,14 @@ final class PhabricatorProjectsMembershipIndexEngineExtension
|
|||
(int)$has_subprojects,
|
||||
$project->getID());
|
||||
|
||||
// Update the hasMilestones flag.
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET hasMilestones = %d WHERE id = %d',
|
||||
$project->getTableName(),
|
||||
(int)$has_milestones,
|
||||
$project->getID());
|
||||
|
||||
$project->saveTransaction();
|
||||
}
|
||||
|
||||
|
|
|
@ -49,49 +49,42 @@ final class PhabricatorProjectUIEventListener
|
|||
|
||||
$annotations = array();
|
||||
if ($handles && $can_appear_on_boards) {
|
||||
$engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($user)
|
||||
->setBoardPHIDs($project_phids)
|
||||
->setObjectPHIDs(array($object->getPHID()))
|
||||
->executeLayout();
|
||||
|
||||
// TDOO: Generalize this UI and move it out of Maniphest.
|
||||
|
||||
require_celerity_resource('maniphest-task-summary-css');
|
||||
|
||||
$positions_query = id(new PhabricatorProjectColumnPositionQuery())
|
||||
->setViewer($user)
|
||||
->withBoardPHIDs($project_phids)
|
||||
->withObjectPHIDs(array($object->getPHID()))
|
||||
->needColumns(true);
|
||||
|
||||
// This is important because positions will be created "on demand"
|
||||
// based on the set of columns. If we don't specify it, positions
|
||||
// won't be created.
|
||||
$columns = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($user)
|
||||
->withProjectPHIDs($project_phids)
|
||||
->execute();
|
||||
if ($columns) {
|
||||
$positions_query->withColumns($columns);
|
||||
}
|
||||
$positions = $positions_query->execute();
|
||||
$positions = mpull($positions, null, 'getBoardPHID');
|
||||
|
||||
foreach ($project_phids as $project_phid) {
|
||||
$handle = $handles[$project_phid];
|
||||
|
||||
$position = idx($positions, $project_phid);
|
||||
if ($position) {
|
||||
$column = $position->getColumn();
|
||||
$columns = $engine->getObjectColumns(
|
||||
$project_phid,
|
||||
$object->getPHID());
|
||||
|
||||
$annotation = array();
|
||||
foreach ($columns as $column) {
|
||||
$project_id = $column->getProject()->getID();
|
||||
|
||||
$column_name = pht('(%s)', $column->getDisplayName());
|
||||
$column_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $handle->getURI().'board/',
|
||||
'href' => "/project/board/{$project_id}/",
|
||||
'class' => 'maniphest-board-link',
|
||||
),
|
||||
$column_name);
|
||||
|
||||
$annotation[] = $column_link;
|
||||
}
|
||||
|
||||
if ($annotation) {
|
||||
$annotations[$project_phid] = array(
|
||||
' ',
|
||||
$column_link,
|
||||
phutil_implode_html(', ', $annotation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
final class ProjectHovercardEngineExtension
|
||||
extends PhabricatorHovercardEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'project.card';
|
||||
|
||||
public function isExtensionEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getExtensionName() {
|
||||
return pht('Project Card');
|
||||
}
|
||||
|
||||
public function canRenderObjectHovercard($object) {
|
||||
return ($object instanceof PhabricatorProject);
|
||||
}
|
||||
|
||||
public function willRenderHovercards(array $objects) {
|
||||
$viewer = $this->getViewer();
|
||||
$phids = mpull($objects, 'getPHID');
|
||||
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($phids)
|
||||
->needImages(true)
|
||||
->execute();
|
||||
$projects = mpull($projects, null, 'getPHID');
|
||||
|
||||
return array(
|
||||
'projects' => $projects,
|
||||
);
|
||||
}
|
||||
|
||||
public function renderHovercard(
|
||||
PHUIHovercardView $hovercard,
|
||||
PhabricatorObjectHandle $handle,
|
||||
$object,
|
||||
$data) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$project = idx($data['projects'], $object->getPHID());
|
||||
if (!$project) {
|
||||
return;
|
||||
}
|
||||
|
||||
$project_card = id(new PhabricatorProjectCardView())
|
||||
->setProject($project)
|
||||
->setViewer($viewer);
|
||||
|
||||
$hovercard->appendChild($project_card);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
interface PhabricatorColumnProxyInterface {
|
||||
|
||||
public function getProxyColumnName();
|
||||
|
||||
}
|
|
@ -37,7 +37,7 @@ final class PhabricatorProjectProjectPHIDType extends PhabricatorPHIDType {
|
|||
foreach ($handles as $phid => $handle) {
|
||||
$project = $objects[$phid];
|
||||
|
||||
$name = $project->getName();
|
||||
$name = $project->getDisplayName();
|
||||
$id = $project->getID();
|
||||
$slug = $project->getPrimarySlug();
|
||||
|
||||
|
|
|
@ -48,7 +48,13 @@ final class PhabricatorProjectsPolicyRule
|
|||
}
|
||||
|
||||
public function getValueControlTemplate() {
|
||||
return $this->getDatasourceTemplate(new PhabricatorProjectDatasource());
|
||||
$datasource = id(new PhabricatorProjectDatasource())
|
||||
->setParameters(
|
||||
array(
|
||||
'policy' => 1,
|
||||
));
|
||||
|
||||
return $this->getDatasourceTemplate($datasource);
|
||||
}
|
||||
|
||||
public function getRuleOrder() {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectSubprojectsProfilePanel
|
||||
extends PhabricatorProfilePanel {
|
||||
|
||||
const PANELKEY = 'project.subprojects';
|
||||
|
||||
public function getPanelTypeName() {
|
||||
return pht('Project Subprojects');
|
||||
}
|
||||
|
||||
private function getDefaultName() {
|
||||
return pht('Subprojects');
|
||||
}
|
||||
|
||||
public function getDisplayName(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
$name = $config->getPanelProperty('name');
|
||||
|
||||
if (strlen($name)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return $this->getDefaultName();
|
||||
}
|
||||
|
||||
public function buildEditEngineFields(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
return array(
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setPlaceholder($this->getDefaultName())
|
||||
->setValue($config->getPanelProperty('name')),
|
||||
);
|
||||
}
|
||||
|
||||
protected function newNavigationMenuItems(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
|
||||
$project = $config->getProfileObject();
|
||||
|
||||
$has_children = ($project->getHasSubprojects()) ||
|
||||
($project->getHasMilestones());
|
||||
|
||||
$id = $project->getID();
|
||||
|
||||
$name = $this->getDisplayName($config);
|
||||
$icon = 'fa-sitemap';
|
||||
$href = "/project/subprojects/{$id}/";
|
||||
|
||||
$item = $this->newItem()
|
||||
->setHref($href)
|
||||
->setName($name)
|
||||
->setDisabled(!$has_children)
|
||||
->setIcon($icon);
|
||||
|
||||
return array(
|
||||
$item,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,10 +6,7 @@ final class PhabricatorProjectColumnPositionQuery
|
|||
private $ids;
|
||||
private $boardPHIDs;
|
||||
private $objectPHIDs;
|
||||
private $columns;
|
||||
|
||||
private $needColumns;
|
||||
private $skipImplicitCreate;
|
||||
private $columnPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -26,277 +23,51 @@ final class PhabricatorProjectColumnPositionQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find objects in specific columns.
|
||||
*
|
||||
* NOTE: Using this method activates logic which constructs virtual
|
||||
* column positions for objects not in any column, if you pass a default
|
||||
* column. Normally these results are not returned.
|
||||
*
|
||||
* @param list<PhabricatorProjectColumn> Columns to look for objects in.
|
||||
* @return this
|
||||
*/
|
||||
public function withColumns(array $columns) {
|
||||
assert_instances_of($columns, 'PhabricatorProjectColumn');
|
||||
$this->columns = $columns;
|
||||
public function withColumnPHIDs(array $column_phids) {
|
||||
$this->columnPHIDs = $column_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needColumns($need_columns) {
|
||||
$this->needColumns = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Skip implicit creation of column positions which are implied but do not
|
||||
* yet exist.
|
||||
*
|
||||
* This is primarily useful internally.
|
||||
*
|
||||
* @param bool True to skip implicit creation of column positions.
|
||||
* @return this
|
||||
*/
|
||||
public function setSkipImplicitCreate($skip) {
|
||||
$this->skipImplicitCreate = $skip;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// NOTE: For now, boards are always attached to projects. However, they might
|
||||
// not be in the future. This generalization just anticipates a future where
|
||||
// we let other types of objects (like users) have boards, or let boards
|
||||
// contain other types of objects.
|
||||
|
||||
private function newPositionObject() {
|
||||
public function newResultObject() {
|
||||
return new PhabricatorProjectColumnPosition();
|
||||
}
|
||||
|
||||
private function newColumnQuery() {
|
||||
return new PhabricatorProjectColumnQuery();
|
||||
}
|
||||
|
||||
private function getBoardMembershipEdgeTypes() {
|
||||
return array(
|
||||
PhabricatorProjectProjectHasObjectEdgeType::EDGECONST,
|
||||
);
|
||||
}
|
||||
|
||||
private function getBoardMembershipPHIDTypes() {
|
||||
return array(
|
||||
ManiphestTaskPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = $this->newPositionObject();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
// We're going to find results by combining two queries: one query finds
|
||||
// objects on a board column, while the other query finds objects not on
|
||||
// any board column and virtually puts them on the default column.
|
||||
|
||||
$unions = array();
|
||||
|
||||
// First, find all the stuff that's actually on a column.
|
||||
|
||||
$unions[] = qsprintf(
|
||||
$conn_r,
|
||||
'SELECT * FROM %T %Q',
|
||||
$table->getTableName(),
|
||||
$this->buildWhereClause($conn_r));
|
||||
|
||||
// If we have a default column, find all the stuff that's not in any
|
||||
// column and put it in the default column.
|
||||
|
||||
$must_type_filter = false;
|
||||
if ($this->columns && !$this->skipImplicitCreate) {
|
||||
$default_map = array();
|
||||
foreach ($this->columns as $column) {
|
||||
if ($column->isDefaultColumn()) {
|
||||
$default_map[$column->getProjectPHID()] = $column->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
if ($default_map) {
|
||||
$where = array();
|
||||
|
||||
// Find the edges attached to the boards we have default columns for.
|
||||
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'e.src IN (%Ls)',
|
||||
array_keys($default_map));
|
||||
|
||||
// Find only edges which describe a board relationship.
|
||||
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'e.type IN (%Ld)',
|
||||
$this->getBoardMembershipEdgeTypes());
|
||||
|
||||
if ($this->boardPHIDs !== null) {
|
||||
// This should normally be redundant, but construct it anyway if
|
||||
// the caller has told us to.
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'e.src IN (%Ls)',
|
||||
$this->boardPHIDs);
|
||||
}
|
||||
|
||||
if ($this->objectPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'e.dst IN (%Ls)',
|
||||
$this->objectPHIDs);
|
||||
}
|
||||
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'p.id IS NULL');
|
||||
|
||||
$where = $this->formatWhereClause($where);
|
||||
|
||||
$unions[] = qsprintf(
|
||||
$conn_r,
|
||||
'SELECT NULL id, e.src boardPHID, NULL columnPHID, e.dst objectPHID,
|
||||
0 sequence
|
||||
FROM %T e LEFT JOIN %T p
|
||||
ON e.src = p.boardPHID AND e.dst = p.objectPHID
|
||||
%Q',
|
||||
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
||||
$table->getTableName(),
|
||||
$where);
|
||||
|
||||
$must_type_filter = true;
|
||||
}
|
||||
}
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'%Q %Q %Q',
|
||||
implode(' UNION ALL ', $unions),
|
||||
$this->buildOrderClause($conn_r),
|
||||
$this->buildLimitClause($conn_r));
|
||||
|
||||
// If we've picked up objects not in any column, we need to filter out any
|
||||
// matched objects which have the wrong edge type.
|
||||
if ($must_type_filter) {
|
||||
$allowed_types = array_fuse($this->getBoardMembershipPHIDTypes());
|
||||
foreach ($data as $id => $row) {
|
||||
if ($row['columnPHID'] === null) {
|
||||
$object_phid = $row['objectPHID'];
|
||||
if (empty($allowed_types[phid_get_type($object_phid)])) {
|
||||
unset($data[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$positions = $table->loadAllFromArray($data);
|
||||
|
||||
// Find the implied positions which don't exist yet. If there are any,
|
||||
// we're going to create them.
|
||||
$create = array();
|
||||
foreach ($positions as $position) {
|
||||
if ($position->getColumnPHID() === null) {
|
||||
$column_phid = idx($default_map, $position->getBoardPHID());
|
||||
$position->setColumnPHID($column_phid);
|
||||
|
||||
$create[] = $position;
|
||||
}
|
||||
}
|
||||
|
||||
if ($create) {
|
||||
// If we're adding several objects to a column, insert the column
|
||||
// position objects in object ID order. This means that newly added
|
||||
// objects float to the top, and when a group of newly added objects
|
||||
// float up at the same time, the most recently created ones end up
|
||||
// highest in the list.
|
||||
|
||||
$objects = id(new PhabricatorObjectQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs(mpull($create, 'getObjectPHID'))
|
||||
->execute();
|
||||
$objects = mpull($objects, null, 'getPHID');
|
||||
$objects = msort($objects, 'getID');
|
||||
|
||||
$create = mgroup($create, 'getObjectPHID');
|
||||
$create = array_select_keys($create, array_keys($objects)) + $create;
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
foreach ($create as $object_phid => $create_positions) {
|
||||
foreach ($create_positions as $create_position) {
|
||||
$create_position->save();
|
||||
}
|
||||
}
|
||||
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
return $positions;
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $page) {
|
||||
|
||||
if ($this->needColumns) {
|
||||
$column_phids = mpull($page, 'getColumnPHID');
|
||||
$columns = $this->newColumnQuery()
|
||||
->setParentQuery($this)
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($column_phids)
|
||||
->execute();
|
||||
$columns = mpull($columns, null, 'getPHID');
|
||||
|
||||
foreach ($page as $key => $position) {
|
||||
$column = idx($columns, $position->getColumnPHID());
|
||||
if (!$column) {
|
||||
unset($page[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$position->attachColumn($column);
|
||||
}
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = array();
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->boardPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'boardPHID IN (%Ls)',
|
||||
$this->boardPHIDs);
|
||||
}
|
||||
|
||||
if ($this->objectPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'objectPHID IN (%Ls)',
|
||||
$this->objectPHIDs);
|
||||
}
|
||||
|
||||
if ($this->columns !== null) {
|
||||
if ($this->columnPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'columnPHID IN (%Ls)',
|
||||
mpull($this->columns, 'getPHID'));
|
||||
$this->columnPHIDs);
|
||||
}
|
||||
|
||||
// NOTE: Explicitly not building the paging clause here, since it won't
|
||||
// work with the UNION.
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
|
|
|
@ -6,6 +6,7 @@ final class PhabricatorProjectColumnQuery
|
|||
private $ids;
|
||||
private $phids;
|
||||
private $projectPHIDs;
|
||||
private $proxyPHIDs;
|
||||
private $statuses;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
|
@ -23,6 +24,11 @@ final class PhabricatorProjectColumnQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withProxyPHIDs(array $proxy_phids) {
|
||||
$this->proxyPHIDs = $proxy_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withStatuses(array $status) {
|
||||
$this->statuses = $status;
|
||||
return $this;
|
||||
|
@ -60,6 +66,55 @@ final class PhabricatorProjectColumnQuery
|
|||
$column->attachProject($project);
|
||||
}
|
||||
|
||||
$proxy_phids = array_filter(mpull($page, 'getProjectPHID'));
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
protected function didFilterPage(array $page) {
|
||||
$proxy_phids = array();
|
||||
foreach ($page as $column) {
|
||||
$proxy_phid = $column->getProxyPHID();
|
||||
if ($proxy_phid !== null) {
|
||||
$proxy_phids[$proxy_phid] = $proxy_phid;
|
||||
}
|
||||
}
|
||||
|
||||
if ($proxy_phids) {
|
||||
$proxies = id(new PhabricatorObjectQuery())
|
||||
->setParentQuery($this)
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($proxy_phids)
|
||||
->execute();
|
||||
$proxies = mpull($proxies, null, 'getPHID');
|
||||
} else {
|
||||
$proxies = array();
|
||||
}
|
||||
|
||||
foreach ($page as $key => $column) {
|
||||
$proxy_phid = $column->getProxyPHID();
|
||||
|
||||
if ($proxy_phid !== null) {
|
||||
$proxy = idx($proxies, $proxy_phid);
|
||||
|
||||
// Only attach valid proxies, so we don't end up getting surprsied if
|
||||
// an install somehow gets junk into their database.
|
||||
if (!($proxy instanceof PhabricatorColumnProxyInterface)) {
|
||||
$proxy = null;
|
||||
}
|
||||
|
||||
if (!$proxy) {
|
||||
$this->didRejectResult($column);
|
||||
unset($page[$key]);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$proxy = null;
|
||||
}
|
||||
|
||||
$column->attachProxy($proxy);
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
|
@ -87,6 +142,13 @@ final class PhabricatorProjectColumnQuery
|
|||
$this->projectPHIDs);
|
||||
}
|
||||
|
||||
if ($this->proxyPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'proxyPHID IN (%Ls)',
|
||||
$this->proxyPHIDs);
|
||||
}
|
||||
|
||||
if ($this->statuses !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
|
|
|
@ -16,7 +16,9 @@ final class ProjectRemarkupRule extends PhabricatorObjectRemarkupRule {
|
|||
return '#'.$id;
|
||||
}
|
||||
|
||||
return $handle->renderTag();
|
||||
$tag = $handle->renderTag();
|
||||
$tag->setPHID($handle->getPHID());
|
||||
return $tag;
|
||||
}
|
||||
|
||||
protected function getObjectIDPattern() {
|
||||
|
|
|
@ -9,7 +9,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorFulltextInterface,
|
||||
PhabricatorConduitResultInterface {
|
||||
PhabricatorConduitResultInterface,
|
||||
PhabricatorColumnProxyInterface {
|
||||
|
||||
protected $name;
|
||||
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
|
||||
|
@ -378,7 +379,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
$this->getPHID());
|
||||
|
||||
$all_strings = ipull($slugs, 'slug');
|
||||
$all_strings[] = $this->getName();
|
||||
$all_strings[] = $this->getDisplayName();
|
||||
$all_strings = implode(' ', $all_strings);
|
||||
|
||||
$tokens = PhabricatorTypeaheadDatasource::tokenizeString($all_strings);
|
||||
|
@ -489,6 +490,20 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
return $number;
|
||||
}
|
||||
|
||||
public function getDisplayName() {
|
||||
$name = $this->getName();
|
||||
|
||||
// If this is a milestone, show it as "Parent > Sprint 99".
|
||||
if ($this->isMilestone()) {
|
||||
$name = pht(
|
||||
'%s (%s)',
|
||||
$this->getParentProject()->getName(),
|
||||
$name);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getDisplayIconKey() {
|
||||
if ($this->isMilestone()) {
|
||||
$key = PhabricatorProjectIconSet::getMilestoneIconKey();
|
||||
|
@ -663,4 +678,25 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorColumnProxyInterface )------------------------------------ */
|
||||
|
||||
|
||||
public function getProxyColumnName() {
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
public function getProxyColumnIcon() {
|
||||
return $this->getDisplayIconIcon();
|
||||
}
|
||||
|
||||
public function getProxyColumnClass() {
|
||||
if ($this->isMilestone()) {
|
||||
return 'phui-workboard-column-milestone';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -17,15 +17,18 @@ final class PhabricatorProjectColumn
|
|||
protected $name;
|
||||
protected $status;
|
||||
protected $projectPHID;
|
||||
protected $proxyPHID;
|
||||
protected $sequence;
|
||||
protected $properties = array();
|
||||
|
||||
private $project = self::ATTACHABLE;
|
||||
private $proxy = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewColumn(PhabricatorUser $user) {
|
||||
return id(new PhabricatorProjectColumn())
|
||||
->setName('')
|
||||
->setStatus(self::STATUS_ACTIVE);
|
||||
->setStatus(self::STATUS_ACTIVE)
|
||||
->attachProxy(null);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
|
@ -38,6 +41,7 @@ final class PhabricatorProjectColumn
|
|||
'name' => 'text255',
|
||||
'status' => 'uint32',
|
||||
'sequence' => 'uint32',
|
||||
'proxyPHID' => 'phid?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_status' => array(
|
||||
|
@ -46,6 +50,10 @@ final class PhabricatorProjectColumn
|
|||
'key_sequence' => array(
|
||||
'columns' => array('projectPHID', 'sequence'),
|
||||
),
|
||||
'key_proxy' => array(
|
||||
'columns' => array('projectPHID', 'proxyPHID'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
@ -64,6 +72,15 @@ final class PhabricatorProjectColumn
|
|||
return $this->assertAttached($this->project);
|
||||
}
|
||||
|
||||
public function attachProxy($proxy) {
|
||||
$this->proxy = $proxy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProxy() {
|
||||
return $this->assertAttached($this->proxy);
|
||||
}
|
||||
|
||||
public function isDefaultColumn() {
|
||||
return (bool)$this->getProperty('isDefault');
|
||||
}
|
||||
|
@ -73,6 +90,11 @@ final class PhabricatorProjectColumn
|
|||
}
|
||||
|
||||
public function getDisplayName() {
|
||||
$proxy = $this->getProxy();
|
||||
if ($proxy) {
|
||||
return $proxy->getProxyColumnName();
|
||||
}
|
||||
|
||||
$name = $this->getName();
|
||||
if (strlen($name)) {
|
||||
return $name;
|
||||
|
@ -96,11 +118,23 @@ final class PhabricatorProjectColumn
|
|||
return null;
|
||||
}
|
||||
|
||||
public function getDisplayClass() {
|
||||
$proxy = $this->getProxy();
|
||||
if ($proxy) {
|
||||
return $proxy->getProxyColumnClass();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getHeaderIcon() {
|
||||
$icon = null;
|
||||
$proxy = $this->getProxy();
|
||||
if ($proxy) {
|
||||
return $proxy->getProxyColumnIcon();
|
||||
}
|
||||
|
||||
if ($this->isHidden()) {
|
||||
$icon = 'fa-eye-slash';
|
||||
return 'fa-eye-slash';
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -124,6 +158,25 @@ final class PhabricatorProjectColumn
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getOrderingKey() {
|
||||
$proxy = $this->getProxy();
|
||||
|
||||
// Normal columns and subproject columns go first, in a user-controlled
|
||||
// order.
|
||||
|
||||
// All the milestone columns go last, in their sequential order.
|
||||
|
||||
if (!$proxy || !$proxy->isMilestone()) {
|
||||
$group = 'A';
|
||||
$sequence = $this->getSequence();
|
||||
} else {
|
||||
$group = 'B';
|
||||
$sequence = $proxy->getMilestoneNumber();
|
||||
}
|
||||
|
||||
return sprintf('%s%012d', $group, $sequence);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ final class PhabricatorProjectColumnPosition extends PhabricatorProjectDAO
|
|||
}
|
||||
|
||||
public function getOrderingKey() {
|
||||
if (!$this->getID() && !$this->getSequence()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Low sequence numbers go above high sequence numbers.
|
||||
// High position IDs go above low position IDs.
|
||||
// Broadly, this makes newly added stuff float to the top.
|
||||
|
|
|
@ -32,6 +32,12 @@ final class PhabricatorProjectDatasource
|
|||
$query->withNameTokens($tokens);
|
||||
}
|
||||
|
||||
// If this is for policy selection, prevent users from using milestones.
|
||||
$for_policy = $this->getParameter('policy');
|
||||
if ($for_policy) {
|
||||
$query->withIsMilestone(false);
|
||||
}
|
||||
|
||||
$projs = $this->executeQuery($query);
|
||||
|
||||
$projs = mpull($projs, null, 'getPHID');
|
||||
|
@ -58,13 +64,13 @@ final class PhabricatorProjectDatasource
|
|||
}
|
||||
|
||||
$all_strings = mpull($proj->getSlugs(), 'getSlug');
|
||||
$all_strings[] = $proj->getName();
|
||||
$all_strings[] = $proj->getDisplayName();
|
||||
$all_strings = implode(' ', $all_strings);
|
||||
|
||||
$proj_result = id(new PhabricatorTypeaheadResult())
|
||||
->setName($all_strings)
|
||||
->setDisplayName($proj->getName())
|
||||
->setDisplayType(pht('Project'))
|
||||
->setDisplayName($proj->getDisplayName())
|
||||
->setDisplayType($proj->getDisplayIconName())
|
||||
->setURI($proj->getURI())
|
||||
->setPHID($proj->getPHID())
|
||||
->setIcon($proj->getDisplayIconIcon())
|
||||
|
|
84
src/applications/project/view/PhabricatorProjectCardView.php
Normal file
84
src/applications/project/view/PhabricatorProjectCardView.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectCardView extends AphrontTagView {
|
||||
|
||||
private $project;
|
||||
private $viewer;
|
||||
private $tag;
|
||||
|
||||
public function setProject(PhabricatorProject $project) {
|
||||
$this->project = $project;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTag($tag) {
|
||||
$this->tag = $tag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getTagName() {
|
||||
if ($this->tag) {
|
||||
return $this->tag;
|
||||
}
|
||||
return 'div';
|
||||
}
|
||||
|
||||
protected function getTagAttributes() {
|
||||
$classes = array();
|
||||
$classes[] = 'project-card-view';
|
||||
|
||||
$color = $this->project->getColor();
|
||||
$classes[] = 'project-card-'.$color;
|
||||
|
||||
return array(
|
||||
'class' => implode($classes, ' '),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getTagContent() {
|
||||
|
||||
$project = $this->project;
|
||||
$viewer = $this->viewer;
|
||||
require_celerity_resource('project-card-view-css');
|
||||
|
||||
$icon = $project->getDisplayIconIcon();
|
||||
$icon_name = $project->getDisplayIconName();
|
||||
$tag = id(new PHUITagView())
|
||||
->setIcon($icon)
|
||||
->setName($icon_name)
|
||||
->addClass('project-view-header-tag')
|
||||
->setType(PHUITagView::TYPE_SHADE);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(array($project->getDisplayName(), $tag))
|
||||
->setUser($viewer)
|
||||
->setPolicyObject($project)
|
||||
->setImage($project->getProfileImageURI());
|
||||
|
||||
if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) {
|
||||
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
|
||||
} else {
|
||||
$header->setStatus('fa-ban', 'red', pht('Archived'));
|
||||
}
|
||||
|
||||
$description = null;
|
||||
|
||||
$card = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'project-card-inner',
|
||||
),
|
||||
array(
|
||||
$header,
|
||||
$description,
|
||||
));
|
||||
|
||||
return $card;
|
||||
}
|
||||
|
||||
}
|
|
@ -86,7 +86,7 @@ abstract class PhabricatorProjectUserListView extends AphrontView {
|
|||
->setImageURI($handle->getImageURI());
|
||||
|
||||
$icon = id(new PHUIIconView())
|
||||
->setIcon($handle->getIcon().' lightbluetext');
|
||||
->setIcon($handle->getIcon());
|
||||
|
||||
$subtitle = $handle->getSubtitle();
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
final class ProjectBoardTaskCard extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $projectHandles;
|
||||
private $task;
|
||||
private $owner;
|
||||
private $canEdit;
|
||||
|
@ -15,6 +16,15 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setProjectHandles(array $handles) {
|
||||
$this->projectHandles = $handles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProjectHandles() {
|
||||
return $this->projectHandles;
|
||||
}
|
||||
|
||||
public function setTask(ManiphestTask $task) {
|
||||
$this->task = $task;
|
||||
return $this;
|
||||
|
@ -44,13 +54,14 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
$task = $this->getTask();
|
||||
$owner = $this->getOwner();
|
||||
$can_edit = $this->getCanEdit();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$color_map = ManiphestTaskPriority::getColorMap();
|
||||
$bar_color = idx($color_map, $task->getPriority(), 'grey');
|
||||
|
||||
$card = id(new PHUIObjectItemView())
|
||||
->setObject($task)
|
||||
->setUser($this->getViewer())
|
||||
->setUser($viewer)
|
||||
->setObjectName('T'.$task->getID())
|
||||
->setHeader($task->getTitle())
|
||||
->setGrippable($can_edit)
|
||||
|
@ -70,7 +81,23 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
->setBarColor($bar_color);
|
||||
|
||||
if ($owner) {
|
||||
$card->addAttribute($owner->renderLink());
|
||||
$card->addHandleIcon($owner, $owner->getName());
|
||||
}
|
||||
|
||||
if ($task->isClosed()) {
|
||||
$icon = ManiphestTaskStatus::getStatusIcon($task->getStatus());
|
||||
$icon = id(new PHUIIconView())
|
||||
->setIcon($icon.' grey');
|
||||
$card->addAttribute($icon);
|
||||
$card->setBarColor('grey');
|
||||
}
|
||||
|
||||
$project_handles = $this->getProjectHandles();
|
||||
if ($project_handles) {
|
||||
$tag_list = id(new PHUIHandleTagListView())
|
||||
->setSlim(true)
|
||||
->setHandles($project_handles);
|
||||
$card->addAttribute($tag_list);
|
||||
}
|
||||
|
||||
return $card;
|
||||
|
|
|
@ -30,7 +30,7 @@ final class PhabricatorRepositoryPullEvent
|
|||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'repositoryPHID' => 'phid?',
|
||||
'pullerPHID' => 'phid?',
|
||||
'remoteAddress' => 'uint32?',
|
||||
'remoteAddress' => 'ipaddress?',
|
||||
'remoteProtocol' => 'text32?',
|
||||
'resultType' => 'text32',
|
||||
'resultCode' => 'uint32',
|
||||
|
|
|
@ -29,7 +29,7 @@ final class PhabricatorRepositoryPushEvent
|
|||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'remoteAddress' => 'uint32?',
|
||||
'remoteAddress' => 'ipaddress?',
|
||||
'remoteProtocol' => 'text32?',
|
||||
'rejectCode' => 'uint32',
|
||||
'rejectDetails' => 'text64?',
|
||||
|
|
|
@ -56,7 +56,7 @@ final class PhabricatorSearchHovercardController
|
|||
$handle = $handles[$phid];
|
||||
$object = idx($objects, $phid);
|
||||
|
||||
$hovercard = id(new PhabricatorHovercardView())
|
||||
$hovercard = id(new PHUIHovercardView())
|
||||
->setUser($viewer)
|
||||
->setObjectHandle($handle);
|
||||
|
||||
|
|
|
@ -663,6 +663,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
|
|||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Profile Menu Items'))
|
||||
->setSubHeader(pht('Drag tabs to reorder menu'))
|
||||
->addActionLink($action_button);
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
|
@ -907,7 +908,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
|
|||
|
||||
$is_target =
|
||||
(($builtin_key !== null) && ($builtin_key === $key)) ||
|
||||
(($id !== null) && ($id === (int)$key));
|
||||
(($id !== null) && ((int)$id === (int)$key));
|
||||
|
||||
if ($is_target) {
|
||||
if (!$panel->isDefault()) {
|
||||
|
|
|
@ -32,7 +32,7 @@ abstract class PhabricatorHovercardEngineExtension extends Phobject {
|
|||
}
|
||||
|
||||
abstract public function renderHovercard(
|
||||
PhabricatorHovercardView $hovercard,
|
||||
PHUIHovercardView $hovercard,
|
||||
PhabricatorObjectHandle $handle,
|
||||
$object,
|
||||
$data);
|
||||
|
|
|
@ -131,6 +131,15 @@ final class PhabricatorMotivatorProfilePanel
|
|||
pht(
|
||||
'The Japanese word for cat is "kome", which is also the word for '.
|
||||
'rice. Japanese cats love to eat rice, so the two are synonymous.'),
|
||||
pht('Cats have five pointy ends.'),
|
||||
pht('cat -A can find mice hiding in files.'),
|
||||
pht('A cat\'s visual, olfactory, and auditory senses, '.
|
||||
'Contribute to their hunting skills and natural defenses.'),
|
||||
pht(
|
||||
'Cats with high self-esteem seek out high perches '.
|
||||
'to launch their attacks. Watch out!'),
|
||||
pht('Cats prefer vanilla ice cream.'),
|
||||
pht('Taco cat spelled backwards is taco cat.'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
|
|||
const PREFERENCE_DESKTOP_NOTIFICATIONS = 'desktop-notifications';
|
||||
|
||||
const PREFERENCE_PROFILE_MENU_COLLAPSED = 'profile-menu.collapsed';
|
||||
const PREFERENCE_FAVORITE_POLICIES = 'policy.favorites';
|
||||
|
||||
// These are in an unusual order for historic reasons.
|
||||
const MAILTAG_PREFERENCE_NOTIFY = 0;
|
||||
|
|
|
@ -400,7 +400,15 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
return $space_phid;
|
||||
}
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
return $this->getEdgeTransactionNewValue($xaction);
|
||||
$new_value = $this->getEdgeTransactionNewValue($xaction);
|
||||
|
||||
$edge_type = $xaction->getMetadataValue('edge:type');
|
||||
$type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
|
||||
if ($edge_type == $type_project) {
|
||||
$new_value = $this->applyProjectConflictRules($new_value);
|
||||
}
|
||||
|
||||
return $new_value;
|
||||
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
|
||||
$field = $this->getCustomFieldForTransaction($object, $xaction);
|
||||
return $field->getNewValueFromApplicationTransactions($xaction);
|
||||
|
@ -3346,4 +3354,127 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
return $state;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove conflicts from a list of projects.
|
||||
*
|
||||
* Objects aren't allowed to be tagged with multiple milestones in the same
|
||||
* group, nor projects such that one tag is the ancestor of any other tag.
|
||||
* If the list of PHIDs include mutually exclusive projects, remove the
|
||||
* conflicting projects.
|
||||
*
|
||||
* @param list<phid> List of project PHIDs.
|
||||
* @return list<phid> List with conflicts removed.
|
||||
*/
|
||||
private function applyProjectConflictRules(array $phids) {
|
||||
if (!$phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Overall, the last project in the list wins in cases of conflict (so when
|
||||
// you add something, the thing you just added sticks and removes older
|
||||
// values).
|
||||
|
||||
// Beyond that, there are two basic cases:
|
||||
|
||||
// Milestones: An object can't be in "A > Sprint 3" and "A > Sprint 4".
|
||||
// If multiple projects are milestones of the same parent, we only keep the
|
||||
// last one.
|
||||
|
||||
// Ancestor: You can't be in "A" and "A > B". If "A > B" comes later
|
||||
// in the list, we remove "A" and keep "A > B". If "A" comes later, we
|
||||
// remove "A > B" and keep "A".
|
||||
|
||||
// Note that it's OK to be in "A > B" and "A > C". There's only a conflict
|
||||
// if one project is an ancestor of another. It's OK to have something
|
||||
// tagged with multiple projects which share a common ancestor, so long as
|
||||
// they are not mutual ancestors.
|
||||
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array_keys($phids))
|
||||
->execute();
|
||||
$projects = mpull($projects, null, 'getPHID');
|
||||
|
||||
// We're going to build a map from each project with milestones to the last
|
||||
// milestone in the list. This last milestone is the milestone we'll keep.
|
||||
$milestone_map = array();
|
||||
|
||||
// We're going to build a set of the projects which have no descendants
|
||||
// later in the list. This allows us to apply both ancestor rules.
|
||||
$ancestor_map = array();
|
||||
|
||||
foreach ($phids as $phid => $ignored) {
|
||||
$project = idx($projects, $phid);
|
||||
if (!$project) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is the last milestone we've seen, so set it as the selection for
|
||||
// the project's parent. This might be setting a new value or overwriting
|
||||
// an earlier value.
|
||||
if ($project->isMilestone()) {
|
||||
$parent_phid = $project->getParentProjectPHID();
|
||||
$milestone_map[$parent_phid] = $phid;
|
||||
}
|
||||
|
||||
// Since this is the last item in the list we've examined so far, add it
|
||||
// to the set of projects with no later descendants.
|
||||
$ancestor_map[$phid] = $phid;
|
||||
|
||||
// Remove any ancestors from the set, since this is a later descendant.
|
||||
foreach ($project->getAncestorProjects() as $ancestor) {
|
||||
$ancestor_phid = $ancestor->getPHID();
|
||||
unset($ancestor_map[$ancestor_phid]);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we've built the maps, we can throw away all the projects which
|
||||
// have conflicts.
|
||||
foreach ($phids as $phid => $ignored) {
|
||||
$project = idx($projects, $phid);
|
||||
|
||||
if (!$project) {
|
||||
// If a PHID is invalid, we just leave it as-is. We could clean it up,
|
||||
// but leaving it untouched is less likely to cause collateral damage.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this was a milestone, check if it was the last milestone from its
|
||||
// group in the list. If not, remove it from the list.
|
||||
if ($project->isMilestone()) {
|
||||
$parent_phid = $project->getParentProjectPHID();
|
||||
if ($milestone_map[$parent_phid] !== $phid) {
|
||||
unset($phids[$phid]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If a later project in the list is a subproject of this one, it will
|
||||
// have removed ancestors from the map. If this project does not point
|
||||
// at itself in the ancestor map, it should be discarded in favor of a
|
||||
// subproject that comes later.
|
||||
if (idx($ancestor_map, $phid) !== $phid) {
|
||||
unset($phids[$phid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If a later project in the list is an ancestor of this one, it will
|
||||
// have added itself to the map. If any ancestor of this project points
|
||||
// at itself in the map, this project should be dicarded in favor of
|
||||
// that later ancestor.
|
||||
foreach ($project->getAncestorProjects() as $ancestor) {
|
||||
$ancestor_phid = $ancestor->getPHID();
|
||||
if (isset($ancestor_map[$ancestor_phid])) {
|
||||
unset($phids[$phid]);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $phids;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ final class PhabricatorTypeaheadTokenView
|
|||
$token = id(new PhabricatorTypeaheadTokenView())
|
||||
->setKey($handle->getPHID())
|
||||
->setValue($handle->getFullName())
|
||||
->setIcon($handle->getIcon());
|
||||
->setIcon($handle->getTokenIcon());
|
||||
|
||||
if ($handle->isDisabled() ||
|
||||
$handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorHovercardUIExample extends PhabricatorUIExample {
|
||||
final class PHUIHovercardUIExample extends PhabricatorUIExample {
|
||||
|
||||
public function getName() {
|
||||
return pht('Hovercard');
|
||||
|
@ -8,8 +8,8 @@ final class PhabricatorHovercardUIExample extends PhabricatorUIExample {
|
|||
|
||||
public function getDescription() {
|
||||
return pht(
|
||||
"Use %s to render hovercards. Aren't I genius?",
|
||||
phutil_tag('tt', array(), 'PhabricatorHovercardView'));
|
||||
'Use %s to render hovercards.',
|
||||
phutil_tag('tt', array(), 'PHUIHovercardView'));
|
||||
}
|
||||
|
||||
public function renderExample() {
|
||||
|
@ -24,7 +24,7 @@ final class PhabricatorHovercardUIExample extends PhabricatorUIExample {
|
|||
pht('Introduce cooler Differential Revisions'));
|
||||
|
||||
$panel = $this->createPanel(pht('Differential Hovercard'));
|
||||
$panel->appendChild(id(new PhabricatorHovercardView())
|
||||
$panel->appendChild(id(new PHUIHovercardView())
|
||||
->setObjectHandle($diff_handle)
|
||||
->addField(pht('Author'), $user->getUsername())
|
||||
->addField(pht('Updated'), phabricator_datetime(time(), $user))
|
||||
|
@ -41,7 +41,7 @@ final class PhabricatorHovercardUIExample extends PhabricatorUIExample {
|
|||
->setType(PHUITagView::TYPE_STATE)
|
||||
->setName(pht('Closed, Resolved'));
|
||||
$panel = $this->createPanel(pht('Maniphest Hovercard'));
|
||||
$panel->appendChild(id(new PhabricatorHovercardView())
|
||||
$panel->appendChild(id(new PHUIHovercardView())
|
||||
->setObjectHandle($task_handle)
|
||||
->setUser($user)
|
||||
->addField(pht('Assigned to'), $user->getUsername())
|
||||
|
@ -66,7 +66,7 @@ final class PhabricatorHovercardUIExample extends PhabricatorUIExample {
|
|||
$user_handle->setImageURI(
|
||||
celerity_get_resource_uri('/rsrc/image/people/washington.png'));
|
||||
$panel = $this->createPanel(pht('Whatevery Hovercard'));
|
||||
$panel->appendChild(id(new PhabricatorHovercardView())
|
||||
$panel->appendChild(id(new PHUIHovercardView())
|
||||
->setObjectHandle($user_handle)
|
||||
->addField(pht('Status'), pht('Available'))
|
||||
->addField(pht('Member since'), '30. February 1750')
|
|
@ -127,8 +127,13 @@ If you plan to use authenticated HTTP, you need to set
|
|||
use only anonymous HTTP, you can leave this setting disabled.
|
||||
|
||||
If you plan to use authenticated HTTP, you'll also need to configure a VCS
|
||||
password in {nav Settings > VCS Password}. This is a different password than
|
||||
your main Phabricator password primarily for security reasons.
|
||||
password in {nav Settings > VCS Password}.
|
||||
|
||||
Your VCS password must be a different password than your main Phabricator
|
||||
password because VCS passwords are very easy to accidentally disclose. They are
|
||||
often stored in plaintext in world-readable files, observable in `ps` output,
|
||||
and present in command output and logs. We strongly encourage you to use SSH
|
||||
instead of HTTP to authenticate access to repositories.
|
||||
|
||||
Otherwise, if you've configured system accounts above, you're all set. No
|
||||
additional server configuration is required to make HTTP work.
|
||||
|
|
|
@ -135,6 +135,142 @@ members or won't have a workboard, you can hide these items to streamline the
|
|||
menu.
|
||||
|
||||
|
||||
Subprojects and Milestones
|
||||
==========================
|
||||
|
||||
IMPORTANT: This feature is only partially implemented.
|
||||
|
||||
After creating a project, you can use the
|
||||
{nav icon="sitemap", name="Subprojects"} menu item to add subprojects or
|
||||
milestones.
|
||||
|
||||
**Subprojects** are projects that are contained inside the main project. You
|
||||
can use them to break large or complex groups, tags, lists, or undertakings
|
||||
apart into smaller pieces.
|
||||
|
||||
**Milestones** are a special kind of subproject for organizing tasks into
|
||||
blocks of work. You can use them to implement sprints, iterations, milestones,
|
||||
versions, etc.
|
||||
|
||||
Subprojects and milestones have some additional special behaviors and rules,
|
||||
particularly around policies and membership. See below for details.
|
||||
|
||||
This is a brief summary of the major differences between normal projects,
|
||||
subprojects, parent projects, and milestones.
|
||||
|
||||
| | Normal | Parent | Subproject | Milestone |
|
||||
|---|---|---|---|---|
|
||||
| //Members// | Yes | Union of Subprojects | Yes | Same as Parent |
|
||||
| //Policies// | Yes | Yes | Affected by Parent | Same as Parent |
|
||||
| //Hashtags// | Yes | Yes | Yes | Special |
|
||||
|
||||
|
||||
Subprojects
|
||||
===========
|
||||
|
||||
Subprojects are full-power projects that are contained inside some parent
|
||||
project. You can use them to divide a large or complex project into smaller
|
||||
parts.
|
||||
|
||||
Subprojects have normal members and normal policies, but note that the policies
|
||||
of the parent project affect the policies of the subproject (see "Parent
|
||||
Projects", below).
|
||||
|
||||
Subprojects can have their own subprojects, milestones, or both. If a
|
||||
subproject has its own subprojects, it is both a subproject and a parent
|
||||
project. Thus, the parent project rules apply to it, and are stronger than the
|
||||
subproject rules.
|
||||
|
||||
Subprojects can have normal workboards.
|
||||
|
||||
The maximum subproject depth is 16. This limit is intended to grossly exceed
|
||||
the depth necessary in normal usage.
|
||||
|
||||
Objects may not be tagged with multiple projects that are ancestors or
|
||||
descendants of one another. For example, a task may not be tagged with both
|
||||
{nav Stonework} and {nav Stonework > Masonry}.
|
||||
|
||||
When a project tag is added that is the ancestor or descendant of one or more
|
||||
existing tags, the old tags are replaced. For example, adding
|
||||
{nav Stonework > Masonry} to a task tagged with {nav Stonework} will replace
|
||||
{nav Stonework} with the newer, more specific tag.
|
||||
|
||||
This restriction does not apply to projects which share some common ancestor
|
||||
but are not themselves mutual ancestors. For example, a task may be tagged
|
||||
with both {nav Stonework > Masonry} and {nav Stonework > Sculpting}.
|
||||
|
||||
This restriction //does// apply when the descendant is a milestone. For
|
||||
example, a task may not be tagged with both {nav Stonework} and
|
||||
{nav Stonework > Iteration II}.
|
||||
|
||||
|
||||
Milestones
|
||||
==========
|
||||
|
||||
Milestones are simple subprojects for tracking sprints, iterations, versions,
|
||||
or other similar blocks of work. Milestones make it easier to create and manage
|
||||
a large number of similar subprojects (for example: {nav Sprint 1},
|
||||
{nav Sprint 2}, {nav Sprint 3}, etc).
|
||||
|
||||
Milestones can not have direct members or policies. Instead, the membership
|
||||
and policies of a milestones are always the same as the milestone's parent
|
||||
project. This makes large numbers of milestones more manageable when changes
|
||||
occur.
|
||||
|
||||
Milestones can not have subprojects, and can not have their own milestones.
|
||||
|
||||
By default, Milestones do not have their own hashtags.
|
||||
|
||||
Milestones can have normal workboards.
|
||||
|
||||
Objects may not be tagged with two different milestones of the same parent
|
||||
project. For example, a task may not be tagged with both {nav Stonework >
|
||||
Iteration III} and {nav Stonework > Iteration V}.
|
||||
|
||||
When a milestone tag is added to an object which already has a tag from the
|
||||
same series of milestones, the old tag is removed. For example, adding the
|
||||
{nav Stonework > Iteration V} tag to a task which already has the
|
||||
{nav Stonework > Iteration III} tag will remove the {nav Iteration III} tag.
|
||||
|
||||
This restriction does not apply to milestones which are not part of the same
|
||||
series. For example, a task may be tagged with both
|
||||
{nav Stonework > Iteration V} and {nav Heraldry > Iteration IX}.
|
||||
|
||||
|
||||
Parent Projects
|
||||
===============
|
||||
|
||||
When you add the first subproject to an existing project, it is converted into
|
||||
a **parent project**. Parent projects have some special rules.
|
||||
|
||||
**No Direct Members**: Parent projects can not have members of their own.
|
||||
Instead, all of the users who are members of any subproject count as members
|
||||
of the parent project. By joining (or leaving) a subproject, a user is
|
||||
implicitly added to (or removed from) all ancestors of that project.
|
||||
|
||||
Consequently, when you add the first subproject to an existing project, all of
|
||||
the project's current members are moved to become members of the subproject
|
||||
instead. Implicitly, they will remain members of the parent project because the
|
||||
parent project is an ancestor of the new subproject.
|
||||
|
||||
You can edit the project afterward to change or remove members if you want to
|
||||
split membership apart in a more granular way across multiple new subprojects.
|
||||
|
||||
**Searching**: When you search for a parent project, results for any subproject
|
||||
are returned. For example, if you search for {nav Engineering}, your query will
|
||||
match results in {nav Engineering} itself, but also subprojects like
|
||||
{nav Engineering > Warp Drive} and {nav Engineering > Shield Batteries}.
|
||||
|
||||
**Policy Effects**: To view a subproject or milestone, you must be able to
|
||||
view the parent project. As a result, the parent project's view policy now
|
||||
affects child projects. If you restrict the visibility of the parent, you also
|
||||
restrict the visibility of the children.
|
||||
|
||||
In contrast, permission to edit a parent project grants permission to edit
|
||||
any subproject. If a user can {nav Root Project}, they can also edit
|
||||
{nav Root Project > Child} and {nav Root Project > Child > Sprint 3}.
|
||||
|
||||
|
||||
Policies In Depth
|
||||
=================
|
||||
|
||||
|
|
|
@ -103,6 +103,24 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
protected function getOptions() {
|
||||
$capability = $this->capability;
|
||||
$policies = $this->policies;
|
||||
$viewer = $this->getUser();
|
||||
|
||||
// Check if we're missing the policy for the current control value. This
|
||||
// is unusual, but can occur if the user is submitting a form and selected
|
||||
// an unusual project as a policy but the change has not been saved yet.
|
||||
$policy_map = mpull($policies, null, 'getPHID');
|
||||
$value = $this->getValue();
|
||||
if ($value && empty($policy_map[$value])) {
|
||||
$handle = id(new PhabricatorHandleQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($value))
|
||||
->executeOne();
|
||||
if ($handle->isComplete()) {
|
||||
$policies[] = PhabricatorPolicy::newFromPolicyAndHandle(
|
||||
$value,
|
||||
$handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude object policies which don't make sense here. This primarily
|
||||
// filters object policies associated from template capabilities (like
|
||||
|
@ -143,12 +161,32 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
'name' => $policy_short_name,
|
||||
'full' => $policy->getName(),
|
||||
'icon' => $policy->getIcon(),
|
||||
'sort' => phutil_utf8_strtolower($policy->getName()),
|
||||
);
|
||||
}
|
||||
|
||||
$type_project = PhabricatorPolicyType::TYPE_PROJECT;
|
||||
|
||||
// Make sure we have a "Projects" group before we adjust it.
|
||||
if (empty($options[$type_project])) {
|
||||
$options[$type_project] = array();
|
||||
}
|
||||
|
||||
$options[$type_project] = isort($options[$type_project], 'sort');
|
||||
|
||||
$placeholder = id(new PhabricatorPolicy())
|
||||
->setName(pht('Other Project...'))
|
||||
->setIcon('fa-search');
|
||||
|
||||
$options[$type_project][$this->getSelectProjectKey()] = array(
|
||||
'name' => $placeholder->getName(),
|
||||
'full' => $placeholder->getName(),
|
||||
'icon' => $placeholder->getIcon(),
|
||||
);
|
||||
|
||||
// If we were passed several custom policy options, throw away the ones
|
||||
// which aren't the value for this capability. For example, an object might
|
||||
// have a custom view pollicy and a custom edit policy. When we render
|
||||
// have a custom view policy and a custom edit policy. When we render
|
||||
// the selector for "Can View", we don't want to show the "Can Edit"
|
||||
// custom policy -- if we did, the menu would look like this:
|
||||
//
|
||||
|
@ -172,7 +210,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
if (empty($options[$type_custom])) {
|
||||
$placeholder = new PhabricatorPolicy();
|
||||
$placeholder->setName(pht('Custom Policy...'));
|
||||
$options[$type_custom][$this->getCustomPolicyPlaceholder()] = array(
|
||||
$options[$type_custom][$this->getSelectCustomKey()] = array(
|
||||
'name' => $placeholder->getName(),
|
||||
'full' => $placeholder->getName(),
|
||||
'icon' => $placeholder->getIcon(),
|
||||
|
@ -266,12 +304,12 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
'options' => $flat_options,
|
||||
'groups' => array_keys($options),
|
||||
'order' => $order,
|
||||
'icons' => $icons,
|
||||
'labels' => $labels,
|
||||
'value' => $this->getValue(),
|
||||
'capability' => $this->capability,
|
||||
'editURI' => '/policy/edit/'.$context_path,
|
||||
'customPlaceholder' => $this->getCustomPolicyPlaceholder(),
|
||||
'customKey' => $this->getSelectCustomKey(),
|
||||
'projectKey' => $this->getSelectProjectKey(),
|
||||
'disabled' => $this->getDisabled(),
|
||||
));
|
||||
|
||||
|
@ -322,8 +360,12 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
));
|
||||
}
|
||||
|
||||
private function getCustomPolicyPlaceholder() {
|
||||
return 'custom:placeholder';
|
||||
public static function getSelectCustomKey() {
|
||||
return 'select:custom';
|
||||
}
|
||||
|
||||
public static function getSelectProjectKey() {
|
||||
return 'select:project';
|
||||
}
|
||||
|
||||
private function buildSpacesControl() {
|
||||
|
|
|
@ -157,7 +157,7 @@ final class PHUIFeedStoryView extends AphrontView {
|
|||
public function render() {
|
||||
|
||||
require_celerity_resource('phui-feed-story-css');
|
||||
Javelin::initBehavior('phabricator-hovercards');
|
||||
Javelin::initBehavior('phui-hovercards');
|
||||
|
||||
$body = null;
|
||||
$foot = null;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* The default one-for-all hovercard. We may derive from this one to create
|
||||
* more specialized ones.
|
||||
*/
|
||||
final class PhabricatorHovercardView extends AphrontView {
|
||||
final class PHUIHovercardView extends AphrontTagView {
|
||||
|
||||
/**
|
||||
* @var PhabricatorObjectHandle
|
||||
|
@ -70,7 +70,16 @@ final class PhabricatorHovercardView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
protected function getTagAttributes() {
|
||||
$classes = array();
|
||||
$classes[] = 'phui-hovercard-wrapper';
|
||||
|
||||
return array(
|
||||
'class' => implode(' ', $classes),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getTagContent() {
|
||||
if (!$this->handle) {
|
||||
throw new PhutilInvalidStateException('setObjectHandle');
|
||||
}
|
||||
|
@ -78,16 +87,19 @@ final class PhabricatorHovercardView extends AphrontView {
|
|||
$viewer = $this->getUser();
|
||||
$handle = $this->handle;
|
||||
|
||||
require_celerity_resource('phabricator-hovercard-view-css');
|
||||
require_celerity_resource('phui-hovercard-view-css');
|
||||
|
||||
// If we're a fully custom Hovercard, skip the common UI
|
||||
$children = $this->renderChildren();
|
||||
if ($children) {
|
||||
return $children;
|
||||
}
|
||||
|
||||
$title = array(
|
||||
id(new PHUISpacesNamespaceContextView())
|
||||
->setUser($viewer)
|
||||
->setObject($this->getObject()),
|
||||
pht(
|
||||
'%s: %s',
|
||||
$handle->getTypeName(),
|
||||
$this->title ? $this->title : $handle->getName()),
|
||||
$this->title ? $this->title : $handle->getName(),
|
||||
);
|
||||
|
||||
$header = new PHUIHeaderView();
|
||||
|
@ -107,7 +119,7 @@ final class PhabricatorHovercardView extends AphrontView {
|
|||
$body_title = $handle->getFullName();
|
||||
}
|
||||
|
||||
$body[] = phutil_tag_div('phabricator-hovercard-body-header', $body_title);
|
||||
$body[] = phutil_tag_div('phui-hovercard-body-header', $body_title);
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
$item = array(
|
||||
|
@ -115,7 +127,7 @@ final class PhabricatorHovercardView extends AphrontView {
|
|||
': ',
|
||||
phutil_tag('span', array(), $field['value']),
|
||||
);
|
||||
$body[] = phutil_tag_div('phabricator-hovercard-body-item', $item);
|
||||
$body[] = phutil_tag_div('phui-hovercard-body-item', $item);
|
||||
}
|
||||
|
||||
if ($this->badges) {
|
||||
|
@ -125,7 +137,7 @@ final class PhabricatorHovercardView extends AphrontView {
|
|||
$body[] = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-hovercard-body-item hovercard-badges',
|
||||
'class' => 'phui-hovercard-body-item hovercard-badges',
|
||||
),
|
||||
$badges);
|
||||
}
|
||||
|
@ -136,7 +148,7 @@ final class PhabricatorHovercardView extends AphrontView {
|
|||
$body = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-hovercard-body-image',
|
||||
'class' => 'phui-hovercard-body-image',
|
||||
),
|
||||
phutil_tag(
|
||||
'div',
|
||||
|
@ -149,7 +161,7 @@ final class PhabricatorHovercardView extends AphrontView {
|
|||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-hovercard-body-details',
|
||||
'class' => 'phui-hovercard-body-details',
|
||||
),
|
||||
$body));
|
||||
}
|
||||
|
@ -178,18 +190,18 @@ final class PhabricatorHovercardView extends AphrontView {
|
|||
|
||||
$tail = null;
|
||||
if ($buttons) {
|
||||
$tail = phutil_tag_div('phabricator-hovercard-tail', $buttons);
|
||||
$tail = phutil_tag_div('phui-hovercard-tail', $buttons);
|
||||
}
|
||||
|
||||
$hovercard = phutil_tag_div(
|
||||
'phabricator-hovercard-container',
|
||||
'phui-hovercard-container grouped',
|
||||
array(
|
||||
phutil_tag_div('phabricator-hovercard-head', $header),
|
||||
phutil_tag_div('phabricator-hovercard-body grouped', $body),
|
||||
phutil_tag_div('phui-hovercard-head', $header),
|
||||
phutil_tag_div('phui-hovercard-body grouped', $body),
|
||||
$tail,
|
||||
));
|
||||
|
||||
return phutil_tag_div('phabricator-hovercard-wrapper', $hovercard);
|
||||
return $hovercard;
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ final class PHUIObjectItemListView extends AphrontTagView {
|
|||
private $flush;
|
||||
private $allowEmptyList;
|
||||
private $states;
|
||||
private $itemClass = 'phui-object-item-standard';
|
||||
|
||||
public function setAllowEmptyList($allow_empty_list) {
|
||||
$this->allowEmptyList = $allow_empty_list;
|
||||
|
@ -49,6 +50,11 @@ final class PHUIObjectItemListView extends AphrontTagView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setItemClass($item_class) {
|
||||
$this->itemClass = $item_class;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getTagName() {
|
||||
return 'ul';
|
||||
}
|
||||
|
@ -89,6 +95,11 @@ final class PHUIObjectItemListView extends AphrontTagView {
|
|||
$item->setUser($viewer);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$item->addClass($this->itemClass);
|
||||
}
|
||||
|
||||
$items = $this->items;
|
||||
} else if ($this->allowEmptyList) {
|
||||
$items = null;
|
||||
|
|
|
@ -383,17 +383,24 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
),
|
||||
$this->header);
|
||||
|
||||
$header = javelin_tag(
|
||||
// Wrap the header content in a <span> with the "slippery" sigil. This
|
||||
// prevents us from beginning a drag if you click the text (like "T123"),
|
||||
// but not if you click the white space after the header.
|
||||
$header = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-object-item-name',
|
||||
'sigil' => 'slippery',
|
||||
),
|
||||
array(
|
||||
$this->headIcons,
|
||||
$header_name,
|
||||
$header_link,
|
||||
));
|
||||
javelin_tag(
|
||||
'span',
|
||||
array(
|
||||
'sigil' => 'slippery',
|
||||
),
|
||||
array(
|
||||
$this->headIcons,
|
||||
$header_name,
|
||||
$header_link,
|
||||
)));
|
||||
|
||||
$icons = array();
|
||||
if ($this->icons) {
|
||||
|
@ -452,14 +459,15 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
$icon_list);
|
||||
}
|
||||
|
||||
$handle_bar = null;
|
||||
if ($this->handleIcons) {
|
||||
$handle_bar = array();
|
||||
foreach ($this->handleIcons as $handleicon) {
|
||||
$handle_bar[] =
|
||||
$this->renderHandleIcon($handleicon['icon'], $handleicon['label']);
|
||||
}
|
||||
$icons[] = phutil_tag(
|
||||
'div',
|
||||
$handle_bar = phutil_tag(
|
||||
'li',
|
||||
array(
|
||||
'class' => 'phui-object-item-handle-icons',
|
||||
),
|
||||
|
@ -504,7 +512,7 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
}
|
||||
|
||||
$attrs = null;
|
||||
if ($this->attributes) {
|
||||
if ($this->attributes || $handle_bar) {
|
||||
$attrs = array();
|
||||
$spacer = phutil_tag(
|
||||
'span',
|
||||
|
@ -531,7 +539,10 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
array(
|
||||
'class' => 'phui-object-item-attributes',
|
||||
),
|
||||
$attrs);
|
||||
array(
|
||||
$handle_bar,
|
||||
$attrs,
|
||||
));
|
||||
}
|
||||
|
||||
$status = null;
|
||||
|
@ -750,7 +761,7 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
|
||||
if (strlen($label)) {
|
||||
$options['sigil'] = 'has-tooltip';
|
||||
$options['meta'] = array('tip' => $label);
|
||||
$options['meta'] = array('tip' => $label, 'align' => 'E');
|
||||
}
|
||||
|
||||
return javelin_tag('span', $options, '');
|
||||
|
|
|
@ -115,14 +115,14 @@ final class PHUIPropertyListView extends AphrontView {
|
|||
$this->invokedWillRenderEvent = true;
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
public function hasAnyProperties() {
|
||||
$this->invokeWillRenderEvent();
|
||||
|
||||
if ($this->parts) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
|
|
|
@ -122,7 +122,7 @@ final class PHUITagView extends AphrontTagView {
|
|||
}
|
||||
|
||||
if ($this->phid) {
|
||||
Javelin::initBehavior('phabricator-hovercards');
|
||||
Javelin::initBehavior('phui-hovercards');
|
||||
|
||||
$attributes = array(
|
||||
'href' => $this->href,
|
||||
|
|
|
@ -10,8 +10,8 @@ final class PHUIWorkpanelView extends AphrontTagView {
|
|||
private $headerTag;
|
||||
private $headerIcon;
|
||||
|
||||
public function setHeaderIcon(PHUIIconView $header_icon) {
|
||||
$this->headerIcon = $header_icon;
|
||||
public function setHeaderIcon($icon) {
|
||||
$this->headerIcon = $icon;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
|
||||
"globals": {
|
||||
"JX": false,
|
||||
"Raphael": false,
|
||||
"d3": false,
|
||||
"__DEV__": false
|
||||
},
|
||||
|
||||
"browser": true
|
||||
}
|
||||
|
|
|
@ -19,14 +19,14 @@
|
|||
|
||||
.phabricator-standard-page-footer {
|
||||
text-align: right;
|
||||
margin: 32px 16px 16px;
|
||||
margin: 44px 16px 16px;
|
||||
padding: 12px 0;
|
||||
border-top: 1px solid rgba(71, 87, 120, 0.20);
|
||||
border-top: 1px solid rgba(55,55,55,.1);
|
||||
color: {$greytext};
|
||||
}
|
||||
|
||||
.device .phabricator-standard-page-footer {
|
||||
margin: 4px 8px;
|
||||
margin: 24px 8px 16px;
|
||||
}
|
||||
|
||||
!print .phabricator-standard-page-footer {
|
||||
|
|
|
@ -43,24 +43,34 @@
|
|||
background-color: #fff;
|
||||
}
|
||||
|
||||
.device .phame-home-view .phui-side-column {
|
||||
background-color: transparent;
|
||||
.phame-home-view {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid rgba(55,55,55,.1);
|
||||
}
|
||||
|
||||
.phame-home-view .phame-home-container {
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.phame-home-view .phui-document-container {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.phame-blog-list {
|
||||
margin: 24px 16px 16px 16px;
|
||||
margin: 96px 16px 16px 16px;
|
||||
}
|
||||
|
||||
.phame-blog-list + .phame-blog-list {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.device .phame-blog-list {
|
||||
padding: 0;
|
||||
background-color: {$bluebackground};
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid {$thinblueborder};
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.phame-blog-list-item:last-child {
|
||||
margin-bottom: 0;
|
||||
.device-phone .phame-blog-list {
|
||||
margin: 16px 8px;
|
||||
}
|
||||
|
||||
.phame-blog-list-header {
|
||||
|
|
150
webroot/rsrc/css/application/project/project-card-view.css
Normal file
150
webroot/rsrc/css/application/project/project-card-view.css
Normal file
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* @provides project-card-view-css
|
||||
*/
|
||||
|
||||
.project-card-view {
|
||||
margin: 0 12px 16px 0;
|
||||
text-align: left;
|
||||
background: #fff;
|
||||
border: 1px solid {$lightblueborder};
|
||||
border-radius: 3px;
|
||||
box-shadow: {$dropshadow};
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-shell {
|
||||
margin: 0;
|
||||
padding: 12px 12px 16px 12px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-shell .phui-header-image {
|
||||
border: 3px solid #fff;
|
||||
border-radius: 3px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-shell .phui-header-header {
|
||||
font-size: 18px;
|
||||
font-family: 'Aleo', {$fontfamily};
|
||||
width: 290px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-shell .phui-header-col1 {
|
||||
vertical-align: top;
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-subheader {
|
||||
font-size: {$normalfontsize};
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-header .phui-tag-view {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
color: {$bluetext};
|
||||
font-size: {$normalfontsize};
|
||||
font-family: {$fontfamily};
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-header .phui-tag-view .phui-tag-core {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-header .phui-tag-view .phui-icon-view {
|
||||
margin-left: 0;
|
||||
color: {$bluetext};
|
||||
}
|
||||
|
||||
|
||||
/* Colors */
|
||||
|
||||
.project-card-view.project-card-red {
|
||||
border-color: {$sh-redborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-red .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-redbackground} 42px, #fff 42px);
|
||||
}
|
||||
|
||||
.project-card-view.project-card-orange {
|
||||
border-color: {$sh-orangeborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-orange .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-orangebackground} 42px, #fff 42px);
|
||||
}
|
||||
|
||||
.project-card-view.project-card-yellow {
|
||||
border-color: {$sh-yellowborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-yellow .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-yellowbackground} 42px, #fff 42px);
|
||||
}
|
||||
|
||||
.project-card-view.project-card-green {
|
||||
border-color: {$sh-greenborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-green .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-greenbackground} 42px, #fff 42px);
|
||||
}
|
||||
|
||||
.project-card-view.project-card-blue {
|
||||
border-color: {$sh-blueborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-blue .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-bluebackground} 42px, #fff 42px);
|
||||
}
|
||||
|
||||
.project-card-view.project-card-indigo {
|
||||
border-color: {$sh-indigoborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-indigo .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-indigobackground} 42px, #fff 42px);
|
||||
}
|
||||
|
||||
.project-card-view.project-card-violet {
|
||||
border-color: {$sh-violetborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-violet .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-violetbackground} 42px, #fff 42px);
|
||||
}
|
||||
|
||||
.project-card-view.project-card-pink {
|
||||
border-color: {$sh-pinkborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-pink .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-pinkbackground} 42px, #fff 42px);
|
||||
}
|
||||
|
||||
.project-card-view.project-card-grey,
|
||||
.project-card-view.project-card-checkered {
|
||||
border-color: {$sh-greyborder};
|
||||
}
|
||||
|
||||
.project-card-view.project-card-grey .phui-header-shell,
|
||||
.project-card-view.project-card-checkered .phui-header-shell {
|
||||
background: linear-gradient(to bottom,
|
||||
{$sh-greybackground} 42px, #fff 42px);
|
||||
}
|
|
@ -7,6 +7,23 @@
|
|||
padding-bottom: 64px;
|
||||
}
|
||||
|
||||
.project-view-header-tag {
|
||||
margin-left: 8px;
|
||||
font-size: {$normalfontsize};
|
||||
color: {$bluetext};
|
||||
font-family: {$fontfamily};
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.device-phone .project-view-header-tag {
|
||||
display: block;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.project-view-header-tag .phui-icon-view {
|
||||
color: {$bluetext};
|
||||
}
|
||||
|
||||
.phui-box.phui-box-grey.project-view-properties {
|
||||
margin: 0 16px 0 16px;
|
||||
padding: 4px 12px;
|
||||
|
@ -31,13 +48,18 @@
|
|||
padding: 8px;
|
||||
}
|
||||
|
||||
.project-view-feed .phui-header-shell {
|
||||
.project-view-feed .phui-object-box .phui-header-shell {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.project-view-feed .phui-header-header {
|
||||
font-size: {$biggerfontsize};
|
||||
margin-left: 4px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.device-desktop .project-view-feed .phui-feed-story,
|
||||
.device-tablet .project-view-feed .phui-feed-story {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.project-view-home .phui-box-grey {
|
||||
|
@ -60,6 +82,10 @@
|
|||
width: 364px;
|
||||
}
|
||||
|
||||
.project-view-home .phui-box-grey .phui-object-item-attribute .phui-icon-view {
|
||||
color: {$lightgreytext};
|
||||
}
|
||||
|
||||
.profile-no-badges {
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
|
|
@ -81,10 +81,6 @@ div.phui-calendar-day-event {
|
|||
z-index: 5;
|
||||
}
|
||||
|
||||
.drag-dragging {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.phui-calendar-date-number {
|
||||
z-index: 5;
|
||||
}
|
||||
|
@ -114,6 +110,10 @@ div.phui-calendar-day-event {
|
|||
z-index: 9;
|
||||
}
|
||||
|
||||
.drag-frame {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.jx-mask {
|
||||
z-index: 10;
|
||||
}
|
||||
|
|
54
webroot/rsrc/css/phui/phui-chart.css
Normal file
54
webroot/rsrc/css/phui/phui-chart.css
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* @provides phui-chart-css
|
||||
*/
|
||||
|
||||
.chart .axis line,
|
||||
.chart .axis path {
|
||||
fill: none;
|
||||
stroke: {$blueborder};
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.chart .axis text {
|
||||
fill: {$darkgreytext};
|
||||
}
|
||||
|
||||
.chart .outer,
|
||||
.chart .inner {
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.chart .outer {
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.chart .inner {
|
||||
fill: {$lightbluebackground};
|
||||
stroke: {$lightblueborder};
|
||||
}
|
||||
|
||||
.chart .line {
|
||||
fill: none;
|
||||
stroke: {$blue};
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.chart .point {
|
||||
fill: {$lightblue};
|
||||
stroke: {$blue};
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.chart-tooltip {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
padding: 2px;
|
||||
background: {$lightbluebackground};
|
||||
border: 1px solid {$blueborder};
|
||||
border-radius: 8px;
|
||||
pointer-events: none;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue