1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-02 03:32:42 +01:00

(stable) Promote 2015 Week 36

This commit is contained in:
epriestley 2015-09-05 05:14:04 -07:00
commit edf7c85e7a
133 changed files with 2527 additions and 1299 deletions

View file

@ -2937,7 +2937,7 @@
//---------------------------------------------------------------------- //----------------------------------------------------------------------
public function getCode() public function getCode()
{ {
$ret; $ret = null;
if($this->count < $this->dataLength) { if($this->count < $this->dataLength) {
$row = $this->count % $this->blocks; $row = $this->count % $this->blocks;

View file

@ -7,8 +7,8 @@
*/ */
return array( return array(
'names' => array( 'names' => array(
'core.pkg.css' => 'aced76a5', 'core.pkg.css' => 'eb8c668d',
'core.pkg.js' => 'a590b451', 'core.pkg.js' => '47dc9ebb',
'darkconsole.pkg.js' => 'e7393ebb', 'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9', 'differential.pkg.css' => '2de124c9',
'differential.pkg.js' => '813c1633', 'differential.pkg.js' => '813c1633',
@ -36,18 +36,18 @@ return array(
'rsrc/css/application/base/notification-menu.css' => 'f31c0bde', 'rsrc/css/application/base/notification-menu.css' => 'f31c0bde',
'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601',
'rsrc/css/application/base/phui-theme.css' => '6b451f24', 'rsrc/css/application/base/phui-theme.css' => '6b451f24',
'rsrc/css/application/base/standard-page-view.css' => '4d176b67', 'rsrc/css/application/base/standard-page-view.css' => '1f53d056',
'rsrc/css/application/calendar/calendar-icon.css' => 'c69aa59f', 'rsrc/css/application/calendar/calendar-icon.css' => 'c69aa59f',
'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020',
'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
'rsrc/css/application/config/config-options.css' => '7fedf08b', 'rsrc/css/application/config/config-options.css' => '0ede4c9b',
'rsrc/css/application/config/config-template.css' => '8e6c6fcd', 'rsrc/css/application/config/config-template.css' => '8e6c6fcd',
'rsrc/css/application/config/config-welcome.css' => '6abd79be', 'rsrc/css/application/config/config-welcome.css' => '6abd79be',
'rsrc/css/application/config/setup-issue.css' => 'db7e9c40', 'rsrc/css/application/config/setup-issue.css' => 'db7e9c40',
'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a',
'rsrc/css/application/conpherence/durable-column.css' => '86396117', 'rsrc/css/application/conpherence/durable-column.css' => '86396117',
'rsrc/css/application/conpherence/menu.css' => 'f99fee4c', 'rsrc/css/application/conpherence/menu.css' => 'f99fee4c',
'rsrc/css/application/conpherence/message-pane.css' => 'dd4f8a3b', 'rsrc/css/application/conpherence/message-pane.css' => '5897d3ac',
'rsrc/css/application/conpherence/notification.css' => '6cdcc253', 'rsrc/css/application/conpherence/notification.css' => '6cdcc253',
'rsrc/css/application/conpherence/transaction.css' => '85d0974c', 'rsrc/css/application/conpherence/transaction.css' => '85d0974c',
'rsrc/css/application/conpherence/update.css' => 'faf6be09', 'rsrc/css/application/conpherence/update.css' => 'faf6be09',
@ -93,7 +93,7 @@ return array(
'rsrc/css/application/policy/policy-edit.css' => '815c66f7', 'rsrc/css/application/policy/policy-edit.css' => '815c66f7',
'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43',
'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/policy/policy.css' => '957ea14c',
'rsrc/css/application/ponder/ponder-view.css' => 'bef48f86', 'rsrc/css/application/ponder/ponder-view.css' => '7b0df4da',
'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a',
'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733',
'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5',
@ -104,14 +104,14 @@ return array(
'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => 'a76cefc9', 'rsrc/css/core/core.css' => 'a76cefc9',
'rsrc/css/core/remarkup.css' => '73fc4395', 'rsrc/css/core/remarkup.css' => 'ef286a6e',
'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/syntax.css' => '9fd11da8',
'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/core/z-index.css' => '57ddcaa2',
'rsrc/css/diviner/diviner-shared.css' => '5a337049', 'rsrc/css/diviner/diviner-shared.css' => '5a337049',
'rsrc/css/font/font-awesome.css' => 'd2fc4e8d', 'rsrc/css/font/font-awesome.css' => 'd2fc4e8d',
'rsrc/css/font/font-lato.css' => '5ab1a46a', 'rsrc/css/font/font-lato.css' => '5ab1a46a',
'rsrc/css/font/font-roboto-slab.css' => 'f24a53cb', 'rsrc/css/font/font-roboto-slab.css' => 'f24a53cb',
'rsrc/css/font/phui-font-icon-base.css' => '3dad2ae3', 'rsrc/css/font/phui-font-icon-base.css' => 'ecbbb4c2',
'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82',
'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52', 'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52',
'rsrc/css/layout/phabricator-side-menu-view.css' => 'bec2458e', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'bec2458e',
@ -138,10 +138,10 @@ return array(
'rsrc/css/phui/phui-info-view.css' => '5b16bac6', 'rsrc/css/phui/phui-info-view.css' => '5b16bac6',
'rsrc/css/phui/phui-list.css' => '125599df', 'rsrc/css/phui/phui-list.css' => '125599df',
'rsrc/css/phui/phui-object-box.css' => '407eaf5a', 'rsrc/css/phui/phui-object-box.css' => '407eaf5a',
'rsrc/css/phui/phui-object-item-list-view.css' => '36ce366c', 'rsrc/css/phui/phui-object-item-list-view.css' => 'ab1bf393',
'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-property-list-view.css' => '15bbe0b0', 'rsrc/css/phui/phui-property-list-view.css' => '03904f6b',
'rsrc/css/phui/phui-remarkup-preview.css' => '867f85b3', 'rsrc/css/phui/phui-remarkup-preview.css' => '867f85b3',
'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-status.css' => '888cedb8',
@ -210,7 +210,7 @@ return array(
'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5', 'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5',
'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9', 'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9',
'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03', 'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03',
'rsrc/externals/javelin/lib/DOM.js' => '147805fa', 'rsrc/externals/javelin/lib/DOM.js' => '805b806a',
'rsrc/externals/javelin/lib/History.js' => 'd4505101', 'rsrc/externals/javelin/lib/History.js' => 'd4505101',
'rsrc/externals/javelin/lib/JSON.js' => '69adf288', 'rsrc/externals/javelin/lib/JSON.js' => '69adf288',
'rsrc/externals/javelin/lib/Leader.js' => '331b1611', 'rsrc/externals/javelin/lib/Leader.js' => '331b1611',
@ -505,11 +505,11 @@ return array(
'calendar-icon-css' => 'c69aa59f', 'calendar-icon-css' => 'c69aa59f',
'changeset-view-manager' => '58562350', 'changeset-view-manager' => '58562350',
'conduit-api-css' => '7bc725c4', 'conduit-api-css' => '7bc725c4',
'config-options-css' => '7fedf08b', 'config-options-css' => '0ede4c9b',
'config-welcome-css' => '6abd79be', 'config-welcome-css' => '6abd79be',
'conpherence-durable-column-view' => '86396117', 'conpherence-durable-column-view' => '86396117',
'conpherence-menu-css' => 'f99fee4c', 'conpherence-menu-css' => 'f99fee4c',
'conpherence-message-pane-css' => 'dd4f8a3b', 'conpherence-message-pane-css' => '5897d3ac',
'conpherence-notification-css' => '6cdcc253', 'conpherence-notification-css' => '6cdcc253',
'conpherence-thread-manager' => '01774ab2', 'conpherence-thread-manager' => '01774ab2',
'conpherence-transaction-css' => '85d0974c', 'conpherence-transaction-css' => '85d0974c',
@ -659,7 +659,7 @@ return array(
'javelin-color' => '7e41274a', 'javelin-color' => '7e41274a',
'javelin-cookie' => '62dfea03', 'javelin-cookie' => '62dfea03',
'javelin-diffusion-locate-file-source' => 'b42eddc7', 'javelin-diffusion-locate-file-source' => 'b42eddc7',
'javelin-dom' => '147805fa', 'javelin-dom' => '805b806a',
'javelin-dynval' => 'f6555212', 'javelin-dynval' => 'f6555212',
'javelin-event' => '85ea0626', 'javelin-event' => '85ea0626',
'javelin-fx' => '54b612ba', 'javelin-fx' => '54b612ba',
@ -737,13 +737,13 @@ return array(
'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-object-selector-css' => '85ee8ce6',
'phabricator-phtize' => 'd254d646', 'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '6920d200', 'phabricator-prefab' => '6920d200',
'phabricator-remarkup-css' => '73fc4395', 'phabricator-remarkup-css' => 'ef286a6e',
'phabricator-search-results-css' => '7dea472c', 'phabricator-search-results-css' => '7dea472c',
'phabricator-shaped-request' => '7cbe244b', 'phabricator-shaped-request' => '7cbe244b',
'phabricator-side-menu-view-css' => 'bec2458e', 'phabricator-side-menu-view-css' => 'bec2458e',
'phabricator-slowvote-css' => '475b4bd2', 'phabricator-slowvote-css' => '475b4bd2',
'phabricator-source-code-view-css' => '5e0178de', 'phabricator-source-code-view-css' => '5e0178de',
'phabricator-standard-page-view' => '4d176b67', 'phabricator-standard-page-view' => '1f53d056',
'phabricator-textareautils' => '5c93c52c', 'phabricator-textareautils' => '5c93c52c',
'phabricator-title' => 'df5e11d2', 'phabricator-title' => 'df5e11d2',
'phabricator-tooltip' => '1d298e3a', 'phabricator-tooltip' => '1d298e3a',
@ -779,7 +779,7 @@ return array(
'phui-crumbs-view-css' => 'd842f867', 'phui-crumbs-view-css' => 'd842f867',
'phui-document-view-css' => '0267054b', 'phui-document-view-css' => '0267054b',
'phui-feed-story-css' => 'b7b26d23', 'phui-feed-story-css' => 'b7b26d23',
'phui-font-icon-base-css' => '3dad2ae3', 'phui-font-icon-base-css' => 'ecbbb4c2',
'phui-fontkit-css' => 'cb8ae7ad', 'phui-fontkit-css' => 'cb8ae7ad',
'phui-form-css' => 'afdb2c6e', 'phui-form-css' => 'afdb2c6e',
'phui-form-view-css' => '621b21c5', 'phui-form-view-css' => '621b21c5',
@ -791,10 +791,10 @@ return array(
'phui-inline-comment-view-css' => '0fdb3667', 'phui-inline-comment-view-css' => '0fdb3667',
'phui-list-view-css' => '125599df', 'phui-list-view-css' => '125599df',
'phui-object-box-css' => '407eaf5a', 'phui-object-box-css' => '407eaf5a',
'phui-object-item-list-view-css' => '36ce366c', 'phui-object-item-list-view-css' => 'ab1bf393',
'phui-pager-css' => 'bea33d23', 'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e', 'phui-pinboard-view-css' => '2495140e',
'phui-property-list-view-css' => '15bbe0b0', 'phui-property-list-view-css' => '03904f6b',
'phui-remarkup-preview-css' => '867f85b3', 'phui-remarkup-preview-css' => '867f85b3',
'phui-spacing-css' => '042804d6', 'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8', 'phui-status-list-view-css' => '888cedb8',
@ -811,7 +811,7 @@ return array(
'policy-css' => '957ea14c', 'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7', 'policy-edit-css' => '815c66f7',
'policy-transaction-detail-css' => '82100a43', 'policy-transaction-detail-css' => '82100a43',
'ponder-view-css' => 'bef48f86', 'ponder-view-css' => '7b0df4da',
'project-icon-css' => '4e3eaa5a', 'project-icon-css' => '4e3eaa5a',
'raphael-core' => '51ee6b43', 'raphael-core' => '51ee6b43',
'raphael-g' => '40dde778', 'raphael-g' => '40dde778',
@ -918,13 +918,6 @@ return array(
'javelin-uri', 'javelin-uri',
'phabricator-textareautils', 'phabricator-textareautils',
), ),
'147805fa' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
'1499a8cb' => array( '1499a8cb' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',
@ -1451,6 +1444,13 @@ return array(
'javelin-behavior', 'javelin-behavior',
'javelin-history', 'javelin-history',
), ),
'805b806a' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
82439934 => array( 82439934 => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom', 'javelin-dom',

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_ponder.ponder_question
ADD answerWiki LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL;

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_ponder.ponder_question
SET status = 'invalid' WHERE status = 'duplicate';

View file

@ -0,0 +1,52 @@
/* The "20150730.herald.5.sql" patch incorrectly swapped blocking and
non-blocking "Add Reviewer" rules. This swaps back any rules which
were last modified before the patch was applied. */
UPDATE {$NAMESPACE}_herald.herald_action a
JOIN {$NAMESPACE}_herald.herald_rule r
ON a.ruleID = r.id
SET a.action = 'differential.reviewers.blocking.tmp'
WHERE a.action = 'differential.reviewers.add'
AND r.dateModified <=
(SELECT applied FROM {$NAMESPACE}_meta_data.patch_status
WHERE patch = 'phabricator:20150730.herald.5.sql');
UPDATE {$NAMESPACE}_herald.herald_action a
JOIN {$NAMESPACE}_herald.herald_rule r
ON a.ruleID = r.id
SET a.action = 'differential.reviewers.add'
WHERE a.action = 'differential.reviewers.blocking'
AND r.dateModified <=
(SELECT applied FROM {$NAMESPACE}_meta_data.patch_status
WHERE patch = 'phabricator:20150730.herald.5.sql');
UPDATE {$NAMESPACE}_herald.herald_action a
JOIN {$NAMESPACE}_herald.herald_rule r
ON a.ruleID = r.id
SET a.action = 'differential.reviewers.blocking'
WHERE a.action = 'differential.reviewers.blocking.tmp';
UPDATE {$NAMESPACE}_herald.herald_action a
JOIN {$NAMESPACE}_herald.herald_rule r
ON a.ruleID = r.id
SET a.action = 'differential.reviewers.self.blocking.tmp'
WHERE a.action = 'differential.reviewers.self.add'
AND r.dateModified <=
(SELECT applied FROM {$NAMESPACE}_meta_data.patch_status
WHERE patch = 'phabricator:20150730.herald.5.sql');
UPDATE {$NAMESPACE}_herald.herald_action a
JOIN {$NAMESPACE}_herald.herald_rule r
ON a.ruleID = r.id
SET a.action = 'differential.reviewers.self.add'
WHERE a.action = 'differential.reviewers.self.blocking'
AND r.dateModified <=
(SELECT applied FROM {$NAMESPACE}_meta_data.patch_status
WHERE patch = 'phabricator:20150730.herald.5.sql');
UPDATE {$NAMESPACE}_herald.herald_action a
JOIN {$NAMESPACE}_herald.herald_rule r
ON a.ruleID = r.id
SET a.action = 'differential.reviewers.self.blocking'
WHERE a.action = 'differential.reviewers.self.blocking.tmp';

View file

@ -1,28 +0,0 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
if ($argc !== 2 || $argv[1] === '--help') {
echo pht('Usage: %s', 'aphrontpath.php <url>')."\n";
echo pht(
"Purpose: Print controller which will process passed %s.\n",
'<url>');
exit(1);
}
$url = parse_url($argv[1]);
$path = '/'.(isset($url['path']) ? ltrim($url['path'], '/') : '');
$config_key = 'aphront.default-application-configuration-class';
$application = PhabricatorEnv::newObjectFromConfig($config_key);
$application->setRequest(new AphrontRequest('', $path));
list($controller) = $application->buildControllerForPath($path);
if (!$controller && substr($path, -1) !== '/') {
list($controller) = $application->buildControllerForPath($path.'/');
}
if ($controller) {
echo get_class($controller)."\n";
}

View file

@ -143,6 +143,7 @@ phutil_register_library_map(array(
'AphrontJavelinView' => 'view/AphrontJavelinView.php', 'AphrontJavelinView' => 'view/AphrontJavelinView.php',
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/AphrontKeyboardShortcutsAvailableView.php', 'AphrontKeyboardShortcutsAvailableView' => 'view/widget/AphrontKeyboardShortcutsAvailableView.php',
'AphrontListFilterView' => 'view/layout/AphrontListFilterView.php', 'AphrontListFilterView' => 'view/layout/AphrontListFilterView.php',
'AphrontMalformedRequestException' => 'aphront/exception/AphrontMalformedRequestException.php',
'AphrontMoreView' => 'view/layout/AphrontMoreView.php', 'AphrontMoreView' => 'view/layout/AphrontMoreView.php',
'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php', 'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php',
'AphrontMySQLDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontMySQLDatabaseConnectionTestCase.php', 'AphrontMySQLDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontMySQLDatabaseConnectionTestCase.php',
@ -156,8 +157,12 @@ phutil_register_library_map(array(
'AphrontRedirectResponseTestCase' => 'aphront/response/__tests__/AphrontRedirectResponseTestCase.php', 'AphrontRedirectResponseTestCase' => 'aphront/response/__tests__/AphrontRedirectResponseTestCase.php',
'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php', 'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php',
'AphrontRequest' => 'aphront/AphrontRequest.php', 'AphrontRequest' => 'aphront/AphrontRequest.php',
'AphrontRequestExceptionHandler' => 'aphront/handler/AphrontRequestExceptionHandler.php',
'AphrontRequestTestCase' => 'aphront/__tests__/AphrontRequestTestCase.php', 'AphrontRequestTestCase' => 'aphront/__tests__/AphrontRequestTestCase.php',
'AphrontResponse' => 'aphront/response/AphrontResponse.php', 'AphrontResponse' => 'aphront/response/AphrontResponse.php',
'AphrontResponseProducerInterface' => 'aphront/interface/AphrontResponseProducerInterface.php',
'AphrontRoutingMap' => 'aphront/site/AphrontRoutingMap.php',
'AphrontRoutingResult' => 'aphront/site/AphrontRoutingResult.php',
'AphrontSideNavFilterView' => 'view/layout/AphrontSideNavFilterView.php', 'AphrontSideNavFilterView' => 'view/layout/AphrontSideNavFilterView.php',
'AphrontSite' => 'aphront/site/AphrontSite.php', 'AphrontSite' => 'aphront/site/AphrontSite.php',
'AphrontStackTraceView' => 'view/widget/AphrontStackTraceView.php', 'AphrontStackTraceView' => 'view/widget/AphrontStackTraceView.php',
@ -166,9 +171,7 @@ phutil_register_library_map(array(
'AphrontTagView' => 'view/AphrontTagView.php', 'AphrontTagView' => 'view/AphrontTagView.php',
'AphrontTokenizerTemplateView' => 'view/control/AphrontTokenizerTemplateView.php', 'AphrontTokenizerTemplateView' => 'view/control/AphrontTokenizerTemplateView.php',
'AphrontTypeaheadTemplateView' => 'view/control/AphrontTypeaheadTemplateView.php', 'AphrontTypeaheadTemplateView' => 'view/control/AphrontTypeaheadTemplateView.php',
'AphrontURIMapper' => 'aphront/AphrontURIMapper.php',
'AphrontUnhandledExceptionResponse' => 'aphront/response/AphrontUnhandledExceptionResponse.php', 'AphrontUnhandledExceptionResponse' => 'aphront/response/AphrontUnhandledExceptionResponse.php',
'AphrontUsageException' => 'aphront/exception/AphrontUsageException.php',
'AphrontView' => 'view/AphrontView.php', 'AphrontView' => 'view/AphrontView.php',
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php', 'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
'ArcanistConduitAPIMethod' => 'applications/arcanist/conduit/ArcanistConduitAPIMethod.php', 'ArcanistConduitAPIMethod' => 'applications/arcanist/conduit/ArcanistConduitAPIMethod.php',
@ -495,6 +498,7 @@ phutil_register_library_map(array(
'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php', 'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php',
'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php', 'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php',
'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php', 'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php',
'DiffusionAuditorFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorFunctionDatasource.php',
'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php', 'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php',
'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php', 'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php',
'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php', 'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php',
@ -1481,6 +1485,7 @@ phutil_register_library_map(array(
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php', 'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php',
'PhabricatorAdministratorsPolicyRule' => 'applications/policy/rule/PhabricatorAdministratorsPolicyRule.php', 'PhabricatorAdministratorsPolicyRule' => 'applications/policy/rule/PhabricatorAdministratorsPolicyRule.php',
'PhabricatorAjaxRequestExceptionHandler' => 'aphront/handler/PhabricatorAjaxRequestExceptionHandler.php',
'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php', 'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php',
'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php', 'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php',
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
@ -1608,6 +1613,7 @@ phutil_register_library_map(array(
'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php', 'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php', 'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php',
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php',
'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php',
'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php',
'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php',
@ -1783,6 +1789,7 @@ phutil_register_library_map(array(
'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php', 'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php', 'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php',
'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php',
'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php', 'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php',
'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php', 'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php',
'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php', 'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php',
@ -1835,6 +1842,7 @@ phutil_register_library_map(array(
'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php', 'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php',
'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php', 'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php',
'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php',
'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php',
'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php',
'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php', 'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php',
@ -1990,6 +1998,7 @@ phutil_register_library_map(array(
'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php',
'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php',
'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php',
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php', 'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php',
@ -2180,6 +2189,7 @@ phutil_register_library_map(array(
'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php', 'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php',
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php', 'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php', 'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php', 'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php', 'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php',
'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php', 'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php',
@ -2435,6 +2445,8 @@ phutil_register_library_map(array(
'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php', 'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php',
'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php',
'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php', 'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php',
'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php',
'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php',
'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php', 'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php',
'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php',
'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php',
@ -2575,6 +2587,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php', 'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php',
'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php', 'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php',
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php', 'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php', 'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php', 'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
@ -2631,6 +2644,7 @@ phutil_register_library_map(array(
'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php',
'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php', 'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php',
'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php',
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php', 'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php', 'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php',
@ -2650,6 +2664,7 @@ phutil_register_library_map(array(
'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php',
'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php',
'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php',
'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php', 'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php', 'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
'PhabricatorProjectsPolicyRule' => 'applications/policy/rule/PhabricatorProjectsPolicyRule.php', 'PhabricatorProjectsPolicyRule' => 'applications/policy/rule/PhabricatorProjectsPolicyRule.php',
@ -2660,6 +2675,7 @@ phutil_register_library_map(array(
'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php',
'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php',
'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php',
'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php',
'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php',
'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php', 'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php',
'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
@ -2751,6 +2767,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php', 'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php', 'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php',
'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php',
'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php', 'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php',
'PhabricatorRobotsController' => 'applications/system/controller/PhabricatorRobotsController.php', 'PhabricatorRobotsController' => 'applications/system/controller/PhabricatorRobotsController.php',
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php', 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
@ -3126,7 +3143,6 @@ phutil_register_library_map(array(
'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php', 'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php',
'PhameBlogLiveController' => 'applications/phame/controller/blog/PhameBlogLiveController.php', 'PhameBlogLiveController' => 'applications/phame/controller/blog/PhameBlogLiveController.php',
'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php', 'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php',
'PhameBlogResourceSite' => 'applications/phame/site/PhameBlogResourceSite.php',
'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php', 'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php',
'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php', 'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php',
'PhameBlogSkin' => 'applications/phame/skins/PhameBlogSkin.php', 'PhameBlogSkin' => 'applications/phame/skins/PhameBlogSkin.php',
@ -3727,7 +3743,10 @@ phutil_register_library_map(array(
'AphrontCursorPagerView' => 'AphrontView', 'AphrontCursorPagerView' => 'AphrontView',
'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration',
'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogResponse' => 'AphrontResponse',
'AphrontDialogView' => 'AphrontView', 'AphrontDialogView' => array(
'AphrontView',
'AphrontResponseProducerInterface',
),
'AphrontException' => 'Exception', 'AphrontException' => 'Exception',
'AphrontFileResponse' => 'AphrontResponse', 'AphrontFileResponse' => 'AphrontResponse',
'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormCheckboxControl' => 'AphrontFormControl',
@ -3762,6 +3781,7 @@ phutil_register_library_map(array(
'AphrontJavelinView' => 'AphrontView', 'AphrontJavelinView' => 'AphrontView',
'AphrontKeyboardShortcutsAvailableView' => 'AphrontView', 'AphrontKeyboardShortcutsAvailableView' => 'AphrontView',
'AphrontListFilterView' => 'AphrontView', 'AphrontListFilterView' => 'AphrontView',
'AphrontMalformedRequestException' => 'AphrontException',
'AphrontMoreView' => 'AphrontView', 'AphrontMoreView' => 'AphrontView',
'AphrontMultiColumnView' => 'AphrontView', 'AphrontMultiColumnView' => 'AphrontView',
'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase', 'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase',
@ -3770,13 +3790,19 @@ phutil_register_library_map(array(
'AphrontPageView' => 'AphrontView', 'AphrontPageView' => 'AphrontView',
'AphrontPlainTextResponse' => 'AphrontResponse', 'AphrontPlainTextResponse' => 'AphrontResponse',
'AphrontProgressBarView' => 'AphrontBarView', 'AphrontProgressBarView' => 'AphrontBarView',
'AphrontProxyResponse' => 'AphrontResponse', 'AphrontProxyResponse' => array(
'AphrontResponse',
'AphrontResponseProducerInterface',
),
'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontRedirectResponse' => 'AphrontResponse',
'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase', 'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase',
'AphrontReloadResponse' => 'AphrontRedirectResponse', 'AphrontReloadResponse' => 'AphrontRedirectResponse',
'AphrontRequest' => 'Phobject', 'AphrontRequest' => 'Phobject',
'AphrontRequestExceptionHandler' => 'Phobject',
'AphrontRequestTestCase' => 'PhabricatorTestCase', 'AphrontRequestTestCase' => 'PhabricatorTestCase',
'AphrontResponse' => 'Phobject', 'AphrontResponse' => 'Phobject',
'AphrontRoutingMap' => 'Phobject',
'AphrontRoutingResult' => 'Phobject',
'AphrontSideNavFilterView' => 'AphrontView', 'AphrontSideNavFilterView' => 'AphrontView',
'AphrontSite' => 'Phobject', 'AphrontSite' => 'Phobject',
'AphrontStackTraceView' => 'AphrontView', 'AphrontStackTraceView' => 'AphrontView',
@ -3785,9 +3811,7 @@ phutil_register_library_map(array(
'AphrontTagView' => 'AphrontView', 'AphrontTagView' => 'AphrontView',
'AphrontTokenizerTemplateView' => 'AphrontView', 'AphrontTokenizerTemplateView' => 'AphrontView',
'AphrontTypeaheadTemplateView' => 'AphrontView', 'AphrontTypeaheadTemplateView' => 'AphrontView',
'AphrontURIMapper' => 'Phobject',
'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse', 'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse',
'AphrontUsageException' => 'AphrontException',
'AphrontView' => array( 'AphrontView' => array(
'Phobject', 'Phobject',
'PhutilSafeHTMLProducerInterface', 'PhutilSafeHTMLProducerInterface',
@ -4157,6 +4181,7 @@ phutil_register_library_map(array(
'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialViewPolicyField' => 'DifferentialCoreCustomField', 'DifferentialViewPolicyField' => 'DifferentialCoreCustomField',
'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionAuditorFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction',
'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction',
'DiffusionAuditorsHeraldAction' => 'HeraldAction', 'DiffusionAuditorsHeraldAction' => 'HeraldAction',
@ -4369,7 +4394,7 @@ phutil_register_library_map(array(
'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionServeController' => 'DiffusionController', 'DiffusionServeController' => 'DiffusionController',
'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel', 'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
'DiffusionSetupException' => 'AphrontUsageException', 'DiffusionSetupException' => 'Exception',
'DiffusionSubversionSSHWorkflow' => 'DiffusionSSHWorkflow', 'DiffusionSubversionSSHWorkflow' => 'DiffusionSSHWorkflow',
'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow', 'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow',
'DiffusionSubversionWireProtocol' => 'Phobject', 'DiffusionSubversionWireProtocol' => 'Phobject',
@ -5289,6 +5314,7 @@ phutil_register_library_map(array(
'PhabricatorActionView' => 'AphrontView', 'PhabricatorActionView' => 'AphrontView',
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorAjaxRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorAlmanacApplication' => 'PhabricatorApplication', 'PhabricatorAlmanacApplication' => 'PhabricatorApplication',
'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorAnchorView' => 'AphrontView', 'PhabricatorAnchorView' => 'AphrontView',
@ -5433,6 +5459,7 @@ phutil_register_library_map(array(
'PhabricatorAuthLinkController' => 'PhabricatorAuthController', 'PhabricatorAuthLinkController' => 'PhabricatorAuthController',
'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
'PhabricatorAuthLoginHandler' => 'Phobject',
'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow',
@ -5653,6 +5680,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
), ),
'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitTestCase' => 'PhabricatorTestCase', 'PhabricatorConduitTestCase' => 'PhabricatorTestCase',
'PhabricatorConduitToken' => array( 'PhabricatorConduitToken' => array(
@ -5715,6 +5743,7 @@ phutil_register_library_map(array(
'PhabricatorConfigOptionType' => 'Phobject', 'PhabricatorConfigOptionType' => 'Phobject',
'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule', 'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule',
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule',
'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse', 'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse',
'PhabricatorConfigSchemaQuery' => 'Phobject', 'PhabricatorConfigSchemaQuery' => 'Phobject',
'PhabricatorConfigSchemaSpec' => 'Phobject', 'PhabricatorConfigSchemaSpec' => 'Phobject',
@ -5899,6 +5928,7 @@ phutil_register_library_map(array(
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDebugController' => 'PhabricatorController', 'PhabricatorDebugController' => 'PhabricatorController',
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDestructionEngine' => 'Phobject', 'PhabricatorDestructionEngine' => 'Phobject',
'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
@ -6124,6 +6154,7 @@ phutil_register_library_map(array(
'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController', 'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
'PhabricatorHeraldApplication' => 'PhabricatorApplication', 'PhabricatorHeraldApplication' => 'PhabricatorApplication',
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorHomeApplication' => 'PhabricatorApplication', 'PhabricatorHomeApplication' => 'PhabricatorApplication',
'PhabricatorHomeController' => 'PhabricatorController', 'PhabricatorHomeController' => 'PhabricatorController',
'PhabricatorHomeMainController' => 'PhabricatorHomeController', 'PhabricatorHomeMainController' => 'PhabricatorHomeController',
@ -6347,7 +6378,7 @@ phutil_register_library_map(array(
'PhabricatorOAuthServer' => 'Phobject', 'PhabricatorOAuthServer' => 'Phobject',
'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO',
'PhabricatorOAuthServerApplication' => 'PhabricatorApplication', 'PhabricatorOAuthServerApplication' => 'PhabricatorApplication',
'PhabricatorOAuthServerAuthController' => 'PhabricatorAuthController', 'PhabricatorOAuthServerAuthController' => 'PhabricatorOAuthServerController',
'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO',
'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorOAuthServerClient' => array( 'PhabricatorOAuthServerClient' => array(
@ -6365,7 +6396,7 @@ phutil_register_library_map(array(
'PhabricatorOAuthServerScope' => 'Phobject', 'PhabricatorOAuthServerScope' => 'Phobject',
'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase', 'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase',
'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController',
'PhabricatorOAuthServerTokenController' => 'PhabricatorAuthController', 'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController',
'PhabricatorObjectHandle' => array( 'PhabricatorObjectHandle' => array(
'Phobject', 'Phobject',
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
@ -6406,6 +6437,8 @@ phutil_register_library_map(array(
'PhabricatorApplicationTransactionInterface', 'PhabricatorApplicationTransactionInterface',
), ),
'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType', 'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType',
'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
@ -6571,6 +6604,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType', 'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorPolicyRule' => 'Phobject', 'PhabricatorPolicyRule' => 'Phobject',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase', 'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => array( 'PhabricatorPolicyTestObject' => array(
@ -6649,6 +6683,7 @@ phutil_register_library_map(array(
'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType',
@ -6671,6 +6706,7 @@ phutil_register_library_map(array(
'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectViewController' => 'PhabricatorProjectController', 'PhabricatorProjectViewController' => 'PhabricatorProjectController',
'PhabricatorProjectWatchController' => 'PhabricatorProjectController', 'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule',
@ -6684,6 +6720,7 @@ phutil_register_library_map(array(
'Phobject', 'Phobject',
'Iterator', 'Iterator',
), ),
'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType', 'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType',
'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRedirectController' => 'PhabricatorController',
@ -6810,6 +6847,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryVersion' => 'Phobject', 'PhabricatorRepositoryVersion' => 'Phobject',
'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler',
'PhabricatorResourceSite' => 'PhabricatorSite', 'PhabricatorResourceSite' => 'PhabricatorSite',
'PhabricatorRobotsController' => 'PhabricatorController', 'PhabricatorRobotsController' => 'PhabricatorController',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
@ -7238,7 +7276,6 @@ phutil_register_library_map(array(
'PhameBlogListController' => 'PhameController', 'PhameBlogListController' => 'PhameController',
'PhameBlogLiveController' => 'PhameController', 'PhameBlogLiveController' => 'PhameController',
'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhameBlogResourceSite' => 'PhameSite',
'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhameBlogSite' => 'PhameSite', 'PhameBlogSite' => 'PhameSite',
'PhameBlogSkin' => 'PhabricatorController', 'PhameBlogSkin' => 'PhabricatorController',

View file

@ -24,10 +24,6 @@ abstract class AphrontController extends Phobject {
return; return;
} }
public function didProcessRequest($response) {
return $response;
}
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
if (method_exists($this, 'processRequest')) { if (method_exists($this, 'processRequest')) {
return $this->processRequest(); return $this->processRequest();

View file

@ -26,6 +26,8 @@ final class AphrontRequest extends Phobject {
private $requestData; private $requestData;
private $user; private $user;
private $applicationConfiguration; private $applicationConfiguration;
private $site;
private $controller;
private $uriData; private $uriData;
private $cookiePrefix; private $cookiePrefix;
@ -77,6 +79,24 @@ final class AphrontRequest extends Phobject {
return $uri->getDomain(); return $uri->getDomain();
} }
public function setSite(AphrontSite $site) {
$this->site = $site;
return $this;
}
public function getSite() {
return $this->site;
}
public function setController(AphrontController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
/* -( Accessing Request Data )--------------------------------------------- */ /* -( Accessing Request Data )--------------------------------------------- */

View file

@ -1,50 +0,0 @@
<?php
final class AphrontURIMapper extends Phobject {
private $map;
public function __construct(array $map) {
$this->map = $map;
}
public function mapPath($path) {
$map = $this->map;
foreach ($map as $rule => $value) {
list($controller, $data) = $this->tryRule($rule, $value, $path);
if ($controller) {
foreach ($data as $k => $v) {
if (is_numeric($k)) {
unset($data[$k]);
}
}
return array($controller, $data);
}
}
return array(null, null);
}
private function tryRule($rule, $value, $path) {
$match = null;
$pattern = '#^'.$rule.(is_array($value) ? '' : '$').'#';
if (!preg_match($pattern, $path, $match)) {
return array(null, null);
}
if (!is_array($value)) {
return array($value, $match);
}
$path = substr($path, strlen($match[0]));
foreach ($value as $srule => $sval) {
list($controller, $data) = $this->tryRule($srule, $sval, $path);
if ($controller) {
return array($controller, $data + $match);
}
}
return array(null, null);
}
}

View file

@ -1,7 +1,9 @@
<?php <?php
/** /**
* @task routing URI Routing * @task routing URI Routing
* @task response Response Handling
* @task exception Exception Handling
*/ */
abstract class AphrontApplicationConfiguration extends Phobject { abstract class AphrontApplicationConfiguration extends Phobject {
@ -10,7 +12,6 @@ abstract class AphrontApplicationConfiguration extends Phobject {
private $path; private $path;
private $console; private $console;
abstract public function getApplicationName();
abstract public function buildRequest(); abstract public function buildRequest();
abstract public function build404Controller(); abstract public function build404Controller();
abstract public function buildRedirectController($uri, $external); abstract public function buildRedirectController($uri, $external);
@ -210,7 +211,9 @@ abstract class AphrontApplicationConfiguration extends Phobject {
)); ));
$multimeter->setEventContext('web.'.$controller_class); $multimeter->setEventContext('web.'.$controller_class);
$request->setController($controller);
$request->setURIMap($uri_data); $request->setURIMap($uri_data);
$controller->setRequest($request); $controller->setRequest($request);
// If execution throws an exception and then trying to render that // If execution throws an exception and then trying to render that
@ -232,6 +235,7 @@ abstract class AphrontApplicationConfiguration extends Phobject {
if (!$response) { if (!$response) {
$controller->willProcessRequest($uri_data); $controller->willProcessRequest($uri_data);
$response = $controller->handleRequest($request); $response = $controller->handleRequest($request);
$this->validateControllerResponse($controller, $response);
} }
} catch (Exception $ex) { } catch (Exception $ex) {
$original_exception = $ex; $original_exception = $ex;
@ -239,8 +243,8 @@ abstract class AphrontApplicationConfiguration extends Phobject {
} }
try { try {
$response = $controller->didProcessRequest($response); $response = $this->produceResponse($request, $response);
$response = $this->willSendResponse($response, $controller); $response = $controller->willSendResponse($response);
$response->setRequest($request); $response->setRequest($request);
$unexpected_output = PhabricatorStartup::endOutputCapture(); $unexpected_output = PhabricatorStartup::endOutputCapture();
@ -283,35 +287,13 @@ abstract class AphrontApplicationConfiguration extends Phobject {
/** /**
* Using builtin and application routes, build the appropriate * Build a controller to respond to the request.
* @{class:AphrontController} class for the request. To route a request, we
* first test if the HTTP_HOST is configured as a valid Phabricator URI. If
* it isn't, we do a special check to see if it's a custom domain for a blog
* in the Phame application and if that fails we error. Otherwise, we test
* against all application routes from installed
* @{class:PhabricatorApplication}s.
*
* If we match a route, we construct the controller it points at, build it,
* and return it.
*
* If we fail to match a route, but the current path is missing a trailing
* "/", we try routing the same path with a trailing "/" and do a redirect
* if that has a valid route. The idea is to canoncalize URIs for consistency,
* but avoid breaking noncanonical URIs that we can easily salvage.
*
* NOTE: We only redirect on GET. On POST, we'd drop parameters and most
* likely mutate the request implicitly, and a bad POST usually indicates a
* programming error rather than a sloppy typist.
*
* If the failing path already has a trailing "/", or we can't route the
* version with a "/", we call @{method:build404Controller}, which build a
* fallback @{class:AphrontController}.
* *
* @return pair<AphrontController,dict> Controller and dictionary of request * @return pair<AphrontController,dict> Controller and dictionary of request
* parameters. * parameters.
* @task routing * @task routing
*/ */
final public function buildController() { final private function buildController() {
$request = $this->getRequest(); $request = $this->getRequest();
// If we're configured to operate in cluster mode, reject requests which // If we're configured to operate in cluster mode, reject requests which
@ -336,7 +318,7 @@ abstract class AphrontApplicationConfiguration extends Phobject {
// This is a command line script (probably something like a unit // This is a command line script (probably something like a unit
// test) so it's fine that we don't have SERVER_ADDR defined. // test) so it's fine that we don't have SERVER_ADDR defined.
} else { } else {
throw new AphrontUsageException( throw new AphrontMalformedRequestException(
pht('No %s', 'SERVER_ADDR'), pht('No %s', 'SERVER_ADDR'),
pht( pht(
'Phabricator is configured to operate in cluster mode, but '. 'Phabricator is configured to operate in cluster mode, but '.
@ -348,7 +330,7 @@ abstract class AphrontApplicationConfiguration extends Phobject {
} }
} else { } else {
if (!PhabricatorEnv::isClusterAddress($server_addr)) { if (!PhabricatorEnv::isClusterAddress($server_addr)) {
throw new AphrontUsageException( throw new AphrontMalformedRequestException(
pht('External Interface'), pht('External Interface'),
pht( pht(
'Phabricator is configured in cluster mode and the address '. 'Phabricator is configured in cluster mode and the address '.
@ -373,78 +355,48 @@ abstract class AphrontApplicationConfiguration extends Phobject {
} }
} }
// TODO: Really, the Site should get more control here and be able to $maps = $site->getRoutingMaps();
// do its own routing logic if it wants, but we don't need that for now. $path = $request->getPath();
$path = $site->getPathForRouting($request);
list($controller, $uri_data) = $this->buildControllerForPath($path); $result = $this->routePath($maps, $path);
if (!$controller) { if ($result) {
if (!preg_match('@/$@', $path)) { return $result;
// If we failed to match anything but don't have a trailing slash, try
// to add a trailing slash and issue a redirect if that resolves.
list($controller, $uri_data) = $this->buildControllerForPath($path.'/');
// NOTE: For POST, just 404 instead of redirecting, since the redirect
// will be a GET without parameters.
if ($controller && !$request->isHTTPPost()) {
$slash_uri = $request->getRequestURI()->setPath($path.'/');
$external = strlen($request->getRequestURI()->getDomain());
return $this->buildRedirectController($slash_uri, $external);
}
}
return $this->build404Controller();
} }
return array($controller, $uri_data); // If we failed to match anything but don't have a trailing slash, try
} // to add a trailing slash and issue a redirect if that resolves.
// NOTE: We only do this for GET, since redirects switch to GET and drop
// data like POST parameters.
if (!preg_match('@/$@', $path) && $request->isHTTPGet()) {
$result = $this->routePath($maps, $path.'/');
if ($result) {
$slash_uri = $request->getRequestURI()->setPath($path.'/');
$external = strlen($request->getRequestURI()->getDomain());
return $this->buildRedirectController($slash_uri, $external);
}
}
return $this->build404Controller();
}
/** /**
* Map a specific path to the corresponding controller. For a description * Map a specific path to the corresponding controller. For a description
* of routing, see @{method:buildController}. * of routing, see @{method:buildController}.
* *
* @param list<AphrontRoutingMap> List of routing maps.
* @param string Path to route.
* @return pair<AphrontController,dict> Controller and dictionary of request * @return pair<AphrontController,dict> Controller and dictionary of request
* parameters. * parameters.
* @task routing * @task routing
*/ */
final public function buildControllerForPath($path) { private function routePath(array $maps, $path) {
$maps = array(); foreach ($maps as $map) {
$result = $map->routePath($path);
$applications = PhabricatorApplication::getAllInstalledApplications(); if ($result) {
foreach ($applications as $application) { return array($result->getController(), $result->getURIData());
$maps[] = array($application, $application->getRoutes());
}
$current_application = null;
$controller_class = null;
foreach ($maps as $map_info) {
list($application, $map) = $map_info;
$mapper = new AphrontURIMapper($map);
list($controller_class, $uri_data) = $mapper->mapPath($path);
if ($controller_class) {
if ($application) {
$current_application = $application;
}
break;
} }
} }
if (!$controller_class) {
return array(null, null);
}
$request = $this->getRequest();
$controller = newv($controller_class, array());
if ($current_application) {
$controller->setCurrentApplication($current_application);
}
return array($controller, $uri_data);
} }
private function buildSiteForRequest(AphrontRequest $request) { private function buildSiteForRequest(AphrontRequest $request) {
@ -461,15 +413,223 @@ abstract class AphrontApplicationConfiguration extends Phobject {
if (!$site) { if (!$site) {
$path = $request->getPath(); $path = $request->getPath();
$host = $request->getHost(); $host = $request->getHost();
throw new Exception( throw new AphrontMalformedRequestException(
pht('Site Not Found'),
pht( pht(
'This request asked for "%s" on host "%s", but no site is '. 'This request asked for "%s" on host "%s", but no site is '.
'configured which can serve this request.', 'configured which can serve this request.',
$path, $path,
$host)); $host),
true);
} }
$request->setSite($site);
return $site; return $site;
} }
/* -( Response Handling )-------------------------------------------------- */
/**
* Tests if a response is of a valid type.
*
* @param wild Supposedly valid response.
* @return bool True if the object is of a valid type.
* @task response
*/
private function isValidResponseObject($response) {
if ($response instanceof AphrontResponse) {
return true;
}
if ($response instanceof AphrontResponseProducerInterface) {
return true;
}
return false;
}
/**
* Verifies that the return value from an @{class:AphrontController} is
* of an allowed type.
*
* @param AphrontController Controller which returned the response.
* @param wild Supposedly valid response.
* @return void
* @task response
*/
private function validateControllerResponse(
AphrontController $controller,
$response) {
if ($this->isValidResponseObject($response)) {
return;
}
throw new Exception(
pht(
'Controller "%s" returned an invalid response from call to "%s". '.
'This method must return an object of class "%s", or an object '.
'which implements the "%s" interface.',
get_class($controller),
'handleRequest()',
'AphrontResponse',
'AphrontResponseProducerInterface'));
}
/**
* Verifies that the return value from an
* @{class:AphrontResponseProducerInterface} is of an allowed type.
*
* @param AphrontResponseProducerInterface Object which produced
* this response.
* @param wild Supposedly valid response.
* @return void
* @task response
*/
private function validateProducerResponse(
AphrontResponseProducerInterface $producer,
$response) {
if ($this->isValidResponseObject($response)) {
return;
}
throw new Exception(
pht(
'Producer "%s" returned an invalid response from call to "%s". '.
'This method must return an object of class "%s", or an object '.
'which implements the "%s" interface.',
get_class($producer),
'produceAphrontResponse()',
'AphrontResponse',
'AphrontResponseProducerInterface'));
}
/**
* Verifies that the return value from an
* @{class:AphrontRequestExceptionHandler} is of an allowed type.
*
* @param AphrontRequestExceptionHandler Object which produced this
* response.
* @param wild Supposedly valid response.
* @return void
* @task response
*/
private function validateErrorHandlerResponse(
AphrontRequestExceptionHandler $handler,
$response) {
if ($this->isValidResponseObject($response)) {
return;
}
throw new Exception(
pht(
'Exception handler "%s" returned an invalid response from call to '.
'"%s". This method must return an object of class "%s", or an object '.
'which implements the "%s" interface.',
get_class($handler),
'handleRequestException()',
'AphrontResponse',
'AphrontResponseProducerInterface'));
}
/**
* Resolves a response object into an @{class:AphrontResponse}.
*
* Controllers are permitted to return actual responses of class
* @{class:AphrontResponse}, or other objects which implement
* @{interface:AphrontResponseProducerInterface} and can produce a response.
*
* If a controller returns a response producer, invoke it now and produce
* the real response.
*
* @param AphrontRequest Request being handled.
* @param AphrontResponse|AphrontResponseProducerInterface Response, or
* response producer.
* @return AphrontResponse Response after any required production.
* @task response
*/
private function produceResponse(AphrontRequest $request, $response) {
$original = $response;
// Detect cycles on the exact same objects. It's still possible to produce
// infinite responses as long as they're all unique, but we can only
// reasonably detect cycles, not guarantee that response production halts.
$seen = array();
while (true) {
// NOTE: It is permissible for an object to be both a response and a
// response producer. If so, being a producer is "stronger". This is
// used by AphrontProxyResponse.
// If this response is a valid response, hand over the request first.
if ($response instanceof AphrontResponse) {
$response->setRequest($request);
}
// If this isn't a producer, we're all done.
if (!($response instanceof AphrontResponseProducerInterface)) {
break;
}
$hash = spl_object_hash($response);
if (isset($seen[$hash])) {
throw new Exception(
pht(
'Failure while producing response for object of class "%s": '.
'encountered production cycle (identical object, of class "%s", '.
'was produced twice).',
get_class($original),
get_class($response)));
}
$seen[$hash] = true;
$new_response = $response->produceAphrontResponse();
$this->validateProducerResponse($response, $new_response);
$response = $new_response;
}
return $response;
}
/* -( Error Handling )----------------------------------------------------- */
/**
* Convert an exception which has escaped the controller into a response.
*
* This method delegates exception handling to available subclasses of
* @{class:AphrontRequestExceptionHandler}.
*
* @param Exception Exception which needs to be handled.
* @return wild Response or response producer, or null if no available
* handler can produce a response.
* @task exception
*/
private function handleException(Exception $ex) {
$handlers = AphrontRequestExceptionHandler::getAllHandlers();
$request = $this->getRequest();
foreach ($handlers as $handler) {
if ($handler->canHandleRequestException($request, $ex)) {
$response = $handler->handleRequestException($request, $ex);
$this->validateErrorHandlerResponse($handler, $response);
return $response;
}
}
throw $ex;
}
} }

View file

@ -8,12 +8,6 @@
class AphrontDefaultApplicationConfiguration class AphrontDefaultApplicationConfiguration
extends AphrontApplicationConfiguration { extends AphrontApplicationConfiguration {
public function __construct() {}
public function getApplicationName() {
return 'aphront-default';
}
/** /**
* @phutil-external-symbol class PhabricatorStartup * @phutil-external-symbol class PhabricatorStartup
*/ */
@ -50,233 +44,6 @@ class AphrontDefaultApplicationConfiguration
return $request; return $request;
} }
public function handleException(Exception $ex) {
$request = $this->getRequest();
// For Conduit requests, return a Conduit response.
if ($request->isConduit()) {
$response = new ConduitAPIResponse();
$response->setErrorCode(get_class($ex));
$response->setErrorInfo($ex->getMessage());
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
}
// For non-workflow requests, return a Ajax response.
if ($request->isAjax() && !$request->isWorkflow()) {
// Log these; they don't get shown on the client and can be difficult
// to debug.
phlog($ex);
$response = new AphrontAjaxResponse();
$response->setError(
array(
'code' => get_class($ex),
'info' => $ex->getMessage(),
));
return $response;
}
$user = $request->getUser();
if (!$user) {
// If we hit an exception very early, we won't have a user.
$user = new PhabricatorUser();
}
if ($ex instanceof PhabricatorSystemActionRateLimitException) {
$dialog = id(new AphrontDialogView())
->setTitle(pht('Slow Down!'))
->setUser($user)
->setErrors(array(pht('You are being rate limited.')))
->appendParagraph($ex->getMessage())
->appendParagraph($ex->getRateExplanation())
->addCancelButton('/', pht('Okaaaaaaaaaaaaaay...'));
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
if ($ex instanceof PhabricatorAuthHighSecurityRequiredException) {
$form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm(
$ex->getFactors(),
$ex->getFactorValidationResults(),
$user,
$request);
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle(pht('Entering High Security'))
->setShortTitle(pht('Security Checkpoint'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->addHiddenInput(AphrontRequest::TYPE_HISEC, true)
->setErrors(
array(
pht(
'You are taking an action which requires you to enter '.
'high security.'),
))
->appendParagraph(
pht(
'High security mode helps protect your account from security '.
'threats, like session theft or someone messing with your stuff '.
'while you\'re grabbing a coffee. To enter high security mode, '.
'confirm your credentials.'))
->appendChild($form->buildLayoutView())
->appendParagraph(
pht(
'Your account will remain in high security mode for a short '.
'period of time. When you are finished taking sensitive '.
'actions, you should leave high security.'))
->setSubmitURI($request->getPath())
->addCancelButton($ex->getCancelURI())
->addSubmitButton(pht('Enter High Security'));
$request_parameters = $request->getPassthroughRequestParameters(
$respect_quicksand = true);
foreach ($request_parameters as $key => $value) {
$dialog->addHiddenInput($key, $value);
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
if ($ex instanceof PhabricatorPolicyException) {
if (!$user->isLoggedIn()) {
// If the user isn't logged in, just give them a login form. This is
// probably a generally more useful response than a policy dialog that
// they have to click through to get a login form.
//
// Possibly we should add a header here like "you need to login to see
// the thing you are trying to look at".
$login_controller = new PhabricatorAuthStartController();
$login_controller->setRequest($request);
$auth_app_class = 'PhabricatorAuthApplication';
$auth_app = PhabricatorApplication::getByClass($auth_app_class);
$login_controller->setCurrentApplication($auth_app);
return $login_controller->handleRequest($request);
}
$content = array(
phutil_tag(
'div',
array(
'class' => 'aphront-policy-rejection',
),
$ex->getRejection()),
);
$list = null;
if ($ex->getCapabilityName()) {
$list = $ex->getMoreInfo();
foreach ($list as $key => $item) {
$list[$key] = $item;
}
$content[] = phutil_tag(
'div',
array(
'class' => 'aphront-capability-details',
),
pht('Users with the "%s" capability:', $ex->getCapabilityName()));
}
$dialog = id(new AphrontDialogView())
->setTitle($ex->getTitle())
->setClass('aphront-access-dialog')
->setUser($user)
->appendChild($content);
if ($list) {
$dialog->appendList($list);
}
if ($this->getRequest()->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
} else {
$dialog->addCancelButton('/', pht('OK'));
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
if ($ex instanceof AphrontUsageException) {
$error = new PHUIInfoView();
$error->setTitle($ex->getTitle());
$error->appendChild($ex->getMessage());
$view = new PhabricatorStandardPageView();
$view->setRequest($this->getRequest());
$view->appendChild($error);
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
$response->setHTTPResponseCode(500);
return $response;
}
// Always log the unhandled exception.
phlog($ex);
$class = get_class($ex);
$message = $ex->getMessage();
if ($ex instanceof AphrontSchemaQueryException) {
$message .= "\n\n".pht(
"NOTE: This usually indicates that the MySQL schema has not been ".
"properly upgraded. Run '%s' to ensure your schema is up to date.",
'bin/storage upgrade');
}
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$trace = id(new AphrontStackTraceView())
->setUser($user)
->setTrace($ex->getTrace());
} else {
$trace = null;
}
$content = phutil_tag(
'div',
array('class' => 'aphront-unhandled-exception'),
array(
phutil_tag('div', array('class' => 'exception-message'), $message),
$trace,
));
$dialog = new AphrontDialogView();
$dialog
->setTitle(pht('Unhandled Exception ("%s")', $class))
->setClass('aphront-exception-dialog')
->setUser($user)
->appendChild($content);
if ($this->getRequest()->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
$response->setHTTPResponseCode(500);
return $response;
}
public function willSendResponse(AphrontResponse $response) {
return $response;
}
public function build404Controller() { public function build404Controller() {
return array(new Phabricator404Controller(), array()); return array(new Phabricator404Controller(), array());
} }

View file

@ -0,0 +1,34 @@
<?php
/**
* These exceptions are raised when a client submits a malformed request.
*
* These errors are caught by Aphront itself and occur too early or too
* fundamentally in request handling to allow the request to route to a
* controller or survive to normal processing.
*
* These exceptions can be made "unlogged", which will prevent them from being
* logged. The intent is that errors which are purely the result of client
* failure and of no interest to the server can be raised silently to avoid
* cluttering the logs with client errors that are not actionable.
*/
final class AphrontMalformedRequestException extends AphrontException {
private $title;
private $isUnlogged;
public function __construct($title, $message, $unlogged = false) {
$this->title = $title;
$this->isUnlogged = $unlogged;
parent::__construct($message);
}
public function getTitle() {
return $this->title;
}
public function getIsUnlogged() {
return $this->isUnlogged;
}
}

View file

@ -1,21 +0,0 @@
<?php
/**
* These exceptions represent user error, and are not logged.
*
* @concrete-extensible
*/
class AphrontUsageException extends AphrontException {
private $title;
public function __construct($title, $message) {
$this->title = $title;
parent::__construct($message);
}
public function getTitle() {
return $this->title;
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* React to an unhandled exception escaping request handling in a controller
* and convert it into a response.
*
* These handlers are generally used to render error pages, but they may
* also perform more specialized handling in situations where an error page
* is not appropriate.
*/
abstract class AphrontRequestExceptionHandler extends Phobject {
abstract public function getRequestExceptionHandlerPriority();
public function shouldLogException(
AphrontRequest $request,
Exception $ex) {
return null;
}
abstract public function canHandleRequestException(
AphrontRequest $request,
Exception $ex);
abstract public function handleRequestException(
AphrontRequest $request,
Exception $ex);
final public static function getAllHandlers() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setSortMethod('getRequestExceptionHandlerPriority')
->execute();
}
}

View file

@ -0,0 +1,38 @@
<?php
final class PhabricatorAjaxRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 110000;
}
public function getRequestExceptionHandlerDescription() {
return pht('Responds to requests made by AJAX clients.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
// For non-workflow requests, return a Ajax response.
return ($request->isAjax() && !$request->isWorkflow());
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
// Log these; they don't get shown on the client and can be difficult
// to debug.
phlog($ex);
$response = new AphrontAjaxResponse();
$response->setError(
array(
'code' => get_class($ex),
'info' => $ex->getMessage(),
));
return $response;
}
}

View file

@ -0,0 +1,33 @@
<?php
final class PhabricatorConduitRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 100000;
}
public function getRequestExceptionHandlerDescription() {
return pht('Responds to requests made by Conduit clients.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
return $request->isConduit();
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$response = id(new ConduitAPIResponse())
->setErrorCode(get_class($ex))
->setErrorInfo($ex->getMessage());
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
}
}

View file

@ -0,0 +1,76 @@
<?php
final class PhabricatorDefaultRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 900000;
}
public function getRequestExceptionHandlerDescription() {
return pht('Handles all other exceptions.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return true;
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$viewer = $this->getViewer($request);
// Always log the unhandled exception.
phlog($ex);
$class = get_class($ex);
$message = $ex->getMessage();
if ($ex instanceof AphrontSchemaQueryException) {
$message .= "\n\n".pht(
"NOTE: This usually indicates that the MySQL schema has not been ".
"properly upgraded. Run '%s' to ensure your schema is up to date.",
'bin/storage upgrade');
}
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$trace = id(new AphrontStackTraceView())
->setUser($viewer)
->setTrace($ex->getTrace());
} else {
$trace = null;
}
$content = phutil_tag(
'div',
array('class' => 'aphront-unhandled-exception'),
array(
phutil_tag('div', array('class' => 'exception-message'), $message),
$trace,
));
$dialog = new AphrontDialogView();
$dialog
->setTitle(pht('Unhandled Exception ("%s")', $class))
->setClass('aphront-exception-dialog')
->setUser($viewer)
->appendChild($content);
if ($request->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
}
return id(new AphrontDialogResponse())
->setDialog($dialog)
->setHTTPResponseCode(500);
}
}

View file

@ -0,0 +1,76 @@
<?php
final class PhabricatorHighSecurityRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 310000;
}
public function getRequestExceptionHandlerDescription() {
return pht(
'Handles high security exceptions which occur when a user needs '.
'to present MFA credentials to take an action.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return ($ex instanceof PhabricatorAuthHighSecurityRequiredException);
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$viewer = $this->getViewer($request);
$form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm(
$ex->getFactors(),
$ex->getFactorValidationResults(),
$viewer,
$request);
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Entering High Security'))
->setShortTitle(pht('Security Checkpoint'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->addHiddenInput(AphrontRequest::TYPE_HISEC, true)
->setErrors(
array(
pht(
'You are taking an action which requires you to enter '.
'high security.'),
))
->appendParagraph(
pht(
'High security mode helps protect your account from security '.
'threats, like session theft or someone messing with your stuff '.
'while you\'re grabbing a coffee. To enter high security mode, '.
'confirm your credentials.'))
->appendChild($form->buildLayoutView())
->appendParagraph(
pht(
'Your account will remain in high security mode for a short '.
'period of time. When you are finished taking sensitive '.
'actions, you should leave high security.'))
->setSubmitURI($request->getPath())
->addCancelButton($ex->getCancelURI())
->addSubmitButton(pht('Enter High Security'));
$request_parameters = $request->getPassthroughRequestParameters(
$respect_quicksand = true);
foreach ($request_parameters as $key => $value) {
$dialog->addHiddenInput($key, $value);
}
return $dialog;
}
}

View file

@ -0,0 +1,93 @@
<?php
final class PhabricatorPolicyRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 320000;
}
public function getRequestExceptionHandlerDescription() {
return pht(
'Handles policy exceptions which occur when a user tries to '.
'do something they do not have permission to do.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return ($ex instanceof PhabricatorPolicyException);
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$viewer = $this->getViewer($request);
if (!$viewer->isLoggedIn()) {
// If the user isn't logged in, just give them a login form. This is
// probably a generally more useful response than a policy dialog that
// they have to click through to get a login form.
//
// Possibly we should add a header here like "you need to login to see
// the thing you are trying to look at".
$auth_app_class = 'PhabricatorAuthApplication';
$auth_app = PhabricatorApplication::getByClass($auth_app_class);
return id(new PhabricatorAuthStartController())
->setRequest($request)
->setCurrentApplication($auth_app)
->handleRequest($request);
}
$content = array(
phutil_tag(
'div',
array(
'class' => 'aphront-policy-rejection',
),
$ex->getRejection()),
);
$list = null;
if ($ex->getCapabilityName()) {
$list = $ex->getMoreInfo();
foreach ($list as $key => $item) {
$list[$key] = $item;
}
$content[] = phutil_tag(
'div',
array(
'class' => 'aphront-capability-details',
),
pht('Users with the "%s" capability:', $ex->getCapabilityName()));
}
$dialog = id(new AphrontDialogView())
->setTitle($ex->getTitle())
->setClass('aphront-access-dialog')
->setUser($viewer)
->appendChild($content);
if ($list) {
$dialog->appendList($list);
}
if ($request->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
} else {
$dialog->addCancelButton('/', pht('OK'));
}
return $dialog;
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorRateLimitRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 300000;
}
public function getRequestExceptionHandlerDescription() {
return pht(
'Handles action rate limiting exceptions which occur when a user '.
'does something too frequently.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return ($ex instanceof PhabricatorSystemActionRateLimitException);
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$viewer = $this->getViewer($request);
return id(new AphrontDialogView())
->setTitle(pht('Slow Down!'))
->setUser($viewer)
->setErrors(array(pht('You are being rate limited.')))
->appendParagraph($ex->getMessage())
->appendParagraph($ex->getRateExplanation())
->addCancelButton('/', pht('Okaaaaaaaaaaaaaay...'));
}
}

View file

@ -0,0 +1,26 @@
<?php
abstract class PhabricatorRequestExceptionHandler
extends AphrontRequestExceptionHandler {
protected function isPhabricatorSite(AphrontRequest $request) {
$site = $request->getSite();
if (!$site) {
return false;
}
return ($site instanceof PhabricatorSite);
}
protected function getViewer(AphrontRequest $request) {
$viewer = $request->getUser();
if ($viewer) {
return $viewer;
}
// If we hit an exception very early, we won't have a user yet.
return new PhabricatorUser();
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* An object can implement this interface to allow it to be returned directly
* from an @{class:AphrontController}.
*
* Normally, controllers must return an @{class:AphrontResponse}. Sometimes,
* this is not convenient or requires an awkward API. If it's preferable to
* return some other type of object which is equivalent to or describes a
* valid response, that object can implement this interface and produce a
* response later.
*/
interface AphrontResponseProducerInterface {
/**
* Produce the equivalent @{class:AphrontResponse} for this object.
*
* @return AphrontResponse Equivalent response.
*/
public function produceAphrontResponse();
}

View file

@ -8,7 +8,9 @@
* then constructing a real @{class:AphrontAjaxResponse} in * then constructing a real @{class:AphrontAjaxResponse} in
* @{method:reduceProxyResponse}. * @{method:reduceProxyResponse}.
*/ */
abstract class AphrontProxyResponse extends AphrontResponse { abstract class AphrontProxyResponse
extends AphrontResponse
implements AphrontResponseProducerInterface {
private $proxy; private $proxy;
@ -71,4 +73,12 @@ abstract class AphrontProxyResponse extends AphrontResponse {
'reduceProxyResponse()')); 'reduceProxyResponse()'));
} }
/* -( AphrontResponseProducerInterface )----------------------------------- */
public function produceAphrontResponse() {
return $this->reduceProxyResponse();
}
} }

View file

@ -6,6 +6,20 @@ final class AphrontUnhandledExceptionResponse
private $exception; private $exception;
public function setException(Exception $exception) { public function setException(Exception $exception) {
// Log the exception unless it's specifically a silent malformed request
// exception.
$should_log = true;
if ($exception instanceof AphrontMalformedRequestException) {
if ($exception->getIsUnlogged()) {
$should_log = false;
}
}
if ($should_log) {
phlog($exception);
}
$this->exception = $exception; $this->exception = $exception;
return $this; return $this;
} }
@ -24,7 +38,7 @@ final class AphrontUnhandledExceptionResponse
protected function getResponseTitle() { protected function getResponseTitle() {
$ex = $this->exception; $ex = $this->exception;
if ($ex instanceof AphrontUsageException) { if ($ex instanceof AphrontMalformedRequestException) {
return $ex->getTitle(); return $ex->getTitle();
} else { } else {
return pht('Unhandled Exception'); return pht('Unhandled Exception');
@ -38,7 +52,7 @@ final class AphrontUnhandledExceptionResponse
protected function getResponseBody() { protected function getResponseBody() {
$ex = $this->exception; $ex = $this->exception;
if ($ex instanceof AphrontUsageException) { if ($ex instanceof AphrontMalformedRequestException) {
$title = $ex->getTitle(); $title = $ex->getTitle();
} else { } else {
$title = get_class($ex); $title = get_class($ex);

View file

@ -0,0 +1,159 @@
<?php
/**
* Collection of routes on a site for an application.
*
* @task info Map Information
* @task routing Routing
*/
final class AphrontRoutingMap extends Phobject {
private $site;
private $application;
private $routes = array();
/* -( Map Info )----------------------------------------------------------- */
public function setSite(AphrontSite $site) {
$this->site = $site;
return $this;
}
public function getSite() {
return $this->site;
}
public function setApplication(PhabricatorApplication $application) {
$this->application = $application;
return $this;
}
public function getApplication() {
return $this->application;
}
public function setRoutes(array $routes) {
$this->routes = $routes;
return $this;
}
public function getRoutes() {
return $this->routes;
}
/* -( Routing )------------------------------------------------------------ */
/**
* Find the route matching a path, if one exists.
*
* @param string Path to route.
* @return AphrontRoutingResult|null Routing result, if path matches map.
* @task routing
*/
public function routePath($path) {
$map = $this->getRoutes();
foreach ($map as $route => $value) {
$match = $this->tryRoute($route, $value, $path);
if (!$match) {
continue;
}
$result = $this->newRoutingResult();
$application = $result->getApplication();
$controller_class = $match['class'];
$controller = newv($controller_class, array());
$controller->setCurrentApplication($application);
$result
->setController($controller)
->setURIData($match['data']);
return $result;
}
return null;
}
/**
* Test a sub-map to see if any routes match a path.
*
* @param string Path to route.
* @param string Pattern from the map.
* @param string Value from the map.
* @return dict<string, wild>|null Match details, if path matches sub-map.
* @task routing
*/
private function tryRoute($route, $value, $path) {
$has_submap = is_array($value);
if (!$has_submap) {
// If the value is a controller rather than a sub-map, any matching
// route must completely consume the path.
$pattern = '(^'.$route.'\z)';
} else {
$pattern = '(^'.$route.')';
}
$data = null;
$ok = preg_match($pattern, $path, $data);
if ($ok === false) {
throw new Exception(
pht(
'Routing fragment "%s" is not a valid regular expression.',
$route));
}
if (!$ok) {
return null;
}
$path_match = $data[0];
// Clean up the data. We only want to retain named capturing groups, not
// the duplicated numeric captures.
foreach ($data as $k => $v) {
if (is_numeric($k)) {
unset($data[$k]);
}
}
if (!$has_submap) {
return array(
'class' => $value,
'data' => $data,
);
}
$sub_path = substr($path, strlen($path_match));
foreach ($value as $sub_route => $sub_value) {
$result = $this->tryRoute($sub_route, $sub_value, $sub_path);
if ($result) {
$result['data'] += $data;
return $result;
}
}
return null;
}
/**
* Build a new routing result for this map.
*
* @return AphrontRoutingResult New, empty routing result.
* @task routing
*/
private function newRoutingResult() {
return id(new AphrontRoutingResult())
->setSite($this->getSite())
->setApplication($this->getApplication());
}
}

View file

@ -0,0 +1,55 @@
<?php
/**
* Details about a routing map match for a path.
*
* @param info Result Information
*/
final class AphrontRoutingResult extends Phobject {
private $site;
private $application;
private $controller;
private $uriData;
/* -( Result Information )------------------------------------------------- */
public function setSite(AphrontSite $site) {
$this->site = $site;
return $this;
}
public function getSite() {
return $this->site;
}
public function setApplication(PhabricatorApplication $application) {
$this->application = $application;
return $this;
}
public function getApplication() {
return $this->application;
}
public function setController(AphrontController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
public function setURIData(array $uri_data) {
$this->uriData = $uri_data;
return $this;
}
public function getURIData() {
return $this->uriData;
}
}

View file

@ -7,14 +7,7 @@ abstract class AphrontSite extends Phobject {
abstract public function shouldRequireHTTPS(); abstract public function shouldRequireHTTPS();
abstract public function newSiteForRequest(AphrontRequest $request); abstract public function newSiteForRequest(AphrontRequest $request);
abstract public function getRoutingMaps();
/**
* NOTE: This is temporary glue; eventually, sites will return an entire
* route map.
*/
public function getPathForRouting(AphrontRequest $request) {
return $request->getPath();
}
protected function isHostMatch($host, array $uris) { protected function isHostMatch($host, array $uris) {
foreach ($uris as $uri) { foreach ($uris as $uri) {
@ -32,14 +25,9 @@ abstract class AphrontSite extends Phobject {
return false; return false;
} }
protected function isPathPrefixMatch($path, array $paths) { protected function newRoutingMap() {
foreach ($paths as $candidate) { return id(new AphrontRoutingMap())
if (strncmp($path, $candidate, strlen($candidate)) === 0) { ->setSite($this);
return true;
}
}
return false;
} }
final public static function getAllSites() { final public static function getAllSites() {

View file

@ -37,4 +37,17 @@ final class PhabricatorPlatformSite extends PhabricatorSite {
return null; return null;
} }
public function getRoutingMaps() {
$applications = PhabricatorApplication::getAllInstalledApplications();
$maps = array();
foreach ($applications as $application) {
$maps[] = $this->newRoutingMap()
->setApplication($application)
->setRoutes($application->getRoutes());
}
return $maps;
}
} }

View file

@ -22,20 +22,20 @@ final class PhabricatorResourceSite extends PhabricatorSite {
return new PhabricatorResourceSite(); return new PhabricatorResourceSite();
} }
// These are CDN routes, so we let them through even if the "Host" header
// doesn't match anything we recognize. The
$whitelist = array(
'/res/',
'/file/data/',
'/file/xform/',
);
$path = $request->getPath();
if ($this->isPathPrefixMatch($path, $whitelist)) {
return new PhabricatorResourceSite();
}
return null; return null;
} }
public function getRoutingMaps() {
$applications = PhabricatorApplication::getAllInstalledApplications();
$maps = array();
foreach ($applications as $application) {
$maps[] = $this->newRoutingMap()
->setApplication($application)
->setRoutes($application->getResourceRoutes());
}
return $maps;
}
} }

View file

@ -70,9 +70,8 @@ final class PhabricatorAuditApplication extends PhabricatorApplication {
$query = id(new DiffusionCommitQuery()) $query = id(new DiffusionCommitQuery())
->setViewer($user) ->setViewer($user)
->withAuditorPHIDs($phids) ->withNeedsAuditByPHIDs($phids)
->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN)
->withAuditAwaitingUser($user)
->setLimit(self::MAX_STATUS_ITEMS); ->setLimit(self::MAX_STATUS_ITEMS);
$commits = $query->execute(); $commits = $query->execute();

View file

@ -11,103 +11,65 @@ final class PhabricatorCommitSearchEngine
return 'PhabricatorDiffusionApplication'; return 'PhabricatorDiffusionApplication';
} }
public function buildSavedQueryFromRequest(AphrontRequest $request) { public function newQuery() {
$saved = new PhabricatorSavedQuery(); return id(new DiffusionCommitQuery())
$saved->setParameter(
'auditorPHIDs',
$this->readPHIDsFromRequest($request, 'auditorPHIDs'));
$saved->setParameter(
'commitAuthorPHIDs',
$this->readUsersFromRequest($request, 'authors'));
$saved->setParameter(
'auditStatus',
$request->getStr('auditStatus'));
$saved->setParameter(
'repositoryPHIDs',
$this->readPHIDsFromRequest($request, 'repositoryPHIDs'));
// -- TODO - T4173 - file location
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new DiffusionCommitQuery())
->needAuditRequests(true) ->needAuditRequests(true)
->needCommitData(true); ->needCommitData(true);
}
$auditor_phids = $saved->getParameter('auditorPHIDs', array()); protected function buildQueryFromParameters(array $map) {
if ($auditor_phids) { $query = $this->newQuery();
$query->withAuditorPHIDs($auditor_phids);
if ($map['needsAuditByPHIDs']) {
$query->withNeedsAuditByPHIDs($map['needsAuditByPHIDs']);
} }
$commit_author_phids = $saved->getParameter('commitAuthorPHIDs', array()); if ($map['auditorPHIDs']) {
if ($commit_author_phids) { $query->withAuditorPHIDs($map['auditorPHIDs']);
$query->withAuthorPHIDs($commit_author_phids);
} }
$audit_status = $saved->getParameter('auditStatus', null); if ($map['commitAuthorPHIDs']) {
if ($audit_status) { $query->withAuthorPHIDs($map['commitAuthorPHIDs']);
$query->withAuditStatus($audit_status);
} }
$awaiting_user_phid = $saved->getParameter('awaitingUserPHID', null); if ($map['auditStatus']) {
if ($awaiting_user_phid) { $query->withAuditStatus($map['auditStatus']);
// This is used only for the built-in "needs attention" filter,
// so cheat and just use the already-loaded viewer rather than reloading
// it.
$query->withAuditAwaitingUser($this->requireViewer());
} }
$repository_phids = $saved->getParameter('repositoryPHIDs', array()); if ($map['repositoryPHIDs']) {
if ($repository_phids) { $query->withRepositoryPHIDs($map['repositoryPHIDs']);
$query->withRepositoryPHIDs($repository_phids);
} }
return $query; return $query;
} }
public function buildSearchForm( protected function buildCustomSearchFields() {
AphrontFormView $form, return array(
PhabricatorSavedQuery $saved) { id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Needs Audit By'))
$auditor_phids = $saved->getParameter('auditorPHIDs', array()); ->setKey('needsAuditByPHIDs')
$commit_author_phids = $saved->getParameter( ->setAliases(array('needs', 'need'))
'commitAuthorPHIDs', ->setDatasource(new DiffusionAuditorFunctionDatasource()),
array()); id(new PhabricatorSearchDatasourceField())
$audit_status = $saved->getParameter('auditStatus', null); ->setLabel(pht('Auditors'))
$repository_phids = $saved->getParameter('repositoryPHIDs', array()); ->setKey('auditorPHIDs')
->setAliases(array('auditor', 'auditors'))
$form ->setDatasource(new DiffusionAuditorFunctionDatasource()),
->appendControl( id(new PhabricatorUsersSearchField())
id(new AphrontFormTokenizerControl()) ->setLabel(pht('Authors'))
->setDatasource(new DiffusionAuditorDatasource()) ->setKey('commitAuthorPHIDs')
->setName('auditorPHIDs') ->setAliases(array('author', 'authors')),
->setLabel(pht('Auditors')) id(new PhabricatorSearchSelectField())
->setValue($auditor_phids)) ->setLabel(pht('Audit Status'))
->appendControl( ->setKey('auditStatus')
id(new AphrontFormTokenizerControl()) ->setAliases(array('status'))
->setDatasource(new PhabricatorPeopleDatasource()) ->setOptions($this->getAuditStatusOptions()),
->setName('authors') id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Commit Authors')) ->setLabel(pht('Repositories'))
->setValue($commit_author_phids)) ->setKey('repositoryPHIDs')
->appendChild( ->setAliases(array('repository', 'repositories'))
id(new AphrontFormSelectControl()) ->setDatasource(new DiffusionRepositoryDatasource()),
->setName('auditStatus') );
->setLabel(pht('Audit Status'))
->setOptions($this->getAuditStatusOptions())
->setValue($audit_status))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Repositories'))
->setName('repositoryPHIDs')
->setDatasource(new DiffusionRepositoryDatasource())
->setValue($repository_phids));
} }
protected function getURI($path) { protected function getURI($path) {
@ -118,7 +80,7 @@ final class PhabricatorCommitSearchEngine
$names = array(); $names = array();
if ($this->requireViewer()->isLoggedIn()) { if ($this->requireViewer()->isLoggedIn()) {
$names['need'] = pht('Need Attention'); $names['need'] = pht('Needs Audit');
$names['problem'] = pht('Problem Commits'); $names['problem'] = pht('Problem Commits');
} }
@ -138,22 +100,24 @@ final class PhabricatorCommitSearchEngine
$query->setQueryKey($query_key); $query->setQueryKey($query_key);
$viewer = $this->requireViewer(); $viewer = $this->requireViewer();
$viewer_phid = $viewer->getPHID();
$status_open = DiffusionCommitQuery::AUDIT_STATUS_OPEN;
switch ($query_key) { switch ($query_key) {
case 'all': case 'all':
return $query; return $query;
case 'open': case 'open':
$query->setParameter( $query->setParameter('auditStatus', $status_open);
'auditStatus',
DiffusionCommitQuery::AUDIT_STATUS_OPEN);
return $query; return $query;
case 'need': case 'need':
$query->setParameter('awaitingUserPHID', $viewer->getPHID()); $needs_tokens = array(
$query->setParameter( $viewer_phid,
'auditStatus', 'projects('.$viewer_phid.')',
DiffusionCommitQuery::AUDIT_STATUS_OPEN); 'packages('.$viewer_phid.')',
$query->setParameter( );
'auditorPHIDs',
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer)); $query->setParameter('needsAuditByPHIDs', $needs_tokens);
$query->setParameter('auditStatus', $status_open);
return $query; return $query;
case 'authored': case 'authored':
$query->setParameter('commitAuthorPHIDs', array($viewer->getPHID())); $query->setParameter('commitAuthorPHIDs', array($viewer->getPHID()));

View file

@ -209,7 +209,7 @@ abstract class PhabricatorAuthController extends PhabricatorController {
$actual = $account->getProperty('registrationKey'); $actual = $account->getProperty('registrationKey');
$expect = PhabricatorHash::digest($registration_key); $expect = PhabricatorHash::digest($registration_key);
if ($actual !== $expect) { if (!phutil_hashes_are_identical($actual, $expect)) {
$response = $this->renderError( $response = $this->renderError(
pht( pht(
'Your browser submitted a different registration key than the one '. 'Your browser submitted a different registration key than the one '.

View file

@ -163,8 +163,22 @@ final class PhabricatorAuthStartController
$button_columns); $button_columns);
} }
$login_message = PhabricatorEnv::getEnvConfig('auth.login-message'); $handlers = PhabricatorAuthLoginHandler::getAllHandlers();
$login_message = phutil_safe_html($login_message);
$delegating_controller = $this->getDelegatingController();
$header = array();
foreach ($handlers as $handler) {
$handler = clone $handler;
$handler->setRequest($request);
if ($delegating_controller) {
$handler->setDelegatingController($delegating_controller);
}
$header[] = $handler->getAuthLoginHeaderContent();
}
$invite_message = null; $invite_message = null;
if ($invite) { if ($invite) {
@ -178,7 +192,7 @@ final class PhabricatorAuthStartController
return $this->buildApplicationPage( return $this->buildApplicationPage(
array( array(
$crumbs, $crumbs,
$login_message, $header,
$invite_message, $invite_message,
$out, $out,
), ),

View file

@ -21,7 +21,10 @@ final class PhabricatorAuthTerminateSessionController
$sessions = $query->execute(); $sessions = $query->execute();
foreach ($sessions as $key => $session) { foreach ($sessions as $key => $session) {
if ($session->getSessionKey() == $current_key) { $is_current = phutil_hashes_are_identical(
$session->getSessionKey(),
$current_key);
if ($is_current) {
// Don't terminate the current login session. // Don't terminate the current login session.
unset($sessions[$key]); unset($sessions[$key]);
} }

View file

@ -296,7 +296,10 @@ final class PhabricatorAuthSessionEngine extends Phobject {
foreach ($sessions as $key => $session) { foreach ($sessions as $key => $session) {
if ($except_session !== null) { if ($except_session !== null) {
if ($except_session == $session->getSessionKey()) { $is_except = phutil_hashes_are_identical(
$session->getSessionKey(),
$except_session);
if ($is_except) {
continue; continue;
} }
} }

View file

@ -201,7 +201,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
// case the server or client has some clock skew. // case the server or client has some clock skew.
for ($offset = -2; $offset <= 2; $offset++) { for ($offset = -2; $offset <= 2; $offset++) {
$real = self::getTOTPCode($key, $now + $offset); $real = self::getTOTPCode($key, $now + $offset);
if ($real === $code) { if (phutil_hashes_are_identical($real, $code)) {
return true; return true;
} }
} }

View file

@ -0,0 +1,36 @@
<?php
abstract class PhabricatorAuthLoginHandler
extends Phobject {
private $request;
private $delegatingController;
public function getAuthLoginHeaderContent() {
return array();
}
final public function setDelegatingController(AphrontController $controller) {
$this->delegatingController = $controller;
return $this;
}
final public function getDelegatingController() {
return $this->delegatingController;
}
final public function setRequest(AphrontRequest $request) {
$this->request = $request;
return $this;
}
final public function getRequest() {
return $this->request;
}
final public static function getAllHandlers() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->execute();
}
}

View file

@ -482,7 +482,7 @@ abstract class PhabricatorAuthProvider extends Phobject {
'problem persists, you may need to clear your cookies.')); 'problem persists, you may need to clear your cookies.'));
} }
if ($actual !== $expect) { if (!phutil_hashes_are_identical($actual, $expect)) {
throw new Exception( throw new Exception(
pht( pht(
'The authentication provider did not return the correct client '. 'The authentication provider did not return the correct client '.

View file

@ -243,6 +243,10 @@ abstract class PhabricatorApplication
return array(); return array();
} }
public function getResourceRoutes() {
return array();
}
/* -( Email Integration )-------------------------------------------------- */ /* -( Email Integration )-------------------------------------------------- */

View file

@ -370,28 +370,8 @@ abstract class PhabricatorController extends AphrontController {
return $this->buildPageResponse($page); return $this->buildPageResponse($page);
} }
public function didProcessRequest($response) { public function willSendResponse(AphrontResponse $response) {
// If a bare DialogView is returned, wrap it in a DialogResponse.
if ($response instanceof AphrontDialogView) {
$response = id(new AphrontDialogResponse())->setDialog($response);
}
$request = $this->getRequest(); $request = $this->getRequest();
$response->setRequest($request);
$seen = array();
while ($response instanceof AphrontProxyResponse) {
$hash = spl_object_hash($response);
if (isset($seen[$hash])) {
$seen[] = get_class($response);
throw new Exception(
pht('Cycle while reducing proxy responses: %s',
implode(' -> ', $seen)));
}
$seen[$hash] = get_class($response);
$response = $response->reduceProxyResponse();
}
if ($response instanceof AphrontDialogResponse) { if ($response instanceof AphrontDialogResponse) {
if (!$request->isAjax() && !$request->isQuicksand()) { if (!$request->isAjax() && !$request->isQuicksand()) {

View file

@ -15,6 +15,17 @@ final class PhabricatorCelerityApplication extends PhabricatorApplication {
} }
public function getRoutes() { public function getRoutes() {
// We serve resources from both the platform site and the resource site.
// This is safe because the user doesn't have any direct control over
// resources.
// The advantage of serving resources from the resource site (if possible)
// is that we can use a CDN there if one is configured, but there is no
// particular security concern.
return $this->getResourceRoutes();
}
public function getResourceRoutes() {
$extensions = CelerityResourceController::getSupportedResourceTypes(); $extensions = CelerityResourceController::getSupportedResourceTypes();
$extensions = array_keys($extensions); $extensions = array_keys($extensions);
$extensions = implode('|', $extensions); $extensions = implode('|', $extensions);

View file

@ -434,7 +434,8 @@ final class PhabricatorConduitAPIController
$token = idx($metadata, 'authToken'); $token = idx($metadata, 'authToken');
$signature = idx($metadata, 'authSignature'); $signature = idx($metadata, 'authSignature');
$certificate = $user->getConduitCertificate(); $certificate = $user->getConduitCertificate();
if (sha1($token.$certificate) !== $signature) { $hash = sha1($token.$certificate);
if (!phutil_hashes_are_identical($hash, $signature)) {
return array( return array(
'ERR-INVALID-AUTH', 'ERR-INVALID-AUTH',
pht('Authentication is invalid.'), pht('Authentication is invalid.'),

View file

@ -142,7 +142,7 @@ final class ConduitConnectConduitAPIMethod extends ConduitAPIMethod {
$threshold)); $threshold));
} }
$valid = sha1($token.$user->getConduitCertificate()); $valid = sha1($token.$user->getConduitCertificate());
if ($valid != $signature) { if (!phutil_hashes_are_identical($valid, $signature)) {
throw new ConduitException('ERR-INVALID-CERTIFICATE'); throw new ConduitException('ERR-INVALID-CERTIFICATE');
} }
$session_key = id(new PhabricatorAuthSessionEngine())->establishSession( $session_key = id(new PhabricatorAuthSessionEngine())->establishSession(

View file

@ -276,6 +276,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'Impersonating users over the API is no longer supported.'), 'Impersonating users over the API is no longer supported.'),
'feed.public' => pht('The framable public feed is no longer supported.'), 'feed.public' => pht('The framable public feed is no longer supported.'),
'auth.login-message' => pht(
'This configuration option has been replaced with a modular '.
'handler. See T9346.'),
); );
return $ancient_config; return $ancient_config;

View file

@ -122,7 +122,7 @@ final class PhabricatorConfigEditController
->appendChild(phutil_tag('p', array(), $msg)); ->appendChild(phutil_tag('p', array(), $msg));
} }
if ($option->getHidden()) { if ($option->getHidden() || $option->getLocked()) {
$control = null; $control = null;
} else { } else {
$control = $this->renderControl( $control = $this->renderControl(
@ -164,14 +164,20 @@ final class PhabricatorConfigEditController
$form $form
->appendChild($control); ->appendChild($control);
$submit_control = id(new AphrontFormSubmitControl())
->addCancelButton($done_uri);
if (!$option->getLocked()) { if (!$option->getLocked()) {
$submit_control->setValue(pht('Save Config Entry')); $form->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($done_uri)
->setValue(pht('Save Config Entry')));
} }
$form->appendChild($submit_control); if (!$option->getHidden()) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Current Configuration'))
->setValue($this->renderDefaults($option, $config_entry)));
}
$examples = $this->renderExamples($option); $examples = $this->renderExamples($option);
if ($examples) { if ($examples) {
@ -181,13 +187,6 @@ final class PhabricatorConfigEditController
->setValue($examples)); ->setValue($examples));
} }
if (!$option->getHidden()) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Default'))
->setValue($this->renderDefaults($option, $config_entry)));
}
$title = pht('Edit %s', $key); $title = pht('Edit %s', $key);
$short = pht('Edit'); $short = pht('Edit');
@ -438,16 +437,12 @@ final class PhabricatorConfigEditController
} }
$control $control
->setLabel(pht('Value')) ->setLabel(pht('Database Value'))
->setError($e_value) ->setError($e_value)
->setValue($display_value) ->setValue($display_value)
->setName('value'); ->setName('value');
} }
if ($option->getLocked()) {
$control->setDisabled(true);
}
return $control; return $control;
} }
@ -501,25 +496,41 @@ final class PhabricatorConfigEditController
phutil_tag('th', array(), pht('Source')), phutil_tag('th', array(), pht('Source')),
phutil_tag('th', array(), pht('Value')), phutil_tag('th', array(), pht('Value')),
)); ));
$is_effective_value = true;
foreach ($stack as $key => $source) { foreach ($stack as $key => $source) {
$row_classes = array(
'column-labels',
);
$value = $source->getKeys( $value = $source->getKeys(
array( array(
$option->getKey(), $option->getKey(),
)); ));
if (!array_key_exists($option->getKey(), $value)) { if (!array_key_exists($option->getKey(), $value)) {
$value = phutil_tag('em', array(), pht('(empty)')); $value = phutil_tag('em', array(), pht('(No Value Configured)'));
} else { } else {
$value = $this->getDisplayValue( $value = $this->getDisplayValue(
$option, $option,
$entry, $entry,
$value[$option->getKey()]); $value[$option->getKey()]);
if ($is_effective_value) {
$is_effective_value = false;
$row_classes[] = 'config-options-effective-value';
}
} }
$table[] = phutil_tag('tr', array('class' => 'column-labels'), array( $table[] = phutil_tag(
phutil_tag('th', array(), $source->getName()), 'tr',
phutil_tag('td', array(), $value), array(
)); 'class' => implode(' ', $row_classes),
),
array(
phutil_tag('th', array(), $source->getName()),
phutil_tag('td', array(), $value),
));
} }
require_celerity_resource('config-options-css'); require_celerity_resource('config-options-css');

View file

@ -41,7 +41,7 @@ final class PhabricatorConfigEdgeModule extends PhabricatorConfigModule {
return id(new PHUIObjectBoxView()) return id(new PHUIObjectBoxView())
->setHeaderText(pht('Edge Types')) ->setHeaderText(pht('Edge Types'))
->appendChild($table); ->setTable($table);
} }
} }

View file

@ -41,7 +41,7 @@ final class PhabricatorConfigPHIDModule extends PhabricatorConfigModule {
return id(new PHUIObjectBoxView()) return id(new PHUIObjectBoxView())
->setHeaderText(pht('PHID Types')) ->setHeaderText(pht('PHID Types'))
->appendChild($table); ->setTable($table);
} }
} }

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorConfigRequestExceptionHandlerModule
extends PhabricatorConfigModule {
public function getModuleKey() {
return 'exception-handler';
}
public function getModuleName() {
return pht('Exception Handlers');
}
public function renderModuleStatus(AphrontRequest $request) {
$viewer = $request->getViewer();
$handlers = AphrontRequestExceptionHandler::getAllHandlers();
$rows = array();
foreach ($handlers as $key => $handler) {
$rows[] = array(
$handler->getRequestExceptionHandlerPriority(),
$key,
$handler->getRequestExceptionHandlerDescription(),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Priority'),
pht('Class'),
pht('Description'),
))
->setColumnClasses(
array(
null,
'pri',
'wide',
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Exception Handlers'))
->setTable($table);
}
}

View file

@ -40,7 +40,7 @@ final class PhabricatorConfigSiteModule extends PhabricatorConfigModule {
return id(new PHUIObjectBoxView()) return id(new PHUIObjectBoxView())
->setHeaderText(pht('Sites')) ->setHeaderText(pht('Sites'))
->appendChild($table); ->setTable($table);
} }
} }

View file

@ -73,14 +73,6 @@ final class PhabricatorAuthenticationConfigOptions
->addExample( ->addExample(
"yourcompany.com\nmail.yourcompany.com", "yourcompany.com\nmail.yourcompany.com",
pht('Valid Setting')), pht('Valid Setting')),
$this->newOption('auth.login-message', 'string', null)
->setLocked(true)
->setSummary(pht('A block of HTML displayed on the login screen.'))
->setDescription(
pht(
"You can provide an arbitrary block of HTML here, which will ".
"appear on the login screen. Normally, you'd use this to provide ".
"login or registration instructions to users.")),
$this->newOption('account.editable', 'bool', true) $this->newOption('account.editable', 'bool', true)
->setBoolOptions( ->setBoolOptions(
array( array(

View file

@ -76,9 +76,9 @@ final class PhabricatorConfigOption
} }
return pht( return pht(
'This configuration is locked and can not be edited from the web '. 'This configuration is locked and can not be edited from the web '.
'interface. Use `%s` in `%s` to edit it.', 'interface. Use %s in %s to edit it.',
'./bin/config', phutil_tag('tt', array(), './bin/config'),
'phabricator/'); phutil_tag('tt', array(), 'phabricator/'));
} }
public function addExample($value, $description) { public function addExample($value, $description) {

View file

@ -90,6 +90,11 @@ final class ConpherenceLayoutView extends AphrontView {
'hasWidgets' => false, 'hasWidgets' => false,
)); ));
$class = null;
if (!$this->getUser()->isLoggedIn()) {
$class = 'conpherence-logged-out';
}
$this->initBehavior( $this->initBehavior(
'conpherence-widget-pane', 'conpherence-widget-pane',
ConpherenceWidgetConfigConstants::getWidgetPaneBehaviorConfig()); ConpherenceWidgetConfigConstants::getWidgetPaneBehaviorConfig());
@ -99,7 +104,9 @@ final class ConpherenceLayoutView extends AphrontView {
array( array(
'id' => $layout_id, 'id' => $layout_id,
'sigil' => 'conpherence-layout', 'sigil' => 'conpherence-layout',
'class' => 'conpherence-layout conpherence-role-'.$this->role, 'class' => 'conpherence-layout '.
$class.
' conpherence-role-'.$this->role,
), ),
array( array(
javelin_tag( javelin_tag(

View file

@ -14,66 +14,121 @@ final class DarkConsoleRequestPlugin extends DarkConsolePlugin {
} }
public function generateData() { public function generateData() {
$addr = idx($_SERVER, 'SERVER_ADDR');
if ($addr) {
$hostname = @gethostbyaddr($addr);
} else {
$hostname = null;
}
$controller = $this->getRequest()->getController();
if ($controller) {
$controller_class = get_class($controller);
} else {
$controller_class = null;
}
$site = $this->getRequest()->getSite();
if ($site) {
$site_class = get_class($site);
} else {
$site_class = null;
}
return array( return array(
'Request' => $_REQUEST, 'request' => $_REQUEST,
'Server' => $_SERVER, 'server' => $_SERVER,
'special' => array(
'site' => $site_class,
'controller' => $controller_class,
'machine' => php_uname('n'),
'host' => $addr,
'hostname' => $hostname,
),
); );
} }
public function renderPanel() { public function renderPanel() {
$data = $this->getData(); $data = $this->getData();
$sections = array( $special_map = array(
'Basics' => array( 'site' => pht('Site'),
'Machine' => php_uname('n'), 'controller' => pht('Controller'),
), 'machine' => pht('Machine'),
'host' => pht('Host'),
'hostname' => pht('Hostname'),
); );
// NOTE: This may not be present for some SAPIs, like php-fpm. $special = idx($data, 'special', array());
if (!empty($data['Server']['SERVER_ADDR'])) {
$addr = $data['Server']['SERVER_ADDR']; $rows = array();
$sections['Basics']['Host'] = $addr; foreach ($special_map as $key => $label) {
$sections['Basics']['Hostname'] = @gethostbyaddr($addr); $rows[] = array(
$label,
idx($special, $key),
);
} }
$sections = array_merge($sections, $data); $sections = array();
$sections[] = array(
'name' => pht('Basics'),
'rows' => $rows,
);
$mask = array( $mask = array(
'HTTP_COOKIE' => true, 'HTTP_COOKIE' => true,
'HTTP_X_PHABRICATOR_CSRF' => true, 'HTTP_X_PHABRICATOR_CSRF' => true,
); );
$out = array(); $maps = array(
foreach ($sections as $header => $map) { array(
'name' => pht('Request'),
'data' => idx($data, 'request', array()),
),
array(
'name' => pht('Server'),
'data' => idx($data, 'server', array()),
),
);
foreach ($maps as $map) {
$data = $map['data'];
$rows = array(); $rows = array();
foreach ($map as $key => $value) { foreach ($data as $key => $value) {
if (isset($mask[$key])) { if (isset($mask[$key])) {
$rows[] = array( $value = phutil_tag('em', array(), pht('(Masked)'));
$key, } else if (is_array($value)) {
phutil_tag('em', array(), pht('(Masked)')), $value = @json_encode($value);
);
} else { } else {
$rows[] = array( $value = $value;
$key,
(is_array($value) ? json_encode($value) : $value),
);
} }
$rows[] = array(
$key,
$value,
);
} }
$table = new AphrontTableView($rows); $sections[] = array(
$table->setHeaders( 'name' => $map['name'],
array( 'rows' => $rows,
$header, );
null,
));
$table->setColumnClasses(
array(
'header',
'wide wrap',
));
$out[] = $table->render();
} }
return phutil_implode_html("\n", $out); $out = array();
foreach ($sections as $section) {
$out[] = id(new AphrontTableView($section['rows']))
->setHeaders(
array(
$section['name'],
null,
))
->setColumnClasses(
array(
'header',
'wide wrap',
));
}
return $out;
} }
} }

View file

@ -26,7 +26,6 @@ final class DifferentialCreateDiffConduitAPIMethod
'okay', 'okay',
'warn', 'warn',
'fail', 'fail',
'postponed',
)); ));
return array( return array(
@ -95,9 +94,6 @@ final class DifferentialCreateDiffConduitAPIMethod
case 'fail': case 'fail':
$lint_status = DifferentialLintStatus::LINT_FAIL; $lint_status = DifferentialLintStatus::LINT_FAIL;
break; break;
case 'postponed':
$lint_status = DifferentialLintStatus::LINT_POSTPONED;
break;
case 'none': case 'none':
default: default:
$lint_status = DifferentialLintStatus::LINT_NONE; $lint_status = DifferentialLintStatus::LINT_NONE;
@ -117,9 +113,6 @@ final class DifferentialCreateDiffConduitAPIMethod
case 'fail': case 'fail':
$unit_status = DifferentialUnitStatus::UNIT_FAIL; $unit_status = DifferentialUnitStatus::UNIT_FAIL;
break; break;
case 'postponed':
$unit_status = DifferentialUnitStatus::UNIT_POSTPONED;
break;
case 'none': case 'none':
default: default:
$unit_status = DifferentialUnitStatus::UNIT_NONE; $unit_status = DifferentialUnitStatus::UNIT_NONE;

View file

@ -2,12 +2,11 @@
final class DifferentialLintStatus extends Phobject { final class DifferentialLintStatus extends Phobject {
const LINT_NONE = 0; const LINT_NONE = 0;
const LINT_OKAY = 1; const LINT_OKAY = 1;
const LINT_WARN = 2; const LINT_WARN = 2;
const LINT_FAIL = 3; const LINT_FAIL = 3;
const LINT_SKIP = 4; const LINT_SKIP = 4;
const LINT_POSTPONED = 5; const LINT_AUTO_SKIP = 6;
const LINT_AUTO_SKIP = 6;
} }

View file

@ -2,12 +2,11 @@
final class DifferentialUnitStatus extends Phobject { final class DifferentialUnitStatus extends Phobject {
const UNIT_NONE = 0; const UNIT_NONE = 0;
const UNIT_OKAY = 1; const UNIT_OKAY = 1;
const UNIT_WARN = 2; const UNIT_WARN = 2;
const UNIT_FAIL = 3; const UNIT_FAIL = 3;
const UNIT_SKIP = 4; const UNIT_SKIP = 4;
const UNIT_POSTPONED = 5; const UNIT_AUTO_SKIP = 6;
const UNIT_AUTO_SKIP = 6;
} }

View file

@ -2,11 +2,10 @@
final class DifferentialUnitTestResult extends Phobject { final class DifferentialUnitTestResult extends Phobject {
const RESULT_PASS = 'pass'; const RESULT_PASS = 'pass';
const RESULT_FAIL = 'fail'; const RESULT_FAIL = 'fail';
const RESULT_SKIP = 'skip'; const RESULT_SKIP = 'skip';
const RESULT_BROKEN = 'broken'; const RESULT_BROKEN = 'broken';
const RESULT_UNSOUND = 'unsound'; const RESULT_UNSOUND = 'unsound';
const RESULT_POSTPONED = 'postponed';
} }

View file

@ -73,9 +73,6 @@ final class DifferentialLintField
if ($status == DifferentialLintStatus::LINT_SKIP) { if ($status == DifferentialLintStatus::LINT_SKIP) {
$warnings[] = pht( $warnings[] = pht(
'Lint was skipped when generating these changes.'); 'Lint was skipped when generating these changes.');
} else if ($status == DifferentialLintStatus::LINT_POSTPONED) {
$warnings[] = pht(
'Background linting has not finished executing on these changes.');
} else { } else {
$warnings[] = pht('These changes have lint problems.'); $warnings[] = pht('These changes have lint problems.');
} }
@ -94,7 +91,6 @@ final class DifferentialLintField
DifferentialLintStatus::LINT_FAIL => 'red', DifferentialLintStatus::LINT_FAIL => 'red',
DifferentialLintStatus::LINT_SKIP => 'blue', DifferentialLintStatus::LINT_SKIP => 'blue',
DifferentialLintStatus::LINT_AUTO_SKIP => 'blue', DifferentialLintStatus::LINT_AUTO_SKIP => 'blue',
DifferentialLintStatus::LINT_POSTPONED => 'blue',
); );
$icon_color = idx($colors, $diff->getLintStatus(), 'grey'); $icon_color = idx($colors, $diff->getLintStatus(), 'grey');

View file

@ -84,9 +84,6 @@ final class DifferentialUnitField
// Don't show any warnings. // Don't show any warnings.
} else if ($status == DifferentialUnitStatus::UNIT_AUTO_SKIP) { } else if ($status == DifferentialUnitStatus::UNIT_AUTO_SKIP) {
// Don't show any warnings. // Don't show any warnings.
} else if ($status == DifferentialUnitStatus::UNIT_POSTPONED) {
$warnings[] = pht(
'Background tests have not finished executing on these changes.');
} else if ($status == DifferentialUnitStatus::UNIT_SKIP) { } else if ($status == DifferentialUnitStatus::UNIT_SKIP) {
$warnings[] = pht( $warnings[] = pht(
'Unit tests were skipped when generating these changes.'); 'Unit tests were skipped when generating these changes.');
@ -108,7 +105,6 @@ final class DifferentialUnitField
DifferentialUnitStatus::UNIT_FAIL => 'red', DifferentialUnitStatus::UNIT_FAIL => 'red',
DifferentialUnitStatus::UNIT_SKIP => 'blue', DifferentialUnitStatus::UNIT_SKIP => 'blue',
DifferentialUnitStatus::UNIT_AUTO_SKIP => 'blue', DifferentialUnitStatus::UNIT_AUTO_SKIP => 'blue',
DifferentialUnitStatus::UNIT_POSTPONED => 'blue',
); );
$icon_color = idx($colors, $diff->getUnitStatus(), 'grey'); $icon_color = idx($colors, $diff->getUnitStatus(), 'grey');

View file

@ -323,7 +323,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
DifferentialLintStatus::LINT_FAIL => self::STAR_FAIL, DifferentialLintStatus::LINT_FAIL => self::STAR_FAIL,
DifferentialLintStatus::LINT_SKIP => self::STAR_SKIP, DifferentialLintStatus::LINT_SKIP => self::STAR_SKIP,
DifferentialLintStatus::LINT_AUTO_SKIP => self::STAR_SKIP, DifferentialLintStatus::LINT_AUTO_SKIP => self::STAR_SKIP,
DifferentialLintStatus::LINT_POSTPONED => self::STAR_SKIP,
); );
$star = idx($map, $diff->getLintStatus(), self::STAR_FAIL); $star = idx($map, $diff->getLintStatus(), self::STAR_FAIL);
@ -339,7 +338,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
DifferentialUnitStatus::UNIT_FAIL => self::STAR_FAIL, DifferentialUnitStatus::UNIT_FAIL => self::STAR_FAIL,
DifferentialUnitStatus::UNIT_SKIP => self::STAR_SKIP, DifferentialUnitStatus::UNIT_SKIP => self::STAR_SKIP,
DifferentialUnitStatus::UNIT_AUTO_SKIP => self::STAR_SKIP, DifferentialUnitStatus::UNIT_AUTO_SKIP => self::STAR_SKIP,
DifferentialUnitStatus::UNIT_POSTPONED => self::STAR_SKIP,
); );
$star = idx($map, $diff->getUnitStatus(), self::STAR_FAIL); $star = idx($map, $diff->getUnitStatus(), self::STAR_FAIL);
@ -361,8 +359,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
return pht('Lint Skipped'); return pht('Lint Skipped');
case DifferentialLintStatus::LINT_AUTO_SKIP: case DifferentialLintStatus::LINT_AUTO_SKIP:
return pht('Automatic diff as part of commit; lint not applicable.'); return pht('Automatic diff as part of commit; lint not applicable.');
case DifferentialLintStatus::LINT_POSTPONED:
return pht('Lint Postponed');
} }
return pht('Unknown'); return pht('Unknown');
} }
@ -382,8 +378,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
case DifferentialUnitStatus::UNIT_AUTO_SKIP: case DifferentialUnitStatus::UNIT_AUTO_SKIP:
return pht( return pht(
'Automatic diff as part of commit; unit tests not applicable.'); 'Automatic diff as part of commit; unit tests not applicable.');
case DifferentialUnitStatus::UNIT_POSTPONED:
return pht('Unit Tests Postponed');
} }
return pht('Unknown'); return pht('Unknown');
} }

View file

@ -302,7 +302,16 @@ final class DiffusionRepositoryController extends DiffusionController {
$info = null; $info = null;
$drequest = $this->getDiffusionRequest(); $drequest = $this->getDiffusionRequest();
if ($drequest->getRefAlternatives()) {
// Try to load alternatives. This may fail for repositories which have not
// cloned yet. If it does, just ignore it and continue.
try {
$alternatives = $drequest->getRefAlternatives();
} catch (ConduitClientException $ex) {
$alternatives = array();
}
if ($alternatives) {
$message = array( $message = array(
pht( pht(
'The ref "%s" is ambiguous in this repository.', 'The ref "%s" is ambiguous in this repository.',

View file

@ -1,9 +1,3 @@
<?php <?php
final class DiffusionSetupException extends AphrontUsageException { final class DiffusionSetupException extends Exception {}
public function __construct($message) {
parent::__construct(pht('Diffusion Setup Exception'), $message);
}
}

View file

@ -168,9 +168,7 @@ final class HeraldCommitAdapter
'commitPHID = %s AND auditStatus IN (%Ls)', 'commitPHID = %s AND auditStatus IN (%Ls)',
$this->commit->getPHID(), $this->commit->getPHID(),
$status_arr); $status_arr);
$this->auditNeededPackages = $requests;
$packages = mpull($requests, 'getAuditorPHID');
$this->auditNeededPackages = $packages;
} }
return $this->auditNeededPackages; return $this->auditNeededPackages;
} }

View file

@ -15,7 +15,7 @@ final class DiffusionCommitQuery
private $needAuditRequests; private $needAuditRequests;
private $auditIDs; private $auditIDs;
private $auditorPHIDs; private $auditorPHIDs;
private $auditAwaitingUser; private $needsAuditByPHIDs;
private $auditStatus; private $auditStatus;
private $epochMin; private $epochMin;
private $epochMax; private $epochMax;
@ -103,27 +103,6 @@ final class DiffusionCommitQuery
return $this; return $this;
} }
/**
* Returns true if we should join the audit table, either because we're
* interested in the information if it's available or because matching rows
* must always have it.
*/
private function shouldJoinAudits() {
return $this->auditStatus ||
$this->rowsMustHaveAudits();
}
/**
* Return true if we should `JOIN` (vs `LEFT JOIN`) the audit table, because
* matching commits will always have audit rows.
*/
private function rowsMustHaveAudits() {
return
$this->auditIDs ||
$this->auditorPHIDs ||
$this->auditAwaitingUser;
}
public function withAuditIDs(array $ids) { public function withAuditIDs(array $ids) {
$this->auditIDs = $ids; $this->auditIDs = $ids;
return $this; return $this;
@ -134,8 +113,8 @@ final class DiffusionCommitQuery
return $this; return $this;
} }
public function withAuditAwaitingUser(PhabricatorUser $user) { public function withNeedsAuditByPHIDs(array $needs_phids) {
$this->auditAwaitingUser = $user; $this->needsAuditByPHIDs = $needs_phids;
return $this; return $this;
} }
@ -175,21 +154,12 @@ final class DiffusionCommitQuery
} }
} }
public function newResultObject() {
return new PhabricatorRepositoryCommit();
}
protected function loadPage() { protected function loadPage() {
$table = new PhabricatorRepositoryCommit(); return $this->loadStandardPage($this->newResultObject());
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT commit.* FROM %T commit %Q %Q %Q %Q %Q',
$table->getTableName(),
$this->buildJoinClause($conn_r),
$this->buildWhereClause($conn_r),
$this->buildGroupClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
} }
protected function willFilterPage(array $commits) { protected function willFilterPage(array $commits) {
@ -296,8 +266,8 @@ final class DiffusionCommitQuery
return $commits; return $commits;
} }
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = array(); $where = parent::buildWhereClauseParts($conn);
if ($this->repositoryPHIDs !== null) { if ($this->repositoryPHIDs !== null) {
$map_repositories = id(new PhabricatorRepositoryQuery()) $map_repositories = id(new PhabricatorRepositoryQuery())
@ -317,42 +287,42 @@ final class DiffusionCommitQuery
if ($this->ids !== null) { if ($this->ids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'commit.id IN (%Ld)', 'commit.id IN (%Ld)',
$this->ids); $this->ids);
} }
if ($this->phids !== null) { if ($this->phids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'commit.phid IN (%Ls)', 'commit.phid IN (%Ls)',
$this->phids); $this->phids);
} }
if ($this->repositoryIDs !== null) { if ($this->repositoryIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'commit.repositoryID IN (%Ld)', 'commit.repositoryID IN (%Ld)',
$this->repositoryIDs); $this->repositoryIDs);
} }
if ($this->authorPHIDs !== null) { if ($this->authorPHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'commit.authorPHID IN (%Ls)', 'commit.authorPHID IN (%Ls)',
$this->authorPHIDs); $this->authorPHIDs);
} }
if ($this->epochMin !== null) { if ($this->epochMin !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'commit.epoch >= %d', 'commit.epoch >= %d',
$this->epochMin); $this->epochMin);
} }
if ($this->epochMax !== null) { if ($this->epochMax !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'commit.epoch <= %d', 'commit.epoch <= %d',
$this->epochMax); $this->epochMax);
} }
@ -360,13 +330,13 @@ final class DiffusionCommitQuery
if ($this->importing !== null) { if ($this->importing !== null) {
if ($this->importing) { if ($this->importing) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'(commit.importStatus & %d) != %d', '(commit.importStatus & %d) != %d',
PhabricatorRepositoryCommit::IMPORTED_ALL, PhabricatorRepositoryCommit::IMPORTED_ALL,
PhabricatorRepositoryCommit::IMPORTED_ALL); PhabricatorRepositoryCommit::IMPORTED_ALL);
} else { } else {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'(commit.importStatus & %d) = %d', '(commit.importStatus & %d) = %d',
PhabricatorRepositoryCommit::IMPORTED_ALL, PhabricatorRepositoryCommit::IMPORTED_ALL,
PhabricatorRepositoryCommit::IMPORTED_ALL); PhabricatorRepositoryCommit::IMPORTED_ALL);
@ -409,7 +379,7 @@ final class DiffusionCommitQuery
foreach ($bare as $identifier) { foreach ($bare as $identifier) {
$sql[] = qsprintf( $sql[] = qsprintf(
$conn_r, $conn,
'(commit.commitIdentifier LIKE %> AND '. '(commit.commitIdentifier LIKE %> AND '.
'LENGTH(commit.commitIdentifier) = 40)', 'LENGTH(commit.commitIdentifier) = 40)',
$identifier); $identifier);
@ -437,7 +407,7 @@ final class DiffusionCommitQuery
continue; continue;
} }
$sql[] = qsprintf( $sql[] = qsprintf(
$conn_r, $conn,
'(commit.repositoryID = %d AND commit.commitIdentifier = %s)', '(commit.repositoryID = %d AND commit.commitIdentifier = %s)',
$repo->getID(), $repo->getID(),
// NOTE: Because the 'commitIdentifier' column is a string, MySQL // NOTE: Because the 'commitIdentifier' column is a string, MySQL
@ -449,7 +419,7 @@ final class DiffusionCommitQuery
continue; continue;
} }
$sql[] = qsprintf( $sql[] = qsprintf(
$conn_r, $conn,
'(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)', '(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)',
$repo->getID(), $repo->getID(),
$ref['identifier']); $ref['identifier']);
@ -470,28 +440,23 @@ final class DiffusionCommitQuery
if ($this->auditIDs !== null) { if ($this->auditIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'audit.id IN (%Ld)', 'audit.id IN (%Ld)',
$this->auditIDs); $this->auditIDs);
} }
if ($this->auditorPHIDs !== null) { if ($this->auditorPHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'audit.auditorPHID IN (%Ls)', 'audit.auditorPHID IN (%Ls)',
$this->auditorPHIDs); $this->auditorPHIDs);
} }
if ($this->auditAwaitingUser) { if ($this->needsAuditByPHIDs !== null) {
$awaiting_user_phid = $this->auditAwaitingUser->getPHID();
// Exclude package and project audits associated with commits where
// the user is the author.
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'(commit.authorPHID IS NULL OR commit.authorPHID != %s) 'needs.auditorPHID IN (%Ls)',
OR (audit.auditorPHID = %s)', $this->needsAuditByPHIDs);
$awaiting_user_phid,
$awaiting_user_phid);
} }
$status = $this->auditStatus; $status = $this->auditStatus;
@ -499,33 +464,27 @@ final class DiffusionCommitQuery
switch ($status) { switch ($status) {
case self::AUDIT_STATUS_PARTIAL: case self::AUDIT_STATUS_PARTIAL:
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'commit.auditStatus = %d', 'commit.auditStatus = %d',
PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED); PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED);
break; break;
case self::AUDIT_STATUS_ACCEPTED: case self::AUDIT_STATUS_ACCEPTED:
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'commit.auditStatus = %d', 'commit.auditStatus = %d',
PhabricatorAuditCommitStatusConstants::FULLY_AUDITED); PhabricatorAuditCommitStatusConstants::FULLY_AUDITED);
break; break;
case self::AUDIT_STATUS_CONCERN: case self::AUDIT_STATUS_CONCERN:
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'audit.auditStatus = %s', 'status.auditStatus = %s',
PhabricatorAuditStatusConstants::CONCERNED); PhabricatorAuditStatusConstants::CONCERNED);
break; break;
case self::AUDIT_STATUS_OPEN: case self::AUDIT_STATUS_OPEN:
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'audit.auditStatus in (%Ls)', 'status.auditStatus in (%Ls)',
PhabricatorAuditStatusConstants::getOpenStatusConstants()); PhabricatorAuditStatusConstants::getOpenStatusConstants());
if ($this->auditAwaitingUser) {
$where[] = qsprintf(
$conn_r,
'awaiting.auditStatus IS NULL OR awaiting.auditStatus != %s',
PhabricatorAuditStatusConstants::RESIGNED);
}
break; break;
case self::AUDIT_STATUS_ANY: case self::AUDIT_STATUS_ANY:
break; break;
@ -545,9 +504,7 @@ final class DiffusionCommitQuery
} }
} }
$where[] = $this->buildPagingClause($conn_r); return $where;
return $this->formatWhereClause($where);
} }
protected function didFilterResults(array $filtered) { protected function didFilterResults(array $filtered) {
@ -560,56 +517,113 @@ final class DiffusionCommitQuery
} }
} }
protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { private function shouldJoinStatus() {
$joins = array(); return $this->auditStatus;
}
private function shouldJoinAudits() {
return $this->auditIDs || $this->auditorPHIDs;
}
private function shouldJoinNeeds() {
return $this->needsAuditByPHIDs;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$join = parent::buildJoinClauseParts($conn);
$audit_request = new PhabricatorRepositoryAuditRequest(); $audit_request = new PhabricatorRepositoryAuditRequest();
if ($this->shouldJoinAudits()) { if ($this->shouldJoinStatus()) {
$joins[] = qsprintf( $join[] = qsprintf(
$conn_r, $conn,
'%Q %T audit ON commit.phid = audit.commitPHID', 'LEFT JOIN %T status ON commit.phid = status.commitPHID',
($this->rowsMustHaveAudits() ? 'JOIN' : 'LEFT JOIN'),
$audit_request->getTableName()); $audit_request->getTableName());
} }
if ($this->auditAwaitingUser) { if ($this->shouldJoinAudits()) {
// Join the request table on the awaiting user's requests, so we can $join[] = qsprintf(
// filter out package and project requests which the user has resigned $conn,
// from. 'JOIN %T audit ON commit.phid = audit.commitPHID',
$joins[] = qsprintf( $audit_request->getTableName());
$conn_r,
'LEFT JOIN %T awaiting ON audit.commitPHID = awaiting.commitPHID AND
awaiting.auditorPHID = %s',
$audit_request->getTableName(),
$this->auditAwaitingUser->getPHID());
} }
if ($joins) { if ($this->shouldJoinNeeds()) {
return implode(' ', $joins); $join[] = qsprintf(
} else { $conn,
return ''; 'JOIN %T needs ON commit.phid = needs.commitPHID
AND needs.auditStatus IN (%Ls)',
$audit_request->getTableName(),
array(
PhabricatorAuditStatusConstants::AUDIT_REQUESTED,
PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
));
} }
return $join;
} }
protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { protected function shouldGroupQueryResultRows() {
$should_group = $this->shouldJoinAudits(); if ($this->shouldJoinStatus()) {
return true;
// TODO: Currently, the audit table is missing a unique key, so we may
// require a GROUP BY if we perform this join. See T1768. This can be
// removed once the table has the key.
if ($this->auditAwaitingUser) {
$should_group = true;
} }
if ($should_group) { if ($this->shouldJoinAudits()) {
return 'GROUP BY commit.id'; return true;
} else {
return '';
} }
if ($this->shouldJoinNeeds()) {
return true;
}
return parent::shouldGroupQueryResultRows();
} }
public function getQueryApplicationClass() { public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication'; return 'PhabricatorDiffusionApplication';
} }
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'epoch' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'epoch',
'type' => 'int',
'reverse' => false,
),
);
}
protected function getPagingValueMap($cursor, array $keys) {
$commit = $this->loadCursorObject($cursor);
return array(
'id' => $commit->getID(),
'epoch' => $commit->getEpoch(),
);
}
public function getBuiltinOrders() {
$parent = parent::getBuiltinOrders();
// Rename the default ID-based orders.
$parent['importnew'] = array(
'name' => pht('Import Date (Newest First)'),
) + $parent['newest'];
$parent['importold'] = array(
'name' => pht('Import Date (Oldest First)'),
) + $parent['oldest'];
return array(
'newest' => array(
'vector' => array('epoch', 'id'),
'name' => pht('Commit Date (Newest First)'),
),
'oldest' => array(
'vector' => array('-epoch', '-id'),
'name' => pht('Commit Date (Oldest First)'),
),
) + $parent;
}
} }

View file

@ -0,0 +1,25 @@
<?php
final class DiffusionAuditorFunctionDatasource
extends PhabricatorTypeaheadCompositeDatasource {
public function getBrowseTitle() {
return pht('Browse Auditors');
}
public function getPlaceholderText() {
return pht('Type a user, project, package name or function...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
public function getComponentDatasources() {
return array(
new PhabricatorProjectOrUserFunctionDatasource(),
new PhabricatorOwnersPackageFunctionDatasource(),
);
}
}

View file

@ -78,25 +78,36 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController', 'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController',
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorFileEditController', 'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorFileEditController',
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController', 'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
'data/'.
'(?:@(?P<instance>[^/]+)/)?'.
'(?P<key>[^/]+)/'.
'(?P<phid>[^/]+)/'.
'(?:(?P<token>[^/]+)/)?'.
'.*'
=> 'PhabricatorFileDataController',
'proxy/' => 'PhabricatorFileProxyController', 'proxy/' => 'PhabricatorFileProxyController',
'xform/'.
'(?:@(?P<instance>[^/]+)/)?'.
'(?P<transform>[^/]+)/'.
'(?P<phid>[^/]+)/'.
'(?P<key>[^/]+)/'
=> 'PhabricatorFileTransformController',
'transforms/(?P<id>[1-9]\d*)/' => 'transforms/(?P<id>[1-9]\d*)/' =>
'PhabricatorFileTransformListController', 'PhabricatorFileTransformListController',
'uploaddialog/' => 'PhabricatorFileUploadDialogController', 'uploaddialog/' => 'PhabricatorFileUploadDialogController',
'download/(?P<phid>[^/]+)/' => 'PhabricatorFileDialogController', 'download/(?P<phid>[^/]+)/' => 'PhabricatorFileDialogController',
), ) + $this->getResourceSubroutes(),
);
}
public function getResourceRoutes() {
return array(
'/file/' => $this->getResourceSubroutes(),
);
}
private function getResourceSubroutes() {
return array(
'data/'.
'(?:@(?P<instance>[^/]+)/)?'.
'(?P<key>[^/]+)/'.
'(?P<phid>[^/]+)/'.
'(?:(?P<token>[^/]+)/)?'.
'.*'
=> 'PhabricatorFileDataController',
'xform/'.
'(?:@(?P<instance>[^/]+)/)?'.
'(?P<transform>[^/]+)/'.
'(?P<phid>[^/]+)/'.
'(?P<key>[^/]+)/'
=> 'PhabricatorFileTransformController',
); );
} }

View file

@ -17,6 +17,7 @@ final class FileAllocateConduitAPIMethod
'contentLength' => 'int', 'contentLength' => 'int',
'contentHash' => 'optional string', 'contentHash' => 'optional string',
'viewPolicy' => 'optional string', 'viewPolicy' => 'optional string',
'deleteAfterEpoch' => 'optional int',
); );
} }
@ -39,6 +40,11 @@ final class FileAllocateConduitAPIMethod
'isExplicitUpload' => true, 'isExplicitUpload' => true,
); );
$ttl = $request->getValue('deleteAfterEpoch');
if ($ttl) {
$properties['ttl'] = $ttl;
}
$file = null; $file = null;
if ($hash) { if ($hash) {
$file = PhabricatorFile::newFileFromContentHash( $file = PhabricatorFile::newFileFromContentHash(

View file

@ -21,10 +21,15 @@ final class FundBackerProduct extends PhortuneProductImplementation {
public function getName(PhortuneProduct $product) { public function getName(PhortuneProduct $product) {
$initiative = $this->getInitiative(); $initiative = $this->getInitiative();
return pht(
'Fund %s %s', if (!$initiative) {
$initiative->getMonogram(), return pht('Fund <Unknown Initiative>');
$initiative->getName()); } else {
return pht(
'Fund %s %s',
$initiative->getMonogram(),
$initiative->getName());
}
} }
public function getPriceAsCurrency(PhortuneProduct $product) { public function getPriceAsCurrency(PhortuneProduct $product) {

View file

@ -88,7 +88,6 @@ final class HarbormasterUnitPropertyView extends AphrontView {
ArcanistUnitTestResult::RESULT_FAIL => pht('Failed'), ArcanistUnitTestResult::RESULT_FAIL => pht('Failed'),
ArcanistUnitTestResult::RESULT_UNSOUND => pht('Unsound'), ArcanistUnitTestResult::RESULT_UNSOUND => pht('Unsound'),
ArcanistUnitTestResult::RESULT_SKIP => pht('Skipped'), ArcanistUnitTestResult::RESULT_SKIP => pht('Skipped'),
ArcanistUnitTestResult::RESULT_POSTPONED => pht('Postponed'),
ArcanistUnitTestResult::RESULT_PASS => pht('Passed'), ArcanistUnitTestResult::RESULT_PASS => pht('Passed'),
); );
$result = idx($names, $result, $result); $result = idx($names, $result, $result);

View file

@ -349,9 +349,8 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController {
$query = id(new DiffusionCommitQuery()) $query = id(new DiffusionCommitQuery())
->setViewer($user) ->setViewer($user)
->withAuditorPHIDs($phids) ->withNeedsAuditByPHIDs($phids)
->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN)
->withAuditAwaitingUser($user)
->needAuditRequests(true) ->needAuditRequests(true)
->needCommitData(true) ->needCommitData(true)
->setLimit(10); ->setLimit(10);

View file

@ -226,39 +226,36 @@ final class PhabricatorAppSearchEngine
), ),
''); '');
$description = phutil_tag( $description = $application->getShortDescription();
'div',
array( $configure = id(new PHUIButtonView())
'style' => 'white-space: nowrap; '. ->setTag('a')
'overflow: hidden; '. ->setHref('/applications/view/'.get_class($application).'/')
'text-overflow: ellipsis;', ->setText(pht('Configure'))
), ->setColor(PHUIButtonView::GREY);
$application->getShortDescription());
$name = $application->getName();
if ($application->isPrototype()) {
$name = $name.' '.pht('(Prototype)');
}
$item = id(new PHUIObjectItemView()) $item = id(new PHUIObjectItemView())
->setHeader($application->getName()) ->setHeader($name)
->setImageIcon($icon_view) ->setImageIcon($icon_view)
->addAttribute($description) ->setSubhead($description)
->addAction( ->setLaunchButton($configure);
id(new PHUIListItemView())
->setName(pht('Help/Options'))
->setIcon('fa-cog')
->setHref('/applications/view/'.get_class($application).'/'));
if ($application->getBaseURI() && $application->isInstalled()) { if ($application->getBaseURI() && $application->isInstalled()) {
$item->setHref($application->getBaseURI()); $item->setHref($application->getBaseURI());
} }
if (!$application->isInstalled()) { if (!$application->isInstalled()) {
$item->addIcon('fa-times', pht('Uninstalled')); $item->addAttribute(pht('Uninstalled'));
} $item->setDisabled(true);
if ($application->isPrototype()) {
$item->addIcon('fa-bomb grey', pht('Prototype'));
} }
if (!$application->isFirstParty()) { if (!$application->isFirstParty()) {
$item->addIcon('fa-puzzle-piece', pht('Extension')); $item->addAttribute(pht('Extension'));
} }
$list->addItem($item); $list->addItem($item);

View file

@ -13,9 +13,11 @@ final class PhabricatorMetaMTAMailgunReceiveController
$timestamp = $request->getStr('timestamp'); $timestamp = $request->getStr('timestamp');
$token = $request->getStr('token'); $token = $request->getStr('token');
$sig = $request->getStr('signature'); $sig = $request->getStr('signature');
return hash_hmac('sha256', $timestamp.$token, $api_key) == $sig; $hash = hash_hmac('sha256', $timestamp.$token, $api_key);
return phutil_hashes_are_identical($sig, $hash);
} }
public function processRequest() { public function processRequest() {
// No CSRF for Mailgun. // No CSRF for Mailgun.

View file

@ -1,20 +1,15 @@
<?php <?php
final class PhabricatorOAuthServerAuthController final class PhabricatorOAuthServerAuthController
extends PhabricatorAuthController { extends PhabricatorOAuthServerController {
public function shouldRequireLogin() { public function handleRequest(AphrontRequest $request) {
return true; $viewer = $this->getViewer();
}
public function processRequest() { $server = new PhabricatorOAuthServer();
$request = $this->getRequest(); $client_phid = $request->getStr('client_id');
$viewer = $request->getUser(); $scope = $request->getStr('scope');
$redirect_uri = $request->getStr('redirect_uri');
$server = new PhabricatorOAuthServer();
$client_phid = $request->getStr('client_id');
$scope = $request->getStr('scope');
$redirect_uri = $request->getStr('redirect_uri');
$response_type = $request->getStr('response_type'); $response_type = $request->getStr('response_type');
// state is an opaque value the client sent us for their own purposes // state is an opaque value the client sent us for their own purposes
@ -30,6 +25,19 @@ final class PhabricatorOAuthServerAuthController
phutil_tag('strong', array(), 'client_id'))); phutil_tag('strong', array(), 'client_id')));
} }
// We require that users must be able to see an OAuth application
// in order to authorize it. This allows an application's visibility
// policy to be used to restrict authorized users.
try {
$client = id(new PhabricatorOAuthServerClientQuery())
->setViewer($viewer)
->withPHIDs(array($client_phid))
->executeOne();
} catch (PhabricatorPolicyException $ex) {
$ex->setContext(self::CONTEXT_AUTHORIZE);
throw $ex;
}
$server->setUser($viewer); $server->setUser($viewer);
$is_authorized = false; $is_authorized = false;
$authorization = null; $authorization = null;
@ -39,24 +47,6 @@ final class PhabricatorOAuthServerAuthController
// one giant try / catch around all the exciting database stuff so we // one giant try / catch around all the exciting database stuff so we
// can return a 'server_error' response if something goes wrong! // can return a 'server_error' response if something goes wrong!
try { try {
try {
$client = id(new PhabricatorOAuthServerClientQuery())
->setViewer($viewer)
->withPHIDs(array($client_phid))
->executeOne();
} catch (PhabricatorPolicyException $ex) {
// We require that users must be able to see an OAuth application
// in order to authorize it. This allows an application's visibility
// policy to be used to restrict authorized users.
// None of the OAuth error responses are a perfect fit for this, but
// 'invalid_client' seems closest.
return $this->buildErrorResponse(
'invalid_client',
pht('Not Authorized'),
pht('You are not authorized to authenticate.'));
}
if (!$client) { if (!$client) {
return $this->buildErrorResponse( return $this->buildErrorResponse(
'invalid_request', 'invalid_request',
@ -211,6 +201,7 @@ final class PhabricatorOAuthServerAuthController
} else { } else {
$desired_scopes = $scope; $desired_scopes = $scope;
} }
if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) { if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) {
return $this->buildErrorResponse( return $this->buildErrorResponse(
'invalid_scope', 'invalid_scope',
@ -236,8 +227,8 @@ final class PhabricatorOAuthServerAuthController
'error_description' => $cancel_msg, 'error_description' => $cancel_msg,
)); ));
$dialog = id(new AphrontDialogView()) return $this->newDialog()
->setUser($viewer) ->setShortTitle(pht('Authorize Access'))
->setTitle(pht('Authorize "%s"?', $name)) ->setTitle(pht('Authorize "%s"?', $name))
->setSubmitURI($request->getRequestURI()->getPath()) ->setSubmitURI($request->getRequestURI()->getPath())
->setWidth(AphrontDialogView::WIDTH_FORM) ->setWidth(AphrontDialogView::WIDTH_FORM)
@ -250,23 +241,18 @@ final class PhabricatorOAuthServerAuthController
->appendChild($form->buildLayoutView()) ->appendChild($form->buildLayoutView())
->addSubmitButton(pht('Authorize Access')) ->addSubmitButton(pht('Authorize Access'))
->addCancelButton((string)$cancel_uri, pht('Do Not Authorize')); ->addCancelButton((string)$cancel_uri, pht('Do Not Authorize'));
return id(new AphrontDialogResponse())->setDialog($dialog);
} }
private function buildErrorResponse($code, $title, $message) { private function buildErrorResponse($code, $title, $message) {
$viewer = $this->getRequest()->getUser(); $viewer = $this->getRequest()->getUser();
$dialog = id(new AphrontDialogView()) return $this->newDialog()
->setUser($viewer)
->setTitle(pht('OAuth: %s', $title)) ->setTitle(pht('OAuth: %s', $title))
->appendParagraph($message) ->appendParagraph($message)
->appendParagraph( ->appendParagraph(
pht('OAuth Error Code: %s', phutil_tag('tt', array(), $code))) pht('OAuth Error Code: %s', phutil_tag('tt', array(), $code)))
->addCancelButton('/', pht('Alas!')); ->addCancelButton('/', pht('Alas!'));
return id(new AphrontDialogResponse())->setDialog($dialog);
} }

View file

@ -3,58 +3,13 @@
abstract class PhabricatorOAuthServerController abstract class PhabricatorOAuthServerController
extends PhabricatorController { extends PhabricatorController {
public function buildStandardPageResponse($view, array $data) { const CONTEXT_AUTHORIZE = 'oauthserver.authorize';
$user = $this->getRequest()->getUser();
$page = $this->buildStandardPageView();
$page->setApplicationName(pht('OAuth Server'));
$page->setBaseURI('/oauthserver/');
$page->setTitle(idx($data, 'title'));
$nav = new AphrontSideNavFilterView(); protected function buildApplicationCrumbs() {
$nav->setBaseURI(new PhutilURI('/oauthserver/')); // We're specifically not putting an "OAuth Server" application crumb
$nav->addLabel(pht('Clients')); // on these pages because it doesn't make sense to send users there on
$nav->addFilter('client/create', pht('Create Client')); // the auth workflows.
foreach ($this->getExtraClientFilters() as $filter) { return new PHUICrumbsView();
$nav->addFilter($filter['url'], $filter['label']);
}
$nav->addFilter('client', pht('My Clients'));
$nav->selectFilter($this->getFilter(), 'clientauthorization');
$nav->appendChild($view);
$page->appendChild($nav);
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
} }
protected function getFilter() {
return 'clientauthorization';
}
protected function getExtraClientFilters() {
return array();
}
protected function getHighlightPHIDs() {
$phids = array();
$request = $this->getRequest();
$edited = $request->getStr('edited');
$new = $request->getStr('new');
if ($edited) {
$phids[$edited] = $edited;
}
if ($new) {
$phids[$new] = $new;
}
return $phids;
}
protected function buildErrorView($error_message) {
$error = new PHUIInfoView();
$error->setSeverity(PHUIInfoView::SEVERITY_ERROR);
$error->setTitle($error_message);
return $error;
}
} }

View file

@ -3,26 +3,13 @@
final class PhabricatorOAuthServerTestController final class PhabricatorOAuthServerTestController
extends PhabricatorOAuthServerController { extends PhabricatorOAuthServerController {
private $id; public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
public function shouldRequireLogin() { $id = $request->getURIData('id');
return true;
}
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$panels = array();
$results = array();
$client = id(new PhabricatorOAuthServerClientQuery()) $client = id(new PhabricatorOAuthServerClientQuery())
->setViewer($viewer) ->setViewer($viewer)
->withIDs(array($this->id)) ->withIDs(array($id))
->executeOne(); ->executeOne();
if (!$client) { if (!$client) {
return new Aphront404Response(); return new Aphront404Response();
@ -37,16 +24,13 @@ final class PhabricatorOAuthServerTestController
->withClientPHIDs(array($client->getPHID())) ->withClientPHIDs(array($client->getPHID()))
->executeOne(); ->executeOne();
if ($authorization) { if ($authorization) {
$dialog = id(new AphrontDialogView()) return $this->newDialog()
->setUser($viewer)
->setTitle(pht('Already Authorized')) ->setTitle(pht('Already Authorized'))
->appendParagraph( ->appendParagraph(
pht( pht(
'You have already authorized this application to access your '. 'You have already authorized this application to access your '.
'account.')) 'account.'))
->addCancelButton($view_uri, pht('Close')); ->addCancelButton($view_uri, pht('Close'));
return id(new AphrontDialogResponse())->setDialog($dialog);
} }
if ($request->isFormPost()) { if ($request->isFormPost()) {
@ -65,8 +49,7 @@ final class PhabricatorOAuthServerTestController
// TODO: It would be nice to put scope options in this dialog, maybe? // TODO: It would be nice to put scope options in this dialog, maybe?
$dialog = id(new AphrontDialogView()) return $this->newDialog()
->setUser($viewer)
->setTitle(pht('Authorize Application?')) ->setTitle(pht('Authorize Application?'))
->appendParagraph( ->appendParagraph(
pht( pht(
@ -75,7 +58,5 @@ final class PhabricatorOAuthServerTestController
phutil_tag('strong', array(), $client->getName()))) phutil_tag('strong', array(), $client->getName())))
->addCancelButton($view_uri) ->addCancelButton($view_uri)
->addSubmitButton(pht('Authorize Application')); ->addSubmitButton(pht('Authorize Application'));
return id(new AphrontDialogResponse())->setDialog($dialog);
} }
} }

View file

@ -1,7 +1,7 @@
<?php <?php
final class PhabricatorOAuthServerTokenController final class PhabricatorOAuthServerTokenController
extends PhabricatorAuthController { extends PhabricatorOAuthServerController {
public function shouldRequireLogin() { public function shouldRequireLogin() {
return false; return false;
@ -14,15 +14,15 @@ final class PhabricatorOAuthServerTokenController
return parent::shouldAllowRestrictedParameter($parameter_name); return parent::shouldAllowRestrictedParameter($parameter_name);
} }
public function processRequest() { public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest(); $grant_type = $request->getStr('grant_type');
$grant_type = $request->getStr('grant_type'); $code = $request->getStr('code');
$code = $request->getStr('code'); $redirect_uri = $request->getStr('redirect_uri');
$redirect_uri = $request->getStr('redirect_uri'); $client_phid = $request->getStr('client_id');
$client_phid = $request->getStr('client_id');
$client_secret = $request->getStr('client_secret'); $client_secret = $request->getStr('client_secret');
$response = new PhabricatorOAuthResponse(); $response = new PhabricatorOAuthResponse();
$server = new PhabricatorOAuthServer(); $server = new PhabricatorOAuthServer();
if ($grant_type != 'authorization_code') { if ($grant_type != 'authorization_code') {
$response->setError('unsupported_grant_type'); $response->setError('unsupported_grant_type');
$response->setErrorDescription( $response->setErrorDescription(
@ -32,11 +32,13 @@ final class PhabricatorOAuthServerTokenController
'authorization_code')); 'authorization_code'));
return $response; return $response;
} }
if (!$code) { if (!$code) {
$response->setError('invalid_request'); $response->setError('invalid_request');
$response->setErrorDescription(pht('Required parameter code missing.')); $response->setErrorDescription(pht('Required parameter code missing.'));
return $response; return $response;
} }
if (!$client_phid) { if (!$client_phid) {
$response->setError('invalid_request'); $response->setError('invalid_request');
$response->setErrorDescription( $response->setErrorDescription(
@ -45,6 +47,7 @@ final class PhabricatorOAuthServerTokenController
'client_id')); 'client_id'));
return $response; return $response;
} }
if (!$client_secret) { if (!$client_secret) {
$response->setError('invalid_request'); $response->setError('invalid_request');
$response->setErrorDescription( $response->setErrorDescription(
@ -53,6 +56,7 @@ final class PhabricatorOAuthServerTokenController
'client_secret')); 'client_secret'));
return $response; return $response;
} }
// one giant try / catch around all the exciting database stuff so we // one giant try / catch around all the exciting database stuff so we
// can return a 'server_error' response if something goes wrong! // can return a 'server_error' response if something goes wrong!
try { try {

View file

@ -26,7 +26,7 @@ final class PhabricatorOwnersApplication extends PhabricatorApplication {
return array( return array(
array( array(
'name' => pht('Owners User Guide'), 'name' => pht('Owners User Guide'),
'href' => PhabricatorEnv::getDoclink('Owners Tool User Guide'), 'href' => PhabricatorEnv::getDoclink('Owners User Guide'),
), ),
); );
} }

View file

@ -372,6 +372,11 @@ final class PhabricatorOwnersPackageQuery
$include = false; $include = false;
foreach ($package->getPaths() as $package_path) { foreach ($package->getPaths() as $package_path) {
if ($package_path->getRepositoryPHID() != $repository_phid) {
// If this path is for some other repository, skip it.
continue;
}
$strength = $package_path->getPathMatchStrength($path); $strength = $package_path->getPathMatchStrength($path);
if ($strength > $best_match) { if ($strength > $best_match) {
$best_match = $strength; $best_match = $strength;

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorOwnersPackageFunctionDatasource
extends PhabricatorTypeaheadCompositeDatasource {
public function getBrowseTitle() {
return pht('Browse Packages');
}
public function getPlaceholderText() {
return pht('Type a package name or function...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorOwnersApplication';
}
public function getComponentDatasources() {
return array(
new PhabricatorOwnersPackageDatasource(),
new PhabricatorOwnersPackageOwnerDatasource(),
);
}
}

View file

@ -0,0 +1,141 @@
<?php
final class PhabricatorOwnersPackageOwnerDatasource
extends PhabricatorTypeaheadCompositeDatasource {
public function getBrowseTitle() {
return pht('Browse Packages by Owner');
}
public function getPlaceholderText() {
return pht('Type packages(<user>) or packages(<project>)...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorOwnersApplication';
}
public function getComponentDatasources() {
return array(
new PhabricatorPeopleDatasource(),
new PhabricatorProjectDatasource(),
);
}
public function getDatasourceFunctions() {
return array(
'packages' => array(
'name' => pht('Packages: ...'),
'arguments' => pht('owner'),
'summary' => pht("Find results in any of an owner's projects."),
'description' => pht(
"This function allows you to find results associated with any ".
"of the packages a specified user or project is an owner of. ".
"For example, this will find results associated with all of ".
"the projects `%s` owns:\n\n%s\n\n",
'alincoln',
'> packages(alincoln)'),
),
);
}
protected function didLoadResults(array $results) {
foreach ($results as $result) {
$result
->setColor(null)
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
->setIcon('fa-asterisk')
->setPHID('packages('.$result->getPHID().')')
->setDisplayName(pht('Packages: %s', $result->getDisplayName()))
->setName($result->getName().' packages');
}
return $results;
}
protected function evaluateFunction($function, array $argv_list) {
$phids = array();
foreach ($argv_list as $argv) {
$phids[] = head($argv);
}
$phids = $this->resolvePHIDs($phids);
$user_phids = array();
foreach ($phids as $key => $phid) {
if (phid_get_type($phid) == PhabricatorPeopleUserPHIDType::TYPECONST) {
$user_phids[] = $phid;
unset($phids[$key]);
}
}
if ($user_phids) {
$packages = id(new PhabricatorOwnersPackageQuery())
->setViewer($this->getViewer())
->withOwnerPHIDs($user_phids)
->execute();
foreach ($packages as $package) {
$phids[] = $package->getPHID();
}
}
return $phids;
}
public function renderFunctionTokens($function, array $argv_list) {
$phids = array();
foreach ($argv_list as $argv) {
$phids[] = head($argv);
}
$phids = $this->resolvePHIDs($phids);
$tokens = $this->renderTokens($phids);
foreach ($tokens as $token) {
$token->setColor(null);
if ($token->isInvalid()) {
$token
->setValue(pht('Packages: Invalid Owner'));
} else {
$token
->setIcon('fa-asterisk')
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
->setKey('packages('.$token->getKey().')')
->setValue(pht('Packages: %s', $token->getValue()));
}
}
return $tokens;
}
private function resolvePHIDs(array $phids) {
// TODO: It would be nice for this to handle `packages(#project)` from a
// query string or eventually via Conduit. This could also share code with
// PhabricatorProjectLogicalUserDatasource.
$usernames = array();
foreach ($phids as $key => $phid) {
if (phid_get_type($phid) != PhabricatorPeopleUserPHIDType::TYPECONST) {
$usernames[$key] = $phid;
}
}
if ($usernames) {
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withUsernames($usernames)
->execute();
$users = mpull($users, null, 'getUsername');
foreach ($usernames as $key => $username) {
$user = idx($users, $username);
if ($user) {
$phids[$key] = $user->getPHID();
}
}
}
return $phids;
}
}

View file

@ -365,19 +365,16 @@ final class PhabricatorUser
} }
public function validateCSRFToken($token) { public function validateCSRFToken($token) {
$salt = null; // We expect a BREACH-mitigating token. See T3684.
$version = 'plain';
// This is a BREACH-mitigating token. See T3684.
$breach_prefix = self::CSRF_BREACH_PREFIX; $breach_prefix = self::CSRF_BREACH_PREFIX;
$breach_prelen = strlen($breach_prefix); $breach_prelen = strlen($breach_prefix);
if (strncmp($token, $breach_prefix, $breach_prelen) !== 0) {
if (!strncmp($token, $breach_prefix, $breach_prelen)) { return false;
$version = 'breach';
$salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH);
$token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH);
} }
$salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH);
$token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH);
// When the user posts a form, we check that it contains a valid CSRF token. // When the user posts a form, we check that it contains a valid CSRF token.
// Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept // Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept
// either the current token, the next token (users can submit a "future" // either the current token, the next token (users can submit a "future"
@ -407,22 +404,11 @@ final class PhabricatorUser
for ($ii = -$csrf_window; $ii <= 1; $ii++) { for ($ii = -$csrf_window; $ii <= 1; $ii++) {
$valid = $this->getRawCSRFToken($ii); $valid = $this->getRawCSRFToken($ii);
switch ($version) {
// TODO: We can remove this after the BREACH version has been in the $digest = PhabricatorHash::digest($valid, $salt);
// wild for a while. $digest = substr($digest, 0, self::CSRF_TOKEN_LENGTH);
case 'plain': if (phutil_hashes_are_identical($digest, $token)) {
if ($token == $valid) { return true;
return true;
}
break;
case 'breach':
$digest = PhabricatorHash::digest($valid, $salt);
if (substr($digest, 0, self::CSRF_TOKEN_LENGTH) == $token) {
return true;
}
break;
default:
throw new Exception(pht('Unknown CSRF token format!'));
} }
} }

View file

@ -39,9 +39,6 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
return array( return array(
'/phame/' => array( '/phame/' => array(
'' => 'PhamePostListController', '' => 'PhamePostListController',
'r/(?P<id>\d+)/(?P<hash>[^/]+)/(?P<name>.*)'
=> 'PhameResourceController',
'live/(?P<id>[^/]+)/(?P<more>.*)' => 'PhameBlogLiveController', 'live/(?P<id>[^/]+)/(?P<more>.*)' => 'PhameBlogLiveController',
'post/' => array( 'post/' => array(
'(?:(?P<filter>draft|all)/)?' => 'PhamePostListController', '(?:(?P<filter>draft|all)/)?' => 'PhamePostListController',
@ -65,6 +62,34 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
'feed/(?P<id>[^/]+)/' => 'PhameBlogFeedController', 'feed/(?P<id>[^/]+)/' => 'PhameBlogFeedController',
'new/' => 'PhameBlogEditController', 'new/' => 'PhameBlogEditController',
), ),
) + $this->getResourceSubroutes(),
);
}
public function getResourceRoutes() {
return array(
'/phame/' => $this->getResourceSubroutes(),
);
}
private function getResourceSubroutes() {
return array(
'r/(?P<id>\d+)/(?P<hash>[^/]+)/(?P<name>.*)' =>
'PhameResourceController',
);
}
public function getBlogRoutes() {
return array(
'/(?P<more>.*)' => 'PhameBlogLiveController',
);
}
public function getBlogCDNRoutes() {
return array(
'/phame/' => array(
'r/(?P<id>\d+)/(?P<hash>[^/]+)/(?P<name>.*)' =>
'PhameResourceController',
), ),
); );
} }

View file

@ -8,14 +8,20 @@ final class PhameBlogLiveController extends PhameController {
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$user = $request->getUser(); $user = $request->getUser();
$id = $request->getURIData('id');
$blog = id(new PhameBlogQuery()) $site = $request->getSite();
->setViewer($user) if ($site instanceof PhameBlogSite) {
->withIDs(array($id)) $blog = $site->getBlog();
->executeOne(); } else {
if (!$blog) { $id = $request->getURIData('id');
return new Aphront404Response();
$blog = id(new PhameBlogQuery())
->setViewer($user)
->withIDs(array($id))
->executeOne();
if (!$blog) {
return new Aphront404Response();
}
} }
if ($blog->getDomain() && ($request->getHost() != $blog->getDomain())) { if ($blog->getDomain() && ($request->getHost() != $blog->getDomain())) {

View file

@ -1,30 +0,0 @@
<?php
final class PhameBlogResourceSite extends PhameSite {
public function getDescription() {
return pht('Serves static resources for blogs.');
}
public function getPriority() {
return 3000;
}
public function newSiteForRequest(AphrontRequest $request) {
if (!$this->isPhameActive()) {
return null;
}
$whitelist = array(
'/phame/r/',
);
$path = $request->getPath();
if (!$this->isPathPrefixMatch($path, $whitelist)) {
return null;
}
return new PhameBlogResourceSite();
}
}

View file

@ -24,7 +24,7 @@ final class PhameBlogSite extends PhameSite {
} }
public function getPriority() { public function getPriority() {
return 4000; return 3000;
} }
public function newSiteForRequest(AphrontRequest $request) { public function newSiteForRequest(AphrontRequest $request) {
@ -53,11 +53,14 @@ final class PhameBlogSite extends PhameSite {
return id(new PhameBlogSite())->setBlog($blog); return id(new PhameBlogSite())->setBlog($blog);
} }
public function getPathForRouting(AphrontRequest $request) { public function getRoutingMaps() {
$path = $request->getPath(); $app = PhabricatorApplication::getByClass('PhabricatorPhameApplication');
$id = $this->getBlog()->getID();
return "/phame/live/{$id}/{$path}"; $maps = array();
$maps[] = $this->newRoutingMap()
->setApplication($app)
->setRoutes($app->getBlogRoutes());
return $maps;
} }
} }

View file

@ -268,6 +268,7 @@ final class PhortuneCartViewController
$refund_uri = $this->getApplicationURI("{$prefix}cart/{$id}/refund/"); $refund_uri = $this->getApplicationURI("{$prefix}cart/{$id}/refund/");
$update_uri = $this->getApplicationURI("{$prefix}cart/{$id}/update/"); $update_uri = $this->getApplicationURI("{$prefix}cart/{$id}/update/");
$accept_uri = $this->getApplicationURI("{$prefix}cart/{$id}/accept/"); $accept_uri = $this->getApplicationURI("{$prefix}cart/{$id}/accept/");
$print_uri = $this->getApplicationURI("{$prefix}cart/{$id}/?__print__=1");
$view->addAction( $view->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
@ -309,6 +310,13 @@ final class PhortuneCartViewController
->setHref($resume_uri)); ->setHref($resume_uri));
} }
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Printable Version'))
->setHref($print_uri)
->setOpenInNewWindow(true)
->setIcon('fa-print'));
return $view; return $view;
} }

View file

@ -109,6 +109,8 @@ final class PhrictionEditController
$notes = null; $notes = null;
$title = $content->getTitle(); $title = $content->getTitle();
$overwrite = false; $overwrite = false;
$v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$document->getPHID());
if ($request->isFormPost()) { if ($request->isFormPost()) {
@ -118,6 +120,7 @@ final class PhrictionEditController
$current_version = $request->getInt('contentVersion'); $current_version = $request->getInt('contentVersion');
$v_view = $request->getStr('viewPolicy'); $v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy'); $v_edit = $request->getStr('editPolicy');
$v_cc = $request->getArr('cc');
$xactions = array(); $xactions = array();
$xactions[] = id(new PhrictionTransaction()) $xactions[] = id(new PhrictionTransaction())
@ -132,6 +135,9 @@ final class PhrictionEditController
$xactions[] = id(new PhrictionTransaction()) $xactions[] = id(new PhrictionTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($v_edit); ->setNewValue($v_edit);
$xactions[] = id(new PhrictionTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
->setNewValue(array('=' => $v_cc));
$editor = id(new PhrictionTransactionEditor()) $editor = id(new PhrictionTransactionEditor())
->setActor($viewer) ->setActor($viewer)
@ -222,6 +228,13 @@ final class PhrictionEditController
->setName('content') ->setName('content')
->setID('document-textarea') ->setID('document-textarea')
->setUser($viewer)) ->setUser($viewer))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Subscribers'))
->setName('cc')
->setValue($v_cc)
->setUser($viewer)
->setDatasource(new PhabricatorMetaMTAMailableDatasource()))
->appendChild( ->appendChild(
id(new AphrontFormPolicyControl()) id(new AphrontFormPolicyControl())
->setName('viewPolicy') ->setName('viewPolicy')

View file

@ -6,6 +6,9 @@ final class PhabricatorPolicyException extends Exception {
private $rejection; private $rejection;
private $capabilityName; private $capabilityName;
private $moreInfo = array(); private $moreInfo = array();
private $objectPHID;
private $context;
private $capability;
public function setTitle($title) { public function setTitle($title) {
$this->title = $title; $this->title = $title;
@ -43,4 +46,31 @@ final class PhabricatorPolicyException extends Exception {
return $this->moreInfo; return $this->moreInfo;
} }
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setContext($context) {
$this->context = $context;
return $this;
}
public function getContext() {
return $this->context;
}
public function setCapability($capability) {
$this->capability = $capability;
return $this;
}
public function getCapability() {
return $this->capability;
}
} }

View file

@ -589,7 +589,9 @@ final class PhabricatorPolicyFilter extends Phobject {
$exception = id(new PhabricatorPolicyException($full_message)) $exception = id(new PhabricatorPolicyException($full_message))
->setTitle($access_denied) ->setTitle($access_denied)
->setObjectPHID($object->getPHID())
->setRejection($rejection) ->setRejection($rejection)
->setCapability($capability)
->setCapabilityName($capability_name) ->setCapabilityName($capability_name)
->setMoreInfo($details); ->setMoreInfo($details);
@ -710,6 +712,11 @@ final class PhabricatorPolicyFilter extends Phobject {
$objects = $policy->getRuleObjects(); $objects = $policy->getRuleObjects();
$action = null; $action = null;
foreach ($policy->getRules() as $rule) { foreach ($policy->getRules() as $rule) {
if (!is_array($rule)) {
// Reject, this policy rule is invalid.
return false;
}
$rule_object = idx($objects, idx($rule, 'rule')); $rule_object = idx($objects, idx($rule, 'rule'));
if (!$rule_object) { if (!$rule_object) {
// Reject, this policy has a bogus rule. // Reject, this policy has a bogus rule.
@ -831,7 +838,9 @@ final class PhabricatorPolicyFilter extends Phobject {
$exception = id(new PhabricatorPolicyException($full_message)) $exception = id(new PhabricatorPolicyException($full_message))
->setTitle($access_denied) ->setTitle($access_denied)
->setRejection($rejection); ->setObjectPHID($object->getPHID())
->setRejection($rejection)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW);
throw $exception; throw $exception;
} }

View file

@ -321,6 +321,12 @@ final class PhabricatorPolicy
$classes = array(); $classes = array();
foreach ($this->getRules() as $rule) { foreach ($this->getRules() as $rule) {
if (!is_array($rule)) {
// This rule is invalid. We'll reject it later, but don't need to
// extract anything from it for now.
continue;
}
$class = idx($rule, 'rule'); $class = idx($rule, 'rule');
try { try {
if (class_exists($class)) { if (class_exists($class)) {

View file

@ -5,14 +5,14 @@ final class PonderQuestionStatus extends PonderConstants {
const STATUS_OPEN = 'open'; const STATUS_OPEN = 'open';
const STATUS_CLOSED_RESOLVED = 'resolved'; const STATUS_CLOSED_RESOLVED = 'resolved';
const STATUS_CLOSED_OBSOLETE = 'obsolete'; const STATUS_CLOSED_OBSOLETE = 'obsolete';
const STATUS_CLOSED_DUPLICATE = 'duplicate'; const STATUS_CLOSED_INVALID = 'invalid';
public static function getQuestionStatusMap() { public static function getQuestionStatusMap() {
return array( return array(
self::STATUS_OPEN => pht('Open'), self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED_RESOLVED => pht('Closed, Resolved'), self::STATUS_CLOSED_RESOLVED => pht('Closed, Resolved'),
self::STATUS_CLOSED_OBSOLETE => pht('Closed, Obsolete'), self::STATUS_CLOSED_OBSOLETE => pht('Closed, Obsolete'),
self::STATUS_CLOSED_DUPLICATE => pht('Closed, Duplicate'), self::STATUS_CLOSED_INVALID => pht('Closed, Invalid'),
); );
} }
@ -21,7 +21,7 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_OPEN => pht('Open'), self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED_RESOLVED => pht('Closed, Resolved'), self::STATUS_CLOSED_RESOLVED => pht('Closed, Resolved'),
self::STATUS_CLOSED_OBSOLETE => pht('Closed, Obsolete'), self::STATUS_CLOSED_OBSOLETE => pht('Closed, Obsolete'),
self::STATUS_CLOSED_DUPLICATE => pht('Closed, Duplicate'), self::STATUS_CLOSED_INVALID => pht('Closed, Invalid'),
); );
return idx($map, $status, pht('Unknown')); return idx($map, $status, pht('Unknown'));
} }
@ -31,7 +31,7 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_OPEN => pht('Open'), self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED_RESOLVED => pht('Resolved'), self::STATUS_CLOSED_RESOLVED => pht('Resolved'),
self::STATUS_CLOSED_OBSOLETE => pht('Obsolete'), self::STATUS_CLOSED_OBSOLETE => pht('Obsolete'),
self::STATUS_CLOSED_DUPLICATE => pht('Duplicate'), self::STATUS_CLOSED_INVALID => pht('Invalid'),
); );
return idx($map, $status, pht('Unknown')); return idx($map, $status, pht('Unknown'));
} }
@ -43,9 +43,9 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_CLOSED_RESOLVED => self::STATUS_CLOSED_RESOLVED =>
pht('This question has been answered or resolved.'), pht('This question has been answered or resolved.'),
self::STATUS_CLOSED_OBSOLETE => self::STATUS_CLOSED_OBSOLETE =>
pht('This question is no longer valid or out of date.'), pht('This question is out of date.'),
self::STATUS_CLOSED_DUPLICATE => self::STATUS_CLOSED_INVALID =>
pht('This question is a duplicate of another question.'), pht('This question is invalid.'),
); );
return idx($map, $status, pht('Unknown')); return idx($map, $status, pht('Unknown'));
} }
@ -55,7 +55,7 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_OPEN => PHUITagView::COLOR_BLUE, self::STATUS_OPEN => PHUITagView::COLOR_BLUE,
self::STATUS_CLOSED_RESOLVED => PHUITagView::COLOR_BLACK, self::STATUS_CLOSED_RESOLVED => PHUITagView::COLOR_BLACK,
self::STATUS_CLOSED_OBSOLETE => PHUITagView::COLOR_BLACK, self::STATUS_CLOSED_OBSOLETE => PHUITagView::COLOR_BLACK,
self::STATUS_CLOSED_DUPLICATE => PHUITagView::COLOR_BLACK, self::STATUS_CLOSED_INVALID => PHUITagView::COLOR_BLACK,
); );
return idx($map, $status); return idx($map, $status);
@ -66,7 +66,7 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_OPEN => 'fa-question-circle', self::STATUS_OPEN => 'fa-question-circle',
self::STATUS_CLOSED_RESOLVED => 'fa-check', self::STATUS_CLOSED_RESOLVED => 'fa-check',
self::STATUS_CLOSED_OBSOLETE => 'fa-ban', self::STATUS_CLOSED_OBSOLETE => 'fa-ban',
self::STATUS_CLOSED_DUPLICATE => 'fa-clone', self::STATUS_CLOSED_INVALID => 'fa-ban',
); );
return idx($map, $status); return idx($map, $status);
@ -82,7 +82,7 @@ final class PonderQuestionStatus extends PonderConstants {
return array( return array(
self::STATUS_CLOSED_RESOLVED, self::STATUS_CLOSED_RESOLVED,
self::STATUS_CLOSED_OBSOLETE, self::STATUS_CLOSED_OBSOLETE,
self::STATUS_CLOSED_DUPLICATE, self::STATUS_CLOSED_INVALID,
); );
} }

View file

@ -32,6 +32,7 @@ final class PonderQuestionEditController extends PonderController {
$v_title = $question->getTitle(); $v_title = $question->getTitle();
$v_content = $question->getContent(); $v_content = $question->getContent();
$v_wiki = $question->getAnswerWiki();
$v_view = $question->getViewPolicy(); $v_view = $question->getViewPolicy();
$v_space = $question->getSpacePHID(); $v_space = $question->getSpacePHID();
$v_status = $question->getStatus(); $v_status = $question->getStatus();
@ -42,6 +43,7 @@ final class PonderQuestionEditController extends PonderController {
if ($request->isFormPost()) { if ($request->isFormPost()) {
$v_title = $request->getStr('title'); $v_title = $request->getStr('title');
$v_content = $request->getStr('content'); $v_content = $request->getStr('content');
$v_wiki = $request->getStr('answerWiki');
$v_projects = $request->getArr('projects'); $v_projects = $request->getArr('projects');
$v_view = $request->getStr('viewPolicy'); $v_view = $request->getStr('viewPolicy');
$v_space = $request->getStr('spacePHID'); $v_space = $request->getStr('spacePHID');
@ -68,6 +70,10 @@ final class PonderQuestionEditController extends PonderController {
->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT) ->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT)
->setNewValue($v_content); ->setNewValue($v_content);
$xactions[] = id(clone $template)
->setTransactionType(PonderQuestionTransaction::TYPE_ANSWERWIKI)
->setNewValue($v_wiki);
if (!$is_new) { if (!$is_new) {
$xactions[] = id(clone $template) $xactions[] = id(clone $template)
->setTransactionType(PonderQuestionTransaction::TYPE_STATUS) ->setTransactionType(PonderQuestionTransaction::TYPE_STATUS)
@ -119,7 +125,15 @@ final class PonderQuestionEditController extends PonderController {
->setName('content') ->setName('content')
->setID('content') ->setID('content')
->setValue($v_content) ->setValue($v_content)
->setLabel(pht('Description')) ->setLabel(pht('Question Details'))
->setUser($viewer))
->appendChild(
id(new PhabricatorRemarkupControl())
->setUser($viewer)
->setName('answerWiki')
->setID('answerWiki')
->setValue($v_wiki)
->setLabel(pht('Answer Summary'))
->setUser($viewer)) ->setUser($viewer))
->appendControl( ->appendControl(
id(new AphrontFormPolicyControl()) id(new AphrontFormPolicyControl())
@ -157,6 +171,11 @@ final class PonderQuestionEditController extends PonderController {
->setControlID('content') ->setControlID('content')
->setPreviewURI($this->getApplicationURI('preview/')); ->setPreviewURI($this->getApplicationURI('preview/'));
$answer_preview = id(new PHUIRemarkupPreviewPanel())
->setHeader(pht('Answer Summary Preview'))
->setControlID('answerWiki')
->setPreviewURI($this->getApplicationURI('preview/'));
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$id = $question->getID(); $id = $question->getID();
@ -179,6 +198,7 @@ final class PonderQuestionEditController extends PonderController {
$crumbs, $crumbs,
$form_box, $form_box,
$preview, $preview,
$answer_preview,
), ),
array( array(
'title' => $title, 'title' => $title,

View file

@ -20,7 +20,7 @@ final class PonderQuestionViewController extends PonderController {
return new Aphront404Response(); return new Aphront404Response();
} }
$answers = $this->buildAnswers($question->getAnswers()); $answers = $this->buildAnswers($question);
$answer_add_panel = id(new PonderAddAnswerView()) $answer_add_panel = id(new PonderAddAnswerView())
->setQuestion($question) ->setQuestion($question)
@ -81,13 +81,37 @@ final class PonderQuestionViewController extends PonderController {
->addPropertyList($properties) ->addPropertyList($properties)
->appendChild($footer); ->appendChild($footer);
if ($viewer->getPHID() == $question->getAuthorPHID()) {
$status = $question->getStatus();
$answers_list = $question->getAnswers();
if ($answers_list && ($status == PonderQuestionStatus::STATUS_OPEN)) {
$info_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->appendChild(
pht(
'If this question has been resolved, please consider closing
the question and marking the answer as helpful.'));
$object_box->setInfoView($info_view);
}
}
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->addTextCrumb('Q'.$id, '/Q'.$id); $crumbs->addTextCrumb('Q'.$id, '/Q'.$id);
$answer_wiki = null;
if ($question->getAnswerWiki()) {
$answer = phutil_tag_div('mlt mlb msr msl', $question->getAnswerWiki());
$answer_wiki = id(new PHUIObjectBoxView())
->setHeaderText(pht('Answer Summary'))
->setColor(PHUIObjectBoxView::COLOR_BLUE)
->appendChild($answer);
}
$ponder_view = id(new PHUITwoColumnView()) $ponder_view = id(new PHUITwoColumnView())
->setMainColumn(array( ->setMainColumn(array(
$object_box, $object_box,
$comment_view, $comment_view,
$answer_wiki,
$answers, $answers,
$answer_add_panel, $answer_add_panel,
)) ))
@ -206,8 +230,9 @@ final class PonderQuestionViewController extends PonderController {
* TODO - re-factor this to ajax in one answer panel at a time in a more * TODO - re-factor this to ajax in one answer panel at a time in a more
* standard fashion. This is necessary to scale this application. * standard fashion. This is necessary to scale this application.
*/ */
private function buildAnswers(array $answers) { private function buildAnswers(PonderQuestion $question) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$answers = $question->getAnswers();
$author_phids = mpull($answers, 'getAuthorPHID'); $author_phids = mpull($answers, 'getAuthorPHID');
$handles = $this->loadViewerHandles($author_phids); $handles = $this->loadViewerHandles($author_phids);

View file

@ -74,6 +74,7 @@ final class PonderQuestionEditor
$types[] = PonderQuestionTransaction::TYPE_CONTENT; $types[] = PonderQuestionTransaction::TYPE_CONTENT;
$types[] = PonderQuestionTransaction::TYPE_ANSWERS; $types[] = PonderQuestionTransaction::TYPE_ANSWERS;
$types[] = PonderQuestionTransaction::TYPE_STATUS; $types[] = PonderQuestionTransaction::TYPE_STATUS;
$types[] = PonderQuestionTransaction::TYPE_ANSWERWIKI;
return $types; return $types;
} }
@ -91,6 +92,8 @@ final class PonderQuestionEditor
return mpull($object->getAnswers(), 'getPHID'); return mpull($object->getAnswers(), 'getPHID');
case PonderQuestionTransaction::TYPE_STATUS: case PonderQuestionTransaction::TYPE_STATUS:
return $object->getStatus(); return $object->getStatus();
case PonderQuestionTransaction::TYPE_ANSWERWIKI:
return $object->getAnswerWiki();
} }
} }
@ -102,6 +105,7 @@ final class PonderQuestionEditor
case PonderQuestionTransaction::TYPE_TITLE: case PonderQuestionTransaction::TYPE_TITLE:
case PonderQuestionTransaction::TYPE_CONTENT: case PonderQuestionTransaction::TYPE_CONTENT:
case PonderQuestionTransaction::TYPE_STATUS: case PonderQuestionTransaction::TYPE_STATUS:
case PonderQuestionTransaction::TYPE_ANSWERWIKI:
return $xaction->getNewValue(); return $xaction->getNewValue();
case PonderQuestionTransaction::TYPE_ANSWERS: case PonderQuestionTransaction::TYPE_ANSWERS:
$raw_new_value = $xaction->getNewValue(); $raw_new_value = $xaction->getNewValue();
@ -136,6 +140,9 @@ final class PonderQuestionEditor
case PonderQuestionTransaction::TYPE_STATUS: case PonderQuestionTransaction::TYPE_STATUS:
$object->setStatus($xaction->getNewValue()); $object->setStatus($xaction->getNewValue());
break; break;
case PonderQuestionTransaction::TYPE_ANSWERWIKI:
$object->setAnswerWiki($xaction->getNewValue());
break;
case PonderQuestionTransaction::TYPE_ANSWERS: case PonderQuestionTransaction::TYPE_ANSWERS:
$old = $xaction->getOldValue(); $old = $xaction->getOldValue();
$new = $xaction->getNewValue(); $new = $xaction->getNewValue();
@ -167,6 +174,7 @@ final class PonderQuestionEditor
case PonderQuestionTransaction::TYPE_TITLE: case PonderQuestionTransaction::TYPE_TITLE:
case PonderQuestionTransaction::TYPE_CONTENT: case PonderQuestionTransaction::TYPE_CONTENT:
case PonderQuestionTransaction::TYPE_STATUS: case PonderQuestionTransaction::TYPE_STATUS:
case PonderQuestionTransaction::TYPE_ANSWERWIKI:
return $v; return $v;
} }

View file

@ -20,6 +20,7 @@ final class PonderQuestion extends PonderDAO
protected $authorPHID; protected $authorPHID;
protected $status; protected $status;
protected $content; protected $content;
protected $answerWiki;
protected $contentSource; protected $contentSource;
protected $viewPolicy; protected $viewPolicy;
protected $spacePHID; protected $spacePHID;
@ -56,6 +57,7 @@ final class PonderQuestion extends PonderDAO
'title' => 'text255', 'title' => 'text255',
'status' => 'text32', 'status' => 'text32',
'content' => 'text', 'content' => 'text',
'answerWiki' => 'text',
'answerCount' => 'uint32', 'answerCount' => 'uint32',
'mailKey' => 'bytes20', 'mailKey' => 'bytes20',

View file

@ -7,6 +7,7 @@ final class PonderQuestionTransaction
const TYPE_CONTENT = 'ponder.question:content'; const TYPE_CONTENT = 'ponder.question:content';
const TYPE_ANSWERS = 'ponder.question:answer'; const TYPE_ANSWERS = 'ponder.question:answer';
const TYPE_STATUS = 'ponder.question:status'; const TYPE_STATUS = 'ponder.question:status';
const TYPE_ANSWERWIKI = 'ponder.question:wiki';
const MAILTAG_DETAILS = 'question:details'; const MAILTAG_DETAILS = 'question:details';
const MAILTAG_COMMENT = 'question:comment'; const MAILTAG_COMMENT = 'question:comment';
@ -78,6 +79,10 @@ final class PonderQuestionTransaction
return pht( return pht(
'%s edited the question description.', '%s edited the question description.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
case self::TYPE_ANSWERWIKI:
return pht(
'%s edited the question answer wiki.',
$this->renderHandleLink($author_phid));
case self::TYPE_ANSWERS: case self::TYPE_ANSWERS:
$answer_handle = $this->getHandle($this->getNewAnswerPHID()); $answer_handle = $this->getHandle($this->getNewAnswerPHID());
$question_handle = $this->getHandle($object_phid); $question_handle = $this->getHandle($object_phid);
@ -100,9 +105,9 @@ final class PonderQuestionTransaction
return pht( return pht(
'%s closed this question as obsolete.', '%s closed this question as obsolete.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
case PonderQuestionStatus::STATUS_CLOSED_DUPLICATE: case PonderQuestionStatus::STATUS_CLOSED_INVALID:
return pht( return pht(
'%s closed this question as a duplicate.', '%s closed this question as invalid.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
} }
} }
@ -120,6 +125,7 @@ final class PonderQuestionTransaction
case self::TYPE_TITLE: case self::TYPE_TITLE:
case self::TYPE_CONTENT: case self::TYPE_CONTENT:
case self::TYPE_STATUS: case self::TYPE_STATUS:
case self::TYPE_ANSWERWIKI:
$tags[] = self::MAILTAG_DETAILS; $tags[] = self::MAILTAG_DETAILS;
break; break;
case self::TYPE_ANSWERS: case self::TYPE_ANSWERS:
@ -139,6 +145,7 @@ final class PonderQuestionTransaction
switch ($this->getTransactionType()) { switch ($this->getTransactionType()) {
case self::TYPE_TITLE: case self::TYPE_TITLE:
case self::TYPE_CONTENT: case self::TYPE_CONTENT:
case self::TYPE_ANSWERWIKI:
return 'fa-pencil'; return 'fa-pencil';
case self::TYPE_STATUS: case self::TYPE_STATUS:
return PonderQuestionStatus::getQuestionStatusIcon($new); return PonderQuestionStatus::getQuestionStatusIcon($new);
@ -156,6 +163,7 @@ final class PonderQuestionTransaction
switch ($this->getTransactionType()) { switch ($this->getTransactionType()) {
case self::TYPE_TITLE: case self::TYPE_TITLE:
case self::TYPE_CONTENT: case self::TYPE_CONTENT:
case self::TYPE_ANSWERWIKI:
return PhabricatorTransactions::COLOR_BLUE; return PhabricatorTransactions::COLOR_BLUE;
case self::TYPE_ANSWERS: case self::TYPE_ANSWERS:
return PhabricatorTransactions::COLOR_GREEN; return PhabricatorTransactions::COLOR_GREEN;
@ -167,6 +175,7 @@ final class PonderQuestionTransaction
public function hasChangeDetails() { public function hasChangeDetails() {
switch ($this->getTransactionType()) { switch ($this->getTransactionType()) {
case self::TYPE_CONTENT: case self::TYPE_CONTENT:
case self::TYPE_ANSWERWIKI:
return true; return true;
} }
return parent::hasChangeDetails(); return parent::hasChangeDetails();
@ -253,6 +262,11 @@ final class PonderQuestionTransaction
'%s edited the description of %s', '%s edited the description of %s',
$this->renderHandleLink($author_phid), $this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid)); $this->renderHandleLink($object_phid));
case self::TYPE_ANSWERWIKI:
return pht(
'%s edited the answer wiki for %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_ANSWERS: case self::TYPE_ANSWERS:
$answer_handle = $this->getHandle($this->getNewAnswerPHID()); $answer_handle = $this->getHandle($this->getNewAnswerPHID());
$question_handle = $this->getHandle($object_phid); $question_handle = $this->getHandle($object_phid);
@ -272,9 +286,9 @@ final class PonderQuestionTransaction
'%s closed %s as resolved.', '%s closed %s as resolved.',
$this->renderHandleLink($author_phid), $this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid)); $this->renderHandleLink($object_phid));
case PonderQuestionStatus::STATUS_CLOSED_DUPLICATE: case PonderQuestionStatus::STATUS_CLOSED_INVALID:
return pht( return pht(
'%s closed %s as duplicate.', '%s closed %s as invalid.',
$this->renderHandleLink($author_phid), $this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid)); $this->renderHandleLink($object_phid));
case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE:

View file

@ -90,6 +90,18 @@ final class PonderAddAnswerView extends AphrontView {
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->setValue(pht('Add Answer'))); ->setValue(pht('Add Answer')));
if (!$viewer->isLoggedIn()) {
$login_href = id(new PhutilURI('/auth/start/'))
->setQueryParam('next', '/Q'.$question->getID());
$form = id(new PHUIFormLayoutView())
->addClass('login-to-participate')
->appendChild(
id(new PHUIButtonView())
->setTag('a')
->setText(pht('Login to Answer'))
->setHref((string)$login_href));
}
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
->setHeader($header) ->setHeader($header)
->appendChild($form); ->appendChild($form);

View file

@ -37,7 +37,7 @@ final class PonderFooterView extends AphrontTagView {
if ($this->count == 0) { if ($this->count == 0) {
$icon = id(new PHUIIconView()) $icon = id(new PHUIIconView())
->setIconFont('fa-plus-circle msr'); ->setIconFont('fa-comments msr');
$text = pht('Add a Comment'); $text = pht('Add a Comment');
} else { } else {
$icon = id(new PHUIIconView()) $icon = id(new PHUIIconView())
@ -78,7 +78,7 @@ final class PonderFooterView extends AphrontTagView {
$actions[] = $hide_action; $actions[] = $hide_action;
$actions[] = $show_action; $actions[] = $show_action;
return array($this->actions, $actions); return array($actions, $this->actions);
} }
} }

View file

@ -624,8 +624,7 @@ final class PhabricatorProjectBoardViewController
->setMetadata( ->setMetadata(
array( array(
'columnPHID' => $column->getPHID(), 'columnPHID' => $column->getPHID(),
)) ));
->setDisabled(!$can_edit);
$batch_edit_uri = $request->getRequestURI(); $batch_edit_uri = $request->getRequestURI();
$batch_edit_uri->setQueryParam('batch', $column->getID()); $batch_edit_uri->setQueryParam('batch', $column->getID());
@ -640,15 +639,13 @@ final class PhabricatorProjectBoardViewController
->setHref($batch_edit_uri) ->setHref($batch_edit_uri)
->setDisabled(!$can_batch_edit); ->setDisabled(!$can_batch_edit);
$edit_uri = $this->getApplicationURI( $detail_uri = $this->getApplicationURI(
'board/'.$this->id.'/column/'.$column->getID().'/'); 'board/'.$this->id.'/column/'.$column->getID().'/');
$column_items[] = id(new PhabricatorActionView()) $column_items[] = id(new PhabricatorActionView())
->setIcon('fa-pencil') ->setIcon('fa-columns')
->setName(pht('Edit Column')) ->setName(pht('Column Details'))
->setHref($edit_uri) ->setHref($detail_uri);
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
$can_hide = ($can_edit && !$column->isDefaultColumn()); $can_hide = ($can_edit && !$column->isDefaultColumn());
$hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/'; $hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/';

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