1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +01:00

(stable) Promote 2016 Week 7

This commit is contained in:
epriestley 2016-02-12 16:22:14 -08:00
commit 70c679110e
113 changed files with 4153 additions and 1068 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -7,18 +7,18 @@
*/
return array(
'names' => array(
'core.pkg.css' => 'e33b14a4',
'core.pkg.js' => 'ef5e33db',
'core.pkg.css' => 'b59766ad',
'core.pkg.js' => 'd7daa6d8',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
'differential.pkg.js' => '5c2ba922',
'differential.pkg.js' => 'd0cd0df6',
'diffusion.pkg.css' => 'f45955ed',
'diffusion.pkg.js' => '3a9a8bfa',
'maniphest.pkg.css' => '4845691a',
'maniphest.pkg.js' => '949a7498',
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
'rsrc/css/aphront/dark-console.css' => '6378ef3d',
'rsrc/css/aphront/dialog-view.css' => 'be0e3a46',
'rsrc/css/aphront/dialog-view.css' => 'b4334e08',
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
'rsrc/css/aphront/multi-column.css' => 'fd18389d',
@ -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' => 'c4467133',
'rsrc/css/application/base/standard-page-view.css' => 'e709f6d0',
'rsrc/css/application/chatlog/chatlog.css' => 'd295b020',
'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
'rsrc/css/application/config/config-options.css' => '0ede4c9b',
@ -93,7 +93,7 @@ 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-card-view.css' => '9c3631e5',
'rsrc/css/application/project/project-card-view.css' => '9418c97d',
'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',
@ -106,7 +106,7 @@ 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' => '5c7025bf',
'rsrc/css/core/z-index.css' => '5b6fcf3f',
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
'rsrc/css/font/font-aleo.css' => '8bdb2835',
'rsrc/css/font/font-awesome.css' => 'c43323c5',
@ -126,7 +126,7 @@ return array(
'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-crumbs-view.css' => '79d536e5',
'rsrc/css/phui/phui-document-pro.css' => '8799acf7',
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
'rsrc/css/phui/phui-document.css' => '9c71d2bf',
@ -143,20 +143,21 @@ 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' => 'fe594a65',
'rsrc/css/phui/phui-object-item-list-view.css' => 'be31c3a7',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-profile-menu.css' => 'ab4fcf5f',
'rsrc/css/phui/phui-profile-menu.css' => 'f709256c',
'rsrc/css/phui/phui-property-list-view.css' => '27b2849e',
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
'rsrc/css/phui/phui-segment-bar-view.css' => '46342871',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => '888cedb8',
'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' => 'b4322ca7',
'rsrc/css/phui/workboards/phui-workpanel.css' => 'e1bd8d04',
'rsrc/css/phui/workboards/phui-workboard.css' => 'e9e56029',
'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96',
'rsrc/css/phui/workboards/phui-workpanel.css' => 'a78c0661',
'rsrc/css/sprite-login.css' => '60e8560e',
'rsrc/css/sprite-menu.css' => '9dd65b92',
'rsrc/css/sprite-tokens.css' => '4f399012',
@ -414,7 +415,11 @@ return array(
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
'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' => '48470f95',
'rsrc/js/application/projects/WorkboardBoard.js' => '52291776',
'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f',
'rsrc/js/application/projects/WorkboardColumn.js' => 'f05d6e5d',
'rsrc/js/application/projects/WorkboardController.js' => '55baf5ed',
'rsrc/js/application/projects/behavior-project-boards.js' => '14a1faae',
'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',
@ -446,9 +451,9 @@ return array(
'rsrc/js/application/uiexample/gesture-example.js' => '558829c2',
'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' => '8905523d',
'rsrc/js/core/FileUpload.js' => '477359c8',
'rsrc/js/core/DragAndDropFileUpload.js' => '81f182b5',
'rsrc/js/core/DraggableList.js' => '5a13c79f',
'rsrc/js/core/FileUpload.js' => '680ea2c8',
'rsrc/js/core/Hovercard.js' => '1bd28176',
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
@ -506,14 +511,14 @@ return array(
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
'rsrc/js/phuix/PHUIXFormControl.js' => '8fba1997',
'rsrc/js/phuix/PHUIXFormControl.js' => 'a7763e11',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
),
'symbols' => array(
'almanac-css' => 'dbb9b3af',
'aphront-bars' => '231ac33c',
'aphront-dark-console-css' => '6378ef3d',
'aphront-dialog-view-css' => 'be0e3a46',
'aphront-dialog-view-css' => 'b4334e08',
'aphront-list-filter-view-css' => '5d6f0526',
'aphront-multi-column-view-css' => 'fd18389d',
'aphront-panel-view-css' => '8427b78d',
@ -654,7 +659,7 @@ return array(
'javelin-behavior-phui-profile-menu' => '12884df9',
'javelin-behavior-policy-control' => 'd0c516d5',
'javelin-behavior-policy-rule-editor' => '5e9f347c',
'javelin-behavior-project-boards' => '48470f95',
'javelin-behavior-project-boards' => '14a1faae',
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
'javelin-behavior-recurring-edit' => '5f1c4d5f',
@ -721,6 +726,10 @@ return array(
'javelin-view-renderer' => '6c2b09a2',
'javelin-view-visitor' => 'efe49472',
'javelin-websocket' => 'e292eaf4',
'javelin-workboard-board' => '52291776',
'javelin-workboard-card' => 'c587b80f',
'javelin-workboard-column' => 'f05d6e5d',
'javelin-workboard-controller' => '55baf5ed',
'javelin-workflow' => '5b2e3e2b',
'lightbox-attachment-css' => '7acac05d',
'maniphest-batch-editor' => 'b0f0b6d5',
@ -741,11 +750,11 @@ return array(
'phabricator-core-css' => '5b3563c8',
'phabricator-countdown-css' => 'e7544472',
'phabricator-dashboard-css' => 'eb458607',
'phabricator-drag-and-drop-file-upload' => 'ad10aeac',
'phabricator-draggable-list' => '8905523d',
'phabricator-drag-and-drop-file-upload' => '81f182b5',
'phabricator-draggable-list' => '5a13c79f',
'phabricator-fatal-config-template-css' => '8e6c6fcd',
'phabricator-feed-css' => 'ecd4ec57',
'phabricator-file-upload' => '477359c8',
'phabricator-file-upload' => '680ea2c8',
'phabricator-filetree-view-css' => 'fccf9f82',
'phabricator-flag-css' => '5337623f',
'phabricator-keyboard-shortcut' => '1ae869f2',
@ -764,7 +773,7 @@ return array(
'phabricator-side-menu-view-css' => '3a3d9f41',
'phabricator-slowvote-css' => 'da0afb1b',
'phabricator-source-code-view-css' => 'cbeef983',
'phabricator-standard-page-view' => 'c4467133',
'phabricator-standard-page-view' => 'e709f6d0',
'phabricator-textareautils' => '9e54692d',
'phabricator-title' => 'df5e11d2',
'phabricator-tooltip' => '6323f942',
@ -779,7 +788,7 @@ return array(
'phabricator-uiexample-reactor-select' => 'a155550f',
'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
'phabricator-zindex-css' => '5c7025bf',
'phabricator-zindex-css' => '5b6fcf3f',
'phame-css' => '1dbbacf9',
'pholio-css' => '95174bdd',
'pholio-edit-css' => '3ad9d1ee',
@ -799,7 +808,7 @@ return array(
'phui-calendar-list-css' => 'c1c7f338',
'phui-calendar-month-css' => '476be7e0',
'phui-chart-css' => '6bf6f78e',
'phui-crumbs-view-css' => '414406b5',
'phui-crumbs-view-css' => '79d536e5',
'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => '9c71d2bf',
'phui-document-view-pro-css' => '8799acf7',
@ -819,32 +828,33 @@ return array(
'phui-inline-comment-view-css' => '0fdb3667',
'phui-list-view-css' => '9da2aa00',
'phui-object-box-css' => '407eaf5a',
'phui-object-item-list-view-css' => 'fe594a65',
'phui-object-item-list-view-css' => 'be31c3a7',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
'phui-profile-menu-css' => 'ab4fcf5f',
'phui-profile-menu-css' => 'f709256c',
'phui-property-list-view-css' => '27b2849e',
'phui-remarkup-preview-css' => '1a8f2591',
'phui-segment-bar-view-css' => '46342871',
'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8',
'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' => 'b4322ca7',
'phui-workpanel-view-css' => 'e1bd8d04',
'phui-workboard-view-css' => 'e9e56029',
'phui-workcard-view-css' => '3646fb96',
'phui-workpanel-view-css' => 'a78c0661',
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => '8cf6d262',
'phuix-autocomplete' => '9196fb06',
'phuix-dropdown-menu' => 'bd4c8dca',
'phuix-form-control-view' => '8fba1997',
'phuix-form-control-view' => 'a7763e11',
'phuix-icon-view' => 'bff6884b',
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
'policy-transaction-detail-css' => '82100a43',
'ponder-view-css' => '7b0df4da',
'project-card-view-css' => '9c3631e5',
'project-card-view-css' => '9418c97d',
'project-view-css' => '4693497c',
'releeph-core' => '9b3c5733',
'releeph-preview-branch' => 'b7a6f4a5',
@ -956,6 +966,15 @@ return array(
'javelin-dom',
'javelin-history',
),
'14a1faae' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'javelin-workboard-controller',
),
'1ad0a787' => array(
'javelin-install',
'javelin-reactor',
@ -1133,11 +1152,6 @@ return array(
'javelin-dom',
'javelin-workflow',
),
'477359c8' => array(
'javelin-install',
'javelin-dom',
'phabricator-notification',
),
47830651 => array(
'javelin-behavior',
'javelin-dom',
@ -1154,15 +1168,6 @@ 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',
@ -1209,6 +1214,15 @@ return array(
'javelin-dom',
'javelin-reactor-dom',
),
52291776 => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
),
'5359e785' => array(
'javelin-install',
'javelin-util',
@ -1239,6 +1253,16 @@ return array(
'javelin-vector',
'javelin-dom',
),
'55baf5ed' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-drag-and-drop-file-upload',
'javelin-workboard-board',
),
'56a1ca03' => array(
'javelin-behavior',
'javelin-behavior-device',
@ -1265,6 +1289,14 @@ return array(
'javelin-dom',
'javelin-stratcom',
),
'5a13c79f' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'5b2e3e2b' => array(
'javelin-stratcom',
'javelin-request',
@ -1331,6 +1363,11 @@ return array(
'javelin-request',
'javelin-workflow',
),
'680ea2c8' => array(
'javelin-install',
'javelin-dom',
'phabricator-notification',
),
'6882e80a' => array(
'javelin-dom',
),
@ -1450,6 +1487,14 @@ return array(
'javelin-vector',
'javelin-stratcom',
),
'81f182b5' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-dom',
'javelin-uri',
'phabricator-file-upload',
),
'834a1173' => array(
'javelin-behavior',
'javelin-scrollbar',
@ -1487,14 +1532,6 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'8905523d' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'8a41885b' => array(
'javelin-install',
'javelin-dom',
@ -1525,10 +1562,6 @@ return array(
'javelin-stratcom',
'javelin-install',
),
'8fba1997' => array(
'javelin-install',
'javelin-dom',
),
'901935ef' => array(
'javelin-behavior',
'javelin-dom',
@ -1622,6 +1655,13 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'a7763e11' => array(
'javelin-install',
'javelin-dom',
),
'a78c0661' => array(
'phui-workcard-view-css',
),
'a80d0378' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1674,14 +1714,6 @@ return array(
'javelin-util',
'phabricator-busy',
),
'ad10aeac' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-dom',
'javelin-uri',
'phabricator-file-upload',
),
'b064af76' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1786,6 +1818,9 @@ return array(
'javelin-dom',
'javelin-vector',
),
'c587b80f' => array(
'javelin-install',
),
'c72aa091' => array(
'javelin-behavior',
'javelin-dom',
@ -1918,9 +1953,6 @@ return array(
'javelin-dom',
'phabricator-prefab',
),
'e1bd8d04' => array(
'phui-workcard-view-css',
),
'e1d25dfb' => array(
'javelin-behavior',
'javelin-stratcom',
@ -2023,6 +2055,10 @@ return array(
'javelin-workflow',
'javelin-json',
),
'f05d6e5d' => array(
'javelin-install',
'javelin-workboard-card',
),
'f411b6ae' => array(
'javelin-behavior',
'javelin-stratcom',

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};
UPDATE {$NAMESPACE}_maniphest.maniphest_task
SET properties = '{}' WHERE properties = '';

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
DROP projectPHIDs;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
DROP attached;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
ADD points DOUBLE;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_project.project
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_project.project
SET properties = '{}' WHERE properties = '';

View file

@ -1,29 +1,3 @@
<?php
echo pht('Migrating task dependencies to edges...')."\n";
$table = new ManiphestTask();
$table->openTransaction();
foreach (new LiskMigrationIterator($table) as $task) {
$id = $task->getID();
echo pht('Task %d: ', $id);
$deps = $task->getAttachedPHIDs(ManiphestTaskPHIDType::TYPECONST);
if (!$deps) {
echo "-\n";
continue;
}
$editor = new PhabricatorEdgeEditor();
foreach ($deps as $dep) {
$editor->addEdge(
$task->getPHID(),
ManiphestTaskDependsOnTaskEdgeType::EDGECONST,
$dep);
}
$editor->save();
echo pht('OKAY')."\n";
}
$table->saveTransaction();
echo pht('Done.')."\n";
// From 2013-2016, this migration moved dependent tasks to edges.

View file

@ -1,28 +1,3 @@
<?php
echo pht('Migrating task revisions to edges...')."\n";
$table = new ManiphestTask();
$table->establishConnection('w');
foreach (new LiskMigrationIterator($table) as $task) {
$id = $task->getID();
echo pht('Task %d: ', $id);
$revs = $task->getAttachedPHIDs(DifferentialRevisionPHIDType::TYPECONST);
if (!$revs) {
echo "-\n";
continue;
}
$editor = new PhabricatorEdgeEditor();
foreach ($revs as $rev) {
$editor->addEdge(
$task->getPHID(),
ManiphestTaskHasRevisionEdgeType::EDGECONST,
$rev);
}
$editor->save();
echo pht('OKAY')."\n";
}
echo pht('Done.')."\n";
// From 2013-2016, this migration moved revisions attached to tasks to edges.

View file

@ -129,7 +129,7 @@ try {
throw new Exception(
pht(
'Invalid device name ("%s"). There is no device with this name.',
$device->getName()));
$device_name));
}
// We're authenticated as a device, but we're going to read the user out of

View file

@ -241,6 +241,7 @@ phutil_register_library_map(array(
'ConduitPHIDParameterType' => 'applications/conduit/parametertype/ConduitPHIDParameterType.php',
'ConduitParameterType' => 'applications/conduit/parametertype/ConduitParameterType.php',
'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php',
'ConduitPointsParameterType' => 'applications/conduit/parametertype/ConduitPointsParameterType.php',
'ConduitProjectListParameterType' => 'applications/conduit/parametertype/ConduitProjectListParameterType.php',
'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php',
'ConduitResultSearchEngineExtension' => 'applications/conduit/query/ConduitResultSearchEngineExtension.php',
@ -1299,6 +1300,7 @@ phutil_register_library_map(array(
'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php',
'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php',
'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php',
'ManiphestPointsConfigOptionType' => 'applications/maniphest/config/ManiphestPointsConfigOptionType.php',
'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php',
'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php',
'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php',
@ -1339,6 +1341,7 @@ phutil_register_library_map(array(
'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php',
'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php',
'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php',
'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.php',
'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php',
'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php',
'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php',
@ -1522,6 +1525,8 @@ phutil_register_library_map(array(
'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php',
'PHUIRemarkupPreviewPanel' => 'view/phui/PHUIRemarkupPreviewPanel.php',
'PHUIRemarkupView' => 'infrastructure/markup/view/PHUIRemarkupView.php',
'PHUISegmentBarSegmentView' => 'view/phui/PHUISegmentBarSegmentView.php',
'PHUISegmentBarView' => 'view/phui/PHUISegmentBarView.php',
'PHUISpacesNamespaceContextView' => 'applications/spaces/view/PHUISpacesNamespaceContextView.php',
'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php',
'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php',
@ -1815,6 +1820,8 @@ phutil_register_library_map(array(
'PhabricatorBinariesSetupCheck' => 'applications/config/check/PhabricatorBinariesSetupCheck.php',
'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php',
'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php',
'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php',
'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php',
'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php',
'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
@ -2203,6 +2210,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditEngineExtension.php',
'PhabricatorEditEngineExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php',
'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php',
'PhabricatorEditEngineSelectCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineSelectCommentAction.php',
@ -2810,6 +2818,7 @@ phutil_register_library_map(array(
'PhabricatorPhurlURLViewController' => 'applications/phurl/controller/PhabricatorPhurlURLViewController.php',
'PhabricatorPirateEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php',
'PhabricatorPlatformSite' => 'aphront/site/PhabricatorPlatformSite.php',
'PhabricatorPointsEditField' => 'applications/transactions/editfield/PhabricatorPointsEditField.php',
'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php',
'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php',
'PhabricatorPolicyApplication' => 'applications/policy/application/PhabricatorPolicyApplication.php',
@ -2859,6 +2868,7 @@ phutil_register_library_map(array(
'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php',
'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php',
'PhabricatorProjectBoardDisableController' => 'applications/project/controller/PhabricatorProjectBoardDisableController.php',
'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php',
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
@ -2879,12 +2889,14 @@ phutil_register_library_map(array(
'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php',
'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php',
'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php',
'PhabricatorProjectCoverController' => 'applications/project/controller/PhabricatorProjectCoverController.php',
'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php',
'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php',
'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php',
'PhabricatorProjectCustomFieldStringIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php',
'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php',
'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php',
'PhabricatorProjectDefaultController' => 'applications/project/controller/PhabricatorProjectDefaultController.php',
'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php',
'PhabricatorProjectDetailsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php',
'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php',
@ -2894,6 +2906,7 @@ phutil_register_library_map(array(
'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php',
'PhabricatorProjectHeraldAdapter' => 'applications/project/herald/PhabricatorProjectHeraldAdapter.php',
'PhabricatorProjectHeraldFieldGroup' => 'applications/project/herald/PhabricatorProjectHeraldFieldGroup.php',
'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php',
'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php',
'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php',
'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php',
@ -2924,6 +2937,7 @@ phutil_register_library_map(array(
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php',
'PhabricatorProjectPointsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectPointsProfilePanel.php',
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
'PhabricatorProjectProfilePanelEngine' => 'applications/project/engine/PhabricatorProjectProfilePanelEngine.php',
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
@ -3365,6 +3379,7 @@ phutil_register_library_map(array(
'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php',
'PhabricatorUserCardView' => 'applications/people/view/PhabricatorUserCardView.php',
'PhabricatorUserConfigOptions' => 'applications/people/config/PhabricatorUserConfigOptions.php',
'PhabricatorUserConfiguredCustomField' => 'applications/people/customfield/PhabricatorUserConfiguredCustomField.php',
'PhabricatorUserConfiguredCustomFieldStorage' => 'applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php',
@ -3817,7 +3832,6 @@ 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',
@ -4221,7 +4235,7 @@ phutil_register_library_map(array(
'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitIntListParameterType' => 'ConduitListParameterType',
'ConduitIntParameterType' => 'ConduitListParameterType',
'ConduitIntParameterType' => 'ConduitParameterType',
'ConduitListParameterType' => 'ConduitParameterType',
'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector',
'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException',
@ -4230,6 +4244,7 @@ phutil_register_library_map(array(
'ConduitPHIDParameterType' => 'ConduitParameterType',
'ConduitParameterType' => 'Phobject',
'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitPointsParameterType' => 'ConduitParameterType',
'ConduitProjectListParameterType' => 'ConduitListParameterType',
'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
@ -5447,6 +5462,7 @@ phutil_register_library_map(array(
'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestNameIndex' => 'ManiphestDAO',
'ManiphestPointsConfigOptionType' => 'PhabricatorConfigJSONOptionType',
'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType',
'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand',
'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
@ -5503,6 +5519,7 @@ phutil_register_library_map(array(
'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver',
'ManiphestTaskPHIDType' => 'PhabricatorPHIDType',
'ManiphestTaskPoints' => 'Phobject',
'ManiphestTaskPriority' => 'ManiphestConstants',
'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskPriorityHeraldAction' => 'HeraldAction',
@ -5702,6 +5719,8 @@ phutil_register_library_map(array(
'PHUIPropertyListView' => 'AphrontView',
'PHUIRemarkupPreviewPanel' => 'AphrontTagView',
'PHUIRemarkupView' => 'AphrontView',
'PHUISegmentBarSegmentView' => 'AphrontTagView',
'PHUISegmentBarView' => 'AphrontTagView',
'PHUISpacesNamespaceContextView' => 'AphrontView',
'PHUIStatusItemView' => 'AphrontTagView',
'PHUIStatusListView' => 'AphrontTagView',
@ -6043,6 +6062,8 @@ phutil_register_library_map(array(
'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider',
'PhabricatorBoardLayoutEngine' => 'Phobject',
'PhabricatorBoardRenderingEngine' => 'Phobject',
'PhabricatorBoardResponseEngine' => 'Phobject',
'PhabricatorBot' => 'PhabricatorDaemon',
'PhabricatorBotChannel' => 'PhabricatorBotTarget',
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
@ -6500,6 +6521,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineExtension' => 'Phobject',
'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction',
@ -7198,6 +7220,7 @@ phutil_register_library_map(array(
'PhabricatorPhurlURLViewController' => 'PhabricatorPhurlController',
'PhabricatorPirateEnglishTranslation' => 'PhutilTranslation',
'PhabricatorPlatformSite' => 'PhabricatorSite',
'PhabricatorPointsEditField' => 'PhabricatorEditField',
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
'PhabricatorPolicy' => array(
'PhabricatorPolicyDAO',
@ -7271,6 +7294,7 @@ phutil_register_library_map(array(
'PhabricatorProjectApplication' => 'PhabricatorApplication',
'PhabricatorProjectArchiveController' => 'PhabricatorProjectController',
'PhabricatorProjectBoardController' => 'PhabricatorProjectController',
'PhabricatorProjectBoardDisableController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
@ -7302,12 +7326,14 @@ phutil_register_library_map(array(
),
'PhabricatorProjectController' => 'PhabricatorController',
'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase',
'PhabricatorProjectCoverController' => 'PhabricatorProjectController',
'PhabricatorProjectCustomField' => 'PhabricatorCustomField',
'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
'PhabricatorProjectCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectDefaultController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
'PhabricatorProjectDetailsProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectEditController' => 'PhabricatorProjectController',
@ -7317,6 +7343,7 @@ phutil_register_library_map(array(
'PhabricatorProjectHeraldAction' => 'HeraldAction',
'PhabricatorProjectHeraldAdapter' => 'HeraldAdapter',
'PhabricatorProjectHeraldFieldGroup' => 'HeraldFieldGroup',
'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PhabricatorProjectIconSet' => 'PhabricatorIconSet',
'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType',
'PhabricatorProjectListController' => 'PhabricatorProjectController',
@ -7346,6 +7373,7 @@ phutil_register_library_map(array(
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorProjectPanelController' => 'PhabricatorProjectController',
'PhabricatorProjectPointsProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProfilePanelEngine' => 'PhabricatorProfilePanelEngine',
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
@ -7865,6 +7893,7 @@ phutil_register_library_map(array(
'PhabricatorFulltextInterface',
),
'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField',
'PhabricatorUserCardView' => 'AphrontTagView',
'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorUserConfiguredCustomField' => array(
'PhabricatorUserCustomField',
@ -8452,7 +8481,6 @@ phutil_register_library_map(array(
'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability',
'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability',
'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'ProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase',

View file

@ -266,7 +266,6 @@ final class PhabricatorCalendarEventSearchEngine
$list = new PHUIObjectItemListView();
foreach ($events as $event) {
$duration = '';
$event_date_info = $this->getEventDateLabel($event);
$creator_handle = $handles[$event->getUserPHID()];
$attendees = array();
@ -275,18 +274,6 @@ final class PhabricatorCalendarEventSearchEngine
$attendees[] = $invitee->getInviteePHID();
}
$attendees = pht(
'Attending: %s',
$viewer->renderHandleList($attendees)
->setAsInline(1)
->render());
if (strlen($event->getDuration()) > 0) {
$duration = pht(
'Duration: %s',
$event->getDuration());
}
if ($event->getIsGhostEvent()) {
$title_text = $event->getMonogram()
.' ('
@ -302,9 +289,25 @@ final class PhabricatorCalendarEventSearchEngine
->setObject($event)
->setHeader($title_text)
->setHref($event->getURI())
->addAttribute($event_date_info)
->addAttribute($attendees)
->addIcon('none', $duration);
->addAttribute($event_date_info);
if ($attendees) {
$attending = pht(
'Attending: %s',
$viewer->renderHandleList($attendees)
->setAsInline(1)
->render());
$item->addAttribute($attending);
}
if (strlen($event->getDuration()) > 0) {
$duration = pht(
'Duration: %s',
$event->getDuration());
$item->addIcon('none', $duration);
}
$list->addItem($item);
}

View file

@ -1,7 +1,7 @@
<?php
final class ConduitIntParameterType
extends ConduitListParameterType {
extends ConduitParameterType {
protected function getParameterValue(array $request, $key) {
$value = parent::getParameterValue($request, $key);

View file

@ -0,0 +1,49 @@
<?php
final class ConduitPointsParameterType
extends ConduitParameterType {
protected function getParameterValue(array $request, $key) {
$value = parent::getParameterValue($request, $key);
if (($value !== null) && !is_numeric($value)) {
$this->raiseValidationException(
$request,
$key,
pht('Expected numeric points value, got something else.'));
}
if ($value !== null) {
$value = (double)$value;
if ($value < 0) {
$this->raiseValidationException(
$request,
$key,
pht('Point values must be nonnegative.'));
}
}
return $value;
}
protected function getParameterTypeName() {
return 'points';
}
protected function getParameterFormatDescriptions() {
return array(
pht('A nonnegative number, or null.'),
);
}
protected function getParameterExamples() {
return array(
'null',
'0',
'1',
'15',
'0.5',
);
}
}

View file

@ -92,10 +92,9 @@ final class DiffusionCommitController extends DiffusionController {
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $user);
require_celerity_resource('phabricator-remarkup-css');
$headsup_view = id(new PHUIHeaderView())
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')));
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')))
->setSubheader(pht('Commit: %s', $commit->getCommitIdentifier()));
$headsup_actions = $this->renderHeadsupActionList($commit, $repository);

View file

@ -147,7 +147,7 @@ abstract class DiffusionController extends PhabricatorController {
$crumb_list[] = $crumb;
$stable_commit = $drequest->getStableCommit();
$commit_name = $repository->formatCommitName($stable_commit);
$commit_name = $repository->formatCommitName($stable_commit, $local = true);
$commit_uri = $repository->getCommitURI($stable_commit);
if ($spec['tags']) {
@ -171,8 +171,7 @@ abstract class DiffusionController extends PhabricatorController {
if ($spec['commit']) {
$crumb = id(new PHUICrumbView())
->setName($commit_name)
->setHref($commit_uri);
->setName($commit_name);
$crumb_list[] = $crumb;
return $crumb_list;
}

View file

@ -427,11 +427,14 @@ final class DiffusionServeController extends DiffusionController {
'$PATH'));
}
// NOTE: We do not set HTTP_CONTENT_ENCODING here, because we already
// decompressed the request when we read the request body, so the body is
// just plain data with no encoding.
$env = array(
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
'QUERY_STRING' => $query_string,
'CONTENT_TYPE' => $request->getHTTPHeader('Content-Type'),
'HTTP_CONTENT_ENCODING' => $request->getHTTPHeader('Content-Encoding'),
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
'GIT_PROJECT_ROOT' => $repository_root,
'GIT_HTTP_EXPORT_ALL' => '1',

View file

@ -7,6 +7,7 @@ final class PhabricatorFileThumbnailTransform
const TRANSFORM_PINBOARD = 'pinboard';
const TRANSFORM_THUMBGRID = 'thumbgrid';
const TRANSFORM_PREVIEW = 'preview';
const TRANSFORM_WORKCARD = 'workcard';
private $name;
private $key;
@ -73,6 +74,11 @@ final class PhabricatorFileThumbnailTransform
->setName(pht('Preview (220px)'))
->setKey(self::TRANSFORM_PREVIEW)
->setDimensions(220, null),
id(new self())
->setName(pht('Workcard (526px)'))
->setKey(self::TRANSFORM_WORKCARD)
->setScaleUp(true)
->setDimensions(526, null),
);
}

View file

@ -14,6 +14,7 @@ abstract class HeraldAdapter extends Phobject {
const CONDITION_IS_ME = 'me';
const CONDITION_IS_NOT_ME = '!me';
const CONDITION_REGEXP = 'regexp';
const CONDITION_NOT_REGEXP = '!regexp';
const CONDITION_RULE = 'conditions';
const CONDITION_NOT_RULE = '!conditions';
const CONDITION_EXISTS = 'exists';
@ -322,6 +323,7 @@ abstract class HeraldAdapter extends Phobject {
self::CONDITION_IS_ME => pht('is myself'),
self::CONDITION_IS_NOT_ME => pht('is not myself'),
self::CONDITION_REGEXP => pht('matches regexp'),
self::CONDITION_NOT_REGEXP => pht('does not match regexp'),
self::CONDITION_RULE => pht('matches:'),
self::CONDITION_NOT_RULE => pht('does not match:'),
self::CONDITION_EXISTS => pht('exists'),
@ -364,16 +366,18 @@ abstract class HeraldAdapter extends Phobject {
switch ($condition_type) {
case self::CONDITION_CONTAINS:
// "Contains" can take an array of strings, as in "Any changed
// filename" for diffs.
case self::CONDITION_NOT_CONTAINS:
// "Contains and "does not contain" can take an array of strings, as in
// "Any changed filename" for diffs.
$result_if_match = ($condition_type == self::CONDITION_CONTAINS);
foreach ((array)$field_value as $value) {
if (stripos($value, $condition_value) !== false) {
return true;
return $result_if_match;
}
}
return false;
case self::CONDITION_NOT_CONTAINS:
return (stripos($field_value, $condition_value) === false);
return !$result_if_match;
case self::CONDITION_IS:
return ($field_value == $condition_value);
case self::CONDITION_IS_NOT:
@ -427,6 +431,9 @@ abstract class HeraldAdapter extends Phobject {
case self::CONDITION_NEVER:
return false;
case self::CONDITION_REGEXP:
case self::CONDITION_NOT_REGEXP:
$result_if_match = ($condition_type == self::CONDITION_REGEXP);
foreach ((array)$field_value as $value) {
// We add the 'S' flag because we use the regexp multiple times.
// It shouldn't cause any troubles if the flag is already there
@ -437,10 +444,10 @@ abstract class HeraldAdapter extends Phobject {
pht('Regular expression is not valid!'));
}
if ($result) {
return true;
return $result_if_match;
}
}
return false;
return !$result_if_match;
case self::CONDITION_REGEXP_PAIR:
// Match a JSON-encoded pair of regular expressions against a
// dictionary. The first regexp must match the dictionary key, and the
@ -509,6 +516,7 @@ abstract class HeraldAdapter extends Phobject {
switch ($condition_type) {
case self::CONDITION_REGEXP:
case self::CONDITION_NOT_REGEXP:
$ok = @preg_match($condition_value, '');
if ($ok === false) {
throw new HeraldInvalidConditionException(

View file

@ -47,6 +47,7 @@ abstract class HeraldField extends Phobject {
HeraldAdapter::CONDITION_IS,
HeraldAdapter::CONDITION_IS_NOT,
HeraldAdapter::CONDITION_REGEXP,
HeraldAdapter::CONDITION_NOT_REGEXP,
);
case self::STANDARD_PHID:
return array(
@ -76,12 +77,16 @@ abstract class HeraldField extends Phobject {
case self::STANDARD_TEXT_LIST:
return array(
HeraldAdapter::CONDITION_CONTAINS,
HeraldAdapter::CONDITION_NOT_CONTAINS,
HeraldAdapter::CONDITION_REGEXP,
HeraldAdapter::CONDITION_NOT_REGEXP,
);
case self::STANDARD_TEXT_MAP:
return array(
HeraldAdapter::CONDITION_CONTAINS,
HeraldAdapter::CONDITION_NOT_CONTAINS,
HeraldAdapter::CONDITION_REGEXP,
HeraldAdapter::CONDITION_NOT_REGEXP,
HeraldAdapter::CONDITION_REGEXP_PAIR,
);
}

View file

@ -59,6 +59,7 @@ final class ManiphestGetTaskTransactionsConduitAPIMethod
$results[$task_id][] = array(
'taskID' => $task_id,
'transactionID' => $transaction->getID(),
'transactionPHID' => $transaction->getPHID(),
'transactionType' => $transaction->getTransactionType(),
'oldValue' => $transaction->getOldValue(),

View file

@ -0,0 +1,10 @@
<?php
final class ManiphestPointsConfigOptionType
extends PhabricatorConfigJSONOptionType {
public function validateOption(PhabricatorConfigOption $option, $value) {
ManiphestTaskPoints::validateConfiguration($value);
}
}

View file

@ -111,6 +111,7 @@ final class PhabricatorManiphestConfigOptions
'name' => pht('Invalid'),
'name.full' => pht('Closed, Invalid'),
'closed' => true,
'claim' => false,
'prefixes' => array(
'invalidate',
'invalidates',
@ -126,6 +127,7 @@ final class PhabricatorManiphestConfigOptions
'transaction.icon' => 'fa-files-o',
'special' => ManiphestTaskStatus::SPECIAL_DUPLICATE,
'closed' => true,
'claim' => false,
),
'spite' => array(
'name' => pht('Spite'),
@ -202,6 +204,9 @@ The keys you can provide in a specification are:
tasks can not be created or edited to have this status. Existing tasks with
this status will not be affected, but you can batch edit them or let them
die out on their own.
- `claim` //Optional bool.// By default, closing an unassigned task claims
it. You can set this to `false` to disable this behavior for a particular
status.
Statuses will appear in the UI in the order specified. Note the status marked
`special` as `duplicate` is not settable directly and will not appear in UI
@ -255,6 +260,40 @@ EOTEXT
);
$fields_json = id(new PhutilJSON())->encodeFormatted($fields_example);
$points_type = 'custom:ManiphestPointsConfigOptionType';
$points_example_1 = array(
'enabled' => true,
'label' => pht('Story Points'),
'action' => pht('Change Story Points'),
);
$points_json_1 = id(new PhutilJSON())->encodeFormatted($points_example_1);
$points_example_2 = array(
'enabled' => true,
'label' => pht('Estimated Hours'),
'action' => pht('Change Estimate'),
);
$points_json_2 = id(new PhutilJSON())->encodeFormatted($points_example_2);
$points_description = $this->deformat(pht(<<<EOTEXT
Activates a points field on tasks. You can use points for estimation or
planning. If configured, points will appear on workboards.
To activate points, set this value to a map with these keys:
- `enabled` //Optional bool.// Use `true` to enable points, or
`false` to disable them.
- `label` //Optional string.// Label for points, like "Story Points" or
"Estimated Hours". If omitted, points will be called "Points".
- `action` //Optional string.// Label for the action which changes points
in Maniphest, like "Change Estimate". If omitted, the action will
be called "Change Points".
See the example below for a starting point.
EOTEXT
));
return array(
$this->newOption('maniphest.custom-field-definitions', 'wild', array())
->setSummary(pht('Custom Maniphest fields.'))
@ -336,7 +375,11 @@ EOTEXT
'"Needs Triage" panel on the home page. You should adjust this if '.
'you adjust priorities using `%s`.',
'maniphest.priorities')),
$this->newOption('maniphest.points', $points_type, array())
->setSummary(pht('Configure point values for tasks.'))
->setDescription($points_description)
->addExample($points_json_1, pht('Points Config'))
->addExample($points_json_2, pht('Hours Config')),
);
}

View file

@ -0,0 +1,41 @@
<?php
final class ManiphestTaskPoints extends Phobject {
public static function getIsEnabled() {
$config = self::getPointsConfig();
return idx($config, 'enabled');
}
public static function getPointsLabel() {
$config = self::getPointsConfig();
return idx($config, 'label', pht('Points'));
}
public static function getPointsActionLabel() {
$config = self::getPointsConfig();
return idx($config, 'action', pht('Change Points'));
}
private static function getPointsConfig() {
return PhabricatorEnv::getEnvConfig('maniphest.points');
}
public static function validateConfiguration($config) {
if (!is_array($config)) {
throw new Exception(
pht(
'Configuration is not valid. Maniphest points configuration must '.
'be a dictionary.'));
}
PhutilTypeSpec::checkMap(
$config,
array(
'enabled' => 'optional bool',
'label' => 'optional string',
'action' => 'optional string',
));
}
}

View file

@ -155,6 +155,10 @@ final class ManiphestTaskStatus extends ManiphestConstants {
return false;
}
public static function isClaimStatus($status) {
return self::getStatusAttribute($status, 'claim', true);
}
public static function isClosedStatus($status) {
return !self::isOpenStatus($status);
}
@ -279,6 +283,7 @@ final class ManiphestTaskStatus extends ManiphestConstants {
'suffixes' => 'optional list<string>',
'keywords' => 'optional list<string>',
'disabled' => 'optional bool',
'claim' => 'optional bool',
));
}

View file

@ -229,6 +229,15 @@ final class ManiphestTaskDetailController extends ManiphestController {
$view->addProperty(pht('Author'), $author);
if (ManiphestTaskPoints::getIsEnabled()) {
$points = $task->getPoints();
if ($points !== null) {
$view->addProperty(
ManiphestTaskPoints::getPointsLabel(),
$task->getPoints());
}
}
$source = $task->getOriginalEmailSource();
if ($source) {
$subject = '[T'.$task->getID().'] '.$task->getTitle();

View file

@ -9,6 +9,7 @@ final class ManiphestTaskEditController extends ManiphestController {
->addContextParameter('responseType')
->addContextParameter('columnPHID')
->addContextParameter('order')
->addContextParameter('visiblePHIDs')
->buildResponse();
}

View file

@ -77,7 +77,7 @@ final class ManiphestEditEngine
$owner_value = array($this->getViewer()->getPHID());
}
return array(
$fields = array(
id(new PhabricatorHandlesEditField())
->setKey('parent')
->setLabel(pht('Parent Task'))
@ -149,18 +149,37 @@ final class ManiphestEditEngine
->setValue($object->getPriority())
->setOptions($priority_map)
->setCommentActionLabel(pht('Change Priority')),
id(new PhabricatorRemarkupEditField())
->setKey('description')
->setLabel(pht('Description'))
->setDescription(pht('Task description.'))
->setConduitDescription(pht('Update the task description.'))
->setConduitTypeDescription(pht('New task description.'))
->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
->setValue($object->getDescription())
->setPreviewPanel(
id(new PHUIRemarkupPreviewPanel())
->setHeader(pht('Description Preview'))),
);
if (ManiphestTaskPoints::getIsEnabled()) {
$points_label = ManiphestTaskPoints::getPointsLabel();
$action_label = ManiphestTaskPoints::getPointsActionLabel();
$fields[] = id(new PhabricatorPointsEditField())
->setKey('points')
->setLabel($points_label)
->setDescription(pht('Point value of the task.'))
->setConduitDescription(pht('Change the task point value.'))
->setConduitTypeDescription(pht('New task point value.'))
->setTransactionType(ManiphestTransaction::TYPE_POINTS)
->setIsCopyable(true)
->setValue($object->getPoints())
->setCommentActionLabel($action_label);
}
$fields[] = id(new PhabricatorRemarkupEditField())
->setKey('description')
->setLabel(pht('Description'))
->setDescription(pht('Task description.'))
->setConduitDescription(pht('Update the task description.'))
->setConduitTypeDescription(pht('New task description.'))
->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
->setValue($object->getDescription())
->setPreviewPanel(
id(new PHUIRemarkupPreviewPanel())
->setHeader(pht('Description Preview')));
return $fields;
}
private function getTaskStatusMap(ManiphestTask $task) {
@ -270,7 +289,11 @@ final class ManiphestEditEngine
$viewer = $request->getViewer();
$column_phid = $request->getStr('columnPHID');
$order = $request->getStr('order');
$visible_phids = $request->getStrList('visiblePHIDs');
if (!$visible_phids) {
$visible_phids = array();
}
$column = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
@ -280,93 +303,15 @@ final class ManiphestEditEngine
return new Aphront404Response();
}
// 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.
$board_phid = $column->getProjectPHID();
$object_phid = $task->getPHID();
// 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())
return id(new PhabricatorBoardResponseEngine())
->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 = !array_intersect_key($board_phids, $project_map);
$positions = id(new PhabricatorProjectColumnPositionQuery())
->setViewer($viewer)
->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) {
// TODO: This is a little bit awkward, because PHP and JS use
// slightly different sort order parameters to achieve the same
// effect. It would be good to unify this a bit at some point.
$sort_map = array();
foreach ($positions as $position) {
$sort_map[$position->getObjectPHID()] = array(
-$position->getSequence(),
$position->getID(),
);
}
} else {
$sort_map = mpull(
$column_tasks,
'getPrioritySortVector',
'getPHID');
}
$data = array(
'removeFromBoard' => $remove_card,
'sortMap' => $sort_map,
);
// TODO: This should just use HandlePool once we get through the EditEngine
// transition.
$owner = null;
if ($task->getOwnerPHID()) {
$owner = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array($task->getOwnerPHID()))
->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,
);
return id(new AphrontAjaxResponse())->setContent($payload);
->setBoardPHID($board_phid)
->setObjectPHID($object_phid)
->setVisiblePHIDs($visible_phids)
->buildResponse();
}

View file

@ -28,6 +28,8 @@ final class ManiphestTransactionEditor
$types[] = ManiphestTransaction::TYPE_UNBLOCK;
$types[] = ManiphestTransaction::TYPE_PARENT;
$types[] = ManiphestTransaction::TYPE_COLUMN;
$types[] = ManiphestTransaction::TYPE_COVER_IMAGE;
$types[] = ManiphestTransaction::TYPE_POINTS;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@ -66,6 +68,14 @@ final class ManiphestTransactionEditor
return $xaction->getOldValue();
case ManiphestTransaction::TYPE_SUBPRIORITY:
return $object->getSubpriority();
case ManiphestTransaction::TYPE_COVER_IMAGE:
return $object->getCoverImageFilePHID();
case ManiphestTransaction::TYPE_POINTS:
$points = $object->getPoints();
if ($points !== null) {
$points = (double)$points;
}
return $points;
case ManiphestTransaction::TYPE_MERGED_INTO:
case ManiphestTransaction::TYPE_MERGED_FROM:
return null;
@ -92,10 +102,20 @@ final class ManiphestTransactionEditor
case ManiphestTransaction::TYPE_MERGED_INTO:
case ManiphestTransaction::TYPE_MERGED_FROM:
case ManiphestTransaction::TYPE_UNBLOCK:
case ManiphestTransaction::TYPE_COVER_IMAGE:
return $xaction->getNewValue();
case ManiphestTransaction::TYPE_PARENT:
case ManiphestTransaction::TYPE_COLUMN:
return $xaction->getNewValue();
case ManiphestTransaction::TYPE_POINTS:
$value = $xaction->getNewValue();
if (!strlen($value)) {
$value = null;
}
if ($value !== null) {
$value = (double)$value;
}
return $value;
}
}
@ -161,6 +181,35 @@ final class ManiphestTransactionEditor
case ManiphestTransaction::TYPE_MERGED_INTO:
$object->setStatus(ManiphestTaskStatus::getDuplicateStatus());
return;
case ManiphestTransaction::TYPE_COVER_IMAGE:
$file_phid = $xaction->getNewValue();
if ($file_phid) {
$file = id(new PhabricatorFileQuery())
->setViewer($this->getActor())
->withPHIDs(array($file_phid))
->executeOne();
} else {
$file = null;
}
if (!$file || !$file->isTransformableImage()) {
$object->setProperty('cover.filePHID', null);
$object->setProperty('cover.thumbnailPHID', null);
return;
}
$xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD;
$xform = PhabricatorFileTransform::getTransformByKey($xform_key)
->executeTransform($file);
$object->setProperty('cover.filePHID', $file->getPHID());
$object->setProperty('cover.thumbnailPHID', $xform->getPHID());
return;
case ManiphestTransaction::TYPE_POINTS:
$object->setPoints($xaction->getNewValue());
return;
case ManiphestTransaction::TYPE_MERGED_FROM:
case ManiphestTransaction::TYPE_PARENT:
case ManiphestTransaction::TYPE_COLUMN:
@ -819,6 +868,65 @@ final class ManiphestTransactionEditor
}
}
break;
case ManiphestTransaction::TYPE_COVER_IMAGE:
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!$new) {
continue;
}
if ($new === $old) {
continue;
}
$file = id(new PhabricatorFileQuery())
->setViewer($this->getActor())
->withPHIDs(array($new))
->executeOne();
if (!$file) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('File "%s" is not valid.', $new),
$xaction);
continue;
}
if (!$file->isTransformableImage()) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('File "%s" is not a valid image file.', $new),
$xaction);
continue;
}
}
break;
case ManiphestTransaction::TYPE_POINTS:
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
if (strlen($new) && !is_numeric($new)) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('Points value must be numeric or empty.'),
$xaction);
continue;
}
if ((double)$new < 0) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('Points value must be nonnegative.'),
$xaction);
continue;
}
}
break;
}
return $errors;
@ -863,8 +971,11 @@ final class ManiphestTransactionEditor
// If the task is not assigned, not being assigned, currently open, and
// being closed, try to assign the actor as the owner.
if ($is_unassigned && !$any_assign && $is_open && $is_closing) {
$is_claim = ManiphestTaskStatus::isClaimStatus($new_status);
// Don't assign the actor if they aren't a real user.
if ($actor_phid) {
// Don't claim the task if the status is configured to not claim.
if ($actor_phid && $is_claim) {
$results[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_OWNER)
->setNewValue($actor_phid);
@ -941,5 +1052,19 @@ final class ManiphestTransactionEditor
->executeOne();
}
protected function extractFilePHIDsFromCustomTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_COVER_IMAGE:
$phids[] = $xaction->getNewValue();
break;
}
return $phids;
}
}

View file

@ -34,19 +34,16 @@ final class ManiphestTask extends ManiphestDAO
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
protected $editPolicy = PhabricatorPolicies::POLICY_USER;
protected $projectPHIDs = array();
protected $ownerOrdering;
protected $spacePHID;
protected $properties = array();
protected $points;
private $subscriberPHIDs = self::ATTACHABLE;
private $groupByProjectPHID = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
private $edgeProjectPHIDs = self::ATTACHABLE;
// TODO: This field is unused and should eventually be removed.
protected $attached = array();
public static function initializeNewTask(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
@ -71,9 +68,7 @@ final class ManiphestTask extends ManiphestDAO
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'ccPHIDs' => self::SERIALIZATION_JSON,
'attached' => self::SERIALIZATION_JSON,
'projectPHIDs' => self::SERIALIZATION_JSON,
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'ownerPHID' => 'phid?',
@ -86,11 +81,7 @@ final class ManiphestTask extends ManiphestDAO
'ownerOrdering' => 'text64?',
'originalEmailSource' => 'text255?',
'subpriority' => 'double',
// T6203/NULLABILITY
// This should not be nullable. It's going away soon anyway.
'ccPHIDs' => 'text?',
'points' => 'double?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
@ -141,10 +132,6 @@ final class ManiphestTask extends ManiphestDAO
ManiphestTaskDependedOnByTaskEdgeType::EDGECONST);
}
public function getAttachedPHIDs($type) {
return array_keys(idx($this->attached, $type, array()));
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(ManiphestTaskPHIDType::TYPECONST);
}
@ -207,11 +194,70 @@ final class ManiphestTask extends ManiphestDAO
return ManiphestTaskStatus::isClosedStatus($this->getStatus());
}
public function getPrioritySortVector() {
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function getCoverImageFilePHID() {
return idx($this->properties, 'cover.filePHID');
}
public function getCoverImageThumbnailPHID() {
return idx($this->properties, 'cover.thumbnailPHID');
}
public function getWorkboardOrderVectors() {
return array(
$this->getPriority(),
-$this->getSubpriority(),
$this->getID(),
PhabricatorProjectColumn::ORDER_PRIORITY => array(
(int)-$this->getPriority(),
(double)-$this->getSubpriority(),
(int)-$this->getID(),
),
);
}
private function comparePriorityTo(ManiphestTask $other) {
$upri = $this->getPriority();
$vpri = $other->getPriority();
if ($upri != $vpri) {
return ($upri - $vpri);
}
$usub = $this->getSubpriority();
$vsub = $other->getSubpriority();
if ($usub != $vsub) {
return ($usub - $vsub);
}
$uid = $this->getID();
$vid = $other->getID();
if ($uid != $vid) {
return ($uid - $vid);
}
return 0;
}
public function isLowerPriorityThan(ManiphestTask $other) {
return ($this->comparePriorityTo($other) < 0);
}
public function isHigherPriorityThan(ManiphestTask $other) {
return ($this->comparePriorityTo($other) > 0);
}
public function getWorkboardProperties() {
return array(
'status' => $this->getStatus(),
'points' => (double)$this->getPoints(),
);
}
@ -416,6 +462,10 @@ final class ManiphestTask extends ManiphestDAO
->setKey('priority')
->setType('map<string, wild>')
->setDescription(pht('Information about task priority.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('points')
->setType('points')
->setDescription(pht('Point value of the task.')),
);
}
@ -442,6 +492,7 @@ final class ManiphestTask extends ManiphestDAO
'ownerPHID' => $this->getOwnerPHID(),
'status' => $status_info,
'priority' => $priority_info,
'points' => $this->getPoints(),
);
}

View file

@ -16,6 +16,8 @@ final class ManiphestTransaction
const TYPE_UNBLOCK = 'unblock';
const TYPE_PARENT = 'parent';
const TYPE_COLUMN = 'column';
const TYPE_COVER_IMAGE = 'cover-image';
const TYPE_POINTS = 'points';
// NOTE: this type is deprecated. Keep it around for legacy installs
// so any transactions render correctly.
@ -162,11 +164,27 @@ final class ManiphestTransaction
sort($new_cols);
return ($old_cols === $new_cols);
case self::TYPE_COVER_IMAGE:
// At least for now, don't show these.
return true;
case self::TYPE_POINTS:
if (!ManiphestTaskPoints::getIsEnabled()) {
return true;
}
}
return parent::shouldHide();
}
public function shouldHideForMail(array $xactions) {
switch ($this->getTransactionType()) {
case self::TYPE_POINTS:
return true;
}
return parent::shouldHideForMail($xactions);
}
public function shouldHideForFeed() {
switch ($this->getTransactionType()) {
case self::TYPE_UNBLOCK:
@ -177,6 +195,8 @@ final class ManiphestTransaction
return true;
}
break;
case self::TYPE_POINTS:
return true;
}
return parent::shouldHideForFeed();
@ -620,6 +640,23 @@ final class ManiphestTransaction
$this->renderHandleList($new));
break;
case self::TYPE_POINTS:
if ($old === null) {
return pht(
'%s set the point value for this task to %s.',
$this->renderHandleLink($author_phid),
$new);
} else if ($new === null) {
return pht(
'%s removed the point value for this task.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s changed the point value for this task from %s to %s.',
$this->renderHandleLink($author_phid),
$old,
$new);
}
}

View file

@ -130,7 +130,10 @@ final class PhabricatorPeopleProfileViewController
->setViewer($viewer)
->withMemberPHIDs(array($user->getPHID()))
->needImages(true)
->withStatus(PhabricatorProjectQuery::STATUS_OPEN)
->withStatuses(
array(
PhabricatorProjectStatus::STATUS_ACTIVE,
))
->execute();
$header = id(new PHUIHeaderView())

View file

@ -25,6 +25,7 @@ final class PeopleHovercardEngineExtension
->setViewer($viewer)
->withPHIDs($phids)
->needAvailability(true)
->needProfileImage(true)
->needProfile(true)
->needBadges(true)
->execute();
@ -47,69 +48,12 @@ final class PeopleHovercardEngineExtension
return;
}
$hovercard->setTitle($user->getUsername());
$user_card = id(new PhabricatorUserCardView())
->setProfile($user)
->setViewer($viewer);
$profile = $user->getUserProfile();
$detail = $user->getRealName();
if ($profile->getTitle()) {
$detail .= ' - '.$profile->getTitle();
}
$hovercard->setDetail($detail);
$hovercard->appendChild($user_card);
if ($user->getIsDisabled()) {
$hovercard->addField(pht('Account'), pht('Disabled'));
} else if (!$user->isUserActivated()) {
$hovercard->addField(pht('Account'), pht('Not Activated'));
} else if (PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorCalendarApplication',
$viewer)) {
$hovercard->addField(
pht('Status'),
$user->getAvailabilityDescription($viewer));
}
$hovercard->addField(
pht('User Since'),
phabricator_date($user->getDateCreated(), $viewer));
if ($profile->getBlurb()) {
$hovercard->addField(pht('Blurb'),
id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(120)
->truncateString($profile->getBlurb()));
}
$badges = $this->buildBadges($user, $viewer);
foreach ($badges as $badge) {
$hovercard->addBadge($badge);
}
}
private function buildBadges(
PhabricatorUser $user,
$viewer) {
$class = 'PhabricatorBadgesApplication';
$items = array();
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();
foreach ($badges as $badge) {
$items[] = id(new PHUIBadgeMiniView())
->setIcon($badge->getIcon())
->setHeader($badge->getName())
->setQuality($badge->getQuality());
}
}
}
return $items;
}
}

View file

@ -0,0 +1,151 @@
<?php
final class PhabricatorUserCardView extends AphrontTagView {
private $profile;
private $viewer;
private $tag;
public function setProfile(PhabricatorUser $profile) {
$this->profile = $profile;
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';
if ($this->profile->getIsDisabled()) {
$classes[] = 'project-card-grey';
} else {
$classes[] = 'project-card-blue';
}
return array(
'class' => implode($classes, ' '),
);
}
protected function getTagContent() {
$user = $this->profile;
$profile = $user->loadUserProfile();
$picture = $user->getProfileImageURI();
$viewer = $this->viewer;
require_celerity_resource('project-card-view-css');
$profile_icon = PhabricatorPeopleIconSet::getIconIcon($profile->getIcon());
$profile_title = $profile->getDisplayTitle();
$tag = id(new PHUITagView())
->setIcon($profile_icon)
->setName($profile_title)
->addClass('project-view-header-tag')
->setType(PHUITagView::TYPE_SHADE);
$header = id(new PHUIHeaderView())
->setHeader(array($user->getFullName(), $tag))
->setUser($viewer)
->setImage($picture);
$body = array();
$body[] = $this->addItem(
pht('User Since'),
phabricator_date($profile->getDateCreated(), $viewer));
if (PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorCalendarApplication',
$viewer)) {
$availability = $user->getAvailabilityDescription($viewer);
$body[] = $this->addItem(pht('Status'), $availability);
}
$badges = $this->buildBadges($user, $viewer);
if ($badges) {
$badges = id(new PHUIBadgeBoxView())
->addItems($badges)
->setCollapsed(true);
$body[] = phutil_tag(
'div',
array(
'class' => 'phui-hovercard-body-item hovercard-badges',
),
$badges);
}
$body = phutil_tag(
'div',
array(
'class' => 'project-card-body',
),
$body);
$card = phutil_tag(
'div',
array(
'class' => 'project-card-inner',
),
array(
$header,
$body,
));
return $card;
}
private function addItem($label, $value) {
$item = array(
phutil_tag('strong', array(), $label),
': ',
phutil_tag('span', array(), $value),
);
return phutil_tag_div('project-card-item', $item);
}
private function buildBadges(
PhabricatorUser $user,
$viewer) {
$class = 'PhabricatorBadgesApplication';
$items = array();
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();
foreach ($badges as $badge) {
$items[] = id(new PHUIBadgeMiniView())
->setIcon($badge->getIcon())
->setHeader($badge->getName())
->setQuality($badge->getQuality());
}
}
}
return $items;
}
}

View file

@ -230,6 +230,10 @@ final class PhabricatorPolicyQuery
->setViewer($viewer)
->withMemberPHIDs(array($viewer->getPHID()))
->withIsMilestone(false)
->withStatuses(
array(
PhabricatorProjectStatus::STATUS_ACTIVE,
))
->setLimit($default_limit)
->execute();
$default_projects = mpull($default_projects, null, 'getPHID');

View file

@ -65,13 +65,12 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
=> $this->getPanelRouting('PhabricatorProjectPanelController'),
'subprojects/(?P<id>[1-9]\d*)/'
=> 'PhabricatorProjectSubprojectsController',
'milestones/(?P<id>[1-9]\d*)/'
=> 'PhabricatorProjectMilestonesController',
'board/(?P<id>[1-9]\d*)/'.
'(?P<filter>filter/)?'.
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorProjectBoardViewController',
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
'cover/' => 'PhabricatorProjectCoverController',
'board/(?P<projectID>[1-9]\d*)/' => array(
'edit/(?:(?P<id>\d+)/)?'
=> 'PhabricatorProjectColumnEditController',
@ -83,6 +82,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
=> 'PhabricatorProjectBoardImportController',
'reorder/'
=> 'PhabricatorProjectBoardReorderController',
'disable/'
=> 'PhabricatorProjectBoardDisableController',
),
'update/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
=> 'PhabricatorProjectUpdateController',
@ -93,6 +94,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
=> 'PhabricatorProjectSilenceController',
'warning/(?P<id>[1-9]\d*)/'
=> 'PhabricatorProjectSubprojectWarningController',
'default/(?P<projectID>[1-9]\d*)/(?P<target>[^/]+)/'
=> 'PhabricatorProjectDefaultController',
),
'/tag/' => array(
'(?P<slug>[^/]+)/' => 'PhabricatorProjectViewController',

View file

@ -0,0 +1,61 @@
<?php
final class PhabricatorProjectBoardDisableController
extends PhabricatorProjectBoardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$project_id = $request->getURIData('projectID');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($project_id))
->executeOne();
if (!$project) {
return new Aphront404Response();
}
if (!$project->getHasWorkboard()) {
return new Aphront404Response();
}
$this->setProject($project);
$id = $project->getID();
$board_uri = $this->getApplicationURI("board/{$id}/");
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD)
->setNewValue(0);
id(new PhabricatorProjectTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())
->setURI($board_uri);
}
return $this->newDialog()
->setTitle(pht('Disable Workboard'))
->appendParagraph(
pht(
'Disabling a workboard hides the board. Objects on the board '.
'will no longer be annotated with column names in other '.
'applications. You can restore the workboard later.'))
->addCancelButton($board_uri)
->addSubmitButton(pht('Disable Workboard'));
}
}

View file

@ -50,6 +50,10 @@ final class PhabricatorProjectBoardImportController
if ($import_column->isHidden()) {
continue;
}
if ($import_column->getProxy()) {
continue;
}
$new_column = PhabricatorProjectColumn::initializeNewColumn($viewer)
->setSequence($import_column->getSequence())
->setProjectPHID($project->getPHID())

View file

@ -7,9 +7,7 @@ final class PhabricatorProjectBoardViewController
private $id;
private $slug;
private $handles;
private $queryKey;
private $filter;
private $sortKey;
private $showHidden;
@ -57,10 +55,18 @@ final class PhabricatorProjectBoardViewController
$search_engine->getQueryResultsPageURI($saved->getQueryKey())));
}
$query_key = $request->getURIData('queryKey');
if (!$query_key) {
$query_key = 'open';
$query_key = $this->getDefaultFilter($project);
$request_query = $request->getStr('filter');
if (strlen($request_query)) {
$query_key = $request_query;
}
$uri_query = $request->getURIData('queryKey');
if (strlen($uri_query)) {
$query_key = $uri_query;
}
$this->queryKey = $query_key;
$custom_query = null;
@ -122,18 +128,37 @@ final class PhabricatorProjectBoardViewController
->setViewer($viewer)
->setBoardPHIDs(array($board_phid))
->setObjectPHIDs(array_keys($tasks))
->setFetchAllBoards(true)
->executeLayout();
$columns = $layout_engine->getColumns($board_phid);
if (!$columns) {
if (!$columns || !$project->getHasWorkboard()) {
$has_normal_columns = false;
foreach ($columns as $column) {
if (!$column->getProxyPHID()) {
$has_normal_columns = true;
break;
}
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
if (!$can_edit) {
$content = $this->buildNoAccessContent($project);
if (!$has_normal_columns) {
if (!$can_edit) {
$content = $this->buildNoAccessContent($project);
} else {
$content = $this->buildInitializeContent($project);
}
} else {
$content = $this->buildInitializeContent($project);
if (!$can_edit) {
$content = $this->buildDisabledContent($project);
} else {
$content = $this->buildEnableContent($project);
}
}
if ($content instanceof AphrontResponse) {
@ -213,35 +238,16 @@ final class PhabricatorProjectBoardViewController
$board = id(new PHUIWorkboardView())
->setUser($viewer)
->setID($board_id);
$behavior_config = array(
'boardID' => $board_id,
'projectPHID' => $project->getPHID(),
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
'createURI' => $this->getCreateURI(),
'order' => $this->sortKey,
);
$this->initBehavior(
'project-boards',
$behavior_config);
$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);
->setID($board_id)
->addSigil('jx-workboard')
->setMetadata(
array(
'boardPHID' => $project->getPHID(),
));
$visible_columns = array();
$column_phids = array();
$visible_phids = array();
foreach ($columns as $column) {
if (!$this->showHidden) {
if ($column->isHidden()) {
@ -268,11 +274,40 @@ final class PhabricatorProjectBoardViewController
$column_tasks = array_select_keys($column_tasks, array_keys($tasks));
}
$column_phid = $column->getPHID();
$visible_columns[$column_phid] = $column;
$column_phids[$column_phid] = $column_tasks;
foreach ($column_tasks as $phid => $task) {
$visible_phids[$phid] = $phid;
}
}
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
->setViewer($viewer)
->setObjects(array_select_keys($tasks, $visible_phids))
->setEditMap($task_can_edit_map)
->setExcludedProjectPHIDs($select_phids);
$templates = array();
$column_maps = array();
$all_tasks = array();
foreach ($visible_columns as $column_phid => $column) {
$column_tasks = $column_phids[$column_phid];
$panel = id(new PHUIWorkpanelView())
->setHeader($column->getDisplayName())
->setSubHeader($column->getDisplayType())
->addSigil('workpanel');
$proxy = $column->getProxy();
if ($proxy) {
$proxy_id = $proxy->getID();
$href = $this->getApplicationURI("view/{$proxy_id}/");
$panel->setHref($href);
}
$header_icon = $column->getHeaderIcon();
if ($header_icon) {
$panel->setHeaderIcon($header_icon);
@ -290,14 +325,17 @@ final class PhabricatorProjectBoardViewController
$column_menu = $this->buildColumnMenu($project, $column);
$panel->addHeaderAction($column_menu);
$tag_id = celerity_generate_unique_node_id();
$tag_content_id = celerity_generate_unique_node_id();
$count_tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setShade(PHUITagView::COLOR_BLUE)
->setID($tag_id)
->setName(phutil_tag('span', array('id' => $tag_content_id), '-'))
->addSigil('column-points')
->setName(
javelin_tag(
'span',
array(
'sigil' => 'column-points-content',
),
pht('-')))
->setStyle('display: none');
$panel->setHeaderTag($count_tag);
@ -311,38 +349,52 @@ final class PhabricatorProjectBoardViewController
->setMetadata(
array(
'columnPHID' => $column->getPHID(),
'countTagID' => $tag_id,
'countTagContentID' => $tag_content_id,
'pointLimit' => $column->getPointLimit(),
));
foreach ($column_tasks as $task) {
$owner = null;
if ($task->getOwnerPHID()) {
$owner = $this->handles[$task->getOwnerPHID()];
}
$can_edit = idx($task_can_edit_map, $task->getPHID(), false);
$object_phid = $task->getPHID();
$handles = array_select_keys($all_handles, $task->getProjectPHIDs());
$card = $rendering_engine->renderCard($object_phid);
$templates[$object_phid] = hsprintf('%s', $card->getItem());
$column_maps[$column_phid][] = $object_phid;
$cards->addItem(id(new ProjectBoardTaskCard())
->setViewer($viewer)
->setProjectHandles($handles)
->setTask($task)
->setOwner($owner)
->setCanEdit($can_edit)
->getItem());
$all_tasks[$object_phid] = $task;
}
$panel->setCards($cards);
$board->addPanel($panel);
}
$behavior_config = array(
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
'createURI' => $this->getCreateURI(),
'uploadURI' => '/file/dropupload/',
'coverURI' => $this->getApplicationURI('cover/'),
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
'pointsEnabled' => ManiphestTaskPoints::getIsEnabled(),
'boardPHID' => $project->getPHID(),
'order' => $this->sortKey,
'templateMap' => $templates,
'columnMaps' => $column_maps,
'orderMaps' => mpull($all_tasks, 'getWorkboardOrderVectors'),
'propertyMaps' => mpull($all_tasks, 'getWorkboardProperties'),
'boardID' => $board_id,
'projectPHID' => $project->getPHID(),
);
$this->initBehavior('project-boards', $behavior_config);
$sort_menu = $this->buildSortMenu(
$viewer,
$project,
$this->sortKey);
$filter_menu = $this->buildFilterMenu(
$viewer,
$project,
$custom_query,
$search_engine,
$query_key);
@ -361,6 +413,9 @@ final class PhabricatorProjectBoardViewController
->addClass('project-board-wrapper');
$nav = $this->getProfileMenu();
$divider = id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_DIVIDER);
$fullscreen = $this->buildFullscreenMenu();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Workboard'));
@ -368,7 +423,9 @@ final class PhabricatorProjectBoardViewController
$crumbs->addAction($sort_menu);
$crumbs->addAction($filter_menu);
$crumbs->addAction($divider);
$crumbs->addAction($manage_menu);
$crumbs->addAction($fullscreen);
return $this->newPage()
->setTitle(
@ -397,20 +454,49 @@ final class PhabricatorProjectBoardViewController
$this->showHidden = $request->getBool('hidden');
$this->id = $project->getID();
$sort_key = $request->getStr('order');
switch ($sort_key) {
$sort_key = $this->getDefaultSort($project);
$request_sort = $request->getStr('order');
if ($this->isValidSort($request_sort)) {
$sort_key = $request_sort;
}
$this->sortKey = $sort_key;
}
private function getDefaultSort(PhabricatorProject $project) {
$default_sort = $project->getDefaultWorkboardSort();
if ($this->isValidSort($default_sort)) {
return $default_sort;
}
return PhabricatorProjectColumn::DEFAULT_ORDER;
}
private function getDefaultFilter(PhabricatorProject $project) {
$default_filter = $project->getDefaultWorkboardFilter();
if (strlen($default_filter)) {
return $default_filter;
}
return 'open';
}
private function isValidSort($sort) {
switch ($sort) {
case PhabricatorProjectColumn::ORDER_NATURAL:
case PhabricatorProjectColumn::ORDER_PRIORITY:
break;
default:
$sort_key = PhabricatorProjectColumn::DEFAULT_ORDER;
break;
return true;
}
$this->sortKey = $sort_key;
return false;
}
private function buildSortMenu(
PhabricatorUser $viewer,
PhabricatorProject $project,
$sort_key) {
$sort_icon = id(new PHUIIconView())
@ -441,6 +527,24 @@ final class PhabricatorProjectBoardViewController
$items[] = $item;
}
$id = $project->getID();
$save_uri = "default/{$id}/sort/";
$save_uri = $this->getApplicationURI($save_uri);
$save_uri = $this->getURIWithState($save_uri, $force = true);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$items[] = id(new PhabricatorActionView())
->setIcon('fa-floppy-o')
->setName(pht('Save as Default'))
->setHref($save_uri)
->setWorkflow(true)
->setDisabled(!$can_edit);
$sort_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($items as $item) {
@ -448,7 +552,7 @@ final class PhabricatorProjectBoardViewController
}
$sort_button = id(new PHUIListItemView())
->setName(pht('Sort: %s', $active_order))
->setName($active_order)
->setIcon('fa-sort-amount-asc')
->setHref('#')
->addSigil('boards-dropdown-menu')
@ -459,8 +563,10 @@ final class PhabricatorProjectBoardViewController
return $sort_button;
}
private function buildFilterMenu(
PhabricatorUser $viewer,
PhabricatorProject $project,
$custom_query,
PhabricatorApplicationSearchEngine $engine,
$query_key) {
@ -503,18 +609,40 @@ final class PhabricatorProjectBoardViewController
$uri = $engine->getQueryResultsPageURI($key);
}
$uri = $this->getURIWithState($uri);
$uri = $this->getURIWithState($uri)
->setQueryParam('filter', null);
$item->setHref($uri);
$items[] = $item;
}
$id = $project->getID();
$filter_uri = $this->getApplicationURI("board/{$id}/filter/");
$filter_uri = $this->getURIWithState($filter_uri, $force = true);
$items[] = id(new PhabricatorActionView())
->setIcon('fa-cog')
->setHref($this->getApplicationURI('board/'.$this->id.'/filter/'))
->setHref($filter_uri)
->setWorkflow(true)
->setName(pht('Advanced Filter...'));
$save_uri = "default/{$id}/filter/";
$save_uri = $this->getApplicationURI($save_uri);
$save_uri = $this->getURIWithState($save_uri, $force = true);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$items[] = id(new PhabricatorActionView())
->setIcon('fa-floppy-o')
->setName(pht('Save as Default'))
->setHref($save_uri)
->setWorkflow(true)
->setDisabled(!$can_edit);
$filter_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($items as $item) {
@ -522,7 +650,7 @@ final class PhabricatorProjectBoardViewController
}
$filter_button = id(new PHUIListItemView())
->setName(pht('Filter: %s', $active_filter))
->setName($active_filter)
->setIcon('fa-search')
->setHref('#')
->addSigil('boards-dropdown-menu')
@ -541,6 +669,12 @@ final class PhabricatorProjectBoardViewController
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $project->getID();
$disable_uri = $this->getApplicationURI("board/{$id}/disable/");
$add_uri = $this->getApplicationURI("board/{$id}/edit/");
$reorder_uri = $this->getApplicationURI("board/{$id}/reorder/");
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
@ -551,14 +685,14 @@ final class PhabricatorProjectBoardViewController
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-plus')
->setName(pht('Add Column'))
->setHref($this->getApplicationURI('board/'.$this->id.'/edit/'))
->setHref($add_uri)
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-exchange')
->setName(pht('Reorder Columns'))
->setHref($this->getApplicationURI('board/'.$this->id.'/reorder/'))
->setHref($reorder_uri)
->setDisabled(!$can_edit)
->setWorkflow(true);
@ -592,6 +726,13 @@ final class PhabricatorProjectBoardViewController
->setHref($batch_edit_uri)
->setDisabled(!$can_batch_edit);
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-ban')
->setName(pht('Disable Workboard'))
->setHref($disable_uri)
->setWorkflow(true)
->setDisabled(!$can_edit);
$manage_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($manage_items as $item) {
@ -599,18 +740,38 @@ final class PhabricatorProjectBoardViewController
}
$manage_button = id(new PHUIListItemView())
->setName(pht('Manage Board'))
->setIcon('fa-cog')
->setHref('#')
->addSigil('boards-dropdown-menu')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Manage'),
'align' => 'S',
'items' => hsprintf('%s', $manage_menu),
));
return $manage_button;
}
private function buildFullscreenMenu() {
$up = id(new PHUIListItemView())
->setIcon('fa-arrows-alt')
->setHref('#')
->addClass('phui-workboard-expand-icon')
->addSigil('jx-toggle-class')
->addSigil('has-tooltip')
->setMetaData(array(
'tip' => pht('Fullscreen'),
'map' => array(
'phabricator-standard-page' => 'phui-workboard-fullscreen',
),
));
return $up;
}
private function buildColumnMenu(
PhabricatorProject $project,
PhabricatorProjectColumn $column) {
@ -639,6 +800,7 @@ final class PhabricatorProjectBoardViewController
->setMetadata(
array(
'columnPHID' => $column->getPHID(),
'boardPHID' => $project->getPHID(),
'projectPHID' => $default_phid,
));
@ -711,22 +873,31 @@ final class PhabricatorProjectBoardViewController
* the rest of the board state persistent. If no URI is provided, this method
* starts with the request URI.
*
* @param string|null URI to add state parameters to.
* @return PhutilURI URI with state parameters.
* @param string|null URI to add state parameters to.
* @param bool True to explicitly include all state.
* @return PhutilURI URI with state parameters.
*/
private function getURIWithState($base = null) {
private function getURIWithState($base = null, $force = false) {
$project = $this->getProject();
if ($base === null) {
$base = $this->getRequest()->getRequestURI();
}
$base = new PhutilURI($base);
if ($this->sortKey != PhabricatorProjectColumn::DEFAULT_ORDER) {
if ($force || ($this->sortKey != $this->getDefaultSort($project))) {
$base->setQueryParam('order', $this->sortKey);
} else {
$base->setQueryParam('order', null);
}
if ($force || ($this->queryKey != $this->getDefaultFilter($project))) {
$base->setQueryParam('filter', $this->queryKey);
} else {
$base->setQueryParam('filter', null);
}
$base->setQueryParam('hidden', $this->showHidden ? 'true' : null);
return $base;
@ -849,4 +1020,59 @@ final class PhabricatorProjectBoardViewController
->addCancelButton($profile_uri);
}
private function buildEnableContent(PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$id = $project->getID();
$profile_uri = $this->getApplicationURI("profile/{$id}/");
$board_uri = $this->getApplicationURI("board/{$id}/");
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD)
->setNewValue(1);
id(new PhabricatorProjectTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())
->setURI($board_uri);
}
return $this->newDialog()
->setTitle(pht('Workboard Disabled'))
->addHiddenInput('initialize', 1)
->appendParagraph(
pht(
'This workboard has been disabled, but can be restored to its '.
'former glory.'))
->addCancelButton($profile_uri)
->addSubmitButton(pht('Enable Workboard'));
}
private function buildDisabledContent(PhabricatorProject $project) {
$viewer = $this->getViewer();
$id = $project->getID();
$profile_uri = $this->getApplicationURI("profile/{$id}/");
return $this->newDialog()
->setTitle(pht('Workboard Disabled'))
->appendParagraph(
pht(
'This workboard has been disabled, and you do not have permission '.
'to enable it. Only users who can edit this project can restore '.
'the workboard.'))
->addCancelButton($profile_uri);
}
}

View file

@ -120,9 +120,12 @@ final class PhabricatorProjectColumnDetailController
->setActionList($actions);
$limit = $column->getPointLimit();
$properties->addProperty(
pht('Point Limit'),
$limit ? $limit : pht('No Limit'));
if ($limit === null) {
$limit_text = pht('No Limit');
} else {
$limit_text = $limit;
}
$properties->addProperty(pht('Point Limit'), $limit_text);
return $properties;
}

View file

@ -52,42 +52,81 @@ final class PhabricatorProjectColumnHideController
->addCancelButton($view_uri, pht('Okay'));
}
$proxy = $column->getProxy();
if ($request->isFormPost()) {
if ($column->isHidden()) {
$new_status = PhabricatorProjectColumn::STATUS_ACTIVE;
if ($proxy) {
if ($proxy->isArchived()) {
$new_status = PhabricatorProjectStatus::STATUS_ACTIVE;
} else {
$new_status = PhabricatorProjectStatus::STATUS_ARCHIVED;
}
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorProjectTransaction::TYPE_STATUS)
->setNewValue($new_status);
id(new PhabricatorProjectTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($proxy, $xactions);
} else {
$new_status = PhabricatorProjectColumn::STATUS_HIDDEN;
if ($column->isHidden()) {
$new_status = PhabricatorProjectColumn::STATUS_ACTIVE;
} else {
$new_status = PhabricatorProjectColumn::STATUS_HIDDEN;
}
$type_status = PhabricatorProjectColumnTransaction::TYPE_STATUS;
$xactions = array(
id(new PhabricatorProjectColumnTransaction())
->setTransactionType($type_status)
->setNewValue($new_status),
);
$editor = id(new PhabricatorProjectColumnTransactionEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request)
->applyTransactions($column, $xactions);
}
$type_status = PhabricatorProjectColumnTransaction::TYPE_STATUS;
$xactions = array(
id(new PhabricatorProjectColumnTransaction())
->setTransactionType($type_status)
->setNewValue($new_status),
);
$editor = id(new PhabricatorProjectColumnTransactionEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->applyTransactions($column, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
if ($column->isHidden()) {
$title = pht('Show Column');
if ($proxy) {
if ($column->isHidden()) {
$title = pht('Activate and Show Column');
$body = pht(
'This column is hidden because it represents an archived '.
'subproject. Do you want to activate the subproject so the '.
'column is visible again?');
$button = pht('Activate Subproject');
} else {
$title = pht('Archive and Hide Column');
$body = pht(
'This column is visible because it represents an active '.
'subproject. Do you want to hide the column by archiving the '.
'subproject?');
$button = pht('Archive Subproject');
}
} else {
$title = pht('Hide Column');
}
if ($column->isHidden()) {
$body = pht(
'Are you sure you want to show this column?');
} else {
$body = pht(
'Are you sure you want to hide this column? It will no longer '.
'appear on the workboard.');
if ($column->isHidden()) {
$title = pht('Show Column');
$body = pht('Are you sure you want to show this column?');
$button = pht('Show Column');
} else {
$title = pht('Hide Column');
$body = pht(
'Are you sure you want to hide this column? It will no longer '.
'appear on the workboard.');
$button = pht('Hide Column');
}
}
$dialog = $this->newDialog()
@ -96,7 +135,7 @@ final class PhabricatorProjectColumnHideController
->appendChild($body)
->setDisableWorkflowOnCancel(true)
->addCancelButton($view_uri)
->addSubmitButton($title);
->addSubmitButton($button);
foreach ($request->getPassthroughRequestData() as $key => $value) {
$dialog->addHiddenInput($key, $value);

View file

@ -147,4 +147,21 @@ abstract class PhabricatorProjectController extends PhabricatorController {
return $this;
}
protected function newCardResponse($board_phid, $object_phid) {
$viewer = $this->getViewer();
$request = $this->getRequest();
$visible_phids = $request->getStrList('visiblePHIDs');
if (!$visible_phids) {
$visible_phids = array();
}
return id(new PhabricatorBoardResponseEngine())
->setViewer($viewer)
->setBoardPHID($board_phid)
->setObjectPHID($object_phid)
->setVisiblePHIDs($visible_phids)
->buildResponse();
}
}

View file

@ -0,0 +1,53 @@
<?php
final class PhabricatorProjectCoverController
extends PhabricatorProjectController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$request->validateCSRF();
$board_phid = $request->getStr('boardPHID');
$object_phid = $request->getStr('objectPHID');
$file_phid = $request->getStr('filePHID');
$object = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withPHIDs(array($object_phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
return new Aphront404Response();
}
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE)
->setNewValue($file->getPHID());
$editor = id(new ManiphestTransactionEditor())
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
$editor->applyTransactions($object, $xactions);
return $this->newCardResponse($board_phid, $object_phid);
}
}

View file

@ -0,0 +1,90 @@
<?php
final class PhabricatorProjectDefaultController
extends PhabricatorProjectBoardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$project_id = $request->getURIData('projectID');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($project_id))
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
$target = $request->getURIData('target');
switch ($target) {
case 'filter':
$title = pht('Set Board Default Filter');
$body = pht(
'Make the current filter the new default filter for this board? '.
'All users will see the new filter as the default when they view '.
'the board.');
$button = pht('Save Default Filter');
$xaction_value = $request->getStr('filter');
$xaction_type = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER;
break;
case 'sort':
$title = pht('Set Board Default Order');
$body = pht(
'Make the current sort order the new default order for this board? '.
'All users will see the new order as the default when they view '.
'the board.');
$button = pht('Save Default Order');
$xaction_value = $request->getStr('order');
$xaction_type = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT;
break;
default:
return new Aphront404Response();
}
$id = $project->getID();
$view_uri = $this->getApplicationURI("board/{$id}/");
$view_uri = new PhutilURI($view_uri);
foreach ($request->getPassthroughRequestData() as $key => $value) {
$view_uri->setQueryParam($key, $value);
}
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType($xaction_type)
->setNewValue($xaction_value);
id(new PhabricatorProjectTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
$dialog = $this->newDialog()
->setTitle($title)
->appendChild($body)
->setDisableWorkflowOnCancel(true)
->addCancelButton($view_uri)
->addSubmitButton($title);
foreach ($request->getPassthroughRequestData() as $key => $value) {
$dialog->addHiddenInput($key, $value);
}
return $dialog;
}
}

View file

@ -7,13 +7,14 @@ final class PhabricatorProjectMoveController
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$request->validateCSRF();
$column_phid = $request->getStr('columnPHID');
$object_phid = $request->getStr('objectPHID');
$after_phid = $request->getStr('afterPHID');
$before_phid = $request->getStr('beforePHID');
$order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->requireCapabilities(
@ -89,55 +90,13 @@ final class PhabricatorProjectMoveController
'projectPHID' => $column->getProjectPHID(),
));
$task_phids = array();
if ($after_phid) {
$task_phids[] = $after_phid;
}
if ($before_phid) {
$task_phids[] = $before_phid;
}
if ($task_phids && ($order == PhabricatorProjectColumn::ORDER_PRIORITY)) {
$tasks = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withPHIDs($task_phids)
->needProjectPHIDs(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
if (count($tasks) != count($task_phids)) {
return new Aphront404Response();
}
$tasks = mpull($tasks, null, 'getPHID');
$try = array(
array($after_phid, true),
array($before_phid, false),
);
$pri = null;
$sub = null;
foreach ($try as $spec) {
list($task_phid, $is_after) = $spec;
$task = idx($tasks, $task_phid);
if ($task) {
list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
$task,
$is_after);
break;
}
}
if ($pri !== null) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
->setNewValue($pri);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
->setNewValue($sub);
if ($order == PhabricatorProjectColumn::ORDER_PRIORITY) {
$priority_xactions = $this->getPriorityTransactions(
$object,
$after_phid,
$before_phid);
foreach ($priority_xactions as $xaction) {
$xactions[] = $xaction;
}
}
@ -175,56 +134,100 @@ final class PhabricatorProjectMoveController
$editor->applyTransactions($object, $xactions);
$owner = null;
if ($object->getOwnerPHID()) {
$owner = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array($object->getOwnerPHID()))
->executeOne();
return $this->newCardResponse($board_phid, $object_phid);
}
private function getPriorityTransactions(
ManiphestTask $task,
$after_phid,
$before_phid) {
list($after_task, $before_task) = $this->loadPriorityTasks(
$after_phid,
$before_phid);
$must_move = false;
if ($after_task && !$task->isLowerPriorityThan($after_task)) {
$must_move = true;
}
// 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();
if ($before_task && !$task->isHigherPriorityThan($before_task)) {
$must_move = true;
}
$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();
// The move doesn't require a priority change to be valid, so don't
// change the priority since we are not being forced to.
if (!$must_move) {
return array();
}
$try = array(
array($after_task, true),
array($before_task, false),
);
$pri = null;
$sub = null;
foreach ($try as $spec) {
list($task, $is_after) = $spec;
if (!$task) {
continue;
}
list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
$task,
$is_after);
}
$except_phids = array_fuse($except_phids);
$handle_phids = array_fuse($object->getProjectPHIDs());
$handle_phids = array_diff_key($handle_phids, $except_phids);
$xactions = array();
if ($pri !== null) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
->setNewValue($pri);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
->setNewValue($sub);
}
$project_handles = $viewer->loadHandles($handle_phids);
$project_handles = iterator_to_array($project_handles);
return $xactions;
}
$card = id(new ProjectBoardTaskCard())
private function loadPriorityTasks($after_phid, $before_phid) {
$viewer = $this->getViewer();
$task_phids = array();
if ($after_phid) {
$task_phids[] = $after_phid;
}
if ($before_phid) {
$task_phids[] = $before_phid;
}
if (!$task_phids) {
return array(null, null);
}
$tasks = id(new ManiphestTaskQuery())
->setViewer($viewer)
->setTask($object)
->setOwner($owner)
->setCanEdit(true)
->setProjectHandles($project_handles)
->getItem();
->withPHIDs($task_phids)
->execute();
$tasks = mpull($tasks, null, 'getPHID');
$card->addClass('phui-workcard');
if ($after_phid) {
$after_task = idx($tasks, $after_phid);
} else {
$after_task = null;
}
return id(new AphrontAjaxResponse())->setContent(
array('task' => $card));
if ($before_phid) {
$before_task = idx($tasks, $before_phid);
} else {
$before_task = null;
}
return array($after_task, $before_task);
}
}

View file

@ -201,6 +201,10 @@ final class PhabricatorProjectProfileController
->withParentProjectPHIDs(array($project->getPHID()))
->needImages(true)
->withIsMilestone(true)
->withStatuses(
array(
PhabricatorProjectStatus::STATUS_ACTIVE,
))
->setOrder('newest')
->execute();
if (!$milestones) {
@ -244,6 +248,10 @@ final class PhabricatorProjectProfileController
->setViewer($viewer)
->withParentProjectPHIDs(array($project->getPHID()))
->needImages(true)
->withStatuses(
array(
PhabricatorProjectStatus::STATUS_ACTIVE,
))
->withIsMilestone(false)
->setLimit($limit)
->execute();

View file

@ -47,10 +47,12 @@ final class PhabricatorProjectColumnTransactionEditor
case PhabricatorProjectColumnTransaction::TYPE_STATUS:
return $xaction->getNewValue();
case PhabricatorProjectColumnTransaction::TYPE_LIMIT:
if ($xaction->getNewValue()) {
$value = $xaction->getNewValue();
if (strlen($value)) {
return (int)$xaction->getNewValue();
} else {
return null;
}
return null;
}
return parent::getCustomTransactionNewValue($object, $xaction);
@ -104,7 +106,9 @@ final class PhabricatorProjectColumnTransactionEditor
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('Column point limit must be empty, or a positive integer.'),
pht(
'Column point limit must either be empty or a nonnegative '.
'integer.'),
$xaction);
}
}

View file

@ -39,6 +39,9 @@ final class PhabricatorProjectTransactionEditor
$types[] = PhabricatorProjectTransaction::TYPE_LOCKED;
$types[] = PhabricatorProjectTransaction::TYPE_PARENT;
$types[] = PhabricatorProjectTransaction::TYPE_MILESTONE;
$types[] = PhabricatorProjectTransaction::TYPE_HASWORKBOARD;
$types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT;
$types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER;
return $types;
}
@ -65,9 +68,15 @@ final class PhabricatorProjectTransactionEditor
return $object->getColor();
case PhabricatorProjectTransaction::TYPE_LOCKED:
return (int)$object->getIsMembershipLocked();
case PhabricatorProjectTransaction::TYPE_HASWORKBOARD:
return (int)$object->getHasWorkboard();
case PhabricatorProjectTransaction::TYPE_PARENT:
case PhabricatorProjectTransaction::TYPE_MILESTONE:
return null;
case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT:
return $object->getDefaultWorkboardSort();
case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER:
return $object->getDefaultWorkboardFilter();
}
return parent::getCustomTransactionOldValue($object, $xaction);
@ -86,7 +95,11 @@ final class PhabricatorProjectTransactionEditor
case PhabricatorProjectTransaction::TYPE_LOCKED:
case PhabricatorProjectTransaction::TYPE_PARENT:
case PhabricatorProjectTransaction::TYPE_MILESTONE:
case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT:
case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER:
return $xaction->getNewValue();
case PhabricatorProjectTransaction::TYPE_HASWORKBOARD:
return (int)$xaction->getNewValue();
case PhabricatorProjectTransaction::TYPE_SLUGS:
return $this->normalizeSlugs($xaction->getNewValue());
}
@ -131,6 +144,15 @@ final class PhabricatorProjectTransactionEditor
$object->setMilestoneNumber($number);
$object->setParentProjectPHID($xaction->getNewValue());
return;
case PhabricatorProjectTransaction::TYPE_HASWORKBOARD:
$object->setHasWorkboard($xaction->getNewValue());
return;
case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT:
$object->setDefaultWorkboardSort($xaction->getNewValue());
return;
case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER:
$object->setDefaultWorkboardFilter($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
@ -147,11 +169,12 @@ final class PhabricatorProjectTransactionEditor
case PhabricatorProjectTransaction::TYPE_NAME:
// First, add the old name as a secondary slug; this is helpful
// for renames and generally a good thing to do.
if ($old !== null) {
$this->addSlug($object, $old, false);
if (!$this->getIsMilestone()) {
if ($old !== null) {
$this->addSlug($object, $old, false);
}
$this->addSlug($object, $new, false);
}
$this->addSlug($object, $new, false);
return;
case PhabricatorProjectTransaction::TYPE_SLUGS:
$old = $xaction->getOldValue();
@ -172,6 +195,9 @@ final class PhabricatorProjectTransactionEditor
case PhabricatorProjectTransaction::TYPE_LOCKED:
case PhabricatorProjectTransaction::TYPE_PARENT:
case PhabricatorProjectTransaction::TYPE_MILESTONE:
case PhabricatorProjectTransaction::TYPE_HASWORKBOARD:
case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT:
case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER:
return;
}
@ -631,6 +657,7 @@ final class PhabricatorProjectTransactionEditor
}
break;
case PhabricatorProjectTransaction::TYPE_PARENT:
case PhabricatorProjectTransaction::TYPE_MILESTONE:
$materialize = true;
$new_parent = $object->getParentProject();
break;
@ -669,6 +696,11 @@ final class PhabricatorProjectTransactionEditor
->rematerialize($object);
}
if ($new_parent) {
id(new PhabricatorProjectsMembershipIndexEngineExtension())
->rematerialize($new_parent);
}
return parent::applyFinalEffects($object, $xactions);
}
@ -851,8 +883,16 @@ final class PhabricatorProjectTransactionEditor
PhabricatorLiskDAO $object,
array $xactions) {
// Herald rules may run on behalf of other users and need to execute
// membership checks against ancestors.
$project = id(new PhabricatorProjectQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($object->getPHID()))
->needAncestorMembers(true)
->executeOne();
return id(new PhabricatorProjectHeraldAdapter())
->setProject($object);
->setProject($project);
}
}

View file

@ -9,6 +9,7 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
private $columnMap = array();
private $objectColumnMap = array();
private $boardLayout = array();
private $fetchAllBoards;
private $remQueue = array();
private $addQueue = array();
@ -40,6 +41,18 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
return $this->objectPHIDs;
}
/**
* Fetch all boards, even if the board is disabled.
*/
public function setFetchAllBoards($fetch_all) {
$this->fetchAllBoards = $fetch_all;
return $this;
}
public function getFetchAllBoards() {
return $this->fetchAllBoards;
}
public function executeLayout() {
$viewer = $this->getViewer();
@ -73,9 +86,14 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
return array_select_keys($this->columnMap, array_keys($columns));
}
public function getColumnObjectPHIDs($board_phid, $column_phid) {
public function getColumnObjectPositions($board_phid, $column_phid) {
$columns = idx($this->boardLayout, $board_phid, array());
$positions = idx($columns, $column_phid, array());
return idx($columns, $column_phid, array());
}
public function getColumnObjectPHIDs($board_phid, $column_phid) {
$positions = $this->getColumnObjectPositions($board_phid, $column_phid);
return mpull($positions, 'getObjectPHID');
}
@ -301,9 +319,11 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
->execute();
$boards = mpull($boards, null, 'getPHID');
foreach ($boards as $key => $board) {
if (!$board->getHasWorkboard()) {
unset($boards[$key]);
if (!$this->fetchAllBoards) {
foreach ($boards as $key => $board) {
if (!$board->getHasWorkboard()) {
unset($boards[$key]);
}
}
}
@ -346,7 +366,12 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
if ($board->getHasMilestones() || $board->getHasSubprojects()) {
$child_projects = idx($children, $board_phid, array());
$next_sequence = last($board_columns)->getSequence() + 1;
if ($board_columns) {
$next_sequence = last($board_columns)->getSequence() + 1;
} else {
$next_sequence = 1;
}
$proxy_columns = mpull($board_columns, null, 'getProxyPHID');
foreach ($child_projects as $child_phid => $child) {
if (isset($proxy_columns[$child_phid])) {
@ -413,6 +438,7 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
$position_groups = mgroup($positions, 'getObjectPHID');
$layout = array();
$default_phid = null;
foreach ($columns as $column) {
$column_phid = $column->getPHID();
$layout[$column_phid] = array();
@ -435,7 +461,7 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
// If we have proxies, we need to force cards into the correct proxy
// columns.
if ($proxy_map) {
if ($proxy_map && $object_phids) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($object_phids)
->withEdgeTypes(
@ -545,8 +571,9 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
}
}
// If the object has no position, put it on the default column.
if (!$positions) {
// If the object has no position, put it on the default column if
// one exists.
if (!$positions && $default_phid) {
$new_position = id(new PhabricatorProjectColumnPosition())
->setBoardPHID($board_phid)
->setColumnPHID($default_phid)

View file

@ -0,0 +1,144 @@
<?php
final class PhabricatorBoardRenderingEngine extends Phobject {
private $viewer;
private $objects;
private $excludedProjectPHIDs;
private $editMap;
private $loaded;
private $handles;
private $coverFiles;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setObjects(array $objects) {
$this->objects = mpull($objects, null, 'getPHID');
return $this;
}
public function getObjects() {
return $this->objects;
}
public function setExcludedProjectPHIDs(array $phids) {
$this->excludedProjectPHIDs = $phids;
return $this;
}
public function getExcludedProjectPHIDs() {
return $this->excludedProjectPHIDs;
}
public function setEditMap(array $edit_map) {
$this->editMap = $edit_map;
return $this;
}
public function getEditMap() {
return $this->editMap;
}
public function renderCard($phid) {
$this->willRender();
$viewer = $this->getViewer();
$object = idx($this->getObjects(), $phid);
$card = id(new ProjectBoardTaskCard())
->setViewer($viewer)
->setTask($object)
->setCanEdit($this->getCanEdit($phid));
$owner_phid = $object->getOwnerPHID();
if ($owner_phid) {
$owner_handle = $this->handles[$owner_phid];
$card->setOwner($owner_handle);
}
$project_phids = $object->getProjectPHIDs();
$project_handles = array_select_keys($this->handles, $project_phids);
if ($project_handles) {
$card->setProjectHandles($project_handles);
}
$cover_phid = $object->getCoverImageThumbnailPHID();
if ($cover_phid) {
$cover_file = idx($this->coverFiles, $cover_phid);
if ($cover_file) {
$card->setCoverImageFile($cover_file);
}
}
return $card;
}
private function willRender() {
if ($this->loaded) {
return;
}
$phids = array();
foreach ($this->objects as $object) {
$owner_phid = $object->getOwnerPHID();
if ($owner_phid) {
$phids[$owner_phid] = $owner_phid;
}
foreach ($object->getProjectPHIDs() as $phid) {
$phids[$phid] = $phid;
}
}
if ($this->excludedProjectPHIDs) {
foreach ($this->excludedProjectPHIDs as $excluded_phid) {
unset($phids[$excluded_phid]);
}
}
$viewer = $this->getViewer();
$handles = $viewer->loadHandles($phids);
$handles = iterator_to_array($handles);
$this->handles = $handles;
$cover_phids = array();
foreach ($this->objects as $object) {
$cover_phid = $object->getCoverImageThumbnailPHID();
if ($cover_phid) {
$cover_phids[$cover_phid] = $cover_phid;
}
}
if ($cover_phids) {
$cover_files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs($cover_phids)
->execute();
$cover_files = mpull($cover_files, null, 'getPHID');
} else {
$cover_files = array();
}
$this->coverFiles = $cover_files;
$this->loaded = true;
}
private function getCanEdit($phid) {
if ($this->editMap === null) {
return true;
}
return idx($this->editMap, $phid);
}
}

View file

@ -0,0 +1,149 @@
<?php
final class PhabricatorBoardResponseEngine extends Phobject {
private $viewer;
private $boardPHID;
private $objectPHID;
private $visiblePHIDs;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setBoardPHID($board_phid) {
$this->boardPHID = $board_phid;
return $this;
}
public function getBoardPHID() {
return $this->boardPHID;
}
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setVisiblePHIDs(array $visible_phids) {
$this->visiblePHIDs = $visible_phids;
return $this;
}
public function getVisiblePHIDs() {
return $this->visiblePHIDs;
}
public function buildResponse() {
$viewer = $this->getViewer();
$object_phid = $this->getObjectPHID();
$board_phid = $this->getBoardPHID();
// Load all the other tasks that are visible in the affected columns and
// perform layout for them.
$visible_phids = $this->getAllVisiblePHIDs();
$layout_engine = id(new PhabricatorBoardLayoutEngine())
->setViewer($viewer)
->setBoardPHIDs(array($board_phid))
->setObjectPHIDs($visible_phids)
->executeLayout();
$object_columns = $layout_engine->getObjectColumns(
$board_phid,
$object_phid);
$natural = array();
foreach ($object_columns as $column_phid => $column) {
$column_object_phids = $layout_engine->getColumnObjectPHIDs(
$board_phid,
$column_phid);
$natural[$column_phid] = array_values($column_object_phids);
}
$all_visible = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withPHIDs($visible_phids)
->execute();
$order_maps = array();
foreach ($all_visible as $visible) {
$order_maps[$visible->getPHID()] = $visible->getWorkboardOrderVectors();
}
$object = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withPHIDs(array($object_phid))
->needProjectPHIDs(true)
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$template = $this->buildTemplate($object);
$payload = array(
'objectPHID' => $object_phid,
'cardHTML' => $template,
'columnMaps' => $natural,
'orderMaps' => $order_maps,
'propertyMaps' => array(
$object_phid => $object->getWorkboardProperties(),
),
);
return id(new AphrontAjaxResponse())
->setContent($payload);
}
private function buildTemplate($object) {
$viewer = $this->getViewer();
$object_phid = $this->getObjectPHID();
$excluded_phids = $this->loadExcludedProjectPHIDs();
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
->setViewer($viewer)
->setObjects(array($object))
->setExcludedProjectPHIDs($excluded_phids);
$card = $rendering_engine->renderCard($object_phid);
return hsprintf('%s', $card->getItem());
}
private function loadExcludedProjectPHIDs() {
$viewer = $this->getViewer();
$board_phid = $this->getBoardPHID();
$exclude_phids = array($board_phid);
$descendants = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withAncestorProjectPHIDs($exclude_phids)
->execute();
foreach ($descendants as $descendant) {
$exclude_phids[] = $descendant->getPHID();
}
return array_fuse($exclude_phids);
}
private function getAllVisiblePHIDs() {
$visible_phids = $this->getVisiblePHIDs();
$visible_phids[] = $this->getObjectPHID();
$visible_phids = array_fuse($visible_phids);
return $visible_phids;
}
}

View file

@ -43,17 +43,7 @@ final class PhabricatorProjectEditEngine
}
protected function newEditableObject() {
$project = PhabricatorProject::initializeNewProject($this->getViewer());
$milestone = $this->getMilestoneProject();
if ($milestone) {
$default_name = pht(
'Milestone %s',
new PhutilNumber($milestone->loadNextMilestoneNumber()));
$project->setName($default_name);
}
return $project;
return PhabricatorProject::initializeNewProject($this->getViewer());
}
protected function newObjectQuery() {
@ -88,15 +78,11 @@ final class PhabricatorProjectEditEngine
protected function getObjectCreateCancelURI($object) {
$parent = $this->getParentProject();
if ($parent) {
$id = $parent->getID();
return "/project/subprojects/{$id}/";
}
$milestone = $this->getMilestoneProject();
if ($milestone) {
$id = $milestone->getID();
return "/project/milestones/{$id}/";
if ($parent || $milestone) {
$id = nonempty($parent, $milestone)->getID();
return "/project/subprojects/{$id}/";
}
return parent::getObjectCreateCancelURI($object);
@ -143,6 +129,7 @@ final class PhabricatorProjectEditEngine
array(
'parent',
'milestone',
'milestone.previous',
'name',
'std:project:internal:description',
'icon',
@ -170,8 +157,26 @@ final class PhabricatorProjectEditEngine
$parent_phid = null;
}
$previous_milestone_phid = null;
if ($milestone) {
$milestone_phid = $milestone->getPHID();
// Load the current milestone so we can show the user a hint about what
// it was called, so they don't have to remember if the next one should
// be "Sprint 287" or "Sprint 278".
$number = ($milestone->loadNextMilestoneNumber() - 1);
if ($number > 0) {
$previous_milestone = id(new PhabricatorProjectQuery())
->setViewer($this->getViewer())
->withParentProjectPHIDs(array($milestone->getPHID()))
->withIsMilestone(true)
->withMilestoneNumberBetween($number, $number)
->executeOne();
if ($previous_milestone) {
$previous_milestone_phid = $previous_milestone->getPHID();
}
}
} else {
$milestone_phid = null;
}
@ -207,6 +212,14 @@ final class PhabricatorProjectEditEngine
->setIsDefaultable(false)
->setIsLockable(false)
->setIsLocked(true),
id(new PhabricatorHandlesEditField())
->setKey('milestone.previous')
->setLabel(pht('Previous Milestone'))
->setSingleValue($previous_milestone_phid)
->setIsReorderable(false)
->setIsDefaultable(false)
->setIsLockable(false)
->setIsLocked(true),
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))

View file

@ -20,6 +20,10 @@ final class PhabricatorProjectProfilePanelEngine
->setBuiltinKey(PhabricatorProject::PANEL_PROFILE)
->setPanelKey(PhabricatorProjectDetailsProfilePanel::PANELKEY);
$panels[] = $this->newPanel()
->setBuiltinKey(PhabricatorProject::PANEL_POINTS)
->setPanelKey(PhabricatorProjectPointsProfilePanel::PANELKEY);
$panels[] = $this->newPanel()
->setBuiltinKey(PhabricatorProject::PANEL_WORKBOARD)
->setPanelKey(PhabricatorProjectWorkboardProfilePanel::PANELKEY);

View file

@ -1,6 +1,6 @@
<?php
final class ProjectHovercardEngineExtension
final class PhabricatorProjectHovercardEngineExtension
extends PhabricatorHovercardEngineExtension {
const EXTENSIONKEY = 'project.card';

View file

@ -0,0 +1,192 @@
<?php
final class PhabricatorProjectPointsProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'project.points';
public function getPanelTypeName() {
return pht('Project Points');
}
private function getDefaultName() {
return pht('Points Bar');
}
public function shouldEnableForObject($object) {
$viewer = $this->getViewer();
// Only render this element for milestones.
if (!$object->isMilestone()) {
return false;
}
// Don't show if points aren't configured.
if (!ManiphestTaskPoints::getIsEnabled()) {
return false;
}
// Points are only available if Maniphest is installed.
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return false;
}
return true;
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorInstructionsEditField())
->setValue(
pht(
'This is a progress bar which shows how many points of work '.
'are complete within the milestone. It has no configurable '.
'settings.')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$viewer = $this->getViewer();
$project = $config->getProfileObject();
$limit = 250;
$tasks = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_AND,
array($project->getPHID()))
->setLimit($limit + 1)
->execute();
if (count($tasks) > $limit) {
return $this->renderError(
pht(
'Too many tasks to compute statistics for (more than %s).',
new PhutilNumber($limit)));
}
if (!$tasks) {
return $this->renderError(
pht(
'This milestone has no tasks yet.'));
}
$statuses = array();
$points_done = 0;
$points_total = 0;
$no_points = 0;
foreach ($tasks as $task) {
$points = $task->getPoints();
if ($points === null) {
$no_points++;
continue;
}
if (!$points) {
continue;
}
$status = $task->getStatus();
if (empty($statuses[$status])) {
$statuses[$status] = 0;
}
$statuses[$status] += $points;
if (ManiphestTaskStatus::isClosedStatus($status)) {
$points_done += $points;
}
$points_total += $points;
}
if ($no_points == count($tasks)) {
return $this->renderError(
pht('No tasks have assigned point values.'));
}
if (!$points_total) {
return $this->renderError(
pht('All tasks with assigned point values are worth zero points.'));
}
$label = pht(
'%s of %s %s',
new PhutilNumber($points_done),
new PhutilNumber($points_total),
ManiphestTaskPoints::getPointsLabel());
$bar = id(new PHUISegmentBarView())
->setLabel($label);
$map = ManiphestTaskStatus::getTaskStatusMap();
$statuses = array_select_keys($statuses, array_keys($map));
foreach ($statuses as $status => $points) {
if (!$points) {
continue;
}
if (!ManiphestTaskStatus::isClosedStatus($status)) {
continue;
}
$color = ManiphestTaskStatus::getStatusColor($status);
if (!$color) {
$color = 'sky';
}
$tooltip = pht(
'%s %s',
new PhutilNumber($points),
ManiphestTaskStatus::getTaskStatusName($status));
$bar->newSegment()
->setWidth($points / $points_total)
->setColor($color)
->setTooltip($tooltip);
}
$bar = phutil_tag(
'div',
array(
'class' => 'phui-profile-segment-bar',
),
$bar);
$item = $this->newItem()
->appendChild($bar);
return array(
$item,
);
}
private function renderError($message) {
$message = phutil_tag(
'div',
array(
'class' => 'phui-profile-menu-error',
),
$message);
$item = $this->newItem()
->appendChild($message);
return array(
$item,
);
}
}

View file

@ -13,6 +13,14 @@ final class PhabricatorProjectSubprojectsProfilePanel
return pht('Subprojects');
}
public function shouldEnableForObject($object) {
if ($object->isMilestone()) {
return false;
}
return true;
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');

View file

@ -18,6 +18,18 @@ final class PhabricatorProjectWorkboardProfilePanel
return true;
}
public function shouldEnableForObject($object) {
$viewer = $this->getViewer();
// Workboards are only available if Maniphest is installed.
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return false;
}
return true;
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
@ -42,14 +54,6 @@ final class PhabricatorProjectWorkboardProfilePanel
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$viewer = $this->getViewer();
// Workboards are only available if Maniphest is installed.
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return array();
}
$project = $config->getProfileObject();
$has_workboard = $project->getHasWorkboard();

View file

@ -20,6 +20,8 @@ final class PhabricatorProjectQuery
private $hasSubprojects;
private $minDepth;
private $maxDepth;
private $minMilestoneNumber;
private $maxMilestoneNumber;
private $status = 'status-any';
const STATUS_ANY = 'status-any';
@ -27,6 +29,7 @@ final class PhabricatorProjectQuery
const STATUS_CLOSED = 'status-closed';
const STATUS_ACTIVE = 'status-active';
const STATUS_ARCHIVED = 'status-archived';
private $statuses;
private $needSlugs;
private $needMembers;
@ -49,6 +52,11 @@ final class PhabricatorProjectQuery
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withMemberPHIDs(array $member_phids) {
$this->memberPHIDs = $member_phids;
return $this;
@ -105,6 +113,12 @@ final class PhabricatorProjectQuery
return $this;
}
public function withMilestoneNumberBetween($min, $max) {
$this->minMilestoneNumber = $min;
$this->maxMilestoneNumber = $max;
return $this;
}
public function needMembers($need_members) {
$this->needMembers = $need_members;
return $this;
@ -387,6 +401,13 @@ final class PhabricatorProjectQuery
$filter);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
@ -481,6 +502,7 @@ final class PhabricatorProjectQuery
}
}
if ($this->hasSubprojects !== null) {
$where[] = qsprintf(
$conn,
@ -502,6 +524,20 @@ final class PhabricatorProjectQuery
$this->maxDepth);
}
if ($this->minMilestoneNumber !== null) {
$where[] = qsprintf(
$conn,
'milestoneNumber >= %d',
$this->minMilestoneNumber);
}
if ($this->maxMilestoneNumber !== null) {
$where[] = qsprintf(
$conn,
'milestoneNumber <= %d',
$this->maxMilestoneNumber);
}
return $where;
}

View file

@ -34,7 +34,7 @@ final class PhabricatorProjectSearchField
if ($slugs) {
$projects = id(new PhabricatorProjectQuery())
->setViewer($this->requireViewer())
->setViewer($this->getViewer())
->withSlugs($slugs)
->execute();
foreach ($projects as $project) {

View file

@ -36,6 +36,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
protected $projectDepth;
protected $projectPathKey;
protected $properties = array();
private $memberPHIDs = self::ATTACHABLE;
private $watcherPHIDs = self::ATTACHABLE;
private $sparseWatchers = self::ATTACHABLE;
@ -48,6 +50,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
const PANEL_PROFILE = 'project.profile';
const PANEL_POINTS = 'project.points';
const PANEL_WORKBOARD = 'project.workboard';
const PANEL_MEMBERS = 'project.members';
const PANEL_MANAGE = 'project.manage';
@ -197,6 +200,9 @@ final class PhabricatorProject extends PhabricatorProjectDAO
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort128',
'status' => 'text32',
@ -548,6 +554,31 @@ final class PhabricatorProject extends PhabricatorProjectDAO
return idx($map, $color, $color);
}
public function getProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getDefaultWorkboardSort() {
return $this->getProperty('workboard.sort.default');
}
public function setDefaultWorkboardSort($sort) {
return $this->setProperty('workboard.sort.default', $sort);
}
public function getDefaultWorkboardFilter() {
return $this->getProperty('workboard.filter.default');
}
public function setDefaultWorkboardFilter($filter) {
return $this->setProperty('workboard.filter.default', $filter);
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */

View file

@ -86,6 +86,11 @@ final class PhabricatorProjectColumn
}
public function isHidden() {
$proxy = $this->getProxy();
if ($proxy) {
return $proxy->isArchived();
}
return ($this->getStatus() == self::STATUS_HIDDEN);
}

View file

@ -12,6 +12,9 @@ final class PhabricatorProjectTransaction
const TYPE_LOCKED = 'project:locked';
const TYPE_PARENT = 'project:parent';
const TYPE_MILESTONE = 'project:milestone';
const TYPE_HASWORKBOARD = 'project:hasworkboard';
const TYPE_DEFAULT_SORT = 'project:sort';
const TYPE_DEFAULT_FILTER = 'project:filter';
// NOTE: This is deprecated, members are just a normal edge now.
const TYPE_MEMBERS = 'project:members';
@ -65,8 +68,29 @@ final class PhabricatorProjectTransaction
return parent::getColor();
}
public function getIcon() {
public function shouldHideForFeed() {
switch ($this->getTransactionType()) {
case self::TYPE_HASWORKBOARD:
case self::TYPE_DEFAULT_SORT:
case self::TYPE_DEFAULT_FILTER:
return true;
}
return parent::shouldHideForFeed();
}
public function shouldHideForMail(array $xactions) {
switch ($this->getTransactionType()) {
case self::TYPE_HASWORKBOARD:
case self::TYPE_DEFAULT_SORT:
case self::TYPE_DEFAULT_FILTER:
return true;
}
return parent::shouldHideForMail($xactions);
}
public function getIcon() {
$old = $this->getOldValue();
$new = $this->getNewValue();
@ -246,6 +270,27 @@ final class PhabricatorProjectTransaction
}
}
break;
case self::TYPE_HASWORKBOARD:
if ($new) {
return pht(
'%s enabled the workboard for this project.',
$author_handle);
} else {
return pht(
'%s disabled the workboard for this project.',
$author_handle);
}
case self::TYPE_DEFAULT_SORT:
return pht(
'%s changed the default sort order for the project workboard.',
$author_handle);
case self::TYPE_DEFAULT_FILTER:
return pht(
'%s changed the default filter for the project workboard.',
$author_handle);
}
return parent::getTitle();
@ -366,6 +411,7 @@ final class PhabricatorProjectTransaction
$object_handle,
$this->renderSlugList($rem));
}
}
return parent::getTitleForFeed();

View file

@ -38,6 +38,8 @@ final class PhabricatorProjectDatasource
$query->withIsMilestone(false);
}
$for_autocomplete = $this->getParameter('autocomplete');
$projs = $this->executeQuery($query);
$projs = mpull($projs, null, 'getPHID');
@ -58,6 +60,23 @@ final class PhabricatorProjectDatasource
if (!isset($has_cols[$proj->getPHID()])) {
continue;
}
$slug = $proj->getPrimarySlug();
if (!strlen($slug)) {
foreach ($proj->getSlugs() as $slug_object) {
$slug = $slug_object->getSlug();
if (strlen($slug)) {
break;
}
}
}
// If we're building results for the autocompleter and this project
// doesn't have any usable slugs, don't return it as a result.
if ($for_autocomplete && !strlen($slug)) {
continue;
}
$closed = null;
if ($proj->isArchived()) {
$closed = pht('Archived');
@ -78,7 +97,6 @@ final class PhabricatorProjectDatasource
->setPriorityType('proj')
->setClosed($closed);
$slug = $proj->getPrimarySlug();
if (strlen($slug)) {
$proj_result->setAutocomplete('#'.$slug);
}

View file

@ -7,6 +7,7 @@ final class ProjectBoardTaskCard extends Phobject {
private $task;
private $owner;
private $canEdit;
private $coverImageFile;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@ -25,6 +26,15 @@ final class ProjectBoardTaskCard extends Phobject {
return $this->projectHandles;
}
public function setCoverImageFile(PhabricatorFile $cover_image_file) {
$this->coverImageFile = $cover_image_file;
return $this;
}
public function getCoverImageFile() {
return $this->coverImageFile;
}
public function setTask(ManiphestTask $task) {
$this->task = $task;
return $this;
@ -68,10 +78,6 @@ final class ProjectBoardTaskCard extends Phobject {
->setHref('/T'.$task->getID())
->addSigil('project-card')
->setDisabled($task->isClosed())
->setMetadata(
array(
'objectPHID' => $task->getPHID(),
))
->addAction(
id(new PHUIListItemView())
->setName(pht('Edit'))
@ -84,6 +90,23 @@ final class ProjectBoardTaskCard extends Phobject {
$card->addHandleIcon($owner, $owner->getName());
}
$cover_file = $this->getCoverImageFile();
if ($cover_file) {
$card->setCoverImage($cover_file->getBestURI());
}
if (ManiphestTaskPoints::getIsEnabled()) {
$points = $task->getPoints();
if ($points !== null) {
$points_tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setShade(PHUITagView::COLOR_BLUE)
->setSlimShady(true)
->setName($points);
$card->addAttribute($points_tag);
}
}
if ($task->isClosed()) {
$icon = ManiphestTaskStatus::getStatusIcon($task->getStatus());
$icon = id(new PHUIIconView())
@ -100,6 +123,8 @@ final class ProjectBoardTaskCard extends Phobject {
$card->addAttribute($tag_list);
}
$card->addClass('phui-workcard');
return $card;
}

View file

@ -236,9 +236,18 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
->withProfilePHIDs(array($object->getPHID()))
->execute();
foreach ($stored_panels as $stored_panel) {
$impl = $stored_panel->getPanel();
$impl->setViewer($viewer);
}
// Merge the stored panels into the builtin panels. If a builtin panel has
// a stored version, replace the defaults with the stored changes.
foreach ($stored_panels as $stored_panel) {
if (!$stored_panel->shouldEnableForObject($object)) {
continue;
}
$builtin_key = $stored_panel->getBuiltinKey();
if ($builtin_key !== null) {
// If this builtin actually exists, replace the builtin with the
@ -255,12 +264,6 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
}
}
foreach ($panels as $panel) {
$impl = $panel->getPanel();
$impl->setViewer($viewer);
}
$panels = msort($panels, 'getSortKey');
// Normalize keys since callers shouldn't rely on this array being
@ -302,6 +305,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
$builtins = $this->getBuiltinProfilePanels($object);
$panels = PhabricatorProfilePanel::getAllPanels();
$viewer = $this->getViewer();
$order = 1;
$map = array();
@ -335,12 +339,19 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
$panel_key));
}
$panel = clone $panel;
$panel->setViewer($viewer);
$builtin
->setProfilePHID($object->getPHID())
->attachPanel($panel)
->attachProfileObject($object)
->setPanelOrder($order);
if (!$builtin->shouldEnableForObject($object)) {
continue;
}
$map[$builtin_key] = $builtin;
$order++;

View file

@ -30,6 +30,10 @@ abstract class PhabricatorProfilePanel extends Phobject {
return false;
}
public function shouldEnableForObject($object) {
return true;
}
public function canHidePanel(
PhabricatorProfilePanelConfiguration $config) {
return true;

View file

@ -109,6 +109,10 @@ final class PhabricatorProfilePanelConfiguration
return $this->getPanel()->canHidePanel($this);
}
public function shouldEnableForObject($object) {
return $this->getPanel()->shouldEnableForObject($object);
}
public function getSortKey() {
$order = $this->getPanelOrder();
if ($order === null) {

View file

@ -0,0 +1,16 @@
<?php
final class PhabricatorEditEnginePointsCommentAction
extends PhabricatorEditEngineCommentAction {
public function getPHUIXControlType() {
return 'points';
}
public function getPHUIXControlSpecification() {
return array(
'value' => $this->getValue(),
);
}
}

View file

@ -0,0 +1,17 @@
<?php
final class PhabricatorPointsEditField
extends PhabricatorEditField {
protected function newControl() {
return new AphrontFormTextControl();
}
protected function newConduitParameterType() {
return new ConduitPointsParameterType();
}
protected function newCommentAction() {
return id(new PhabricatorEditEnginePointsCommentAction());
}
}

View file

@ -175,8 +175,20 @@ final class PhabricatorEditEngineConfiguration
}
private function reorderFields(array $fields) {
// Fields which can not be reordered are fixed in order at the top of the
// form. These are used to show instructions or contextual information.
$fixed = array();
foreach ($fields as $key => $field) {
if (!$field->getIsReorderable()) {
$fixed[$key] = $field;
}
}
$keys = $this->getFieldOrder();
$fields = array_select_keys($fields, $keys) + $fields;
$fields = $fixed + array_select_keys($fields, $keys) + $fields;
return $fields;
}

View file

@ -12,10 +12,7 @@ final class PhabricatorAphrontBarUIExample extends PhabricatorUIExample {
public function renderExample() {
$out = array();
$out[] = $this->renderTestThings('AphrontProgressBarView', 13, 10);
$out[] = $this->renderTestThings('AphrontGlyphBarView', 13, 10);
$out[] = $this->renderWeirdOrderGlyphBars();
$out[] = $this->renderAsciiStarBar();
$out[] = $this->renderRainbow();
return $out;
}
@ -26,48 +23,46 @@ final class PhabricatorAphrontBarUIExample extends PhabricatorUIExample {
->appendChild($thing);
}
private function renderTestThings($class, $max, $incr) {
private function renderRainbow() {
$colors = array(
'red',
'orange',
'yellow',
'green',
'blue',
'indigo',
'violet',
);
$labels = array(
pht('Empty'),
pht('Red'),
pht('Orange'),
pht('Yellow'),
pht('Green'),
pht('Blue'),
pht('Indigo'),
pht('Violet'),
);
$bars = array();
for ($ii = 0; $ii <= $max; $ii++) {
$bars[] = newv($class, array())
->setValue($ii * $incr)
->setMax($max * $incr)
->setCaption("{$ii} outta {$max} ain't bad!");
}
return $this->wrap("Test {$class}", $bars);
}
private function renderWeirdOrderGlyphBars() {
$views = array();
$indices = array(1, 3, 7, 4, 2, 8, 9, 5, 10, 6);
$max = count($indices);
foreach ($indices as $index) {
$views[] = id(new AphrontGlyphBarView())
->setValue($index)
->setMax($max)
->setNumGlyphs(5)
->setCaption("Lol score is {$index}/{$max}")
->setGlyph(hsprintf('%s', 'LOL!'))
->setBackgroundGlyph(hsprintf('%s', '____'));
$views[] = hsprintf('<div style="clear:both;"></div>');
for ($jj = -1; $jj < count($colors); $jj++) {
$bar = id(new PHUISegmentBarView())
->setLabel($labels[$jj + 1]);
for ($ii = 0; $ii <= $jj; $ii++) {
$bar->newSegment()
->setWidth(1 / 7)
->setColor($colors[$ii]);
}
$bars[] = $bar;
}
return $this->wrap(
pht('Glyph bars in weird order'),
$views);
}
$bars = phutil_implode_html(
phutil_tag('br'),
$bars);
private function renderAsciiStarBar() {
$bar = id(new AphrontGlyphBarView())
->setValue(50)
->setMax(100)
->setCaption(pht('Glyphs!'))
->setNumGlyphs(10)
->setGlyph(hsprintf('%s', '*'));
return $this->wrap(
pht('ASCII star glyph bar'),
$bar);
return $this->wrap(pht('Rainbow Bars'), $bars);
}
}

View file

@ -28,11 +28,10 @@ Databases
=========
Each Phabricator application has its own database. The names are prefixed by
`phabricator_` (this is configurable). This design has two advantages:
`phabricator_` (this is configurable).
- Each database is easier to comprehend and to maintain.
- We don't do cross-database joins so each database can live on its own
machine. This gives us flexibility in sharding data later.
Phabricator uses a separate database for each application. To understand why,
see @{article:Why does Phabricator need so many databases?}.
Connections
===========

View file

@ -0,0 +1,130 @@
@title Why does Phabricator need so many databases?
@group lore
Phabricator uses about 60 databases (and we may have added more by the time you
read this document). This sometimes comes as a surprise, since you might assume
it would only use one database.
The approach we use is designed to work at scale for huge installs with many
thousands of users. We care a lot about working well for large installs, and
about scaling up gracefully to meet the needs of growing organizations. We want
small startups to be able to install Phabricator and have it grow with them as
they expand to many thousands of employees.
A cost of this approach is that it makes Phabricator more difficult to install
on shared hosts which require a lot of work to create or authorize access to
each database. However, Phabricator does a lot of advanced or complex things
which are difficult to configure or manage on shared hosts, and we don't
recommend installing it on a shared host. The install documentation explicitly
discouarges installing on shared hosts.
Broadly, in cases where we must choose between operating well at scale for
growing organizations and installing easily on shared hosts, we prioritize
operating at scale.
Listing Databases
=================
You can get a full list of the databases Phabricator needs with `bin/storage
databases`. It will look something like this:
```
$ /core/lib/phabricator/bin/storage databases
secure_audit
secure_calendar
secure_chatlog
secure_conduit
secure_countdown
secure_daemon
secure_differential
secure_draft
secure_drydock
secure_feed
...<dozens more databases>...
```
Roughly, each application has its own database, and then there are some
databases which support internal systems or shared infrastructure.
Operating at Scale
==================
This storage design is aimed at large installs that may need more than one
physical database server to handle the load the install generates.
The primary reason we use a separate database for each application is to allow
large installs to scale up by spreading database load across more hardware. A
large organization with many thousands of active users may find themselves
limited by the capacity of a single database backend.
If so, they can launch a second backend, move some applications over to it, and
continue piling on more users.
This can't continue forever, but provides a substantial amount of headroom for
large installs to spread the workload across more hardware and continue scaling
up.
To make this possible, we put each application in its own database and use
database boundaries to enforce the logical constraints that the application
must have in order for this to work. For example, we can not perform joins
between separable tables, because they may not be on the same hardware.
Establishing boundaries with application databases is a simple, straightforward
way to partition storage and make administrative operations like spreading load
realistic.
Ease of Development
===================
This design is also easier for us to work with, and easier for users who
want to work with the raw data in the database.
We have a large number of tables (more than 400) and we can not reasonably
reduce the number of tables very much (each table generally represents some
meaningful type of object in some application). It's easier to develop with
tables which are organized into separate application databases, just like it's
easier to work with a large project if you organize source files into
directories.
If you aren't developing Phabricator and never look at the data in the
database, you probably won't benefit from this organization. However, if you
are a developer or want to extend Phabricator or look under the hood, it's
easier to find what you're looking for and work with the tables when they're
organized by application.
More Databases Cost Nothing
===========================
In almost all cases, creating more databases has zero cost, just like
organizing source code into directories has zero cost. Even if we didn't derive
enormous benefits from this approach at scale, there is little reason //not//
to organize storage like this.
There are a handful of administrative tasks which are very slightly more
complex to perform on multiple databases, but these are all either automated
with `bin/storage` or easy to build on top of the list of databases emitted by
`bin/storage databases`.
For example, you can dump all the databases with `bin/storage dump`, and you
can destroy all the databases with `bin/storage destroy`.
As mentioned above, an exception to this is that if you're installing on a
shared host and need to jump through hoops to individually authorize access to
each database, databases do cost something.
However, this cost is an artificial cost imposed by the selected environment,
and this is only the first of many issues you'll run into trying to install and
run Phabricator on a shared host. These issues are why we strongly discourage
using shared hosts, and recommend against them in the install guide.
Next Steps
==========
Continue by:
- learning more about databases in @{article:Database Schema}.

View file

@ -52,7 +52,7 @@ inherent complexity, like these:
- {icon times, color=red} A 100MB wiki page takes a long time to render.
- {icon times, color=red} A turing-complete simulation of Conway's Game of
Life implented in 958,000 Herald rules executes slowly.
Life implemented in 958,000 Herald rules executes slowly.
- {icon times, color=red} Uploading an 8GB file takes several minutes.
Generally, the path forward will be:

View file

@ -1446,5 +1446,19 @@ abstract class PhabricatorCustomField extends Phobject {
return null;
}
public function getHeraldFieldStandardType() {
if ($this->proxy) {
return $this->proxy->getHeraldFieldStandardType();
}
return null;
}
public function getHeraldDatasource() {
if ($this->proxy) {
return $this->proxy->getHeraldDatasource();
}
return null;
}
}

View file

@ -65,8 +65,20 @@ final class PhabricatorCustomFieldHeraldField extends HeraldField {
return $this->getCustomField()->getHeraldFieldConditions();
}
protected function getHeraldFieldStandardType() {
return $this->getCustomField()->getHeraldFieldStandardType();
}
public function getHeraldFieldValueType($condition) {
if ($this->getHeraldFieldStandardType()) {
return parent::getHeraldFieldValueType($condition);
}
return $this->getCustomField()->getHeraldFieldValueType($condition);
}
protected function getDatasource() {
return $this->getCustomField()->getHeraldDatasource();
}
}

View file

@ -129,6 +129,10 @@ final class PhabricatorStandardCustomFieldBool
);
}
public function getHeraldFieldStandardType() {
return HeraldField::STANDARD_BOOL;
}
protected function getHTTPParameterType() {
return new AphrontBoolHTTPParameterType();
}

View file

@ -77,9 +77,14 @@ final class PhabricatorStandardCustomFieldLink
HeraldAdapter::CONDITION_IS,
HeraldAdapter::CONDITION_IS_NOT,
HeraldAdapter::CONDITION_REGEXP,
HeraldAdapter::CONDITION_NOT_REGEXP,
);
}
public function getHeraldFieldStandardType() {
return HeraldField::STANDARD_TEXT;
}
protected function getHTTPParameterType() {
return new AphrontStringHTTPParameterType();
}

View file

@ -241,6 +241,10 @@ abstract class PhabricatorStandardCustomFieldPHIDs
);
}
public function getHeraldFieldStandardType() {
return HeraldField::STANDARD_PHID_NULLABLE;
}
public function getHeraldFieldValue() {
// If the field has a `null` value, make sure we hand an `array()` to
// Herald.

View file

@ -92,9 +92,14 @@ final class PhabricatorStandardCustomFieldRemarkup
HeraldAdapter::CONDITION_IS,
HeraldAdapter::CONDITION_IS_NOT,
HeraldAdapter::CONDITION_REGEXP,
HeraldAdapter::CONDITION_NOT_REGEXP,
);
}
public function getHeraldFieldStandardType() {
return HeraldField::STANDARD_TEXT;
}
protected function getHTTPParameterType() {
return new AphrontStringHTTPParameterType();
}

View file

@ -60,9 +60,14 @@ final class PhabricatorStandardCustomFieldText
HeraldAdapter::CONDITION_IS,
HeraldAdapter::CONDITION_IS_NOT,
HeraldAdapter::CONDITION_REGEXP,
HeraldAdapter::CONDITION_NOT_REGEXP,
);
}
public function getHeraldFieldStandardType() {
return HeraldField::STANDARD_TEXT;
}
protected function getHTTPParameterType() {
return new AphrontStringHTTPParameterType();
}

View file

@ -44,4 +44,12 @@ abstract class PhabricatorStandardCustomFieldTokenizer
->setDatasource($this->getDatasource());
}
public function getHeraldFieldStandardType() {
return HeraldField::STANDARD_PHID_LIST;
}
public function getHeraldDatasource() {
return $this->getDatasource();
}
}

View file

@ -1514,6 +1514,20 @@ final class PhabricatorUSEnglishTranslation
'Permanently destroyed %s object.',
'Permanently destroyed %s objects.',
),
'%s added %s watcher(s) for %s: %s.' => array(
array(
'%s added a watcher for %3$s: %4$s.',
'%s added watchers for %3$s: %4$s.',
),
),
'%s removed %s watcher(s) for %s: %s.' => array(
array(
'%s removed a watcher for %3$s: %4$s.',
'%s removed watchers for %3$s: %4$s.',
),
),
);
}

View file

@ -45,7 +45,11 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl {
$root_id = celerity_generate_unique_node_id();
$user_datasource = new PhabricatorPeopleDatasource();
$proj_datasource = new PhabricatorProjectDatasource();
$proj_datasource = id(new PhabricatorProjectDatasource())
->setParameters(
array(
'autocomplete' => 1,
));
Javelin::initBehavior(
'phabricator-remarkup-assist',

View file

@ -73,12 +73,16 @@ final class PHUICrumbView extends AphrontView {
->setIcon($this->icon);
}
// Surround the crumb name with spaces so that double clicking it only
// selects the crumb itself.
$name = array(' ', $this->name, ' ');
$name = phutil_tag(
'span',
array(
'class' => 'phui-crumb-name',
),
$this->name);
$name);
$divider = null;
if (!$this->isLastCrumb) {

View file

@ -52,6 +52,15 @@ final class PHUICrumbsView extends AphrontView {
if ($this->actions) {
$actions = array();
foreach ($this->actions as $action) {
if ($action->getType() == PHUIListItemView::TYPE_DIVIDER) {
$actions[] = phutil_tag(
'span',
array(
'class' => 'phui-crumb-action-divider',
));
continue;
}
$icon = null;
if ($action->getIcon()) {
$icon_name = $action->getIcon();
@ -63,19 +72,26 @@ final class PHUICrumbsView extends AphrontView {
->setIcon($icon_name);
}
$name = phutil_tag(
'span',
array(
'class' => 'phui-crumbs-action-name',
),
$action->getName());
$action_classes = $action->getClasses();
$action_classes[] = 'phui-crumbs-action';
$name = null;
if ($action->getName()) {
$name = phutil_tag(
'span',
array(
'class' => 'phui-crumbs-action-name',
),
$action->getName());
} else {
$action_classes[] = 'phui-crumbs-action-icon';
}
$action_sigils = $action->getSigils();
if ($action->getWorkflow()) {
$action_sigils[] = 'workflow';
}
$action_classes = $action->getClasses();
$action_classes[] = 'phui-crumbs-action';
if ($action->getDisabled()) {
$action_classes[] = 'phui-crumbs-action-disabled';

View file

@ -22,6 +22,7 @@ final class PHUIHeaderView extends AphrontTagView {
private $epoch;
private $actionIcons = array();
private $badges = array();
private $href;
public function setHeader($header) {
$this->header = $header;
@ -147,6 +148,15 @@ final class PHUIHeaderView extends AphrontTagView {
return $this;
}
public function setHref($href) {
$this->href = $href;
return $this;
}
public function getHref() {
return $this->href;
}
protected function getTagName() {
return 'div';
}
@ -290,12 +300,25 @@ final class PHUIHeaderView extends AphrontTagView {
->setIcon($this->headerIcon);
$left[] = $icon;
}
$header_content = $this->header;
$href = $this->getHref();
if ($href !== null) {
$header_content = phutil_tag(
'a',
array(
'href' => $href,
),
$header_content);
}
$left[] = phutil_tag(
'span',
array(
'class' => 'phui-header-header',
),
$this->header);
$header_content);
if ($this->subheader || $this->badges) {
$badges = null;

View file

@ -27,6 +27,7 @@ final class PHUIObjectItemView extends AphrontTagView {
private $countdownNum;
private $countdownNoun;
private $launchButton;
private $coverImage;
const AGE_FRESH = 'fresh';
const AGE_STALE = 'stale';
@ -150,6 +151,11 @@ final class PHUIObjectItemView extends AphrontTagView {
return $this->imageIcon;
}
public function setCoverImage($image) {
$this->coverImage = $image;
return $this;
}
public function setState($state) {
$this->state = $state;
switch ($state) {
@ -720,16 +726,45 @@ final class PHUIObjectItemView extends AphrontTagView {
$actions);
}
return phutil_tag(
$frame_content = phutil_tag(
'div',
array(
'class' => 'phui-object-item-frame',
'class' => 'phui-object-item-frame-content',
),
array(
$actions,
$image,
$box,
));
$frame_cover = null;
if ($this->coverImage) {
$cover_image = phutil_tag(
'img',
array(
'src' => $this->coverImage,
'class' => 'phui-object-item-cover-image',
));
$frame_cover = phutil_tag(
'div',
array(
'class' => 'phui-object-item-frame-cover',
),
$cover_image);
}
$frame = phutil_tag(
'div',
array(
'class' => 'phui-object-item-frame',
),
array(
$frame_cover,
$frame_content,
));
return $frame;
}
private function renderStatusIcon($icon, $label) {

View file

@ -0,0 +1,79 @@
<?php
final class PHUISegmentBarSegmentView extends AphrontTagView {
private $width;
private $color;
private $position;
private $tooltip;
public function setWidth($width) {
$this->width = $width;
return $this;
}
public function getWidth() {
return $this->width;
}
public function setColor($color) {
$this->color = $color;
return $this;
}
public function setPosition($position) {
$this->position = $position;
return $this;
}
public function setTooltip($tooltip) {
$this->tooltip = $tooltip;
return $this;
}
protected function canAppendChild() {
return false;
}
protected function getTagAttributes() {
$classes = array(
'phui-segment-bar-segment-view',
);
if ($this->color) {
$classes[] = $this->color;
}
// Convert width to a percentage, and round it up slightly so that bars
// are full if they have, e.g., three segments at 1/3 + 1/3 + 1/3.
$width = 100 * $this->width;
$width = ceil(100 * $width) / 100;
$width = sprintf('%.2f%%', $width);
$left = 100 * $this->position;
$left = floor(100 * $left) / 100;
$left = sprintf('%.2f%%', $left);
$tooltip = $this->tooltip;
if (strlen($tooltip)) {
Javelin::initBehavior('phabricator-tooltips');
$sigil = 'has-tooltip';
$meta = array(
'tip' => $tooltip,
'align' => 'E',
);
} else {
$sigil = null;
$meta = null;
}
return array(
'class' => implode(' ', $classes),
'style' => "left: {$left}; width: {$width};",
'sigil' => $sigil,
'meta' => $meta,
);
}
}

View file

@ -0,0 +1,65 @@
<?php
final class PHUISegmentBarView extends AphrontTagView {
private $label;
private $segments = array();
public function setLabel($label) {
$this->label = $label;
return $this;
}
public function newSegment() {
$segment = new PHUISegmentBarSegmentView();
$this->segments[] = $segment;
return $segment;
}
protected function canAppendChild() {
return false;
}
protected function getTagAttributes() {
return array(
'class' => 'phui-segment-bar-view',
);
}
protected function getTagContent() {
require_celerity_resource('phui-segment-bar-view-css');
$label = $this->label;
if (strlen($label)) {
$label = phutil_tag(
'div',
array(
'class' => 'phui-segment-bar-label',
),
$label);
}
$segments = $this->segments;
$position = 0;
foreach ($segments as $segment) {
$segment->setPosition($position);
$position += $segment->getWidth();
}
$segments = array_reverse($segments);
$segments = phutil_tag(
'div',
array(
'class' => 'phui-segment-bar-segments',
),
$segments);
return array(
$label,
$segments,
);
}
}

View file

@ -25,12 +25,13 @@ final class PHUIWorkboardView extends AphrontTagView {
$view->addColumn($panel);
}
$board = phutil_tag(
$board = javelin_tag(
'div',
array(
'class' => 'phui-workboard-view-shadow',
),
$view);
array(
'class' => 'phui-workboard-view-shadow',
'sigil' => 'workboard-shadow lock-scroll-y-while-dragging',
),
$view);
return $board;
}

View file

@ -9,6 +9,7 @@ final class PHUIWorkpanelView extends AphrontTagView {
private $headerActions = array();
private $headerTag;
private $headerIcon;
private $href;
public function setHeaderIcon($icon) {
$this->headerIcon = $icon;
@ -49,6 +50,15 @@ final class PHUIWorkpanelView extends AphrontTagView {
return $this;
}
public function setHref($href) {
$this->href = $href;
return $this;
}
public function getHref() {
return $this->href;
}
protected function getTagAttributes() {
return array(
'class' => 'phui-workpanel-view',
@ -85,13 +95,20 @@ final class PHUIWorkpanelView extends AphrontTagView {
$header->addActionIcon($action);
}
$href = $this->getHref();
if ($href !== null) {
$header->setHref($href);
}
$body = phutil_tag(
'div',
array(
'class' => 'phui-workpanel-body',
'class' => 'phui-workpanel-body-content',
),
$this->cards);
$body = phutil_tag_div('phui-workpanel-body', $body);
$view = id(new PHUIBoxView())
->setColor(PHUIBoxView::GREY)
->addClass('phui-workpanel-view-inner')

View file

@ -10,13 +10,17 @@
background-color: #fff;
}
.jx-client-dialog .aphront-dialog-view {
box-shadow: {$dropshadow};
}
.device-phone .aphront-dialog-view {
margin: 16px;
width: auto;
}
.aphront-dialog-view-standalone {
margin: auto;
margin: 32px auto;
}
.aphront-dialog-head {
@ -33,6 +37,7 @@
.aphront-dialog-view-width-full {
width: 90%;
max-width: 1040px;
}
.aphront-dialog-body {

View file

@ -136,18 +136,6 @@ a.handle-availability-disabled {
right: 0;
}
/* Fixes so pages actually print when magic scrollbar is present */
!print .main-page-frame {
position: static;
overflow: visible;
}
!print .jx-scrollbar-viewport {
position: static;
width: auto !important;
height: auto !important;
}
.jx-scrollbar-test {
position: absolute;
left: -300px;

View file

@ -14,7 +14,7 @@
.project-card-view .phui-header-shell {
margin: 0;
padding: 12px 12px 16px 12px;
padding: 12px 12px 4px 12px;
border: none;
border-radius: 3px;
}
@ -43,6 +43,7 @@
.project-card-view .phui-header-subheader {
font-size: {$normalfontsize};
margin-top: 12px;
padding-bottom: 12px;
}
.project-card-view .phui-header-header .phui-tag-view {
@ -63,6 +64,10 @@
color: {$bluetext};
}
.project-card-view .project-card-body {
padding: 0 12px 12px 76px;
color: {$darkbluetext};
}
/* Colors */

View file

@ -110,10 +110,6 @@ div.phui-calendar-day-event {
z-index: 9;
}
.drag-frame {
z-index: 10;
}
.jx-mask {
z-index: 10;
}
@ -142,6 +138,10 @@ div.jx-typeahead-results {
z-index: 15;
}
.drag-frame {
z-index: 16;
}
.jx-hovercard-container {
z-index: 17;
}

View file

@ -89,12 +89,15 @@
}
a.phui-crumbs-action .phui-icon-view {
margin-right: 5px;
color: {$darkbluetext};
}
a.phui-crumbs-action .phui-crumbs-action-name {
margin-left: 6px;
}
.device-phone a.phui-crumbs-action .phui-icon-view {
margin-left: 5px;
margin-left: 4px;
}
.phui-crumb-divider {
@ -112,3 +115,11 @@ body .phui-crumbs-view + .phui-object-box {
body .phui-crumbs-view + .phui-object-item-list-view {
padding-top: 0;
}
.phui-crumb-action-divider {
border-left: 1px solid {$lightgreyborder};
}
.phui-crumbs-action-icon + .phui-crumbs-action-icon {
padding-left: 4px;
}

View file

@ -56,6 +56,10 @@ ul.phui-object-item-list-view {
overflow: hidden;
}
.phui-object-item-cover-image {
display: none;
}
.phui-object-item-no-bar .phui-object-item-frame {
border-width: 1px;
}
@ -303,7 +307,7 @@ ul.phui-object-item-list-view {
.phui-object-item-attribute {
display: inline-block;
color: {$greytext};
vertical-align: middle;
vertical-align: top;
}
.phui-object-item-attribute-spacer {

View file

@ -149,6 +149,25 @@
color: {$menu.profile.text};
}
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-error {
color: {$greytext};
font-size: {$smallerfontsize};
padding: 18px 15px;
}
.phui-profile-menu .phabricator-side-menu .phui-profile-segment-bar {
color: {$menu.profile.text};
font-size: {$smallerfontsize};
-webkit-font-smoothing: antialiased;
padding: 8px 12px 16px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-profile-segment-bar {
padding: 8px 8px 16px;
}
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-spacer {
box-sizing: border-box;
height: {$menu.profile.item.height};
@ -300,3 +319,7 @@
max-width: {$menu.profile.width};
}
}
!print .phui-profile-menu .phabricator-side-menu {
display: none;
}

Some files were not shown because too many files have changed in this diff Show more