1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-02 11:42: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()
{
$ret;
$ret = null;
if($this->count < $this->dataLength) {
$row = $this->count % $this->blocks;

View file

@ -7,8 +7,8 @@
*/
return array(
'names' => array(
'core.pkg.css' => 'aced76a5',
'core.pkg.js' => 'a590b451',
'core.pkg.css' => 'eb8c668d',
'core.pkg.js' => '47dc9ebb',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
'differential.pkg.js' => '813c1633',
@ -36,18 +36,18 @@ return array(
'rsrc/css/application/base/notification-menu.css' => 'f31c0bde',
'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601',
'rsrc/css/application/base/phui-theme.css' => '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/chatlog/chatlog.css' => 'd295b020',
'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-welcome.css' => '6abd79be',
'rsrc/css/application/config/setup-issue.css' => 'db7e9c40',
'rsrc/css/application/config/unhandled-exception.css' => '4c96257a',
'rsrc/css/application/conpherence/durable-column.css' => '86396117',
'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/transaction.css' => '85d0974c',
'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-transaction-detail.css' => '82100a43',
'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/releeph/releeph-core.css' => '9b3c5733',
'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/uiexample/example.css' => '528b19de',
'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/z-index.css' => '57ddcaa2',
'rsrc/css/diviner/diviner-shared.css' => '5a337049',
'rsrc/css/font/font-awesome.css' => 'd2fc4e8d',
'rsrc/css/font/font-lato.css' => '5ab1a46a',
'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-hovercard-view.css' => '1239cd52',
'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-list.css' => '125599df',
'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-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-spacing.css' => '042804d6',
'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__/ViewRenderer.js' => '6ea96ac9',
'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/JSON.js' => '69adf288',
'rsrc/externals/javelin/lib/Leader.js' => '331b1611',
@ -505,11 +505,11 @@ return array(
'calendar-icon-css' => 'c69aa59f',
'changeset-view-manager' => '58562350',
'conduit-api-css' => '7bc725c4',
'config-options-css' => '7fedf08b',
'config-options-css' => '0ede4c9b',
'config-welcome-css' => '6abd79be',
'conpherence-durable-column-view' => '86396117',
'conpherence-menu-css' => 'f99fee4c',
'conpherence-message-pane-css' => 'dd4f8a3b',
'conpherence-message-pane-css' => '5897d3ac',
'conpherence-notification-css' => '6cdcc253',
'conpherence-thread-manager' => '01774ab2',
'conpherence-transaction-css' => '85d0974c',
@ -659,7 +659,7 @@ return array(
'javelin-color' => '7e41274a',
'javelin-cookie' => '62dfea03',
'javelin-diffusion-locate-file-source' => 'b42eddc7',
'javelin-dom' => '147805fa',
'javelin-dom' => '805b806a',
'javelin-dynval' => 'f6555212',
'javelin-event' => '85ea0626',
'javelin-fx' => '54b612ba',
@ -737,13 +737,13 @@ return array(
'phabricator-object-selector-css' => '85ee8ce6',
'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '6920d200',
'phabricator-remarkup-css' => '73fc4395',
'phabricator-remarkup-css' => 'ef286a6e',
'phabricator-search-results-css' => '7dea472c',
'phabricator-shaped-request' => '7cbe244b',
'phabricator-side-menu-view-css' => 'bec2458e',
'phabricator-slowvote-css' => '475b4bd2',
'phabricator-source-code-view-css' => '5e0178de',
'phabricator-standard-page-view' => '4d176b67',
'phabricator-standard-page-view' => '1f53d056',
'phabricator-textareautils' => '5c93c52c',
'phabricator-title' => 'df5e11d2',
'phabricator-tooltip' => '1d298e3a',
@ -779,7 +779,7 @@ return array(
'phui-crumbs-view-css' => 'd842f867',
'phui-document-view-css' => '0267054b',
'phui-feed-story-css' => 'b7b26d23',
'phui-font-icon-base-css' => '3dad2ae3',
'phui-font-icon-base-css' => 'ecbbb4c2',
'phui-fontkit-css' => 'cb8ae7ad',
'phui-form-css' => 'afdb2c6e',
'phui-form-view-css' => '621b21c5',
@ -791,10 +791,10 @@ return array(
'phui-inline-comment-view-css' => '0fdb3667',
'phui-list-view-css' => '125599df',
'phui-object-box-css' => '407eaf5a',
'phui-object-item-list-view-css' => '36ce366c',
'phui-object-item-list-view-css' => 'ab1bf393',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
'phui-property-list-view-css' => '15bbe0b0',
'phui-property-list-view-css' => '03904f6b',
'phui-remarkup-preview-css' => '867f85b3',
'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8',
@ -811,7 +811,7 @@ return array(
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
'policy-transaction-detail-css' => '82100a43',
'ponder-view-css' => 'bef48f86',
'ponder-view-css' => '7b0df4da',
'project-icon-css' => '4e3eaa5a',
'raphael-core' => '51ee6b43',
'raphael-g' => '40dde778',
@ -918,13 +918,6 @@ return array(
'javelin-uri',
'phabricator-textareautils',
),
'147805fa' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
'1499a8cb' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1451,6 +1444,13 @@ return array(
'javelin-behavior',
'javelin-history',
),
'805b806a' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
82439934 => array(
'javelin-behavior',
'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',
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/AphrontKeyboardShortcutsAvailableView.php',
'AphrontListFilterView' => 'view/layout/AphrontListFilterView.php',
'AphrontMalformedRequestException' => 'aphront/exception/AphrontMalformedRequestException.php',
'AphrontMoreView' => 'view/layout/AphrontMoreView.php',
'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php',
'AphrontMySQLDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontMySQLDatabaseConnectionTestCase.php',
@ -156,8 +157,12 @@ phutil_register_library_map(array(
'AphrontRedirectResponseTestCase' => 'aphront/response/__tests__/AphrontRedirectResponseTestCase.php',
'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php',
'AphrontRequest' => 'aphront/AphrontRequest.php',
'AphrontRequestExceptionHandler' => 'aphront/handler/AphrontRequestExceptionHandler.php',
'AphrontRequestTestCase' => 'aphront/__tests__/AphrontRequestTestCase.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',
'AphrontSite' => 'aphront/site/AphrontSite.php',
'AphrontStackTraceView' => 'view/widget/AphrontStackTraceView.php',
@ -166,9 +171,7 @@ phutil_register_library_map(array(
'AphrontTagView' => 'view/AphrontTagView.php',
'AphrontTokenizerTemplateView' => 'view/control/AphrontTokenizerTemplateView.php',
'AphrontTypeaheadTemplateView' => 'view/control/AphrontTypeaheadTemplateView.php',
'AphrontURIMapper' => 'aphront/AphrontURIMapper.php',
'AphrontUnhandledExceptionResponse' => 'aphront/response/AphrontUnhandledExceptionResponse.php',
'AphrontUsageException' => 'aphront/exception/AphrontUsageException.php',
'AphrontView' => 'view/AphrontView.php',
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
'ArcanistConduitAPIMethod' => 'applications/arcanist/conduit/ArcanistConduitAPIMethod.php',
@ -495,6 +498,7 @@ phutil_register_library_map(array(
'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php',
'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php',
'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php',
'DiffusionAuditorFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorFunctionDatasource.php',
'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php',
'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php',
'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php',
@ -1481,6 +1485,7 @@ phutil_register_library_map(array(
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php',
'PhabricatorAdministratorsPolicyRule' => 'applications/policy/rule/PhabricatorAdministratorsPolicyRule.php',
'PhabricatorAjaxRequestExceptionHandler' => 'aphront/handler/PhabricatorAjaxRequestExceptionHandler.php',
'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php',
'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php',
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
@ -1608,6 +1613,7 @@ phutil_register_library_map(array(
'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php',
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php',
'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php',
'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php',
'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php',
@ -1783,6 +1789,7 @@ phutil_register_library_map(array(
'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php',
'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php',
'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php',
'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php',
'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php',
@ -1835,6 +1842,7 @@ phutil_register_library_map(array(
'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php',
'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php',
'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php',
'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php',
'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php',
'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php',
@ -1990,6 +1998,7 @@ phutil_register_library_map(array(
'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php',
'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php',
'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php',
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php',
@ -2180,6 +2189,7 @@ phutil_register_library_map(array(
'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php',
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php',
'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php',
@ -2435,6 +2445,8 @@ phutil_register_library_map(array(
'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php',
'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.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',
'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php',
'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php',
@ -2575,6 +2587,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php',
'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php',
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
@ -2631,6 +2644,7 @@ phutil_register_library_map(array(
'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php',
'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php',
'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php',
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php',
@ -2650,6 +2664,7 @@ phutil_register_library_map(array(
'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php',
'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php',
'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php',
'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
'PhabricatorProjectsPolicyRule' => 'applications/policy/rule/PhabricatorProjectsPolicyRule.php',
@ -2660,6 +2675,7 @@ phutil_register_library_map(array(
'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php',
'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php',
'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php',
'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php',
'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php',
'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php',
'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
@ -2751,6 +2767,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php',
'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php',
'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php',
'PhabricatorRobotsController' => 'applications/system/controller/PhabricatorRobotsController.php',
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
@ -3126,7 +3143,6 @@ phutil_register_library_map(array(
'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php',
'PhameBlogLiveController' => 'applications/phame/controller/blog/PhameBlogLiveController.php',
'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php',
'PhameBlogResourceSite' => 'applications/phame/site/PhameBlogResourceSite.php',
'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php',
'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php',
'PhameBlogSkin' => 'applications/phame/skins/PhameBlogSkin.php',
@ -3727,7 +3743,10 @@ phutil_register_library_map(array(
'AphrontCursorPagerView' => 'AphrontView',
'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration',
'AphrontDialogResponse' => 'AphrontResponse',
'AphrontDialogView' => 'AphrontView',
'AphrontDialogView' => array(
'AphrontView',
'AphrontResponseProducerInterface',
),
'AphrontException' => 'Exception',
'AphrontFileResponse' => 'AphrontResponse',
'AphrontFormCheckboxControl' => 'AphrontFormControl',
@ -3762,6 +3781,7 @@ phutil_register_library_map(array(
'AphrontJavelinView' => 'AphrontView',
'AphrontKeyboardShortcutsAvailableView' => 'AphrontView',
'AphrontListFilterView' => 'AphrontView',
'AphrontMalformedRequestException' => 'AphrontException',
'AphrontMoreView' => 'AphrontView',
'AphrontMultiColumnView' => 'AphrontView',
'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase',
@ -3770,13 +3790,19 @@ phutil_register_library_map(array(
'AphrontPageView' => 'AphrontView',
'AphrontPlainTextResponse' => 'AphrontResponse',
'AphrontProgressBarView' => 'AphrontBarView',
'AphrontProxyResponse' => 'AphrontResponse',
'AphrontProxyResponse' => array(
'AphrontResponse',
'AphrontResponseProducerInterface',
),
'AphrontRedirectResponse' => 'AphrontResponse',
'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase',
'AphrontReloadResponse' => 'AphrontRedirectResponse',
'AphrontRequest' => 'Phobject',
'AphrontRequestExceptionHandler' => 'Phobject',
'AphrontRequestTestCase' => 'PhabricatorTestCase',
'AphrontResponse' => 'Phobject',
'AphrontRoutingMap' => 'Phobject',
'AphrontRoutingResult' => 'Phobject',
'AphrontSideNavFilterView' => 'AphrontView',
'AphrontSite' => 'Phobject',
'AphrontStackTraceView' => 'AphrontView',
@ -3785,9 +3811,7 @@ phutil_register_library_map(array(
'AphrontTagView' => 'AphrontView',
'AphrontTokenizerTemplateView' => 'AphrontView',
'AphrontTypeaheadTemplateView' => 'AphrontView',
'AphrontURIMapper' => 'Phobject',
'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse',
'AphrontUsageException' => 'AphrontException',
'AphrontView' => array(
'Phobject',
'PhutilSafeHTMLProducerInterface',
@ -4157,6 +4181,7 @@ phutil_register_library_map(array(
'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialViewPolicyField' => 'DifferentialCoreCustomField',
'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionAuditorFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction',
'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction',
'DiffusionAuditorsHeraldAction' => 'HeraldAction',
@ -4369,7 +4394,7 @@ phutil_register_library_map(array(
'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionServeController' => 'DiffusionController',
'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
'DiffusionSetupException' => 'AphrontUsageException',
'DiffusionSetupException' => 'Exception',
'DiffusionSubversionSSHWorkflow' => 'DiffusionSSHWorkflow',
'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow',
'DiffusionSubversionWireProtocol' => 'Phobject',
@ -5289,6 +5314,7 @@ phutil_register_library_map(array(
'PhabricatorActionView' => 'AphrontView',
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorAjaxRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorAlmanacApplication' => 'PhabricatorApplication',
'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorAnchorView' => 'AphrontView',
@ -5433,6 +5459,7 @@ phutil_register_library_map(array(
'PhabricatorAuthLinkController' => 'PhabricatorAuthController',
'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
'PhabricatorAuthLoginHandler' => 'Phobject',
'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow',
@ -5653,6 +5680,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
),
'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitTestCase' => 'PhabricatorTestCase',
'PhabricatorConduitToken' => array(
@ -5715,6 +5743,7 @@ phutil_register_library_map(array(
'PhabricatorConfigOptionType' => 'Phobject',
'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule',
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule',
'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse',
'PhabricatorConfigSchemaQuery' => 'Phobject',
'PhabricatorConfigSchemaSpec' => 'Phobject',
@ -5899,6 +5928,7 @@ phutil_register_library_map(array(
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDebugController' => 'PhabricatorController',
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDestructionEngine' => 'Phobject',
'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
@ -6124,6 +6154,7 @@ phutil_register_library_map(array(
'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
'PhabricatorHeraldApplication' => 'PhabricatorApplication',
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorHomeApplication' => 'PhabricatorApplication',
'PhabricatorHomeController' => 'PhabricatorController',
'PhabricatorHomeMainController' => 'PhabricatorHomeController',
@ -6347,7 +6378,7 @@ phutil_register_library_map(array(
'PhabricatorOAuthServer' => 'Phobject',
'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO',
'PhabricatorOAuthServerApplication' => 'PhabricatorApplication',
'PhabricatorOAuthServerAuthController' => 'PhabricatorAuthController',
'PhabricatorOAuthServerAuthController' => 'PhabricatorOAuthServerController',
'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO',
'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorOAuthServerClient' => array(
@ -6365,7 +6396,7 @@ phutil_register_library_map(array(
'PhabricatorOAuthServerScope' => 'Phobject',
'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase',
'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController',
'PhabricatorOAuthServerTokenController' => 'PhabricatorAuthController',
'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController',
'PhabricatorObjectHandle' => array(
'Phobject',
'PhabricatorPolicyInterface',
@ -6406,6 +6437,8 @@ phutil_register_library_map(array(
'PhabricatorApplicationTransactionInterface',
),
'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType',
'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
@ -6571,6 +6604,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorPolicyRule' => 'Phobject',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => array(
@ -6649,6 +6683,7 @@ phutil_register_library_map(array(
'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType',
@ -6671,6 +6706,7 @@ phutil_register_library_map(array(
'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectViewController' => 'PhabricatorProjectController',
'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule',
@ -6684,6 +6720,7 @@ phutil_register_library_map(array(
'Phobject',
'Iterator',
),
'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType',
'PhabricatorRedirectController' => 'PhabricatorController',
@ -6810,6 +6847,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryVersion' => 'Phobject',
'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler',
'PhabricatorResourceSite' => 'PhabricatorSite',
'PhabricatorRobotsController' => 'PhabricatorController',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
@ -7238,7 +7276,6 @@ phutil_register_library_map(array(
'PhameBlogListController' => 'PhameController',
'PhameBlogLiveController' => 'PhameController',
'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhameBlogResourceSite' => 'PhameSite',
'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhameBlogSite' => 'PhameSite',
'PhameBlogSkin' => 'PhabricatorController',

View file

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

View file

@ -26,6 +26,8 @@ final class AphrontRequest extends Phobject {
private $requestData;
private $user;
private $applicationConfiguration;
private $site;
private $controller;
private $uriData;
private $cookiePrefix;
@ -77,6 +79,24 @@ final class AphrontRequest extends Phobject {
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 )--------------------------------------------- */

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

@ -2,6 +2,8 @@
/**
* @task routing URI Routing
* @task response Response Handling
* @task exception Exception Handling
*/
abstract class AphrontApplicationConfiguration extends Phobject {
@ -10,7 +12,6 @@ abstract class AphrontApplicationConfiguration extends Phobject {
private $path;
private $console;
abstract public function getApplicationName();
abstract public function buildRequest();
abstract public function build404Controller();
abstract public function buildRedirectController($uri, $external);
@ -210,7 +211,9 @@ abstract class AphrontApplicationConfiguration extends Phobject {
));
$multimeter->setEventContext('web.'.$controller_class);
$request->setController($controller);
$request->setURIMap($uri_data);
$controller->setRequest($request);
// If execution throws an exception and then trying to render that
@ -232,6 +235,7 @@ abstract class AphrontApplicationConfiguration extends Phobject {
if (!$response) {
$controller->willProcessRequest($uri_data);
$response = $controller->handleRequest($request);
$this->validateControllerResponse($controller, $response);
}
} catch (Exception $ex) {
$original_exception = $ex;
@ -239,8 +243,8 @@ abstract class AphrontApplicationConfiguration extends Phobject {
}
try {
$response = $controller->didProcessRequest($response);
$response = $this->willSendResponse($response, $controller);
$response = $this->produceResponse($request, $response);
$response = $controller->willSendResponse($response);
$response->setRequest($request);
$unexpected_output = PhabricatorStartup::endOutputCapture();
@ -283,35 +287,13 @@ abstract class AphrontApplicationConfiguration extends Phobject {
/**
* Using builtin and application routes, build the appropriate
* @{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}.
* Build a controller to respond to the request.
*
* @return pair<AphrontController,dict> Controller and dictionary of request
* parameters.
* @task routing
*/
final public function buildController() {
final private function buildController() {
$request = $this->getRequest();
// 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
// test) so it's fine that we don't have SERVER_ADDR defined.
} else {
throw new AphrontUsageException(
throw new AphrontMalformedRequestException(
pht('No %s', 'SERVER_ADDR'),
pht(
'Phabricator is configured to operate in cluster mode, but '.
@ -348,7 +330,7 @@ abstract class AphrontApplicationConfiguration extends Phobject {
}
} else {
if (!PhabricatorEnv::isClusterAddress($server_addr)) {
throw new AphrontUsageException(
throw new AphrontMalformedRequestException(
pht('External Interface'),
pht(
'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
// do its own routing logic if it wants, but we don't need that for now.
$path = $site->getPathForRouting($request);
$maps = $site->getRoutingMaps();
$path = $request->getPath();
$result = $this->routePath($maps, $path);
if ($result) {
return $result;
}
list($controller, $uri_data) = $this->buildControllerForPath($path);
if (!$controller) {
if (!preg_match('@/$@', $path)) {
// 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()) {
// 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();
}
return array($controller, $uri_data);
}
/**
* Map a specific path to the corresponding controller. For a description
* 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
* parameters.
* @task routing
*/
final public function buildControllerForPath($path) {
$maps = array();
$applications = PhabricatorApplication::getAllInstalledApplications();
foreach ($applications as $application) {
$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;
private function routePath(array $maps, $path) {
foreach ($maps as $map) {
$result = $map->routePath($path);
if ($result) {
return array($result->getController(), $result->getURIData());
}
}
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) {
@ -461,15 +413,223 @@ abstract class AphrontApplicationConfiguration extends Phobject {
if (!$site) {
$path = $request->getPath();
$host = $request->getHost();
throw new Exception(
throw new AphrontMalformedRequestException(
pht('Site Not Found'),
pht(
'This request asked for "%s" on host "%s", but no site is '.
'configured which can serve this request.',
$path,
$host));
$host),
true);
}
$request->setSite($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
extends AphrontApplicationConfiguration {
public function __construct() {}
public function getApplicationName() {
return 'aphront-default';
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
@ -50,233 +44,6 @@ class AphrontDefaultApplicationConfiguration
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() {
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
* @{method:reduceProxyResponse}.
*/
abstract class AphrontProxyResponse extends AphrontResponse {
abstract class AphrontProxyResponse
extends AphrontResponse
implements AphrontResponseProducerInterface {
private $proxy;
@ -71,4 +73,12 @@ abstract class AphrontProxyResponse extends AphrontResponse {
'reduceProxyResponse()'));
}
/* -( AphrontResponseProducerInterface )----------------------------------- */
public function produceAphrontResponse() {
return $this->reduceProxyResponse();
}
}

View file

@ -6,6 +6,20 @@ final class AphrontUnhandledExceptionResponse
private $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;
return $this;
}
@ -24,7 +38,7 @@ final class AphrontUnhandledExceptionResponse
protected function getResponseTitle() {
$ex = $this->exception;
if ($ex instanceof AphrontUsageException) {
if ($ex instanceof AphrontMalformedRequestException) {
return $ex->getTitle();
} else {
return pht('Unhandled Exception');
@ -38,7 +52,7 @@ final class AphrontUnhandledExceptionResponse
protected function getResponseBody() {
$ex = $this->exception;
if ($ex instanceof AphrontUsageException) {
if ($ex instanceof AphrontMalformedRequestException) {
$title = $ex->getTitle();
} else {
$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 newSiteForRequest(AphrontRequest $request);
/**
* NOTE: This is temporary glue; eventually, sites will return an entire
* route map.
*/
public function getPathForRouting(AphrontRequest $request) {
return $request->getPath();
}
abstract public function getRoutingMaps();
protected function isHostMatch($host, array $uris) {
foreach ($uris as $uri) {
@ -32,14 +25,9 @@ abstract class AphrontSite extends Phobject {
return false;
}
protected function isPathPrefixMatch($path, array $paths) {
foreach ($paths as $candidate) {
if (strncmp($path, $candidate, strlen($candidate)) === 0) {
return true;
}
}
return false;
protected function newRoutingMap() {
return id(new AphrontRoutingMap())
->setSite($this);
}
final public static function getAllSites() {

View file

@ -37,4 +37,17 @@ final class PhabricatorPlatformSite extends PhabricatorSite {
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();
}
// 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;
}
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())
->setViewer($user)
->withAuditorPHIDs($phids)
->withNeedsAuditByPHIDs($phids)
->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN)
->withAuditAwaitingUser($user)
->setLimit(self::MAX_STATUS_ITEMS);
$commits = $query->execute();

View file

@ -11,103 +11,65 @@ final class PhabricatorCommitSearchEngine
return 'PhabricatorDiffusionApplication';
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$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())
public function newQuery() {
return id(new DiffusionCommitQuery())
->needAuditRequests(true)
->needCommitData(true);
$auditor_phids = $saved->getParameter('auditorPHIDs', array());
if ($auditor_phids) {
$query->withAuditorPHIDs($auditor_phids);
}
$commit_author_phids = $saved->getParameter('commitAuthorPHIDs', array());
if ($commit_author_phids) {
$query->withAuthorPHIDs($commit_author_phids);
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['needsAuditByPHIDs']) {
$query->withNeedsAuditByPHIDs($map['needsAuditByPHIDs']);
}
$audit_status = $saved->getParameter('auditStatus', null);
if ($audit_status) {
$query->withAuditStatus($audit_status);
if ($map['auditorPHIDs']) {
$query->withAuditorPHIDs($map['auditorPHIDs']);
}
$awaiting_user_phid = $saved->getParameter('awaitingUserPHID', null);
if ($awaiting_user_phid) {
// 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());
if ($map['commitAuthorPHIDs']) {
$query->withAuthorPHIDs($map['commitAuthorPHIDs']);
}
$repository_phids = $saved->getParameter('repositoryPHIDs', array());
if ($repository_phids) {
$query->withRepositoryPHIDs($repository_phids);
if ($map['auditStatus']) {
$query->withAuditStatus($map['auditStatus']);
}
if ($map['repositoryPHIDs']) {
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$auditor_phids = $saved->getParameter('auditorPHIDs', array());
$commit_author_phids = $saved->getParameter(
'commitAuthorPHIDs',
array());
$audit_status = $saved->getParameter('auditStatus', null);
$repository_phids = $saved->getParameter('repositoryPHIDs', array());
$form
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new DiffusionAuditorDatasource())
->setName('auditorPHIDs')
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Needs Audit By'))
->setKey('needsAuditByPHIDs')
->setAliases(array('needs', 'need'))
->setDatasource(new DiffusionAuditorFunctionDatasource()),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Auditors'))
->setValue($auditor_phids))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setName('authors')
->setLabel(pht('Commit Authors'))
->setValue($commit_author_phids))
->appendChild(
id(new AphrontFormSelectControl())
->setName('auditStatus')
->setKey('auditorPHIDs')
->setAliases(array('auditor', 'auditors'))
->setDatasource(new DiffusionAuditorFunctionDatasource()),
id(new PhabricatorUsersSearchField())
->setLabel(pht('Authors'))
->setKey('commitAuthorPHIDs')
->setAliases(array('author', 'authors')),
id(new PhabricatorSearchSelectField())
->setLabel(pht('Audit Status'))
->setOptions($this->getAuditStatusOptions())
->setValue($audit_status))
->appendControl(
id(new AphrontFormTokenizerControl())
->setKey('auditStatus')
->setAliases(array('status'))
->setOptions($this->getAuditStatusOptions()),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Repositories'))
->setName('repositoryPHIDs')
->setDatasource(new DiffusionRepositoryDatasource())
->setValue($repository_phids));
->setKey('repositoryPHIDs')
->setAliases(array('repository', 'repositories'))
->setDatasource(new DiffusionRepositoryDatasource()),
);
}
protected function getURI($path) {
@ -118,7 +80,7 @@ final class PhabricatorCommitSearchEngine
$names = array();
if ($this->requireViewer()->isLoggedIn()) {
$names['need'] = pht('Need Attention');
$names['need'] = pht('Needs Audit');
$names['problem'] = pht('Problem Commits');
}
@ -138,22 +100,24 @@ final class PhabricatorCommitSearchEngine
$query->setQueryKey($query_key);
$viewer = $this->requireViewer();
$viewer_phid = $viewer->getPHID();
$status_open = DiffusionCommitQuery::AUDIT_STATUS_OPEN;
switch ($query_key) {
case 'all':
return $query;
case 'open':
$query->setParameter(
'auditStatus',
DiffusionCommitQuery::AUDIT_STATUS_OPEN);
$query->setParameter('auditStatus', $status_open);
return $query;
case 'need':
$query->setParameter('awaitingUserPHID', $viewer->getPHID());
$query->setParameter(
'auditStatus',
DiffusionCommitQuery::AUDIT_STATUS_OPEN);
$query->setParameter(
'auditorPHIDs',
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer));
$needs_tokens = array(
$viewer_phid,
'projects('.$viewer_phid.')',
'packages('.$viewer_phid.')',
);
$query->setParameter('needsAuditByPHIDs', $needs_tokens);
$query->setParameter('auditStatus', $status_open);
return $query;
case 'authored':
$query->setParameter('commitAuthorPHIDs', array($viewer->getPHID()));

View file

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

View file

@ -163,8 +163,22 @@ final class PhabricatorAuthStartController
$button_columns);
}
$login_message = PhabricatorEnv::getEnvConfig('auth.login-message');
$login_message = phutil_safe_html($login_message);
$handlers = PhabricatorAuthLoginHandler::getAllHandlers();
$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;
if ($invite) {
@ -178,7 +192,7 @@ final class PhabricatorAuthStartController
return $this->buildApplicationPage(
array(
$crumbs,
$login_message,
$header,
$invite_message,
$out,
),

View file

@ -21,7 +21,10 @@ final class PhabricatorAuthTerminateSessionController
$sessions = $query->execute();
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.
unset($sessions[$key]);
}

View file

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

View file

@ -201,7 +201,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
// case the server or client has some clock skew.
for ($offset = -2; $offset <= 2; $offset++) {
$real = self::getTOTPCode($key, $now + $offset);
if ($real === $code) {
if (phutil_hashes_are_identical($real, $code)) {
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.'));
}
if ($actual !== $expect) {
if (!phutil_hashes_are_identical($actual, $expect)) {
throw new Exception(
pht(
'The authentication provider did not return the correct client '.

View file

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

View file

@ -370,28 +370,8 @@ abstract class PhabricatorController extends AphrontController {
return $this->buildPageResponse($page);
}
public function didProcessRequest($response) {
// If a bare DialogView is returned, wrap it in a DialogResponse.
if ($response instanceof AphrontDialogView) {
$response = id(new AphrontDialogResponse())->setDialog($response);
}
public function willSendResponse(AphrontResponse $response) {
$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 (!$request->isAjax() && !$request->isQuicksand()) {

View file

@ -15,6 +15,17 @@ final class PhabricatorCelerityApplication extends PhabricatorApplication {
}
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 = array_keys($extensions);
$extensions = implode('|', $extensions);

View file

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

View file

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

View file

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

View file

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

View file

@ -41,7 +41,7 @@ final class PhabricatorConfigPHIDModule extends PhabricatorConfigModule {
return id(new PHUIObjectBoxView())
->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())
->setHeaderText(pht('Sites'))
->appendChild($table);
->setTable($table);
}
}

View file

@ -73,14 +73,6 @@ final class PhabricatorAuthenticationConfigOptions
->addExample(
"yourcompany.com\nmail.yourcompany.com",
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)
->setBoolOptions(
array(

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,6 @@ final class DifferentialLintStatus extends Phobject {
const LINT_WARN = 2;
const LINT_FAIL = 3;
const LINT_SKIP = 4;
const LINT_POSTPONED = 5;
const LINT_AUTO_SKIP = 6;
}

View file

@ -7,7 +7,6 @@ final class DifferentialUnitStatus extends Phobject {
const UNIT_WARN = 2;
const UNIT_FAIL = 3;
const UNIT_SKIP = 4;
const UNIT_POSTPONED = 5;
const UNIT_AUTO_SKIP = 6;
}

View file

@ -7,6 +7,5 @@ final class DifferentialUnitTestResult extends Phobject {
const RESULT_SKIP = 'skip';
const RESULT_BROKEN = 'broken';
const RESULT_UNSOUND = 'unsound';
const RESULT_POSTPONED = 'postponed';
}

View file

@ -73,9 +73,6 @@ final class DifferentialLintField
if ($status == DifferentialLintStatus::LINT_SKIP) {
$warnings[] = pht(
'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 {
$warnings[] = pht('These changes have lint problems.');
}
@ -94,7 +91,6 @@ final class DifferentialLintField
DifferentialLintStatus::LINT_FAIL => 'red',
DifferentialLintStatus::LINT_SKIP => 'blue',
DifferentialLintStatus::LINT_AUTO_SKIP => 'blue',
DifferentialLintStatus::LINT_POSTPONED => 'blue',
);
$icon_color = idx($colors, $diff->getLintStatus(), 'grey');

View file

@ -84,9 +84,6 @@ final class DifferentialUnitField
// Don't show any warnings.
} else if ($status == DifferentialUnitStatus::UNIT_AUTO_SKIP) {
// 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) {
$warnings[] = pht(
'Unit tests were skipped when generating these changes.');
@ -108,7 +105,6 @@ final class DifferentialUnitField
DifferentialUnitStatus::UNIT_FAIL => 'red',
DifferentialUnitStatus::UNIT_SKIP => 'blue',
DifferentialUnitStatus::UNIT_AUTO_SKIP => 'blue',
DifferentialUnitStatus::UNIT_POSTPONED => 'blue',
);
$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_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);
@ -339,7 +338,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
DifferentialUnitStatus::UNIT_FAIL => self::STAR_FAIL,
DifferentialUnitStatus::UNIT_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);
@ -361,8 +359,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
return pht('Lint Skipped');
case DifferentialLintStatus::LINT_AUTO_SKIP:
return pht('Automatic diff as part of commit; lint not applicable.');
case DifferentialLintStatus::LINT_POSTPONED:
return pht('Lint Postponed');
}
return pht('Unknown');
}
@ -382,8 +378,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
case DifferentialUnitStatus::UNIT_AUTO_SKIP:
return pht(
'Automatic diff as part of commit; unit tests not applicable.');
case DifferentialUnitStatus::UNIT_POSTPONED:
return pht('Unit Tests Postponed');
}
return pht('Unknown');
}

View file

@ -302,7 +302,16 @@ final class DiffusionRepositoryController extends DiffusionController {
$info = null;
$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(
pht(
'The ref "%s" is ambiguous in this repository.',

View file

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

View file

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

View file

@ -15,7 +15,7 @@ final class DiffusionCommitQuery
private $needAuditRequests;
private $auditIDs;
private $auditorPHIDs;
private $auditAwaitingUser;
private $needsAuditByPHIDs;
private $auditStatus;
private $epochMin;
private $epochMax;
@ -103,27 +103,6 @@ final class DiffusionCommitQuery
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) {
$this->auditIDs = $ids;
return $this;
@ -134,8 +113,8 @@ final class DiffusionCommitQuery
return $this;
}
public function withAuditAwaitingUser(PhabricatorUser $user) {
$this->auditAwaitingUser = $user;
public function withNeedsAuditByPHIDs(array $needs_phids) {
$this->needsAuditByPHIDs = $needs_phids;
return $this;
}
@ -175,21 +154,12 @@ final class DiffusionCommitQuery
}
}
public function newResultObject() {
return new PhabricatorRepositoryCommit();
}
protected function loadPage() {
$table = new PhabricatorRepositoryCommit();
$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);
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $commits) {
@ -296,8 +266,8 @@ final class DiffusionCommitQuery
return $commits;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->repositoryPHIDs !== null) {
$map_repositories = id(new PhabricatorRepositoryQuery())
@ -317,42 +287,42 @@ final class DiffusionCommitQuery
if ($this->ids !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'commit.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'commit.phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'commit.repositoryID IN (%Ld)',
$this->repositoryIDs);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'commit.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->epochMin !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'commit.epoch >= %d',
$this->epochMin);
}
if ($this->epochMax !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'commit.epoch <= %d',
$this->epochMax);
}
@ -360,13 +330,13 @@ final class DiffusionCommitQuery
if ($this->importing !== null) {
if ($this->importing) {
$where[] = qsprintf(
$conn_r,
$conn,
'(commit.importStatus & %d) != %d',
PhabricatorRepositoryCommit::IMPORTED_ALL,
PhabricatorRepositoryCommit::IMPORTED_ALL);
} else {
$where[] = qsprintf(
$conn_r,
$conn,
'(commit.importStatus & %d) = %d',
PhabricatorRepositoryCommit::IMPORTED_ALL,
PhabricatorRepositoryCommit::IMPORTED_ALL);
@ -409,7 +379,7 @@ final class DiffusionCommitQuery
foreach ($bare as $identifier) {
$sql[] = qsprintf(
$conn_r,
$conn,
'(commit.commitIdentifier LIKE %> AND '.
'LENGTH(commit.commitIdentifier) = 40)',
$identifier);
@ -437,7 +407,7 @@ final class DiffusionCommitQuery
continue;
}
$sql[] = qsprintf(
$conn_r,
$conn,
'(commit.repositoryID = %d AND commit.commitIdentifier = %s)',
$repo->getID(),
// NOTE: Because the 'commitIdentifier' column is a string, MySQL
@ -449,7 +419,7 @@ final class DiffusionCommitQuery
continue;
}
$sql[] = qsprintf(
$conn_r,
$conn,
'(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)',
$repo->getID(),
$ref['identifier']);
@ -470,28 +440,23 @@ final class DiffusionCommitQuery
if ($this->auditIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'audit.id IN (%Ld)',
$this->auditIDs);
}
if ($this->auditorPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'audit.auditorPHID IN (%Ls)',
$this->auditorPHIDs);
}
if ($this->auditAwaitingUser) {
$awaiting_user_phid = $this->auditAwaitingUser->getPHID();
// Exclude package and project audits associated with commits where
// the user is the author.
if ($this->needsAuditByPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'(commit.authorPHID IS NULL OR commit.authorPHID != %s)
OR (audit.auditorPHID = %s)',
$awaiting_user_phid,
$awaiting_user_phid);
$conn,
'needs.auditorPHID IN (%Ls)',
$this->needsAuditByPHIDs);
}
$status = $this->auditStatus;
@ -499,33 +464,27 @@ final class DiffusionCommitQuery
switch ($status) {
case self::AUDIT_STATUS_PARTIAL:
$where[] = qsprintf(
$conn_r,
$conn,
'commit.auditStatus = %d',
PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED);
break;
case self::AUDIT_STATUS_ACCEPTED:
$where[] = qsprintf(
$conn_r,
$conn,
'commit.auditStatus = %d',
PhabricatorAuditCommitStatusConstants::FULLY_AUDITED);
break;
case self::AUDIT_STATUS_CONCERN:
$where[] = qsprintf(
$conn_r,
'audit.auditStatus = %s',
$conn,
'status.auditStatus = %s',
PhabricatorAuditStatusConstants::CONCERNED);
break;
case self::AUDIT_STATUS_OPEN:
$where[] = qsprintf(
$conn_r,
'audit.auditStatus in (%Ls)',
$conn,
'status.auditStatus in (%Ls)',
PhabricatorAuditStatusConstants::getOpenStatusConstants());
if ($this->auditAwaitingUser) {
$where[] = qsprintf(
$conn_r,
'awaiting.auditStatus IS NULL OR awaiting.auditStatus != %s',
PhabricatorAuditStatusConstants::RESIGNED);
}
break;
case self::AUDIT_STATUS_ANY:
break;
@ -545,9 +504,7 @@ final class DiffusionCommitQuery
}
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
return $where;
}
protected function didFilterResults(array $filtered) {
@ -560,56 +517,113 @@ final class DiffusionCommitQuery
}
}
protected function buildJoinClause(AphrontDatabaseConnection $conn_r) {
$joins = array();
private function shouldJoinStatus() {
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();
if ($this->shouldJoinAudits()) {
$joins[] = qsprintf(
$conn_r,
'%Q %T audit ON commit.phid = audit.commitPHID',
($this->rowsMustHaveAudits() ? 'JOIN' : 'LEFT JOIN'),
if ($this->shouldJoinStatus()) {
$join[] = qsprintf(
$conn,
'LEFT JOIN %T status ON commit.phid = status.commitPHID',
$audit_request->getTableName());
}
if ($this->auditAwaitingUser) {
// Join the request table on the awaiting user's requests, so we can
// filter out package and project requests which the user has resigned
// from.
$joins[] = qsprintf(
$conn_r,
'LEFT JOIN %T awaiting ON audit.commitPHID = awaiting.commitPHID AND
awaiting.auditorPHID = %s',
if ($this->shouldJoinAudits()) {
$join[] = qsprintf(
$conn,
'JOIN %T audit ON commit.phid = audit.commitPHID',
$audit_request->getTableName());
}
if ($this->shouldJoinNeeds()) {
$join[] = qsprintf(
$conn,
'JOIN %T needs ON commit.phid = needs.commitPHID
AND needs.auditStatus IN (%Ls)',
$audit_request->getTableName(),
$this->auditAwaitingUser->getPHID());
array(
PhabricatorAuditStatusConstants::AUDIT_REQUESTED,
PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
));
}
if ($joins) {
return implode(' ', $joins);
} else {
return '';
}
return $join;
}
protected function buildGroupClause(AphrontDatabaseConnection $conn_r) {
$should_group = $this->shouldJoinAudits();
// 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;
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinStatus()) {
return true;
}
if ($should_group) {
return 'GROUP BY commit.id';
} else {
return '';
if ($this->shouldJoinAudits()) {
return true;
}
if ($this->shouldJoinNeeds()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
public function getQueryApplicationClass() {
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,6 +78,23 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController',
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorFileEditController',
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
'proxy/' => 'PhabricatorFileProxyController',
'transforms/(?P<id>[1-9]\d*)/' =>
'PhabricatorFileTransformListController',
'uploaddialog/' => 'PhabricatorFileUploadDialogController',
'download/(?P<phid>[^/]+)/' => 'PhabricatorFileDialogController',
) + $this->getResourceSubroutes(),
);
}
public function getResourceRoutes() {
return array(
'/file/' => $this->getResourceSubroutes(),
);
}
private function getResourceSubroutes() {
return array(
'data/'.
'(?:@(?P<instance>[^/]+)/)?'.
'(?P<key>[^/]+)/'.
@ -85,18 +102,12 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
'(?:(?P<token>[^/]+)/)?'.
'.*'
=> 'PhabricatorFileDataController',
'proxy/' => 'PhabricatorFileProxyController',
'xform/'.
'(?:@(?P<instance>[^/]+)/)?'.
'(?P<transform>[^/]+)/'.
'(?P<phid>[^/]+)/'.
'(?P<key>[^/]+)/'
=> 'PhabricatorFileTransformController',
'transforms/(?P<id>[1-9]\d*)/' =>
'PhabricatorFileTransformListController',
'uploaddialog/' => 'PhabricatorFileUploadDialogController',
'download/(?P<phid>[^/]+)/' => 'PhabricatorFileDialogController',
),
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,9 +13,11 @@ final class PhabricatorMetaMTAMailgunReceiveController
$timestamp = $request->getStr('timestamp');
$token = $request->getStr('token');
$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() {
// No CSRF for Mailgun.

View file

@ -1,15 +1,10 @@
<?php
final class PhabricatorOAuthServerAuthController
extends PhabricatorAuthController {
extends PhabricatorOAuthServerController {
public function shouldRequireLogin() {
return true;
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$server = new PhabricatorOAuthServer();
$client_phid = $request->getStr('client_id');
@ -30,6 +25,19 @@ final class PhabricatorOAuthServerAuthController
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);
$is_authorized = false;
$authorization = null;
@ -39,24 +47,6 @@ final class PhabricatorOAuthServerAuthController
// one giant try / catch around all the exciting database stuff so we
// can return a 'server_error' response if something goes wrong!
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) {
return $this->buildErrorResponse(
'invalid_request',
@ -211,6 +201,7 @@ final class PhabricatorOAuthServerAuthController
} else {
$desired_scopes = $scope;
}
if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) {
return $this->buildErrorResponse(
'invalid_scope',
@ -236,8 +227,8 @@ final class PhabricatorOAuthServerAuthController
'error_description' => $cancel_msg,
));
$dialog = id(new AphrontDialogView())
->setUser($viewer)
return $this->newDialog()
->setShortTitle(pht('Authorize Access'))
->setTitle(pht('Authorize "%s"?', $name))
->setSubmitURI($request->getRequestURI()->getPath())
->setWidth(AphrontDialogView::WIDTH_FORM)
@ -250,23 +241,18 @@ final class PhabricatorOAuthServerAuthController
->appendChild($form->buildLayoutView())
->addSubmitButton(pht('Authorize Access'))
->addCancelButton((string)$cancel_uri, pht('Do Not Authorize'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function buildErrorResponse($code, $title, $message) {
$viewer = $this->getRequest()->getUser();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
return $this->newDialog()
->setTitle(pht('OAuth: %s', $title))
->appendParagraph($message)
->appendParagraph(
pht('OAuth Error Code: %s', phutil_tag('tt', array(), $code)))
->addCancelButton('/', pht('Alas!'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}

View file

@ -3,58 +3,13 @@
abstract class PhabricatorOAuthServerController
extends PhabricatorController {
public function buildStandardPageResponse($view, array $data) {
$user = $this->getRequest()->getUser();
$page = $this->buildStandardPageView();
$page->setApplicationName(pht('OAuth Server'));
$page->setBaseURI('/oauthserver/');
$page->setTitle(idx($data, 'title'));
const CONTEXT_AUTHORIZE = 'oauthserver.authorize';
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/oauthserver/'));
$nav->addLabel(pht('Clients'));
$nav->addFilter('client/create', pht('Create Client'));
foreach ($this->getExtraClientFilters() as $filter) {
$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 buildApplicationCrumbs() {
// We're specifically not putting an "OAuth Server" application crumb
// on these pages because it doesn't make sense to send users there on
// the auth workflows.
return new PHUICrumbsView();
}
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
extends PhabricatorOAuthServerController {
private $id;
public function shouldRequireLogin() {
return true;
}
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$panels = array();
$results = array();
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$client = id(new PhabricatorOAuthServerClientQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->withIDs(array($id))
->executeOne();
if (!$client) {
return new Aphront404Response();
@ -37,16 +24,13 @@ final class PhabricatorOAuthServerTestController
->withClientPHIDs(array($client->getPHID()))
->executeOne();
if ($authorization) {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
return $this->newDialog()
->setTitle(pht('Already Authorized'))
->appendParagraph(
pht(
'You have already authorized this application to access your '.
'account.'))
->addCancelButton($view_uri, pht('Close'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if ($request->isFormPost()) {
@ -65,8 +49,7 @@ final class PhabricatorOAuthServerTestController
// TODO: It would be nice to put scope options in this dialog, maybe?
$dialog = id(new AphrontDialogView())
->setUser($viewer)
return $this->newDialog()
->setTitle(pht('Authorize Application?'))
->appendParagraph(
pht(
@ -75,7 +58,5 @@ final class PhabricatorOAuthServerTestController
phutil_tag('strong', array(), $client->getName())))
->addCancelButton($view_uri)
->addSubmitButton(pht('Authorize Application'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

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

View file

@ -26,7 +26,7 @@ final class PhabricatorOwnersApplication extends PhabricatorApplication {
return array(
array(
'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;
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);
if ($strength > $best_match) {
$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,18 +365,15 @@ final class PhabricatorUser
}
public function validateCSRFToken($token) {
$salt = null;
$version = 'plain';
// This is a BREACH-mitigating token. See T3684.
// We expect a BREACH-mitigating token. See T3684.
$breach_prefix = self::CSRF_BREACH_PREFIX;
$breach_prelen = strlen($breach_prefix);
if (strncmp($token, $breach_prefix, $breach_prelen) !== 0) {
return false;
}
if (!strncmp($token, $breach_prefix, $breach_prelen)) {
$version = 'breach';
$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.
// Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept
@ -407,23 +404,12 @@ final class PhabricatorUser
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
$valid = $this->getRawCSRFToken($ii);
switch ($version) {
// TODO: We can remove this after the BREACH version has been in the
// wild for a while.
case 'plain':
if ($token == $valid) {
return true;
}
break;
case 'breach':
$digest = PhabricatorHash::digest($valid, $salt);
if (substr($digest, 0, self::CSRF_TOKEN_LENGTH) == $token) {
$digest = substr($digest, 0, self::CSRF_TOKEN_LENGTH);
if (phutil_hashes_are_identical($digest, $token)) {
return true;
}
break;
default:
throw new Exception(pht('Unknown CSRF token format!'));
}
}
return false;

View file

@ -39,9 +39,6 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
return array(
'/phame/' => array(
'' => 'PhamePostListController',
'r/(?P<id>\d+)/(?P<hash>[^/]+)/(?P<name>.*)'
=> 'PhameResourceController',
'live/(?P<id>[^/]+)/(?P<more>.*)' => 'PhameBlogLiveController',
'post/' => array(
'(?:(?P<filter>draft|all)/)?' => 'PhamePostListController',
@ -65,6 +62,34 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
'feed/(?P<id>[^/]+)/' => 'PhameBlogFeedController',
'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,6 +8,11 @@ final class PhameBlogLiveController extends PhameController {
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
$site = $request->getSite();
if ($site instanceof PhameBlogSite) {
$blog = $site->getBlog();
} else {
$id = $request->getURIData('id');
$blog = id(new PhameBlogQuery())
@ -17,6 +22,7 @@ final class PhameBlogLiveController extends PhameController {
if (!$blog) {
return new Aphront404Response();
}
}
if ($blog->getDomain() && ($request->getHost() != $blog->getDomain())) {
$base_uri = $blog->getLiveURI();

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() {
return 4000;
return 3000;
}
public function newSiteForRequest(AphrontRequest $request) {
@ -53,11 +53,14 @@ final class PhameBlogSite extends PhameSite {
return id(new PhameBlogSite())->setBlog($blog);
}
public function getPathForRouting(AphrontRequest $request) {
$path = $request->getPath();
$id = $this->getBlog()->getID();
public function getRoutingMaps() {
$app = PhabricatorApplication::getByClass('PhabricatorPhameApplication');
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/");
$update_uri = $this->getApplicationURI("{$prefix}cart/{$id}/update/");
$accept_uri = $this->getApplicationURI("{$prefix}cart/{$id}/accept/");
$print_uri = $this->getApplicationURI("{$prefix}cart/{$id}/?__print__=1");
$view->addAction(
id(new PhabricatorActionView())
@ -309,6 +310,13 @@ final class PhortuneCartViewController
->setHref($resume_uri));
}
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Printable Version'))
->setHref($print_uri)
->setOpenInNewWindow(true)
->setIcon('fa-print'));
return $view;
}

View file

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

View file

@ -6,6 +6,9 @@ final class PhabricatorPolicyException extends Exception {
private $rejection;
private $capabilityName;
private $moreInfo = array();
private $objectPHID;
private $context;
private $capability;
public function setTitle($title) {
$this->title = $title;
@ -43,4 +46,31 @@ final class PhabricatorPolicyException extends Exception {
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))
->setTitle($access_denied)
->setObjectPHID($object->getPHID())
->setRejection($rejection)
->setCapability($capability)
->setCapabilityName($capability_name)
->setMoreInfo($details);
@ -710,6 +712,11 @@ final class PhabricatorPolicyFilter extends Phobject {
$objects = $policy->getRuleObjects();
$action = null;
foreach ($policy->getRules() as $rule) {
if (!is_array($rule)) {
// Reject, this policy rule is invalid.
return false;
}
$rule_object = idx($objects, idx($rule, 'rule'));
if (!$rule_object) {
// Reject, this policy has a bogus rule.
@ -831,7 +838,9 @@ final class PhabricatorPolicyFilter extends Phobject {
$exception = id(new PhabricatorPolicyException($full_message))
->setTitle($access_denied)
->setRejection($rejection);
->setObjectPHID($object->getPHID())
->setRejection($rejection)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW);
throw $exception;
}

View file

@ -321,6 +321,12 @@ final class PhabricatorPolicy
$classes = array();
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');
try {
if (class_exists($class)) {

View file

@ -5,14 +5,14 @@ final class PonderQuestionStatus extends PonderConstants {
const STATUS_OPEN = 'open';
const STATUS_CLOSED_RESOLVED = 'resolved';
const STATUS_CLOSED_OBSOLETE = 'obsolete';
const STATUS_CLOSED_DUPLICATE = 'duplicate';
const STATUS_CLOSED_INVALID = 'invalid';
public static function getQuestionStatusMap() {
return array(
self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED_RESOLVED => pht('Closed, Resolved'),
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_CLOSED_RESOLVED => pht('Closed, Resolved'),
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'));
}
@ -31,7 +31,7 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED_RESOLVED => pht('Resolved'),
self::STATUS_CLOSED_OBSOLETE => pht('Obsolete'),
self::STATUS_CLOSED_DUPLICATE => pht('Duplicate'),
self::STATUS_CLOSED_INVALID => pht('Invalid'),
);
return idx($map, $status, pht('Unknown'));
}
@ -43,9 +43,9 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_CLOSED_RESOLVED =>
pht('This question has been answered or resolved.'),
self::STATUS_CLOSED_OBSOLETE =>
pht('This question is no longer valid or out of date.'),
self::STATUS_CLOSED_DUPLICATE =>
pht('This question is a duplicate of another question.'),
pht('This question is out of date.'),
self::STATUS_CLOSED_INVALID =>
pht('This question is invalid.'),
);
return idx($map, $status, pht('Unknown'));
}
@ -55,7 +55,7 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_OPEN => PHUITagView::COLOR_BLUE,
self::STATUS_CLOSED_RESOLVED => 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);
@ -66,7 +66,7 @@ final class PonderQuestionStatus extends PonderConstants {
self::STATUS_OPEN => 'fa-question-circle',
self::STATUS_CLOSED_RESOLVED => 'fa-check',
self::STATUS_CLOSED_OBSOLETE => 'fa-ban',
self::STATUS_CLOSED_DUPLICATE => 'fa-clone',
self::STATUS_CLOSED_INVALID => 'fa-ban',
);
return idx($map, $status);
@ -82,7 +82,7 @@ final class PonderQuestionStatus extends PonderConstants {
return array(
self::STATUS_CLOSED_RESOLVED,
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_content = $question->getContent();
$v_wiki = $question->getAnswerWiki();
$v_view = $question->getViewPolicy();
$v_space = $question->getSpacePHID();
$v_status = $question->getStatus();
@ -42,6 +43,7 @@ final class PonderQuestionEditController extends PonderController {
if ($request->isFormPost()) {
$v_title = $request->getStr('title');
$v_content = $request->getStr('content');
$v_wiki = $request->getStr('answerWiki');
$v_projects = $request->getArr('projects');
$v_view = $request->getStr('viewPolicy');
$v_space = $request->getStr('spacePHID');
@ -68,6 +70,10 @@ final class PonderQuestionEditController extends PonderController {
->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT)
->setNewValue($v_content);
$xactions[] = id(clone $template)
->setTransactionType(PonderQuestionTransaction::TYPE_ANSWERWIKI)
->setNewValue($v_wiki);
if (!$is_new) {
$xactions[] = id(clone $template)
->setTransactionType(PonderQuestionTransaction::TYPE_STATUS)
@ -119,7 +125,15 @@ final class PonderQuestionEditController extends PonderController {
->setName('content')
->setID('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))
->appendControl(
id(new AphrontFormPolicyControl())
@ -157,6 +171,11 @@ final class PonderQuestionEditController extends PonderController {
->setControlID('content')
->setPreviewURI($this->getApplicationURI('preview/'));
$answer_preview = id(new PHUIRemarkupPreviewPanel())
->setHeader(pht('Answer Summary Preview'))
->setControlID('answerWiki')
->setPreviewURI($this->getApplicationURI('preview/'));
$crumbs = $this->buildApplicationCrumbs();
$id = $question->getID();
@ -179,6 +198,7 @@ final class PonderQuestionEditController extends PonderController {
$crumbs,
$form_box,
$preview,
$answer_preview,
),
array(
'title' => $title,

View file

@ -20,7 +20,7 @@ final class PonderQuestionViewController extends PonderController {
return new Aphront404Response();
}
$answers = $this->buildAnswers($question->getAnswers());
$answers = $this->buildAnswers($question);
$answer_add_panel = id(new PonderAddAnswerView())
->setQuestion($question)
@ -81,13 +81,37 @@ final class PonderQuestionViewController extends PonderController {
->addPropertyList($properties)
->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->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())
->setMainColumn(array(
$object_box,
$comment_view,
$answer_wiki,
$answers,
$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
* standard fashion. This is necessary to scale this application.
*/
private function buildAnswers(array $answers) {
private function buildAnswers(PonderQuestion $question) {
$viewer = $this->getViewer();
$answers = $question->getAnswers();
$author_phids = mpull($answers, 'getAuthorPHID');
$handles = $this->loadViewerHandles($author_phids);

View file

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

View file

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

View file

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

View file

@ -90,6 +90,18 @@ final class PonderAddAnswerView extends AphrontView {
id(new AphrontFormSubmitControl())
->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())
->setHeader($header)
->appendChild($form);

View file

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

View file

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

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