1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-07 05:11:05 +01:00

(stable) Promote 2019 Week 7

This commit is contained in:
epriestley 2019-02-15 19:06:06 -08:00
commit dd060b94f0
129 changed files with 2004 additions and 904 deletions

View file

@ -9,7 +9,7 @@ return array(
'names' => array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '7a73ffc5',
'core.pkg.css' => '85a1da99',
'core.pkg.js' => '5c737607',
'differential.pkg.css' => 'b8df73d4',
'differential.pkg.js' => '67c9ea4c',
@ -30,7 +30,7 @@ return array(
'rsrc/css/aphront/notification.css' => '30240bd2',
'rsrc/css/aphront/panel-view.css' => '46923d46',
'rsrc/css/aphront/phabricator-nav-view.css' => 'f8a0c1bf',
'rsrc/css/aphront/table-view.css' => 'daa1f9df',
'rsrc/css/aphront/table-view.css' => '205053cd',
'rsrc/css/aphront/tokenizer.css' => 'b52d0668',
'rsrc/css/aphront/tooltip.css' => 'e3f2412f',
'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2',
@ -46,7 +46,7 @@ return array(
'rsrc/css/application/config/config-options.css' => '16c920ae',
'rsrc/css/application/config/config-template.css' => '20babf50',
'rsrc/css/application/config/setup-issue.css' => '5eed85b2',
'rsrc/css/application/config/unhandled-exception.css' => '9da8fdab',
'rsrc/css/application/config/unhandled-exception.css' => '9ecfc00d',
'rsrc/css/application/conpherence/color.css' => 'b17746b0',
'rsrc/css/application/conpherence/durable-column.css' => '2d57072b',
'rsrc/css/application/conpherence/header-pane.css' => 'c9a3db8e',
@ -151,7 +151,7 @@ return array(
'rsrc/css/phui/phui-document.css' => '52b748a5',
'rsrc/css/phui/phui-feed-story.css' => 'a0c05029',
'rsrc/css/phui/phui-fontkit.css' => '9b714a5e',
'rsrc/css/phui/phui-form-view.css' => '0807e7ac',
'rsrc/css/phui/phui-form-view.css' => '01b796c0',
'rsrc/css/phui/phui-form.css' => '159e2d9c',
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
'rsrc/css/phui/phui-header-view.css' => '93cea4ec',
@ -164,7 +164,7 @@ return array(
'rsrc/css/phui/phui-left-right.css' => '68513c34',
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
'rsrc/css/phui/phui-list.css' => '470b1adb',
'rsrc/css/phui/phui-object-box.css' => '9b58483d',
'rsrc/css/phui/phui-object-box.css' => 'f434b6be',
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
'rsrc/css/phui/phui-property-list-view.css' => 'cad62236',
@ -502,6 +502,7 @@ return array(
'rsrc/js/phui/behavior-phui-selectable-list.js' => 'b26a41e4',
'rsrc/js/phui/behavior-phui-submenu.js' => 'b5e9bff9',
'rsrc/js/phui/behavior-phui-tab-group.js' => '242aa08b',
'rsrc/js/phui/behavior-phui-timer-control.js' => 'f84bcbf4',
'rsrc/js/phuix/PHUIXActionListView.js' => 'c68f183f',
'rsrc/js/phuix/PHUIXActionView.js' => 'aaa08f3b',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '58cc4ab8',
@ -519,7 +520,7 @@ return array(
'aphront-list-filter-view-css' => 'feb64255',
'aphront-multi-column-view-css' => 'fbc00ba3',
'aphront-panel-view-css' => '46923d46',
'aphront-table-view-css' => 'daa1f9df',
'aphront-table-view-css' => '205053cd',
'aphront-tokenizer-control-css' => 'b52d0668',
'aphront-tooltip-css' => 'e3f2412f',
'aphront-typeahead-control-css' => '8779483d',
@ -650,6 +651,7 @@ return array(
'javelin-behavior-phui-selectable-list' => 'b26a41e4',
'javelin-behavior-phui-submenu' => 'b5e9bff9',
'javelin-behavior-phui-tab-group' => '242aa08b',
'javelin-behavior-phui-timer-control' => 'f84bcbf4',
'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
@ -817,7 +819,7 @@ return array(
'phui-font-icon-base-css' => 'd7994e06',
'phui-fontkit-css' => '9b714a5e',
'phui-form-css' => '159e2d9c',
'phui-form-view-css' => '0807e7ac',
'phui-form-view-css' => '01b796c0',
'phui-head-thing-view-css' => 'd7f293df',
'phui-header-view-css' => '93cea4ec',
'phui-hovercard' => '074f0783',
@ -831,7 +833,7 @@ return array(
'phui-left-right-css' => '68513c34',
'phui-lightbox-css' => '4ebf22da',
'phui-list-view-css' => '470b1adb',
'phui-object-box-css' => '9b58483d',
'phui-object-box-css' => 'f434b6be',
'phui-oi-big-ui-css' => '9e037c7a',
'phui-oi-color-css' => 'b517bfa0',
'phui-oi-drag-ui-css' => 'da15d3dc',
@ -877,7 +879,7 @@ return array(
'syntax-highlighting-css' => '8a16f91b',
'tokens-css' => 'ce5a50bd',
'typeahead-browse-css' => 'b7ed02d2',
'unhandled-exception-css' => '9da8fdab',
'unhandled-exception-css' => '9ecfc00d',
),
'requires' => array(
'01384686' => array(
@ -2111,6 +2113,11 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'f84bcbf4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'f8c4e135' => array(
'javelin-install',
'javelin-dom',

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_user.user_externalaccount
ADD providerConfigPHID VARBINARY(64) NOT NULL;

View file

@ -0,0 +1,36 @@
<?php
$account_table = new PhabricatorExternalAccount();
$account_conn = $account_table->establishConnection('w');
$table_name = $account_table->getTableName();
$config_table = new PhabricatorAuthProviderConfig();
$config_conn = $config_table->establishConnection('w');
foreach (new LiskRawMigrationIterator($account_conn, $table_name) as $row) {
if (strlen($row['providerConfigPHID'])) {
continue;
}
$config_row = queryfx_one(
$config_conn,
'SELECT phid
FROM %R
WHERE providerType = %s AND providerDomain = %s
LIMIT 1',
$config_table,
$row['accountType'],
$row['accountDomain']);
if (!$config_row) {
continue;
}
queryfx(
$account_conn,
'UPDATE %R
SET providerConfigPHID = %s
WHERE id = %d',
$account_table,
$config_row['phid'],
$row['id']);
}

View file

@ -1714,6 +1714,7 @@ phutil_register_library_map(array(
'ManiphestTaskFerretEngine' => 'applications/maniphest/search/ManiphestTaskFerretEngine.php',
'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php',
'ManiphestTaskGraph' => 'infrastructure/graph/ManiphestTaskGraph.php',
'ManiphestTaskGraphController' => 'applications/maniphest/controller/ManiphestTaskGraphController.php',
'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
'ManiphestTaskHasCommitRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php',
'ManiphestTaskHasDuplicateTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php',
@ -2195,6 +2196,8 @@ phutil_register_library_map(array(
'PhabricatorAuthChallengeGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthChallengeGarbageCollector.php',
'PhabricatorAuthChallengePHIDType' => 'applications/auth/phid/PhabricatorAuthChallengePHIDType.php',
'PhabricatorAuthChallengeQuery' => 'applications/auth/query/PhabricatorAuthChallengeQuery.php',
'PhabricatorAuthChallengeStatusController' => 'applications/auth/controller/mfa/PhabricatorAuthChallengeStatusController.php',
'PhabricatorAuthChallengeUpdate' => 'applications/auth/view/PhabricatorAuthChallengeUpdate.php',
'PhabricatorAuthChangePasswordAction' => 'applications/auth/action/PhabricatorAuthChangePasswordAction.php',
'PhabricatorAuthConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthConduitAPIMethod.php',
'PhabricatorAuthConduitTokenRevoker' => 'applications/auth/revoker/PhabricatorAuthConduitTokenRevoker.php',
@ -2270,6 +2273,7 @@ phutil_register_library_map(array(
'PhabricatorAuthInviteVerifyException' => 'applications/auth/exception/PhabricatorAuthInviteVerifyException.php',
'PhabricatorAuthInviteWorker' => 'applications/auth/worker/PhabricatorAuthInviteWorker.php',
'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
'PhabricatorAuthLinkMessageType' => 'applications/auth/message/PhabricatorAuthLinkMessageType.php',
'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php',
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
'PhabricatorAuthLoginMessageType' => 'applications/auth/message/PhabricatorAuthLoginMessageType.php',
@ -2368,6 +2372,7 @@ phutil_register_library_map(array(
'PhabricatorAuthSessionPHIDType' => 'applications/auth/phid/PhabricatorAuthSessionPHIDType.php',
'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php',
'PhabricatorAuthSessionRevoker' => 'applications/auth/revoker/PhabricatorAuthSessionRevoker.php',
'PhabricatorAuthSetExternalController' => 'applications/auth/controller/PhabricatorAuthSetExternalController.php',
'PhabricatorAuthSetPasswordController' => 'applications/auth/controller/PhabricatorAuthSetPasswordController.php',
'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php',
'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php',
@ -3867,7 +3872,6 @@ phutil_register_library_map(array(
'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php',
'PhabricatorPeopleInviteListController' => 'applications/people/controller/PhabricatorPeopleInviteListController.php',
'PhabricatorPeopleInviteSendController' => 'applications/people/controller/PhabricatorPeopleInviteSendController.php',
'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php',
'PhabricatorPeopleListController' => 'applications/people/controller/PhabricatorPeopleListController.php',
'PhabricatorPeopleLogQuery' => 'applications/people/query/PhabricatorPeopleLogQuery.php',
'PhabricatorPeopleLogSearchEngine' => 'applications/people/query/PhabricatorPeopleLogSearchEngine.php',
@ -4987,6 +4991,7 @@ phutil_register_library_map(array(
'PhortuneAccountViewController' => 'applications/phortune/controller/account/PhortuneAccountViewController.php',
'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php',
'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php',
'PhortuneAddPaymentMethodAction' => 'applications/phortune/action/PhortuneAddPaymentMethodAction.php',
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
'PhortuneCartAcceptController' => 'applications/phortune/controller/cart/PhortuneCartAcceptController.php',
'PhortuneCartCancelController' => 'applications/phortune/controller/cart/PhortuneCartCancelController.php',
@ -7397,6 +7402,7 @@ phutil_register_library_map(array(
'ManiphestTaskFerretEngine' => 'PhabricatorFerretEngine',
'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine',
'ManiphestTaskGraph' => 'PhabricatorObjectGraph',
'ManiphestTaskGraphController' => 'ManiphestController',
'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasCommitRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasDuplicateTaskEdgeType' => 'PhabricatorEdgeType',
@ -7925,6 +7931,8 @@ phutil_register_library_map(array(
'PhabricatorAuthChallengeGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorAuthChallengePHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthChallengeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthChallengeStatusController' => 'PhabricatorAuthController',
'PhabricatorAuthChallengeUpdate' => 'Phobject',
'PhabricatorAuthChangePasswordAction' => 'PhabricatorSystemAction',
'PhabricatorAuthConduitAPIMethod' => 'ConduitAPIMethod',
'PhabricatorAuthConduitTokenRevoker' => 'PhabricatorAuthRevoker',
@ -8019,6 +8027,7 @@ phutil_register_library_map(array(
'PhabricatorAuthInviteVerifyException' => 'PhabricatorAuthInviteDialogException',
'PhabricatorAuthInviteWorker' => 'PhabricatorWorker',
'PhabricatorAuthLinkController' => 'PhabricatorAuthController',
'PhabricatorAuthLinkMessageType' => 'PhabricatorAuthMessageType',
'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
'PhabricatorAuthLoginMessageType' => 'PhabricatorAuthMessageType',
@ -8138,6 +8147,7 @@ phutil_register_library_map(array(
'PhabricatorAuthSessionPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthSessionRevoker' => 'PhabricatorAuthRevoker',
'PhabricatorAuthSetExternalController' => 'PhabricatorAuthController',
'PhabricatorAuthSetPasswordController' => 'PhabricatorAuthController',
'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorAuthStartController' => 'PhabricatorAuthController',
@ -9866,7 +9876,6 @@ phutil_register_library_map(array(
'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController',
'PhabricatorPeopleInviteListController' => 'PhabricatorPeopleInviteController',
'PhabricatorPeopleInviteSendController' => 'PhabricatorPeopleInviteController',
'PhabricatorPeopleLdapController' => 'PhabricatorPeopleController',
'PhabricatorPeopleListController' => 'PhabricatorPeopleController',
'PhabricatorPeopleLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPeopleLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
@ -11229,6 +11238,7 @@ phutil_register_library_map(array(
'PhortuneAccountViewController' => 'PhortuneAccountProfileController',
'PhortuneAdHocCart' => 'PhortuneCartImplementation',
'PhortuneAdHocProduct' => 'PhortuneProductImplementation',
'PhortuneAddPaymentMethodAction' => 'PhabricatorSystemAction',
'PhortuneCart' => array(
'PhortuneDAO',
'PhabricatorApplicationTransactionInterface',

View file

@ -594,7 +594,7 @@ final class AphrontRequest extends Phobject {
$request_uri = idx($_SERVER, 'REQUEST_URI', '/');
$uri = new PhutilURI($request_uri);
$uri->setQueryParam('__path__', null);
$uri->removeQueryParam('__path__');
$path = phutil_escape_uri($this->getPath());
$uri->setPath($path);
@ -829,7 +829,10 @@ final class AphrontRequest extends Phobject {
}
$uri->setPath($this->getPath());
$uri->setQueryParams(self::flattenData($_GET));
$uri->removeAllQueryParams();
foreach (self::flattenData($_GET) as $query_key => $query_value) {
$uri->appendQueryParam($query_key, $query_value);
}
$input = PhabricatorStartup::getRawInput();

View file

@ -118,6 +118,12 @@ final class AphrontApplicationConfiguration
$database_exception = $ex;
}
// If we're in developer mode, set a flag so that top-level exception
// handlers can add more information.
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$sink->setShowStackTraces(true);
}
if ($database_exception) {
$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(
$database_exception,
@ -282,23 +288,69 @@ final class AphrontApplicationConfiguration
}
} catch (Exception $ex) {
$original_exception = $ex;
$response = $this->handleThrowable($ex);
} catch (Throwable $ex) {
$original_exception = $ex;
$response = $this->handleThrowable($ex);
}
$response_exception = null;
try {
if ($original_exception) {
$response = $this->handleThrowable($original_exception);
}
$response = $this->produceResponse($request, $response);
$response = $controller->willSendResponse($response);
$response->setRequest($request);
self::writeResponse($sink, $response);
} catch (Exception $ex) {
$response_exception = $ex;
} catch (Throwable $ex) {
$response_exception = $ex;
}
if ($response_exception) {
// If we encountered an exception while building a normal response, then
// encountered another exception while building a response for the first
// exception, just throw the original exception. It is more likely to be
// useful and point at a root cause than the second exception we ran into
// while telling the user about it.
if ($original_exception) {
throw $original_exception;
}
throw $ex;
// If we built a response successfully and then ran into an exception
// trying to render it, try to handle and present that exception to the
// user using the standard handler.
// The problem here might be in rendering (more common) or in the actual
// response mechanism (less common). If it's in rendering, we can likely
// still render a nice exception page: the majority of rendering issues
// are in main page content, not content shared with the exception page.
$handling_exception = null;
try {
$response = $this->handleThrowable($response_exception);
$response = $this->produceResponse($request, $response);
$response = $controller->willSendResponse($response);
$response->setRequest($request);
self::writeResponse($sink, $response);
} catch (Exception $ex) {
$handling_exception = $ex;
} catch (Throwable $ex) {
$handling_exception = $ex;
}
// If we didn't have any luck with that, raise the original response
// exception. As above, this is the root cause exception and more likely
// to be useful. This will go to the fallback error handler at top
// level.
if ($handling_exception) {
throw $response_exception;
}
}
return $response;

View file

@ -32,22 +32,21 @@ final class AphrontAjaxResponse extends AphrontResponse {
}
public function buildResponseString() {
$request = $this->getRequest();
$console = $this->getConsole();
if ($console) {
// NOTE: We're stripping query parameters here both for readability and
// to mitigate BREACH and similar attacks. The parameters are available
// in the "Request" tab, so this should not impact usability. See T3684.
$uri = $this->getRequest()->getRequestURI();
$uri = new PhutilURI($uri);
$uri->setQueryParams(array());
$path = $request->getPath();
Javelin::initBehavior(
'dark-console',
array(
'uri' => (string)$uri,
'key' => $console->getKey($this->getRequest()),
'uri' => $path,
'key' => $console->getKey($request),
'color' => $console->getColor(),
'quicksand' => $this->getRequest()->isQuicksand(),
'quicksand' => $request->isQuicksand(),
));
}
@ -60,7 +59,6 @@ final class AphrontAjaxResponse extends AphrontResponse {
$response = CelerityAPI::getStaticResourceResponse();
$request = $this->getRequest();
if ($request) {
$viewer = $request->getViewer();
if ($viewer) {

View file

@ -218,7 +218,7 @@ abstract class AphrontResponse extends Phobject {
$uri = id(new PhutilURI($uri))
->setPath(null)
->setFragment(null)
->setQueryParams(array());
->removeAllQueryParams();
$uri = (string)$uri;
if (preg_match('/[ ;\']/', $uri)) {

View file

@ -4,8 +4,20 @@ final class AphrontUnhandledExceptionResponse
extends AphrontStandaloneHTMLResponse {
private $exception;
private $showStackTraces;
public function setShowStackTraces($show_stack_traces) {
$this->showStackTraces = $show_stack_traces;
return $this;
}
public function getShowStackTraces() {
return $this->showStackTraces;
}
public function setException($exception) {
// NOTE: We accept an Exception or a Throwable.
public function setException(Exception $exception) {
// Log the exception unless it's specifically a silent malformed request
// exception.
@ -61,10 +73,36 @@ final class AphrontUnhandledExceptionResponse
$body = $ex->getMessage();
$body = phutil_escape_html_newlines($body);
$classes = array();
$classes[] = 'unhandled-exception-detail';
$stack = null;
if ($this->getShowStackTraces()) {
try {
$stack = id(new AphrontStackTraceView())
->setTrace($ex->getTrace());
$stack = hsprintf('%s', $stack);
$stack = phutil_tag(
'div',
array(
'class' => 'unhandled-exception-stack',
),
$stack);
$classes[] = 'unhandled-exception-with-stack';
} catch (Exception $trace_exception) {
$stack = null;
} catch (Throwable $trace_exception) {
$stack = null;
}
}
return phutil_tag(
'div',
array(
'class' => 'unhandled-exception-detail',
'class' => implode(' ', $classes),
),
array(
phutil_tag(
@ -79,6 +117,7 @@ final class AphrontUnhandledExceptionResponse
'class' => 'unhandled-exception-body',
),
$body),
$stack,
));
}

View file

@ -5,14 +5,22 @@
* Normally this is just @{class:AphrontPHPHTTPSink}, which uses "echo" and
* "header()" to emit responses.
*
* Mostly, this class allows us to do install security or metrics hooks in the
* output pipeline.
*
* @task write Writing Response Components
* @task emit Emitting the Response
*/
abstract class AphrontHTTPSink extends Phobject {
private $showStackTraces = false;
final public function setShowStackTraces($show_stack_traces) {
$this->showStackTraces = $show_stack_traces;
return $this;
}
final public function getShowStackTraces() {
return $this->showStackTraces;
}
/* -( Writing Response Components )---------------------------------------- */

View file

@ -67,19 +67,13 @@ abstract class AlmanacController
$is_builtin = isset($builtins[$key]);
$is_persistent = (bool)$property->getID();
$delete_uri = id(new PhutilURI($delete_base))
->setQueryParams(
array(
'key' => $key,
'objectPHID' => $object->getPHID(),
));
$params = array(
'key' => $key,
'objectPHID' => $object->getPHID(),
);
$edit_uri = id(new PhutilURI($edit_base))
->setQueryParams(
array(
'key' => $key,
'objectPHID' => $object->getPHID(),
));
$delete_uri = new PhutilURI($delete_base, $params);
$edit_uri = new PhutilURI($edit_base, $params);
$delete = javelin_tag(
'a',
@ -143,7 +137,7 @@ abstract class AlmanacController
$phid = $object->getPHID();
$add_uri = id(new PhutilURI($edit_base))
->setQueryParam('objectPHID', $object->getPHID());
->replaceQueryParam('objectPHID', $object->getPHID());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,

View file

@ -105,10 +105,10 @@ final class PhabricatorAuditManagementDeleteWorkflow
$query->withPHIDs(mpull($commits, 'getPHID'));
}
$commits = $query->execute();
$commits = mpull($commits, null, 'getPHID');
$commit_iterator = new PhabricatorQueryIterator($query);
$audits = array();
foreach ($commits as $commit) {
foreach ($commit_iterator as $commit) {
$commit_audits = $commit->getAudits();
foreach ($commit_audits as $key => $audit) {
if ($id_map && empty($id_map[$audit->getID()])) {
@ -131,51 +131,87 @@ final class PhabricatorAuditManagementDeleteWorkflow
continue;
}
}
$audits[] = $commit_audits;
}
$audits = array_mergev($audits);
$console = PhutilConsole::getConsole();
if (!$commit_audits) {
continue;
}
if (!$audits) {
$console->writeErr("%s\n", pht('No audits match the query.'));
return 0;
}
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(mpull($commit_audits, 'getAuditorPHID'))
->execute();
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
->withPHIDs(mpull($audits, 'getAuditorPHID'))
->execute();
foreach ($commit_audits as $audit) {
$audit_id = $audit->getID();
foreach ($audits as $audit) {
$commit = $commits[$audit->getCommitPHID()];
$console->writeOut(
"%s\n",
sprintf(
$description = sprintf(
'%10d %-16s %-16s %s: %s',
$audit->getID(),
$audit_id,
$handles[$audit->getAuditorPHID()]->getName(),
PhabricatorAuditStatusConstants::getStatusName(
$audit->getAuditStatus()),
$commit->getRepository()->formatCommitName(
$commit->getCommitIdentifier()),
trim($commit->getSummary())));
trim($commit->getSummary()));
$audits[] = array(
'auditID' => $audit_id,
'commitPHID' => $commit->getPHID(),
'description' => $description,
);
}
}
if (!$is_dry_run) {
$message = pht(
'Really delete these %d audit(s)? They will be permanently deleted '.
'and can not be recovered.',
count($audits));
if ($console->confirm($message)) {
foreach ($audits as $audit) {
$id = $audit->getID();
$console->writeOut("%s\n", pht('Deleting audit %d...', $id));
$audit->delete();
}
if (!$audits) {
echo tsprintf(
"%s\n",
pht('No audits match the query.'));
return 0;
}
foreach ($audits as $audit_spec) {
echo tsprintf(
"%s\n",
$audit_spec['description']);
}
if ($is_dry_run) {
echo tsprintf(
"%s\n",
pht('This is a dry run, so no changes will be made.'));
return 0;
}
$message = pht(
'Really delete these %s audit(s)? They will be permanently deleted '.
'and can not be recovered.',
phutil_count($audits));
if (!phutil_console_confirm($message)) {
echo tsprintf(
"%s\n",
pht('User aborted the workflow.'));
return 1;
}
$audits_by_commit = igroup($audits, 'commitPHID');
foreach ($audits_by_commit as $commit_phid => $audit_specs) {
$audit_ids = ipull($audit_specs, 'auditID');
$audits = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere(
'id IN (%Ld)',
$audit_ids);
foreach ($audits as $audit) {
$id = $audit->getID();
echo tsprintf(
"%s\n",
pht('Deleting audit %d...', $id));
$audit->delete();
}
$this->synchronizeCommitAuditState($commit_phid);
}
return 0;

View file

@ -87,4 +87,39 @@ abstract class PhabricatorAuditManagementWorkflow
return $commits;
}
protected function synchronizeCommitAuditState($commit_phid) {
$viewer = $this->getViewer();
$commit = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withPHIDs(array($commit_phid))
->needAuditRequests(true)
->executeOne();
if (!$commit) {
return;
}
$old_status = $commit->getAuditStatusObject();
$commit->updateAuditStatus($commit->getAudits());
$new_status = $commit->getAuditStatusObject();
if ($old_status->getKey() == $new_status->getKey()) {
echo tsprintf(
"%s\n",
pht(
'No synchronization changes for "%s".',
$commit->getDisplayName()));
} else {
echo tsprintf(
"%s\n",
pht(
'Synchronizing "%s": "%s" -> "%s".',
$commit->getDisplayName(),
$old_status->getName(),
$new_status->getName()));
$commit->save();
}
}
}

View file

@ -6,8 +6,16 @@ final class PhabricatorAuditSynchronizeManagementWorkflow
protected function didConstruct() {
$this
->setName('synchronize')
->setExamples('**synchronize** ...')
->setSynopsis(pht('Update audit status for commits.'))
->setExamples(
"**synchronize** __repository__ ...\n".
"**synchronize** __commit__ ...\n".
"**synchronize** --all")
->setSynopsis(
pht(
'Update commits to make their summary audit state reflect the '.
'state of their actual audit requests. This can fix inconsistencies '.
'in database state if audit requests have been mangled '.
'accidentally (or on purpose).'))
->setArguments(
array_merge(
$this->getCommitConstraintArguments(),
@ -21,36 +29,7 @@ final class PhabricatorAuditSynchronizeManagementWorkflow
foreach ($objects as $object) {
$commits = $this->loadCommitsForConstraintObject($object);
foreach ($commits as $commit) {
$commit = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withPHIDs(array($commit->getPHID()))
->needAuditRequests(true)
->executeOne();
if (!$commit) {
continue;
}
$old_status = $commit->getAuditStatusObject();
$commit->updateAuditStatus($commit->getAudits());
$new_status = $commit->getAuditStatusObject();
if ($old_status->getKey() == $new_status->getKey()) {
echo tsprintf(
"%s\n",
pht(
'No changes for "%s".',
$commit->getDisplayName()));
} else {
echo tsprintf(
"%s\n",
pht(
'Updating "%s": "%s" -> "%s".',
$commit->getDisplayName(),
$old_status->getName(),
$new_status->getName()));
$commit->save();
}
$this->synchronizeCommitAuditState($commit->getPHID());
}
}
}

View file

@ -61,8 +61,8 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'start/' => 'PhabricatorAuthStartController',
'validate/' => 'PhabricatorAuthValidateController',
'finish/' => 'PhabricatorAuthFinishController',
'unlink/(?P<pkey>[^/]+)/' => 'PhabricatorAuthUnlinkController',
'(?P<action>link|refresh)/(?P<pkey>[^/]+)/'
'unlink/(?P<id>\d+)/' => 'PhabricatorAuthUnlinkController',
'(?P<action>link|refresh)/(?P<id>\d+)/'
=> 'PhabricatorAuthLinkController',
'confirmlink/(?P<akey>[^/]+)/'
=> 'PhabricatorAuthConfirmLinkController',
@ -86,7 +86,9 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
=> 'PhabricatorAuthSSHKeyRevokeController',
'view/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyViewController',
),
'password/' => 'PhabricatorAuthSetPasswordController',
'external/' => 'PhabricatorAuthSetExternalController',
'mfa/' => array(
$this->getQueryRoutePattern() =>
@ -97,6 +99,8 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'PhabricatorAuthFactorProviderViewController',
'message/(?P<id>[1-9]\d*)/' =>
'PhabricatorAuthFactorProviderMessageController',
'challenge/status/(?P<id>[1-9]\d*)/' =>
'PhabricatorAuthChallengeStatusController',
),
'message/' => array(

View file

@ -20,7 +20,15 @@ final class PhabricatorAuthConfirmLinkController
$panel_uri = '/settings/panel/external/';
if ($request->isFormPost()) {
if ($request->isFormOrHisecPost()) {
$workflow_key = sprintf(
'account.link(%s)',
$account->getPHID());
$hisec_token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken($viewer, $request, $panel_uri);
$account->setUserPHID($viewer->getPHID());
$account->save();
@ -31,14 +39,7 @@ final class PhabricatorAuthConfirmLinkController
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
// TODO: Provide more information about the external account. Clicking
// through this form blindly is dangerous.
// TODO: If the user has password authentication, require them to retype
// their password here.
$dialog = id(new AphrontDialogView())
->setUser($viewer)
$dialog = $this->newDialog()
->setTitle(pht('Confirm %s Account Link', $provider->getProviderName()))
->addCancelButton($panel_uri)
->addSubmitButton(pht('Confirm Account Link'));

View file

@ -95,7 +95,7 @@ abstract class PhabricatorAuthController extends PhabricatorController {
private function buildLoginValidateResponse(PhabricatorUser $user) {
$validate_uri = new PhutilURI($this->getApplicationURI('validate/'));
$validate_uri->setQueryParam('expect', $user->getUsername());
$validate_uri->replaceQueryParam('expect', $user->getUsername());
return id(new AphrontRedirectResponse())->setURI((string)$validate_uri);
}
@ -213,19 +213,19 @@ abstract class PhabricatorAuthController extends PhabricatorController {
return array($account, $provider, $response);
}
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
$account->getProviderKey());
if (!$provider) {
$config = $account->getProviderConfig();
if (!$config->getIsEnabled()) {
$response = $this->renderError(
pht(
'The account you are attempting to register with uses a nonexistent '.
'or disabled authentication provider (with key "%s"). An '.
'administrator may have recently disabled this provider.',
$account->getProviderKey()));
'The account you are attempting to register with uses a disabled '.
'authentication provider ("%s"). An administrator may have '.
'recently disabled this provider.',
$config->getDisplayName()));
return array($account, $provider, $response);
}
$provider = $config->getProvider();
return array($account, $provider, null);
}

View file

@ -6,14 +6,20 @@ final class PhabricatorAuthLinkController
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$action = $request->getURIData('action');
$provider_key = $request->getURIData('pkey');
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
$provider_key);
if (!$provider) {
$id = $request->getURIData('id');
$config = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->withIDs(array($id))
->withIsEnabled(true)
->executeOne();
if (!$config) {
return new Aphront404Response();
}
$provider = $config->getProvider();
switch ($action) {
case 'link':
if (!$provider->shouldAllowAccountLink()) {
@ -37,15 +43,15 @@ final class PhabricatorAuthLinkController
return new Aphront400Response();
}
$account = id(new PhabricatorExternalAccount())->loadOneWhere(
'accountType = %s AND accountDomain = %s AND userPHID = %s',
$provider->getProviderType(),
$provider->getProviderDomain(),
$viewer->getPHID());
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->withProviderConfigPHIDs(array($config->getPHID()))
->execute();
switch ($action) {
case 'link':
if ($account) {
if ($accounts) {
return $this->renderErrorPage(
pht('Account Already Linked'),
array(
@ -56,7 +62,7 @@ final class PhabricatorAuthLinkController
}
break;
case 'refresh':
if (!$account) {
if (!$accounts) {
return $this->renderErrorPage(
pht('No Account Linked'),
array(
@ -76,11 +82,6 @@ final class PhabricatorAuthLinkController
switch ($action) {
case 'link':
id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$panel_uri);
$form = $provider->buildLinkForm($this);
break;
case 'refresh':

View file

@ -35,6 +35,7 @@ final class PhabricatorAuthLoginController
return $response;
}
$invite = $this->loadInvite();
$provider = $this->provider;
try {
@ -103,7 +104,7 @@ final class PhabricatorAuthLoginController
// The account is not yet attached to a Phabricator user, so this is
// either a registration or an account link request.
if (!$viewer->isLoggedIn()) {
if ($provider->shouldAllowRegistration()) {
if ($provider->shouldAllowRegistration() || $invite) {
return $this->processRegisterUser($account);
} else {
return $this->renderError(

View file

@ -14,11 +14,6 @@ final class PhabricatorAuthOneTimeLoginController
$key = $request->getURIData('key');
$email_id = $request->getURIData('emailID');
if ($request->getUser()->isLoggedIn()) {
return $this->renderError(
pht('You are already logged in.'));
}
$target_user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($id))
@ -27,6 +22,19 @@ final class PhabricatorAuthOneTimeLoginController
return new Aphront404Response();
}
// NOTE: We allow you to use a one-time login link for your own current
// login account. This supports the "Set Password" flow.
$is_logged_in = false;
if ($viewer->isLoggedIn()) {
if ($viewer->getPHID() !== $target_user->getPHID()) {
return $this->renderError(
pht('You are already logged in.'));
} else {
$is_logged_in = true;
}
}
// NOTE: As a convenience to users, these one-time login URIs may also
// be associated with an email address which will be verified when the
// URI is used.
@ -100,7 +108,7 @@ final class PhabricatorAuthOneTimeLoginController
->addCancelButton('/');
}
if ($request->isFormPost()) {
if ($request->isFormPost() || $is_logged_in) {
// If we have an email bound into this URI, verify email so that clicking
// the link in the "Welcome" email is good enough, without requiring users
// to go through a second round of email verification.
@ -121,6 +129,12 @@ final class PhabricatorAuthOneTimeLoginController
$next_uri = $this->getNextStepURI($target_user);
// If the user is already logged in, we're just doing a "password set"
// flow. Skip directly to the next step.
if ($is_logged_in) {
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
PhabricatorCookies::setNextURICookie($request, $next_uri, $force = true);
$force_full_session = false;
@ -204,24 +218,52 @@ final class PhabricatorAuthOneTimeLoginController
$request->setTemporaryCookie(PhabricatorCookies::COOKIE_HISEC, 'yes');
return (string)id(new PhutilURI($panel_uri))
->setQueryParams(
array(
'key' => $key,
));
$params = array(
'key' => $key,
);
return (string)new PhutilURI($panel_uri, $params);
}
$providers = id(new PhabricatorAuthProviderConfigQuery())
// Check if the user already has external accounts linked. If they do,
// it's not obvious why they aren't using them to log in, but assume they
// know what they're doing. We won't send them to the link workflow.
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($user)
->withUserPHIDs(array($user->getPHID()))
->execute();
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($user)
->withIsEnabled(true)
->execute();
$linkable = array();
foreach ($configs as $config) {
if (!$config->getShouldAllowLink()) {
continue;
}
$provider = $config->getProvider();
if (!$provider->isLoginFormAButton()) {
continue;
}
$linkable[] = $provider;
}
// If there's at least one linkable provider, and the user doesn't already
// have accounts, send the user to the link workflow.
if (!$accounts && $linkable) {
return '/auth/external/';
}
// If there are no configured providers and the user is an administrator,
// send them to Auth to configure a provider. This is probably where they
// want to go. You can end up in this state by accidentally losing your
// first session during initial setup, or after restoring exported data
// from a hosted instance.
if (!$providers && $user->getIsAdmin()) {
if (!$configs && $user->getIsAdmin()) {
return '/auth/';
}

View file

@ -11,21 +11,25 @@ final class PhabricatorAuthRegisterController
$viewer = $this->getViewer();
$account_key = $request->getURIData('akey');
if ($request->getUser()->isLoggedIn()) {
if ($viewer->isLoggedIn()) {
return id(new AphrontRedirectResponse())->setURI('/');
}
$invite = $this->loadInvite();
$is_setup = false;
if (strlen($account_key)) {
$result = $this->loadAccountForRegistrationOrLinking($account_key);
list($account, $provider, $response) = $result;
$is_default = false;
} else if ($this->isFirstTimeSetup()) {
list($account, $provider, $response) = $this->loadSetupAccount();
$account = null;
$provider = null;
$response = null;
$is_default = true;
$is_setup = true;
} else {
list($account, $provider, $response) = $this->loadDefaultAccount();
list($account, $provider, $response) = $this->loadDefaultAccount($invite);
$is_default = true;
}
@ -33,24 +37,24 @@ final class PhabricatorAuthRegisterController
return $response;
}
$invite = $this->loadInvite();
if (!$is_setup) {
if (!$provider->shouldAllowRegistration()) {
if ($invite) {
// If the user has an invite, we allow them to register with any
// provider, even a login-only provider.
} else {
// TODO: This is a routine error if you click "Login" on an external
// auth source which doesn't allow registration. The error should be
// more tailored.
if (!$provider->shouldAllowRegistration()) {
if ($invite) {
// If the user has an invite, we allow them to register with any
// provider, even a login-only provider.
} else {
// TODO: This is a routine error if you click "Login" on an external
// auth source which doesn't allow registration. The error should be
// more tailored.
return $this->renderError(
pht(
'The account you are attempting to register with uses an '.
'authentication provider ("%s") which does not allow '.
'registration. An administrator may have recently disabled '.
'registration with this provider.',
$provider->getProviderName()));
return $this->renderError(
pht(
'The account you are attempting to register with uses an '.
'authentication provider ("%s") which does not allow '.
'registration. An administrator may have recently disabled '.
'registration with this provider.',
$provider->getProviderName()));
}
}
}
@ -58,14 +62,19 @@ final class PhabricatorAuthRegisterController
$user = new PhabricatorUser();
$default_username = $account->getUsername();
$default_realname = $account->getRealName();
if ($is_setup) {
$default_username = null;
$default_realname = null;
$default_email = null;
} else {
$default_username = $account->getUsername();
$default_realname = $account->getRealName();
$default_email = $account->getEmail();
}
$account_type = PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT;
$content_source = PhabricatorContentSource::newFromRequest($request);
$default_email = $account->getEmail();
if ($invite) {
$default_email = $invite->getEmailAddress();
}
@ -212,7 +221,11 @@ final class PhabricatorAuthRegisterController
$can_edit_email = $profile->getCanEditEmail();
$can_edit_realname = $profile->getCanEditRealName();
$must_set_password = $provider->shouldRequireRegistrationPassword();
if ($is_setup) {
$must_set_password = false;
} else {
$must_set_password = $provider->shouldRequireRegistrationPassword();
}
$can_edit_anything = $profile->getCanEditAnything() || $must_set_password;
$force_verify = $profile->getShouldVerifyEmail();
@ -334,9 +347,11 @@ final class PhabricatorAuthRegisterController
}
if (!$errors) {
$image = $this->loadProfilePicture($account);
if ($image) {
$user->setProfileImagePHID($image->getPHID());
if (!$is_setup) {
$image = $this->loadProfilePicture($account);
if ($image) {
$user->setProfileImagePHID($image->getPHID());
}
}
try {
@ -346,17 +361,19 @@ final class PhabricatorAuthRegisterController
$verify_email = true;
}
if ($value_email === $default_email) {
if ($account->getEmailVerified()) {
$verify_email = true;
}
if (!$is_setup) {
if ($value_email === $default_email) {
if ($account->getEmailVerified()) {
$verify_email = true;
}
if ($provider->shouldTrustEmails()) {
$verify_email = true;
}
if ($provider->shouldTrustEmails()) {
$verify_email = true;
}
if ($invite) {
$verify_email = true;
if ($invite) {
$verify_email = true;
}
}
}
@ -438,9 +455,11 @@ final class PhabricatorAuthRegisterController
$transaction_editor->applyTransactions($user, $xactions);
}
$account->setUserPHID($user->getPHID());
$provider->willRegisterAccount($account);
$account->save();
if (!$is_setup) {
$account->setUserPHID($user->getPHID());
$provider->willRegisterAccount($account);
$account->save();
}
$user->saveTransaction();
@ -501,7 +520,6 @@ final class PhabricatorAuthRegisterController
->setAuthProvider($provider)));
}
if ($can_edit_username) {
$form->appendChild(
id(new AphrontFormTextControl())
@ -595,7 +613,7 @@ final class PhabricatorAuthRegisterController
pht(
'Installation is complete. Register your administrator account '.
'below to log in. You will be able to configure options and add '.
'other authentication mechanisms (like LDAP or OAuth) later on.'));
'authentication mechanisms later on.'));
}
$object_box = id(new PHUIObjectBoxView())
@ -612,11 +630,12 @@ final class PhabricatorAuthRegisterController
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$welcome_view,
$invite_header,
$object_box,
));
->setFooter(
array(
$welcome_view,
$invite_header,
$object_box,
));
return $this->newPage()
->setTitle($title)
@ -624,17 +643,20 @@ final class PhabricatorAuthRegisterController
->appendChild($view);
}
private function loadDefaultAccount() {
private function loadDefaultAccount($invite) {
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
$account = null;
$provider = null;
$response = null;
foreach ($providers as $key => $candidate_provider) {
if (!$candidate_provider->shouldAllowRegistration()) {
unset($providers[$key]);
continue;
if (!$invite) {
if (!$candidate_provider->shouldAllowRegistration()) {
unset($providers[$key]);
continue;
}
}
if (!$candidate_provider->isDefaultRegistrationProvider()) {
unset($providers[$key]);
}
@ -652,24 +674,11 @@ final class PhabricatorAuthRegisterController
}
$provider = head($providers);
$account = $provider->getDefaultExternalAccount();
$account = $provider->newDefaultExternalAccount();
return array($account, $provider, $response);
}
private function loadSetupAccount() {
$provider = new PhabricatorPasswordAuthProvider();
$provider->attachProviderConfig(
id(new PhabricatorAuthProviderConfig())
->setShouldAllowRegistration(1)
->setShouldAllowLogin(1)
->setIsEnabled(true));
$account = $provider->getDefaultExternalAccount();
$response = null;
return array($account, $provider, $response);
}
private function loadProfilePicture(PhabricatorExternalAccount $account) {
$phid = $account->getProfileImagePHID();
if (!$phid) {

View file

@ -0,0 +1,110 @@
<?php
final class PhabricatorAuthSetExternalController
extends PhabricatorAuthController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->withIsEnabled(true)
->execute();
$linkable = array();
foreach ($configs as $config) {
if (!$config->getShouldAllowLink()) {
continue;
}
// For now, only buttons get to appear here: for example, we can't
// reasonably embed an entire LDAP form into this UI.
$provider = $config->getProvider();
if (!$provider->isLoginFormAButton()) {
continue;
}
$linkable[] = $config;
}
if (!$linkable) {
return $this->newDialog()
->setTitle(pht('No Linkable External Providers'))
->appendParagraph(
pht(
'Currently, there are no configured external auth providers '.
'which you can link your account to.'))
->addCancelButton('/');
}
$text = PhabricatorAuthMessage::loadMessageText(
$viewer,
PhabricatorAuthLinkMessageType::MESSAGEKEY);
if (!strlen($text)) {
$text = pht(
'You can link your Phabricator account to an external account to '.
'allow you to log in more easily in the future. To continue, choose '.
'an account to link below. If you prefer not to link your account, '.
'you can skip this step.');
}
$remarkup_view = new PHUIRemarkupView($viewer, $text);
$remarkup_view = phutil_tag(
'div',
array(
'class' => 'phui-object-box-instructions',
),
$remarkup_view);
PhabricatorCookies::setClientIDCookie($request);
$view = array();
foreach ($configs as $config) {
$provider = $config->getProvider();
$form = $provider->buildLinkForm($this);
if ($provider->isLoginFormAButton()) {
require_celerity_resource('auth-css');
$form = phutil_tag(
'div',
array(
'class' => 'phabricator-link-button pl',
),
$form);
}
$view[] = $form;
}
$form = id(new AphrontFormView())
->setViewer($viewer)
->appendControl(
id(new AphrontFormSubmitControl())
->addCancelButton('/', pht('Skip This Step')));
$header = id(new PHUIHeaderView())
->setHeader(pht('Link External Account'));
$box = id(new PHUIObjectBoxView())
->setViewer($viewer)
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($remarkup_view)
->appendChild($view)
->appendChild($form);
$main_view = id(new PHUITwoColumnView())
->setFooter($box);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Link External Account'))
->setBorder(true);
return $this->newPage()
->setTitle(pht('Link External Account'))
->setCrumbs($crumbs)
->appendChild($main_view);
}
}

View file

@ -54,7 +54,7 @@ final class PhabricatorAuthStartController
}
$redirect_uri = $request->getRequestURI();
$redirect_uri->setQueryParam('cleared', 1);
$redirect_uri->replaceQueryParam('cleared', 1);
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
}
}
@ -64,7 +64,7 @@ final class PhabricatorAuthStartController
// the workflow will continue normally.
if ($did_clear) {
$redirect_uri = $request->getRequestURI();
$redirect_uri->setQueryParam('cleared', null);
$redirect_uri->removeQueryParam('cleared');
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
}

View file

@ -3,48 +3,45 @@
final class PhabricatorAuthUnlinkController
extends PhabricatorAuthController {
private $providerKey;
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$this->providerKey = $request->getURIData('pkey');
$id = $request->getURIData('id');
list($type, $domain) = explode(':', $this->providerKey, 2);
// Check that this account link actually exists. We don't require the
// provider to exist because we want users to be able to delete links to
// dead accounts if they want.
$account = id(new PhabricatorExternalAccount())->loadOneWhere(
'accountType = %s AND accountDomain = %s AND userPHID = %s',
$type,
$domain,
$viewer->getPHID());
$account = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$account) {
return $this->renderNoAccountErrorDialog();
return new Aphront404Response();
}
// Check that the provider (if it exists) allows accounts to be unlinked.
$provider_key = $this->providerKey;
$provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key);
if ($provider) {
if (!$provider->shouldAllowAccountUnlink()) {
return $this->renderNotUnlinkableErrorDialog($provider);
}
$done_uri = '/settings/panel/external/';
$config = $account->getProviderConfig();
$provider = $config->getProvider();
if (!$provider->shouldAllowAccountUnlink()) {
return $this->renderNotUnlinkableErrorDialog($provider, $done_uri);
}
$confirmations = $request->getStrList('confirmations');
$confirmations = array_fuse($confirmations);
if (!$request->isFormPost() || !isset($confirmations['unlink'])) {
return $this->renderConfirmDialog($confirmations);
if (!$request->isFormOrHisecPost() || !isset($confirmations['unlink'])) {
return $this->renderConfirmDialog($confirmations, $config, $done_uri);
}
// Check that this account isn't the only account which can be used to
// login. We warn you when you remove your only login account.
if ($account->isUsableForLogin()) {
$other_accounts = id(new PhabricatorExternalAccount())->loadAllWhere(
'userPHID = %s',
$viewer->getPHID());
$other_accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->execute();
$valid_accounts = 0;
foreach ($other_accounts as $other_account) {
@ -55,11 +52,21 @@ final class PhabricatorAuthUnlinkController
if ($valid_accounts < 2) {
if (!isset($confirmations['only'])) {
return $this->renderOnlyUsableAccountConfirmDialog($confirmations);
return $this->renderOnlyUsableAccountConfirmDialog(
$confirmations,
$done_uri);
}
}
}
$workflow_key = sprintf(
'account.unlink(%s)',
$account->getPHID());
$hisec_token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken($viewer, $request, $done_uri);
$account->delete();
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
@ -67,42 +74,27 @@ final class PhabricatorAuthUnlinkController
new PhutilOpaqueEnvelope(
$request->getCookie(PhabricatorCookies::COOKIE_SESSION)));
return id(new AphrontRedirectResponse())->setURI($this->getDoneURI());
}
private function getDoneURI() {
return '/settings/panel/external/';
}
private function renderNoAccountErrorDialog() {
$dialog = id(new AphrontDialogView())
->setUser($this->getRequest()->getUser())
->setTitle(pht('No Such Account'))
->appendChild(
pht(
'You can not unlink this account because it is not linked.'))
->addCancelButton($this->getDoneURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
private function renderNotUnlinkableErrorDialog(
PhabricatorAuthProvider $provider) {
PhabricatorAuthProvider $provider,
$done_uri) {
$dialog = id(new AphrontDialogView())
->setUser($this->getRequest()->getUser())
return $this->newDialog()
->setTitle(pht('Permanent Account Link'))
->appendChild(
pht(
'You can not unlink this account because the administrator has '.
'configured Phabricator to make links to %s accounts permanent.',
'configured Phabricator to make links to "%s" accounts permanent.',
$provider->getProviderName()))
->addCancelButton($this->getDoneURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
->addCancelButton($done_uri);
}
private function renderOnlyUsableAccountConfirmDialog(array $confirmations) {
private function renderOnlyUsableAccountConfirmDialog(
array $confirmations,
$done_uri) {
$confirmations[] = 'only';
return $this->newDialog()
@ -116,28 +108,23 @@ final class PhabricatorAuthUnlinkController
pht(
'If you lose access to your account, you can recover access by '.
'sending yourself an email login link from the login screen.'))
->addCancelButton($this->getDoneURI())
->addCancelButton($done_uri)
->addSubmitButton(pht('Unlink External Account'));
}
private function renderConfirmDialog(array $confirmations) {
private function renderConfirmDialog(
array $confirmations,
PhabricatorAuthProviderConfig $config,
$done_uri) {
$confirmations[] = 'unlink';
$provider = $config->getProvider();
$provider_key = $this->providerKey;
$provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key);
if ($provider) {
$title = pht('Unlink "%s" Account?', $provider->getProviderName());
$body = pht(
'You will no longer be able to use your %s account to '.
'log in to Phabricator.',
$provider->getProviderName());
} else {
$title = pht('Unlink Account?');
$body = pht(
'You will no longer be able to use this account to log in '.
'to Phabricator.');
}
$title = pht('Unlink "%s" Account?', $provider->getProviderName());
$body = pht(
'You will no longer be able to use your %s account to '.
'log in to Phabricator.',
$provider->getProviderName());
return $this->newDialog()
->setTitle($title)
@ -148,7 +135,7 @@ final class PhabricatorAuthUnlinkController
'Note: Unlinking an authentication provider will terminate any '.
'other active login sessions.'))
->addSubmitButton(pht('Unlink Account'))
->addCancelButton($this->getDoneURI());
->addCancelButton($done_uri);
}
}

View file

@ -9,20 +9,38 @@ final class PhabricatorEmailLoginController
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$is_logged_in = $viewer->isLoggedIn();
$e_email = true;
$e_captcha = true;
$errors = array();
$v_email = $request->getStr('email');
if ($is_logged_in) {
if (!$this->isPasswordAuthEnabled()) {
return $this->newDialog()
->setTitle(pht('No Password Auth'))
->appendParagraph(
pht(
'Password authentication is not enabled and you are already '.
'logged in. There is nothing for you here.'))
->addCancelButton('/', pht('Continue'));
}
$v_email = $viewer->loadPrimaryEmailAddress();
} else {
$v_email = $request->getStr('email');
}
if ($request->isFormPost()) {
$e_email = null;
$e_captcha = pht('Again');
$captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
if (!$captcha_ok) {
$errors[] = pht('Captcha response is incorrect, try again.');
$e_captcha = pht('Invalid');
if (!$is_logged_in) {
$captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
if (!$captcha_ok) {
$errors[] = pht('Captcha response is incorrect, try again.');
$e_captcha = pht('Invalid');
}
}
if (!strlen($v_email)) {
@ -76,10 +94,24 @@ final class PhabricatorEmailLoginController
}
if (!$errors) {
$body = $this->newAccountLoginMailBody($target_user);
$body = $this->newAccountLoginMailBody(
$target_user,
$is_logged_in);
if ($is_logged_in) {
$subject = pht('[Phabricator] Account Password Link');
$instructions = pht(
'An email has been sent containing a link you can use to set '.
'a password for your account.');
} else {
$subject = pht('[Phabricator] Account Login Link');
$instructions = pht(
'An email has been sent containing a link you can use to log '.
'in to your account.');
}
$mail = id(new PhabricatorMetaMTAMail())
->setSubject(pht('[Phabricator] Account Login Link'))
->setSubject($subject)
->setForceDelivery(true)
->addRawTos(array($target_email->getAddress()))
->setBody($body)
@ -88,8 +120,7 @@ final class PhabricatorEmailLoginController
return $this->newDialog()
->setTitle(pht('Check Your Email'))
->setShortTitle(pht('Email Sent'))
->appendParagraph(
pht('An email has been sent with a link you can use to log in.'))
->appendParagraph($instructions)
->addCancelButton('/', pht('Done'));
}
}
@ -99,33 +130,47 @@ final class PhabricatorEmailLoginController
->setViewer($viewer);
if ($this->isPasswordAuthEnabled()) {
$form->appendRemarkupInstructions(
pht(
'To reset your password, provide your email address. An email '.
'with a login link will be sent to you.'));
if ($is_logged_in) {
$title = pht('Set Password');
$form->appendRemarkupInstructions(
pht(
'A password reset link will be sent to your primary email '.
'address. Follow the link to set an account password.'));
} else {
$title = pht('Password Reset');
$form->appendRemarkupInstructions(
pht(
'To reset your password, provide your email address. An email '.
'with a login link will be sent to you.'));
}
} else {
$title = pht('Email Login');
$form->appendRemarkupInstructions(
pht(
'To access your account, provide your email address. An email '.
'with a login link will be sent to you.'));
}
if ($is_logged_in) {
$address_control = new AphrontFormStaticControl();
} else {
$address_control = id(new AphrontFormTextControl())
->setName('email')
->setError($e_email);
}
$address_control
->setLabel(pht('Email Address'))
->setValue($v_email);
$form
->appendControl(
id(new AphrontFormTextControl())
->setLabel(pht('Email Address'))
->setName('email')
->setValue($v_email)
->setError($e_email))
->appendControl(
->appendControl($address_control);
if (!$is_logged_in) {
$form->appendControl(
id(new AphrontFormRecaptchaControl())
->setLabel(pht('Captcha'))
->setError($e_captcha));
if ($this->isPasswordAuthEnabled()) {
$title = pht('Password Reset');
} else {
$title = pht('Email Login');
}
return $this->newDialog()
@ -137,7 +182,10 @@ final class PhabricatorEmailLoginController
->addSubmitButton(pht('Send Email'));
}
private function newAccountLoginMailBody(PhabricatorUser $user) {
private function newAccountLoginMailBody(
PhabricatorUser $user,
$is_logged_in) {
$engine = new PhabricatorAuthSessionEngine();
$uri = $engine->getOneTimeLoginURI(
$user,
@ -148,7 +196,12 @@ final class PhabricatorEmailLoginController
$have_passwords = $this->isPasswordAuthEnabled();
if ($have_passwords) {
if ($is_serious) {
if ($is_logged_in) {
$body = pht(
'You can use this link to set a password on your account:'.
"\n\n %s\n",
$uri);
} else if ($is_serious) {
$body = pht(
"You can use this link to reset your Phabricator password:".
"\n\n %s\n",

View file

@ -32,7 +32,7 @@ final class PhabricatorAuthNewController
$provider_class = get_class($provider);
$provider_uri = id(new PhutilURI('/config/edit/'))
->setQueryParam('provider', $provider_class);
->replaceQueryParam('provider', $provider_class);
$provider_uri = $this->getApplicationURI($provider_uri);
$already_exists = isset($configured_classes[get_class($provider)]);

View file

@ -0,0 +1,40 @@
<?php
final class PhabricatorAuthChallengeStatusController
extends PhabricatorAuthController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$now = PhabricatorTime::getNow();
$result = new PhabricatorAuthChallengeUpdate();
$challenge = id(new PhabricatorAuthChallengeQuery())
->setViewer($viewer)
->withIDs(array($id))
->withUserPHIDs(array($viewer->getPHID()))
->withChallengeTTLBetween($now, null)
->executeOne();
if ($challenge) {
$config = id(new PhabricatorAuthFactorConfigQuery())
->setViewer($viewer)
->withPHIDs(array($challenge->getFactorPHID()))
->executeOne();
if ($config) {
$provider = $config->getFactorProvider();
$factor = $provider->getFactor();
$result = $factor->newChallengeStatusView(
$config,
$provider,
$viewer,
$challenge);
}
}
return id(new AphrontAjaxResponse())
->setContent($result->newContent());
}
}

View file

@ -45,7 +45,7 @@ final class PhabricatorAuthFactorProviderEditController
foreach ($factors as $factor_key => $factor) {
$factor_uri = id(new PhutilURI('/mfa/edit/'))
->setQueryParam('providerFactorKey', $factor_key);
->replaceQueryParam('providerFactorKey', $factor_key);
$factor_uri = $this->getApplicationURI($factor_uri);
$is_enabled = $factor->canCreateNewProvider();

View file

@ -42,7 +42,7 @@ final class PhabricatorAuthMainMenuBarExtension
$uri = new PhutilURI('/auth/start/');
if ($controller) {
$path = $controller->getRequest()->getPath();
$uri->setQueryParam('next', $path);
$uri->replaceQueryParam('next', $path);
}
return id(new PHUIButtonView())

View file

@ -80,6 +80,14 @@ abstract class PhabricatorAuthFactor extends Phobject {
return array();
}
public function newChallengeStatusView(
PhabricatorAuthFactorConfig $config,
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $viewer,
PhabricatorAuthChallenge $challenge) {
return null;
}
/**
* Is this a factor which depends on the user's contact number?
*
@ -210,8 +218,6 @@ abstract class PhabricatorAuthFactor extends Phobject {
get_class($this)));
}
$result->setIssuedChallenges($challenges);
return $result;
}
@ -242,8 +248,6 @@ abstract class PhabricatorAuthFactor extends Phobject {
get_class($this)));
}
$result->setIssuedChallenges($challenges);
return $result;
}
@ -339,9 +343,18 @@ abstract class PhabricatorAuthFactor extends Phobject {
->setIcon('fa-commenting', 'green');
}
return id(new PHUIFormTimerControl())
$control = id(new PHUIFormTimerControl())
->setIcon($icon)
->appendChild($error);
$status_challenge = $result->getStatusChallenge();
if ($status_challenge) {
$id = $status_challenge->getID();
$uri = "/auth/mfa/challenge/status/{$id}/";
$control->setUpdateURI($uri);
}
return $control;
}

View file

@ -11,6 +11,7 @@ final class PhabricatorAuthFactorResult
private $value;
private $issuedChallenges = array();
private $icon;
private $statusChallenge;
public function setAnsweredChallenge(PhabricatorAuthChallenge $challenge) {
if (!$challenge->getIsAnsweredChallenge()) {
@ -34,6 +35,15 @@ final class PhabricatorAuthFactorResult
return $this->answeredChallenge;
}
public function setStatusChallenge(PhabricatorAuthChallenge $challenge) {
$this->statusChallenge = $challenge;
return $this;
}
public function getStatusChallenge() {
return $this->statusChallenge;
}
public function getIsValid() {
return (bool)$this->getAnsweredChallenge();
}
@ -83,16 +93,6 @@ final class PhabricatorAuthFactorResult
return $this->value;
}
public function setIssuedChallenges(array $issued_challenges) {
assert_instances_of($issued_challenges, 'PhabricatorAuthChallenge');
$this->issuedChallenges = $issued_challenges;
return $this;
}
public function getIssuedChallenges() {
return $this->issuedChallenges;
}
public function setIcon(PHUIIconView $icon) {
$this->icon = $icon;
return $this;

View file

@ -585,7 +585,7 @@ final class PhabricatorDuoAuthFactor
$result = $this->newDuoFuture($provider)
->setHTTPMethod('GET')
->setMethod('auth_status', $parameters)
->setTimeout(5)
->setTimeout(3)
->resolve();
$state = $result['response']['result'];
@ -661,15 +661,6 @@ final class PhabricatorDuoAuthFactor
PhabricatorAuthFactorResult $result) {
$control = $this->newAutomaticControl($result);
if (!$control) {
$result = $this->newResult()
->setIsContinue(true)
->setErrorMessage(
pht(
'A challenge has been sent to your phone. Open the Duo '.
'application and confirm the challenge, then continue.'));
$control = $this->newAutomaticControl($result);
}
$control
->setLabel(pht('Duo'))
@ -689,7 +680,27 @@ final class PhabricatorDuoAuthFactor
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
return $this->newResult();
$result = $this->newResult()
->setIsContinue(true)
->setErrorMessage(
pht(
'A challenge has been sent to your phone. Open the Duo '.
'application and confirm the challenge, then continue.'));
$challenge = $this->getChallengeForCurrentContext(
$config,
$viewer,
$challenges);
if ($challenge) {
$result
->setStatusChallenge($challenge)
->setIcon(
id(new PHUIIconView())
->setIcon('fa-refresh', 'green ph-spin'));
}
return $result;
}
private function newDuoFuture(PhabricatorAuthFactorProvider $provider) {
@ -790,4 +801,54 @@ final class PhabricatorDuoAuthFactor
$hostname));
}
public function newChallengeStatusView(
PhabricatorAuthFactorConfig $config,
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $viewer,
PhabricatorAuthChallenge $challenge) {
$duo_xaction = $challenge->getChallengeKey();
$parameters = array(
'txid' => $duo_xaction,
);
$default_result = id(new PhabricatorAuthChallengeUpdate())
->setRetry(true);
try {
$result = $this->newDuoFuture($provider)
->setHTTPMethod('GET')
->setMethod('auth_status', $parameters)
->setTimeout(5)
->resolve();
$state = $result['response']['result'];
} catch (HTTPFutureCURLResponseStatus $exception) {
// If we failed or timed out, retry. Usually, this is a timeout.
return id(new PhabricatorAuthChallengeUpdate())
->setRetry(true);
}
// For now, don't update the view for anything but an "Allow". Updates
// here are just about providing more visual feedback for user convenience.
if ($state !== 'allow') {
return id(new PhabricatorAuthChallengeUpdate())
->setRetry(false);
}
$icon = id(new PHUIIconView())
->setIcon('fa-check-circle-o', 'green');
$view = id(new PHUIFormTimerControl())
->setIcon($icon)
->appendChild(pht('You responded to this challenge correctly.'))
->newTimerView();
return id(new PhabricatorAuthChallengeUpdate())
->setState('allow')
->setRetry(false)
->setMarkup($view);
}
}

View file

@ -80,11 +80,6 @@ final class PhabricatorDuoFuture
$host = $this->apiHostname;
$host = phutil_utf8_strtolower($host);
$uri = id(new PhutilURI(''))
->setProtocol('https')
->setDomain($host)
->setPath($path);
$data = $this->parameters;
$date = date('r');
@ -109,11 +104,19 @@ final class PhabricatorDuoFuture
$signature = new PhutilOpaqueEnvelope($signature);
if ($http_method === 'GET') {
$uri->setQueryParams($data);
$data = array();
$uri_data = $data;
$body_data = array();
} else {
$uri_data = array();
$body_data = $data;
}
$future = id(new HTTPSFuture($uri, $data))
$uri = id(new PhutilURI('', $uri_data))
->setProtocol('https')
->setDomain($host)
->setPath($path);
$future = id(new HTTPSFuture($uri, $body_data))
->setHTTPBasicAuthCredentials($this->integrationKey, $signature)
->setMethod($http_method)
->addHeader('Accept', 'application/json')

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorAuthLinkMessageType
extends PhabricatorAuthMessageType {
const MESSAGEKEY = 'auth.link';
public function getDisplayName() {
return pht('Unlinked Account Instructions');
}
public function getShortDescription() {
return pht(
'Guidance shown after a user logs in with an email link and is '.
'prompted to link an external account.');
}
}

View file

@ -161,7 +161,7 @@ abstract class PhabricatorAuthProvider extends Phobject {
abstract public function processLoginRequest(
PhabricatorAuthLoginController $controller);
public function buildLinkForm(PhabricatorAuthLinkController $controller) {
public function buildLinkForm($controller) {
return $this->renderLoginForm($controller->getRequest(), $mode = 'link');
}
@ -220,9 +220,7 @@ abstract class PhabricatorAuthProvider extends Phobject {
$adapter->getAdapterDomain(),
$account_id);
if (!$account) {
$account = id(new PhabricatorExternalAccount())
->setAccountType($adapter->getAdapterType())
->setAccountDomain($adapter->getAdapterDomain())
$account = $this->newExternalAccount()
->setAccountID($account_id);
}
@ -299,8 +297,18 @@ abstract class PhabricatorAuthProvider extends Phobject {
return false;
}
public function getDefaultExternalAccount() {
throw new PhutilMethodNotImplementedException();
public function newDefaultExternalAccount() {
return $this->newExternalAccount();
}
protected function newExternalAccount() {
$config = $this->getProviderConfig();
$adapter = $this->getAdapter();
return id(new PhabricatorExternalAccount())
->setAccountType($adapter->getAdapterType())
->setAccountDomain($adapter->getAdapterDomain())
->setProviderConfigPHID($config->getPHID());
}
public function getLoginOrder() {
@ -438,12 +446,13 @@ abstract class PhabricatorAuthProvider extends Phobject {
$uri = $attributes['uri'];
$uri = new PhutilURI($uri);
$params = $uri->getQueryParams();
$uri->setQueryParams(array());
$params = $uri->getQueryParamsAsPairList();
$uri->removeAllQueryParams();
$content = array($button);
foreach ($params as $key => $value) {
foreach ($params as $pair) {
list($key, $value) = $pair;
$content[] = phutil_tag(
'input',
array(

View file

@ -159,8 +159,7 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
return $dialog;
}
public function buildLinkForm(
PhabricatorAuthLinkController $controller) {
public function buildLinkForm($controller) {
throw new Exception(pht("Password providers can't be linked."));
}
@ -359,14 +358,6 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
return true;
}
public function getDefaultExternalAccount() {
$adapter = $this->getAdapter();
return id(new PhabricatorExternalAccount())
->setAccountType($adapter->getAdapterType())
->setAccountDomain($adapter->getAdapterDomain());
}
protected function willSaveAccount(PhabricatorExternalAccount $account) {
parent::willSaveAccount($account);
$account->setUserPHID($account->getAccountID());

View file

@ -21,6 +21,7 @@ final class PhabricatorExternalAccountQuery
private $userPHIDs;
private $needImages;
private $accountSecrets;
private $providerConfigPHIDs;
public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
@ -62,6 +63,11 @@ final class PhabricatorExternalAccountQuery
return $this;
}
public function withProviderConfigPHIDs(array $phids) {
$this->providerConfigPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorExternalAccount();
}
@ -71,6 +77,26 @@ final class PhabricatorExternalAccountQuery
}
protected function willFilterPage(array $accounts) {
$viewer = $this->getViewer();
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->withPHIDs(mpull($accounts, 'getProviderConfigPHID'))
->execute();
$configs = mpull($configs, null, 'getPHID');
foreach ($accounts as $key => $account) {
$config_phid = $account->getProviderConfigPHID();
$config = idx($configs, $config_phid);
if (!$config) {
unset($accounts[$key]);
continue;
}
$account->attachProviderConfig($config);
}
if ($this->needImages) {
$file_phids = mpull($accounts, 'getProfileImagePHID');
$file_phids = array_filter($file_phids);
@ -161,6 +187,13 @@ final class PhabricatorExternalAccountQuery
$this->accountSecrets);
}
if ($this->providerConfigPHIDs !== null) {
$where[] = qsprintf(
$conn,
'providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
}
return $where;
}

View file

@ -0,0 +1,44 @@
<?php
final class PhabricatorAuthChallengeUpdate
extends Phobject {
private $retry = false;
private $state;
private $markup;
public function setRetry($retry) {
$this->retry = $retry;
return $this;
}
public function getRetry() {
return $this->retry;
}
public function setState($state) {
$this->state = $state;
return $this;
}
public function getState() {
return $this->state;
}
public function setMarkup($markup) {
$this->markup = $markup;
return $this;
}
public function getMarkup() {
return $this->markup;
}
public function newContent() {
return array(
'retry' => $this->getRetry(),
'state' => $this->getState(),
'markup' => $this->getMarkup(),
);
}
}

View file

@ -234,7 +234,7 @@ final class PhabricatorCalendarImportViewController
$all_uri = $this->getApplicationURI('import/log/');
$all_uri = (string)id(new PhutilURI($all_uri))
->setQueryParam('importSourcePHID', $import->getPHID());
->replaceQueryParam('importSourcePHID', $import->getPHID());
$all_button = id(new PHUIButtonView())
->setTag('a')
@ -273,8 +273,8 @@ final class PhabricatorCalendarImportViewController
$all_uri = $this->getApplicationURI();
$all_uri = (string)id(new PhutilURI($all_uri))
->setQueryParam('importSourcePHID', $import->getPHID())
->setQueryParam('display', 'list');
->replaceQueryParam('importSourcePHID', $import->getPHID())
->replaceQueryParam('display', 'list');
$all_button = id(new PHUIButtonView())
->setTag('a')

View file

@ -11,8 +11,7 @@ final class PhabricatorChatLogChannelLogController
$viewer = $request->getViewer();
$id = $request->getURIData('channelID');
$uri = clone $request->getRequestURI();
$uri->setQueryParams(array());
$uri = new PhutilURI($request->getPath());
$pager = new AphrontCursorPagerView();
$pager->setURI($uri);

View file

@ -15,6 +15,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
$defined_keys = PhabricatorApplicationConfigOptions::loadAllOptions();
$stack = PhabricatorEnv::getConfigSourceStack();
$stack = $stack->getStack();
foreach ($all_keys as $key) {
if (isset($defined_keys[$key])) {
continue;
@ -48,9 +51,6 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
->setName($name)
->setSummary($summary);
$stack = PhabricatorEnv::getConfigSourceStack();
$stack = $stack->getStack();
$found = array();
$found_local = false;
$found_database = false;
@ -85,6 +85,101 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
}
}
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
foreach ($defined_keys as $key => $value) {
$option = idx($options, $key);
if (!$option) {
continue;
}
if (!$option->getLocked()) {
continue;
}
$found_database = false;
foreach ($stack as $source_key => $source) {
$value = $source->getKeys(array($key));
if ($value) {
if ($source instanceof PhabricatorConfigDatabaseSource) {
$found_database = true;
break;
}
}
}
if (!$found_database) {
continue;
}
// NOTE: These are values which we don't let you edit directly, but edit
// via other UI workflows. For now, don't raise this warning about them.
// In the future, before we stop reading database configuration for
// locked values, we either need to add a flag which lets these values
// continue reading from the database or move them to some other storage
// mechanism.
$soft_locks = array(
'phabricator.uninstalled-applications',
'phabricator.application-settings',
'config.ignore-issues',
);
$soft_locks = array_fuse($soft_locks);
if (isset($soft_locks[$key])) {
continue;
}
$doc_name = 'Configuration Guide: Locked and Hidden Configuration';
$doc_href = PhabricatorEnv::getDoclink($doc_name);
$set_command = phutil_tag(
'tt',
array(),
csprintf(
'bin/config set %R <value>',
$key));
$summary = pht(
'Configuration value "%s" is locked, but has a value in the database.',
$key);
$message = pht(
'The configuration value "%s" is locked (so it can not be edited '.
'from the web UI), but has a database value. Usually, this means '.
'that it was previously not locked, you set it using the web UI, '.
'and it later became locked.'.
"\n\n".
'You should copy this configuration value in a local configuration '.
'source (usually by using %s) and then remove it from the database '.
'with the command below.'.
"\n\n".
'For more information on locked and hidden configuration, including '.
'details about this setup issue, see %s.'.
"\n\n".
'This database value is currently respected, but a future version '.
'of Phabricator will stop respecting database values for locked '.
'configuration options.',
$key,
$set_command,
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
$doc_name));
$command = csprintf(
'phabricator/ $ ./bin/config delete --database %R',
$key);
$this->newIssue('config.locked.'.$key)
->setShortName(pht('Deprecated Config Source'))
->setName(
pht(
'Locked Configuration Option "%s" Has Database Value',
$key))
->setSummary($summary)
->setMessage($message)
->addCommand($command)
->addPhabricatorConfig($key);
}
if (PhabricatorEnv::getEnvConfig('feed.http-hooks')) {
$this->newIssue('config.deprecated.feed.http-hooks')

View file

@ -40,7 +40,7 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
$base_uri = id(new PhutilURI($base_uri))
->setPath($send_path)
->setQueryParam($expect_key, $expect_value);
->replaceQueryParam($expect_key, $expect_value);
$self_future = id(new HTTPSFuture($base_uri))
->addHeader('X-Phabricator-SelfCheck', 1)

View file

@ -41,7 +41,12 @@ final class PhabricatorPHDConfigOptions
"If you are running a cluster, this limit applies separately ".
"to each instance of `phd`. For example, if this limit is set ".
"to `4` and you have three hosts running daemons, the effective ".
"global limit will be 12.")),
"global limit will be 12.".
"\n\n".
"After changing this value, you must restart the daemons. Most ".
"configuration changes are picked up by the daemons ".
"automatically, but pool sizes can not be changed without a ".
"restart.")),
$this->newOption('phd.verbose', 'bool', false)
->setLocked(true)
->setBoolOptions(

View file

@ -188,7 +188,7 @@ final class ConpherenceViewController extends
} else {
// user not logged in so give them a login button.
$login_href = id(new PhutilURI('/auth/start/'))
->setQueryParam('next', '/'.$conpherence->getMonogram());
->replaceQueryParam('next', '/'.$conpherence->getMonogram());
return id(new PHUIFormLayoutView())
->addClass('login-to-participate')
->appendInstructions(pht('Log in to join this room and participate.'))

View file

@ -287,7 +287,7 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
$edit_uri = "/dashboard/panel/edit/{$panel_id}/";
$edit_uri = new PhutilURI($edit_uri);
if ($dashboard_id) {
$edit_uri->setQueryParam('dashboardID', $dashboard_id);
$edit_uri->replaceQueryParam('dashboardID', $dashboard_id);
}
$action_edit = id(new PHUIIconView())
@ -303,7 +303,7 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
$remove_uri = "/dashboard/removepanel/{$dashboard_id}/";
$remove_uri = id(new PhutilURI($remove_uri))
->setQueryParam('panelPHID', $panel_phid);
->replaceQueryParam('panelPHID', $panel_phid);
$action_remove = id(new PHUIIconView())
->setIcon('fa-trash-o')

View file

@ -113,11 +113,11 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
$dashboard_id = $this->dashboard->getID();
$create_uri = id(new PhutilURI('/dashboard/panel/create/'))
->setQueryParam('dashboardID', $dashboard_id)
->setQueryParam('column', $column);
->replaceQueryParam('dashboardID', $dashboard_id)
->replaceQueryParam('column', $column);
$add_uri = id(new PhutilURI('/dashboard/addpanel/'.$dashboard_id.'/'))
->setQueryParam('column', $column);
->replaceQueryParam('column', $column);
$create_button = id(new PHUIButtonView())
->setTag('a')

View file

@ -71,7 +71,7 @@ final class DifferentialDiffCreateController extends DifferentialController {
$uri = $this->getApplicationURI("diff/{$diff_id}/");
$uri = new PhutilURI($uri);
if ($revision) {
$uri->setQueryParam('revisionID', $revision->getID());
$uri->replaceQueryParam('revisionID', $revision->getID());
}
return id(new AphrontRedirectResponse())->setURI($uri);

View file

@ -1098,7 +1098,8 @@ final class DifferentialRevisionViewController
// D123.vs123.id123.whitespaceignore-all.diff
// lame but nice to include these options
$file_name = ltrim($request_uri->getPath(), '/').'.';
foreach ($request_uri->getQueryParams() as $key => $value) {
foreach ($request_uri->getQueryParamsAsPairList() as $pair) {
list($key, $value) = $pair;
if ($key == 'download') {
continue;
}

View file

@ -7,7 +7,11 @@ final class DifferentialBuildableEngine
$object = $this->getObject();
if ($object instanceof DifferentialDiff) {
return $object->getRevision();
if ($object->getRevisionID()) {
return $object->getRevision();
} else {
return null;
}
}
return $object;

View file

@ -358,7 +358,7 @@ final class DifferentialChangesetListView extends AphrontView {
if ($this->standaloneURI) {
$uri = new PhutilURI($this->standaloneURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$uri = $this->appendDefaultQueryParams($uri, $qparams);
$meta['standaloneURI'] = (string)$uri;
}
@ -381,7 +381,7 @@ final class DifferentialChangesetListView extends AphrontView {
if ($this->leftRawFileURI) {
if ($change != DifferentialChangeType::TYPE_ADD) {
$uri = new PhutilURI($this->leftRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$uri = $this->appendDefaultQueryParams($uri, $qparams);
$meta['leftURI'] = (string)$uri;
}
}
@ -390,7 +390,7 @@ final class DifferentialChangesetListView extends AphrontView {
if ($change != DifferentialChangeType::TYPE_DELETE &&
$change != DifferentialChangeType::TYPE_MULTICOPY) {
$uri = new PhutilURI($this->rightRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$uri = $this->appendDefaultQueryParams($uri, $qparams);
$meta['rightURI'] = (string)$uri;
}
}
@ -421,4 +421,23 @@ final class DifferentialChangesetListView extends AphrontView {
}
private function appendDefaultQueryParams(PhutilURI $uri, array $params) {
// Add these default query parameters to the query string if they do not
// already exist.
$have = array();
foreach ($uri->getQueryParamsAsPairList() as $pair) {
list($key, $value) = $pair;
$have[$key] = true;
}
foreach ($params as $key => $value) {
if (!isset($have[$key])) {
$uri->appendQueryParam($key, $value);
}
}
return $uri;
}
}

View file

@ -709,8 +709,6 @@ final class DiffusionBrowseController extends DiffusionController {
'path' => $path,
));
$before_uri->setQueryParams($request->getRequestURI()->getQueryParams());
$before_uri = $before_uri->alter('before', null);
$before_uri = $before_uri->alter('renamed', $renamed);
$before_uri = $before_uri->alter('follow', $follow);

View file

@ -81,12 +81,12 @@ abstract class DiffusionView extends AphrontView {
}
if (isset($details['external'])) {
$href = id(new PhutilURI('/diffusion/external/'))
->setQueryParams(
array(
'uri' => idx($details, 'external'),
'id' => idx($details, 'hash'),
));
$params = array(
'uri' => idx($details, 'external'),
'id' => idx($details, 'hash'),
);
$href = new PhutilURI('/diffusion/external/', $params);
$tip = pht('Browse External');
} else {
$href = $this->getDiffusionRequest()->generateURI(

View file

@ -111,15 +111,15 @@ final class DivinerSymbolRemarkupRule extends PhutilRemarkupRule {
// Here, we're generating comment text or something like that. Just
// link to Diviner and let it sort things out.
$href = id(new PhutilURI('/diviner/find/'))
->setQueryParams(
array(
'book' => $ref->getBook(),
'name' => $ref->getName(),
'type' => $ref->getType(),
'context' => $ref->getContext(),
'jump' => true,
));
$params = array(
'book' => $ref->getBook(),
'name' => $ref->getName(),
'type' => $ref->getType(),
'context' => $ref->getContext(),
'jump' => true,
);
$href = new PhutilURI('/diviner/find/', $params);
}
// TODO: This probably is not the best place to do this. Move it somewhere

View file

@ -11,7 +11,7 @@ final class PhabricatorFactHomeController extends PhabricatorFactController {
if ($request->isFormPost()) {
$uri = new PhutilURI('/fact/chart/');
$uri->setQueryParam('y1', $request->getStr('y1'));
$uri->replaceQueryParam('y1', $request->getStr('y1'));
return id(new AphrontRedirectResponse())->setURI($uri);
}

View file

@ -135,7 +135,7 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
$request_uri = id(clone $request->getAbsoluteRequestURI())
->setPath(null)
->setFragment(null)
->setQueryParams(array());
->removeAllQueryParams();
$response->addContentSecurityPolicyURI(
'object-src',

View file

@ -70,7 +70,7 @@ final class PhabricatorFileLightboxController
if (!$viewer->isLoggedIn()) {
$login_href = id(new PhutilURI('/auth/start/'))
->setQueryParam('next', '/'.$file->getMonogram());
->replaceQueryParam('next', '/'.$file->getMonogram());
return id(new PHUIFormLayoutView())
->addClass('phui-comment-panel-empty')
->appendChild(

View file

@ -61,7 +61,7 @@ final class PhabricatorFileTransformListController
$view_href = $file->getURIForTransform($xform);
$view_href = new PhutilURI($view_href);
$view_href->setQueryParam('regenerate', 'true');
$view_href->replaceQueryParam('regenerate', 'true');
$view_text = pht('Regenerate');

View file

@ -149,7 +149,7 @@ final class PhabricatorImageRemarkupRule extends PhutilRemarkupRule {
));
} else {
$src_uri = id(new PhutilURI('/file/imageproxy/'))
->setQueryParam('uri', $uri);
->replaceQueryParam('uri', $uri);
$img = id(new PHUIRemarkupImageView())
->setURI($src_uri)

View file

@ -81,13 +81,13 @@ final class HeraldNewController extends HeraldController {
}
if (!$errors && $done) {
$uri = id(new PhutilURI('edit/'))
->setQueryParams(
array(
'content_type' => $content_type,
'rule_type' => $rule_type,
'targetPHID' => $target_phid,
));
$params = array(
'content_type' => $content_type,
'rule_type' => $rule_type,
'targetPHID' => $target_phid,
);
$uri = new PhutilURI('edit/', $params);
$uri = $this->getApplicationURI($uri);
return id(new AphrontRedirectResponse())->setURI($uri);
}
@ -126,13 +126,13 @@ final class HeraldNewController extends HeraldController {
->addHiddenInput('step', 2)
->appendChild($rule_types);
$params = array(
'content_type' => $content_type,
'step' => '0',
);
$cancel_text = pht('Back');
$cancel_uri = id(new PhutilURI('new/'))
->setQueryParams(
array(
'content_type' => $content_type,
'step' => 0,
));
$cancel_uri = new PhutilURI('new/', $params);
$cancel_uri = $this->getApplicationURI($cancel_uri);
$title = pht('Create Herald Rule: %s',
idx($content_type_map, $content_type));
@ -173,14 +173,14 @@ final class HeraldNewController extends HeraldController {
->setValue($request->getStr('objectName'))
->setLabel(pht('Object')));
$params = array(
'content_type' => $content_type,
'rule_type' => $rule_type,
'step' => 1,
);
$cancel_text = pht('Back');
$cancel_uri = id(new PhutilURI('new/'))
->setQueryParams(
array(
'content_type' => $content_type,
'rule_type' => $rule_type,
'step' => 1,
));
$cancel_uri = new PhutilURI('new/', $params);
$cancel_uri = $this->getApplicationURI($cancel_uri);
$title = pht('Create Herald Rule: %s',
idx($content_type_map, $content_type));

View file

@ -120,7 +120,7 @@ final class HeraldWebhookRequest
public function getErrorTypeForDisplay() {
$map = array(
self::ERRORTYPE_HOOK => pht('Hook Error'),
self::ERRORTYPE_HTTP => pht('HTTP Error'),
self::ERRORTYPE_HTTP => pht('HTTP Status Code'),
self::ERRORTYPE_TIMEOUT => pht('Request Timeout'),
);

View file

@ -47,10 +47,13 @@ final class PhabricatorMemeEngine extends Phobject {
}
public function getGenerateURI() {
return id(new PhutilURI('/macro/meme/'))
->alter('macro', $this->getTemplate())
->alter('above', $this->getAboveText())
->alter('below', $this->getBelowText());
$params = array(
'macro' => $this->getTemplate(),
'above' => $this->getAboveText(),
'below' => $this->getBelowText(),
);
return new PhutilURI('/macro/meme/', $params);
}
public function newAsset() {

View file

@ -55,6 +55,7 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
'subtask/(?P<id>[1-9]\d*)/' => 'ManiphestTaskSubtaskController',
),
'subpriority/' => 'ManiphestSubpriorityController',
'graph/(?P<id>[1-9]\d*)/' => 'ManiphestTaskGraphController',
),
);
}

View file

@ -61,4 +61,102 @@ abstract class ManiphestController extends PhabricatorController {
return $view;
}
final protected function newTaskGraphDropdownMenu(
ManiphestTask $task,
$has_parents,
$has_subtasks,
$include_standalone) {
$viewer = $this->getViewer();
$parents_uri = urisprintf(
'/?subtaskIDs=%d#R',
$task->getID());
$parents_uri = $this->getApplicationURI($parents_uri);
$subtasks_uri = urisprintf(
'/?parentIDs=%d#R',
$task->getID());
$subtasks_uri = $this->getApplicationURI($subtasks_uri);
$dropdown_menu = id(new PhabricatorActionListView())
->setViewer($viewer)
->addAction(
id(new PhabricatorActionView())
->setHref($parents_uri)
->setName(pht('Search Parent Tasks'))
->setDisabled(!$has_parents)
->setIcon('fa-chevron-circle-up'))
->addAction(
id(new PhabricatorActionView())
->setHref($subtasks_uri)
->setName(pht('Search Subtasks'))
->setDisabled(!$has_subtasks)
->setIcon('fa-chevron-circle-down'));
if ($include_standalone) {
$standalone_uri = urisprintf('/graph/%d/', $task->getID());
$standalone_uri = $this->getApplicationURI($standalone_uri);
$dropdown_menu->addAction(
id(new PhabricatorActionView())
->setHref($standalone_uri)
->setName(pht('View Standalone Graph'))
->setIcon('fa-code-fork'));
}
$graph_menu = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-search')
->setText(pht('Search...'))
->setDropdownMenu($dropdown_menu);
return $graph_menu;
}
final protected function newTaskGraphOverflowView(
ManiphestTask $task,
$overflow_message,
$include_standalone) {
$id = $task->getID();
if ($include_standalone) {
$standalone_uri = $this->getApplicationURI("graph/{$id}/");
$standalone_link = id(new PHUIButtonView())
->setTag('a')
->setHref($standalone_uri)
->setColor(PHUIButtonView::GREY)
->setIcon('fa-code-fork')
->setText(pht('View Standalone Graph'));
} else {
$standalone_link = null;
}
$standalone_icon = id(new PHUIIconView())
->setIcon('fa-exclamation-triangle', 'yellow')
->addClass('object-graph-header-icon');
$standalone_view = phutil_tag(
'div',
array(
'class' => 'object-graph-header',
),
array(
$standalone_link,
$standalone_icon,
phutil_tag(
'div',
array(
'class' => 'object-graph-header-message',
),
array(
$overflow_message,
)),
));
return $standalone_view;
}
}

View file

@ -80,7 +80,8 @@ final class ManiphestTaskDetailController extends ManiphestController {
$related_tabs = array();
$graph_menu = null;
$graph_limit = 100;
$graph_limit = 200;
$overflow_message = null;
$task_graph = id(new ManiphestTaskGraph())
->setViewer($viewer)
->setSeedPHID($task->getPHID())
@ -96,61 +97,55 @@ final class ManiphestTaskDetailController extends ManiphestController {
$has_parents = (bool)$parent_list;
$has_subtasks = (bool)$subtask_list;
$search_text = pht('Search...');
// First, get a count of direct parent tasks and subtasks. If there
// are too many of these, we just don't draw anything. You can use
// the search button to browse tasks with the search UI instead.
$direct_count = count($parent_list) + count($subtask_list);
if ($direct_count > $graph_limit) {
$message = pht(
'Task graph too large to display (this task is directly connected '.
'to more than %s other tasks). Use %s to explore connected tasks.',
$graph_limit,
phutil_tag('strong', array(), $search_text));
$message = phutil_tag('em', array(), $message);
$graph_table = id(new PHUIPropertyListView())
->addTextContent($message);
$overflow_message = pht(
'This task is directly connected to more than %s other tasks. '.
'Use %s to browse parents or subtasks, or %s to show more of the '.
'graph.',
new PhutilNumber($graph_limit),
phutil_tag('strong', array(), pht('Search...')),
phutil_tag('strong', array(), pht('View Standalone Graph')));
$graph_table = null;
} else {
// If there aren't too many direct tasks, but there are too many total
// tasks, we'll only render directly connected tasks.
if ($task_graph->isOverLimit()) {
$task_graph->setRenderOnlyAdjacentNodes(true);
$overflow_message = pht(
'This task is connected to more than %s other tasks. '.
'Only direct parents and subtasks are shown here. Use '.
'%s to show more of the graph.',
new PhutilNumber($graph_limit),
phutil_tag('strong', array(), pht('View Standalone Graph')));
}
$graph_table = $task_graph->newGraphTable();
}
$parents_uri = urisprintf(
'/?subtaskIDs=%d#R',
$task->getID());
$parents_uri = $this->getApplicationURI($parents_uri);
if ($overflow_message) {
$overflow_view = $this->newTaskGraphOverflowView(
$task,
$overflow_message,
true);
$subtasks_uri = urisprintf(
'/?parentIDs=%d#R',
$task->getID());
$subtasks_uri = $this->getApplicationURI($subtasks_uri);
$graph_table = array(
$overflow_view,
$graph_table,
);
}
$dropdown_menu = id(new PhabricatorActionListView())
->setViewer($viewer)
->addAction(
id(new PhabricatorActionView())
->setHref($parents_uri)
->setName(pht('Search Parent Tasks'))
->setDisabled(!$has_parents)
->setIcon('fa-chevron-circle-up'))
->addAction(
id(new PhabricatorActionView())
->setHref($subtasks_uri)
->setName(pht('Search Subtasks'))
->setDisabled(!$has_subtasks)
->setIcon('fa-chevron-circle-down'));
$graph_menu = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-search')
->setText($search_text)
->setDropdownMenu($dropdown_menu);
$graph_menu = $this->newTaskGraphDropdownMenu(
$task,
$has_parents,
$has_subtasks,
true);
$related_tabs[] = id(new PHUITabView())
->setName(pht('Task Graph'))
@ -300,9 +295,9 @@ final class ManiphestTaskDetailController extends ManiphestController {
$subtask_form = head($subtask_options);
$form_key = $subtask_form->getIdentifier();
$subtask_uri = id(new PhutilURI("/task/edit/form/{$form_key}/"))
->setQueryParam('parent', $id)
->setQueryParam('template', $id)
->setQueryParam('status', ManiphestTaskStatus::getDefaultStatus());
->replaceQueryParam('parent', $id)
->replaceQueryParam('template', $id)
->replaceQueryParam('status', ManiphestTaskStatus::getDefaultStatus());
$subtask_workflow = false;
}

View file

@ -0,0 +1,125 @@
<?php
final class ManiphestTaskGraphController
extends ManiphestController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$task = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$task) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($task->getMonogram(), $task->getURI())
->addTextCrumb(pht('Graph'))
->setBorder(true);
$graph_limit = 2000;
$overflow_message = null;
$task_graph = id(new ManiphestTaskGraph())
->setViewer($viewer)
->setSeedPHID($task->getPHID())
->setLimit($graph_limit)
->loadGraph();
if (!$task_graph->isEmpty()) {
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
$parent_map = $task_graph->getEdges($parent_type);
$subtask_map = $task_graph->getEdges($subtask_type);
$parent_list = idx($parent_map, $task->getPHID(), array());
$subtask_list = idx($subtask_map, $task->getPHID(), array());
$has_parents = (bool)$parent_list;
$has_subtasks = (bool)$subtask_list;
// First, get a count of direct parent tasks and subtasks. If there
// are too many of these, we just don't draw anything. You can use
// the search button to browse tasks with the search UI instead.
$direct_count = count($parent_list) + count($subtask_list);
if ($direct_count > $graph_limit) {
$overflow_message = pht(
'This task is directly connected to more than %s other tasks, '.
'which is too many tasks to display. Use %s to browse parents '.
'or subtasks.',
new PhutilNumber($graph_limit),
phutil_tag('strong', array(), pht('Search...')));
$graph_table = null;
} else {
// If there aren't too many direct tasks, but there are too many total
// tasks, we'll only render directly connected tasks.
if ($task_graph->isOverLimit()) {
$task_graph->setRenderOnlyAdjacentNodes(true);
$overflow_message = pht(
'This task is connected to more than %s other tasks. '.
'Only direct parents and subtasks are shown here.',
new PhutilNumber($graph_limit));
}
$graph_table = $task_graph->newGraphTable();
}
$graph_menu = $this->newTaskGraphDropdownMenu(
$task,
$has_parents,
$has_subtasks,
false);
} else {
$graph_menu = null;
$graph_table = null;
$overflow_message = pht(
'This task has no parent tasks and no subtasks, so there is no '.
'graph to draw.');
}
if ($overflow_message) {
$overflow_view = $this->newTaskGraphOverflowView(
$task,
$overflow_message,
false);
$graph_table = array(
$overflow_view,
$graph_table,
);
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Task Graph'));
if ($graph_menu) {
$header->addActionLink($graph_menu);
}
$tab_view = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($graph_table);
$view = id(new PHUITwoColumnView())
->setFooter($tab_view);
return $this->newPage()
->setTitle(
array(
$task->getMonogram(),
pht('Graph'),
))
->setCrumbs($crumbs)
->appendChild($view);
}
}

View file

@ -47,9 +47,9 @@ final class ManiphestTaskSubtaskController
$subtype = $subtype_map->getSubtype($subtype_key);
$subtask_uri = id(new PhutilURI("/task/edit/form/{$form_key}/"))
->setQueryParam('parent', $id)
->setQueryParam('template', $id)
->setQueryParam('status', ManiphestTaskStatus::getDefaultStatus());
->replaceQueryParam('parent', $id)
->replaceQueryParam('template', $id)
->replaceQueryParam('status', ManiphestTaskStatus::getDefaultStatus());
$subtask_uri = $this->getApplicationURI($subtask_uri);
$item = id(new PHUIObjectItemView())

View file

@ -135,7 +135,7 @@ final class ManiphestTaskListView extends ManiphestView {
if ($this->showBatchControls) {
$href = new PhutilURI('/maniphest/task/edit/'.$task->getID().'/');
if (!$this->showSubpriorityControls) {
$href->setQueryParam('ungrippable', 'true');
$href->replaceQueryParam('ungrippable', 'true');
}
$item->addAction(
id(new PHUIListItemView())

View file

@ -24,6 +24,7 @@ final class PhabricatorMailMailgunAdapter
array(
'api-key' => 'string',
'domain' => 'string',
'api-hostname' => 'string',
));
}
@ -31,12 +32,14 @@ final class PhabricatorMailMailgunAdapter
return array(
'api-key' => null,
'domain' => null,
'api-hostname' => 'api.mailgun.net',
);
}
public function sendMessage(PhabricatorMailExternalMessage $message) {
$api_key = $this->getOption('api-key');
$domain = $this->getOption('domain');
$api_hostname = $this->getOption('api-hostname');
$params = array();
$subject = $message->getSubject();
@ -92,7 +95,8 @@ final class PhabricatorMailMailgunAdapter
}
$mailgun_uri = urisprintf(
'https://api.mailgun.net/v2/%s/messages',
'https://%s/v2/%s/messages',
$api_hostname,
$domain);
$future = id(new HTTPSFuture($mailgun_uri, $params))

View file

@ -54,8 +54,7 @@ final class PhabricatorMetaMTAApplicationEmailPanel
return new Aphront404Response();
}
$uri = $request->getRequestURI();
$uri->setQueryParams(array());
$uri = new PhutilURI($request->getPath());
$new = $request->getStr('new');
$edit = $request->getInt('edit');

View file

@ -302,11 +302,11 @@ final class MultimeterSampleController extends MultimeterController {
if (!strlen($group)) {
$group = null;
}
$uri->setQueryParam('group', $group);
$uri->replaceQueryParam('group', $group);
if ($wipe) {
foreach ($this->getColumnMap() as $key => $column) {
$uri->setQueryParam($key, null);
$uri->removeQueryParam($key);
}
}
@ -317,7 +317,7 @@ final class MultimeterSampleController extends MultimeterController {
$value = (array)$value;
$uri = clone $this->getRequest()->getRequestURI();
$uri->setQueryParam($key, implode(',', $value));
$uri->replaceQueryParam($key, implode(',', $value));
return phutil_tag(
'a',

View file

@ -153,7 +153,7 @@ final class PhabricatorNotificationServerRef
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
$uri->setQueryParam('instance', $instance);
$uri->replaceQueryParam('instance', $instance);
}
return $uri;

View file

@ -25,7 +25,7 @@ final class PhabricatorNotificationPanelController
$notifications_view = $builder->buildView();
$content = $notifications_view->render();
$clear_uri->setQueryParam(
$clear_uri->replaceQueryParam(
'chronoKey',
head($stories)->getChronologicalKey());
} else {

View file

@ -111,7 +111,7 @@ final class PhabricatorNotificationSearchEngine
->setUser($viewer);
$view = $builder->buildView();
$clear_uri->setQueryParam(
$clear_uri->replaceQueryParam(
'chronoKey',
head($notifications)->getChronologicalKey());
} else {

View file

@ -36,7 +36,7 @@ final class PhabricatorOAuthResponse extends AphrontResponse {
$base_uri = $this->getClientURI();
$query_params = $this->buildResponseDict();
foreach ($query_params as $key => $value) {
$base_uri->setQueryParam($key, $value);
$base_uri->replaceQueryParam($key, $value);
}
return $base_uri;
}

View file

@ -256,8 +256,8 @@ final class PhabricatorOAuthServer extends Phobject {
// Any query parameters present in the first URI must be exactly present
// in the second URI.
$need_params = $primary_uri->getQueryParams();
$have_params = $secondary_uri->getQueryParams();
$need_params = $primary_uri->getQueryParamsAsMap();
$have_params = $secondary_uri->getQueryParamsAsMap();
foreach ($need_params as $key => $value) {
if (!array_key_exists($key, $have_params)) {

View file

@ -306,7 +306,7 @@ final class PhabricatorOAuthServerAuthController
foreach ($params as $key => $value) {
if (strlen($value)) {
$full_uri->setQueryParam($key, $value);
$full_uri->replaceQueryParam($key, $value);
}
}

View file

@ -65,11 +65,11 @@ final class PhabricatorOwnersDetailController
$commit_views = array();
$commit_uri = id(new PhutilURI('/diffusion/commit/'))
->setQueryParams(
array(
'package' => $package->getPHID(),
));
$params = array(
'package' => $package->getPHID(),
);
$commit_uri = new PhutilURI('/diffusion/commit/', $params);
$status_concern = DiffusionCommitAuditStatus::CONCERN_RAISED;

View file

@ -63,7 +63,6 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
'welcome/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleWelcomeController',
'create/' => 'PhabricatorPeopleCreateController',
'new/(?P<type>[^/]+)/' => 'PhabricatorPeopleNewController',
'ldap/' => 'PhabricatorPeopleLdapController',
'editprofile/(?P<id>[1-9]\d*)/' =>
'PhabricatorPeopleProfileEditController',
'badges/(?P<id>[1-9]\d*)/' =>

View file

@ -28,10 +28,6 @@ abstract class PhabricatorPeopleController extends PhabricatorController {
if ($viewer->getIsAdmin()) {
$nav->addLabel(pht('User Administration'));
if (PhabricatorLDAPAuthProvider::getLDAPProvider()) {
$nav->addFilter('ldap', pht('Import from LDAP'));
}
$nav->addFilter('logs', pht('Activity Logs'));
$nav->addFilter('invite', pht('Email Invitations'));
}

View file

@ -1,214 +0,0 @@
<?php
final class PhabricatorPeopleLdapController
extends PhabricatorPeopleController {
public function handleRequest(AphrontRequest $request) {
$this->requireApplicationCapability(
PeopleCreateUsersCapability::CAPABILITY);
$admin = $request->getUser();
$content = array();
$form = id(new AphrontFormView())
->setAction($request->getRequestURI()
->alter('search', 'true')->alter('import', null))
->setUser($admin)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('LDAP username'))
->setName('username'))
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setLabel(pht('Password'))
->setName('password'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('LDAP query'))
->setCaption(pht('A filter such as %s.', '(objectClass=*)'))
->setName('query'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Search')));
$panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Import LDAP Users'))
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Import LDAP Users'),
$this->getApplicationURI('/ldap/'));
$nav = $this->buildSideNavView();
$nav->selectFilter('ldap');
$nav->appendChild($content);
if ($request->getStr('import')) {
$nav->appendChild($this->processImportRequest($request));
}
$nav->appendChild($panel);
if ($request->getStr('search')) {
$nav->appendChild($this->processSearchRequest($request));
}
return $this->newPage()
->setTitle(pht('Import LDAP Users'))
->setCrumbs($crumbs)
->setNavigation($nav);
}
private function processImportRequest($request) {
$admin = $request->getUser();
$usernames = $request->getArr('usernames');
$emails = $request->getArr('email');
$names = $request->getArr('name');
$notice_view = new PHUIInfoView();
$notice_view->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
$notice_view->setTitle(pht('Import Successful'));
$notice_view->setErrors(array(
pht('Successfully imported users from LDAP'),
));
$list = new PHUIObjectItemListView();
$list->setNoDataString(pht('No users imported?'));
foreach ($usernames as $username) {
$user = new PhabricatorUser();
$user->setUsername($username);
$user->setRealname($names[$username]);
$email_obj = id(new PhabricatorUserEmail())
->setAddress($emails[$username])
->setIsVerified(1);
try {
id(new PhabricatorUserEditor())
->setActor($admin)
->createNewUser($user, $email_obj);
id(new PhabricatorExternalAccount())
->setUserPHID($user->getPHID())
->setAccountType('ldap')
->setAccountDomain('self')
->setAccountID($username)
->save();
$header = pht('Successfully added %s', $username);
$attribute = null;
$color = 'fa-check green';
} catch (Exception $ex) {
$header = pht('Failed to add %s', $username);
$attribute = $ex->getMessage();
$color = 'fa-times red';
}
$item = id(new PHUIObjectItemView())
->setHeader($header)
->addAttribute($attribute)
->setStatusIcon($color);
$list->addItem($item);
}
return array(
$notice_view,
$list,
);
}
private function processSearchRequest($request) {
$panel = new PHUIBoxView();
$admin = $request->getUser();
$search = $request->getStr('query');
$ldap_provider = PhabricatorLDAPAuthProvider::getLDAPProvider();
if (!$ldap_provider) {
throw new Exception(pht('No LDAP provider enabled!'));
}
$ldap_adapter = $ldap_provider->getAdapter();
$ldap_adapter->setLoginUsername($request->getStr('username'));
$ldap_adapter->setLoginPassword(
new PhutilOpaqueEnvelope($request->getStr('password')));
// This causes us to connect and bind.
// TODO: Clean up this discard mode stuff.
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
$ldap_adapter->getAccountID();
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
$results = $ldap_adapter->searchLDAP('%Q', $search);
foreach ($results as $key => $record) {
$account_id = $ldap_adapter->readLDAPRecordAccountID($record);
if (!$account_id) {
unset($results[$key]);
continue;
}
$info = array(
$account_id,
$ldap_adapter->readLDAPRecordEmail($record),
$ldap_adapter->readLDAPRecordRealName($record),
);
$results[$key] = $info;
$results[$key][] = $this->renderUserInputs($info);
}
$form = id(new AphrontFormView())
->setUser($admin);
$table = new AphrontTableView($results);
$table->setHeaders(
array(
pht('Username'),
pht('Email'),
pht('Real Name'),
pht('Import?'),
));
$form->appendChild($table);
$form->setAction($request->getRequestURI()
->alter('import', 'true')->alter('search', null))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Import')));
$panel->appendChild($form);
return $panel;
}
private function renderUserInputs($user) {
$username = $user[0];
return hsprintf(
'%s%s%s',
phutil_tag(
'input',
array(
'type' => 'checkbox',
'name' => 'usernames[]',
'value' => $username,
)),
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => "email[$username]",
'value' => $user[1],
)),
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => "name[$username]",
'value' => $user[2],
)));
}
}

View file

@ -157,13 +157,10 @@ final class PhabricatorPeopleProfilePictureController
continue;
}
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
$account->getProviderKey());
if ($provider) {
$tip = pht('Picture From %s', $provider->getProviderName());
} else {
$tip = pht('Picture From External Account');
}
$config = $account->getProviderConfig();
$provider = $config->getProvider();
$tip = pht('Picture From %s', $provider->getProviderName());
if ($file->isTransformableImage()) {
$images[$file->getPHID()] = array(

View file

@ -16,8 +16,10 @@ final class PhabricatorExternalAccount extends PhabricatorUserDAO
protected $accountURI;
protected $profileImagePHID;
protected $properties = array();
protected $providerConfigPHID;
private $profileImageFile = self::ATTACHABLE;
private $providerConfig = self::ATTACHABLE;
public function getProfileImageFile() {
return $this->assertAttached($this->profileImageFile);
@ -65,13 +67,6 @@ final class PhabricatorExternalAccount extends PhabricatorUserDAO
) + parent::getConfiguration();
}
public function getPhabricatorUser() {
$tmp_usr = id(new PhabricatorUser())
->makeEphemeral()
->setPHID($this->getPHID());
return $tmp_usr;
}
public function getProviderKey() {
return $this->getAccountType().':'.$this->getAccountDomain();
}
@ -93,13 +88,12 @@ final class PhabricatorExternalAccount extends PhabricatorUserDAO
}
public function isUsableForLogin() {
$key = $this->getProviderKey();
$provider = PhabricatorAuthProvider::getEnabledProviderByKey($key);
if (!$provider) {
$config = $this->getProviderConfig();
if (!$config->getIsEnabled()) {
return false;
}
$provider = $config->getProvider();
if (!$provider->shouldAllowLogin()) {
return false;
}
@ -125,6 +119,14 @@ final class PhabricatorExternalAccount extends PhabricatorUserDAO
return idx($map, $type, pht('"%s" User', $type));
}
public function attachProviderConfig(PhabricatorAuthProviderConfig $config) {
$this->providerConfig = $config;
return $this;
}
public function getProviderConfig() {
return $this->assertAttached($this->providerConfig);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -557,7 +557,7 @@ final class PhabricatorUser
public static function describeValidUsername() {
return pht(
'Usernames must contain only numbers, letters, period, underscore and '.
'Usernames must contain only numbers, letters, period, underscore, and '.
'hyphen, and can not end with a period. They must have no more than %d '.
'characters.',
new PhutilNumber(self::MAXIMUM_USERNAME_LENGTH));

View file

@ -83,9 +83,8 @@ final class PhabricatorUserEmail extends PhabricatorUserDAO {
*/
public static function describeValidAddresses() {
return pht(
"Email addresses should be in the form '%s'. The maximum ".
"length of an email address is %s character(s).",
'user@domain.com',
'Email addresses should be in the form "user@domain.com". The maximum '.
'length of an email address is %s characters.',
new PhutilNumber(self::MAX_ADDRESS_LENGTH));
}

View file

@ -133,7 +133,7 @@ final class PholioMockImagesView extends AphrontView {
);
$login_uri = id(new PhutilURI('/login/'))
->setQueryParam('next', (string)$this->getRequestURI());
->replaceQueryParam('next', (string)$this->getRequestURI());
$config = array(
'mockID' => $mock->getID(),

View file

@ -0,0 +1,22 @@
<?php
final class PhortuneAddPaymentMethodAction
extends PhabricatorSystemAction {
const TYPECONST = 'phortune.payment-method.add';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() {
return 60 / phutil_units('1 hour in seconds');
}
public function getLimitExplanation() {
return pht(
'You are making too many attempts to add payment methods in a short '.
'period of time.');
}
}

View file

@ -134,13 +134,13 @@ final class PhortuneCartCheckoutController
$account_id = $account->getID();
$params = array(
'merchantID' => $merchant->getID(),
'cartID' => $cart->getID(),
);
$payment_method_uri = $this->getApplicationURI("{$account_id}/card/new/");
$payment_method_uri = new PhutilURI($payment_method_uri);
$payment_method_uri->setQueryParams(
array(
'merchantID' => $merchant->getID(),
'cartID' => $cart->getID(),
));
$payment_method_uri = new PhutilURI($payment_method_uri, $params);
$form = id(new AphrontFormView())
->setUser($viewer)

View file

@ -82,6 +82,15 @@ final class PhortunePaymentMethodCreateController
->setProviderPHID($provider->getProviderConfig()->getPHID())
->setStatus(PhortunePaymentMethod::STATUS_ACTIVE);
// Limit the rate at which you can attempt to add payment methods. This
// is intended as a line of defense against using Phortune to validate a
// large list of stolen credit card numbers.
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhortuneAddPaymentMethodAction(),
1);
if (!$errors) {
$errors = $this->processClientErrors(
$provider,
@ -134,7 +143,7 @@ final class PhortunePaymentMethodCreateController
"cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID());
} else if ($subscription_id) {
$next_uri = new PhutilURI($cancel_uri);
$next_uri->setQueryParam('added', true);
$next_uri->replaceQueryParam('added', true);
} else {
$account_uri = $this->getApplicationURI($account->getID().'/');
$next_uri = new PhutilURI($account_uri);

View file

@ -118,8 +118,8 @@ final class PhortuneSubscriptionEditController extends PhortuneController {
$uri = $this->getApplicationURI($account->getID().'/card/new/');
$uri = new PhutilURI($uri);
$uri->setQueryParam('merchantID', $merchant->getID());
$uri->setQueryParam('subscriptionID', $subscription->getID());
$uri->replaceQueryParam('merchantID', $merchant->getID());
$uri->replaceQueryParam('subscriptionID', $subscription->getID());
$add_method_button = phutil_tag(
'a',

View file

@ -348,12 +348,14 @@ final class PhortunePayPalPaymentProvider extends PhortunePaymentProvider {
->setRawPayPalQuery('SetExpressCheckout', $params)
->resolve();
$uri = new PhutilURI('https://www.sandbox.paypal.com/cgi-bin/webscr');
$uri->setQueryParams(
array(
'cmd' => '_express-checkout',
'token' => $result['TOKEN'],
));
$params = array(
'cmd' => '_express-checkout',
'token' => $result['TOKEN'],
);
$uri = new PhutilURI(
'https://www.sandbox.paypal.com/cgi-bin/webscr',
$params);
$cart->setMetadataValue('provider.checkoutURI', (string)$uri);
$cart->save();

View file

@ -273,8 +273,7 @@ abstract class PhortunePaymentProvider extends Phobject {
$app = PhabricatorApplication::getByClass('PhabricatorPhortuneApplication');
$path = $app->getBaseURI().'provider/'.$id.'/'.$action.'/';
$uri = new PhutilURI($path);
$uri->setQueryParams($params);
$uri = new PhutilURI($path, $params);
if ($local) {
return $uri;

View file

@ -229,9 +229,14 @@ final class PhrictionTransactionEditor
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhrictionDocumentContentTransaction::TRANSACTIONTYPE:
$uri = id(new PhutilURI('/phriction/diff/'.$object->getID().'/'))
->alter('l', $this->getOldContent()->getVersion())
->alter('r', $this->getNewContent()->getVersion());
$params = array(
'l' => $this->getOldContent()->getVersion(),
'r' => $this->getNewContent()->getVersion(),
);
$path = '/phriction/diff/'.$object->getID().'/';
$uri = new PhutilURI($path, $params);
$this->contentDiffURI = (string)$uri;
break 2;
default:

View file

@ -66,7 +66,7 @@ final class PonderAddAnswerView extends AphrontView {
if (!$viewer->isLoggedIn()) {
$login_href = id(new PhutilURI('/auth/start/'))
->setQueryParam('next', '/Q'.$question->getID());
->replaceQueryParam('next', '/Q'.$question->getID());
$form = id(new PHUIFormLayoutView())
->addClass('login-to-participate')
->appendChild(

View file

@ -284,7 +284,7 @@ final class PhabricatorProjectBoardViewController
$query_key = $saved_query->getQueryKey();
$bulk_uri = new PhutilURI("/maniphest/bulk/query/{$query_key}/");
$bulk_uri->setQueryParam('board', $this->id);
$bulk_uri->replaceQueryParam('board', $this->id);
return id(new AphrontRedirectResponse())
->setURI($bulk_uri);
@ -878,7 +878,7 @@ final class PhabricatorProjectBoardViewController
}
$uri = $this->getURIWithState($uri)
->setQueryParam('filter', null);
->removeQueryParam('filter');
$item->setHref($uri);
$items[] = $item;
@ -966,12 +966,12 @@ final class PhabricatorProjectBoardViewController
if ($show_hidden) {
$hidden_uri = $this->getURIWithState()
->setQueryParam('hidden', null);
->removeQueryParam('hidden');
$hidden_icon = 'fa-eye-slash';
$hidden_text = pht('Hide Hidden Columns');
} else {
$hidden_uri = $this->getURIWithState()
->setQueryParam('hidden', 'true');
->replaceQueryParam('hidden', 'true');
$hidden_icon = 'fa-eye';
$hidden_text = pht('Show Hidden Columns');
}
@ -999,7 +999,7 @@ final class PhabricatorProjectBoardViewController
->setHref($manage_uri);
$batch_edit_uri = $request->getRequestURI();
$batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL);
$batch_edit_uri->replaceQueryParam('batch', self::BATCH_EDIT_ALL);
$can_batch_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
@ -1090,7 +1090,7 @@ final class PhabricatorProjectBoardViewController
}
$batch_edit_uri = $request->getRequestURI();
$batch_edit_uri->setQueryParam('batch', $column->getID());
$batch_edit_uri->replaceQueryParam('batch', $column->getID());
$can_batch_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
@ -1103,7 +1103,7 @@ final class PhabricatorProjectBoardViewController
->setDisabled(!$can_batch_edit);
$batch_move_uri = $request->getRequestURI();
$batch_move_uri->setQueryParam('move', $column->getID());
$batch_move_uri->replaceQueryParam('move', $column->getID());
$column_items[] = id(new PhabricatorActionView())
->setIcon('fa-arrow-right')
->setName(pht('Move Tasks to Column...'))
@ -1111,7 +1111,7 @@ final class PhabricatorProjectBoardViewController
->setWorkflow(true);
$query_uri = $request->getRequestURI();
$query_uri->setQueryParam('queryColumnID', $column->getID());
$query_uri->replaceQueryParam('queryColumnID', $column->getID());
$column_items[] = id(new PhabricatorActionView())
->setName(pht('View as Query'))
@ -1188,18 +1188,22 @@ final class PhabricatorProjectBoardViewController
$base = new PhutilURI($base);
if ($force || ($this->sortKey != $this->getDefaultSort($project))) {
$base->setQueryParam('order', $this->sortKey);
$base->replaceQueryParam('order', $this->sortKey);
} else {
$base->setQueryParam('order', null);
$base->removeQueryParam('order');
}
if ($force || ($this->queryKey != $this->getDefaultFilter($project))) {
$base->setQueryParam('filter', $this->queryKey);
$base->replaceQueryParam('filter', $this->queryKey);
} else {
$base->setQueryParam('filter', null);
$base->removeQueryParam('filter');
}
$base->setQueryParam('hidden', $this->showHidden ? 'true' : null);
if ($this->showHidden) {
$base->replaceQueryParam('hidden', 'true');
} else {
$base->removeQueryParam('hidden');
}
return $base;
}

View file

@ -41,7 +41,7 @@ final class PhabricatorProjectColumnHideController
$view_uri = $this->getApplicationURI('/board/'.$project_id.'/');
$view_uri = new PhutilURI($view_uri);
foreach ($request->getPassthroughRequestData() as $key => $value) {
$view_uri->setQueryParam($key, $value);
$view_uri->replaceQueryParam($key, $value);
}
if ($column->isDefaultColumn()) {

View file

@ -54,7 +54,7 @@ final class PhabricatorProjectDefaultController
$view_uri = $this->getApplicationURI("board/{$id}/");
$view_uri = new PhutilURI($view_uri);
foreach ($request->getPassthroughRequestData() as $key => $value) {
$view_uri->setQueryParam($key, $value);
$view_uri->replaceQueryParam($key, $value);
}
if ($request->isFormPost()) {

View file

@ -96,7 +96,7 @@ final class ReleephRequestDifferentialCreateController
private function buildReleephRequestURI(ReleephBranch $branch) {
$uri = $branch->getURI('request/');
return id(new PhutilURI($uri))
->setQueryParam('D', $this->revision->getID());
->replaceQueryParam('D', $this->revision->getID());
}
}

View file

@ -820,8 +820,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return $uri;
}
$uri = new PhutilURI($uri);
if (isset($params['lint'])) {
$params['params'] = idx($params, 'params', array()) + array(
'lint' => $params['lint'],
@ -830,11 +828,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
$query = idx($params, 'params', array()) + $query;
if ($query) {
$uri->setQueryParams($query);
}
return $uri;
return new PhutilURI($uri, $query);
}
public function updateURIIndex() {

View file

@ -905,7 +905,7 @@ final class PhabricatorApplicationSearchController
$engine = $this->getSearchEngine();
$nux_uri = $engine->getQueryBaseURI();
$nux_uri = id(new PhutilURI($nux_uri))
->setQueryParam('nux', true);
->replaceQueryParam('nux', true);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-user-plus')
@ -915,7 +915,7 @@ final class PhabricatorApplicationSearchController
if ($is_dev) {
$overheated_uri = $this->getRequest()->getRequestURI()
->setQueryParam('overheated', true);
->replaceQueryParam('overheated', true);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-fire')

View file

@ -14,7 +14,7 @@ final class PhabricatorSearchWorker extends PhabricatorWorker {
'parameters' => $parameters,
),
array(
'priority' => parent::PRIORITY_IMPORT,
'priority' => parent::PRIORITY_INDEX,
'objectPHID' => $phid,
));
}

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