Merge branch 'master' into blender-tweaks

This commit is contained in:
2019-09-02 10:19:50 +02:00
389 changed files with 12755 additions and 6339 deletions

View File

@@ -1 +0,0 @@
../scripts/user/account_admin.php

View File

@@ -1 +0,0 @@
../scripts/people/manage_people.php

1
bin/user Symbolic link
View File

@@ -0,0 +1 @@
../scripts/setup/manage_user.php

View File

@@ -9,10 +9,10 @@ return array(
'names' => array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => 'efa1b78b',
'core.pkg.js' => '8225dc58',
'core.pkg.css' => 'eef4903d',
'core.pkg.js' => '73a06a9f',
'differential.pkg.css' => '8d8360fb',
'differential.pkg.js' => '67e02996',
'differential.pkg.js' => '0b037a4f',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => 'a98c0bf7',
'maniphest.pkg.css' => '35995d6d',
@@ -24,7 +24,7 @@ return array(
'rsrc/audio/basic/ting.mp3' => 'a6b6540e',
'rsrc/css/aphront/aphront-bars.css' => '4a327b4a',
'rsrc/css/aphront/dark-console.css' => '7f06cda2',
'rsrc/css/aphront/dialog-view.css' => 'b70c70df',
'rsrc/css/aphront/dialog-view.css' => '874f5c06',
'rsrc/css/aphront/list-filter-view.css' => 'feb64255',
'rsrc/css/aphront/multi-column.css' => 'fbc00ba3',
'rsrc/css/aphront/notification.css' => '30240bd2',
@@ -92,7 +92,7 @@ return array(
'rsrc/css/application/pholio/pholio.css' => '88ef5ef1',
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8',
'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241',
'rsrc/css/application/phortune/phortune.css' => '12e8251a',
'rsrc/css/application/phortune/phortune.css' => '508a1a5e',
'rsrc/css/application/phrequent/phrequent.css' => 'bd79cc67',
'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0',
'rsrc/css/application/policy/policy-edit.css' => '8794e2ed',
@@ -255,7 +255,7 @@ return array(
'rsrc/externals/javelin/lib/URI.js' => '2e255291',
'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
'rsrc/externals/javelin/lib/Workflow.js' => '851f642d',
'rsrc/externals/javelin/lib/Workflow.js' => '945ff654',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => 'ca686f71',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae',
@@ -414,16 +414,16 @@ return array(
'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
'rsrc/js/application/projects/WorkboardBoard.js' => 'c02a5497',
'rsrc/js/application/projects/WorkboardBoard.js' => 'b46d88c5',
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '2a61f8d4',
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '84f82dad',
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
'rsrc/js/application/projects/WorkboardController.js' => '42c7a5a7',
'rsrc/js/application/projects/WorkboardController.js' => 'b9d0c2f3',
'rsrc/js/application/projects/WorkboardDropEffect.js' => '8e0aa661',
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f',
'rsrc/js/application/projects/behavior-project-boards.js' => 'aad45445',
'rsrc/js/application/projects/behavior-project-boards.js' => '58cb6a88',
'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
@@ -486,7 +486,7 @@ return array(
'rsrc/js/core/behavior-line-linker.js' => 'e15c8b1f',
'rsrc/js/core/behavior-linked-container.js' => '74446546',
'rsrc/js/core/behavior-more.js' => '506aa3f4',
'rsrc/js/core/behavior-object-selector.js' => 'a4af0b4a',
'rsrc/js/core/behavior-object-selector.js' => '98ef467f',
'rsrc/js/core/behavior-oncopy.js' => 'ff7b3f22',
'rsrc/js/core/behavior-phabricator-nav.js' => 'f166c949',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '2f80333f',
@@ -532,7 +532,7 @@ return array(
'almanac-css' => '2e050f4f',
'aphront-bars' => '4a327b4a',
'aphront-dark-console-css' => '7f06cda2',
'aphront-dialog-view-css' => 'b70c70df',
'aphront-dialog-view-css' => '874f5c06',
'aphront-list-filter-view-css' => 'feb64255',
'aphront-multi-column-view-css' => 'fbc00ba3',
'aphront-panel-view-css' => '46923d46',
@@ -647,7 +647,7 @@ return array(
'javelin-behavior-phabricator-line-linker' => 'e15c8b1f',
'javelin-behavior-phabricator-nav' => 'f166c949',
'javelin-behavior-phabricator-notification-example' => '29819b75',
'javelin-behavior-phabricator-object-selector' => 'a4af0b4a',
'javelin-behavior-phabricator-object-selector' => '98ef467f',
'javelin-behavior-phabricator-oncopy' => 'ff7b3f22',
'javelin-behavior-phabricator-remarkup-assist' => '2f80333f',
'javelin-behavior-phabricator-reveal-content' => 'b105a3a6',
@@ -669,7 +669,7 @@ return array(
'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
'javelin-behavior-project-boards' => 'aad45445',
'javelin-behavior-project-boards' => '58cb6a88',
'javelin-behavior-project-create' => '34c53422',
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
'javelin-behavior-read-only-warning' => 'b9109f8f',
@@ -745,16 +745,16 @@ return array(
'javelin-view-renderer' => '9aae2b66',
'javelin-view-visitor' => '308f9fe4',
'javelin-websocket' => 'fdc13e4e',
'javelin-workboard-board' => 'c02a5497',
'javelin-workboard-board' => 'b46d88c5',
'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '2a61f8d4',
'javelin-workboard-card-template' => '84f82dad',
'javelin-workboard-column' => 'c3d24e63',
'javelin-workboard-controller' => '42c7a5a7',
'javelin-workboard-controller' => 'b9d0c2f3',
'javelin-workboard-drop-effect' => '8e0aa661',
'javelin-workboard-header' => '111bfd2d',
'javelin-workboard-header-template' => 'ebe83a6b',
'javelin-workboard-order-template' => '03e8891f',
'javelin-workflow' => '851f642d',
'javelin-workflow' => '945ff654',
'maniphest-report-css' => '3d53188b',
'maniphest-task-edit-css' => '272daa84',
'maniphest-task-summary-css' => '61d1667e',
@@ -813,7 +813,7 @@ return array(
'pholio-inline-comments-css' => '722b48c2',
'phortune-credit-card-form' => 'd12d214f',
'phortune-credit-card-form-css' => '3b9868a8',
'phortune-css' => '12e8251a',
'phortune-css' => '508a1a5e',
'phortune-invoice-css' => '4436b241',
'phrequent-css' => 'bd79cc67',
'phriction-document-css' => '03380da0',
@@ -1133,9 +1133,6 @@ return array(
'javelin-stratcom',
'javelin-behavior',
),
'2a61f8d4' => array(
'javelin-install',
),
'2a8b62d9' => array(
'multirow-row-manager',
'javelin-install',
@@ -1264,16 +1261,6 @@ return array(
'4234f572' => array(
'syntax-default-css',
),
'42c7a5a7' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-drag-and-drop-file-upload',
'javelin-workboard-board',
),
'4370900d' => array(
'javelin-install',
'javelin-util',
@@ -1412,6 +1399,16 @@ return array(
'javelin-vector',
'javelin-typeahead-static-source',
),
'58cb6a88' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'javelin-workboard-controller',
'javelin-workboard-drop-effect',
),
'5902260c' => array(
'javelin-util',
'javelin-magical-init',
@@ -1607,16 +1604,8 @@ return array(
'javelin-resource',
'javelin-routable',
),
'851f642d' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'84f82dad' => array(
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'87428eb2' => array(
'javelin-behavior',
@@ -1709,6 +1698,17 @@ return array(
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'945ff654' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'94681e22' => array(
'javelin-magical-init',
'javelin-install',
@@ -1730,6 +1730,12 @@ return array(
'javelin-dom',
'javelin-router',
),
'98ef467f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'9aae2b66' => array(
'javelin-install',
'javelin-util',
@@ -1790,12 +1796,6 @@ return array(
'phui-button-css',
'phui-button-simple-css',
),
'a4af0b4a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'a5257c4e' => array(
'javelin-install',
'javelin-dom',
@@ -1843,16 +1843,6 @@ return array(
'javelin-dom',
'javelin-util',
),
'aad45445' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'javelin-workboard-controller',
'javelin-workboard-drop-effect',
),
'ab85e184' => array(
'javelin-install',
'javelin-dom',
@@ -1901,6 +1891,18 @@ return array(
'b347a301' => array(
'javelin-behavior',
),
'b46d88c5' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
'javelin-workboard-header-template',
'javelin-workboard-card-template',
'javelin-workboard-order-template',
),
'b49fd60c' => array(
'multirow-row-manager',
'trigger-rule',
@@ -1943,20 +1945,18 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'bde53589' => array(
'phui-inline-comment-view-css',
),
'c02a5497' => array(
'b9d0c2f3' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
'javelin-workboard-header-template',
'javelin-workboard-card-template',
'javelin-workboard-order-template',
'phabricator-drag-and-drop-file-upload',
'javelin-workboard-board',
),
'bde53589' => array(
'phui-inline-comment-view-css',
),
'c03f2fb4' => array(
'javelin-install',

View File

@@ -0,0 +1,2 @@
RENAME TABLE {$NAMESPACE}_pastebin.edge
TO {$NAMESPACE}_paste.edge;

View File

@@ -0,0 +1,2 @@
RENAME TABLE {$NAMESPACE}_pastebin.edgedata
TO {$NAMESPACE}_paste.edgedata;

View File

@@ -0,0 +1,2 @@
RENAME TABLE {$NAMESPACE}_pastebin.pastebin_paste
TO {$NAMESPACE}_paste.paste;

View File

@@ -0,0 +1,2 @@
RENAME TABLE {$NAMESPACE}_pastebin.pastebin_pastetransaction
TO {$NAMESPACE}_paste.paste_transaction;

View File

@@ -0,0 +1,2 @@
RENAME TABLE {$NAMESPACE}_pastebin.pastebin_pastetransaction_comment
TO {$NAMESPACE}_paste.paste_transaction_comment;

View File

@@ -0,0 +1,12 @@
CREATE TABLE {$NAMESPACE}_phortune.phortune_accountemail (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
accountPHID VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
address VARCHAR(128) NOT NULL COLLATE {$COLLATE_SORT},
status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
addressKey VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
accessKey VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_phortune.phortune_accountemailtransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL,
oldValue LONGTEXT NOT NULL,
newValue LONGTEXT NOT NULL,
contentSource LONGTEXT NOT NULL,
metadata LONGTEXT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,10 @@
<?php
$edge_type = PhortuneAccountHasMerchantEdgeType::EDGECONST;
$table = new PhortuneCart();
foreach (new LiskMigrationIterator($table) as $cart) {
id(new PhabricatorEdgeEditor())
->addEdge($cart->getAccountPHID(), $edge_type, $cart->getMerchantPHID())
->save();
}

View File

@@ -0,0 +1,10 @@
<?php
$edge_type = PhortuneAccountHasMerchantEdgeType::EDGECONST;
$table = new PhortuneSubscription();
foreach (new LiskMigrationIterator($table) as $sub) {
id(new PhabricatorEdgeEditor())
->addEdge($sub->getAccountPHID(), $edge_type, $sub->getMerchantPHID())
->save();
}

View File

@@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_phortune.phortune_paymentmethodtransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL,
oldValue LONGTEXT NOT NULL,
newValue LONGTEXT NOT NULL,
contentSource LONGTEXT NOT NULL,
metadata LONGTEXT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_phortune.phortune_subscriptiontransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL,
oldValue LONGTEXT NOT NULL,
newValue LONGTEXT NOT NULL,
contentSource LONGTEXT NOT NULL,
metadata LONGTEXT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phortune.phortune_merchant
DROP viewPolicy;

View File

@@ -6,8 +6,8 @@ require_once $root.'/scripts/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setSynopsis(<<<EOSYNOPSIS
**people** __command__ [__options__]
Manage user profiles and accounts.
**user** __command__ [__options__]
Modify user accounts to regain access to an install.
EOSYNOPSIS
);

View File

@@ -1,228 +0,0 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$table = new PhabricatorUser();
$any_user = queryfx_one(
$table->establishConnection('r'),
'SELECT * FROM %T LIMIT 1',
$table->getTableName());
$is_first_user = (!$any_user);
if ($is_first_user) {
echo pht(
"WARNING\n\n".
"You're about to create the first account on this install. Normally, ".
"you should use the web interface to create the first account, not ".
"this script.\n\n".
"If you use the web interface, it will drop you into a nice UI workflow ".
"which gives you more help setting up your install. If you create an ".
"account with this script instead, you will skip the setup help and you ".
"will not be able to access it later.");
if (!phutil_console_confirm(pht('Skip easy setup and create account?'))) {
echo pht('Cancelled.')."\n";
exit(1);
}
}
echo pht(
'Enter a username to create a new account or edit an existing account.');
$username = phutil_console_prompt(pht('Enter a username:'));
if (!strlen($username)) {
echo pht('Cancelled.')."\n";
exit(1);
}
if (!PhabricatorUser::validateUsername($username)) {
$valid = PhabricatorUser::describeValidUsername();
echo pht("The username '%s' is invalid. %s", $username, $valid)."\n";
exit(1);
}
$user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$username);
if (!$user) {
$original = new PhabricatorUser();
echo pht("There is no existing user account '%s'.", $username)."\n";
$ok = phutil_console_confirm(
pht("Do you want to create a new '%s' account?", $username),
$default_no = false);
if (!$ok) {
echo pht('Cancelled.')."\n";
exit(1);
}
$user = new PhabricatorUser();
$user->setUsername($username);
$is_new = true;
} else {
$original = clone $user;
echo pht("There is an existing user account '%s'.", $username)."\n";
$ok = phutil_console_confirm(
pht("Do you want to edit the existing '%s' account?", $username),
$default_no = false);
if (!$ok) {
echo pht('Cancelled.')."\n";
exit(1);
}
$is_new = false;
}
$user_realname = $user->getRealName();
if (strlen($user_realname)) {
$realname_prompt = ' ['.$user_realname.']:';
} else {
$realname_prompt = ':';
}
$realname = nonempty(
phutil_console_prompt(pht('Enter user real name').$realname_prompt),
$user_realname);
$user->setRealName($realname);
// When creating a new user we prompt for an email address; when editing an
// existing user we just skip this because it would be quite involved to provide
// a reasonable CLI interface for editing multiple addresses and managing email
// verification and primary addresses.
$create_email = null;
if ($is_new) {
do {
$email = phutil_console_prompt(pht('Enter user email address:'));
$duplicate = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$email);
if ($duplicate) {
echo pht(
"ERROR: There is already a user with that email address. ".
"Each user must have a unique email address.\n");
} else {
break;
}
} while (true);
$create_email = $email;
}
$is_system_agent = $user->getIsSystemAgent();
$set_system_agent = phutil_console_confirm(
pht('Is this user a bot?'),
$default_no = !$is_system_agent);
$verify_email = null;
$set_verified = false;
// Allow administrators to verify primary email addresses at this time in edit
// scenarios. (Create will work just fine from here as we auto-verify email
// on create.)
if (!$is_new) {
$verify_email = $user->loadPrimaryEmail();
if (!$verify_email->getIsVerified()) {
$set_verified = phutil_console_confirm(
pht('Should the primary email address be verified?'),
$default_no = true);
} else {
// Already verified so let's not make a fuss.
$verify_email = null;
}
}
$is_admin = $user->getIsAdmin();
$set_admin = phutil_console_confirm(
pht('Should this user be an administrator?'),
$default_no = !$is_admin);
echo "\n\n".pht('ACCOUNT SUMMARY')."\n\n";
$tpl = "%12s %-30s %-30s\n";
printf($tpl, null, pht('OLD VALUE'), pht('NEW VALUE'));
printf($tpl, pht('Username'), $original->getUsername(), $user->getUsername());
printf($tpl, pht('Real Name'), $original->getRealName(), $user->getRealName());
if ($is_new) {
printf($tpl, pht('Email'), '', $create_email);
}
printf(
$tpl,
pht('Bot'),
$original->getIsSystemAgent() ? 'Y' : 'N',
$set_system_agent ? 'Y' : 'N');
if ($verify_email) {
printf(
$tpl,
pht('Verify Email'),
$verify_email->getIsVerified() ? 'Y' : 'N',
$set_verified ? 'Y' : 'N');
}
printf(
$tpl,
pht('Admin'),
$original->getIsAdmin() ? 'Y' : 'N',
$set_admin ? 'Y' : 'N');
echo "\n";
if (!phutil_console_confirm(pht('Save these changes?'), $default_no = false)) {
echo pht('Cancelled.')."\n";
exit(1);
}
$user->openTransaction();
$editor = new PhabricatorUserEditor();
// TODO: This is wrong, but we have a chicken-and-egg problem when you use
// this script to create the first user.
$editor->setActor($user);
if ($is_new) {
$email = id(new PhabricatorUserEmail())
->setAddress($create_email)
->setIsVerified(1);
// Unconditionally approve new accounts created from the CLI.
$user->setIsApproved(1);
$editor->createNewUser($user, $email);
} else {
if ($verify_email) {
$user->setIsEmailVerified(1);
$verify_email->setIsVerified($set_verified ? 1 : 0);
}
$editor->updateUser($user, $verify_email);
}
$editor->makeSystemAgentUser($user, $set_system_agent);
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(
PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE)
->setNewValue($set_admin);
$actor = PhabricatorUser::getOmnipotentUser();
$content_source = PhabricatorContentSource::newForSource(
PhabricatorConsoleContentSource::SOURCECONST);
$people_application_phid = id(new PhabricatorPeopleApplication())->getPHID();
$transaction_editor = id(new PhabricatorUserTransactionEditor())
->setActor($actor)
->setActingAsPHID($people_application_phid)
->setContentSource($content_source)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$transaction_editor->applyTransactions($user, $xactions);
$user->saveTransaction();
echo pht('Saved changes.')."\n";

View File

@@ -1,73 +0,0 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
if ($argc !== 5) {
echo pht(
"Usage: %s\n",
'add_user.php <username> <email> <realname> <admin_user>');
exit(1);
}
$username = $argv[1];
$email = $argv[2];
$realname = $argv[3];
$admin = $argv[4];
$admin = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$argv[4]);
if (!$admin) {
throw new Exception(
pht(
'Admin user must be the username of a valid Phabricator account, used '.
'to send the new user a welcome email.'));
}
$existing_user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$username);
if ($existing_user) {
throw new Exception(
pht(
"There is already a user with the username '%s'!",
$username));
}
$existing_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$email);
if ($existing_email) {
throw new Exception(
pht(
"There is already a user with the email '%s'!",
$email));
}
$user = new PhabricatorUser();
$user->setUsername($username);
$user->setRealname($realname);
$user->setIsApproved(1);
$email_object = id(new PhabricatorUserEmail())
->setAddress($email)
->setIsVerified(1);
id(new PhabricatorUserEditor())
->setActor($admin)
->createNewUser($user, $email_object);
$welcome_engine = id(new PhabricatorPeopleWelcomeMailEngine())
->setSender($admin)
->setRecipient($user);
if ($welcome_engine->canSendMail()) {
$welcome_engine->sendMail();
}
echo pht(
"Created user '%s' (realname='%s', email='%s').\n",
$username,
$realname,
$email);

File diff suppressed because it is too large Load Diff

View File

@@ -312,11 +312,17 @@ final class AphrontApplicationConfiguration
if ($response_exception) {
// If we encountered an exception while building a normal response, then
// encountered another exception while building a response for the first
// exception, just throw the original exception. It is more likely to be
// useful and point at a root cause than the second exception we ran into
// while telling the user about it.
// exception, throw an aggregate exception that will be unpacked by the
// higher-level handler. This is above our pay grade.
if ($original_exception) {
throw $original_exception;
throw new PhutilAggregateException(
pht(
'Encountered a processing exception, then another exception when '.
'trying to build a response for the first exception.'),
array(
$response_exception,
$original_exception,
));
}
// If we built a response successfully and then ran into an exception

View File

@@ -8,16 +8,19 @@ final class Aphront404Response extends AphrontHTMLResponse {
public function buildResponseString() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer = $request->getViewer();
$dialog = id(new AphrontDialogView())
->setUser($user)
->setViewer($viewer)
->setTitle(pht('404 Not Found'))
->addCancelButton('/', pht('Focus'))
->addCancelButton('/', pht('Return to Charted Waters'))
->appendParagraph(
pht(
'Do not dwell in the past, do not dream of the future, '.
'concentrate the mind on the present moment.'));
'You arrive at your destination, but there is nothing here.'))
->appendParagraph(
pht(
'Perhaps the real treasure was the friends you made '.
'along the way.'));
$view = id(new PhabricatorStandardPageView())
->setTitle(pht('404 Not Found'))

View File

@@ -61,9 +61,39 @@ final class AphrontUnhandledExceptionResponse
return 'unhandled-exception';
}
protected function getResponseBody() {
$ex = $this->exception;
private function getExceptionList() {
return $this->expandException($this->exception);
}
private function expandException($root) {
if ($root instanceof PhutilAggregateException) {
$list = array();
$list[] = $root;
foreach ($root->getExceptions() as $ex) {
foreach ($this->expandException($ex) as $child) {
$list[] = $child;
}
}
return $list;
}
return array($root);
}
protected function getResponseBody() {
$body = array();
foreach ($this->getExceptionList() as $ex) {
$body[] = $this->newHTMLMessage($ex);
}
return $body;
}
private function newHTMLMessage($ex) {
if ($ex instanceof AphrontMalformedRequestException) {
$title = $ex->getTitle();
} else {
@@ -122,12 +152,20 @@ final class AphrontUnhandledExceptionResponse
}
protected function buildPlainTextResponseString() {
$ex = $this->exception;
$messages = array();
foreach ($this->getExceptionList() as $exception) {
$messages[] = $this->newPlainTextMessage($exception);
}
return implode("\n\n", $messages);
}
private function newPlainTextMessage($exception) {
return pht(
'%s: %s',
get_class($ex),
$ex->getMessage());
get_class($exception),
$exception->getMessage());
}
}

View File

@@ -5,10 +5,6 @@ final class PhabricatorAuthChangePasswordAction
const TYPECONST = 'auth.password';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() {
return 20 / phutil_units('1 hour in seconds');
}

View File

@@ -0,0 +1,17 @@
<?php
final class PhabricatorAuthEmailLoginAction extends PhabricatorSystemAction {
const TYPECONST = 'mail.login';
public function getScoreThreshold() {
return 3 / phutil_units('1 hour in seconds');
}
public function getLimitExplanation() {
return pht(
'Too many account recovery email links have been sent to this account '.
'in a short period of time.');
}
}

View File

@@ -4,10 +4,6 @@ final class PhabricatorAuthNewFactorAction extends PhabricatorSystemAction {
const TYPECONST = 'auth.factor.new';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() {
return 60 / phutil_units('1 hour in seconds');
}

View File

@@ -4,10 +4,6 @@ final class PhabricatorAuthTestSMSAction extends PhabricatorSystemAction {
const TYPECONST = 'auth.sms.test';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() {
return 60 / phutil_units('1 hour in seconds');
}

View File

@@ -0,0 +1,18 @@
<?php
final class PhabricatorAuthTryEmailLoginAction
extends PhabricatorSystemAction {
const TYPECONST = 'mail.try-login';
public function getScoreThreshold() {
return 20 / phutil_units('1 hour in seconds');
}
public function getLimitExplanation() {
return pht(
'You have made too many account recovery requests in a short period '.
'of time.');
}
}

View File

@@ -4,10 +4,6 @@ final class PhabricatorAuthTryFactorAction extends PhabricatorSystemAction {
const TYPECONST = 'auth.factor';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() {
return 10 / phutil_units('1 hour in seconds');
}

View File

@@ -0,0 +1,18 @@
<?php
final class PhabricatorAuthTryPasswordAction
extends PhabricatorSystemAction {
const TYPECONST = 'auth.password';
public function getScoreThreshold() {
return 100 / phutil_units('1 hour in seconds');
}
public function getLimitExplanation() {
return pht(
'Your remote address has made too many login attempts in a short '.
'period of time.');
}
}

View File

@@ -0,0 +1,12 @@
<?php
final class PhabricatorAuthTryPasswordWithoutCAPTCHAAction
extends PhabricatorSystemAction {
const TYPECONST = 'auth.password-without-captcha';
public function getScoreThreshold() {
return 10 / phutil_units('1 hour in seconds');
}
}

View File

@@ -108,7 +108,7 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'PhabricatorAuthMessageListController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorAuthMessageEditController',
'(?P<id>[1-9]\d*)/' =>
'(?P<id>[^/]+)/' =>
'PhabricatorAuthMessageViewController',
),

View File

@@ -53,6 +53,14 @@ final class PhabricatorEmailLoginController
// it expensive to fish for valid email addresses while giving the user
// a better error if they goof their email.
$action_actor = PhabricatorSystemActionEngine::newActorFromRequest(
$request);
PhabricatorSystemActionEngine::willTakeAction(
array($action_actor),
new PhabricatorAuthTryEmailLoginAction(),
1);
$target_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$v_email);
@@ -94,29 +102,40 @@ final class PhabricatorEmailLoginController
}
if (!$errors) {
$body = $this->newAccountLoginMailBody(
$target_user,
$is_logged_in);
$target_address = new PhutilEmailAddress($target_email->getAddress());
$user_log = PhabricatorUserLog::initializeNewLog(
$viewer,
$target_user->getPHID(),
PhabricatorEmailLoginUserLogType::LOGTYPE);
$mail_engine = id(new PhabricatorPeopleEmailLoginMailEngine())
->setSender($viewer)
->setRecipient($target_user)
->setRecipientAddress($target_address)
->setActivityLog($user_log);
try {
$mail_engine->validateMail();
} catch (PhabricatorPeopleMailEngineException $ex) {
return $this->newDialog()
->setTitle($ex->getTitle())
->appendParagraph($ex->getBody())
->addCancelButton('/auth/start/', pht('Done'));
}
$mail_engine->sendMail();
if ($is_logged_in) {
$subject = pht('[Phabricator] Account Password Link');
$instructions = pht(
'An email has been sent containing a link you can use to set '.
'a password for your account.');
} else {
$subject = pht('[Phabricator] Account Login Link');
$instructions = pht(
'An email has been sent containing a link you can use to log '.
'in to your account.');
}
$mail = id(new PhabricatorMetaMTAMail())
->setSubject($subject)
->setForceDelivery(true)
->addRawTos(array($target_email->getAddress()))
->setBody($body)
->saveAndSend();
return $this->newDialog()
->setTitle(pht('Check Your Email'))
->setShortTitle(pht('Email Sent'))
@@ -182,55 +201,6 @@ final class PhabricatorEmailLoginController
->addSubmitButton(pht('Send Email'));
}
private function newAccountLoginMailBody(
PhabricatorUser $user,
$is_logged_in) {
$engine = new PhabricatorAuthSessionEngine();
$uri = $engine->getOneTimeLoginURI(
$user,
null,
PhabricatorAuthSessionEngine::ONETIME_RESET);
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$have_passwords = $this->isPasswordAuthEnabled();
if ($have_passwords) {
if ($is_logged_in) {
$body = pht(
'You can use this link to set a password on your account:'.
"\n\n %s\n",
$uri);
} else if ($is_serious) {
$body = pht(
"You can use this link to reset your Phabricator password:".
"\n\n %s\n",
$uri);
} else {
$body = pht(
"Condolences on forgetting your password. You can use this ".
"link to reset it:\n\n".
" %s\n\n".
"After you set a new password, consider writing it down on a ".
"sticky note and attaching it to your monitor so you don't ".
"forget again! Choosing a very short, easy-to-remember password ".
"like \"cat\" or \"1234\" might also help.\n\n".
"Best Wishes,\nPhabricator\n",
$uri);
}
} else {
$body = pht(
"You can use this login link to regain access to your Phabricator ".
"account:".
"\n\n".
" %s\n",
$uri);
}
return $body;
}
private function isPasswordAuthEnabled() {
return (bool)PhabricatorPasswordAuthProvider::getPasswordProvider();
}

View File

@@ -79,6 +79,7 @@ final class PhabricatorAuthEditController
}
$errors = array();
$validation_exception = null;
$v_login = $config->getShouldAllowLogin();
$v_registration = $config->getShouldAllowRegistration();
@@ -153,12 +154,16 @@ final class PhabricatorAuthEditController
$editor = id(new PhabricatorAuthProviderConfigEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->applyTransactions($config, $xactions);
->setContinueOnNoEffect(true);
$next_uri = $config->getURI();
try {
$editor->applyTransactions($config, $xactions);
$next_uri = $config->getURI();
return id(new AphrontRedirectResponse())->setURI($next_uri);
return id(new AphrontRedirectResponse())->setURI($next_uri);
} catch (Exception $ex) {
$validation_exception = $ex;
}
}
} else {
$properties = $provider->readFormValuesFromProvider();
@@ -325,12 +330,35 @@ final class PhabricatorAuthEditController
$provider->extendEditForm($request, $form, $properties, $issues);
$locked_config_key = 'auth.lock-config';
$is_locked = PhabricatorEnv::getEnvConfig($locked_config_key);
$locked_warning = null;
if ($is_locked && !$validation_exception) {
$message = pht(
'Authentication provider configuration is locked, and can not be '.
'changed without being unlocked. See the configuration setting %s '.
'for details.',
phutil_tag(
'a',
array(
'href' => '/config/edit/'.$locked_config_key,
),
$locked_config_key));
$locked_warning = id(new PHUIInfoView())
->setViewer($viewer)
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(array($message));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setDisabled($is_locked)
->setValue($button));
$help = $provider->getConfigurationHelp();
if ($help) {
$form->appendChild(id(new PHUIFormDividerControl()));
@@ -346,12 +374,16 @@ final class PhabricatorAuthEditController
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Provider'))
->setFormErrors($errors)
->setValidationException($validation_exception)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$locked_warning,
$form_box,
$footer,
));

View File

@@ -78,12 +78,14 @@ final class PhabricatorAuthListController
->setGuidanceContext($guidance_context)
->newInfoView();
$is_disabled = (!$can_manage || $is_locked);
$button = id(new PHUIButtonView())
->setTag('a')
->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)
->setHref($this->getApplicationURI('config/new/'))
->setIcon('fa-plus')
->setDisabled(!$can_manage || $is_locked)
->setDisabled($is_disabled)
->setWorkflow($is_disabled)
->setHref($this->getApplicationURI('config/new/'))
->setText(pht('Add Provider'));
$list->setFlush(true);

View File

@@ -9,6 +9,27 @@ final class PhabricatorAuthNewController
$viewer = $this->getViewer();
$cancel_uri = $this->getApplicationURI();
$locked_config_key = 'auth.lock-config';
$is_locked = PhabricatorEnv::getEnvConfig($locked_config_key);
if ($is_locked) {
$message = pht(
'Authentication provider configuration is locked, and can not be '.
'changed without being unlocked. See the configuration setting %s '.
'for details.',
phutil_tag(
'a',
array(
'href' => '/config/edit/'.$locked_config_key,
),
$locked_config_key));
return $this->newDialog()
->setUser($viewer)
->setTitle(pht('Authentication Config Locked'))
->appendChild($message)
->addCancelButton($cancel_uri);
}
$providers = PhabricatorAuthProvider::getAllBaseProviders();

View File

@@ -114,6 +114,86 @@ final class PhabricatorAuthProviderViewController
pht('Provider Type'),
$config->getProvider()->getProviderName());
$status = $this->buildStatus($config);
$view->addProperty(pht('Status'), $status);
return $view;
}
private function buildStatus(PhabricatorAuthProviderConfig $config) {
$viewer = $this->getViewer();
$view = id(new PHUIStatusListView())
->setViewer($viewer);
$icon_enabled = PHUIStatusItemView::ICON_ACCEPT;
$icon_disabled = PHUIStatusItemView::ICON_REJECT;
$icon_map = array(
true => $icon_enabled,
false => $icon_disabled,
);
$color_map = array(
true => 'green',
false => 'red',
);
$provider = $config->getProvider();
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(
$icon_map[$config->getIsEnabled()],
$color_map[$config->getIsEnabled()])
->setTarget(pht('Provider Enabled')));
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(
$icon_map[$config->getShouldAllowLogin()],
$color_map[$config->getShouldAllowLogin()])
->setTarget(pht('Allow Logins')));
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(
$icon_map[$config->getShouldAllowRegistration()],
$color_map[$config->getShouldAllowRegistration()])
->setTarget(pht('Allow Registration')));
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(
$icon_map[$config->getShouldAllowLink()],
$color_map[$config->getShouldAllowLink()])
->setTarget(pht('Allow Account Linking')));
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(
$icon_map[$config->getShouldAllowUnlink()],
$color_map[$config->getShouldAllowUnlink()])
->setTarget(pht('Allow Account Unlinking')));
if ($provider->shouldAllowEmailTrustConfiguration()) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(
$icon_map[$config->getShouldTrustEmails()],
$color_map[$config->getShouldTrustEmails()])
->setTarget(pht('Trust Email Addresses')));
}
if ($provider->supportsAutoLogin()) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(
$icon_map[$config->getShouldAutoLogin()],
$color_map[$config->getShouldAutoLogin()])
->setTarget(pht('Allow Auto Login')));
}
return $view;
}
}

View File

@@ -19,11 +19,14 @@ final class PhabricatorAuthMessageListController
$list = new PHUIObjectItemListView();
foreach ($types as $type) {
$message = idx($messages, $type->getMessageTypeKey());
if ($message) {
$href = $message->getURI();
$name = $message->getMessageTypeDisplayName();
} else {
$href = '/auth/message/edit/?messageKey='.$type->getMessageTypeKey();
$href = urisprintf(
'/auth/message/%s/',
$type->getMessageTypeKey());
$name = $type->getDisplayName();
}

View File

@@ -9,26 +9,61 @@ final class PhabricatorAuthMessageViewController
$this->requireApplicationCapability(
AuthManageProvidersCapability::CAPABILITY);
$message = id(new PhabricatorAuthMessageQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$message) {
return new Aphront404Response();
// The "id" in the URI may either be an actual storage record ID (if a
// message has already been created) or a message type key (for a message
// type which does not have a record yet).
// This flow allows messages which have not been set yet to have a detail
// page (so users can get detailed information about the message and see
// any default value).
$id = $request->getURIData('id');
if (ctype_digit($id)) {
$message = id(new PhabricatorAuthMessageQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$message) {
return new Aphront404Response();
}
} else {
$types = PhabricatorAuthMessageType::getAllMessageTypes();
if (!isset($types[$id])) {
return new Aphront404Response();
}
// If this message type already has a storage record, redirect to the
// canonical page for the record.
$message = id(new PhabricatorAuthMessageQuery())
->setViewer($viewer)
->withMessageKeys(array($id))
->executeOne();
if ($message) {
$message_uri = $message->getURI();
return id(new AphrontRedirectResponse())->setURI($message_uri);
}
// Otherwise, create an empty placeholder message object with the
// appropriate message type.
$message = PhabricatorAuthMessage::initializeNewMessage($types[$id]);
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($message->getObjectName())
->addTextCrumb($message->getMessageType()->getDisplayName())
->setBorder(true);
$header = $this->buildHeaderView($message);
$properties = $this->buildPropertiesView($message);
$curtain = $this->buildCurtain($message);
$timeline = $this->buildTransactionTimeline(
$message,
new PhabricatorAuthMessageTransactionQuery());
$timeline->setShouldTerminate(true);
if ($message->getID()) {
$timeline = $this->buildTransactionTimeline(
$message,
new PhabricatorAuthMessageTransactionQuery());
$timeline->setShouldTerminate(true);
} else {
$timeline = null;
}
$view = id(new PHUITwoColumnView())
->setHeader($header)
@@ -62,19 +97,36 @@ final class PhabricatorAuthMessageViewController
private function buildPropertiesView(PhabricatorAuthMessage $message) {
$viewer = $this->getViewer();
$message_type = $message->getMessageType();
$view = id(new PHUIPropertyListView())
->setViewer($viewer);
$view->addProperty(
pht('Description'),
$message->getMessageType()->getShortDescription());
$full_description = $message_type->getFullDescription();
if (strlen($full_description)) {
$view->addTextContent(new PHUIRemarkupView($viewer, $full_description));
} else {
$short_description = $message_type->getShortDescription();
$view->addProperty(pht('Description'), $short_description);
}
$view->addSectionHeader(
pht('Message Preview'),
PHUIPropertyListView::ICON_SUMMARY);
$message_text = $message->getMessageText();
if (strlen($message_text)) {
$view->addSectionHeader(
pht('Message Preview'),
PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent(
new PHUIRemarkupView($viewer, $message->getMessageText()));
$view->addTextContent(new PHUIRemarkupView($viewer, $message_text));
}
$default_text = $message_type->getDefaultMessageText();
if (strlen($default_text)) {
$view->addSectionHeader(
pht('Default Message'),
PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent(new PHUIRemarkupView($viewer, $default_text));
}
return $view;
}
@@ -88,13 +140,27 @@ final class PhabricatorAuthMessageViewController
$message,
PhabricatorPolicyCapability::CAN_EDIT);
if ($id) {
$edit_uri = urisprintf('message/edit/%s/', $id);
$edit_name = pht('Edit Message');
} else {
$edit_uri = urisprintf('message/edit/');
$params = array(
'messageKey' => $message->getMessageKey(),
);
$edit_uri = new PhutilURI($edit_uri, $params);
$edit_name = pht('Customize Message');
}
$edit_uri = $this->getApplicationURI($edit_uri);
$curtain = $this->newCurtainView($message);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Message'))
->setName($edit_name)
->setIcon('fa-pencil')
->setHref($this->getApplicationURI("message/edit/{$id}/"))
->setHref($edit_uri)
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));

View File

@@ -125,4 +125,25 @@ final class PhabricatorAuthProviderConfigEditor
return parent::mergeTransactions($u, $v);
}
protected function validateAllTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$errors = parent::validateAllTransactions($object, $xactions);
$locked_config_key = 'auth.lock-config';
$is_locked = PhabricatorEnv::getEnvConfig($locked_config_key);
if ($is_locked) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
null,
pht('Config Locked'),
pht('Authentication provider configuration is locked, and can not be '.
'changed without being unlocked.'),
null);
}
return $errors;
}
}

View File

@@ -294,8 +294,8 @@ final class PhabricatorAuthSessionEngine extends Phobject {
null,
$identity_phid,
($partial
? PhabricatorUserLog::ACTION_LOGIN_PARTIAL
: PhabricatorUserLog::ACTION_LOGIN));
? PhabricatorPartialLoginUserLogType::LOGTYPE
: PhabricatorLoginUserLogType::LOGTYPE));
$log->setDetails(
array(
@@ -366,7 +366,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog(
$user,
$user->getPHID(),
PhabricatorUserLog::ACTION_LOGOUT);
PhabricatorLogoutUserLogType::LOGTYPE);
$log->save();
$extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions();
@@ -688,13 +688,13 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$viewer->getPHID(),
PhabricatorUserLog::ACTION_ENTER_HISEC);
PhabricatorEnterHisecUserLogType::LOGTYPE);
$log->save();
} else {
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$viewer->getPHID(),
PhabricatorUserLog::ACTION_FAIL_HISEC);
PhabricatorFailHisecUserLogType::LOGTYPE);
$log->save();
}
}
@@ -831,7 +831,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$viewer->getPHID(),
PhabricatorUserLog::ACTION_EXIT_HISEC);
PhabricatorExitHisecUserLogType::LOGTYPE);
$log->save();
}
@@ -872,7 +872,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$viewer->getPHID(),
PhabricatorUserLog::ACTION_LOGIN_FULL);
PhabricatorFullLoginUserLogType::LOGTYPE);
$log->save();
unset($unguarded);
}
@@ -917,7 +917,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$viewer->getPHID(),
PhabricatorUserLog::ACTION_LOGIN_LEGALPAD);
PhabricatorSignDocumentsUserLogType::LOGTYPE);
$log->save();
}
unset($unguarded);

View File

@@ -194,6 +194,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$control = id(new PHUIFormNumberControl())
->setName($name)
->setDisableAutocomplete(true)
->setAutofocus(true)
->setValue($value)
->setError($error);
}

View File

@@ -0,0 +1,41 @@
<?php
final class PhabricatorAuthEmailLoginMessageType
extends PhabricatorAuthMessageType {
const MESSAGEKEY = 'mail.login';
public function getDisplayName() {
return pht('Mail Body: Email Login');
}
public function getShortDescription() {
return pht(
'Guidance in the message body when users request an email link '.
'to access their account.');
}
public function getFullDescription() {
return pht(
'Guidance included in the mail message body when users request an '.
'email link to access their account.'.
"\n\n".
'For installs with password authentication enabled, users access this '.
'workflow by using the "Forgot your password?" link on the login '.
'screen.'.
"\n\n".
'For installs without password authentication enabled, users access '.
'this workflow by using the "Send a login link to your email address." '.
'link on the login screen. This workflow allows users to recover '.
'access to their account if there is an issue with an external '.
'login service.');
}
public function getDefaultMessageText() {
return pht(
'You (or someone pretending to be you) recently requested an account '.
'recovery link be sent to this email address. If you did not make '.
'this request, you can ignore this message.');
}
}

View File

@@ -0,0 +1,18 @@
<?php
final class PhabricatorAuthEmailSetPasswordMessageType
extends PhabricatorAuthMessageType {
const MESSAGEKEY = 'mail.set-password';
public function getDisplayName() {
return pht('Mail Body: Set Password');
}
public function getShortDescription() {
return pht(
'Guidance in the message body when users set a password on an account '.
'which did not previously have a password.');
}
}

View File

@@ -28,5 +28,14 @@ abstract class PhabricatorAuthMessageType
}
abstract public function getDisplayName();
abstract public function getShortDescription();
public function getFullDescription() {
return null;
}
public function getDefaultMessageText() {
return null;
}
}

View File

@@ -6,7 +6,7 @@ final class PhabricatorAuthWelcomeMailMessageType
const MESSAGEKEY = 'mail.welcome';
public function getDisplayName() {
return pht('Welcome Email Body');
return pht('Mail Body: Welcome');
}
public function getShortDescription() {

View File

@@ -59,6 +59,14 @@ final class PhabricatorAsanaAuthProvider
return null;
}
if (strlen($uri->getFragment())) {
return null;
}
if ($uri->getQueryParamsAsPairList()) {
return null;
}
$context_id = $matches[1];
$task_id = $matches[2];

View File

@@ -341,6 +341,14 @@ final class PhabricatorJIRAAuthProvider
return null;
}
if (strlen($uri->getFragment())) {
return null;
}
if ($uri->getQueryParamsAsPairList()) {
return null;
}
$domain = $matches[1];
$issue = $matches[2];

View File

@@ -282,48 +282,29 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
$viewer = $request->getUser();
$content_source = PhabricatorContentSource::newFromRequest($request);
$captcha_limit = 5;
$hard_limit = 32;
$limit_window = phutil_units('15 minutes in seconds');
$rate_actor = PhabricatorSystemActionEngine::newActorFromRequest($request);
$failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP(
PhabricatorUserLog::ACTION_LOGIN_FAILURE,
$limit_window);
PhabricatorSystemActionEngine::willTakeAction(
array($rate_actor),
new PhabricatorAuthTryPasswordAction(),
1);
// If the same remote address has submitted several failed login attempts
// recently, require they provide a CAPTCHA response for new attempts.
$require_captcha = false;
$captcha_valid = false;
if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) {
if (count($failed_attempts) > $captcha_limit) {
try {
PhabricatorSystemActionEngine::willTakeAction(
array($rate_actor),
new PhabricatorAuthTryPasswordWithoutCAPTCHAAction(),
1);
} catch (PhabricatorSystemActionRateLimitException $ex) {
$require_captcha = true;
$captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request);
}
}
// If the user has submitted quite a few failed login attempts recently,
// give them a hard limit.
if (count($failed_attempts) > $hard_limit) {
$guidance = array();
$guidance[] = pht(
'Your remote address has failed too many login attempts recently. '.
'Wait a few minutes before trying again.');
$guidance[] = pht(
'If you are unable to log in to your account, you can '.
'[[ /login/email | send a reset link to your email address ]].');
$guidance = implode("\n\n", $guidance);
$dialog = $controller->newDialog()
->setTitle(pht('Too Many Login Attempts'))
->appendChild(new PHUIRemarkupView($viewer, $guidance))
->addCancelButton('/auth/start/', pht('Wait Patiently'));
return array(null, $dialog);
}
$response = null;
$account = null;
$log_user = null;
@@ -364,7 +345,7 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
$log = PhabricatorUserLog::initializeNewLog(
null,
$log_user ? $log_user->getPHID() : null,
PhabricatorUserLog::ACTION_LOGIN_FAILURE);
PhabricatorLoginFailureUserLogType::LOGTYPE);
$log->save();
}

View File

@@ -45,7 +45,7 @@ final class PhabricatorAuthMessage
}
public function getURI() {
return urisprintf('/auth/message/%s', $this->getID());
return urisprintf('/auth/message/%s/', $this->getID());
}
public function attachMessageType(PhabricatorAuthMessageType $type) {
@@ -75,12 +75,16 @@ final class PhabricatorAuthMessage
$message_key) {
$message = self::loadMessage($viewer, $message_key);
if (!$message) {
return null;
if ($message) {
$message_text = $message->getMessageText();
if (strlen($message_text)) {
return $message_text;
}
}
return $message->getMessageText();
$message_type = PhabricatorAuthMessageType::newFromKey($message_key);
return $message_type->getDefaultMessageText();
}

View File

@@ -14,15 +14,8 @@ final class PhabricatorAuthProviderConfigTransaction
const PROPERTY_KEY = 'auth:property';
private $provider;
public function setProvider(PhabricatorAuthProvider $provider) {
$this->provider = $provider;
return $this;
}
public function getProvider() {
return $this->provider;
return $this->getObject()->getProvider();
}
public function getApplicationName() {

View File

@@ -481,7 +481,7 @@ abstract class PhabricatorController extends AphrontController {
protected function buildTransactionTimeline(
PhabricatorApplicationTransactionInterface $object,
PhabricatorApplicationTransactionQuery $query,
PhabricatorApplicationTransactionQuery $query = null,
PhabricatorMarkupEngine $engine = null,
$view_data = array()) {
@@ -489,6 +489,17 @@ abstract class PhabricatorController extends AphrontController {
$viewer = $this->getViewer();
$xaction = $object->getApplicationTransactionTemplate();
if (!$query) {
$query = PhabricatorApplicationTransactionQuery::newQueryForObject(
$object);
if (!$query) {
throw new Exception(
pht(
'Unable to find transaction query for object of class "%s".',
get_class($object)));
}
}
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request)
->setURI(new PhutilURI(

View File

@@ -30,4 +30,8 @@ final class PhabricatorMarkupCache extends PhabricatorCacheDAO {
) + parent::getConfiguration();
}
public function getSchemaPersistence() {
return PhabricatorConfigTableSchema::PERSISTENCE_CACHE;
}
}

View File

@@ -41,7 +41,7 @@ final class ConduitGetCertificateConduitAPIMethod extends ConduitAPIMethod {
protected function execute(ConduitAPIRequest $request) {
$failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP(
PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE_FAILURE,
PhabricatorConduitCertificateFailureUserLogType::LOGTYPE,
60 * 5);
if (count($failed_attempts) > 5) {
@@ -61,7 +61,7 @@ final class ConduitGetCertificateConduitAPIMethod extends ConduitAPIMethod {
$log = PhabricatorUserLog::initializeNewLog(
$request->getUser(),
$info->getUserPHID(),
PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE)
PhabricatorConduitCertificateUserLogType::LOGTYPE)
->save();
}
@@ -85,7 +85,7 @@ final class ConduitGetCertificateConduitAPIMethod extends ConduitAPIMethod {
$log = PhabricatorUserLog::initializeNewLog(
$request->getUser(),
$info ? $info->getUserPHID() : '-',
PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE_FAILURE)
PhabricatorConduitCertificateFailureUserLogType::LOGTYPE)
->save();
}

View File

@@ -3,8 +3,24 @@
final class ConduitEpochParameterType
extends ConduitParameterType {
private $allowNull;
public function setAllowNull($allow_null) {
$this->allowNull = $allow_null;
return $this;
}
public function getAllowNull() {
return $this->allowNull;
}
protected function getParameterValue(array $request, $key, $strict) {
$value = parent::getParameterValue($request, $key, $strict);
if ($this->allowNull && ($value === null)) {
return $value;
}
$value = $this->parseIntValue($request, $key, $value, $strict);
if ($value <= 0) {

View File

@@ -536,6 +536,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'differential.whitespace-matters' => pht(
'Whitespace rendering is now handled automatically.'),
'phd.pid-directory' => pht(
'Phabricator daemons no longer use PID files.'),
);
return $ancient_config;

View File

@@ -0,0 +1,29 @@
<?php
final class PhabricatorZipSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
if (!extension_loaded('zip')) {
$message = pht(
'The PHP "zip" extension is not installed. This extension is '.
'required by certain data export operations, including exporting '.
'data to Excel.'.
"\n\n".
'To clear this setup issue, install the extension and restart your '.
'webserver.'.
"\n\n".
'You may safely ignore this issue if you do not plan to export '.
'data in Zip archives or Excel spreadsheets, or intend to install '.
'the extension later.');
$this->newIssue('extension.zip')
->setName(pht('Missing "zip" Extension'))
->setMessage($message)
->addPHPExtension('zip');
}
}
}

View File

@@ -30,11 +30,10 @@ final class PhabricatorConfigManagementSetWorkflow
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$argv = $args->getArg('args');
if (count($argv) == 0) {
if (!$argv) {
throw new PhutilArgumentUsageException(
pht('Specify a configuration key and a value to set it to.'));
pht('Specify the configuration key you want to set.'));
}
$is_stdin = $args->getArg('stdin');
@@ -45,7 +44,8 @@ final class PhabricatorConfigManagementSetWorkflow
if (count($argv) > 1) {
throw new PhutilArgumentUsageException(
pht(
'Too many arguments: expected only a key when using "--stdin".'));
'Too many arguments: expected only a configuration key when '.
'using "--stdin".'));
}
fprintf(STDERR, tsprintf("%s\n", pht('Reading value from stdin...')));
@@ -54,7 +54,8 @@ final class PhabricatorConfigManagementSetWorkflow
if (count($argv) == 1) {
throw new PhutilArgumentUsageException(
pht(
"Specify a value to set the key '%s' to.",
'Specify a value to set the configuration key "%s" to, or '.
'use "--stdin" to read a value from stdin.',
$key));
}
@@ -67,14 +68,13 @@ final class PhabricatorConfigManagementSetWorkflow
$value = $argv[1];
}
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
if (empty($options[$key])) {
throw new PhutilArgumentUsageException(
pht(
"No such configuration key '%s'! Use `%s` to list all keys.",
$key,
'config list'));
'Configuration key "%s" is unknown. Use "bin/config list" to list '.
'all known keys.',
$key));
}
$option = $options[$key];
@@ -99,7 +99,7 @@ final class PhabricatorConfigManagementSetWorkflow
switch ($type) {
default:
$message = pht(
'Config key "%s" is of type "%s". Specify it in JSON.',
'Configuration key "%s" is of type "%s". Specify it in JSON.',
$key,
$type);
break;
@@ -128,7 +128,6 @@ final class PhabricatorConfigManagementSetWorkflow
}
if ($use_database) {
$config_type = 'database';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$config_entry->setValue($value);
@@ -136,15 +135,28 @@ final class PhabricatorConfigManagementSetWorkflow
$config_entry->setIsDeleted(0);
$config_entry->save();
$write_message = pht(
'Wrote configuration key "%s" to database storage.',
$key);
} else {
$config_type = 'local';
id(new PhabricatorConfigLocalSource())
$config_source = id(new PhabricatorConfigLocalSource())
->setKeys(array($key => $value));
$local_path = $config_source->getReadablePath();
$write_message = pht(
'Wrote configuration key "%s" to local storage (in file "%s").',
$key,
$local_path);
}
$console->writeOut(
"%s\n",
pht("Set '%s' in %s configuration.", $key, $config_type));
echo tsprintf(
"<bg:green>** %s **</bg> %s\n",
pht('DONE'),
$write_message);
return 0;
}
}

View File

@@ -21,10 +21,6 @@ final class PhabricatorPHDConfigOptions
public function getOptions() {
return array(
$this->newOption('phd.pid-directory', 'string', '/var/tmp/phd/pid')
->setLocked(true)
->setDescription(
pht('Directory that phd should use to track running daemons.')),
$this->newOption('phd.log-directory', 'string', '/var/tmp/phd/log')
->setLocked(true)
->setDescription(

View File

@@ -48,11 +48,19 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject {
abstract public function buildSchemata();
protected function buildLiskObjectSchema(PhabricatorLiskDAO $object) {
$index_options = array();
$persistence = $object->getSchemaPersistence();
if ($persistence !== null) {
$index_options['persistence'] = $persistence;
}
$this->buildRawSchema(
$object->getApplicationName(),
$object->getTableName(),
$object->getSchemaColumns(),
$object->getSchemaKeys());
$object->getSchemaKeys(),
$index_options);
}
protected function buildFerretIndexSchema(PhabricatorFerretEngine $engine) {

View File

@@ -85,6 +85,7 @@ final class PhabricatorDaemonConsoleController
phutil_tag('em', array(), pht('Temporary Failures')),
count($failed),
null,
null,
);
}

View File

@@ -6,7 +6,10 @@ final class PhabricatorDaemonManagementRestartWorkflow
protected function didConstruct() {
$this
->setName('restart')
->setSynopsis(pht('Stop, then start the standard daemon loadout.'))
->setSynopsis(
pht(
'Stop daemon processes on this host, then start the standard '.
'daemon loadout.'))
->setArguments(
array(
array(
@@ -17,17 +20,15 @@ final class PhabricatorDaemonManagementRestartWorkflow
'seconds. Defaults to __15__ seconds.'),
'default' => 15,
),
array(
'name' => 'gently',
'help' => pht(
'Ignore running processes that look like daemons but do not '.
'have corresponding PID files.'),
),
array(
'name' => 'force',
'help' => pht(
'Also stop running processes that look like daemons but do '.
'not have corresponding PID files.'),
'Stop all daemon processes on this host, even if they belong '.
'to another Phabricator instance.'),
),
array(
'name' => 'gently',
'help' => pht('Deprecated. Has no effect.'),
),
$this->getAutoscaleReserveArgument(),
));
@@ -35,12 +36,11 @@ final class PhabricatorDaemonManagementRestartWorkflow
public function execute(PhutilArgumentParser $args) {
$err = $this->executeStopCommand(
array(),
array(
'graceful' => $args->getArg('graceful'),
'force' => $args->getArg('force'),
'gently' => $args->getArg('gently'),
));
if ($err) {
return $err;
}

View File

@@ -6,101 +6,52 @@ final class PhabricatorDaemonManagementStatusWorkflow
protected function didConstruct() {
$this
->setName('status')
->setSynopsis(pht('Show status of running daemons.'))
->setArguments(
array(
array(
'name' => 'local',
'help' => pht('Show only local daemons.'),
),
));
->setSynopsis(pht('Show daemon processes on this host.'));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$process_refs = $this->getOverseerProcessRefs();
if ($args->getArg('local')) {
$daemons = $this->loadRunningDaemons();
} else {
$daemons = $this->loadAllRunningDaemons();
}
if (!$process_refs) {
$instance = $this->getInstance();
if ($instance !== null) {
$this->logInfo(
pht('NO DAEMONS'),
pht(
'There are no running daemon processes for the current '.
'instance ("%s").',
$instance));
} else {
$this->logInfo(
pht('NO DAEMONS'),
pht('There are no running daemon processes.'));
}
if (!$daemons) {
$console->writeErr(
"%s\n",
pht('There are no running Phabricator daemons.'));
return 1;
}
$status = 0;
$table = id(new PhutilConsoleTable())
->addColumns(array(
'id' => array(
'title' => pht('Log'),
),
'daemonID' => array(
'title' => pht('Daemon'),
),
'host' => array(
'title' => pht('Host'),
),
'pid' => array(
'title' => pht('Overseer'),
),
'started' => array(
'title' => pht('Started'),
),
'daemon' => array(
'title' => pht('Class'),
),
'argv' => array(
'title' => pht('Arguments'),
),
));
foreach ($daemons as $daemon) {
if ($daemon instanceof PhabricatorDaemonLog) {
$table->addRow(array(
'id' => $daemon->getID(),
'daemonID' => $daemon->getDaemonID(),
'host' => $daemon->getHost(),
'pid' => $daemon->getPID(),
'started' => date('M j Y, g:i:s A', $daemon->getDateCreated()),
'daemon' => $daemon->getDaemon(),
'argv' => csprintf('%LR', $daemon->getExplicitArgv()),
->addColumns(
array(
'pid' => array(
'title' => pht('PID'),
),
'command' => array(
'title' => pht('Command'),
),
));
} else if ($daemon instanceof PhabricatorDaemonReference) {
$name = $daemon->getName();
if (!$daemon->isRunning()) {
$daemon->updateStatus(PhabricatorDaemonLog::STATUS_DEAD);
$status = 2;
$name = pht('<DEAD> %s', $name);
}
$daemon_log = $daemon->getDaemonLog();
$id = null;
$daemon_id = null;
if ($daemon_log) {
$id = $daemon_log->getID();
$daemon_id = $daemon_log->getDaemonID();
}
$table->addRow(array(
'id' => $id,
'daemonID' => $daemon_id,
'host' => 'localhost',
'pid' => $daemon->getPID(),
'started' => $daemon->getEpochStarted()
? date('M j Y, g:i:s A', $daemon->getEpochStarted())
: null,
'daemon' => $name,
'argv' => csprintf('%LR', $daemon->getArgv()),
foreach ($process_refs as $process_ref) {
$table->addRow(
array(
'pid' => $process_ref->getPID(),
'command' => $process_ref->getCommand(),
));
}
}
$table->draw();
return 0;
}
}

View File

@@ -6,11 +6,7 @@ final class PhabricatorDaemonManagementStopWorkflow
protected function didConstruct() {
$this
->setName('stop')
->setSynopsis(
pht(
'Stop all running daemons, or specific daemons identified by PIDs. '.
'Use **%s** to find PIDs.',
'phd status'))
->setSynopsis(pht('Stop daemon processes on this host.'))
->setArguments(
array(
array(
@@ -24,29 +20,21 @@ final class PhabricatorDaemonManagementStopWorkflow
array(
'name' => 'force',
'help' => pht(
'Also stop running processes that look like daemons but do '.
'not have corresponding PID files.'),
'Stop all daemon processes on this host, even if they belong '.
'to another Phabricator instance.'),
),
array(
'name' => 'gently',
'help' => pht(
'Ignore running processes that look like daemons but do not '.
'have corresponding PID files.'),
),
array(
'name' => 'pids',
'wildcard' => true,
'help' => pht('Deprecated. Has no effect.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
return $this->executeStopCommand(
$args->getArg('pids'),
array(
'graceful' => $args->getArg('graceful'),
'force' => $args->getArg('force'),
'gently' => $args->getArg('gently'),
));
}

View File

@@ -12,11 +12,6 @@ abstract class PhabricatorDaemonManagementWorkflow
->selectSymbolsWithoutLoading();
}
final protected function getPIDDirectory() {
$path = PhabricatorEnv::getEnvConfig('phd.pid-directory');
return $this->getControlDirectory($path);
}
final protected function getLogDirectory() {
$path = PhabricatorEnv::getEnvConfig('phd.log-directory');
return $this->getControlDirectory($path);
@@ -30,56 +25,16 @@ abstract class PhabricatorDaemonManagementWorkflow
pht(
"%s requires the directory '%s' to exist, but it does not exist ".
"and could not be created. Create this directory or update ".
"'%s' / '%s' in your configuration to point to an existing ".
"'%s' in your configuration to point to an existing ".
"directory.",
'phd',
$path,
'phd.pid-directory',
'phd.log-directory'));
}
}
return $path;
}
final protected function loadRunningDaemons() {
$daemons = array();
$pid_dir = $this->getPIDDirectory();
$pid_files = Filesystem::listDirectory($pid_dir);
foreach ($pid_files as $pid_file) {
$path = $pid_dir.'/'.$pid_file;
$daemons[] = PhabricatorDaemonReference::loadReferencesFromFile($path);
}
return array_mergev($daemons);
}
final protected function loadAllRunningDaemons() {
$local_daemons = $this->loadRunningDaemons();
$local_ids = array();
foreach ($local_daemons as $daemon) {
$daemon_log = $daemon->getDaemonLog();
if ($daemon_log) {
$local_ids[] = $daemon_log->getID();
}
}
$daemon_query = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE);
if ($local_ids) {
$daemon_query->withoutIDs($local_ids);
}
$remote_daemons = $daemon_query->execute();
return array_merge($local_daemons, $remote_daemons);
}
private function findDaemonClass($substring) {
$symbols = $this->loadAvailableDaemonClasses();
@@ -169,7 +124,7 @@ abstract class PhabricatorDaemonManagementWorkflow
$flags[] = '--verbose';
}
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
$instance = $this->getInstance();
if ($instance) {
$flags[] = '-l';
$flags[] = $instance;
@@ -185,14 +140,6 @@ abstract class PhabricatorDaemonManagementWorkflow
$config['log'] = $this->getLogDirectory().'/daemons.log';
}
$pid_dir = $this->getPIDDirectory();
// TODO: This should be a much better user experience.
Filesystem::assertExists($pid_dir);
Filesystem::assertIsDirectory($pid_dir);
Filesystem::assertWritable($pid_dir);
$config['piddir'] = $pid_dir;
$config['daemons'] = $daemons;
$command = csprintf('./phd-daemon %Ls', $flags);
@@ -324,28 +271,31 @@ abstract class PhabricatorDaemonManagementWorkflow
$console = PhutilConsole::getConsole();
if (!idx($options, 'force')) {
$running = $this->loadRunningDaemons();
$process_refs = $this->getOverseerProcessRefs();
if ($process_refs) {
$this->logWarn(
pht('RUNNING DAEMONS'),
pht('Daemons are already running:'));
// This may include daemons which were launched but which are no longer
// running; check that we actually have active daemons before failing.
foreach ($running as $daemon) {
if ($daemon->isRunning()) {
$message = pht(
"phd start: Unable to start daemons because daemons are already ".
"running.\n\n".
"You can view running daemons with '%s'.\n".
"You can stop running daemons with '%s'.\n".
"You can use '%s' to stop all daemons before starting ".
"new daemons.\n".
"You can force daemons to start anyway with %s.",
'phd status',
'phd stop',
'phd restart',
'--force');
$console->writeErr("%s\n", $message);
exit(1);
fprintf(STDERR, '%s', "\n");
foreach ($process_refs as $process_ref) {
fprintf(
STDERR,
'%s',
tsprintf(
" %s %s\n",
$process_ref->getPID(),
$process_ref->getCommand()));
}
fprintf(STDERR, '%s', "\n");
$this->logFail(
pht('RUNNING DAEMONS'),
pht(
'Use "phd stop" to stop daemons, "phd restart" to restart '.
'daemons, or "phd start --force" to ignore running processes.'));
exit(1);
}
}
@@ -386,148 +336,115 @@ abstract class PhabricatorDaemonManagementWorkflow
return 0;
}
final protected function executeStopCommand(
array $pids,
array $options) {
$console = PhutilConsole::getConsole();
final protected function executeStopCommand(array $options) {
$grace_period = idx($options, 'graceful', 15);
$force = idx($options, 'force');
$gently = idx($options, 'gently');
if ($gently && $force) {
throw new PhutilArgumentUsageException(
$query = id(new PhutilProcessQuery())
->withIsOverseer(true);
$instance = $this->getInstance();
if ($instance !== null && !$force) {
$query->withInstances(array($instance));
}
try {
$process_refs = $query->execute();
} catch (Exception $ex) {
// See T13321. If this fails for some reason, just continue for now so
// that daemon management still works. In the long run, we don't expect
// this to fail, but I don't want to break this workflow while we iron
// bugs out.
// See T12827. Particularly, this is likely to fail on Solaris.
phlog($ex);
$process_refs = array();
}
if (!$process_refs) {
if ($instance !== null && !$force) {
$this->logInfo(
pht('NO DAEMONS'),
pht(
'There are no running daemons for the current instance ("%s"). '.
'Use "--force" to stop daemons for all instances.',
$instance));
} else {
$this->logInfo(
pht('NO DAEMONS'),
pht('There are no running daemons.'));
}
return 0;
}
$process_refs = mpull($process_refs, null, 'getPID');
$stop_pids = array_keys($process_refs);
$live_pids = $this->sendStopSignals($stop_pids, $grace_period);
$stop_pids = array_fuse($stop_pids);
$live_pids = array_fuse($live_pids);
$dead_pids = array_diff_key($stop_pids, $live_pids);
foreach ($dead_pids as $dead_pid) {
$dead_ref = $process_refs[$dead_pid];
$this->logOkay(
pht('STOP'),
pht(
'You can not specify conflicting options %s and %s together.',
'--gently',
'--force'));
'Stopped PID %d ("%s")',
$dead_pid,
$dead_ref->getCommand()));
}
$daemons = $this->loadRunningDaemons();
if (!$daemons) {
$survivors = array();
if (!$pids && !$gently) {
$survivors = $this->processRogueDaemons(
$grace_period,
$warn = true,
$force);
}
if (!$survivors) {
$console->writeErr(
"%s\n",
pht('There are no running Phabricator daemons.'));
}
return 0;
foreach ($live_pids as $live_pid) {
$live_ref = $process_refs[$live_pid];
$this->logFail(
pht('SURVIVED'),
pht(
'Unable to stop PID %d ("%s").',
$live_pid,
$live_ref->getCommand()));
}
$stop_pids = $this->selectDaemonPIDs($daemons, $pids);
if (!$stop_pids) {
$console->writeErr("%s\n", pht('No daemons to kill.'));
return 0;
}
$survivors = $this->sendStopSignals($stop_pids, $grace_period);
// Try to clean up PID files for daemons we killed.
$remove = array();
foreach ($daemons as $daemon) {
$pid = $daemon->getPID();
if (empty($stop_pids[$pid])) {
// We did not try to stop this overseer.
continue;
}
if (isset($survivors[$pid])) {
// We weren't able to stop this overseer.
continue;
}
if (!$daemon->getPIDFile()) {
// We don't know where the PID file is.
continue;
}
$remove[] = $daemon->getPIDFile();
}
foreach (array_unique($remove) as $remove_file) {
Filesystem::remove($remove_file);
}
if (!$gently) {
$this->processRogueDaemons($grace_period, !$pids, $force);
if ($live_pids) {
$this->logWarn(
pht('SURVIVORS'),
pht(
'Unable to stop all daemon processes. You may need to run this '.
'command as root with "sudo".'));
}
return 0;
}
final protected function executeReloadCommand(array $pids) {
$console = PhutilConsole::getConsole();
$process_refs = $this->getOverseerProcessRefs();
if (!$process_refs) {
$this->logInfo(
pht('NO DAEMONS'),
pht('There are no running daemon processes to reload.'));
$daemons = $this->loadRunningDaemons();
if (!$daemons) {
$console->writeErr(
"%s\n",
pht('There are no running daemons to reload.'));
return 0;
}
$reload_pids = $this->selectDaemonPIDs($daemons, $pids);
if (!$reload_pids) {
$console->writeErr(
"%s\n",
pht('No daemons to reload.'));
return 0;
}
foreach ($process_refs as $process_ref) {
$pid = $process_ref->getPID();
foreach ($reload_pids as $pid) {
$console->writeOut(
"%s\n",
$this->logInfo(
pht('RELOAD'),
pht('Reloading process %d...', $pid));
posix_kill($pid, SIGHUP);
}
return 0;
}
private function processRogueDaemons($grace_period, $warn, $force_stop) {
$console = PhutilConsole::getConsole();
$rogue_daemons = PhutilDaemonOverseer::findRunningDaemons();
if ($rogue_daemons) {
if ($force_stop) {
$rogue_pids = ipull($rogue_daemons, 'pid');
$survivors = $this->sendStopSignals($rogue_pids, $grace_period);
if ($survivors) {
$console->writeErr(
"%s\n",
pht(
'Unable to stop processes running without PID files. '.
'Try running this command again with sudo.'));
}
} else if ($warn) {
$console->writeErr("%s\n", $this->getForceStopHint($rogue_daemons));
}
}
return $rogue_daemons;
}
private function getForceStopHint($rogue_daemons) {
$debug_output = '';
foreach ($rogue_daemons as $rogue) {
$debug_output .= $rogue['pid'].' '.$rogue['command']."\n";
}
return pht(
"There are processes running that look like Phabricator daemons but ".
"have no corresponding PID files:\n\n%s\n\n".
"Stop these processes by re-running this command with the %s parameter.",
$debug_output,
'--force');
}
private function sendStopSignals($pids, $grace_period) {
// If we're doing a graceful shutdown, try SIGINT first.
if ($grace_period) {
@@ -674,4 +591,21 @@ abstract class PhabricatorDaemonManagementWorkflow
return $select_pids;
}
protected function getOverseerProcessRefs() {
$query = id(new PhutilProcessQuery())
->withIsOverseer(true);
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if ($instance !== null) {
$query->withInstances(array($instance));
}
return $query->execute();
}
protected function getInstance() {
return PhabricatorEnv::getEnvConfig('cluster.instance');
}
}

View File

@@ -119,8 +119,11 @@ final class DifferentialCreateDiffConduitAPIMethod
break;
}
$source_path = $request->getValue('sourcePath');
$source_path = $this->normalizeSourcePath($source_path);
$diff_data_dict = array(
'sourcePath' => $request->getValue('sourcePath'),
'sourcePath' => $source_path,
'sourceMachine' => $request->getValue('sourceMachine'),
'branch' => $request->getValue('branch'),
'creationMethod' => $request->getValue('creationMethod'),
@@ -158,4 +161,18 @@ final class DifferentialCreateDiffConduitAPIMethod
);
}
private function normalizeSourcePath($source_path) {
// See T13385. This property is probably headed for deletion. Until we get
// there, stop errors arising from running "arc diff" in a working copy
// with too many characters.
$max_size = id(new DifferentialDiff())
->getColumnMaximumByteLength('sourcePath');
return id(new PhutilUTF8StringTruncator())
->setMaximumBytes($max_size)
->setTerminator('')
->truncateString($source_path);
}
}

View File

@@ -51,6 +51,12 @@ final class DifferentialRevisionSearchEngine
$map['createdEnd']);
}
if ($map['modifiedStart'] || $map['modifiedEnd']) {
$query->withUpdatedEpochBetween(
$map['modifiedStart'],
$map['modifiedEnd']);
}
return $query;
}
@@ -100,6 +106,18 @@ final class DifferentialRevisionSearchEngine
->setKey('createdEnd')
->setDescription(
pht('Find revisions created at or before a particular time.')),
id(new PhabricatorSearchDateField())
->setLabel(pht('Modified After'))
->setKey('modifiedStart')
->setIsHidden(true)
->setDescription(
pht('Find revisions modified at or after a particular time.')),
id(new PhabricatorSearchDateField())
->setLabel(pht('Modified Before'))
->setKey('modifiedEnd')
->setIsHidden(true)
->setDescription(
pht('Find revisions modified at or before a particular time.')),
);
}

View File

@@ -112,11 +112,6 @@ final class DifferentialRevision extends DifferentialDAO
'repositoryPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'authorPHID' => array(
'columns' => array('authorPHID', 'status'),
),
@@ -131,6 +126,9 @@ final class DifferentialRevision extends DifferentialDAO
'key_status' => array(
'columns' => array('status', 'phid'),
),
'key_modified' => array(
'columns' => array('dateModified'),
),
),
) + parent::getConfiguration();
}

View File

@@ -7,17 +7,6 @@ abstract class DiffusionQueryConduitAPIMethod
return true;
}
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function getMethodStatusDescription() {
return pht(
'See T2784 - migrating Diffusion working copy calls to conduit methods. '.
'Until that task is completed (and possibly after) these methods are '.
'unstable.');
}
private $diffusionRequest;
private $repository;

View File

@@ -145,13 +145,26 @@ final class DiffusionRepositoryController extends DiffusionController {
->setRight(array($this->branchButton, $actions_button, $clone_button))
->addClass('diffusion-action-bar');
$status_view = null;
if ($repository->isReadOnly()) {
$status_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(
array(
phutil_escape_html_newlines(
$repository->getReadOnlyMessageForDisplay()),
));
}
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$bar,
$description,
$content,
));
->setFooter(
array(
$status_view,
$bar,
$description,
$content,
));
if ($page_has_content) {
$view->setTabs($tabs);
@@ -327,6 +340,8 @@ final class DiffusionRepositoryController extends DiffusionController {
if (!$repository->isTracked()) {
$header->setStatus('fa-ban', 'dark', pht('Inactive'));
} else if ($repository->isReadOnly()) {
$header->setStatus('fa-wrench', 'indigo', pht('Under Maintenance'));
} else if ($repository->isImporting()) {
$ratio = $repository->loadImportProgress();
$percentage = sprintf('%.2f%%', 100 * $ratio);

View File

@@ -17,32 +17,31 @@ final class DiffusionRepositoryEditDeleteController
->setRepository($repository)
->getPanelURI();
$dialog = new AphrontDialogView();
$text_1 = pht(
'If you really want to delete the repository, run this command from '.
'the command line:');
$command = csprintf(
'phabricator/ $ ./bin/remove destroy %R',
$repository->getMonogram());
$text_2 = pht(
'Repositories touch many objects and as such deletes are '.
'prohibitively expensive to run from the web UI.');
$body = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
array(
phutil_tag('p', array(), $text_1),
phutil_tag('p', array(),
phutil_tag('tt', array(), $command)),
phutil_tag('p', array(), $text_2),
));
$doc_uri = PhabricatorEnv::getDoclink(
'Permanently Destroying Data');
return $this->newDialog()
->setTitle(pht('Really want to delete the repository?'))
->appendChild($body)
->addCancelButton($panel_uri, pht('Okay'));
->setTitle(pht('Delete Repository'))
->appendParagraph(
pht(
'To permanently destroy this repository, run this command from '.
'the command line:'))
->appendCommand(
csprintf(
'phabricator/ $ ./bin/remove destroy %R',
$repository->getMonogram()))
->appendParagraph(
pht(
'Repositories can not be permanently destroyed from the web '.
'interface. See %s in the documentation for more information.',
phutil_tag(
'a',
array(
'href' => $doc_uri,
'target' => '_blank',
),
pht('Permanently Destroying Data'))))
->addCancelButton($panel_uri, pht('Close'));
}
}

View File

@@ -302,6 +302,12 @@ final class DiffusionServeController extends DiffusionController {
}
if ($is_push) {
if ($repository->isReadOnly()) {
return new PhabricatorVCSResponse(
503,
$repository->getReadOnlyMessageForDisplay());
}
$can_write =
$repository->canServeProtocol($proto_https, true) ||
$repository->canServeProtocol($proto_http, true);

View File

@@ -48,7 +48,8 @@ final class DiffusionCommitEditEngine
return id(new DiffusionCommitQuery())
->needCommitData(true)
->needAuditRequests(true)
->needAuditAuthority(array($viewer));
->needAuditAuthority(array($viewer))
->needIdentities(true);
}
protected function getEditorURI() {

View File

@@ -155,8 +155,6 @@ final class DiffusionRepositoryBasicsManagementPanel
->setName(pht('Delete Repository'))
->setHref($delete_uri)
->setIcon('fa-times')
->setColor(PhabricatorActionView::RED)
->setDisabled(true)
->setWorkflow(true));
return $this->newCurtainView()

View File

@@ -255,6 +255,10 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
'user account.'));
}
if ($repository->isReadOnly()) {
throw new Exception($repository->getReadOnlyMessageForDisplay());
}
$protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH;
if ($repository->canServeProtocol($protocol, true)) {
$can_push = PhabricatorPolicyFilter::hasCapability(

View File

@@ -47,7 +47,7 @@ final class DiffusionPatternSearchView extends DiffusionView {
$offset = $match[1];
if ($cursor != $offset) {
$output[] = array(
'text' => substr($string, $cursor, $offset),
'text' => substr($string, $cursor, ($offset - $cursor)),
'highlight' => false,
);
}

View File

@@ -0,0 +1,18 @@
<?php
final class DrydockResourceSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'drydock.resource.search';
}
public function newSearchEngine() {
return new DrydockResourceSearchEngine();
}
public function getMethodSummary() {
return pht('Retrieve information about Drydock resources.');
}
}

View File

@@ -44,6 +44,10 @@ final class DrydockLeaseSearchEngine
$query->withOwnerPHIDs($map['ownerPHIDs']);
}
if ($map['resourcePHIDs']) {
$query->withResourcePHIDs($map['resourcePHIDs']);
}
return $query;
}
@@ -58,6 +62,11 @@ final class DrydockLeaseSearchEngine
->setKey('ownerPHIDs')
->setAliases(array('owner', 'owners', 'ownerPHID'))
->setDescription(pht('Search leases by owner.')),
id(new PhabricatorPHIDsSearchField())
->setLabel(pht('Resources'))
->setKey('resourcePHIDs')
->setAliases(array('resorucePHID', 'resource', 'resources'))
->setDescription(pht('Search leases by resource.')),
);
}

View File

@@ -100,46 +100,50 @@ final class DrydockResourceQuery extends DrydockQuery {
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
'resource.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
'resource.phid IN (%Ls)',
$this->phids);
}
if ($this->types !== null) {
$where[] = qsprintf(
$conn,
'type IN (%Ls)',
'resource.type IN (%Ls)',
$this->types);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
'resource.status IN (%Ls)',
$this->statuses);
}
if ($this->blueprintPHIDs !== null) {
$where[] = qsprintf(
$conn,
'blueprintPHID IN (%Ls)',
'resource.blueprintPHID IN (%Ls)',
$this->blueprintPHIDs);
}
if ($this->datasourceQuery !== null) {
$where[] = qsprintf(
$conn,
'name LIKE %>',
'resource.name LIKE %>',
$this->datasourceQuery);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'resource';
}
}

View File

@@ -40,6 +40,10 @@ final class DrydockResourceSearchEngine
$query->withStatuses($map['statuses']);
}
if ($map['blueprintPHIDs']) {
$query->withBlueprintPHIDs($map['blueprintPHIDs']);
}
return $query;
}
@@ -49,6 +53,12 @@ final class DrydockResourceSearchEngine
->setLabel(pht('Statuses'))
->setKey('statuses')
->setOptions(DrydockResourceStatus::getStatusMap()),
id(new PhabricatorPHIDsSearchField())
->setLabel(pht('Blueprints'))
->setKey('blueprintPHIDs')
->setAliases(array('blueprintPHID', 'blueprints', 'blueprint'))
->setDescription(
pht('Search for resources generated by particular blueprints.')),
);
}

View File

@@ -1,7 +1,9 @@
<?php
final class DrydockResource extends DrydockDAO
implements PhabricatorPolicyInterface {
implements
PhabricatorPolicyInterface,
PhabricatorConduitResultInterface {
protected $id;
protected $phid;
@@ -340,4 +342,38 @@ final class DrydockResource extends DrydockDAO
public function describeAutomaticCapability($capability) {
return pht('Resources inherit the policies of their blueprints.');
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('blueprintPHID')
->setType('phid')
->setDescription(pht('The blueprint which generated this resource.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('status')
->setType('map<string, wild>')
->setDescription(pht('Information about resource status.')),
);
}
public function getFieldValuesForConduit() {
$status = $this->getStatus();
return array(
'blueprintPHID' => $this->getBlueprintPHID(),
'status' => array(
'value' => $status,
'name' => DrydockResourceStatus::getNameForStatus($status),
),
);
}
public function getConduitSearchAttachments() {
return array();
}
}

View File

@@ -5,10 +5,6 @@ final class PhabricatorFilesOutboundRequestAction
const TYPECONST = 'files.outbound';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() {
return 60 / phutil_units('1 hour in seconds');
}

View File

@@ -4,41 +4,25 @@ final class PhabricatorFilesManagementCompactWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$arguments = $this->newIteratorArguments();
$arguments[] = array(
'name' => 'dry-run',
'help' => pht('Show what would be compacted.'),
);
$this
->setName('compact')
->setSynopsis(
pht(
'Merge identical files to share the same storage. In some cases, '.
'this can repair files with missing data.'))
->setArguments(
array(
array(
'name' => 'dry-run',
'help' => pht('Show what would be compacted.'),
),
array(
'name' => 'all',
'help' => pht('Compact all files.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
));
->setArguments($arguments);
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$iterator = $this->buildIterator($args);
if (!$iterator) {
throw new PhutilArgumentUsageException(
pht(
'Either specify a list of files to compact, or use `%s` '.
'to compact all files.',
'--all'));
}
$is_dry_run = $args->getArg('dry-run');
foreach ($iterator as $file) {

View File

@@ -4,36 +4,22 @@ final class PhabricatorFilesManagementCycleWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$arguments = $this->newIteratorArguments();
$arguments[] = array(
'name' => 'key',
'param' => 'keyname',
'help' => pht('Select a specific storage key to cycle to.'),
);
$this
->setName('cycle')
->setSynopsis(
pht('Cycle master key for encrypted files.'))
->setArguments(
array(
array(
'name' => 'key',
'param' => 'keyname',
'help' => pht('Select a specific storage key to cycle to.'),
),
array(
'name' => 'all',
'help' => pht('Change encoding for all files.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
));
->setArguments($arguments);
}
public function execute(PhutilArgumentParser $args) {
$iterator = $this->buildIterator($args);
if (!$iterator) {
throw new PhutilArgumentUsageException(
pht(
'Either specify a list of files to cycle, or use --all to cycle '.
'all files.'));
}
$format_map = PhabricatorFileStorageFormat::getAllFormats();
$engines = PhabricatorFileStorageEngine::loadAllEngines();

View File

@@ -4,47 +4,36 @@ final class PhabricatorFilesManagementEncodeWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$arguments = $this->newIteratorArguments();
$arguments[] = array(
'name' => 'as',
'param' => 'format',
'help' => pht('Select the storage format to use.'),
);
$arguments[] = array(
'name' => 'key',
'param' => 'keyname',
'help' => pht('Select a specific storage key.'),
);
$arguments[] = array(
'name' => 'force',
'help' => pht(
'Re-encode files which are already stored in the target '.
'encoding.'),
);
$this
->setName('encode')
->setSynopsis(
pht('Change the storage encoding of files.'))
->setArguments(
array(
array(
'name' => 'as',
'param' => 'format',
'help' => pht('Select the storage format to use.'),
),
array(
'name' => 'key',
'param' => 'keyname',
'help' => pht('Select a specific storage key.'),
),
array(
'name' => 'all',
'help' => pht('Change encoding for all files.'),
),
array(
'name' => 'force',
'help' => pht(
'Re-encode files which are already stored in the target '.
'encoding.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
));
->setArguments($arguments);
}
public function execute(PhutilArgumentParser $args) {
$iterator = $this->buildIterator($args);
if (!$iterator) {
throw new PhutilArgumentUsageException(
pht(
'Either specify a list of files to encode, or use --all to '.
'encode all files.'));
}
$force = (bool)$args->getArg('force');

View File

@@ -4,52 +4,50 @@ final class PhabricatorFilesManagementIntegrityWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$arguments = $this->newIteratorArguments();
$arguments[] = array(
'name' => 'strip',
'help' => pht(
'DANGEROUS. Strip integrity hashes from files. This makes '.
'files vulnerable to corruption or tampering.'),
);
$arguments[] = array(
'name' => 'corrupt',
'help' => pht(
'Corrupt integrity hashes for given files. This is intended '.
'for debugging.'),
);
$arguments[] = array(
'name' => 'compute',
'help' => pht(
'Compute and update integrity hashes for files which do not '.
'yet have them.'),
);
$arguments[] = array(
'name' => 'overwrite',
'help' => pht(
'DANGEROUS. Recompute and update integrity hashes, overwriting '.
'invalid hashes. This may mark corrupt or dangerous files as '.
'valid.'),
);
$arguments[] = array(
'name' => 'force',
'short' => 'f',
'help' => pht(
'Execute dangerous operations without prompting for '.
'confirmation.'),
);
$this
->setName('integrity')
->setSynopsis(pht('Verify or recalculate file integrity hashes.'))
->setArguments(
array(
array(
'name' => 'all',
'help' => pht('Affect all files.'),
),
array(
'name' => 'strip',
'help' => pht(
'DANGEROUS. Strip integrity hashes from files. This makes '.
'files vulnerable to corruption or tampering.'),
),
array(
'name' => 'corrupt',
'help' => pht(
'Corrupt integrity hashes for given files. This is intended '.
'for debugging.'),
),
array(
'name' => 'compute',
'help' => pht(
'Compute and update integrity hashes for files which do not '.
'yet have them.'),
),
array(
'name' => 'overwrite',
'help' => pht(
'DANGEROUS. Recompute and update integrity hashes, overwriting '.
'invalid hashes. This may mark corrupt or dangerous files as '.
'valid.'),
),
array(
'name' => 'force',
'short' => 'f',
'help' => pht(
'Execute dangerous operations without prompting for '.
'confirmation.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
));
->setArguments($arguments);
}
public function execute(PhutilArgumentParser $args) {
@@ -120,12 +118,6 @@ final class PhabricatorFilesManagementIntegrityWorkflow
}
$iterator = $this->buildIterator($args);
if (!$iterator) {
throw new PhutilArgumentUsageException(
pht(
'Either specify a list of files to affect, or use "--all" to '.
'affect all files.'));
}
$failure_count = 0;
$total_count = 0;

View File

@@ -4,61 +4,54 @@ final class PhabricatorFilesManagementMigrateWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$arguments = $this->newIteratorArguments();
$arguments[] = array(
'name' => 'engine',
'param' => 'storage-engine',
'help' => pht('Migrate to the named storage engine.'),
);
$arguments[] = array(
'name' => 'dry-run',
'help' => pht('Show what would be migrated.'),
);
$arguments[] = array(
'name' => 'min-size',
'param' => 'bytes',
'help' => pht(
'Do not migrate data for files which are smaller than a given '.
'filesize.'),
);
$arguments[] = array(
'name' => 'max-size',
'param' => 'bytes',
'help' => pht(
'Do not migrate data for files which are larger than a given '.
'filesize.'),
);
$arguments[] = array(
'name' => 'copy',
'help' => pht(
'Copy file data instead of moving it: after migrating, do not '.
'remove the old data even if it is no longer referenced.'),
);
$arguments[] = array(
'name' => 'local-disk-source',
'param' => 'path',
'help' => pht(
'When migrating from a local disk source, use the specified '.
'path as the root directory.'),
);
$this
->setName('migrate')
->setSynopsis(pht('Migrate files between storage engines.'))
->setArguments(
array(
array(
'name' => 'engine',
'param' => 'storage_engine',
'help' => pht('Migrate to the named storage engine.'),
),
array(
'name' => 'dry-run',
'help' => pht('Show what would be migrated.'),
),
array(
'name' => 'min-size',
'param' => 'bytes',
'help' => pht(
'Do not migrate data for files which are smaller than a given '.
'filesize.'),
),
array(
'name' => 'max-size',
'param' => 'bytes',
'help' => pht(
'Do not migrate data for files which are larger than a given '.
'filesize.'),
),
array(
'name' => 'all',
'help' => pht('Migrate all files.'),
),
array(
'name' => 'copy',
'help' => pht(
'Copy file data instead of moving it: after migrating, do not '.
'remove the old data even if it is no longer referenced.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
array(
'name' => 'from-engine',
'param' => 'engine',
'help' => pht('Migrate files from the named storage engine.'),
),
array(
'name' => 'local-disk-source',
'param' => 'path',
'help' => pht(
'When migrating from a local disk source, use the specified '.
'path as the root directory.'),
),
));
->setArguments($arguments);
}
public function execute(PhutilArgumentParser $args) {
@@ -97,14 +90,6 @@ final class PhabricatorFilesManagementMigrateWorkflow
$target_engine = PhabricatorFile::buildEngine($target_key);
$iterator = $this->buildIterator($args);
if (!$iterator) {
throw new PhutilArgumentUsageException(
pht(
'Either specify a list of files to migrate, or use `%s` '.
'to migrate all files.',
'--all'));
}
$is_dry_run = $args->getArg('dry-run');
$min_size = (int)$args->getArg('min-size');

View File

@@ -4,45 +4,33 @@ final class PhabricatorFilesManagementRebuildWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$arguments = $this->newIteratorArguments();
$arguments[] = array(
'name' => 'dry-run',
'help' => pht('Show what would be updated.'),
);
$arguments[] = array(
'name' => 'rebuild-mime',
'help' => pht('Rebuild MIME information.'),
);
$arguments[] = array(
'name' => 'rebuild-dimensions',
'help' => pht('Rebuild image dimension information.'),
);
$this
->setName('rebuild')
->setSynopsis(pht('Rebuild metadata of old files.'))
->setArguments(
array(
array(
'name' => 'all',
'help' => pht('Update all files.'),
),
array(
'name' => 'dry-run',
'help' => pht('Show what would be updated.'),
),
array(
'name' => 'rebuild-mime',
'help' => pht('Rebuild MIME information.'),
),
array(
'name' => 'rebuild-dimensions',
'help' => pht('Rebuild image dimension information.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
));
->setArguments($arguments);
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$iterator = $this->buildIterator($args);
if (!$iterator) {
throw new PhutilArgumentUsageException(
pht(
'Either specify a list of files to update, or use `%s` '.
'to update all files.',
'--all'));
}
$update = array(
'mime' => $args->getArg('rebuild-mime'),

View File

@@ -3,11 +3,30 @@
abstract class PhabricatorFilesManagementWorkflow
extends PhabricatorManagementWorkflow {
protected function newIteratorArguments() {
return array(
array(
'name' => 'all',
'help' => pht('Operate on all files.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
array(
'name' => 'from-engine',
'param' => 'storage-engine',
'help' => pht('Operate on files stored in a specified engine.'),
),
);
}
protected function buildIterator(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$names = $args->getArg('names');
$is_all = $args->getArg('all');
$names = $args->getArg('names');
$from_engine = $args->getArg('from-engine');
$any_constraint = ($from_engine || $names);
@@ -15,15 +34,16 @@ abstract class PhabricatorFilesManagementWorkflow
if (!$is_all && !$any_constraint) {
throw new PhutilArgumentUsageException(
pht(
'Use "--all" to migrate all files, or choose files to migrate '.
'with "--names" or "--from-engine".'));
'Specify which files to operate on, or use "--all" to operate on '.
'all files.'));
}
if ($is_all && $any_constraint) {
throw new PhutilArgumentUsageException(
pht(
'You can not migrate all files with "--all" and also migrate only '.
'a subset of files with "--from-engine" or "--names".'));
'You can not operate on all files with "--all" and also operate '.
'on a subset of files by naming them explicitly or using '.
'constraint flags like "--from-engine".'));
}
// If we're migrating specific named files, convert the names into IDs

View File

@@ -6,6 +6,7 @@ final class PhabricatorFlagQuery
const GROUP_COLOR = 'color';
const GROUP_NONE = 'none';
private $ids;
private $ownerPHIDs;
private $types;
private $objectPHIDs;
@@ -15,6 +16,11 @@ final class PhabricatorFlagQuery
private $needHandles;
private $needObjects;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withOwnerPHIDs(array $owner_phids) {
$this->ownerPHIDs = $owner_phids;
return $this;
@@ -126,6 +132,13 @@ final class PhabricatorFlagQuery
protected function buildWhereClause(AphrontDatabaseConnection $conn) {
$where = array();
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'flag.id IN (%Ld)',
$this->ids);
}
if ($this->ownerPHIDs) {
$where[] = qsprintf(
$conn,

View File

@@ -24,26 +24,27 @@ final class HarbormasterBuildableActionController
$issuable = array();
foreach ($buildable->getBuilds() as $build) {
$builds = $buildable->getBuilds();
foreach ($builds as $key => $build) {
switch ($action) {
case HarbormasterBuildCommand::COMMAND_RESTART:
if ($build->canRestartBuild()) {
$issuable[] = $build;
$issuable[$key] = $build;
}
break;
case HarbormasterBuildCommand::COMMAND_PAUSE:
if ($build->canPauseBuild()) {
$issuable[] = $build;
$issuable[$key] = $build;
}
break;
case HarbormasterBuildCommand::COMMAND_RESUME:
if ($build->canResumeBuild()) {
$issuable[] = $build;
$issuable[$key] = $build;
}
break;
case HarbormasterBuildCommand::COMMAND_ABORT:
if ($build->canAbortBuild()) {
$issuable[] = $build;
$issuable[$key] = $build;
}
break;
default:
@@ -59,6 +60,14 @@ final class HarbormasterBuildableActionController
}
}
$building = false;
foreach ($issuable as $key => $build) {
if ($build->isBuilding()) {
$building = true;
break;
}
}
$return_uri = '/'.$buildable->getMonogram();
if ($request->isDialogFormPost() && $issuable) {
$editor = id(new HarbormasterBuildableTransactionEditor())
@@ -89,34 +98,137 @@ final class HarbormasterBuildableActionController
return id(new AphrontRedirectResponse())->setURI($return_uri);
}
$width = AphrontDialogView::WIDTH_DEFAULT;
switch ($action) {
case HarbormasterBuildCommand::COMMAND_RESTART:
// See T13348. The "Restart Builds" action may restart only a subset
// of builds, so show the user a preview of which builds will actually
// restart.
$body = array();
if ($issuable) {
$title = pht('Really restart builds?');
if ($restricted) {
$body = pht(
'You only have permission to restart some builds. Progress '.
'on builds you have permission to restart will be discarded '.
'and they will restart. Side effects of these builds will '.
'occur again. Really restart all builds?');
} else {
$body = pht(
'Progress on all builds will be discarded, and all builds will '.
'restart. Side effects of the builds will occur again. Really '.
'restart all builds?');
}
$title = pht('Restart Builds');
$submit = pht('Restart Builds');
} else {
$title = pht('Unable to Restart Builds');
}
if ($builds) {
$width = AphrontDialogView::WIDTH_FORM;
$body[] = pht('Builds for this buildable:');
$rows = array();
foreach ($builds as $key => $build) {
if (isset($issuable[$key])) {
$icon = id(new PHUIIconView())
->setIcon('fa-repeat green');
$build_note = pht('Will Restart');
} else {
$icon = null;
try {
$build->assertCanRestartBuild();
} catch (HarbormasterRestartException $ex) {
$icon = id(new PHUIIconView())
->setIcon('fa-times red');
$build_note = pht(
'%s: %s',
phutil_tag('strong', array(), pht('Not Restartable')),
$ex->getTitle());
}
if (!$icon) {
try {
$build->assertCanIssueCommand($viewer, $action);
} catch (PhabricatorPolicyException $ex) {
$icon = id(new PHUIIconView())
->setIcon('fa-lock red');
$build_note = pht(
'%s: %s',
phutil_tag('strong', array(), pht('Not Restartable')),
pht('You do not have permission to restart this build.'));
}
}
if (!$icon) {
$icon = id(new PHUIIconView())
->setIcon('fa-times red');
$build_note = pht('Will Not Restart');
}
}
$build_name = phutil_tag(
'a',
array(
'href' => $build->getURI(),
'target' => '_blank',
),
pht('%s %s', $build->getObjectName(), $build->getName()));
$rows[] = array(
$icon,
$build_name,
$build_note,
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Build'),
pht('Action'),
))
->setColumnClasses(
array(
null,
'pri',
'wide',
));
$table = phutil_tag(
'div',
array(
'class' => 'mlt mlb',
),
$table);
$body[] = $table;
}
if ($issuable) {
$warnings = array();
if ($restricted) {
$body = pht('You do not have permission to restart any builds.');
$warnings[] = pht(
'You only have permission to restart some builds.');
}
if ($building) {
$warnings[] = pht(
'Progress on running builds will be discarded.');
}
$warnings[] = pht(
'When a build is restarted, side effects associated with '.
'the build may occur again.');
$body[] = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors($warnings);
$body[] = pht('Really restart builds?');
} else {
if ($restricted) {
$body[] = pht('You do not have permission to restart any builds.');
} else {
$body = pht('No builds can be restarted.');
$body[] = pht('No builds can be restarted.');
}
}
break;
case HarbormasterBuildCommand::COMMAND_PAUSE:
if ($issuable) {
@@ -193,6 +305,7 @@ final class HarbormasterBuildableActionController
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setWidth($width)
->setTitle($title)
->appendChild($body)
->addCancelButton($return_uri);

View File

@@ -128,7 +128,7 @@ final class HarbormasterBuildableViewController
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-repeat')
->setName(pht('Restart All Builds'))
->setName(pht('Restart Builds'))
->setHref($this->getApplicationURI($restart_uri))
->setWorkflow(true)
->setDisabled(!$can_restart || !$can_edit));
@@ -136,7 +136,7 @@ final class HarbormasterBuildableViewController
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-pause')
->setName(pht('Pause All Builds'))
->setName(pht('Pause Builds'))
->setHref($this->getApplicationURI($pause_uri))
->setWorkflow(true)
->setDisabled(!$can_pause || !$can_edit));
@@ -144,7 +144,7 @@ final class HarbormasterBuildableViewController
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-play')
->setName(pht('Resume All Builds'))
->setName(pht('Resume Builds'))
->setHref($this->getApplicationURI($resume_uri))
->setWorkflow(true)
->setDisabled(!$can_resume || !$can_edit));
@@ -152,7 +152,7 @@ final class HarbormasterBuildableViewController
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-exclamation-triangle')
->setName(pht('Abort All Builds'))
->setName(pht('Abort Builds'))
->setHref($this->getApplicationURI($abort_uri))
->setWorkflow(true)
->setDisabled(!$can_abort || !$can_edit));

View File

@@ -25,8 +25,7 @@ final class HarbormasterBuildGraph extends AbstractDirectedGraph {
$graph = id(new HarbormasterBuildGraph($steps_by_phid))
->addNodes($step_phids);
$raw_results =
$graph->getBestEffortTopographicallySortedNodes();
$raw_results = $graph->getNodesInRoughTopologicalOrder();
$results = array();
foreach ($raw_results as $node) {

View File

@@ -40,17 +40,10 @@ final class HeraldRuleEditTransaction
public function newChangeDetailView() {
$viewer = $this->getViewer();
$old = $this->getOldValue();
$new = $this->getNewValue();
$json = new PhutilJSON();
$old_json = $json->encodeFormatted($old);
$new_json = $json->encodeFormatted($new);
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
return id(new PhabricatorApplicationTransactionJSONDiffDetailView())
->setViewer($viewer)
->setOldText($old_json)
->setNewText($new_json);
->setOld($this->getOldValue())
->setNew($this->getNewValue());
}
}

View File

@@ -451,15 +451,20 @@ You can choose the default priority for newly created tasks with
EOTEXT
));
$fields_description = $this->deformat(pht(<<<EOTEXT
List of custom fields for Maniphest tasks.
For details on adding custom fields to Maniphest, see [[ %s | %s ]] in the
documentation.
EOTEXT
,
PhabricatorEnv::getDoclink('Configuring Custom Fields'),
pht('Configuring Custom Fields')));
return array(
$this->newOption('maniphest.custom-field-definitions', 'wild', array())
->setSummary(pht('Custom Maniphest fields.'))
->setDescription(
pht(
'Array of custom fields for Maniphest tasks. For details on '.
'adding custom fields to Maniphest, see "Configuring Custom '.
'Fields" in the documentation.'))
->setDescription($fields_description)
->addExample($fields_json, pht('Valid setting')),
$this->newOption('maniphest.fields', $custom_field_type, $default_fields)
->setCustomData(id(new ManiphestTask())->getCustomFieldBaseClass())

View File

@@ -264,6 +264,7 @@ EODOCS
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
$commit_type = ManiphestTaskHasCommitEdgeType::EDGECONST;
$src_phid = $object->getPHID();
if ($src_phid) {
@@ -273,6 +274,7 @@ EODOCS
array(
$parent_type,
$subtask_type,
$commit_type,
));
$edge_query->execute();
@@ -283,9 +285,14 @@ EODOCS
$subtask_phids = $edge_query->getDestinationPHIDs(
array($src_phid),
array($subtask_type));
$commit_phids = $edge_query->getDestinationPHIDs(
array($src_phid),
array($commit_type));
} else {
$parent_phids = array();
$subtask_phids = array();
$commit_phids = array();
}
$fields[] = id(new PhabricatorHandlesEditField())
@@ -310,7 +317,19 @@ EODOCS
->setIsFormField(false)
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $subtask_type)
->setValue($parent_phids);
->setValue($subtask_phids);
$fields[] = id(new PhabricatorHandlesEditField())
->setKey('commits')
->setLabel(pht('Commits'))
->setDescription(pht('Related commits.'))
->setConduitDescription(pht('Change the related commits for this task.'))
->setConduitTypeDescription(pht('List of related commit PHIDs.'))
->setUseEdgeTransactions(true)
->setIsFormField(false)
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $commit_type)
->setValue($commit_phids);
return $fields;
}
@@ -437,7 +456,7 @@ EODOCS
$engine = id(new PhabricatorBoardResponseEngine())
->setViewer($viewer)
->setBoardPHID($board_phid)
->setObjectPHID($object_phid)
->setUpdatePHIDs(array($object_phid))
->setVisiblePHIDs($visible_phids);
if ($ordering) {

View File

@@ -3,6 +3,7 @@
final class ManiphestTransactionEditor
extends PhabricatorApplicationTransactionEditor {
private $oldProjectPHIDs;
private $moreValidationErrors = array();
public function getEditorApplicationClass() {
@@ -378,6 +379,11 @@ final class ManiphestTransactionEditor
}
}
$send_notifications = PhabricatorNotificationClient::isEnabled();
if ($send_notifications) {
$this->oldProjectPHIDs = $this->loadProjectPHIDs($object);
}
return $results;
}
@@ -859,4 +865,71 @@ final class ManiphestTransactionEditor
return array_values($phid_list);
}
protected function didApplyTransactions($object, array $xactions) {
$send_notifications = PhabricatorNotificationClient::isEnabled();
if ($send_notifications) {
$old_phids = $this->oldProjectPHIDs;
$new_phids = $this->loadProjectPHIDs($object);
// We want to emit update notifications for all old and new tagged
// projects, and all parents of those projects. For example, if an
// edit removes project "A > B" from a task, the "A" workboard should
// receive an update event.
$project_phids = array_fuse($old_phids) + array_fuse($new_phids);
$project_phids = array_keys($project_phids);
if ($project_phids) {
$projects = id(new PhabricatorProjectQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($project_phids)
->execute();
$notify_projects = array();
foreach ($projects as $project) {
$notify_projects[$project->getPHID()] = $project;
foreach ($project->getAncestorProjects() as $ancestor) {
$notify_projects[$ancestor->getPHID()] = $ancestor;
}
}
foreach ($notify_projects as $key => $project) {
if (!$project->getHasWorkboard()) {
unset($notify_projects[$key]);
}
}
$notify_phids = array_keys($notify_projects);
if ($notify_phids) {
$data = array(
'type' => 'workboards',
'subscribers' => $notify_phids,
);
PhabricatorNotificationClient::tryToPostMessage($data);
}
}
}
return $xactions;
}
private function loadProjectPHIDs(ManiphestTask $task) {
if (!$task->getPHID()) {
return array();
}
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($task->getPHID()))
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$edge_query->execute();
return $edge_query->getDestinationPHIDs();
}
}

View File

@@ -2,9 +2,7 @@
final class PhabricatorMetaMTAErrorMailAction extends PhabricatorSystemAction {
public function getActionConstant() {
return 'email.error';
}
const TYPECONST = 'email.error';
public function getScoreThreshold() {
return 6 / phutil_units('1 hour in seconds');

View File

@@ -37,4 +37,8 @@ final class PhabricatorNotificationClient extends Phobject {
}
}
public static function isEnabled() {
return (bool)PhabricatorNotificationServerRef::getEnabledAdminServers();
}
}

View File

@@ -15,12 +15,11 @@ final class PhabricatorOAuthClientViewController
}
$header = $this->buildHeaderView($client);
$actions = $this->buildActionView($client);
$properties = $this->buildPropertyListView($client);
$properties->setActionList($actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($client->getName());
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($client->getName())
->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$client,
@@ -28,19 +27,27 @@ final class PhabricatorOAuthClientViewController
$timeline->setShouldTerminate(true);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->setHeaderText(pht('Details'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($properties);
$title = pht('OAuth Application: %s', $client->getName());
return $this->newPage()
->setCrumbs($crumbs)
->setTitle($title)
->appendChild(
$curtain = $this->buildCurtain($client);
$columns = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(
array(
$box,
$timeline,
));
return $this->newPage()
->setCrumbs($crumbs)
->setTitle($title)
->appendChild($columns);
}
private function buildHeaderView(PhabricatorOAuthServerClient $client) {
@@ -60,8 +67,9 @@ final class PhabricatorOAuthClientViewController
return $header;
}
private function buildActionView(PhabricatorOAuthServerClient $client) {
private function buildCurtain(PhabricatorOAuthServerClient $client) {
$viewer = $this->getViewer();
$actions = array();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
@@ -70,24 +78,19 @@ final class PhabricatorOAuthClientViewController
$id = $client->getID();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$actions[] = id(new PhabricatorActionView())
->setName(pht('Edit Application'))
->setIcon('fa-pencil')
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit)
->setHref($client->getEditURI());
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Application'))
->setIcon('fa-pencil')
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit)
->setHref($client->getEditURI()));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Show Application Secret'))
->setIcon('fa-eye')
->setHref($this->getApplicationURI("client/secret/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true));
$actions[] = id(new PhabricatorActionView())
->setName(pht('Show Application Secret'))
->setIcon('fa-eye')
->setHref($this->getApplicationURI("client/secret/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true);
$is_disabled = $client->getIsDisabled();
if ($is_disabled) {
@@ -100,22 +103,26 @@ final class PhabricatorOAuthClientViewController
$disable_uri = $this->getApplicationURI("client/disable/{$id}/");
$view->addAction(
id(new PhabricatorActionView())
->setName($disable_text)
->setIcon($disable_icon)
->setWorkflow(true)
->setDisabled(!$can_edit)
->setHref($disable_uri));
$actions[] = id(new PhabricatorActionView())
->setName($disable_text)
->setIcon($disable_icon)
->setWorkflow(true)
->setDisabled(!$can_edit)
->setHref($disable_uri);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Generate Test Token'))
->setIcon('fa-plus')
->setWorkflow(true)
->setHref($this->getApplicationURI("client/test/{$id}/")));
$actions[] = id(new PhabricatorActionView())
->setName(pht('Generate Test Token'))
->setIcon('fa-plus')
->setWorkflow(true)
->setHref($this->getApplicationURI("client/test/{$id}/"));
return $view;
$curtain = $this->newCurtainView($client);
foreach ($actions as $action) {
$curtain->addAction($action);
}
return $curtain;
}
private function buildPropertyListView(PhabricatorOAuthServerClient $client) {
@@ -132,10 +139,6 @@ final class PhabricatorOAuthClientViewController
pht('Redirect URI'),
$client->getRedirectURI());
$view->addProperty(
pht('Created'),
phabricator_datetime($client->getDateCreated(), $viewer));
return $view;
}
}

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