1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-24 15:52:41 +01:00

(stable) Promote 2016 Week 26

This commit is contained in:
epriestley 2016-06-24 15:30:20 -07:00
commit cadac75b82
95 changed files with 2285 additions and 526 deletions

View file

@ -7,18 +7,18 @@
*/
return array(
'names' => array(
'core.pkg.css' => 'c7fc5aec',
'core.pkg.js' => '10275c16',
'core.pkg.css' => 'b6b40555',
'core.pkg.js' => 'f2139810',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'b3eea3f5',
'differential.pkg.js' => '4b7d8f19',
'differential.pkg.js' => '01a010d6',
'diffusion.pkg.css' => '91c5d3a6',
'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' => 'f54bf286',
'rsrc/css/aphront/dialog-view.css' => 'b4334e08',
'rsrc/css/aphront/dialog-view.css' => '913c172e',
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
'rsrc/css/aphront/multi-column.css' => 'fd18389d',
@ -28,7 +28,7 @@ return array(
'rsrc/css/aphront/table-view.css' => '9258e19f',
'rsrc/css/aphront/tokenizer.css' => '056da01b',
'rsrc/css/aphront/tooltip.css' => '1a07aea8',
'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c',
'rsrc/css/aphront/typeahead-browse.css' => '8904346a',
'rsrc/css/aphront/typeahead.css' => 'd4f16145',
'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af',
'rsrc/css/application/auth/auth.css' => '0877ed6e',
@ -124,7 +124,7 @@ return array(
'rsrc/css/phui/phui-badge.css' => '3baef8db',
'rsrc/css/phui/phui-big-info-view.css' => 'bd903741',
'rsrc/css/phui/phui-box.css' => '5c8387cf',
'rsrc/css/phui/phui-button.css' => 'a64a8de6',
'rsrc/css/phui/phui-button.css' => '4a5fbe3d',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-crumbs-view.css' => '6b813619',
'rsrc/css/phui/phui-curtain-view.css' => '7148ae25',
@ -149,13 +149,13 @@ return array(
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-profile-menu.css' => 'c8557f33',
'rsrc/css/phui/phui-property-list-view.css' => 'd4bbd0cb',
'rsrc/css/phui/phui-property-list-view.css' => '6d8e58ac',
'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' => 'd5263e49',
'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2',
'rsrc/css/phui/phui-timeline-view.css' => '8ea41b25',
'rsrc/css/phui/phui-timeline-view.css' => 'c3782437',
'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
@ -245,7 +245,7 @@ return array(
'rsrc/externals/javelin/lib/URI.js' => 'c989ade3',
'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8',
'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4',
'rsrc/externals/javelin/lib/Workflow.js' => '0eb34d1d',
'rsrc/externals/javelin/lib/Workflow.js' => '1e911d0f',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68',
@ -339,6 +339,7 @@ return array(
'rsrc/image/people/washington.png' => '40dd301c',
'rsrc/image/phrequent_active.png' => 'a466a8ed',
'rsrc/image/phrequent_inactive.png' => 'bfc15a69',
'rsrc/image/resize.png' => 'fd476de4',
'rsrc/image/sprite-login-X2.png' => 'e3991e37',
'rsrc/image/sprite-login.png' => '03d5af29',
'rsrc/image/sprite-menu-X2.png' => 'cfd8fca5',
@ -494,7 +495,7 @@ return array(
'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7',
'rsrc/js/core/behavior-line-linker.js' => '1499a8cb',
'rsrc/js/core/behavior-more.js' => 'a80d0378',
'rsrc/js/core/behavior-object-selector.js' => '49b73b36',
'rsrc/js/core/behavior-object-selector.js' => '9030ebef',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '116cf19b',
@ -515,14 +516,15 @@ return array(
'rsrc/js/core/behavior-watch-anchor.js' => '9f36c42d',
'rsrc/js/core/behavior-workflow.js' => '0a3f3021',
'rsrc/js/core/phtize.js' => 'd254d646',
'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475',
'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '1aa4c968',
'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb',
'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836',
'rsrc/js/phui/behavior-phui-profile-menu.js' => '12884df9',
'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '82e270da',
'rsrc/js/phuix/PHUIXFormControl.js' => 'e15869a8',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
),
@ -530,7 +532,7 @@ return array(
'almanac-css' => 'dbb9b3af',
'aphront-bars' => '231ac33c',
'aphront-dark-console-css' => 'f54bf286',
'aphront-dialog-view-css' => 'b4334e08',
'aphront-dialog-view-css' => '913c172e',
'aphront-list-filter-view-css' => '5d6f0526',
'aphront-multi-column-view-css' => 'fd18389d',
'aphront-panel-view-css' => '8427b78d',
@ -656,7 +658,7 @@ return array(
'javelin-behavior-phabricator-line-linker' => '1499a8cb',
'javelin-behavior-phabricator-nav' => '56a1ca03',
'javelin-behavior-phabricator-notification-example' => '8ce821c5',
'javelin-behavior-phabricator-object-selector' => '49b73b36',
'javelin-behavior-phabricator-object-selector' => '9030ebef',
'javelin-behavior-phabricator-oncopy' => '2926fff2',
'javelin-behavior-phabricator-remarkup-assist' => '116cf19b',
'javelin-behavior-phabricator-reveal-content' => '60821bc7',
@ -668,11 +670,12 @@ return array(
'javelin-behavior-phabricator-watch-anchor' => '9f36c42d',
'javelin-behavior-pholio-mock-edit' => 'bee502c8',
'javelin-behavior-pholio-mock-view' => 'fbe497e7',
'javelin-behavior-phui-dropdown-menu' => '54733475',
'javelin-behavior-phui-dropdown-menu' => '1aa4c968',
'javelin-behavior-phui-file-upload' => 'b003d4fb',
'javelin-behavior-phui-hovercards' => 'bcaccd64',
'javelin-behavior-phui-object-box-tabs' => '2bfa2836',
'javelin-behavior-phui-profile-menu' => '12884df9',
'javelin-behavior-phui-submenu' => 'a6f7a73b',
'javelin-behavior-policy-control' => 'd0c516d5',
'javelin-behavior-policy-rule-editor' => '5e9f347c',
'javelin-behavior-project-boards' => '14a1faae',
@ -749,7 +752,7 @@ return array(
'javelin-workboard-card' => 'c587b80f',
'javelin-workboard-column' => 'bae58312',
'javelin-workboard-controller' => '55baf5ed',
'javelin-workflow' => '0eb34d1d',
'javelin-workflow' => '1e911d0f',
'lightbox-attachment-css' => '7acac05d',
'maniphest-batch-editor' => 'b0f0b6d5',
'maniphest-report-css' => '9b9580b7',
@ -821,7 +824,7 @@ return array(
'phui-badge-view-css' => '3baef8db',
'phui-big-info-view-css' => 'bd903741',
'phui-box-css' => '5c8387cf',
'phui-button-css' => 'a64a8de6',
'phui-button-css' => '4a5fbe3d',
'phui-calendar-css' => 'ccabe893',
'phui-calendar-day-css' => 'd1cf6f93',
'phui-calendar-list-css' => '56e6381a',
@ -853,14 +856,14 @@ return array(
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
'phui-profile-menu-css' => 'c8557f33',
'phui-property-list-view-css' => 'd4bbd0cb',
'phui-property-list-view-css' => '6d8e58ac',
'phui-remarkup-preview-css' => '1a8f2591',
'phui-segment-bar-view-css' => '46342871',
'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => 'd5263e49',
'phui-tag-view-css' => '6bbd83e2',
'phui-theme-css' => '027ba77e',
'phui-timeline-view-css' => '8ea41b25',
'phui-timeline-view-css' => 'c3782437',
'phui-two-column-view-css' => '9fb86c85',
'phui-workboard-color-css' => 'ac6fe6a7',
'phui-workboard-view-css' => 'e6d89647',
@ -869,7 +872,7 @@ return array(
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => '8cf6d262',
'phuix-autocomplete' => '9196fb06',
'phuix-dropdown-menu' => 'bd4c8dca',
'phuix-dropdown-menu' => '82e270da',
'phuix-form-control-view' => 'e15869a8',
'phuix-icon-view' => 'bff6884b',
'policy-css' => '957ea14c',
@ -889,7 +892,7 @@ return array(
'syntax-default-css' => '9923583c',
'syntax-highlighting-css' => '9fc496d5',
'tokens-css' => '3d0f239e',
'typeahead-browse-css' => 'd8581d2c',
'typeahead-browse-css' => '8904346a',
'unhandled-exception-css' => '4c96257a',
),
'requires' => array(
@ -981,17 +984,6 @@ return array(
'javelin-dom',
'javelin-router',
),
'0eb34d1d' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'0f764c35' => array(
'javelin-install',
'javelin-util',
@ -1034,6 +1026,12 @@ return array(
'javelin-workflow',
'javelin-workboard-controller',
),
'1aa4c968' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'1ad0a787' => array(
'javelin-install',
'javelin-reactor',
@ -1071,6 +1069,17 @@ return array(
'javelin-dom',
'javelin-reactor-dom',
),
'1e911d0f' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'21ba5861' => array(
'javelin-behavior',
'javelin-dom',
@ -1205,12 +1214,6 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'49b73b36' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'4b700e9e' => array(
'javelin-behavior',
'javelin-dom',
@ -1274,12 +1277,6 @@ return array(
'javelin-leader',
'javelin-json',
),
54733475 => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'54b612ba' => array(
'javelin-color',
'javelin-install',
@ -1528,6 +1525,13 @@ return array(
'javelin-vector',
'javelin-stratcom',
),
'82e270da' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
'javelin-stratcom',
),
'834a1173' => array(
'javelin-behavior',
'javelin-scrollbar',
@ -1598,6 +1602,12 @@ return array(
'javelin-dom',
'javelin-request',
),
'9030ebef' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'9196fb06' => array(
'javelin-install',
'javelin-dom',
@ -1692,6 +1702,11 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'a6f7a73b' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'a80d0378' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1845,13 +1860,6 @@ return array(
'javelin-vector',
'phui-hovercard',
),
'bd4c8dca' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
'javelin-stratcom',
),
'bdaf4d04' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phame.phame_blog
ADD parentDomain VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phame.phame_blog
ADD parentSite VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phame.phame_blog
ADD domainFullURI VARCHAR(128) COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,3 @@
UPDATE {$NAMESPACE}_phame.phame_blog
SET domainFullURI = CONCAT('http://', domain, '/')
WHERE domain IS NOT NULL;

View file

@ -0,0 +1,3 @@
UPDATE {$NAMESPACE}_phame.phame_blogtransaction
SET transactionType = 'phame.blog.full.domain'
WHERE transactionType = 'phame.blog.domain';

File diff suppressed because one or more lines are too long

View file

@ -54,10 +54,62 @@ if (!$repository->isHosted()) {
$engine->setRepository($repository);
$args = new PhutilArgumentParser($argv);
$args->parsePartial(
array(
array(
'name' => 'hook-mode',
'param' => 'mode',
'help' => pht('Hook execution mode.'),
),
));
$argv = array_merge(
array($argv[0]),
$args->getUnconsumedArgumentVector());
// Figure out which user is writing the commit.
$hook_mode = $args->getArg('hook-mode');
if ($hook_mode !== null) {
$known_modes = array(
'svn-revprop' => true,
);
if ($repository->isGit() || $repository->isHg()) {
if (empty($known_modes[$hook_mode])) {
throw new Exception(
pht(
'Invalid Hook Mode: This hook was invoked in "%s" mode, but this '.
'is not a recognized hook mode. Valid modes are: %s.',
$hook_mode,
implode(', ', array_keys($known_modes))));
}
}
$is_svnrevprop = ($hook_mode == 'svn-revprop');
if ($is_svnrevprop) {
// For now, we let these through if the repository allows dangerous changes
// and prevent them if it doesn't. See T11208 for discussion.
$revprop_key = $argv[5];
if ($repository->shouldAllowDangerousChanges()) {
$err = 0;
} else {
$err = 1;
$console = PhutilConsole::getConsole();
$console->writeErr(
pht(
"DANGEROUS CHANGE: Dangerous change protection is enabled for this ".
"repository, so you can not change revision properties (you are ".
"attempting to edit \"%s\").\n".
"Edit the repository configuration before making dangerous changes.",
$revprop_key));
}
exit($err);
} else if ($repository->isGit() || $repository->isHg()) {
$username = getenv(DiffusionCommitHookEngine::ENV_USER);
if (!strlen($username)) {
throw new Exception(

View file

@ -88,7 +88,22 @@ if ($as_device) {
$arguments[] = AlmanacKeys::getKeyPath('device.key');
}
// Subversion passes us a host in the form "domain.com:port", which is not
// valid for normal SSH but which we can parse into a valid "-p" flag.
$passthru_args = $unconsumed_argv;
$host = array_shift($passthru_args);
$parts = explode(':', $host, 2);
$host = $parts[0];
$port = $args->getArg('port');
if (!$port) {
if (count($parts) == 2) {
$port = $parts[1];
}
}
if ($port) {
$pattern[] = '-p %d';
$arguments[] = $port;
@ -96,7 +111,9 @@ if ($port) {
$pattern[] = '--';
$passthru_args = $unconsumed_argv;
$pattern[] = '%s';
$arguments[] = $host;
foreach ($passthru_args as $passthru_arg) {
$pattern[] = '%s';
$arguments[] = $passthru_arg;

View file

@ -190,12 +190,14 @@ try {
'P' => $user->getPHID(),
));
if (!$user->canEstablishSSHSessions()) {
throw new Exception(
pht(
'Your account ("%s") does not have permission to establish SSH '.
'sessions. Visit the web interface for more information.',
$user_name));
if (!$device) {
if (!$user->canEstablishSSHSessions()) {
throw new Exception(
pht(
'Your account ("%s") does not have permission to establish SSH '.
'sessions. Visit the web interface for more information.',
$user_name));
}
}
$workflows = id(new PhutilClassMapQuery())

View file

@ -350,7 +350,6 @@ phutil_register_library_map(array(
'DefaultDatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DefaultDatabaseConfigurationProvider.php',
'DifferentialAction' => 'applications/differential/constants/DifferentialAction.php',
'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php',
'DifferentialActionMenuEventListener' => 'applications/differential/event/DifferentialActionMenuEventListener.php',
'DifferentialAddCommentView' => 'applications/differential/view/DifferentialAddCommentView.php',
'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php',
'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php',
@ -1420,8 +1419,13 @@ phutil_register_library_map(array(
'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php',
'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php',
'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
'ManiphestTaskHasCommitRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php',
'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php',
'ManiphestTaskHasMockRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php',
'ManiphestTaskHasParentRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasParentRelationship.php',
'ManiphestTaskHasRevisionEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasRevisionEdgeType.php',
'ManiphestTaskHasRevisionRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasRevisionRelationship.php',
'ManiphestTaskHasSubtaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasSubtaskRelationship.php',
'ManiphestTaskHeraldField' => 'applications/maniphest/herald/ManiphestTaskHeraldField.php',
'ManiphestTaskHeraldFieldGroup' => 'applications/maniphest/herald/ManiphestTaskHeraldFieldGroup.php',
'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php',
@ -1437,6 +1441,7 @@ phutil_register_library_map(array(
'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php',
'ManiphestTaskPriorityHeraldField' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php',
'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php',
'ManiphestTaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskRelationship.php',
'ManiphestTaskResultListView' => 'applications/maniphest/view/ManiphestTaskResultListView.php',
'ManiphestTaskSearchEngine' => 'applications/maniphest/query/ManiphestTaskSearchEngine.php',
'ManiphestTaskStatus' => 'applications/maniphest/constants/ManiphestTaskStatus.php',
@ -2860,6 +2865,8 @@ phutil_register_library_map(array(
'PhabricatorObjectMentionedByObjectEdgeType' => 'applications/transactions/edges/PhabricatorObjectMentionedByObjectEdgeType.php',
'PhabricatorObjectMentionsObjectEdgeType' => 'applications/transactions/edges/PhabricatorObjectMentionsObjectEdgeType.php',
'PhabricatorObjectQuery' => 'applications/phid/query/PhabricatorObjectQuery.php',
'PhabricatorObjectRelationship' => 'applications/search/relationship/PhabricatorObjectRelationship.php',
'PhabricatorObjectRelationshipList' => 'applications/search/relationship/PhabricatorObjectRelationshipList.php',
'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php',
'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php',
'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php',
@ -3389,6 +3396,7 @@ phutil_register_library_map(array(
'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php',
'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php',
'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php',
'PhabricatorSearchRelationshipController' => 'applications/search/controller/PhabricatorSearchRelationshipController.php',
'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php',
'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php',
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
@ -3834,7 +3842,6 @@ phutil_register_library_map(array(
'PhluxVariablePHIDType' => 'applications/phlux/phid/PhluxVariablePHIDType.php',
'PhluxVariableQuery' => 'applications/phlux/query/PhluxVariableQuery.php',
'PhluxViewController' => 'applications/phlux/controller/PhluxViewController.php',
'PholioActionMenuEventListener' => 'applications/pholio/event/PholioActionMenuEventListener.php',
'PholioController' => 'applications/pholio/controller/PholioController.php',
'PholioDAO' => 'applications/pholio/storage/PholioDAO.php',
'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php',
@ -4662,7 +4669,6 @@ phutil_register_library_map(array(
),
'DifferentialAction' => 'Phobject',
'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand',
'DifferentialActionMenuEventListener' => 'PhabricatorEventListener',
'DifferentialAddCommentView' => 'AphrontView',
'DifferentialAdjustmentMapTestCase' => 'PhutilTestCase',
'DifferentialAffectedPath' => 'DifferentialDAO',
@ -5906,8 +5912,13 @@ phutil_register_library_map(array(
'ManiphestTaskEditController' => 'ManiphestController',
'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine',
'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasCommitRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasMockRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasParentRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasRevisionEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasRevisionRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasSubtaskRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHeraldField' => 'HeraldField',
'ManiphestTaskHeraldFieldGroup' => 'HeraldFieldGroup',
'ManiphestTaskListController' => 'ManiphestController',
@ -5923,6 +5934,7 @@ phutil_register_library_map(array(
'ManiphestTaskPriorityHeraldAction' => 'HeraldAction',
'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'ManiphestTaskRelationship' => 'PhabricatorObjectRelationship',
'ManiphestTaskResultListView' => 'ManiphestView',
'ManiphestTaskSearchEngine' => 'PhabricatorApplicationSearchEngine',
'ManiphestTaskStatus' => 'ManiphestConstants',
@ -7547,6 +7559,8 @@ phutil_register_library_map(array(
'PhabricatorObjectMentionedByObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectMentionsObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorObjectRelationship' => 'Phobject',
'PhabricatorObjectRelationshipList' => 'Phobject',
'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorObjectSelectorDialog' => 'Phobject',
'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery',
@ -8204,6 +8218,7 @@ phutil_register_library_map(array(
'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchOrderField' => 'PhabricatorSearchField',
'PhabricatorSearchRelationship' => 'Phobject',
'PhabricatorSearchRelationshipController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchResultBucket' => 'Phobject',
'PhabricatorSearchResultBucketGroup' => 'Phobject',
'PhabricatorSearchResultView' => 'AphrontView',
@ -8730,7 +8745,6 @@ phutil_register_library_map(array(
'PhluxVariablePHIDType' => 'PhabricatorPHIDType',
'PhluxVariableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhluxViewController' => 'PhluxController',
'PholioActionMenuEventListener' => 'PhabricatorEventListener',
'PholioController' => 'PhabricatorController',
'PholioDAO' => 'PhabricatorLiskDAO',
'PholioDefaultEditCapability' => 'PhabricatorPolicyCapability',

View file

@ -225,10 +225,10 @@ final class PhabricatorAuthStartController
}
// Often, users end up here by clicking a disabled action link in the UI
// (for example, they might click "Edit Blocking Tasks" on a Maniphest
// task page). After they log in we want to send them back to that main
// object page if we can, since it's confusing to end up on a standalone
// page with only a dialog (particularly if that dialog is another error,
// (for example, they might click "Edit Subtasks" on a Maniphest task
// page). After they log in we want to send them back to that main object
// page if we can, since it's confusing to end up on a standalone page with
// only a dialog (particularly if that dialog is another error,
// like a policy exception).
$via_header = AphrontRequest::getViaHeaderName();

View file

@ -47,7 +47,7 @@ final class PhabricatorBadgesEditor
case PhabricatorBadgesTransaction::TYPE_ICON:
return $object->getIcon();
case PhabricatorBadgesTransaction::TYPE_QUALITY:
return $object->getQuality();
return (int)$object->getQuality();
case PhabricatorBadgesTransaction::TYPE_STATUS:
return $object->getStatus();
case PhabricatorBadgesTransaction::TYPE_AWARD:

View file

@ -105,14 +105,14 @@ final class PhabricatorBadgesTransaction
}
break;
case self::TYPE_QUALITY:
$qual_new = PhabricatorBadgesQuality::getQualityName($new);
$qual_old = PhabricatorBadgesQuality::getQualityName($old);
if ($old === null) {
return pht(
'%s set the quality for this badge as "%s".',
$this->renderHandleLink($author_phid),
$new);
$qual_new);
} else {
$qual_new = PhabricatorBadgesQuality::getQualityName($new);
$qual_old = PhabricatorBadgesQuality::getQualityName($old);
return pht(
'%s updated the quality for this badge from "%s" to "%s".',
$this->renderHandleLink($author_phid),

View file

@ -474,8 +474,11 @@ abstract class PhabricatorController extends AphrontController {
public function newCurtainView($object) {
$viewer = $this->getViewer();
$action_id = celerity_generate_unique_node_id();
$action_list = id(new PhabricatorActionListView())
->setViewer($viewer);
->setViewer($viewer)
->setID($action_id);
// NOTE: Applications (objects of class PhabricatorApplication) can't
// currently be set here, although they don't need any of the extensions

View file

@ -13,6 +13,7 @@ final class CelerityStaticResourceResponse extends Phobject {
private $packaged;
private $metadata = array();
private $metadataBlock = 0;
private $metadataLocked;
private $behaviors = array();
private $hasRendered = array();
private $postprocessorKey;
@ -24,6 +25,13 @@ final class CelerityStaticResourceResponse extends Phobject {
}
public function addMetadata($metadata) {
if ($this->metadataLocked) {
throw new Exception(
pht(
'Attempting to add more metadata after metadata has been '.
'locked.'));
}
$id = count($this->metadata);
$this->metadata[$id] = $metadata;
return $this->metadataBlock.'_'.$id;
@ -189,6 +197,8 @@ final class CelerityStaticResourceResponse extends Phobject {
}
public function renderHTMLFooter() {
$this->metadataLocked = true;
$data = array();
if ($this->metadata) {
$json_metadata = AphrontResponse::encodeJSONForHTTPResponse(

View file

@ -43,7 +43,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
public function getEventListeners() {
return array(
new DifferentialActionMenuEventListener(),
new DifferentialLandingActionMenuEventListener(),
);
}

View file

@ -1,50 +0,0 @@
<?php
final class DifferentialActionMenuEventListener
extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionsEvent($event);
break;
}
}
private function handleActionsEvent(PhutilEvent $event) {
$object = $event->getValue('object');
$actions = null;
if ($object instanceof ManiphestTask) {
$actions = $this->renderTaskItems($event);
$this->addActionMenuItems($event, $actions);
}
}
private function renderTaskItems(PhutilEvent $event) {
if (!$this->canUseApplication($event->getUser())) {
return null;
}
$task = $event->getValue('object');
$phid = $task->getPHID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$event->getUser(),
$task,
PhabricatorPolicyCapability::CAN_EDIT);
return id(new PhabricatorActionView())
->setName(pht('Edit Differential Revisions'))
->setHref("/search/attach/{$phid}/DREV/")
->setIcon('fa-cog')
->setDisabled(!$can_edit)
->setWorkflow(true);
}
}

View file

@ -18,25 +18,14 @@ final class DiffusionRepositoryEditDangerousController
->getPanelURI();
if (!$repository->canAllowDangerousChanges()) {
if ($repository->isSVN()) {
return $this->newDialog()
->setTitle(pht('Not in Danger'))
->appendParagraph(
pht(
'It is not possible for users to push any dangerous changes '.
'to a Subversion repository. Pushes to a Subversion repository '.
'can always be reverted and never destroy data.'))
->addCancelButton($panel_uri);
} else {
return $this->newDialog()
->setTitle(pht('Unprotectable Repository'))
->appendParagraph(
pht(
'This repository can not be protected from dangerous changes '.
'because Phabricator does not control what users are allowed '.
'to push to it.'))
->addCancelButton($panel_uri);
}
return $this->newDialog()
->setTitle(pht('Unprotectable Repository'))
->appendParagraph(
pht(
'This repository can not be protected from dangerous changes '.
'because Phabricator does not control what users are allowed '.
'to push to it.'))
->addCancelButton($panel_uri);
}
if ($request->isFormPost()) {
@ -57,18 +46,34 @@ final class DiffusionRepositoryEditDangerousController
if ($repository->shouldAllowDangerousChanges()) {
$title = pht('Prevent Dangerous Changes');
$body = pht(
'It will no longer be possible to delete branches from this '.
'repository, or %s push to this repository.',
$force);
if ($repository->isSVN()) {
$body = pht(
'It will no longer be possible to edit revprops in this '.
'repository.');
} else {
$body = pht(
'It will no longer be possible to delete branches from this '.
'repository, or %s push to this repository.',
$force);
}
$submit = pht('Prevent Dangerous Changes');
} else {
$title = pht('Allow Dangerous Changes');
$body = pht(
'If you allow dangerous changes, it will be possible to delete '.
'branches and %s push this repository. These operations can '.
'alter a repository in a way that is difficult to recover from.',
$force);
if ($repository->isSVN()) {
$body = pht(
'If you allow dangerous changes, it will be possible to edit '.
'reprops in this repository, including arbitrarily rewriting '.
'commit messages. These operations can alter a repository in a '.
'way that is difficult to recover from.');
} else {
$body = pht(
'If you allow dangerous changes, it will be possible to delete '.
'branches and %s push this repository. These operations can '.
'alter a repository in a way that is difficult to recover from.',
$force);
}
$submit = pht('Allow Dangerous Changes');
}

View file

@ -31,24 +31,50 @@ final class DiffusionRepositoryDatasource
"phabricator-search-icon phui-font-fa phui-icon-view {$type_icon}";
$results = array();
foreach ($repos as $repo) {
$display_name = $repo->getMonogram().' '.$repo->getName();
foreach ($repos as $repository) {
$monogram = $repository->getMonogram();
$name = $repository->getName();
$name = $display_name;
$slug = $repo->getRepositorySlug();
$display_name = "{$monogram} {$name}";
$parts = array();
$parts[] = $name;
$slug = $repository->getRepositorySlug();
if (strlen($slug)) {
$name = "{$name} {$slug}";
$parts[] = $slug;
}
$results[] = id(new PhabricatorTypeaheadResult())
$callsign = $repository->getCallsign();
if ($callsign) {
$parts[] = $callsign;
}
foreach ($repository->getAllMonograms() as $monogram) {
$parts[] = $monogram;
}
$name = implode(' ', $parts);
$vcs = $repository->getVersionControlSystem();
$vcs_type = PhabricatorRepositoryType::getNameForRepositoryType($vcs);
$result = id(new PhabricatorTypeaheadResult())
->setName($name)
->setDisplayName($display_name)
->setURI($repo->getURI())
->setPHID($repo->getPHID())
->setPriorityString($repo->getMonogram())
->setURI($repository->getURI())
->setPHID($repository->getPHID())
->setPriorityString($repository->getMonogram())
->setPriorityType('repo')
->setImageSprite($image_sprite)
->setDisplayType(pht('Repository'));
->setDisplayType(pht('Repository'))
->addAttribute($vcs_type);
if (!$repository->isTracked()) {
$result->setClosed(pht('Inactive'));
}
$results[] = $result;
}
return $results;

View file

@ -43,7 +43,10 @@ final class DiffusionTaggedRepositoriesFunctionDatasource
->setColor(null)
->setPHID('tagged('.$result->getPHID().')')
->setDisplayName(pht('Tagged: %s', $result->getDisplayName()))
->setName('tagged '.$result->getName());
->setName('tagged '.$result->getName())
->resetAttributes()
->addAttribute(pht('Function'))
->addAttribute(pht('Select repositories tagged with this project.'));
}
return $results;

View file

@ -7,6 +7,10 @@ final class DrydockBlueprintDatasource
return pht('Type a blueprint name...');
}
public function getBrowseTitle() {
return pht('Browse Blueprints');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorDrydockApplication';
}
@ -37,6 +41,9 @@ final class DrydockBlueprintDatasource
$result->setClosed(pht('Disabled'));
}
$result->addAttribute(
$blueprint->getImplementation()->getBlueprintName());
$results[] = $result;
}

View file

@ -80,6 +80,8 @@ abstract class HeraldField extends Phobject {
HeraldAdapter::CONDITION_NOT_CONTAINS,
HeraldAdapter::CONDITION_REGEXP,
HeraldAdapter::CONDITION_NOT_REGEXP,
HeraldAdapter::CONDITION_EXISTS,
HeraldAdapter::CONDITION_NOT_EXISTS,
);
case self::STANDARD_TEXT_MAP:
return array(
@ -107,7 +109,13 @@ abstract class HeraldField extends Phobject {
case self::STANDARD_TEXT:
case self::STANDARD_TEXT_LIST:
case self::STANDARD_TEXT_MAP:
return new HeraldTextFieldValue();
switch ($condition) {
case HeraldAdapter::CONDITION_EXISTS:
case HeraldAdapter::CONDITION_NOT_EXISTS:
return new HeraldEmptyFieldValue();
default:
return new HeraldTextFieldValue();
}
case self::STANDARD_PHID:
case self::STANDARD_PHID_NULLABLE:
case self::STANDARD_PHID_LIST:

View file

@ -166,15 +166,6 @@ final class ManiphestTaskDetailController extends ManiphestController {
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Merge Duplicates In'))
->setHref("/search/attach/{$phid}/TASK/merge/")
->setWorkflow(true)
->setIcon('fa-compress')
->setDisabled(!$can_edit)
->setWorkflow(true));
$edit_config = $edit_engine->loadDefaultEditConfiguration();
$can_create = (bool)$edit_config;
@ -195,23 +186,45 @@ final class ManiphestTaskDetailController extends ManiphestController {
$edit_uri = $this->getApplicationURI($edit_uri);
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Subtask'))
->setHref($edit_uri)
->setIcon('fa-level-down')
->setDisabled(!$can_create)
->setWorkflow(!$can_create));
$task_submenu = array();
$task_submenu[] = id(new PhabricatorActionView())
->setName(pht('Create Subtask'))
->setHref($edit_uri)
->setIcon('fa-level-down')
->setDisabled(!$can_create)
->setWorkflow(!$can_create);
$relationship_list = PhabricatorObjectRelationshipList::newForObject(
$viewer,
$task);
$parent_key = ManiphestTaskHasParentRelationship::RELATIONSHIPKEY;
$subtask_key = ManiphestTaskHasSubtaskRelationship::RELATIONSHIPKEY;
$task_submenu[] = $relationship_list->getRelationship($parent_key)
->newAction($task);
$task_submenu[] = $relationship_list->getRelationship($subtask_key)
->newAction($task);
$task_submenu[] = id(new PhabricatorActionView())
->setName(pht('Merge Duplicates In'))
->setHref("/search/attach/{$phid}/TASK/merge/")
->setIcon('fa-compress')
->setDisabled(!$can_edit)
->setWorkflow(true);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Blocking Tasks'))
->setHref("/search/attach/{$phid}/TASK/blocks/")
->setWorkflow(true)
->setIcon('fa-link')
->setDisabled(!$can_edit)
->setWorkflow(true));
->setName(pht('Edit Related Tasks...'))
->setIcon('fa-anchor')
->setSubmenu($task_submenu));
$relationship_submenu = $relationship_list->newActionMenu();
if ($relationship_submenu) {
$curtain->addAction($relationship_submenu);
}
$owner_phid = $task->getOwnerPHID();
$author_phid = $task->getAuthorPHID();
@ -277,9 +290,9 @@ final class ManiphestTaskDetailController extends ManiphestController {
$edge_types = array(
ManiphestTaskDependedOnByTaskEdgeType::EDGECONST
=> pht('Blocks'),
=> pht('Parent Tasks'),
ManiphestTaskDependsOnTaskEdgeType::EDGECONST
=> pht('Blocked By'),
=> pht('Subtasks'),
ManiphestTaskHasRevisionEdgeType::EDGECONST
=> pht('Differential Revisions'),
ManiphestTaskHasMockEdgeType::EDGECONST

View file

@ -17,7 +17,7 @@ final class ManiphestTaskDependedOnByTaskEdgeType extends PhabricatorEdgeType {
$add_edges) {
return pht(
'%s added %s blocked task(s): %s.',
'%s added %s parent task(s): %s.',
$actor,
$add_count,
$add_edges);
@ -29,7 +29,7 @@ final class ManiphestTaskDependedOnByTaskEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s removed %s blocked task(s): %s.',
'%s removed %s parent task(s): %s.',
$actor,
$rem_count,
$rem_edges);
@ -44,7 +44,7 @@ final class ManiphestTaskDependedOnByTaskEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s edited blocked task(s), added %s: %s; removed %s: %s.',
'%s edited parent task(s), added %s: %s; removed %s: %s.',
$actor,
$add_count,
$add_edges,
@ -59,7 +59,7 @@ final class ManiphestTaskDependedOnByTaskEdgeType extends PhabricatorEdgeType {
$add_edges) {
return pht(
'%s added %s blocked task(s) for %s: %s.',
'%s added %s parent task(s) for %s: %s.',
$actor,
$add_count,
$object,
@ -73,7 +73,7 @@ final class ManiphestTaskDependedOnByTaskEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s removed %s blocked task(s) for %s: %s.',
'%s removed %s parent task(s) for %s: %s.',
$actor,
$rem_count,
$object,
@ -90,7 +90,7 @@ final class ManiphestTaskDependedOnByTaskEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s edited blocked task(s) for %s, added %s: %s; removed %s: %s.',
'%s edited parent task(s) for %s, added %s: %s; removed %s: %s.',
$actor,
$object,
$add_count,

View file

@ -22,7 +22,7 @@ final class ManiphestTaskDependsOnTaskEdgeType extends PhabricatorEdgeType {
$add_edges) {
return pht(
'%s added %s blocking task(s): %s.',
'%s added %s subtask(s): %s.',
$actor,
$add_count,
$add_edges);
@ -34,7 +34,7 @@ final class ManiphestTaskDependsOnTaskEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s removed %s blocking task(s): %s.',
'%s removed %s subtask(s): %s.',
$actor,
$rem_count,
$rem_edges);
@ -49,7 +49,7 @@ final class ManiphestTaskDependsOnTaskEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s edited blocking task(s), added %s: %s; removed %s: %s.',
'%s edited subtask(s), added %s: %s; removed %s: %s.',
$actor,
$add_count,
$add_edges,
@ -64,7 +64,7 @@ final class ManiphestTaskDependsOnTaskEdgeType extends PhabricatorEdgeType {
$add_edges) {
return pht(
'%s added %s blocking task(s) for %s: %s.',
'%s added %s subtask(s) for %s: %s.',
$actor,
$add_count,
$object,
@ -78,7 +78,7 @@ final class ManiphestTaskDependsOnTaskEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s removed %s blocking task(s) for %s: %s.',
'%s removed %s subtask(s) for %s: %s.',
$actor,
$rem_count,
$object,
@ -95,7 +95,7 @@ final class ManiphestTaskDependsOnTaskEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s edited blocking task(s) for %s, added %s: %s; removed %s: %s.',
'%s edited subtask(s) for %s, added %s: %s; removed %s: %s.',
$actor,
$object,
$add_count,

View file

@ -325,7 +325,7 @@ final class ManiphestTransactionEditor
ManiphestTransaction::MAILTAG_PROJECTS =>
pht("A task's associated projects change."),
ManiphestTransaction::MAILTAG_UNBLOCK =>
pht('One of the tasks a task is blocked by changes status.'),
pht("One of a task's subtasks changes status."),
ManiphestTransaction::MAILTAG_COLUMN =>
pht('A task is moved between columns on a workboard.'),
ManiphestTransaction::MAILTAG_COMMENT =>

View file

@ -0,0 +1,43 @@
<?php
final class ManiphestTaskHasCommitRelationship
extends ManiphestTaskRelationship {
const RELATIONSHIPKEY = 'task.has-commit';
public function getEdgeConstant() {
return ManiphestTaskHasCommitEdgeType::EDGECONST;
}
protected function getActionName() {
return pht('Edit Commits');
}
protected function getActionIcon() {
return 'fa-code';
}
public function shouldAppearInActionMenu() {
// TODO: For now, the default search for commits is not very good, so
// it is hard to find objects to link to. Until that works better, just
// hide this item.
return false;
}
public function canRelateObjects($src, $dst) {
return ($dst instanceof PhabricatorRepositoryCommit);
}
public function getDialogTitleText() {
return pht('Edit Related Commits');
}
public function getDialogHeaderText() {
return pht('Current Commits');
}
public function getDialogButtonText() {
return pht('Save Related Commits');
}
}

View file

@ -0,0 +1,36 @@
<?php
final class ManiphestTaskHasMockRelationship
extends ManiphestTaskRelationship {
const RELATIONSHIPKEY = 'task.has-mock';
public function getEdgeConstant() {
return ManiphestTaskHasMockEdgeType::EDGECONST;
}
protected function getActionName() {
return pht('Edit Mocks');
}
protected function getActionIcon() {
return 'fa-camera-retro';
}
public function canRelateObjects($src, $dst) {
return ($dst instanceof PholioMock);
}
public function getDialogTitleText() {
return pht('Edit Related Mocks');
}
public function getDialogHeaderText() {
return pht('Current Mocks');
}
public function getDialogButtonText() {
return pht('Save Related Mocks');
}
}

View file

@ -0,0 +1,40 @@
<?php
final class ManiphestTaskHasParentRelationship
extends ManiphestTaskRelationship {
const RELATIONSHIPKEY = 'task.has-parent';
public function getEdgeConstant() {
return ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
}
protected function getActionName() {
return pht('Edit Parent Tasks');
}
protected function getActionIcon() {
return 'fa-chevron-circle-up';
}
public function canRelateObjects($src, $dst) {
return ($dst instanceof ManiphestTask);
}
public function shouldAppearInActionMenu() {
return false;
}
public function getDialogTitleText() {
return pht('Edit Parent Tasks');
}
public function getDialogHeaderText() {
return pht('Current Parent Tasks');
}
public function getDialogButtonText() {
return pht('Save Parent Tasks');
}
}

View file

@ -0,0 +1,36 @@
<?php
final class ManiphestTaskHasRevisionRelationship
extends ManiphestTaskRelationship {
const RELATIONSHIPKEY = 'task.has-revision';
public function getEdgeConstant() {
return ManiphestTaskHasRevisionEdgeType::EDGECONST;
}
protected function getActionName() {
return pht('Edit Revisions');
}
protected function getActionIcon() {
return 'fa-cog';
}
public function canRelateObjects($src, $dst) {
return ($dst instanceof DifferentialRevision);
}
public function getDialogTitleText() {
return pht('Edit Related Revisions');
}
public function getDialogHeaderText() {
return pht('Current Revisions');
}
public function getDialogButtonText() {
return pht('Save Related Revisions');
}
}

View file

@ -0,0 +1,40 @@
<?php
final class ManiphestTaskHasSubtaskRelationship
extends ManiphestTaskRelationship {
const RELATIONSHIPKEY = 'task.has-subtask';
public function getEdgeConstant() {
return ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
}
protected function getActionName() {
return pht('Edit Subtasks');
}
protected function getActionIcon() {
return 'fa-chevron-circle-down';
}
public function canRelateObjects($src, $dst) {
return ($dst instanceof ManiphestTask);
}
public function shouldAppearInActionMenu() {
return false;
}
public function getDialogTitleText() {
return pht('Edit Subtasks');
}
public function getDialogHeaderText() {
return pht('Current Subtasks');
}
public function getDialogButtonText() {
return pht('Save Subtasks');
}
}

View file

@ -0,0 +1,19 @@
<?php
abstract class ManiphestTaskRelationship
extends PhabricatorObjectRelationship {
public function isEnabledForObject($object) {
$viewer = $this->getViewer();
$has_app = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorManiphestApplication',
$viewer);
if (!$has_app) {
return false;
}
return ($object instanceof ManiphestTask);
}
}

View file

@ -500,24 +500,24 @@ final class ManiphestTransaction
if ($this->getMetadataValue('blocker.new')) {
return pht(
'%s created blocking task %s.',
'%s created subtask %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($blocker_phid));
} else if ($old_closed && !$new_closed) {
return pht(
'%s reopened blocking task %s as "%s".',
'%s reopened subtask %s as "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($blocker_phid),
$new_name);
} else if (!$old_closed && $new_closed) {
return pht(
'%s closed blocking task %s as "%s".',
'%s closed subtask %s as "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($blocker_phid),
$new_name);
} else {
return pht(
'%s changed the status of blocking task %s from "%s" to "%s".',
'%s changed the status of subtask %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($blocker_phid),
$old_name,
@ -753,21 +753,21 @@ final class ManiphestTransaction
if ($old_closed && !$new_closed) {
return pht(
'%s reopened %s, a task blocking %s, as "%s".',
'%s reopened %s, a subtask of %s, as "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($blocker_phid),
$this->renderHandleLink($object_phid),
$new_name);
} else if (!$old_closed && $new_closed) {
return pht(
'%s closed %s, a task blocking %s, as "%s".',
'%s closed %s, a subtask of %s, as "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($blocker_phid),
$this->renderHandleLink($object_phid),
$new_name);
} else {
return pht(
'%s changed the status of %s, a task blocking %s, '.
'%s changed the status of %s, a subtasktask of %s, '.
'from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($blocker_phid),

View file

@ -67,7 +67,8 @@ final class ManiphestTaskClosedStatusDatasource
->setName($name.' closed')
->setDisplayName($name)
->setPHID(self::FUNCTION_TOKEN)
->setUnique(true);
->setUnique(true)
->addAttribute(pht('Select any closed status.'));
}
}

View file

@ -67,7 +67,8 @@ final class ManiphestTaskOpenStatusDatasource
->setName($name.' open')
->setDisplayName($name)
->setPHID(self::FUNCTION_TOKEN)
->setUnique(true);
->setUnique(true)
->addAttribute(pht('Select any open status.'));
}
}

View file

@ -32,7 +32,8 @@ final class ManiphestTaskPriorityDatasource
$result = id(new PhabricatorTypeaheadResult())
->setIcon(ManiphestTaskPriority::getTaskPriorityIcon($value))
->setPHID($value)
->setName($name);
->setName($name)
->addAttribute(pht('Priority'));
if (ManiphestTaskPriority::isDisabledPriority($value)) {
$result->setClosed(pht('Disabled'));

View file

@ -35,6 +35,12 @@ final class ManiphestTaskStatusDatasource
->setPHID($value)
->setName($name);
if (ManiphestTaskStatus::isOpenStatus($value)) {
$result->addAttribute(pht('Open Status'));
} else {
$result->addAttribute(pht('Closed Status'));
}
if (ManiphestTaskStatus::isDisabledStatus($value)) {
$result->setClosed(pht('Disabled'));
}

View file

@ -94,6 +94,10 @@ final class PhabricatorUser
* @return bool True if this is a standard, usable account.
*/
public function isUserActivated() {
if (!$this->isLoggedIn()) {
return false;
}
if ($this->isOmnipotent()) {
return true;
}

View file

@ -62,7 +62,8 @@ final class PhabricatorPeopleAnyOwnerDatasource
->setDisplayName($name)
->setIcon('fa-certificate')
->setPHID(self::FUNCTION_TOKEN)
->setUnique(true);
->setUnique(true)
->addAttribute(pht('Select results with any owner.'));
}
}

View file

@ -3,18 +3,6 @@
final class PhabricatorPeopleDatasource
extends PhabricatorTypeaheadDatasource {
private $enrichResults;
/**
* Controls enriched rendering, for global search. This is a bit hacky and
* should probably be handled in a more general way, but is fairly reasonable
* for now.
*/
public function setEnrichResults($enrich) {
$this->enrichResults = $enrich;
return $this;
}
public function getBrowseTitle() {
return pht('Browse Users');
}
@ -40,7 +28,9 @@ final class PhabricatorPeopleDatasource
$users = $this->executeQuery($query);
if ($this->enrichResults && $users) {
$is_browse = $this->getIsBrowse();
if ($is_browse && $users) {
$phids = mpull($users, 'getPHID');
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
@ -50,6 +40,8 @@ final class PhabricatorPeopleDatasource
$results = array();
foreach ($users as $user) {
$phid = $user->getPHID();
$closed = null;
if ($user->getIsDisabled()) {
$closed = pht('Disabled');
@ -64,7 +56,7 @@ final class PhabricatorPeopleDatasource
$result = id(new PhabricatorTypeaheadResult())
->setName($user->getFullName())
->setURI('/p/'.$username.'/')
->setPHID($user->getPHID())
->setPHID($phid)
->setPriorityString($username)
->setPriorityType('user')
->setAutocomplete('@'.$username)
@ -74,13 +66,29 @@ final class PhabricatorPeopleDatasource
$result->setIcon('fa-envelope-o');
}
if ($this->enrichResults) {
$display_type = pht('User');
if ($is_browse) {
$handle = $handles[$phid];
$result
->setIcon($handle->getIcon())
->setImageURI($handle->getImageURI())
->addAttribute($handle->getSubtitle());
if ($user->getIsAdmin()) {
$result->addAttribute(
array(
id(new PHUIIconView())->setIcon('fa-star'),
' ',
pht('Administrator'),
));
}
if ($user->getIsAdmin()) {
$display_type = pht('Administrator');
} else {
$display_type = pht('User');
}
$result->setDisplayType($display_type);
$result->setImageURI($handles[$user->getPHID()]->getImageURI());
}
$results[] = $result;

View file

@ -69,7 +69,8 @@ final class PhabricatorPeopleNoOwnerDatasource
->setDisplayName($name)
->setIcon('fa-ban')
->setPHID('none()')
->setUnique(true);
->setUnique(true)
->addAttribute(pht('Select results with no owner.'));
}
}

View file

@ -74,7 +74,8 @@ final class PhabricatorViewerDatasource
->setName(pht('Current Viewer'))
->setPHID('viewer()')
->setIcon('fa-user')
->setUnique(true);
->setUnique(true)
->addAttribute(pht('Select current viewer.'));
}
}

View file

@ -159,6 +159,12 @@ abstract class PhameLiveController extends PhameController {
// "Blogs" crumb into the crumbs list.
if ($is_external) {
$crumbs = new PHUICrumbsView();
// Link back to parent site
if ($blog->getParentSite() && $blog->getParentDomain()) {
$crumbs->addTextCrumb(
$blog->getParentSite(),
$blog->getExternalParentURI());
}
} else {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addTextCrumb(

View file

@ -95,15 +95,27 @@ final class PhameBlogManageController extends PhameBlogController {
Javelin::initBehavior('phabricator-tooltips');
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($blog);
->setUser($viewer);
$domain = $blog->getDomain();
if (!$domain) {
$domain = phutil_tag('em', array(), pht('No external domain'));
$full_domain = $blog->getDomainFullURI();
if (!$full_domain) {
$full_domain = phutil_tag('em', array(), pht('No external domain'));
}
$properties->addProperty(pht('Full Domain'), $full_domain);
$parent_site = $blog->getParentSite();
if (!$parent_site) {
$parent_site = phutil_tag('em', array(), pht('No parent site'));
}
$properties->addProperty(pht('Domain'), $domain);
$properties->addProperty(pht('Parent Site'), $parent_site);
$parent_domain = $blog->getParentDomain();
if (!$parent_domain) {
$parent_domain = phutil_tag('em', array(), pht('No parent domain'));
}
$properties->addProperty(pht('Parent Domain'), $parent_domain);
$feed_uri = PhabricatorEnv::getProductionURI(
$this->getApplicationURI('blog/feed/'.$blog->getID().'/'));
@ -133,8 +145,6 @@ final class PhameBlogManageController extends PhameBlogController {
->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)
->process();
$properties->invokeWillRenderEvent();
$description = $blog->getDescription();
if (strlen($description)) {
$description = new PHUIRemarkupView($viewer, $description);
@ -150,7 +160,7 @@ final class PhameBlogManageController extends PhameBlogController {
private function buildCurtain(PhameBlog $blog) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($viewer);
$curtain = $this->newCurtainView($blog);
$actions = id(new PhabricatorActionListView())
->setObject($blog)

View file

@ -94,13 +94,29 @@ final class PhameBlogEditEngine
->setTransactionType(PhameBlogTransaction::TYPE_DESCRIPTION)
->setValue($object->getDescription()),
id(new PhabricatorTextEditField())
->setKey('domain')
->setLabel(pht('Custom Domain'))
->setDescription(pht('Blog domain name.'))
->setConduitDescription(pht('Change the blog domain.'))
->setConduitTypeDescription(pht('New blog domain.'))
->setValue($object->getDomain())
->setTransactionType(PhameBlogTransaction::TYPE_DOMAIN),
->setKey('domainFullURI')
->setLabel(pht('Full Domain URI'))
->setDescription(pht('Blog full domain URI.'))
->setConduitDescription(pht('Change the blog full domain URI.'))
->setConduitTypeDescription(pht('New blog full domain URI.'))
->setValue($object->getDomainFullURI())
->setTransactionType(PhameBlogTransaction::TYPE_FULLDOMAIN),
id(new PhabricatorTextEditField())
->setKey('parentSite')
->setLabel(pht('Parent Site'))
->setDescription(pht('Blog parent site name.'))
->setConduitDescription(pht('Change the blog parent site name.'))
->setConduitTypeDescription(pht('New blog parent site name.'))
->setValue($object->getParentSite())
->setTransactionType(PhameBlogTransaction::TYPE_PARENTSITE),
id(new PhabricatorTextEditField())
->setKey('parentDomain')
->setLabel(pht('Parent Domain'))
->setDescription(pht('Blog parent domain name.'))
->setConduitDescription(pht('Change the blog parent domain.'))
->setConduitTypeDescription(pht('New blog parent domain.'))
->setValue($object->getParentDomain())
->setTransactionType(PhameBlogTransaction::TYPE_PARENTDOMAIN),
id(new PhabricatorSelectEditField())
->setKey('status')
->setLabel(pht('Status'))

View file

@ -17,7 +17,9 @@ final class PhameBlogEditor
$types[] = PhameBlogTransaction::TYPE_NAME;
$types[] = PhameBlogTransaction::TYPE_SUBTITLE;
$types[] = PhameBlogTransaction::TYPE_DESCRIPTION;
$types[] = PhameBlogTransaction::TYPE_DOMAIN;
$types[] = PhameBlogTransaction::TYPE_FULLDOMAIN;
$types[] = PhameBlogTransaction::TYPE_PARENTSITE;
$types[] = PhameBlogTransaction::TYPE_PARENTDOMAIN;
$types[] = PhameBlogTransaction::TYPE_STATUS;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@ -36,8 +38,12 @@ final class PhameBlogEditor
return $object->getSubtitle();
case PhameBlogTransaction::TYPE_DESCRIPTION:
return $object->getDescription();
case PhameBlogTransaction::TYPE_DOMAIN:
return $object->getDomain();
case PhameBlogTransaction::TYPE_FULLDOMAIN:
return $object->getDomainFullURI();
case PhameBlogTransaction::TYPE_PARENTSITE:
return $object->getParentSite();
case PhameBlogTransaction::TYPE_PARENTDOMAIN:
return $object->getParentDomain();
case PhameBlogTransaction::TYPE_STATUS:
return $object->getStatus();
}
@ -52,8 +58,10 @@ final class PhameBlogEditor
case PhameBlogTransaction::TYPE_SUBTITLE:
case PhameBlogTransaction::TYPE_DESCRIPTION:
case PhameBlogTransaction::TYPE_STATUS:
case PhameBlogTransaction::TYPE_PARENTSITE:
case PhameBlogTransaction::TYPE_PARENTDOMAIN:
return $xaction->getNewValue();
case PhameBlogTransaction::TYPE_DOMAIN:
case PhameBlogTransaction::TYPE_FULLDOMAIN:
$domain = $xaction->getNewValue();
if (!strlen($xaction->getNewValue())) {
return null;
@ -73,10 +81,23 @@ final class PhameBlogEditor
return $object->setSubtitle($xaction->getNewValue());
case PhameBlogTransaction::TYPE_DESCRIPTION:
return $object->setDescription($xaction->getNewValue());
case PhameBlogTransaction::TYPE_DOMAIN:
return $object->setDomain($xaction->getNewValue());
case PhameBlogTransaction::TYPE_FULLDOMAIN:
$new_value = $xaction->getNewValue();
if (strlen($new_value)) {
$uri = new PhutilURI($new_value);
$domain = $uri->getDomain();
$object->setDomain($domain);
} else {
$object->setDomain(null);
}
$object->setDomainFullURI($new_value);
return;
case PhameBlogTransaction::TYPE_STATUS:
return $object->setStatus($xaction->getNewValue());
case PhameBlogTransaction::TYPE_PARENTSITE:
return $object->setParentSite($xaction->getNewValue());
case PhameBlogTransaction::TYPE_PARENTDOMAIN:
return $object->setParentDomain($xaction->getNewValue());
}
return parent::applyCustomInternalTransaction($object, $xaction);
@ -90,7 +111,9 @@ final class PhameBlogEditor
case PhameBlogTransaction::TYPE_NAME:
case PhameBlogTransaction::TYPE_SUBTITLE:
case PhameBlogTransaction::TYPE_DESCRIPTION:
case PhameBlogTransaction::TYPE_DOMAIN:
case PhameBlogTransaction::TYPE_FULLDOMAIN:
case PhameBlogTransaction::TYPE_PARENTSITE:
case PhameBlogTransaction::TYPE_PARENTDOMAIN:
case PhameBlogTransaction::TYPE_STATUS:
return;
}
@ -123,7 +146,26 @@ final class PhameBlogEditor
$errors[] = $error;
}
break;
case PhameBlogTransaction::TYPE_DOMAIN:
case PhameBlogTransaction::TYPE_PARENTDOMAIN:
if (!$xactions) {
continue;
}
$parent_domain = last($xactions)->getNewValue();
if (empty($parent_domain)) {
continue;
}
try {
PhabricatorEnv::requireValidRemoteURIForLink($parent_domain);
} catch (Exception $ex) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid URI'),
pht('Parent Domain must be set to a valid Remote URI.'),
nonempty(last($xactions), null));
$errors[] = $error;
}
break;
case PhameBlogTransaction::TYPE_FULLDOMAIN:
if (!$xactions) {
continue;
}
@ -152,9 +194,11 @@ final class PhameBlogEditor
nonempty(last($xactions), null));
$errors[] = $error;
}
$domain = new PhutilURI($custom_domain);
$domain = $domain->getDomain();
$duplicate_blog = id(new PhameBlogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withDomain($custom_domain)
->withDomain($domain)
->executeOne();
if ($duplicate_blog && $duplicate_blog->getID() != $object->getID()) {
$error = new PhabricatorApplicationTransactionValidationError(

View file

@ -39,6 +39,7 @@ final class PhameBlogSite extends PhameSite {
->setViewer(new PhabricatorUser())
->withDomain($host)
->needProfileImage(true)
->needHeaderImage(true)
->withStatuses(
array(
PhameBlog::STATUS_ACTIVE,

View file

@ -18,6 +18,9 @@ final class PhameBlog extends PhameDAO
protected $subtitle;
protected $description;
protected $domain;
protected $domainFullURI;
protected $parentSite;
protected $parentDomain;
protected $configData;
protected $creatorPHID;
protected $viewPolicy;
@ -44,6 +47,9 @@ final class PhameBlog extends PhameDAO
'subtitle' => 'text64',
'description' => 'text',
'domain' => 'text128?',
'domainFullURI' => 'text128?',
'parentSite' => 'text128',
'parentDomain' => 'text128',
'status' => 'text32',
'mailKey' => 'bytes20',
'profileImagePHID' => 'phid?',
@ -108,34 +114,29 @@ final class PhameBlog extends PhameDAO
*
* @return string
*/
public function validateCustomDomain($custom_domain) {
$example_domain = 'blog.example.com';
public function validateCustomDomain($domain_full_uri) {
$example_domain = 'http://blog.example.com/';
$label = pht('Invalid');
// note this "uri" should be pretty busted given the desired input
// so just use it to test if there's a protocol specified
$uri = new PhutilURI($custom_domain);
if ($uri->getProtocol()) {
$uri = new PhutilURI($domain_full_uri);
$domain = $uri->getDomain();
$protocol = $uri->getProtocol();
$path = $uri->getPath();
$supported_protocols = array('http', 'https');
if (!in_array($protocol, $supported_protocols)) {
return array(
$label,
pht(
'The custom domain should not include a protocol. Just provide '.
'the bare domain name (for example, "%s").',
'The custom domain should include a valid protocol in the URI '.
'(for example, "%s"). Valid protocols are "http" or "https".',
$example_domain),
);
);
}
if ($uri->getPort()) {
return array(
$label,
pht(
'The custom domain should not include a port number. Just provide '.
'the bare domain name (for example, "%s").',
$example_domain),
);
}
if (strpos($custom_domain, '/') !== false) {
if (strlen($path) && $path != '/') {
return array(
$label,
pht(
@ -146,7 +147,7 @@ final class PhameBlog extends PhameDAO
);
}
if (strpos($custom_domain, '.') === false) {
if (strpos($domain, '.') === false) {
return array(
$label,
pht(
@ -187,8 +188,14 @@ final class PhameBlog extends PhameDAO
}
public function getExternalLiveURI() {
$domain = $this->getDomain();
$uri = new PhutilURI('http://'.$this->getDomain().'/');
$uri = new PhutilURI($this->getDomainFullURI());
PhabricatorEnv::requireValidRemoteURIForLink($uri);
return (string)$uri;
}
public function getExternalParentURI() {
$uri = $this->getParentDomain();
PhabricatorEnv::requireValidRemoteURIForLink($uri);
return (string)$uri;
}

View file

@ -6,8 +6,10 @@ final class PhameBlogTransaction
const TYPE_NAME = 'phame.blog.name';
const TYPE_SUBTITLE = 'phame.blog.subtitle';
const TYPE_DESCRIPTION = 'phame.blog.description';
const TYPE_DOMAIN = 'phame.blog.domain';
const TYPE_FULLDOMAIN = 'phame.blog.full.domain';
const TYPE_STATUS = 'phame.blog.status';
const TYPE_PARENTSITE = 'phame.blog.parent.site';
const TYPE_PARENTDOMAIN = 'phame.blog.parent.domain';
const MAILTAG_DETAILS = 'phame-blog-details';
const MAILTAG_SUBSCRIBERS = 'phame-blog-subscribers';
@ -44,7 +46,7 @@ final class PhameBlogTransaction
}
break;
case self::TYPE_DESCRIPTION:
case self::TYPE_DOMAIN:
case self::TYPE_FULLDOMAIN:
return 'fa-pencil';
case self::TYPE_STATUS:
if ($new == PhameBlog::STATUS_ARCHIVED) {
@ -65,7 +67,7 @@ final class PhameBlogTransaction
switch ($this->getTransactionType()) {
case self::TYPE_STATUS:
if ($new == PhameBlog::STATUS_ARCHIVED) {
return 'red';
return 'violet';
} else {
return 'green';
}
@ -83,7 +85,9 @@ final class PhameBlogTransaction
case self::TYPE_NAME:
case self::TYPE_SUBTITLE:
case self::TYPE_DESCRIPTION:
case self::TYPE_DOMAIN:
case self::TYPE_FULLDOMAIN:
case self::TYPE_PARENTSITE:
case self::TYPE_PARENTDOMAIN:
$tags[] = self::MAILTAG_DETAILS;
break;
default:
@ -136,12 +140,38 @@ final class PhameBlogTransaction
'%s updated the blog\'s description.',
$this->renderHandleLink($author_phid));
break;
case self::TYPE_DOMAIN:
case self::TYPE_FULLDOMAIN:
return pht(
'%s updated the blog\'s domain to "%s".',
'%s updated the blog\'s full domain to "%s".',
$this->renderHandleLink($author_phid),
$new);
break;
case self::TYPE_PARENTSITE:
if ($old === null) {
return pht(
'%s set this blog\'s parent site to "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
return pht(
'%s updated the blog\'s parent site to "%s".',
$this->renderHandleLink($author_phid),
$new);
}
break;
case self::TYPE_PARENTDOMAIN:
if ($old === null) {
return pht(
'%s set this blog\'s parent domain to "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
return pht(
'%s updated the blog\'s parent domain to "%s".',
$this->renderHandleLink($author_phid),
$new);
}
break;
case self::TYPE_STATUS:
switch ($new) {
case PhameBlog::STATUS_ACTIVE:
@ -200,9 +230,21 @@ final class PhameBlogTransaction
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case self::TYPE_DOMAIN:
case self::TYPE_FULLDOMAIN:
return pht(
'%s updated the domain for %s.',
'%s updated the full domain for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case self::TYPE_PARENTSITE:
return pht(
'%s updated the parent site for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case self::TYPE_PARENTDOMAIN:
return pht(
'%s updated the parent domain for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;

View file

@ -26,12 +26,6 @@ final class PhabricatorPholioApplication extends PhabricatorApplication {
return pht('Things before they were cool.');
}
public function getEventListeners() {
return array(
new PholioActionMenuEventListener(),
);
}
public function getRemarkupRules() {
return array(
new PholioRemarkupRule(),

View file

@ -1,51 +0,0 @@
<?php
final class PholioActionMenuEventListener
extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionsEvent($event);
break;
}
}
private function handleActionsEvent(PhutilEvent $event) {
$object = $event->getValue('object');
$actions = null;
if ($object instanceof ManiphestTask) {
$actions = $this->renderTaskItems($event);
}
$this->addActionMenuItems($event, $actions);
}
private function renderTaskItems(PhutilEvent $event) {
if (!$this->canUseApplication($event->getUser())) {
return;
}
$task = $event->getValue('object');
$phid = $task->getPHID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$event->getUser(),
$task,
PhabricatorPolicyCapability::CAN_EDIT);
return id(new PhabricatorActionView())
->setName(pht('Edit Pholio Mocks'))
->setHref("/search/attach/{$phid}/MOCK/edge/")
->setWorkflow(true)
->setIcon('fa-camera-retro')
->setDisabled(!$can_edit)
->setWorkflow(true);
}
}

View file

@ -55,9 +55,25 @@ final class PhabricatorProjectDatasource
$has_cols = array_fill_keys(array_keys($projs), true);
}
$is_browse = $this->getIsBrowse();
if ($is_browse && $projs) {
// TODO: This is a little ad-hoc, but we don't currently have
// infrastructure for bulk querying custom fields efficiently.
$table = new PhabricatorProjectCustomFieldStorage();
$descriptions = $table->loadAllWhere(
'objectPHID IN (%Ls) AND fieldIndex = %s',
array_keys($projs),
PhabricatorHash::digestForIndex('std:project:internal:description'));
$descriptions = mpull($descriptions, 'getFieldValue', 'getObjectPHID');
} else {
$descriptions = array();
}
$results = array();
foreach ($projs as $proj) {
if (!isset($has_cols[$proj->getPHID()])) {
$phid = $proj->getPHID();
if (!isset($has_cols[$phid])) {
continue;
}
@ -99,7 +115,7 @@ final class PhabricatorProjectDatasource
->setDisplayName($proj->getDisplayName())
->setDisplayType($proj->getDisplayIconName())
->setURI($proj->getURI())
->setPHID($proj->getPHID())
->setPHID($phid)
->setIcon($proj->getDisplayIconIcon())
->setColor($proj->getColor())
->setPriorityType('proj')
@ -111,6 +127,16 @@ final class PhabricatorProjectDatasource
$proj_result->setImageURI($proj->getProfileImageURI());
if ($is_browse) {
$proj_result->addAttribute($proj->getDisplayIconName());
$description = idx($descriptions, $phid);
if (strlen($description)) {
$summary = PhabricatorMarkupEngine::summarize($description);
$proj_result->addAttribute($summary);
}
}
$results[] = $proj_result;
}

View file

@ -86,20 +86,24 @@ final class PhabricatorProjectLogicalOrNotDatasource
$result
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
->setIcon('fa-asterisk')
->setColor(null);
->setColor(null)
->resetAttributes()
->addAttribute(pht('Function'));
if ($return_any) {
$return[] = id(clone $result)
->setPHID('any('.$result->getPHID().')')
->setDisplayName(pht('In Any: %s', $result->getDisplayName()))
->setName('any '.$result->getName());
->setName('any '.$result->getName())
->addAttribute(pht('Include results tagged with this project.'));
}
if ($return_not) {
$return[] = id(clone $result)
->setPHID('not('.$result->getPHID().')')
->setDisplayName(pht('Not In: %s', $result->getDisplayName()))
->setName('not '.$result->getName());
->setName('not '.$result->getName())
->addAttribute(pht('Exclude results tagged with this project.'));
}
}

View file

@ -96,7 +96,8 @@ final class PhabricatorProjectLogicalViewerDatasource
->setName(pht('Current Viewer\'s Projects'))
->setPHID('viewerprojects()')
->setIcon('fa-asterisk')
->setUnique(true);
->setUnique(true)
->addAttribute(pht('Select projects current viewer is a member of.'));
}
}

View file

@ -44,7 +44,10 @@ final class PhabricatorProjectMembersDatasource
->setColor(null)
->setPHID('members('.$result->getPHID().')')
->setDisplayName(pht('Members: %s', $result->getDisplayName()))
->setName($result->getName().' members');
->setName($result->getName().' members')
->resetAttributes()
->addAttribute(pht('Function'))
->addAttribute(pht('Select project members.'));
}
return $results;

View file

@ -68,7 +68,8 @@ final class PhabricatorProjectNoProjectsDatasource
->setPHID('null()')
->setIcon('fa-ban')
->setName('null '.$name)
->setDisplayName($name);
->setDisplayName($name)
->addAttribute(pht('Select results with no tags.'));
}
}

View file

@ -191,7 +191,7 @@ final class PhabricatorRepositoryPullEngine
));
}
private function installHook($path) {
private function installHook($path, array $hook_argv = array()) {
$this->log('%s', pht('Installing commit hook to "%s"...', $path));
$repository = $this->getRepository();
@ -202,10 +202,11 @@ final class PhabricatorRepositoryPullEngine
$full_php_path = Filesystem::resolveBinary('php');
$cmd = csprintf(
'exec %s -f %s -- %s "$@"',
'exec %s -f %s -- %s %Ls "$@"',
$full_php_path,
$bin,
$identifier);
$identifier,
$hook_argv);
$hook = "#!/bin/sh\nexport TERM=dumb\n{$cmd}\n";
@ -585,8 +586,16 @@ final class PhabricatorRepositoryPullEngine
$root = $repository->getLocalPath();
$path = '/hooks/pre-commit';
$this->installHook($root.$path);
$revprop_path = '/hooks/pre-revprop-change';
$revprop_argv = array(
'--hook-mode',
'svn-revprop',
);
$this->installHook($root.$revprop_path, $revprop_argv);
}

View file

@ -1623,11 +1623,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return false;
}
if ($this->isGit() || $this->isHg()) {
return true;
}
// In Git and Mercurial, ref deletions and rewrites are dangerous.
// In Subversion, editing revprops is dangerous.
return false;
return true;
}
public function shouldAllowDangerousChanges() {

View file

@ -41,6 +41,8 @@ final class PhabricatorSearchApplication extends PhabricatorApplication {
'delete/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/'
=> 'PhabricatorSearchDeleteController',
'order/(?P<engine>[^/]+)/' => 'PhabricatorSearchOrderController',
'rel/(?P<relationshipKey>[^/]+)/(?P<sourcePHID>[^/]+)/'
=> 'PhabricatorSearchRelationshipController',
),
);
}

View file

@ -18,6 +18,11 @@ final class PhabricatorSearchAttachController
$object = id(new PhabricatorObjectQuery())
->setViewer($user)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withPHIDs(array($phid))
->executeOne();

View file

@ -0,0 +1,205 @@
<?php
final class PhabricatorSearchRelationshipController
extends PhabricatorSearchBaseController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$phid = $request->getURIData('sourcePHID');
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$list = PhabricatorObjectRelationshipList::newForObject(
$viewer,
$object);
$relationship_key = $request->getURIData('relationshipKey');
$relationship = $list->getRelationship($relationship_key);
if (!$relationship) {
return new Aphront404Response();
}
$src_phid = $object->getPHID();
$edge_type = $relationship->getEdgeConstant();
$dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$src_phid,
$edge_type);
$all_phids = $dst_phids;
$all_phids[] = $src_phid;
$handles = $viewer->loadHandles($all_phids);
$src_handle = $handles[$src_phid];
$done_uri = $src_handle->getURI();
$initial_phids = $dst_phids;
if ($request->isFormPost()) {
$phids = explode(';', $request->getStr('phids'));
$phids = array_filter($phids);
$phids = array_values($phids);
$initial_phids = $request->getStrList('initialPHIDs');
// Apply the changes as adds and removes relative to the original state
// of the object when the dialog was rendered so that two users adding
// relationships at the same time don't race and overwrite one another.
$add_phids = array_diff($phids, $initial_phids);
$rem_phids = array_diff($initial_phids, $phids);
if ($add_phids) {
$dst_objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs($phids)
->setRaisePolicyExceptions(true)
->execute();
$dst_objects = mpull($dst_objects, null, 'getPHID');
} else {
$dst_objects = array();
}
try {
foreach ($add_phids as $add_phid) {
$dst_object = idx($dst_objects, $add_phid);
if (!$dst_object) {
throw new Exception(
pht(
'You can not create a relationship to object "%s" because '.
'the object does not exist or could not be loaded.',
$add_phid));
}
if (!$relationship->canRelateObjects($object, $dst_object)) {
throw new Exception(
pht(
'You can not create a relationship (of type "%s") to object '.
'"%s" because it is not the right type of object for this '.
'relationship.',
$relationship->getRelationshipConstant(),
$add_phid));
}
}
} catch (Exception $ex) {
return $this->newUnrelatableObjectResponse($ex, $done_uri);
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true);
$xactions = array();
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $edge_type)
->setNewValue(array(
'+' => array_fuse($add_phids),
'-' => array_fuse($rem_phids),
));
try {
$editor->applyTransactions($object, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
} catch (PhabricatorEdgeCycleException $ex) {
return $this->newGraphCycleResponse($ex, $done_uri);
}
}
$handles = iterator_to_array($handles);
$handles = array_select_keys($handles, $dst_phids);
// TODO: These are hard-coded for now.
$filters = array(
'assigned' => pht('Assigned to Me'),
'created' => pht('Created By Me'),
'open' => pht('All Open Objects'),
'all' => pht('All Objects'),
);
$dialog_title = $relationship->getDialogTitleText();
$dialog_header = $relationship->getDialogHeaderText();
$dialog_button = $relationship->getDialogButtonText();
$dialog_instructions = $relationship->getDialogInstructionsText();
// TODO: Remove this, this is just legacy support.
$legacy_kinds = array(
ManiphestTaskHasCommitEdgeType::EDGECONST => 'CMIT',
ManiphestTaskHasMockEdgeType::EDGECONST => 'MOCK',
ManiphestTaskHasRevisionEdgeType::EDGECONST => 'DREV',
ManiphestTaskDependsOnTaskEdgeType::EDGECONST => 'TASK',
ManiphestTaskDependedOnByTaskEdgeType::EDGECONST => 'TASK',
);
$edge_type = $relationship->getEdgeConstant();
$legacy_kind = idx($legacy_kinds, $edge_type);
if (!$legacy_kind) {
throw new Exception(
pht('Only specific legacy relationships are supported!'));
}
return id(new PhabricatorObjectSelectorDialog())
->setUser($viewer)
->setInitialPHIDs($initial_phids)
->setHandles($handles)
->setFilters($filters)
->setSelectedFilter('created')
->setExcluded($phid)
->setCancelURI($done_uri)
->setSearchURI("/search/select/{$legacy_kind}/edge/")
->setTitle($dialog_title)
->setHeader($dialog_header)
->setButtonText($dialog_button)
->setInstructions($dialog_instructions)
->buildDialog();
}
private function newGraphCycleResponse(
PhabricatorEdgeCycleException $ex,
$done_uri) {
$viewer = $this->getViewer();
$cycle = $ex->getCycle();
$handles = $this->loadViewerHandles($cycle);
$names = array();
foreach ($cycle as $cycle_phid) {
$names[] = $handles[$cycle_phid]->getFullName();
}
$message = pht(
'You can not create that relationship because it would create a '.
'circular dependency:');
$list = implode(" \xE2\x86\x92 ", $names);
return $this->newDialog()
->setTitle(pht('Circular Dependency'))
->appendParagraph($message)
->appendParagraph($list)
->addCancelButton($done_uri);
}
private function newUnrelatableObjectResponse(Exception $ex, $done_uri) {
$message = $ex->getMessage();
return $this->newDialog()
->setTitle(pht('Invalid Relationship'))
->appendParagraph($message)
->addCancelButton($done_uri);
}
}

View file

@ -0,0 +1,75 @@
<?php
abstract class PhabricatorObjectRelationship extends Phobject {
private $viewer;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
final public function getRelationshipConstant() {
return $this->getPhobjectClassConstant('RELATIONSHIPKEY');
}
abstract public function isEnabledForObject($object);
abstract public function getEdgeConstant();
abstract protected function getActionName();
abstract protected function getActionIcon();
abstract public function canRelateObjects($src, $dst);
abstract public function getDialogTitleText();
abstract public function getDialogHeaderText();
abstract public function getDialogButtonText();
public function getDialogInstructionsText() {
return null;
}
public function shouldAppearInActionMenu() {
return true;
}
protected function isActionEnabled($object) {
$viewer = $this->getViewer();
return PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
}
final public function newAction($object) {
$is_enabled = $this->isActionEnabled($object);
$action_uri = $this->getActionURI($object);
return id(new PhabricatorActionView())
->setName($this->getActionName())
->setHref($action_uri)
->setIcon($this->getActionIcon())
->setDisabled(!$is_enabled)
->setWorkflow(true);
}
final public static function getAllRelationships() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getRelationshipConstant')
->execute();
}
private function getActionURI($object) {
$phid = $object->getPHID();
$type = $this->getRelationshipConstant();
return "/search/rel/{$type}/{$phid}/";
}
}

View file

@ -0,0 +1,99 @@
<?php
final class PhabricatorObjectRelationshipList extends Phobject {
private $viewer;
private $object;
private $relationships;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
if ($this->viewer === null) {
throw new PhutilInvalidStateException('setViewer');
}
return $this->viewer;
}
public function setObject($object) {
$this->object = $object;
return $this;
}
public function getObject() {
if ($this->object === null) {
throw new PhutilInvalidStateException('setObject');
}
return $this->object;
}
public function setRelationships(array $relationships) {
assert_instances_of($relationships, 'PhabricatorObjectRelationship');
$this->relationships = $relationships;
return $this;
}
public function getRelationships() {
if ($this->relationships === null) {
throw new PhutilInvalidStateException('setRelationships');
}
return $this->relationships;
}
public function newActionMenu() {
$relationships = $this->getRelationships();
$object = $this->getObject();
$actions = array();
foreach ($relationships as $key => $relationship) {
if (!$relationship->shouldAppearInActionMenu()) {
continue;
}
$actions[$key] = $relationship->newAction($object);
}
if (!$actions) {
return null;
}
$actions = msort($actions, 'getName');
return id(new PhabricatorActionView())
->setName(pht('Edit Related Objects...'))
->setIcon('fa-link')
->setSubmenu($actions);
}
public function getRelationship($key) {
return idx($this->relationships, $key);
}
public static function newForObject(PhabricatorUser $viewer, $object) {
$relationships = PhabricatorObjectRelationship::getAllRelationships();
$results = array();
foreach ($relationships as $key => $relationship) {
$relationship = clone $relationship;
$relationship->setViewer($viewer);
if (!$relationship->isEnabledForObject($object)) {
continue;
}
$results[$key] = $relationship;
}
return id(new self())
->setViewer($viewer)
->setObject($object)
->setRelationships($results);
}
}

View file

@ -16,14 +16,22 @@ final class PhabricatorSearchDatasource
}
public function getComponentDatasources() {
return array(
id(new PhabricatorPeopleDatasource())->setEnrichResults(true),
$sources = array(
new PhabricatorPeopleDatasource(),
new PhabricatorProjectDatasource(),
new PhabricatorApplicationDatasource(),
new PhabricatorTypeaheadMonogramDatasource(),
new DiffusionRepositoryDatasource(),
new DiffusionSymbolDatasource(),
);
// These results are always rendered in the full browse display mode, so
// set the browse flag on all component sources.
foreach ($sources as $source) {
$source->setIsBrowse(true);
}
return $sources;
}
}

View file

@ -931,6 +931,7 @@ abstract class PhabricatorApplicationTransactionEditor
$object->openTransaction();
}
try {
foreach ($xactions as $xaction) {
$this->applyInternalEffects($object, $xaction);
}
@ -940,8 +941,6 @@ abstract class PhabricatorApplicationTransactionEditor
try {
$object->save();
} catch (AphrontDuplicateKeyQueryException $ex) {
$object->killTransaction();
// This callback has an opportunity to throw a better exception,
// so execution may end here.
$this->didCatchDuplicateKeyException($object, $xactions, $ex);
@ -973,7 +972,11 @@ abstract class PhabricatorApplicationTransactionEditor
$read_locking = false;
}
$object->saveTransaction();
$object->saveTransaction();
} catch (Exception $ex) {
$object->killTransaction();
throw $ex;
}
// Now that we've completely applied the core transaction set, try to apply
// Herald rules. Herald rules are allowed to either take direct actions on

View file

@ -343,6 +343,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
array(
'id' => $this->getPreviewPanelID(),
'style' => 'display: none',
'class' => 'phui-comment-preview-view',
),
$preview);
}

View file

@ -65,7 +65,8 @@ final class PhabricatorTypeaheadModularDatasourceController
}
$composite
->setOffset($offset);
->setOffset($offset)
->setIsBrowse(true);
}
$results = $composite->loadResults();
@ -142,9 +143,6 @@ final class PhabricatorTypeaheadModularDatasourceController
$items = array();
foreach ($results as $result) {
$token = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
$result);
// Disable already-selected tokens.
$disabled = isset($exclude[$result->getPHID()]);
@ -161,15 +159,14 @@ final class PhabricatorTypeaheadModularDatasourceController
),
pht('Select'));
$information = $this->renderBrowseResult($result, $button);
$items[] = phutil_tag(
'div',
array(
'class' => 'typeahead-browse-item grouped',
),
array(
$token,
$button,
));
$information);
}
$markup = array(
@ -250,6 +247,8 @@ final class PhabricatorTypeaheadModularDatasourceController
->setRenderDialogAsDiv(true)
->setTitle($source->getBrowseTitle())
->appendChild($browser)
->setResizeX(true)
->setResizeY($frame_id)
->addFooter($function_help)
->addCancelButton('/', pht('Close'));
}
@ -350,4 +349,60 @@ final class PhabricatorTypeaheadModularDatasourceController
->appendChild($view);
}
private function renderBrowseResult(
PhabricatorTypeaheadResult $result,
$button) {
$class = array();
$style = array();
$separator = " \xC2\xB7 ";
$class[] = 'phabricator-main-search-typeahead-result';
$name = phutil_tag(
'div',
array(
'class' => 'result-name',
),
$result->getDisplayName());
$icon = $result->getIcon();
$icon = id(new PHUIIconView())->setIcon($icon);
$attributes = $result->getAttributes();
$attributes = phutil_implode_html($separator, $attributes);
$attributes = array($icon, ' ', $attributes);
$closed = $result->getClosed();
if ($closed) {
$class[] = 'result-closed';
$attributes = array($closed, $separator, $attributes);
}
$attributes = phutil_tag(
'div',
array(
'class' => 'result-type',
),
$attributes);
$image = $result->getImageURI();
if ($image) {
$style[] = 'background-image: url('.$image.');';
$class[] = 'has-image';
}
return phutil_tag(
'div',
array(
'class' => implode(' ', $class),
'style' => implode(' ', $style),
),
array(
$button,
$name,
$attributes,
));
}
}

View file

@ -37,6 +37,7 @@ abstract class PhabricatorTypeaheadCompositeDatasource
}
$stack = $this->getFunctionStack();
$is_browse = $this->getIsBrowse();
$results = array();
foreach ($this->getUsableDatasources() as $source) {
@ -70,6 +71,10 @@ abstract class PhabricatorTypeaheadCompositeDatasource
$source->setLimit($offset + $limit);
}
if ($is_browse) {
$source->setIsBrowse(true);
}
$source_results = $source->loadResults();
$source_results = $source->didLoadResults($source_results);
$results[] = $source_results;

View file

@ -12,6 +12,7 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject {
private $limit;
private $parameters = array();
private $functionStack = array();
private $isBrowse;
public function setLimit($limit) {
$this->limit = $limit;
@ -71,6 +72,15 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject {
return idx($this->parameters, $name, $default);
}
public function setIsBrowse($is_browse) {
$this->isBrowse = $is_browse;
return $this;
}
public function getIsBrowse() {
return $this->isBrowse;
}
public function getDatasourceURI() {
$uri = new PhutilURI('/typeahead/class/'.get_class($this).'/');
$uri->setQueryParams($this->parameters);
@ -199,7 +209,8 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject {
protected function newFunctionResult() {
return id(new PhabricatorTypeaheadResult())
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
->setIcon('fa-asterisk');
->setIcon('fa-asterisk')
->addAttribute(pht('Function'));
}
public function newInvalidToken($name) {

View file

@ -17,6 +17,7 @@ final class PhabricatorTypeaheadResult extends Phobject {
private $tokenType;
private $unique;
private $autocomplete;
private $attributes = array();
public function setIcon($icon) {
$this->icon = $icon;
@ -188,4 +189,26 @@ final class PhabricatorTypeaheadResult extends Phobject {
return null;
}
public function getImageURI() {
return $this->imageURI;
}
public function getClosed() {
return $this->closed;
}
public function resetAttributes() {
$this->attributes = array();
return $this;
}
public function getAttributes() {
return $this->attributes;
}
public function addAttribute($attribute) {
$this->attributes[] = $attribute;
return $this;
}
}

View file

@ -134,7 +134,6 @@ final class PhabricatorEdgeEditor extends Phobject {
$caught = $ex;
}
if ($caught) {
$this->killTransactions();
}

View file

@ -28,10 +28,20 @@ final class PhabricatorEdgesDestructionEngineExtension
foreach ($edges as $type => $type_edges) {
foreach ($type_edges as $src => $src_edges) {
foreach ($src_edges as $dst => $edge) {
$editor->removeEdge($edge['src'], $edge['type'], $edge['dst']);
try {
$editor->removeEdge($edge['src'], $edge['type'], $edge['dst']);
} catch (Exception $ex) {
// We can run into an exception while removing the edge if the
// edge type no longer exists. This prevents us from figuring out
// if there's an inverse type. Just ignore any errors here and
// continue, since the best we can do is clean up all the edges
// we still have information about. See T11201.
phlog($ex);
}
}
}
}
$editor->save();
}

View file

@ -206,73 +206,73 @@ final class PhabricatorUSEnglishTranslation
),
),
'%s added %s blocking task(s): %s.' => array(
'%s added %s subtask(s): %s.' => array(
array(
'%s added a blocking task: %3$s.',
'%s added blocking tasks: %3$s.',
'%s added a subtask: %3$s.',
'%s added subtasks: %3$s.',
),
),
'%s added %s blocked task(s): %s.' => array(
'%s added %s parent task(s): %s.' => array(
array(
'%s added a blocked task: %3$s.',
'%s added blocked tasks: %3$s.',
'%s added a parent task: %3$s.',
'%s added parent tasks: %3$s.',
),
),
'%s removed %s blocking task(s): %s.' => array(
'%s removed %s subtask(s): %s.' => array(
array(
'%s removed a blocking task: %3$s.',
'%s removed blocking tasks: %3$s.',
'%s removed a subtask: %3$s.',
'%s removed subtasks: %3$s.',
),
),
'%s removed %s blocked task(s): %s.' => array(
'%s removed %s parent task(s): %s.' => array(
array(
'%s removed a blocked task: %3$s.',
'%s removed blocked tasks: %3$s.',
'%s removed a parent task: %3$s.',
'%s removed parent tasks: %3$s.',
),
),
'%s added %s blocking task(s) for %s: %s.' => array(
'%s added %s subtask(s) for %s: %s.' => array(
array(
'%s added a blocking task for %3$s: %4$s.',
'%s added blocking tasks for %3$s: %4$s.',
'%s added a subtask for %3$s: %4$s.',
'%s added subtasks for %3$s: %4$s.',
),
),
'%s added %s blocked task(s) for %s: %s.' => array(
'%s added %s parent task(s) for %s: %s.' => array(
array(
'%s added a blocked task for %3$s: %4$s.',
'%s added blocked tasks for %3$s: %4$s.',
'%s added a parent task for %3$s: %4$s.',
'%s added parent tasks for %3$s: %4$s.',
),
),
'%s removed %s blocking task(s) for %s: %s.' => array(
'%s removed %s subtask(s) for %s: %s.' => array(
array(
'%s removed a blocking task for %3$s: %4$s.',
'%s removed blocking tasks for %3$s: %4$s.',
'%s removed a subtask for %3$s: %4$s.',
'%s removed subtasks for %3$s: %4$s.',
),
),
'%s removed %s blocked task(s) for %s: %s.' => array(
'%s removed %s parent task(s) for %s: %s.' => array(
array(
'%s removed a blocked task for %3$s: %4$s.',
'%s removed blocked tasks for %3$s: %4$s.',
'%s removed a parent task for %3$s: %4$s.',
'%s removed parent tasks for %3$s: %4$s.',
),
),
'%s edited blocking task(s), added %s: %s; removed %s: %s.' =>
'%s edited blocking tasks, added: %3$s; removed: %5$s.',
'%s edited subtask(s), added %s: %s; removed %s: %s.' =>
'%s edited subtasks, added: %3$s; removed: %5$s.',
'%s edited blocking task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited blocking tasks for %s, added: %4$s; removed: %6$s.',
'%s edited subtask(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited subtasks for %s, added: %4$s; removed: %6$s.',
'%s edited blocked task(s), added %s: %s; removed %s: %s.' =>
'%s edited blocked tasks, added: %3$s; removed: %5$s.',
'%s edited parent task(s), added %s: %s; removed %s: %s.' =>
'%s edited parent tasks, added: %3$s; removed: %5$s.',
'%s edited blocked task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited blocked tasks for %s, added: %4$s; removed: %6$s.',
'%s edited parent task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited parent tasks for %s, added: %4$s; removed: %6$s.',
'%s edited answer(s), added %s: %s; removed %d: %s.' =>
'%s edited answers, added: %3$s; removed: %5$s.',

View file

@ -24,6 +24,8 @@ final class AphrontDialogView
private $flush;
private $validationException;
private $objectList;
private $resizeX;
private $resizeY;
const WIDTH_DEFAULT = 'default';
@ -72,6 +74,24 @@ final class AphrontDialogView
return $this->shortTitle;
}
public function setResizeY($resize_y) {
$this->resizeY = $resize_y;
return $this;
}
public function getResizeY() {
return $this->resizeY;
}
public function setResizeX($resize_x) {
$this->resizeX = $resize_x;
return $this;
}
public function getResizeX() {
return $this->resizeX;
}
public function addSubmitButton($text = null) {
if (!$text) {
$text = pht('Okay');
@ -347,6 +367,20 @@ final class AphrontDialogView
$this->footers);
}
$resize = null;
if ($this->resizeX || $this->resizeY) {
$resize = javelin_tag(
'div',
array(
'class' => 'aphront-dialog-resize',
'sigil' => 'jx-dialog-resize',
'meta' => array(
'resizeX' => $this->resizeX,
'resizeY' => $this->resizeY,
),
));
}
$tail = null;
if ($buttons || $footer) {
$tail = phutil_tag(
@ -357,6 +391,7 @@ final class AphrontDialogView
array(
$buttons,
$footer,
$resize,
));
}

View file

@ -10,6 +10,7 @@ final class PhabricatorObjectSelectorDialog extends Phobject {
private $searchURI;
private $selectedFilter;
private $excluded;
private $initialPHIDs;
private $title;
private $header;
@ -77,6 +78,15 @@ final class PhabricatorObjectSelectorDialog extends Phobject {
return $this;
}
public function setInitialPHIDs(array $initial_phids) {
$this->initialPHIDs = $initial_phids;
return $this;
}
public function getInitialPHIDs() {
return $this->initialPHIDs;
}
public function buildDialog() {
$user = $this->user;
@ -171,8 +181,14 @@ final class PhabricatorObjectSelectorDialog extends Phobject {
$view = new PhabricatorHandleObjectSelectorDataView($handle);
$handle_views[$phid] = $view->renderData();
}
$dialog->addHiddenInput('phids', implode(';', array_keys($this->handles)));
$initial_phids = $this->getInitialPHIDs();
if ($initial_phids) {
$initial_phids = implode(', ', $initial_phids);
$dialog->addHiddenInput('initialPHIDs', $initial_phids);
}
Javelin::initBehavior(
'phabricator-object-selector',
@ -188,7 +204,10 @@ final class PhabricatorObjectSelectorDialog extends Phobject {
'handles' => $handle_views,
));
return $dialog;
$dialog->setResizeX(true);
$dialog->setResizeY($results_id);
return $dialog;
}
}

View file

@ -21,6 +21,10 @@ final class PhabricatorActionListView extends AphrontView {
return $this;
}
public function getID() {
return $this->id;
}
public function render() {
$viewer = $this->getViewer();
@ -44,13 +48,26 @@ final class PhabricatorActionListView extends AphrontView {
require_celerity_resource('phabricator-action-list-view-css');
$items = array();
foreach ($actions as $action) {
foreach ($action->getItems() as $item) {
$items[] = $item;
}
}
return phutil_tag(
'ul',
array(
'class' => 'phabricator-action-list-view',
'id' => $this->id,
),
$actions);
$items);
}
public function getDropdownMenuMetadata() {
return array(
'items' => (string)hsprintf('%s', $this),
);
}

View file

@ -14,6 +14,10 @@ final class PhabricatorActionView extends AphrontView {
private $metadata;
private $selected;
private $openInNewWindow;
private $submenu = array();
private $hidden;
private $depth;
private $id;
public function setSelected($selected) {
$this->selected = $selected;
@ -66,6 +70,10 @@ final class PhabricatorActionView extends AphrontView {
return $this;
}
public function getName() {
return $this->name;
}
public function setLabel($label) {
$this->label = $label;
return $this;
@ -95,7 +103,60 @@ final class PhabricatorActionView extends AphrontView {
return $this->openInNewWindow;
}
public function getID() {
if (!$this->id) {
$this->id = celerity_generate_unique_node_id();
}
return $this->id;
}
public function setSubmenu(array $submenu) {
$this->submenu = $submenu;
if (!$this->getHref()) {
$this->setHref('#');
}
return $this;
}
public function getItems($depth = 0) {
$items = array();
$items[] = $this;
foreach ($this->submenu as $action) {
foreach ($action->getItems($depth + 1) as $item) {
$item
->setHidden(true)
->setDepth($depth + 1);
$items[] = $item;
}
}
return $items;
}
public function setHidden($hidden) {
$this->hidden = $hidden;
return $this;
}
public function getHidden() {
return $this->hidden;
}
public function setDepth($depth) {
$this->depth = $depth;
return $this;
}
public function getDepth() {
return $this->depth;
}
public function render() {
$caret_id = celerity_generate_unique_node_id();
$icon = null;
if ($this->icon) {
@ -155,6 +216,18 @@ final class PhabricatorActionView extends AphrontView {
$target = null;
}
if ($this->submenu) {
$caret = javelin_tag(
'span',
array(
'class' => 'caret-right',
'id' => $caret_id,
),
'');
} else {
$caret = null;
}
$item = javelin_tag(
'a',
array(
@ -164,7 +237,7 @@ final class PhabricatorActionView extends AphrontView {
'sigil' => $sigils,
'meta' => $this->metadata,
),
array($icon, $this->name));
array($icon, $this->name, $caret));
}
} else {
$item = phutil_tag(
@ -190,10 +263,47 @@ final class PhabricatorActionView extends AphrontView {
$classes[] = 'phabricator-action-view-selected';
}
return phutil_tag(
if ($this->submenu) {
$classes[] = 'phabricator-action-view-submenu';
}
$style = array();
if ($this->hidden) {
$style[] = 'display: none;';
}
if ($this->depth) {
$indent = ($this->depth * 16);
$style[] = "margin-left: {$indent}px;";
}
$sigil = null;
$meta = null;
if ($this->submenu) {
Javelin::initBehavior('phui-submenu');
$sigil = 'phui-submenu';
$item_ids = array();
foreach ($this->submenu as $subitem) {
$item_ids[] = $subitem->getID();
}
$meta = array(
'itemIDs' => $item_ids,
'caretID' => $caret_id,
);
}
return javelin_tag(
'li',
array(
'id' => $this->getID(),
'class' => implode(' ', $classes),
'style' => implode(' ', $style),
'sigil' => $sigil,
'meta' => $meta,
),
$item);
}

View file

@ -222,7 +222,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView
}
if ($user) {
if ($user->isLoggedIn()) {
if ($user->isUserActivated()) {
$offset = $user->getTimeZoneOffset();
$ignore_key = PhabricatorTimezoneIgnoreOffsetSetting::SETTINGKEY;

View file

@ -104,10 +104,19 @@ final class PHUIButtonView extends AphrontTagView {
public function setDropdownMenu(PhabricatorActionListView $actions) {
Javelin::initBehavior('phui-dropdown-menu');
$this->addSigil('phui-dropdown-menu');
$this->setMetadata($actions->getDropdownMenuMetadata());
return $this;
}
public function setDropdownMenuID($id) {
Javelin::initBehavior('phui-dropdown-menu');
$this->addSigil('phui-dropdown-menu');
$this->setMetadata(
array(
'items' => $actions,
'menuID' => $id,
));
return $this;

View file

@ -24,6 +24,7 @@ final class PHUIHeaderView extends AphrontTagView {
private $badges = array();
private $href;
private $actionList;
private $actionListID;
public function setHeader($header) {
$this->header = $header;
@ -90,6 +91,11 @@ final class PHUIHeaderView extends AphrontTagView {
return $this;
}
public function setActionListID($action_list_id) {
$this->actionListID = $action_list_id;
return $this;
}
public function setPolicyObject(PhabricatorPolicyInterface $object) {
$this->policyObject = $object;
return $this;
@ -189,14 +195,20 @@ final class PHUIHeaderView extends AphrontTagView {
protected function getTagContent() {
if ($this->actionList) {
if ($this->actionList || $this->actionListID) {
$action_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Actions'))
->setHref('#')
->setIcon('fa-bars')
->addClass('phui-mobile-menu')
->setDropdownMenu($this->actionList);
->addClass('phui-mobile-menu');
if ($this->actionList) {
$action_button->setDropdownMenu($this->actionList);
} else if ($this->actionListID) {
$action_button->setDropdownMenuID($this->actionListID);
}
$this->addActionLink($action_button);
}

View file

@ -43,10 +43,7 @@ final class PHUIListItemView extends AphrontTagView {
Javelin::initBehavior('phui-dropdown-menu');
$this->addSigil('phui-dropdown-menu');
$this->setMetadata(
array(
'items' => $actions,
));
$this->setMetadata($actions->getDropdownMenuMetadata());
return $this;
}

View file

@ -327,9 +327,7 @@ final class PHUITimelineEventView extends AphrontView {
'sigil' => $sigil,
'aria-haspopup' => 'true',
'aria-expanded' => 'false',
'meta' => array(
'items' => hsprintf('%s', $action_list),
),
'meta' => $action_list->getDropdownMenuMetadata(),
),
array(
$aural,

View file

@ -114,7 +114,7 @@ final class PHUITwoColumnView extends AphrontTagView {
$curtain = $this->getCurtain();
if ($curtain) {
$action_list = $curtain->getActionList();
$this->header->setActionList($action_list);
$this->header->setActionListID($action_list->getID());
}
$header = phutil_tag_div(

View file

@ -48,6 +48,7 @@
.aphront-dialog-tail {
border: none;
position: relative;
background: {$lightgreybackground};
padding: 8px 16px;
border-top: 1px solid {$thinblueborder};
@ -55,6 +56,23 @@
border-bottom-right-radius: 3px;
}
.device .aphront-dialog-resize {
/* No resizing on devices. */
display: none;
}
.aphront-dialog-resize {
position: absolute;
right: -4px;
bottom: -4px;
width: 18px;
height: 18px;
background-image: url(/rsrc/image/resize.png);
background-size: 100%;
cursor: nwse-resize;
pointer-events: all;
}
.aphront-dialog-foot {
padding: 6px 0;
float: left;

View file

@ -57,10 +57,26 @@ input.typeahead-browse-input {
.typeahead-browse-item button {
float: right;
margin: 2px 6px;
margin: 8px 6px 0;
}
.typeahead-browse-item a.jx-tokenizer-token {
margin-top: 1px;
margin-left: 6px;
}
.typeahead-browse-item .phabricator-main-search-typeahead-result {
margin: 2px 0;
padding: 0 8px;
}
.typeahead-browse-item .phabricator-main-search-typeahead-result.has-image {
padding-left: 48px;
}
.typeahead-browse-item
.phabricator-main-search-typeahead-result.result-closed
.result-name {
text-decoration: line-through;
color: {$lightgreytext};
}

View file

@ -264,6 +264,32 @@ a.policy-control .caret {
border-top-color: #000;
}
.phabricator-action-view-submenu .caret-right {
float: right;
margin-top: 4px;
margin-right: 6px;
border-left-color: {$alphablue};
}
.phabricator-action-view-submenu .caret {
float: right;
margin-top: 5px;
margin-right: 4px;
border-top: 7px solid {$lightgreytext};
}
.phabricator-action-list-view .phabricator-action-view-submenu.phui-submenu-open
.phabricator-action-view-item {
background-color: rgba({$alphablue}, 0.07);
color: {$sky};
border-radius: 3px;
}
.phabricator-action-list-view .phabricator-action-view-submenu.phui-submenu-open
.phabricator-action-view-item .phui-icon-view {
color: {$sky};
}
/* Icons */
.button.has-icon {
position: relative;

View file

@ -169,8 +169,9 @@
/* When tags appear in property lists, give them a little more vertical
spacing. */
.phui-property-list-view .phui-tag-view {
.phui-property-list-value .phui-tag-view {
margin: 2px 0;
white-space: pre-wrap;
}
.phui-property-list-has-actions .phui-property-list-properties-wrap {

View file

@ -417,3 +417,7 @@ a.phui-timeline-menu .phui-icon-view {
.phui-timeline-badges .phui-badge-mini .phui-icon-view {
font-size: 10px;
}
.phui-comment-preview-view {
margin-bottom: 20px;
}

View file

@ -157,6 +157,80 @@ JX.install('Workflow', {
_getActiveWorkflow : function() {
var stack = JX.Workflow._stack;
return stack[stack.length - 1];
},
_onresizestart: function(e) {
var self = JX.Workflow;
if (self._resizing) {
return;
}
var workflow = self._getActiveWorkflow();
if (!workflow) {
return;
}
e.kill();
var form = JX.DOM.find(workflow._root, 'div', 'jx-dialog');
var resize = e.getNodeData('jx-dialog-resize');
var node_y = JX.$(resize.resizeY);
var dim = JX.Vector.getDim(form);
dim.y = JX.Vector.getDim(node_y).y;
if (!form._minimumSize) {
form._minimumSize = dim;
}
self._resizing = {
min: form._minimumSize,
form: form,
startPos: JX.$V(e),
startDim: dim,
resizeY: node_y,
resizeX: resize.resizeX
};
},
_onmousemove: function(e) {
var self = JX.Workflow;
if (!self._resizing) {
return;
}
var spec = self._resizing;
var form = spec.form;
var min = spec.min;
var delta = JX.$V(e).add(-spec.startPos.x, -spec.startPos.y);
var src_dim = spec.startDim;
var dst_dim = JX.$V(src_dim.x + delta.x, src_dim.y + delta.y);
if (dst_dim.x < min.x) {
dst_dim.x = min.x;
}
if (dst_dim.y < min.y) {
dst_dim.y = min.y;
}
if (spec.resizeX) {
JX.$V(dst_dim.x, null).setDim(form);
}
if (spec.resizeY) {
JX.$V(null, dst_dim.y).setDim(spec.resizeY);
}
},
_onmouseup: function() {
var self = JX.Workflow;
if (!self._resizing) {
return;
}
self._resizing = false;
}
},
@ -220,6 +294,12 @@ JX.install('Workflow', {
[],
JX.Workflow._onsyntheticsubmit);
JX.DOM.listen(
this._root,
'mousedown',
'jx-dialog-resize',
JX.Workflow._onresizestart);
// Note that even in the presence of a content frame, we're doing
// everything here at top level: dialogs are fully modal and cover
// the entire window.
@ -413,6 +493,9 @@ JX.install('Workflow', {
}
JX.Stratcom.listen('keydown', null, close_dialog_when_user_presses_escape);
JX.Stratcom.listen('mousemove', null, JX.Workflow._onmousemove);
JX.Stratcom.listen('mouseup', null, JX.Workflow._onmouseup);
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

View file

@ -18,10 +18,21 @@ JX.behavior('phabricator-object-selector', function(config) {
var query_timer = null;
var query_delay = 50;
var phid_input = JX.DOM.find(
// TODO: This is fairly grotesque, but the dialog has two different forms
// inside it and there's no way to sigil the inputs in the "real" form right
// now. Clean this up when the dialog as a whole gets cleaned up.
var inputs = JX.DOM.scry(
JX.$(config.form),
'input',
'aphront-dialog-application-input');
var phid_input;
for (var ii = 0; ii < inputs.length; ii++) {
if (inputs[ii].name == 'phids') {
phid_input = inputs[ii];
break;
}
}
var last_value = JX.$(config.query).value;

View file

@ -16,17 +16,42 @@ JX.behavior('phui-dropdown-menu', function() {
e.kill();
var list = JX.$H(data.items).getFragment().firstChild;
var list;
var placeholder;
if (data.items) {
list = JX.$H(data.items).getFragment().firstChild;
} else {
list = JX.$(data.menuID);
placeholder = JX.$N('span');
}
var icon = e.getNode('phui-dropdown-menu');
data.menu = new JX.PHUIXDropdownMenu(icon);
data.menu.setContent(list);
data.menu.listen('open', function() {
if (placeholder) {
JX.DOM.replace(list, placeholder);
}
data.menu.setContent(list);
});
data.menu.listen('close', function() {
if (placeholder) {
JX.DOM.replace(placeholder, list);
}
});
data.menu.open();
JX.DOM.listen(list, 'click', 'tag:a', function(e) {
if (!e.isNormalClick()) {
return;
}
if (JX.Stratcom.pass()) {
return;
}
data.menu.close();
});
});

View file

@ -0,0 +1,44 @@
/**
* @provides javelin-behavior-phui-submenu
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
*/
JX.behavior('phui-submenu', function() {
JX.Stratcom.listen('click', 'phui-submenu', function(e) {
if (!e.isNormalClick()) {
return;
}
var node = e.getNode('phui-submenu');
var data = e.getNodeData('phui-submenu');
e.kill();
data.open = !data.open;
for (var ii = 0; ii < data.itemIDs.length; ii++) {
var id = data.itemIDs[ii];
var item = JX.$(id);
if (data.open) {
JX.DOM.show(item);
} else {
JX.DOM.hide(item);
}
// Add a class so we can animate zany effects.
JX.DOM.alterClass(item, 'phui-submenu-animate', data.open);
}
JX.DOM.alterClass(node, 'phui-submenu-open', data.open);
// Toggle the caret from ">" to "V" when opening the menu, and back again
// when closing it.
var caret = JX.$(data.caretID);
JX.DOM.alterClass(caret, 'caret', data.open);
JX.DOM.alterClass(caret, 'caret-right', !data.open);
});
});

View file

@ -42,7 +42,7 @@ JX.install('PHUIXDropdownMenu', {
JX.Stratcom.listen('keydown', null, JX.bind(this, this._onkey));
},
events: ['open'],
events: ['open', 'close'],
properties: {
width: null,
@ -83,6 +83,8 @@ JX.install('PHUIXDropdownMenu', {
this._open = false;
this._hide();
this.invoke('close');
return this;
},