mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-23 21:18:19 +01:00
(stable) Promote 2019 Week 5
This commit is contained in:
commit
c65ad751ab
47 changed files with 1082 additions and 220 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_project.project
|
||||||
|
ADD subtype VARCHAR(64) COLLATE {$COLLATE_TEXT} NOT NULL;
|
|
@ -0,0 +1,2 @@
|
||||||
|
UPDATE {$NAMESPACE}_project.project
|
||||||
|
SET subtype = 'default' WHERE subtype = '';
|
18
resources/sql/autopatches/20190129.project.01.spaces.php
Normal file
18
resources/sql/autopatches/20190129.project.01.spaces.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// See PHI1046. The "spacePHID" column for milestones may have fallen out of
|
||||||
|
// sync; correct all existing values.
|
||||||
|
|
||||||
|
$table = new PhabricatorProject();
|
||||||
|
$conn = $table->establishConnection('w');
|
||||||
|
$table_name = $table->getTableName();
|
||||||
|
|
||||||
|
foreach (new LiskRawMigrationIterator($conn, $table_name) as $project_row) {
|
||||||
|
queryfx(
|
||||||
|
$conn,
|
||||||
|
'UPDATE %R SET spacePHID = %ns
|
||||||
|
WHERE parentProjectPHID = %s AND milestoneNumber IS NOT NULL',
|
||||||
|
$table,
|
||||||
|
$project_row['spacePHID'],
|
||||||
|
$project_row['phid']);
|
||||||
|
}
|
|
@ -55,8 +55,8 @@ foreach (array('text', 'html') as $part) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = $parser->getHeaders();
|
$headers = $parser->getHeaders();
|
||||||
$headers['subject'] = iconv_mime_decode($headers['subject'], 0, 'UTF-8');
|
$headers['subject'] = phutil_decode_mime_header($headers['subject']);
|
||||||
$headers['from'] = iconv_mime_decode($headers['from'], 0, 'UTF-8');
|
$headers['from'] = phutil_decode_mime_header($headers['from']);
|
||||||
|
|
||||||
if ($args->getArg('process-duplicates')) {
|
if ($args->getArg('process-duplicates')) {
|
||||||
$headers['message-id'] = Filesystem::readRandomCharacters(64);
|
$headers['message-id'] = Filesystem::readRandomCharacters(64);
|
||||||
|
|
|
@ -183,7 +183,6 @@ phutil_register_library_map(array(
|
||||||
'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php',
|
'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php',
|
||||||
'AphrontController' => 'aphront/AphrontController.php',
|
'AphrontController' => 'aphront/AphrontController.php',
|
||||||
'AphrontCursorPagerView' => 'view/control/AphrontCursorPagerView.php',
|
'AphrontCursorPagerView' => 'view/control/AphrontCursorPagerView.php',
|
||||||
'AphrontDefaultApplicationConfiguration' => 'aphront/configuration/AphrontDefaultApplicationConfiguration.php',
|
|
||||||
'AphrontDialogResponse' => 'aphront/response/AphrontDialogResponse.php',
|
'AphrontDialogResponse' => 'aphront/response/AphrontDialogResponse.php',
|
||||||
'AphrontDialogView' => 'view/AphrontDialogView.php',
|
'AphrontDialogView' => 'view/AphrontDialogView.php',
|
||||||
'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php',
|
'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php',
|
||||||
|
@ -2235,8 +2234,10 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthFactorProviderEditController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderEditController.php',
|
'PhabricatorAuthFactorProviderEditController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderEditController.php',
|
||||||
'PhabricatorAuthFactorProviderEditEngine' => 'applications/auth/editor/PhabricatorAuthFactorProviderEditEngine.php',
|
'PhabricatorAuthFactorProviderEditEngine' => 'applications/auth/editor/PhabricatorAuthFactorProviderEditEngine.php',
|
||||||
'PhabricatorAuthFactorProviderEditor' => 'applications/auth/editor/PhabricatorAuthFactorProviderEditor.php',
|
'PhabricatorAuthFactorProviderEditor' => 'applications/auth/editor/PhabricatorAuthFactorProviderEditor.php',
|
||||||
|
'PhabricatorAuthFactorProviderEnrollMessageTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderEnrollMessageTransaction.php',
|
||||||
'PhabricatorAuthFactorProviderListController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php',
|
'PhabricatorAuthFactorProviderListController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php',
|
||||||
'PhabricatorAuthFactorProviderMFAEngine' => 'applications/auth/engine/PhabricatorAuthFactorProviderMFAEngine.php',
|
'PhabricatorAuthFactorProviderMFAEngine' => 'applications/auth/engine/PhabricatorAuthFactorProviderMFAEngine.php',
|
||||||
|
'PhabricatorAuthFactorProviderMessageController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderMessageController.php',
|
||||||
'PhabricatorAuthFactorProviderNameTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderNameTransaction.php',
|
'PhabricatorAuthFactorProviderNameTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderNameTransaction.php',
|
||||||
'PhabricatorAuthFactorProviderQuery' => 'applications/auth/query/PhabricatorAuthFactorProviderQuery.php',
|
'PhabricatorAuthFactorProviderQuery' => 'applications/auth/query/PhabricatorAuthFactorProviderQuery.php',
|
||||||
'PhabricatorAuthFactorProviderStatus' => 'applications/auth/constants/PhabricatorAuthFactorProviderStatus.php',
|
'PhabricatorAuthFactorProviderStatus' => 'applications/auth/constants/PhabricatorAuthFactorProviderStatus.php',
|
||||||
|
@ -3061,6 +3062,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php',
|
'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php',
|
||||||
'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php',
|
'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php',
|
||||||
'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php',
|
'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php',
|
||||||
|
'PhabricatorEditorExtension' => 'applications/transactions/engineextension/PhabricatorEditorExtension.php',
|
||||||
|
'PhabricatorEditorExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditorExtensionModule.php',
|
||||||
'PhabricatorEditorMailEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditorMailEngineExtension.php',
|
'PhabricatorEditorMailEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditorMailEngineExtension.php',
|
||||||
'PhabricatorEditorMultipleSetting' => 'applications/settings/setting/PhabricatorEditorMultipleSetting.php',
|
'PhabricatorEditorMultipleSetting' => 'applications/settings/setting/PhabricatorEditorMultipleSetting.php',
|
||||||
'PhabricatorEditorSetting' => 'applications/settings/setting/PhabricatorEditorSetting.php',
|
'PhabricatorEditorSetting' => 'applications/settings/setting/PhabricatorEditorSetting.php',
|
||||||
|
@ -4114,6 +4117,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
|
'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
|
||||||
'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php',
|
'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php',
|
||||||
'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php',
|
'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php',
|
||||||
|
'PhabricatorProjectSubtypeDatasource' => 'applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php',
|
||||||
|
'PhabricatorProjectSubtypesConfigType' => 'applications/project/config/PhabricatorProjectSubtypesConfigType.php',
|
||||||
'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php',
|
'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php',
|
||||||
'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php',
|
'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php',
|
||||||
'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php',
|
'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php',
|
||||||
|
@ -5625,7 +5630,6 @@ phutil_register_library_map(array(
|
||||||
'AphrontCalendarEventView' => 'AphrontView',
|
'AphrontCalendarEventView' => 'AphrontView',
|
||||||
'AphrontController' => 'Phobject',
|
'AphrontController' => 'Phobject',
|
||||||
'AphrontCursorPagerView' => 'AphrontView',
|
'AphrontCursorPagerView' => 'AphrontView',
|
||||||
'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration',
|
|
||||||
'AphrontDialogResponse' => 'AphrontResponse',
|
'AphrontDialogResponse' => 'AphrontResponse',
|
||||||
'AphrontDialogView' => array(
|
'AphrontDialogView' => array(
|
||||||
'AphrontView',
|
'AphrontView',
|
||||||
|
@ -7971,8 +7975,10 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthFactorProviderEditController' => 'PhabricatorAuthFactorProviderController',
|
'PhabricatorAuthFactorProviderEditController' => 'PhabricatorAuthFactorProviderController',
|
||||||
'PhabricatorAuthFactorProviderEditEngine' => 'PhabricatorEditEngine',
|
'PhabricatorAuthFactorProviderEditEngine' => 'PhabricatorEditEngine',
|
||||||
'PhabricatorAuthFactorProviderEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorAuthFactorProviderEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
|
'PhabricatorAuthFactorProviderEnrollMessageTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
|
||||||
'PhabricatorAuthFactorProviderListController' => 'PhabricatorAuthProviderController',
|
'PhabricatorAuthFactorProviderListController' => 'PhabricatorAuthProviderController',
|
||||||
'PhabricatorAuthFactorProviderMFAEngine' => 'PhabricatorEditEngineMFAEngine',
|
'PhabricatorAuthFactorProviderMFAEngine' => 'PhabricatorEditEngineMFAEngine',
|
||||||
|
'PhabricatorAuthFactorProviderMessageController' => 'PhabricatorAuthFactorProviderController',
|
||||||
'PhabricatorAuthFactorProviderNameTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
|
'PhabricatorAuthFactorProviderNameTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
|
||||||
'PhabricatorAuthFactorProviderQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorAuthFactorProviderQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorAuthFactorProviderStatus' => 'Phobject',
|
'PhabricatorAuthFactorProviderStatus' => 'Phobject',
|
||||||
|
@ -8925,6 +8931,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorEditPage' => 'Phobject',
|
'PhabricatorEditPage' => 'Phobject',
|
||||||
'PhabricatorEditType' => 'Phobject',
|
'PhabricatorEditType' => 'Phobject',
|
||||||
'PhabricatorEditor' => 'Phobject',
|
'PhabricatorEditor' => 'Phobject',
|
||||||
|
'PhabricatorEditorExtension' => 'Phobject',
|
||||||
|
'PhabricatorEditorExtensionModule' => 'PhabricatorConfigModule',
|
||||||
'PhabricatorEditorMailEngineExtension' => 'PhabricatorMailEngineExtension',
|
'PhabricatorEditorMailEngineExtension' => 'PhabricatorMailEngineExtension',
|
||||||
'PhabricatorEditorMultipleSetting' => 'PhabricatorSelectSetting',
|
'PhabricatorEditorMultipleSetting' => 'PhabricatorSelectSetting',
|
||||||
'PhabricatorEditorSetting' => 'PhabricatorStringSetting',
|
'PhabricatorEditorSetting' => 'PhabricatorStringSetting',
|
||||||
|
@ -10029,6 +10037,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorConduitResultInterface',
|
'PhabricatorConduitResultInterface',
|
||||||
'PhabricatorColumnProxyInterface',
|
'PhabricatorColumnProxyInterface',
|
||||||
'PhabricatorSpacesInterface',
|
'PhabricatorSpacesInterface',
|
||||||
|
'PhabricatorEditEngineSubtypeInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
||||||
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
||||||
|
@ -10156,6 +10165,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController',
|
'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController',
|
'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||||
|
'PhabricatorProjectSubtypeDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
|
'PhabricatorProjectSubtypesConfigType' => 'PhabricatorJSONConfigType',
|
||||||
'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
||||||
'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction',
|
'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction',
|
||||||
'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
|
|
|
@ -5,55 +5,81 @@
|
||||||
* @task response Response Handling
|
* @task response Response Handling
|
||||||
* @task exception Exception Handling
|
* @task exception Exception Handling
|
||||||
*/
|
*/
|
||||||
abstract class AphrontApplicationConfiguration extends Phobject {
|
final class AphrontApplicationConfiguration
|
||||||
|
extends Phobject {
|
||||||
|
|
||||||
private $request;
|
private $request;
|
||||||
private $host;
|
private $host;
|
||||||
private $path;
|
private $path;
|
||||||
private $console;
|
private $console;
|
||||||
|
|
||||||
abstract public function buildRequest();
|
public function buildRequest() {
|
||||||
abstract public function build404Controller();
|
$parser = new PhutilQueryStringParser();
|
||||||
abstract public function buildRedirectController($uri, $external);
|
|
||||||
|
|
||||||
final public function setRequest(AphrontRequest $request) {
|
$data = array();
|
||||||
|
$data += $_POST;
|
||||||
|
$data += $parser->parseQueryString(idx($_SERVER, 'QUERY_STRING', ''));
|
||||||
|
|
||||||
|
$cookie_prefix = PhabricatorEnv::getEnvConfig('phabricator.cookie-prefix');
|
||||||
|
|
||||||
|
$request = new AphrontRequest($this->getHost(), $this->getPath());
|
||||||
|
$request->setRequestData($data);
|
||||||
|
$request->setApplicationConfiguration($this);
|
||||||
|
$request->setCookiePrefix($cookie_prefix);
|
||||||
|
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build404Controller() {
|
||||||
|
return array(new Phabricator404Controller(), array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildRedirectController($uri, $external) {
|
||||||
|
return array(
|
||||||
|
new PhabricatorRedirectController(),
|
||||||
|
array(
|
||||||
|
'uri' => $uri,
|
||||||
|
'external' => $external,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRequest(AphrontRequest $request) {
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function getRequest() {
|
public function getRequest() {
|
||||||
return $this->request;
|
return $this->request;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function getConsole() {
|
public function getConsole() {
|
||||||
return $this->console;
|
return $this->console;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function setConsole($console) {
|
public function setConsole($console) {
|
||||||
$this->console = $console;
|
$this->console = $console;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function setHost($host) {
|
public function setHost($host) {
|
||||||
$this->host = $host;
|
$this->host = $host;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function getHost() {
|
public function getHost() {
|
||||||
return $this->host;
|
return $this->host;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function setPath($path) {
|
public function setPath($path) {
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function getPath() {
|
public function getPath() {
|
||||||
return $this->path;
|
return $this->path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function willBuildRequest() {}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @phutil-external-symbol class PhabricatorStartup
|
* @phutil-external-symbol class PhabricatorStartup
|
||||||
|
@ -83,6 +109,8 @@ abstract class AphrontApplicationConfiguration extends Phobject {
|
||||||
|
|
||||||
PhabricatorStartup::beginStartupPhase('env.init');
|
PhabricatorStartup::beginStartupPhase('env.init');
|
||||||
|
|
||||||
|
self::readHTTPPOSTData();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PhabricatorEnv::initializeWebEnvironment();
|
PhabricatorEnv::initializeWebEnvironment();
|
||||||
$database_exception = null;
|
$database_exception = null;
|
||||||
|
@ -142,16 +170,10 @@ abstract class AphrontApplicationConfiguration extends Phobject {
|
||||||
$host = AphrontRequest::getHTTPHeader('Host');
|
$host = AphrontRequest::getHTTPHeader('Host');
|
||||||
$path = $_REQUEST['__path__'];
|
$path = $_REQUEST['__path__'];
|
||||||
|
|
||||||
switch ($host) {
|
$application = new self();
|
||||||
default:
|
|
||||||
$config_key = 'aphront.default-application-configuration-class';
|
|
||||||
$application = PhabricatorEnv::newObjectFromConfig($config_key);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$application->setHost($host);
|
$application->setHost($host);
|
||||||
$application->setPath($path);
|
$application->setPath($path);
|
||||||
$application->willBuildRequest();
|
|
||||||
$request = $application->buildRequest();
|
$request = $application->buildRequest();
|
||||||
|
|
||||||
// Now that we have a request, convert the write guard into one which
|
// Now that we have a request, convert the write guard into one which
|
||||||
|
@ -313,7 +335,7 @@ abstract class AphrontApplicationConfiguration extends Phobject {
|
||||||
* parameters.
|
* parameters.
|
||||||
* @task routing
|
* @task routing
|
||||||
*/
|
*/
|
||||||
final private function buildController() {
|
private function buildController() {
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
|
|
||||||
// If we're configured to operate in cluster mode, reject requests which
|
// If we're configured to operate in cluster mode, reject requests which
|
||||||
|
@ -708,4 +730,88 @@ abstract class AphrontApplicationConfiguration extends Phobject {
|
||||||
->setContent($result);
|
->setContent($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function readHTTPPOSTData() {
|
||||||
|
$request_method = idx($_SERVER, 'REQUEST_METHOD');
|
||||||
|
if ($request_method === 'PUT') {
|
||||||
|
// For PUT requests, do nothing: in particular, do NOT read input. This
|
||||||
|
// allows us to stream input later and process very large PUT requests,
|
||||||
|
// like those coming from Git LFS.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// For POST requests, we're going to read the raw input ourselves here
|
||||||
|
// if we can. Among other things, this corrects variable names with
|
||||||
|
// the "." character in them, which PHP normally converts into "_".
|
||||||
|
|
||||||
|
// There are two major considerations here: whether the
|
||||||
|
// `enable_post_data_reading` option is set, and whether the content
|
||||||
|
// type is "multipart/form-data" or not.
|
||||||
|
|
||||||
|
// If `enable_post_data_reading` is off, we're free to read the entire
|
||||||
|
// raw request body and parse it -- and we must, because $_POST and
|
||||||
|
// $_FILES are not built for us. If `enable_post_data_reading` is on,
|
||||||
|
// which is the default, we may not be able to read the body (the
|
||||||
|
// documentation says we can't, but empirically we can at least some
|
||||||
|
// of the time).
|
||||||
|
|
||||||
|
// If the content type is "multipart/form-data", we need to build both
|
||||||
|
// $_POST and $_FILES, which is involved. The body itself is also more
|
||||||
|
// difficult to parse than other requests.
|
||||||
|
$raw_input = PhabricatorStartup::getRawInput();
|
||||||
|
$parser = new PhutilQueryStringParser();
|
||||||
|
|
||||||
|
if (strlen($raw_input)) {
|
||||||
|
$content_type = idx($_SERVER, 'CONTENT_TYPE');
|
||||||
|
$is_multipart = preg_match('@^multipart/form-data@i', $content_type);
|
||||||
|
if ($is_multipart && !ini_get('enable_post_data_reading')) {
|
||||||
|
$multipart_parser = id(new AphrontMultipartParser())
|
||||||
|
->setContentType($content_type);
|
||||||
|
|
||||||
|
$multipart_parser->beginParse();
|
||||||
|
$multipart_parser->continueParse($raw_input);
|
||||||
|
$parts = $multipart_parser->endParse();
|
||||||
|
|
||||||
|
// We're building and then parsing a query string so that requests
|
||||||
|
// with arrays (like "x[]=apple&x[]=banana") work correctly. This also
|
||||||
|
// means we can't use "phutil_build_http_querystring()", since it
|
||||||
|
// can't build a query string with duplicate names.
|
||||||
|
|
||||||
|
$query_string = array();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if (!$part->isVariable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $part->getName();
|
||||||
|
$value = $part->getVariableValue();
|
||||||
|
$query_string[] = rawurlencode($name).'='.rawurlencode($value);
|
||||||
|
}
|
||||||
|
$query_string = implode('&', $query_string);
|
||||||
|
$post = $parser->parseQueryString($query_string);
|
||||||
|
|
||||||
|
$files = array();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if ($part->isVariable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files[$part->getName()] = $part->getPHPFileDictionary();
|
||||||
|
}
|
||||||
|
$_FILES = $files;
|
||||||
|
} else {
|
||||||
|
$post = $parser->parseQueryString($raw_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
$_POST = $post;
|
||||||
|
PhabricatorStartup::rebuildRequest();
|
||||||
|
} else if ($_POST) {
|
||||||
|
$post = filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW);
|
||||||
|
if (is_array($post)) {
|
||||||
|
$_POST = $post;
|
||||||
|
PhabricatorStartup::rebuildRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE: Do not extend this!
|
|
||||||
*
|
|
||||||
* @concrete-extensible
|
|
||||||
*/
|
|
||||||
class AphrontDefaultApplicationConfiguration
|
|
||||||
extends AphrontApplicationConfiguration {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @phutil-external-symbol class PhabricatorStartup
|
|
||||||
*/
|
|
||||||
public function buildRequest() {
|
|
||||||
$parser = new PhutilQueryStringParser();
|
|
||||||
$data = array();
|
|
||||||
|
|
||||||
$request_method = idx($_SERVER, 'REQUEST_METHOD');
|
|
||||||
if ($request_method === 'PUT') {
|
|
||||||
// For PUT requests, do nothing: in particular, do NOT read input. This
|
|
||||||
// allows us to stream input later and process very large PUT requests,
|
|
||||||
// like those coming from Git LFS.
|
|
||||||
} else {
|
|
||||||
// For POST requests, we're going to read the raw input ourselves here
|
|
||||||
// if we can. Among other things, this corrects variable names with
|
|
||||||
// the "." character in them, which PHP normally converts into "_".
|
|
||||||
|
|
||||||
// There are two major considerations here: whether the
|
|
||||||
// `enable_post_data_reading` option is set, and whether the content
|
|
||||||
// type is "multipart/form-data" or not.
|
|
||||||
|
|
||||||
// If `enable_post_data_reading` is off, we're free to read the entire
|
|
||||||
// raw request body and parse it -- and we must, because $_POST and
|
|
||||||
// $_FILES are not built for us. If `enable_post_data_reading` is on,
|
|
||||||
// which is the default, we may not be able to read the body (the
|
|
||||||
// documentation says we can't, but empirically we can at least some
|
|
||||||
// of the time).
|
|
||||||
|
|
||||||
// If the content type is "multipart/form-data", we need to build both
|
|
||||||
// $_POST and $_FILES, which is involved. The body itself is also more
|
|
||||||
// difficult to parse than other requests.
|
|
||||||
$raw_input = PhabricatorStartup::getRawInput();
|
|
||||||
if (strlen($raw_input)) {
|
|
||||||
$content_type = idx($_SERVER, 'CONTENT_TYPE');
|
|
||||||
$is_multipart = preg_match('@^multipart/form-data@i', $content_type);
|
|
||||||
if ($is_multipart && !ini_get('enable_post_data_reading')) {
|
|
||||||
$multipart_parser = id(new AphrontMultipartParser())
|
|
||||||
->setContentType($content_type);
|
|
||||||
|
|
||||||
$multipart_parser->beginParse();
|
|
||||||
$multipart_parser->continueParse($raw_input);
|
|
||||||
$parts = $multipart_parser->endParse();
|
|
||||||
|
|
||||||
$query_string = array();
|
|
||||||
foreach ($parts as $part) {
|
|
||||||
if (!$part->isVariable()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = $part->getName();
|
|
||||||
$value = $part->getVariableValue();
|
|
||||||
|
|
||||||
$query_string[] = urlencode($name).'='.urlencode($value);
|
|
||||||
}
|
|
||||||
$query_string = implode('&', $query_string);
|
|
||||||
$post = $parser->parseQueryString($query_string);
|
|
||||||
|
|
||||||
$files = array();
|
|
||||||
foreach ($parts as $part) {
|
|
||||||
if ($part->isVariable()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$files[$part->getName()] = $part->getPHPFileDictionary();
|
|
||||||
}
|
|
||||||
$_FILES = $files;
|
|
||||||
} else {
|
|
||||||
$post = $parser->parseQueryString($raw_input);
|
|
||||||
}
|
|
||||||
|
|
||||||
$_POST = $post;
|
|
||||||
PhabricatorStartup::rebuildRequest();
|
|
||||||
|
|
||||||
$data += $post;
|
|
||||||
} else if ($_POST) {
|
|
||||||
$post = filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW);
|
|
||||||
if (is_array($post)) {
|
|
||||||
$_POST = $post;
|
|
||||||
PhabricatorStartup::rebuildRequest();
|
|
||||||
}
|
|
||||||
$data += $_POST;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data += $parser->parseQueryString(idx($_SERVER, 'QUERY_STRING', ''));
|
|
||||||
|
|
||||||
$cookie_prefix = PhabricatorEnv::getEnvConfig('phabricator.cookie-prefix');
|
|
||||||
|
|
||||||
$request = new AphrontRequest($this->getHost(), $this->getPath());
|
|
||||||
$request->setRequestData($data);
|
|
||||||
$request->setApplicationConfiguration($this);
|
|
||||||
$request->setCookiePrefix($cookie_prefix);
|
|
||||||
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function build404Controller() {
|
|
||||||
return array(new Phabricator404Controller(), array());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildRedirectController($uri, $external) {
|
|
||||||
return array(
|
|
||||||
new PhabricatorRedirectController(),
|
|
||||||
array(
|
|
||||||
'uri' => $uri,
|
|
||||||
'external' => $external,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -78,15 +78,13 @@ final class PhabricatorHighSecurityRequestExceptionHandler
|
||||||
$form_layout = $form->buildLayoutView();
|
$form_layout = $form->buildLayoutView();
|
||||||
|
|
||||||
if ($is_upgrade) {
|
if ($is_upgrade) {
|
||||||
$messages = array(
|
$message = pht(
|
||||||
pht(
|
'You are taking an action which requires you to enter '.
|
||||||
'You are taking an action which requires you to enter '.
|
'high security.');
|
||||||
'high security.'),
|
|
||||||
);
|
|
||||||
|
|
||||||
$info_view = id(new PHUIInfoView())
|
$info_view = id(new PHUIInfoView())
|
||||||
->setSeverity(PHUIInfoView::SEVERITY_MFA)
|
->setSeverity(PHUIInfoView::SEVERITY_MFA)
|
||||||
->setErrors($messages);
|
->setErrors(array($message));
|
||||||
|
|
||||||
$dialog
|
$dialog
|
||||||
->appendChild($info_view)
|
->appendChild($info_view)
|
||||||
|
@ -100,12 +98,18 @@ final class PhabricatorHighSecurityRequestExceptionHandler
|
||||||
'period of time. When you are finished taking sensitive '.
|
'period of time. When you are finished taking sensitive '.
|
||||||
'actions, you should leave high security.'));
|
'actions, you should leave high security.'));
|
||||||
} else {
|
} else {
|
||||||
|
$message = pht(
|
||||||
|
'You are taking an action which requires you to provide '.
|
||||||
|
'multi-factor credentials.');
|
||||||
|
|
||||||
|
$info_view = id(new PHUIInfoView())
|
||||||
|
->setSeverity(PHUIInfoView::SEVERITY_MFA)
|
||||||
|
->setErrors(array($message));
|
||||||
|
|
||||||
$dialog
|
$dialog
|
||||||
|
->appendChild($info_view)
|
||||||
->setErrors(
|
->setErrors(
|
||||||
array(
|
array(
|
||||||
pht(
|
|
||||||
'You are taking an action which requires you to provide '.
|
|
||||||
'multi-factor credentials.'),
|
|
||||||
))
|
))
|
||||||
->appendChild($form_layout);
|
->appendChild($form_layout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,8 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
|
||||||
'PhabricatorAuthFactorProviderEditController',
|
'PhabricatorAuthFactorProviderEditController',
|
||||||
'(?P<id>[1-9]\d*)/' =>
|
'(?P<id>[1-9]\d*)/' =>
|
||||||
'PhabricatorAuthFactorProviderViewController',
|
'PhabricatorAuthFactorProviderViewController',
|
||||||
|
'message/(?P<id>[1-9]\d*)/' =>
|
||||||
|
'PhabricatorAuthFactorProviderMessageController',
|
||||||
),
|
),
|
||||||
|
|
||||||
'message/' => array(
|
'message/' => array(
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthFactorProviderMessageController
|
||||||
|
extends PhabricatorAuthFactorProviderController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$this->requireApplicationCapability(
|
||||||
|
AuthManageProvidersCapability::CAPABILITY);
|
||||||
|
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
|
||||||
|
$provider = id(new PhabricatorAuthFactorProviderQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$provider) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancel_uri = $provider->getURI();
|
||||||
|
$enroll_key =
|
||||||
|
PhabricatorAuthFactorProviderEnrollMessageTransaction::TRANSACTIONTYPE;
|
||||||
|
|
||||||
|
$message = $provider->getEnrollMessage();
|
||||||
|
|
||||||
|
if ($request->isFormOrHisecPost()) {
|
||||||
|
$message = $request->getStr('message');
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorAuthFactorProviderTransaction())
|
||||||
|
->setTransactionType($enroll_key)
|
||||||
|
->setNewValue($message);
|
||||||
|
|
||||||
|
$editor = id(new PhabricatorAuthFactorProviderEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true)
|
||||||
|
->setCancelURI($cancel_uri);
|
||||||
|
|
||||||
|
$editor->applyTransactions($provider, $xactions);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$default_message = $provider->getEnrollDescription($viewer);
|
||||||
|
$default_message = new PHUIRemarkupView($viewer, $default_message);
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->appendRemarkupInstructions(
|
||||||
|
pht(
|
||||||
|
'When users add a factor for this provider, they are given this '.
|
||||||
|
'enrollment guidance by default:'))
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormMarkupControl())
|
||||||
|
->setLabel(pht('Default Message'))
|
||||||
|
->setValue($default_message))
|
||||||
|
->appendRemarkupInstructions(
|
||||||
|
pht(
|
||||||
|
'You may optionally customize the enrollment message users are '.
|
||||||
|
'presented with by providing a replacement message below:'))
|
||||||
|
->appendControl(
|
||||||
|
id(new PhabricatorRemarkupControl())
|
||||||
|
->setLabel(pht('Custom Message'))
|
||||||
|
->setName('message')
|
||||||
|
->setValue($message));
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Change Enroll Message'))
|
||||||
|
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||||
|
->appendForm($form)
|
||||||
|
->addCancelButton($cancel_uri)
|
||||||
|
->addSubmitButton(pht('Save'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -81,6 +81,16 @@ final class PhabricatorAuthFactorProviderViewController
|
||||||
pht('Factor Type'),
|
pht('Factor Type'),
|
||||||
$provider->getFactor()->getFactorName());
|
$provider->getFactor()->getFactorName());
|
||||||
|
|
||||||
|
|
||||||
|
$custom_enroll = $provider->getEnrollMessage();
|
||||||
|
if (strlen($custom_enroll)) {
|
||||||
|
$view->addSectionHeader(
|
||||||
|
pht('Custom Enroll Message'),
|
||||||
|
PHUIPropertyListView::ICON_SUMMARY);
|
||||||
|
$view->addTextContent(
|
||||||
|
new PHUIRemarkupView($viewer, $custom_enroll));
|
||||||
|
}
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +113,14 @@ final class PhabricatorAuthFactorProviderViewController
|
||||||
->setDisabled(!$can_edit)
|
->setDisabled(!$can_edit)
|
||||||
->setWorkflow(!$can_edit));
|
->setWorkflow(!$can_edit));
|
||||||
|
|
||||||
|
$curtain->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setName(pht('Customize Enroll Message'))
|
||||||
|
->setIcon('fa-commenting-o')
|
||||||
|
->setHref($this->getApplicationURI("mfa/message/{$id}/"))
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(true));
|
||||||
|
|
||||||
return $curtain;
|
return $curtain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ abstract class PhabricatorAuthFactor extends Phobject {
|
||||||
$viewer,
|
$viewer,
|
||||||
$challenges);
|
$challenges);
|
||||||
|
|
||||||
if ($new_challenges instanceof PhabricatorAuthFactorResult) {
|
if ($this->isAuthResult($new_challenges)) {
|
||||||
unset($unguarded);
|
unset($unguarded);
|
||||||
return $new_challenges;
|
return $new_challenges;
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ abstract class PhabricatorAuthFactor extends Phobject {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!($result instanceof PhabricatorAuthFactorResult)) {
|
if (!$this->isAuthResult($result)) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'Expected "newResultFromIssuedChallenges()" to return null or '.
|
'Expected "newResultFromIssuedChallenges()" to return null or '.
|
||||||
|
@ -232,7 +232,7 @@ abstract class PhabricatorAuthFactor extends Phobject {
|
||||||
$request,
|
$request,
|
||||||
$challenges);
|
$challenges);
|
||||||
|
|
||||||
if (!($result instanceof PhabricatorAuthFactorResult)) {
|
if (!$this->isAuthResult($result)) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'Expected "newResultFromChallengeResponse()" to return an object '.
|
'Expected "newResultFromChallengeResponse()" to return an object '.
|
||||||
|
@ -408,6 +408,10 @@ abstract class PhabricatorAuthFactor extends Phobject {
|
||||||
$provider,
|
$provider,
|
||||||
$user);
|
$user);
|
||||||
|
|
||||||
|
if ($this->isAuthResult($properties)) {
|
||||||
|
return $properties;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($properties as $key => $value) {
|
foreach ($properties as $key => $value) {
|
||||||
$sync_token->setTemporaryTokenProperty($key, $value);
|
$sync_token->setTemporaryTokenProperty($key, $value);
|
||||||
}
|
}
|
||||||
|
@ -555,4 +559,8 @@ abstract class PhabricatorAuthFactor extends Phobject {
|
||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final protected function isAuthResult($object) {
|
||||||
|
return ($object instanceof PhabricatorAuthFactorResult);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,6 +157,10 @@ final class PhabricatorDuoAuthFactor
|
||||||
PhabricatorUser $user) {
|
PhabricatorUser $user) {
|
||||||
|
|
||||||
$token = $this->loadMFASyncToken($provider, $request, $form, $user);
|
$token = $this->loadMFASyncToken($provider, $request, $form, $user);
|
||||||
|
if ($this->isAuthResult($token)) {
|
||||||
|
$form->appendChild($this->newAutomaticControl($token));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$enroll = $token->getTemporaryTokenProperty('duo.enroll');
|
$enroll = $token->getTemporaryTokenProperty('duo.enroll');
|
||||||
$duo_id = $token->getTemporaryTokenProperty('duo.user-id');
|
$duo_id = $token->getTemporaryTokenProperty('duo.user-id');
|
||||||
|
@ -350,6 +354,7 @@ final class PhabricatorDuoAuthFactor
|
||||||
|
|
||||||
$external_uri = null;
|
$external_uri = null;
|
||||||
$result_code = $result['response']['result'];
|
$result_code = $result['response']['result'];
|
||||||
|
$status_message = $result['response']['status_msg'];
|
||||||
switch ($result_code) {
|
switch ($result_code) {
|
||||||
case 'auth':
|
case 'auth':
|
||||||
case 'allow':
|
case 'allow':
|
||||||
|
@ -376,7 +381,13 @@ final class PhabricatorDuoAuthFactor
|
||||||
return $this->newResult()
|
return $this->newResult()
|
||||||
->setIsError(true)
|
->setIsError(true)
|
||||||
->setErrorMessage(
|
->setErrorMessage(
|
||||||
pht('Your account is not permitted to access this system.'));
|
pht(
|
||||||
|
'Your Duo account ("%s") is not permitted to access this '.
|
||||||
|
'system. Contact your Duo administrator for help. '.
|
||||||
|
'The Duo preauth API responded with status message ("%s"): %s',
|
||||||
|
$duo_user,
|
||||||
|
$result_code,
|
||||||
|
$status_message));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duo's "/enroll" API isn't repeatable for the same username. If we're
|
// Duo's "/enroll" API isn't repeatable for the same username. If we're
|
||||||
|
@ -476,7 +487,10 @@ final class PhabricatorDuoAuthFactor
|
||||||
->setIsError(true)
|
->setIsError(true)
|
||||||
->setErrorMessage(
|
->setErrorMessage(
|
||||||
pht(
|
pht(
|
||||||
'Duo has denied you access. Duo status message ("%s"): %s',
|
'Your Duo account ("%s") is not permitted to access this '.
|
||||||
|
'system. Contact your Duo administrator for help. The Duo '.
|
||||||
|
'preauth API responded with status message ("%s"): %s',
|
||||||
|
$duo_user,
|
||||||
$next_step,
|
$next_step,
|
||||||
$status_message));
|
$status_message));
|
||||||
}
|
}
|
||||||
|
@ -504,10 +518,7 @@ final class PhabricatorDuoAuthFactor
|
||||||
$push_info = array(
|
$push_info = array(
|
||||||
pht('Domain') => $this->getInstallDisplayName(),
|
pht('Domain') => $this->getInstallDisplayName(),
|
||||||
);
|
);
|
||||||
foreach ($push_info as $k => $v) {
|
$push_info = phutil_build_http_querystring($push_info);
|
||||||
$push_info[$k] = rawurlencode($k).'='.rawurlencode($v);
|
|
||||||
}
|
|
||||||
$push_info = implode('&', $push_info);
|
|
||||||
|
|
||||||
$parameters = array(
|
$parameters = array(
|
||||||
'username' => $duo_user,
|
'username' => $duo_user,
|
||||||
|
|
|
@ -91,11 +91,7 @@ final class PhabricatorDuoFuture
|
||||||
$http_method = $this->getHTTPMethod();
|
$http_method = $this->getHTTPMethod();
|
||||||
|
|
||||||
ksort($data);
|
ksort($data);
|
||||||
$data_parts = array();
|
$data_parts = phutil_build_http_querystring($data);
|
||||||
foreach ($data as $key => $value) {
|
|
||||||
$data_parts[] = rawurlencode($key).'='.rawurlencode($value);
|
|
||||||
}
|
|
||||||
$data_parts = implode('&', $data_parts);
|
|
||||||
|
|
||||||
$corpus = array(
|
$corpus = array(
|
||||||
$date,
|
$date,
|
||||||
|
|
|
@ -57,6 +57,14 @@ final class PhabricatorAuthFactorProvider
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEnrollMessage() {
|
||||||
|
return $this->getAuthFactorProviderProperty('enroll-message');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEnrollMessage($message) {
|
||||||
|
return $this->setAuthFactorProviderProperty('enroll-message', $message);
|
||||||
|
}
|
||||||
|
|
||||||
public function attachFactor(PhabricatorAuthFactor $factor) {
|
public function attachFactor(PhabricatorAuthFactor $factor) {
|
||||||
$this->factor = $factor;
|
$this->factor = $factor;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthFactorProviderEnrollMessageTransaction
|
||||||
|
extends PhabricatorAuthFactorProviderTransactionType {
|
||||||
|
|
||||||
|
const TRANSACTIONTYPE = 'enroll-message';
|
||||||
|
|
||||||
|
public function generateOldValue($object) {
|
||||||
|
return $object->getEnrollMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyInternalEffects($object, $value) {
|
||||||
|
$object->setEnrollMessage($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
return pht(
|
||||||
|
'%s updated the enroll message.',
|
||||||
|
$this->renderAuthor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasChangeDetailView() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMailDiffSectionHeader() {
|
||||||
|
return pht('CHANGES TO ENROLL MESSAGE');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newChangeDetailView() {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setOldText($this->getOldValue())
|
||||||
|
->setNewText($this->getNewValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase {
|
||||||
$root = dirname(phutil_get_library_root('phabricator'));
|
$root = dirname(phutil_get_library_root('phabricator'));
|
||||||
require_once $root.'/support/startup/PhabricatorStartup.php';
|
require_once $root.'/support/startup/PhabricatorStartup.php';
|
||||||
|
|
||||||
$application_configuration = new AphrontDefaultApplicationConfiguration();
|
$application_configuration = new AphrontApplicationConfiguration();
|
||||||
|
|
||||||
$host = 'meow.example.com';
|
$host = 'meow.example.com';
|
||||||
|
|
||||||
|
|
|
@ -11,14 +11,13 @@ final class PhabricatorExtensionsSetupCheck extends PhabricatorSetupCheck {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function executeChecks() {
|
protected function executeChecks() {
|
||||||
// TODO: Make 'mbstring' and 'iconv' soft requirements.
|
// TODO: Make 'mbstring' a soft requirement.
|
||||||
|
|
||||||
$required = array(
|
$required = array(
|
||||||
'hash',
|
'hash',
|
||||||
'json',
|
'json',
|
||||||
'openssl',
|
'openssl',
|
||||||
'mbstring',
|
'mbstring',
|
||||||
'iconv',
|
|
||||||
'ctype',
|
'ctype',
|
||||||
|
|
||||||
// There is a tiny chance we might not need this, but a significant
|
// There is a tiny chance we might not need this, but a significant
|
||||||
|
|
|
@ -416,6 +416,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
||||||
'metamta.pholio.subject-prefix' => $prefix_reason,
|
'metamta.pholio.subject-prefix' => $prefix_reason,
|
||||||
'metamta.phriction.subject-prefix' => $prefix_reason,
|
'metamta.phriction.subject-prefix' => $prefix_reason,
|
||||||
|
|
||||||
|
'aphront.default-application-configuration-class' => pht(
|
||||||
|
'This ancient extension point has been replaced with other '.
|
||||||
|
'mechanisms, including "AphrontSite".'),
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return $ancient_config;
|
return $ancient_config;
|
||||||
|
|
|
@ -63,6 +63,8 @@ final class PhabricatorConfigVersionController
|
||||||
$version_from_file);
|
$version_from_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$version_property_list->addProperty('php', phpversion());
|
||||||
|
|
||||||
$binaries = PhutilBinaryAnalyzer::getAllBinaries();
|
$binaries = PhutilBinaryAnalyzer::getAllBinaries();
|
||||||
foreach ($binaries as $binary) {
|
foreach ($binaries as $binary) {
|
||||||
if (!$binary->isBinaryAvailable()) {
|
if (!$binary->isBinaryAvailable()) {
|
||||||
|
|
|
@ -36,14 +36,6 @@ final class PhabricatorExtendingPhabricatorConfigOptions
|
||||||
'occur. Specify a list of classes which extend '.
|
'occur. Specify a list of classes which extend '.
|
||||||
'PhabricatorEventListener here.'))
|
'PhabricatorEventListener here.'))
|
||||||
->addExample('MyEventListener', pht('Valid Setting')),
|
->addExample('MyEventListener', pht('Valid Setting')),
|
||||||
$this->newOption(
|
|
||||||
'aphront.default-application-configuration-class',
|
|
||||||
'class',
|
|
||||||
'AphrontDefaultApplicationConfiguration')
|
|
||||||
->setLocked(true)
|
|
||||||
->setBaseClass('AphrontApplicationConfiguration')
|
|
||||||
// TODO: This could probably use some better documentation.
|
|
||||||
->setDescription(pht('Application configuration class.')),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -235,6 +235,32 @@ final class DifferentialRevisionEditEngine
|
||||||
->setConduitTypeDescription(pht('List of tasks.'))
|
->setConduitTypeDescription(pht('List of tasks.'))
|
||||||
->setValue(array());
|
->setValue(array());
|
||||||
|
|
||||||
|
$fields[] = id(new PhabricatorHandlesEditField())
|
||||||
|
->setKey('parents')
|
||||||
|
->setUseEdgeTransactions(true)
|
||||||
|
->setIsFormField(false)
|
||||||
|
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||||
|
->setMetadataValue(
|
||||||
|
'edge:type',
|
||||||
|
DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST)
|
||||||
|
->setDescription(pht('Parent revisions of this revision.'))
|
||||||
|
->setConduitDescription(pht('Change associated parent revisions.'))
|
||||||
|
->setConduitTypeDescription(pht('List of revisions.'))
|
||||||
|
->setValue(array());
|
||||||
|
|
||||||
|
$fields[] = id(new PhabricatorHandlesEditField())
|
||||||
|
->setKey('children')
|
||||||
|
->setUseEdgeTransactions(true)
|
||||||
|
->setIsFormField(false)
|
||||||
|
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||||
|
->setMetadataValue(
|
||||||
|
'edge:type',
|
||||||
|
DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST)
|
||||||
|
->setDescription(pht('Child revisions of this revision.'))
|
||||||
|
->setConduitDescription(pht('Change associated child revisions.'))
|
||||||
|
->setConduitTypeDescription(pht('List of revisions.'))
|
||||||
|
->setValue(array());
|
||||||
|
|
||||||
$actions = DifferentialRevisionActionTransaction::loadAllActions();
|
$actions = DifferentialRevisionActionTransaction::loadAllActions();
|
||||||
$actions = msortv($actions, 'getRevisionActionOrderVector');
|
$actions = msortv($actions, 'getRevisionActionOrderVector');
|
||||||
|
|
||||||
|
|
|
@ -177,14 +177,21 @@ final class DifferentialDiffExtractionEngine extends Phobject {
|
||||||
'repository' => $repository,
|
'repository' => $repository,
|
||||||
));
|
));
|
||||||
|
|
||||||
$response = DiffusionQuery::callConduitWithDiffusionRequest(
|
try {
|
||||||
$viewer,
|
$response = DiffusionQuery::callConduitWithDiffusionRequest(
|
||||||
$drequest,
|
$viewer,
|
||||||
'diffusion.filecontentquery',
|
$drequest,
|
||||||
array(
|
'diffusion.filecontentquery',
|
||||||
'commit' => $identifier,
|
array(
|
||||||
'path' => $path,
|
'commit' => $identifier,
|
||||||
));
|
'path' => $path,
|
||||||
|
));
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
// TODO: See PHI1044. This call may fail if the diff deleted the
|
||||||
|
// file. If the call fails, just detect a change for now. This should
|
||||||
|
// generally be made cleaner in the future.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
$new_file_phid = $response['filePHID'];
|
$new_file_phid = $response['filePHID'];
|
||||||
if (!$new_file_phid) {
|
if (!$new_file_phid) {
|
||||||
|
|
|
@ -528,7 +528,7 @@ final class DiffusionServeController extends DiffusionController {
|
||||||
unset($query_data[$key]);
|
unset($query_data[$key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$query_string = http_build_query($query_data, '', '&');
|
$query_string = phutil_build_http_querystring($query_data);
|
||||||
|
|
||||||
// We're about to wipe out PATH with the rest of the environment, so
|
// We're about to wipe out PATH with the rest of the environment, so
|
||||||
// resolve the binary first.
|
// resolve the binary first.
|
||||||
|
|
|
@ -188,7 +188,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
||||||
if ($this_version) {
|
if ($this_version) {
|
||||||
$this_version = (int)$this_version->getRepositoryVersion();
|
$this_version = (int)$this_version->getRepositoryVersion();
|
||||||
} else {
|
} else {
|
||||||
$this_version = -1;
|
$this_version = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($versions) {
|
if ($versions) {
|
||||||
|
@ -197,7 +197,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
||||||
// leader, we want to fetch from a leader and then update our version.
|
// leader, we want to fetch from a leader and then update our version.
|
||||||
|
|
||||||
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
|
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
|
||||||
if ($max_version > $this_version) {
|
if (($this_version === null) || ($max_version > $this_version)) {
|
||||||
if ($repository->isHosted()) {
|
if ($repository->isHosted()) {
|
||||||
$fetchable = array();
|
$fetchable = array();
|
||||||
foreach ($versions as $version) {
|
foreach ($versions as $version) {
|
||||||
|
@ -206,6 +206,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->synchronizeWorkingCopyFromDevices(
|
$this->synchronizeWorkingCopyFromDevices(
|
||||||
$fetchable,
|
$fetchable,
|
||||||
$this_version,
|
$this_version,
|
||||||
|
@ -445,10 +446,10 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
|
||||||
if ($this_version) {
|
if ($this_version) {
|
||||||
$this_version = (int)$this_version->getRepositoryVersion();
|
$this_version = (int)$this_version->getRepositoryVersion();
|
||||||
} else {
|
} else {
|
||||||
$this_version = -1;
|
$this_version = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($new_version > $this_version) {
|
if (($this_version === null) || ($new_version > $this_version)) {
|
||||||
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
||||||
$repository_phid,
|
$repository_phid,
|
||||||
$device_phid,
|
$device_phid,
|
||||||
|
|
|
@ -222,8 +222,10 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
||||||
pht('No repository "%s" exists!', $identifier));
|
pht('No repository "%s" exists!', $identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$is_cluster = $this->getIsClusterRequest();
|
||||||
|
|
||||||
$protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH;
|
$protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH;
|
||||||
if (!$repository->canServeProtocol($protocol, false)) {
|
if (!$repository->canServeProtocol($protocol, false, $is_cluster)) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'This repository ("%s") is not available over SSH.',
|
'This repository ("%s") is not available over SSH.',
|
||||||
|
|
|
@ -83,6 +83,34 @@ EOTEXT
|
||||||
|
|
||||||
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
|
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
|
||||||
|
|
||||||
|
|
||||||
|
$subtype_type = 'projects.subtypes';
|
||||||
|
$subtype_default_key = PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT;
|
||||||
|
$subtype_example = array(
|
||||||
|
array(
|
||||||
|
'key' => $subtype_default_key,
|
||||||
|
'name' => pht('Project'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'key' => 'team',
|
||||||
|
'name' => pht('Team'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$subtype_example = id(new PhutilJSON())->encodeAsList($subtype_example);
|
||||||
|
|
||||||
|
$subtype_default = array(
|
||||||
|
array(
|
||||||
|
'key' => $subtype_default_key,
|
||||||
|
'name' => pht('Project'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$subtype_description = $this->deformat(pht(<<<EOTEXT
|
||||||
|
Allows you to define project subtypes. For a more detailed description of
|
||||||
|
subtype configuration, see @{config:maniphest.subtypes}.
|
||||||
|
EOTEXT
|
||||||
|
));
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
$this->newOption('projects.custom-field-definitions', 'wild', array())
|
$this->newOption('projects.custom-field-definitions', 'wild', array())
|
||||||
->setSummary(pht('Custom Projects fields.'))
|
->setSummary(pht('Custom Projects fields.'))
|
||||||
|
@ -102,6 +130,11 @@ EOTEXT
|
||||||
$this->newOption('projects.colors', $colors_type, $default_colors)
|
$this->newOption('projects.colors', $colors_type, $default_colors)
|
||||||
->setSummary(pht('Adjust project colors.'))
|
->setSummary(pht('Adjust project colors.'))
|
||||||
->setDescription($colors_description),
|
->setDescription($colors_description),
|
||||||
|
$this->newOption('projects.subtypes', $subtype_type, $subtype_default)
|
||||||
|
->setSummary(pht('Define project subtypes.'))
|
||||||
|
->setDescription($subtype_description)
|
||||||
|
->addExample($subtype_example, pht('Simple Subtypes')),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectSubtypesConfigType
|
||||||
|
extends PhabricatorJSONConfigType {
|
||||||
|
|
||||||
|
const TYPEKEY = 'projects.subtypes';
|
||||||
|
|
||||||
|
public function validateStoredValue(
|
||||||
|
PhabricatorConfigOption $option,
|
||||||
|
$value) {
|
||||||
|
PhabricatorEditEngineSubtype::validateConfiguration($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -51,6 +51,12 @@ final class PhabricatorProjectProfileController
|
||||||
$watch_action = $this->renderWatchAction($project);
|
$watch_action = $this->renderWatchAction($project);
|
||||||
$header->addActionLink($watch_action);
|
$header->addActionLink($watch_action);
|
||||||
|
|
||||||
|
$subtype = $project->newSubtypeObject();
|
||||||
|
if ($subtype && $subtype->hasTagView()) {
|
||||||
|
$subtype_tag = $subtype->newTagView();
|
||||||
|
$header->addTag($subtype_tag);
|
||||||
|
}
|
||||||
|
|
||||||
$milestone_list = $this->buildMilestoneList($project);
|
$milestone_list = $this->buildMilestoneList($project);
|
||||||
$subproject_list = $this->buildSubprojectList($project);
|
$subproject_list = $this->buildSubprojectList($project);
|
||||||
|
|
||||||
|
|
|
@ -249,6 +249,17 @@ final class PhabricatorProjectTransactionEditor
|
||||||
->rematerialize($new_parent);
|
->rematerialize($new_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See PHI1046. Milestones are always in the Space of their parent project.
|
||||||
|
// Synchronize the database values to match the application values.
|
||||||
|
$conn = $object->establishConnection('w');
|
||||||
|
queryfx(
|
||||||
|
$conn,
|
||||||
|
'UPDATE %R SET spacePHID = %ns
|
||||||
|
WHERE parentProjectPHID = %s AND milestoneNumber IS NOT NULL',
|
||||||
|
$object,
|
||||||
|
$object->getSpacePHID(),
|
||||||
|
$object->getPHID());
|
||||||
|
|
||||||
return parent::applyFinalEffects($object, $xactions);
|
return parent::applyFinalEffects($object, $xactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ final class PhabricatorProjectQuery
|
||||||
private $maxDepth;
|
private $maxDepth;
|
||||||
private $minMilestoneNumber;
|
private $minMilestoneNumber;
|
||||||
private $maxMilestoneNumber;
|
private $maxMilestoneNumber;
|
||||||
|
private $subtypes;
|
||||||
|
|
||||||
private $status = 'status-any';
|
private $status = 'status-any';
|
||||||
const STATUS_ANY = 'status-any';
|
const STATUS_ANY = 'status-any';
|
||||||
|
@ -131,6 +132,11 @@ final class PhabricatorProjectQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withSubtypes(array $subtypes) {
|
||||||
|
$this->subtypes = $subtypes;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function needMembers($need_members) {
|
public function needMembers($need_members) {
|
||||||
$this->needMembers = $need_members;
|
$this->needMembers = $need_members;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -618,6 +624,13 @@ final class PhabricatorProjectQuery
|
||||||
$this->maxMilestoneNumber);
|
$this->maxMilestoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->subtypes !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'subtype IN (%Ls)',
|
||||||
|
$this->subtypes);
|
||||||
|
}
|
||||||
|
|
||||||
return $where;
|
return $where;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ final class PhabricatorProjectSearchEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildCustomSearchFields() {
|
protected function buildCustomSearchFields() {
|
||||||
|
$subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap();
|
||||||
|
$hide_subtypes = ($subtype_map->getCount() == 1);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
id(new PhabricatorSearchTextField())
|
id(new PhabricatorSearchTextField())
|
||||||
->setLabel(pht('Name'))
|
->setLabel(pht('Name'))
|
||||||
|
@ -62,6 +65,14 @@ final class PhabricatorProjectSearchEngine
|
||||||
pht(
|
pht(
|
||||||
'Pass true to find only milestones, or false to omit '.
|
'Pass true to find only milestones, or false to omit '.
|
||||||
'milestones.')),
|
'milestones.')),
|
||||||
|
id(new PhabricatorSearchDatasourceField())
|
||||||
|
->setLabel(pht('Subtypes'))
|
||||||
|
->setKey('subtypes')
|
||||||
|
->setAliases(array('subtype'))
|
||||||
|
->setDescription(
|
||||||
|
pht('Search for projects with given subtypes.'))
|
||||||
|
->setDatasource(new PhabricatorProjectSubtypeDatasource())
|
||||||
|
->setIsHidden($hide_subtypes),
|
||||||
id(new PhabricatorSearchCheckboxesField())
|
id(new PhabricatorSearchCheckboxesField())
|
||||||
->setLabel(pht('Icons'))
|
->setLabel(pht('Icons'))
|
||||||
->setKey('icons')
|
->setKey('icons')
|
||||||
|
@ -134,6 +145,10 @@ final class PhabricatorProjectSearchEngine
|
||||||
$query->withAncestorProjectPHIDs($map['ancestorPHIDs']);
|
$query->withAncestorProjectPHIDs($map['ancestorPHIDs']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($map['subtypes']) {
|
||||||
|
$query->withSubtypes($map['subtypes']);
|
||||||
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
PhabricatorFerretInterface,
|
PhabricatorFerretInterface,
|
||||||
PhabricatorConduitResultInterface,
|
PhabricatorConduitResultInterface,
|
||||||
PhabricatorColumnProxyInterface,
|
PhabricatorColumnProxyInterface,
|
||||||
PhabricatorSpacesInterface {
|
PhabricatorSpacesInterface,
|
||||||
|
PhabricatorEditEngineSubtypeInterface {
|
||||||
|
|
||||||
protected $name;
|
protected $name;
|
||||||
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
|
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
|
||||||
|
@ -40,6 +41,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
|
|
||||||
protected $properties = array();
|
protected $properties = array();
|
||||||
protected $spacePHID;
|
protected $spacePHID;
|
||||||
|
protected $subtype;
|
||||||
|
|
||||||
private $memberPHIDs = self::ATTACHABLE;
|
private $memberPHIDs = self::ATTACHABLE;
|
||||||
private $watcherPHIDs = self::ATTACHABLE;
|
private $watcherPHIDs = self::ATTACHABLE;
|
||||||
|
@ -102,6 +104,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
->setHasWorkboard(0)
|
->setHasWorkboard(0)
|
||||||
->setHasMilestones(0)
|
->setHasMilestones(0)
|
||||||
->setHasSubprojects(0)
|
->setHasSubprojects(0)
|
||||||
|
->setSubtype(PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT)
|
||||||
->attachParentProject(null);
|
->attachParentProject(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +240,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
'projectPath' => 'hashpath64',
|
'projectPath' => 'hashpath64',
|
||||||
'projectDepth' => 'uint32',
|
'projectDepth' => 'uint32',
|
||||||
'projectPathKey' => 'bytes4',
|
'projectPathKey' => 'bytes4',
|
||||||
|
'subtype' => 'text64',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'key_icon' => array(
|
'key_icon' => array(
|
||||||
|
@ -765,6 +769,10 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
->setKey('slug')
|
->setKey('slug')
|
||||||
->setType('string')
|
->setType('string')
|
||||||
->setDescription(pht('Primary slug/hashtag.')),
|
->setDescription(pht('Primary slug/hashtag.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('subtype')
|
||||||
|
->setType('string')
|
||||||
|
->setDescription(pht('Subtype of the project.')),
|
||||||
id(new PhabricatorConduitSearchFieldSpecification())
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
->setKey('milestone')
|
->setKey('milestone')
|
||||||
->setType('int?')
|
->setType('int?')
|
||||||
|
@ -814,6 +822,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
return array(
|
return array(
|
||||||
'name' => $this->getName(),
|
'name' => $this->getName(),
|
||||||
'slug' => $this->getPrimarySlug(),
|
'slug' => $this->getPrimarySlug(),
|
||||||
|
'subtype' => $this->getSubtype(),
|
||||||
'milestone' => $milestone,
|
'milestone' => $milestone,
|
||||||
'depth' => (int)$this->getProjectDepth(),
|
'depth' => (int)$this->getProjectDepth(),
|
||||||
'parent' => $parent_ref,
|
'parent' => $parent_ref,
|
||||||
|
@ -873,4 +882,26 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorEditEngineSubtypeInterface )------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
public function getEditEngineSubtype() {
|
||||||
|
return $this->getSubtype();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEditEngineSubtype($value) {
|
||||||
|
return $this->setSubtype($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newEditEngineSubtypeMap() {
|
||||||
|
$config = PhabricatorEnv::getEnvConfig('projects.subtypes');
|
||||||
|
return PhabricatorEditEngineSubtype::newSubtypeMap($config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newSubtypeObject() {
|
||||||
|
$subtype_key = $this->getEditEngineSubtype();
|
||||||
|
$subtype_map = $this->newEditEngineSubtypeMap();
|
||||||
|
return $subtype_map->getSubtype($subtype_key);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectSubtypeDatasource
|
||||||
|
extends PhabricatorTypeaheadDatasource {
|
||||||
|
|
||||||
|
public function getBrowseTitle() {
|
||||||
|
return pht('Browse Subtypes');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlaceholderText() {
|
||||||
|
return pht('Type a project subtype name...');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDatasourceApplicationClass() {
|
||||||
|
return 'PhabricatorProjectApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadResults() {
|
||||||
|
$results = $this->buildResults();
|
||||||
|
return $this->filterResultsAgainstTokens($results);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderSpecialTokens(array $values) {
|
||||||
|
return $this->renderTokensFromResults($this->buildResults(), $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildResults() {
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
$subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap();
|
||||||
|
foreach ($subtype_map->getSubtypes() as $key => $subtype) {
|
||||||
|
|
||||||
|
$result = id(new PhabricatorTypeaheadResult())
|
||||||
|
->setIcon($subtype->getIcon())
|
||||||
|
->setColor($subtype->getColor())
|
||||||
|
->setPHID($key)
|
||||||
|
->setName($subtype->getName());
|
||||||
|
|
||||||
|
$results[$key] = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -87,6 +87,13 @@ final class PhabricatorProjectListView extends AphrontView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$subtype = $project->newSubtypeObject();
|
||||||
|
if ($subtype && $subtype->hasTagView()) {
|
||||||
|
$subtype_tag = $subtype->newTagView()
|
||||||
|
->setSlimShady(true);
|
||||||
|
$item->addAttribute($subtype_tag);
|
||||||
|
}
|
||||||
|
|
||||||
$list->addItem($item);
|
$list->addItem($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,9 @@ final class PhabricatorRepositoryPullLocalDaemon
|
||||||
$futures = array();
|
$futures = array();
|
||||||
$queue = array();
|
$queue = array();
|
||||||
|
|
||||||
|
$sync_wait = phutil_units('2 minutes in seconds');
|
||||||
|
$last_sync = array();
|
||||||
|
|
||||||
while (!$this->shouldExit()) {
|
while (!$this->shouldExit()) {
|
||||||
PhabricatorCaches::destroyRequestCache();
|
PhabricatorCaches::destroyRequestCache();
|
||||||
$device = AlmanacKeys::getLiveDevice();
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
@ -96,6 +99,37 @@ final class PhabricatorRepositoryPullLocalDaemon
|
||||||
$retry_after[$message->getRepositoryID()] = time();
|
$retry_after[$message->getRepositoryID()] = time();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($device) {
|
||||||
|
$unsynchronized = $this->loadUnsynchronizedRepositories($device);
|
||||||
|
$now = PhabricatorTime::getNow();
|
||||||
|
foreach ($unsynchronized as $repository) {
|
||||||
|
$id = $repository->getID();
|
||||||
|
|
||||||
|
$this->log(
|
||||||
|
pht(
|
||||||
|
'Cluster repository ("%s") is out of sync on this node ("%s").',
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
$device->getName()));
|
||||||
|
|
||||||
|
// Don't let out-of-sync conditions trigger updates too frequently,
|
||||||
|
// since we don't want to get trapped in a death spiral if sync is
|
||||||
|
// failing.
|
||||||
|
$sync_at = idx($last_sync, $id, 0);
|
||||||
|
$wait_duration = ($now - $sync_at);
|
||||||
|
if ($wait_duration < $sync_wait) {
|
||||||
|
$this->log(
|
||||||
|
pht(
|
||||||
|
'Skipping forced out-of-sync update because the last update '.
|
||||||
|
'was too recent (%s seconds ago).',
|
||||||
|
$wait_duration));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$last_sync[$id] = $now;
|
||||||
|
$retry_after[$id] = $now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If any repositories were deleted, remove them from the retry timer map
|
// If any repositories were deleted, remove them from the retry timer map
|
||||||
// so we don't end up with a retry timer that never gets updated and
|
// so we don't end up with a retry timer that never gets updated and
|
||||||
// causes us to sleep for the minimum amount of time.
|
// causes us to sleep for the minimum amount of time.
|
||||||
|
@ -521,4 +555,41 @@ final class PhabricatorRepositoryPullLocalDaemon
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function loadUnsynchronizedRepositories(AlmanacDevice $device) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$table = new PhabricatorRepositoryWorkingCopyVersion();
|
||||||
|
$conn = $table->establishConnection('r');
|
||||||
|
|
||||||
|
$our_versions = queryfx_all(
|
||||||
|
$conn,
|
||||||
|
'SELECT repositoryPHID, repositoryVersion FROM %R WHERE devicePHID = %s',
|
||||||
|
$table,
|
||||||
|
$device->getPHID());
|
||||||
|
$our_versions = ipull($our_versions, 'repositoryVersion', 'repositoryPHID');
|
||||||
|
|
||||||
|
$max_versions = queryfx_all(
|
||||||
|
$conn,
|
||||||
|
'SELECT repositoryPHID, MAX(repositoryVersion) maxVersion FROM %R
|
||||||
|
GROUP BY repositoryPHID',
|
||||||
|
$table);
|
||||||
|
$max_versions = ipull($max_versions, 'maxVersion', 'repositoryPHID');
|
||||||
|
|
||||||
|
$unsynchronized_phids = array();
|
||||||
|
foreach ($max_versions as $repository_phid => $max_version) {
|
||||||
|
$our_version = idx($our_versions, $repository_phid);
|
||||||
|
if (($our_version === null) || ($our_version < $max_version)) {
|
||||||
|
$unsynchronized_phids[] = $repository_phid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$unsynchronized_phids) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return id(new PhabricatorRepositoryQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs($unsynchronized_phids)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ abstract class PhabricatorRepositoryManagementWorkflow
|
||||||
$identifiers = $args->getArg($param);
|
$identifiers = $args->getArg($param);
|
||||||
|
|
||||||
if (!$identifiers) {
|
if (!$identifiers) {
|
||||||
return null;
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = id(new PhabricatorRepositoryQuery())
|
$query = id(new PhabricatorRepositoryQuery())
|
||||||
|
|
|
@ -1506,9 +1506,18 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
||||||
return $this->setDetail('hosting-enabled', $enabled);
|
return $this->setDetail('hosting-enabled', $enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canServeProtocol($protocol, $write) {
|
public function canServeProtocol(
|
||||||
if (!$this->isTracked()) {
|
$protocol,
|
||||||
return false;
|
$write,
|
||||||
|
$is_intracluster = false) {
|
||||||
|
|
||||||
|
// See T13192. If a repository is inactive, don't serve it to users. We
|
||||||
|
// still synchronize it within the cluster and serve it to other repository
|
||||||
|
// nodes.
|
||||||
|
if (!$is_intracluster) {
|
||||||
|
if (!$this->isTracked()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$clone_uris = $this->getCloneURIs();
|
$clone_uris = $this->getCloneURIs();
|
||||||
|
|
|
@ -256,13 +256,16 @@ final class PhabricatorMultiFactorSettingsPanel
|
||||||
// sometimes requires us to push a challenge to them as a side effect (for
|
// sometimes requires us to push a challenge to them as a side effect (for
|
||||||
// example, with SMS).
|
// example, with SMS).
|
||||||
if (!$request->isFormPost() || !$request->getBool('mfa.start')) {
|
if (!$request->isFormPost() || !$request->getBool('mfa.start')) {
|
||||||
$description = $selected_provider->getEnrollDescription($viewer);
|
$enroll = $selected_provider->getEnrollMessage();
|
||||||
|
if (!strlen($enroll)) {
|
||||||
|
$enroll = $selected_provider->getEnrollDescription($viewer);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->newDialog()
|
return $this->newDialog()
|
||||||
->addHiddenInput('providerPHID', $selected_provider->getPHID())
|
->addHiddenInput('providerPHID', $selected_provider->getPHID())
|
||||||
->addHiddenInput('mfa.start', 1)
|
->addHiddenInput('mfa.start', 1)
|
||||||
->setTitle(pht('Add Authentication Factor'))
|
->setTitle(pht('Add Authentication Factor'))
|
||||||
->appendChild(new PHUIRemarkupView($viewer, $description))
|
->appendChild(new PHUIRemarkupView($viewer, $enroll))
|
||||||
->addCancelButton($cancel_uri)
|
->addCancelButton($cancel_uri)
|
||||||
->addSubmitButton($selected_provider->getEnrollButtonText($viewer));
|
->addSubmitButton($selected_provider->getEnrollButtonText($viewer));
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ final class TransactionSearchConduitAPIMethod
|
||||||
$object);
|
$object);
|
||||||
|
|
||||||
$xaction_query
|
$xaction_query
|
||||||
|
->needHandles(false)
|
||||||
->withObjectPHIDs(array($object->getPHID()))
|
->withObjectPHIDs(array($object->getPHID()))
|
||||||
->setViewer($viewer);
|
->setViewer($viewer);
|
||||||
|
|
||||||
|
|
|
@ -1279,14 +1279,41 @@ abstract class PhabricatorEditEngine
|
||||||
|
|
||||||
$fields = $this->willBuildEditForm($object, $fields);
|
$fields = $this->willBuildEditForm($object, $fields);
|
||||||
|
|
||||||
|
$request_path = $request->getRequestURI()
|
||||||
|
->setQueryParams(array());
|
||||||
|
|
||||||
$form = id(new AphrontFormView())
|
$form = id(new AphrontFormView())
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
|
->setAction($request_path)
|
||||||
->addHiddenInput('editEngine', 'true');
|
->addHiddenInput('editEngine', 'true');
|
||||||
|
|
||||||
foreach ($this->contextParameters as $param) {
|
foreach ($this->contextParameters as $param) {
|
||||||
$form->addHiddenInput($param, $request->getStr($param));
|
$form->addHiddenInput($param, $request->getStr($param));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$requires_mfa = false;
|
||||||
|
if ($object instanceof PhabricatorEditEngineMFAInterface) {
|
||||||
|
$mfa_engine = PhabricatorEditEngineMFAEngine::newEngineForObject($object)
|
||||||
|
->setViewer($viewer);
|
||||||
|
$requires_mfa = $mfa_engine->shouldRequireMFA();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($requires_mfa) {
|
||||||
|
$message = pht(
|
||||||
|
'You will be required to provide multi-factor credentials to make '.
|
||||||
|
'changes.');
|
||||||
|
$form->appendChild(
|
||||||
|
id(new PHUIInfoView())
|
||||||
|
->setSeverity(PHUIInfoView::SEVERITY_MFA)
|
||||||
|
->setErrors(array($message)));
|
||||||
|
|
||||||
|
// TODO: This should also set workflow on the form, so the user doesn't
|
||||||
|
// lose any form data if they "Cancel". However, Maniphest currently
|
||||||
|
// overrides "newEditResponse()" if the request is Ajax and returns a
|
||||||
|
// bag of view data. This can reasonably be cleaned up when workboards
|
||||||
|
// get their next iteration.
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
if (!$field->getIsFormField()) {
|
if (!$field->getIsFormField()) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1565,11 +1592,19 @@ abstract class PhabricatorEditEngine
|
||||||
|
|
||||||
$comment_uri = $this->getEditURI($object, 'comment/');
|
$comment_uri = $this->getEditURI($object, 'comment/');
|
||||||
|
|
||||||
|
$requires_mfa = false;
|
||||||
|
if ($object instanceof PhabricatorEditEngineMFAInterface) {
|
||||||
|
$mfa_engine = PhabricatorEditEngineMFAEngine::newEngineForObject($object)
|
||||||
|
->setViewer($viewer);
|
||||||
|
$requires_mfa = $mfa_engine->shouldRequireMFA();
|
||||||
|
}
|
||||||
|
|
||||||
$view = id(new PhabricatorApplicationTransactionCommentView())
|
$view = id(new PhabricatorApplicationTransactionCommentView())
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
->setObjectPHID($object_phid)
|
->setObjectPHID($object_phid)
|
||||||
->setHeaderText($header_text)
|
->setHeaderText($header_text)
|
||||||
->setAction($comment_uri)
|
->setAction($comment_uri)
|
||||||
|
->setRequiresMFA($requires_mfa)
|
||||||
->setSubmitButtonName($button_text);
|
->setSubmitButtonName($button_text);
|
||||||
|
|
||||||
$draft = PhabricatorVersionedDraft::loadDraft(
|
$draft = PhabricatorVersionedDraft::loadDraft(
|
||||||
|
|
|
@ -88,6 +88,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
private $hasRequiredMFA = false;
|
private $hasRequiredMFA = false;
|
||||||
private $request;
|
private $request;
|
||||||
private $cancelURI;
|
private $cancelURI;
|
||||||
|
private $extensions;
|
||||||
|
|
||||||
const STORAGE_ENCODING_BINARY = 'binary';
|
const STORAGE_ENCODING_BINARY = 'binary';
|
||||||
|
|
||||||
|
@ -1013,6 +1014,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
$errors[] = $this->validateAllTransactions($object, $xactions);
|
$errors[] = $this->validateAllTransactions($object, $xactions);
|
||||||
|
$errors[] = $this->validateTransactionsWithExtensions($object, $xactions);
|
||||||
$errors = array_mergev($errors);
|
$errors = array_mergev($errors);
|
||||||
|
|
||||||
$continue_on_missing = $this->getContinueOnMissingFields();
|
$continue_on_missing = $this->getContinueOnMissingFields();
|
||||||
|
@ -2667,9 +2669,15 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$transaction_type) {
|
$transaction_type) {
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
|
||||||
$factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere(
|
$factors = id(new PhabricatorAuthFactorConfigQuery())
|
||||||
'userPHID = %s',
|
->setViewer($this->getActor())
|
||||||
$this->getActingAsPHID());
|
->withUserPHIDs(array($this->getActingAsPHID()))
|
||||||
|
->withFactorProviderStatuses(
|
||||||
|
array(
|
||||||
|
PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE,
|
||||||
|
PhabricatorAuthFactorProviderStatus::STATUS_DEPRECATED,
|
||||||
|
))
|
||||||
|
->execute();
|
||||||
|
|
||||||
foreach ($xactions as $xaction) {
|
foreach ($xactions as $xaction) {
|
||||||
if (!$factors) {
|
if (!$factors) {
|
||||||
|
@ -3286,7 +3294,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
// move the other transactions down so they provide context above the
|
// move the other transactions down so they provide context above the
|
||||||
// actual comment.
|
// actual comment.
|
||||||
|
|
||||||
$comment = $xaction->getBodyForMail();
|
$comment = $this->getBodyForTextMail($xaction);
|
||||||
if ($comment !== null) {
|
if ($comment !== null) {
|
||||||
$is_comment = true;
|
$is_comment = true;
|
||||||
$comments[] = array(
|
$comments[] = array(
|
||||||
|
@ -3299,12 +3307,12 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$is_comment || !$seen_comment) {
|
if (!$is_comment || !$seen_comment) {
|
||||||
$header = $xaction->getTitleForTextMail();
|
$header = $this->getTitleForTextMail($xaction);
|
||||||
if ($header !== null) {
|
if ($header !== null) {
|
||||||
$headers[] = $header;
|
$headers[] = $header;
|
||||||
}
|
}
|
||||||
|
|
||||||
$header_html = $xaction->getTitleForHTMLMail();
|
$header_html = $this->getTitleForHTMLMail($xaction);
|
||||||
if ($header_html !== null) {
|
if ($header_html !== null) {
|
||||||
$headers_html[] = $header_html;
|
$headers_html[] = $header_html;
|
||||||
}
|
}
|
||||||
|
@ -3384,12 +3392,12 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
// If this is not the first comment in the mail, add the header showing
|
// If this is not the first comment in the mail, add the header showing
|
||||||
// who wrote the comment immediately above the comment.
|
// who wrote the comment immediately above the comment.
|
||||||
if (!$is_initial) {
|
if (!$is_initial) {
|
||||||
$header = $xaction->getTitleForTextMail();
|
$header = $this->getTitleForTextMail($xaction);
|
||||||
if ($header !== null) {
|
if ($header !== null) {
|
||||||
$body->addRawPlaintextSection($header);
|
$body->addRawPlaintextSection($header);
|
||||||
}
|
}
|
||||||
|
|
||||||
$header_html = $xaction->getTitleForHTMLMail();
|
$header_html = $this->getTitleForHTMLMail($xaction);
|
||||||
if ($header_html !== null) {
|
if ($header_html !== null) {
|
||||||
$body->addRawHTMLSection($header_html);
|
$body->addRawHTMLSection($header_html);
|
||||||
}
|
}
|
||||||
|
@ -4848,6 +4856,13 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
private function requireMFA(PhabricatorLiskDAO $object, array $xactions) {
|
private function requireMFA(PhabricatorLiskDAO $object, array $xactions) {
|
||||||
|
$actor = $this->getActor();
|
||||||
|
|
||||||
|
// Let omnipotent editors skip MFA. This is mostly aimed at scripts.
|
||||||
|
if ($actor->isOmnipotent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$editor_class = get_class($this);
|
$editor_class = get_class($this);
|
||||||
|
|
||||||
$object_phid = $object->getPHID();
|
$object_phid = $object->getPHID();
|
||||||
|
@ -4862,8 +4877,6 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$editor_class);
|
$editor_class);
|
||||||
}
|
}
|
||||||
|
|
||||||
$actor = $this->getActor();
|
|
||||||
|
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
if ($request === null) {
|
if ($request === null) {
|
||||||
$source_type = $this->getContentSource()->getSourceTypeConstant();
|
$source_type = $this->getContentSource()->getSourceTypeConstant();
|
||||||
|
@ -4975,4 +4988,112 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
return $xactions;
|
return $xactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getTitleForTextMail(
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
$type = $xaction->getTransactionType();
|
||||||
|
|
||||||
|
$xtype = $this->getModularTransactionType($type);
|
||||||
|
if ($xtype) {
|
||||||
|
$xtype = clone $xtype;
|
||||||
|
$xtype->setStorage($xaction);
|
||||||
|
$comment = $xtype->getTitleForTextMail();
|
||||||
|
if ($comment !== false) {
|
||||||
|
return $comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $xaction->getTitleForTextMail();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTitleForHTMLMail(
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
$type = $xaction->getTransactionType();
|
||||||
|
|
||||||
|
$xtype = $this->getModularTransactionType($type);
|
||||||
|
if ($xtype) {
|
||||||
|
$xtype = clone $xtype;
|
||||||
|
$xtype->setStorage($xaction);
|
||||||
|
$comment = $xtype->getTitleForHTMLMail();
|
||||||
|
if ($comment !== false) {
|
||||||
|
return $comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $xaction->getTitleForHTMLMail();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getBodyForTextMail(
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
$type = $xaction->getTransactionType();
|
||||||
|
|
||||||
|
$xtype = $this->getModularTransactionType($type);
|
||||||
|
if ($xtype) {
|
||||||
|
$xtype = clone $xtype;
|
||||||
|
$xtype->setStorage($xaction);
|
||||||
|
$comment = $xtype->getBodyForTextMail();
|
||||||
|
if ($comment !== false) {
|
||||||
|
return $comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $xaction->getBodyForMail();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( Extensions )--------------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
private function validateTransactionsWithExtensions(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
array $xactions) {
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
$extensions = $this->getEditorExtensions();
|
||||||
|
foreach ($extensions as $extension) {
|
||||||
|
$extension_errors = $extension
|
||||||
|
->setObject($object)
|
||||||
|
->validateTransactions($object, $xactions);
|
||||||
|
|
||||||
|
assert_instances_of(
|
||||||
|
$extension_errors,
|
||||||
|
'PhabricatorApplicationTransactionValidationError');
|
||||||
|
|
||||||
|
$errors[] = $extension_errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_mergev($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getEditorExtensions() {
|
||||||
|
if ($this->extensions === null) {
|
||||||
|
$this->extensions = $this->newEditorExtensions();
|
||||||
|
}
|
||||||
|
return $this->extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newEditorExtensions() {
|
||||||
|
$extensions = PhabricatorEditorExtension::getAllExtensions();
|
||||||
|
|
||||||
|
$actor = $this->getActor();
|
||||||
|
$object = $this->object;
|
||||||
|
foreach ($extensions as $key => $extension) {
|
||||||
|
|
||||||
|
$extension = id(clone $extension)
|
||||||
|
->setViewer($actor)
|
||||||
|
->setEditor($this)
|
||||||
|
->setObject($object);
|
||||||
|
|
||||||
|
if (!$extension->supportsObject($this, $object)) {
|
||||||
|
unset($extensions[$key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extensions[$key] = $extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class PhabricatorEditorExtension
|
||||||
|
extends Phobject {
|
||||||
|
|
||||||
|
private $viewer;
|
||||||
|
private $editor;
|
||||||
|
private $object;
|
||||||
|
|
||||||
|
final public function getExtensionKey() {
|
||||||
|
return $this->getPhobjectClassConstant('EXTENSIONKEY');
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function setEditor(
|
||||||
|
PhabricatorApplicationTransactionEditor $editor) {
|
||||||
|
$this->editor = $editor;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function getEditor() {
|
||||||
|
return $this->editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function setViewer(PhabricatorUser $viewer) {
|
||||||
|
$this->viewer = $viewer;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function getViewer() {
|
||||||
|
return $this->viewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function setObject(
|
||||||
|
PhabricatorApplicationTransactionInterface $object) {
|
||||||
|
$this->object = $object;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function getAllExtensions() {
|
||||||
|
return id(new PhutilClassMapQuery())
|
||||||
|
->setAncestorClass(__CLASS__)
|
||||||
|
->setUniqueMethod('getExtensionKey')
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function getExtensionName();
|
||||||
|
|
||||||
|
public function supportsObject(
|
||||||
|
PhabricatorApplicationTransactionEditor $editor,
|
||||||
|
PhabricatorApplicationTransactionInterface $object) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateTransactions($object, array $xactions) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function newTransactionError(
|
||||||
|
PhabricatorApplicationTransaction $xaction,
|
||||||
|
$title,
|
||||||
|
$message) {
|
||||||
|
return new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$xaction->getTransactionType(),
|
||||||
|
$title,
|
||||||
|
$message,
|
||||||
|
$xaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function newRequiredTransasctionError(
|
||||||
|
PhabricatorApplicationTransaction $xaction,
|
||||||
|
$message) {
|
||||||
|
return $this->newError($xaction, pht('Required'), $message)
|
||||||
|
->setIsMissingFieldError(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function newInvalidTransactionError(
|
||||||
|
PhabricatorApplicationTransaction $xaction,
|
||||||
|
$message) {
|
||||||
|
return $this->newTransactionError($xaction, pht('Invalid'), $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorEditorExtensionModule
|
||||||
|
extends PhabricatorConfigModule {
|
||||||
|
|
||||||
|
public function getModuleKey() {
|
||||||
|
return 'editor';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getModuleName() {
|
||||||
|
return pht('Engine: Editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderModuleStatus(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
$extensions = PhabricatorEditorExtension::getAllExtensions();
|
||||||
|
|
||||||
|
$rows = array();
|
||||||
|
foreach ($extensions as $extension) {
|
||||||
|
$rows[] = array(
|
||||||
|
get_class($extension),
|
||||||
|
$extension->getExtensionName(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id(new AphrontTableView($rows))
|
||||||
|
->setHeaders(
|
||||||
|
array(
|
||||||
|
pht('Class'),
|
||||||
|
pht('Name'),
|
||||||
|
))
|
||||||
|
->setColumnClasses(
|
||||||
|
array(
|
||||||
|
null,
|
||||||
|
'wide pri',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -740,8 +740,9 @@ abstract class PhabricatorApplicationTransaction
|
||||||
|
|
||||||
switch ($this->getTransactionType()) {
|
switch ($this->getTransactionType()) {
|
||||||
case PhabricatorTransactions::TYPE_TOKEN:
|
case PhabricatorTransactions::TYPE_TOKEN:
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
return true;
|
return true;
|
||||||
case PhabricatorTransactions::TYPE_EDGE:
|
case PhabricatorTransactions::TYPE_EDGE:
|
||||||
$edge_type = $this->getMetadataValue('edge:type');
|
$edge_type = $this->getMetadataValue('edge:type');
|
||||||
switch ($edge_type) {
|
switch ($edge_type) {
|
||||||
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
|
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
|
||||||
|
|
|
@ -431,4 +431,68 @@ abstract class PhabricatorModularTransactionType
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: See T12921. These APIs are somewhat aspirational. For now, all of
|
||||||
|
// these use "TARGET_TEXT" (even the HTML methods!) and the body methods
|
||||||
|
// actually return Remarkup, not text or HTML.
|
||||||
|
|
||||||
|
final public function getTitleForTextMail() {
|
||||||
|
return $this->getTitleForMailWithRenderingTarget(
|
||||||
|
PhabricatorApplicationTransaction::TARGET_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function getTitleForHTMLMail() {
|
||||||
|
return $this->getTitleForMailWithRenderingTarget(
|
||||||
|
PhabricatorApplicationTransaction::TARGET_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function getBodyForTextMail() {
|
||||||
|
return $this->getBodyForMailWithRenderingTarget(
|
||||||
|
PhabricatorApplicationTransaction::TARGET_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function getBodyForHTMLMail() {
|
||||||
|
return $this->getBodyForMailWithRenderingTarget(
|
||||||
|
PhabricatorApplicationTransaction::TARGET_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTitleForMailWithRenderingTarget($target) {
|
||||||
|
$storage = $this->getStorage();
|
||||||
|
|
||||||
|
$old_target = $storage->getRenderingTarget();
|
||||||
|
try {
|
||||||
|
$storage->setRenderingTarget($target);
|
||||||
|
$result = $this->getTitleForMail();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$storage->setRenderingTarget($old_target);
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
$storage->setRenderingTarget($old_target);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBodyForMailWithRenderingTarget($target) {
|
||||||
|
$storage = $this->getStorage();
|
||||||
|
|
||||||
|
$old_target = $storage->getRenderingTarget();
|
||||||
|
try {
|
||||||
|
$storage->setRenderingTarget($target);
|
||||||
|
$result = $this->getBodyForMail();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$storage->setRenderingTarget($old_target);
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
$storage->setRenderingTarget($old_target);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTitleForMail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getBodyForMail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
final class PhabricatorApplicationTransactionCommentView
|
||||||
* @concrete-extensible
|
extends AphrontView {
|
||||||
*/
|
|
||||||
class PhabricatorApplicationTransactionCommentView extends AphrontView {
|
|
||||||
|
|
||||||
private $submitButtonName;
|
private $submitButtonName;
|
||||||
private $action;
|
private $action;
|
||||||
|
@ -24,6 +22,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
|
||||||
private $infoView;
|
private $infoView;
|
||||||
private $editEngineLock;
|
private $editEngineLock;
|
||||||
private $noBorder;
|
private $noBorder;
|
||||||
|
private $requiresMFA;
|
||||||
|
|
||||||
private $currentVersion;
|
private $currentVersion;
|
||||||
private $versionedDraft;
|
private $versionedDraft;
|
||||||
|
@ -160,6 +159,15 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
|
||||||
return $this->editEngineLock;
|
return $this->editEngineLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setRequiresMFA($requires_mfa) {
|
||||||
|
$this->requiresMFA = $requires_mfa;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiresMFA() {
|
||||||
|
return $this->requiresMFA;
|
||||||
|
}
|
||||||
|
|
||||||
public function setTransactionTimeline(
|
public function setTransactionTimeline(
|
||||||
PhabricatorApplicationTransactionView $timeline) {
|
PhabricatorApplicationTransactionView $timeline) {
|
||||||
|
|
||||||
|
@ -187,8 +195,8 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->getUser();
|
$viewer = $this->getViewer();
|
||||||
if (!$user->isLoggedIn()) {
|
if (!$viewer->isLoggedIn()) {
|
||||||
$uri = id(new PhutilURI('/login/'))
|
$uri = id(new PhutilURI('/login/'))
|
||||||
->setQueryParam('next', (string)$this->getRequestURI());
|
->setQueryParam('next', (string)$this->getRequestURI());
|
||||||
return id(new PHUIObjectBoxView())
|
return id(new PHUIObjectBoxView())
|
||||||
|
@ -203,6 +211,25 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
|
||||||
pht('Log In to Comment')));
|
pht('Log In to Comment')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->getRequiresMFA()) {
|
||||||
|
if (!$viewer->getIsEnrolledInMultiFactor()) {
|
||||||
|
$viewer->updateMultiFactorEnrollment();
|
||||||
|
if (!$viewer->getIsEnrolledInMultiFactor()) {
|
||||||
|
$messages = array();
|
||||||
|
$messages[] = pht(
|
||||||
|
'You must provide multi-factor credentials to comment or make '.
|
||||||
|
'changes, but you do not have multi-factor authentication '.
|
||||||
|
'configured on your account.');
|
||||||
|
$messages[] = pht(
|
||||||
|
'To continue, configure multi-factor authentication in Settings.');
|
||||||
|
|
||||||
|
return id(new PHUIInfoView())
|
||||||
|
->setSeverity(PHUIInfoView::SEVERITY_MFA)
|
||||||
|
->setErrors($messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$data = array();
|
$data = array();
|
||||||
|
|
||||||
$comment = $this->renderCommentPanel();
|
$comment = $this->renderCommentPanel();
|
||||||
|
@ -226,7 +253,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
|
||||||
}
|
}
|
||||||
|
|
||||||
require_celerity_resource('phui-comment-form-css');
|
require_celerity_resource('phui-comment-form-css');
|
||||||
$image_uri = $user->getProfileImageURI();
|
$image_uri = $viewer->getProfileImageURI();
|
||||||
$image = phutil_tag(
|
$image = phutil_tag(
|
||||||
'div',
|
'div',
|
||||||
array(
|
array(
|
||||||
|
@ -388,6 +415,17 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
|
||||||
$form->appendChild($info_view);
|
$form->appendChild($info_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->getRequiresMFA()) {
|
||||||
|
$message = pht(
|
||||||
|
'You will be required to provide multi-factor credentials to '.
|
||||||
|
'comment or make changes.');
|
||||||
|
|
||||||
|
$form->appendChild(
|
||||||
|
id(new PHUIInfoView())
|
||||||
|
->setSeverity(PHUIInfoView::SEVERITY_MFA)
|
||||||
|
->setErrors(array($message)));
|
||||||
|
}
|
||||||
|
|
||||||
$form->appendChild($invisi_bar);
|
$form->appendChild($invisi_bar);
|
||||||
$form->addClass('phui-comment-has-actions');
|
$form->addClass('phui-comment-has-actions');
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue