1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-10 14:51:06 +01:00

(stable) Promote 2016 Week 6

This commit is contained in:
epriestley 2016-02-06 04:57:31 -08:00
commit b3dd0fd860
121 changed files with 4039 additions and 1388 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -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',

View file

@ -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',

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_project.project_column
ADD proxyPHID VARBINARY(64);

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

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

View file

@ -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',

View file

@ -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() {

View file

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

View file

@ -322,6 +322,7 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject {
case 'phid':
case 'policy';
case 'hashpath64':
case 'ipaddress':
$column_type = 'varbinary(64)';
break;
case 'bytes64':

View file

@ -35,7 +35,7 @@ final class DifferentialHovercardEngineExtension
}
public function renderHovercard(
PhabricatorHovercardView $hovercard,
PHUIHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$object,
$data) {

View file

@ -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];

View file

@ -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',
);
}

View file

@ -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());
}

View file

@ -19,7 +19,7 @@ final class DiffusionHovercardEngineExtension
}
public function renderHovercard(
PhabricatorHovercardView $hovercard,
PHUIHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$commit,
$data) {

View file

@ -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');
}
}

View file

@ -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(),

View file

@ -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'),

View file

@ -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'),

View file

@ -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;
}

View file

@ -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) {

View file

@ -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,

View file

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

View file

@ -19,7 +19,7 @@ final class ManiphestHovercardEngineExtension
}
public function renderHovercard(
PhabricatorHovercardView $hovercard,
PHUIHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$task,
$data) {

View file

@ -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;
}

View file

@ -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() {

View file

@ -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) {

View file

@ -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',

View file

@ -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

View file

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

View file

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

View file

@ -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() {

View file

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

View file

@ -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',

View file

@ -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;
}

View file

@ -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('#');
}
}

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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',

View file

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

View file

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

View file

@ -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,

View file

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

View file

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

View file

@ -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,

View file

@ -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,

View file

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

View file

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

View file

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

View file

@ -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'));
}
}

View file

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

View file

@ -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()) {

View 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;
}
}

View file

@ -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;
}
}

View file

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

View file

@ -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();
}

View file

@ -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),
);
}
}

View file

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

View file

@ -0,0 +1,7 @@
<?php
interface PhabricatorColumnProxyInterface {
public function getProxyColumnName();
}

View file

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

View file

@ -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() {

View file

@ -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,
);
}
}

View file

@ -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() {

View file

@ -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,

View file

@ -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() {

View file

@ -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;
}
}

View file

@ -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 )------------------------- */

View file

@ -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.

View file

@ -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())

View 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;
}
}

View file

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

View file

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

View file

@ -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',

View file

@ -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?',

View file

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

View file

@ -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()) {

View file

@ -32,7 +32,7 @@ abstract class PhabricatorHovercardEngineExtension extends Phobject {
}
abstract public function renderHovercard(
PhabricatorHovercardView $hovercard,
PHUIHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$object,
$data);

View file

@ -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.'),
);
}

View file

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

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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')

View file

@ -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.

View file

@ -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
=================

View file

@ -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() {

View file

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

View file

@ -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;
}
}

View file

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

View file

@ -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, '');

View file

@ -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() {

View file

@ -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,

View file

@ -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;
}

View file

@ -17,8 +17,9 @@
"globals": {
"JX": false,
"Raphael": false,
"d3": false,
"__DEV__": false
},
"browser": true
}

View file

@ -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 {

View file

@ -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 {

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

View file

@ -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;
}

View file

@ -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;
}

View 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