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( 'names' => array(
'conpherence.pkg.css' => '3c8a0668', 'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf', 'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => 'efa1b78b', 'core.pkg.css' => 'eef4903d',
'core.pkg.js' => '8225dc58', 'core.pkg.js' => '73a06a9f',
'differential.pkg.css' => '8d8360fb', 'differential.pkg.css' => '8d8360fb',
'differential.pkg.js' => '67e02996', 'differential.pkg.js' => '0b037a4f',
'diffusion.pkg.css' => '42c75c37', 'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => 'a98c0bf7', 'diffusion.pkg.js' => 'a98c0bf7',
'maniphest.pkg.css' => '35995d6d', 'maniphest.pkg.css' => '35995d6d',
@@ -24,7 +24,7 @@ return array(
'rsrc/audio/basic/ting.mp3' => 'a6b6540e', 'rsrc/audio/basic/ting.mp3' => 'a6b6540e',
'rsrc/css/aphront/aphront-bars.css' => '4a327b4a', 'rsrc/css/aphront/aphront-bars.css' => '4a327b4a',
'rsrc/css/aphront/dark-console.css' => '7f06cda2', '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/list-filter-view.css' => 'feb64255',
'rsrc/css/aphront/multi-column.css' => 'fbc00ba3', 'rsrc/css/aphront/multi-column.css' => 'fbc00ba3',
'rsrc/css/aphront/notification.css' => '30240bd2', 'rsrc/css/aphront/notification.css' => '30240bd2',
@@ -92,7 +92,7 @@ return array(
'rsrc/css/application/pholio/pholio.css' => '88ef5ef1', 'rsrc/css/application/pholio/pholio.css' => '88ef5ef1',
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8', 'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8',
'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241', '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/phrequent/phrequent.css' => 'bd79cc67',
'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0', 'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0',
'rsrc/css/application/policy/policy-edit.css' => '8794e2ed', '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/URI.js' => '2e255291',
'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb', 'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e', '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__/Cookie.js' => 'ca686f71',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae', '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/phortune/phortune-credit-card-form.js' => 'd12d214f',
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9', 'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172', '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/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/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/WorkboardDropEffect.js' => '8e0aa661',
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d', 'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b', 'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f', '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-project-create.js' => '34c53422',
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9', 'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68', '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-line-linker.js' => 'e15c8b1f',
'rsrc/js/core/behavior-linked-container.js' => '74446546', 'rsrc/js/core/behavior-linked-container.js' => '74446546',
'rsrc/js/core/behavior-more.js' => '506aa3f4', '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-oncopy.js' => 'ff7b3f22',
'rsrc/js/core/behavior-phabricator-nav.js' => 'f166c949', 'rsrc/js/core/behavior-phabricator-nav.js' => 'f166c949',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '2f80333f', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '2f80333f',
@@ -532,7 +532,7 @@ return array(
'almanac-css' => '2e050f4f', 'almanac-css' => '2e050f4f',
'aphront-bars' => '4a327b4a', 'aphront-bars' => '4a327b4a',
'aphront-dark-console-css' => '7f06cda2', 'aphront-dark-console-css' => '7f06cda2',
'aphront-dialog-view-css' => 'b70c70df', 'aphront-dialog-view-css' => '874f5c06',
'aphront-list-filter-view-css' => 'feb64255', 'aphront-list-filter-view-css' => 'feb64255',
'aphront-multi-column-view-css' => 'fbc00ba3', 'aphront-multi-column-view-css' => 'fbc00ba3',
'aphront-panel-view-css' => '46923d46', 'aphront-panel-view-css' => '46923d46',
@@ -647,7 +647,7 @@ return array(
'javelin-behavior-phabricator-line-linker' => 'e15c8b1f', 'javelin-behavior-phabricator-line-linker' => 'e15c8b1f',
'javelin-behavior-phabricator-nav' => 'f166c949', 'javelin-behavior-phabricator-nav' => 'f166c949',
'javelin-behavior-phabricator-notification-example' => '29819b75', '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-oncopy' => 'ff7b3f22',
'javelin-behavior-phabricator-remarkup-assist' => '2f80333f', 'javelin-behavior-phabricator-remarkup-assist' => '2f80333f',
'javelin-behavior-phabricator-reveal-content' => 'b105a3a6', 'javelin-behavior-phabricator-reveal-content' => 'b105a3a6',
@@ -669,7 +669,7 @@ return array(
'javelin-behavior-phuix-example' => 'c2c500a7', 'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9', 'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172', 'javelin-behavior-policy-rule-editor' => '9347f172',
'javelin-behavior-project-boards' => 'aad45445', 'javelin-behavior-project-boards' => '58cb6a88',
'javelin-behavior-project-create' => '34c53422', 'javelin-behavior-project-create' => '34c53422',
'javelin-behavior-quicksand-blacklist' => '5a6f6a06', 'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
'javelin-behavior-read-only-warning' => 'b9109f8f', 'javelin-behavior-read-only-warning' => 'b9109f8f',
@@ -745,16 +745,16 @@ return array(
'javelin-view-renderer' => '9aae2b66', 'javelin-view-renderer' => '9aae2b66',
'javelin-view-visitor' => '308f9fe4', 'javelin-view-visitor' => '308f9fe4',
'javelin-websocket' => 'fdc13e4e', 'javelin-websocket' => 'fdc13e4e',
'javelin-workboard-board' => 'c02a5497', 'javelin-workboard-board' => 'b46d88c5',
'javelin-workboard-card' => '0392a5d8', 'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '2a61f8d4', 'javelin-workboard-card-template' => '84f82dad',
'javelin-workboard-column' => 'c3d24e63', 'javelin-workboard-column' => 'c3d24e63',
'javelin-workboard-controller' => '42c7a5a7', 'javelin-workboard-controller' => 'b9d0c2f3',
'javelin-workboard-drop-effect' => '8e0aa661', 'javelin-workboard-drop-effect' => '8e0aa661',
'javelin-workboard-header' => '111bfd2d', 'javelin-workboard-header' => '111bfd2d',
'javelin-workboard-header-template' => 'ebe83a6b', 'javelin-workboard-header-template' => 'ebe83a6b',
'javelin-workboard-order-template' => '03e8891f', 'javelin-workboard-order-template' => '03e8891f',
'javelin-workflow' => '851f642d', 'javelin-workflow' => '945ff654',
'maniphest-report-css' => '3d53188b', 'maniphest-report-css' => '3d53188b',
'maniphest-task-edit-css' => '272daa84', 'maniphest-task-edit-css' => '272daa84',
'maniphest-task-summary-css' => '61d1667e', 'maniphest-task-summary-css' => '61d1667e',
@@ -813,7 +813,7 @@ return array(
'pholio-inline-comments-css' => '722b48c2', 'pholio-inline-comments-css' => '722b48c2',
'phortune-credit-card-form' => 'd12d214f', 'phortune-credit-card-form' => 'd12d214f',
'phortune-credit-card-form-css' => '3b9868a8', 'phortune-credit-card-form-css' => '3b9868a8',
'phortune-css' => '12e8251a', 'phortune-css' => '508a1a5e',
'phortune-invoice-css' => '4436b241', 'phortune-invoice-css' => '4436b241',
'phrequent-css' => 'bd79cc67', 'phrequent-css' => 'bd79cc67',
'phriction-document-css' => '03380da0', 'phriction-document-css' => '03380da0',
@@ -1133,9 +1133,6 @@ return array(
'javelin-stratcom', 'javelin-stratcom',
'javelin-behavior', 'javelin-behavior',
), ),
'2a61f8d4' => array(
'javelin-install',
),
'2a8b62d9' => array( '2a8b62d9' => array(
'multirow-row-manager', 'multirow-row-manager',
'javelin-install', 'javelin-install',
@@ -1264,16 +1261,6 @@ return array(
'4234f572' => array( '4234f572' => array(
'syntax-default-css', '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( '4370900d' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
@@ -1412,6 +1399,16 @@ return array(
'javelin-vector', 'javelin-vector',
'javelin-typeahead-static-source', '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( '5902260c' => array(
'javelin-util', 'javelin-util',
'javelin-magical-init', 'javelin-magical-init',
@@ -1607,16 +1604,8 @@ return array(
'javelin-resource', 'javelin-resource',
'javelin-routable', 'javelin-routable',
), ),
'851f642d' => array( '84f82dad' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install', 'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
), ),
'87428eb2' => array( '87428eb2' => array(
'javelin-behavior', 'javelin-behavior',
@@ -1709,6 +1698,17 @@ return array(
'javelin-typeahead-preloaded-source', 'javelin-typeahead-preloaded-source',
'javelin-util', 'javelin-util',
), ),
'945ff654' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'94681e22' => array( '94681e22' => array(
'javelin-magical-init', 'javelin-magical-init',
'javelin-install', 'javelin-install',
@@ -1730,6 +1730,12 @@ return array(
'javelin-dom', 'javelin-dom',
'javelin-router', 'javelin-router',
), ),
'98ef467f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'9aae2b66' => array( '9aae2b66' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
@@ -1790,12 +1796,6 @@ return array(
'phui-button-css', 'phui-button-css',
'phui-button-simple-css', 'phui-button-simple-css',
), ),
'a4af0b4a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'a5257c4e' => array( 'a5257c4e' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',
@@ -1843,16 +1843,6 @@ return array(
'javelin-dom', 'javelin-dom',
'javelin-util', '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( 'ab85e184' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',
@@ -1901,6 +1891,18 @@ return array(
'b347a301' => array( 'b347a301' => array(
'javelin-behavior', '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( 'b49fd60c' => array(
'multirow-row-manager', 'multirow-row-manager',
'trigger-rule', 'trigger-rule',
@@ -1943,20 +1945,18 @@ return array(
'javelin-uri', 'javelin-uri',
'phabricator-notification', 'phabricator-notification',
), ),
'bde53589' => array( 'b9d0c2f3' => array(
'phui-inline-comment-view-css',
),
'c02a5497' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',
'javelin-util', 'javelin-util',
'javelin-vector',
'javelin-stratcom', 'javelin-stratcom',
'javelin-workflow', 'javelin-workflow',
'phabricator-draggable-list', 'phabricator-drag-and-drop-file-upload',
'javelin-workboard-column', 'javelin-workboard-board',
'javelin-workboard-header-template', ),
'javelin-workboard-card-template', 'bde53589' => array(
'javelin-workboard-order-template', 'phui-inline-comment-view-css',
), ),
'c03f2fb4' => array( 'c03f2fb4' => array(
'javelin-install', '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 = new PhutilArgumentParser($argv);
$args->setSynopsis(<<<EOSYNOPSIS $args->setSynopsis(<<<EOSYNOPSIS
**people** __command__ [__options__] **user** __command__ [__options__]
Manage user profiles and accounts. Modify user accounts to regain access to an install.
EOSYNOPSIS 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 ($response_exception) {
// If we encountered an exception while building a normal response, then // If we encountered an exception while building a normal response, then
// encountered another exception while building a response for the first // encountered another exception while building a response for the first
// exception, just throw the original exception. It is more likely to be // exception, throw an aggregate exception that will be unpacked by the
// useful and point at a root cause than the second exception we ran into // higher-level handler. This is above our pay grade.
// while telling the user about it.
if ($original_exception) { 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 // 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() { public function buildResponseString() {
$request = $this->getRequest(); $request = $this->getRequest();
$user = $request->getUser(); $viewer = $request->getViewer();
$dialog = id(new AphrontDialogView()) $dialog = id(new AphrontDialogView())
->setUser($user) ->setViewer($viewer)
->setTitle(pht('404 Not Found')) ->setTitle(pht('404 Not Found'))
->addCancelButton('/', pht('Focus')) ->addCancelButton('/', pht('Return to Charted Waters'))
->appendParagraph( ->appendParagraph(
pht( pht(
'Do not dwell in the past, do not dream of the future, '. 'You arrive at your destination, but there is nothing here.'))
'concentrate the mind on the present moment.')); ->appendParagraph(
pht(
'Perhaps the real treasure was the friends you made '.
'along the way.'));
$view = id(new PhabricatorStandardPageView()) $view = id(new PhabricatorStandardPageView())
->setTitle(pht('404 Not Found')) ->setTitle(pht('404 Not Found'))

View File

@@ -61,9 +61,39 @@ final class AphrontUnhandledExceptionResponse
return 'unhandled-exception'; return 'unhandled-exception';
} }
protected function getResponseBody() { private function getExceptionList() {
$ex = $this->exception; 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) { if ($ex instanceof AphrontMalformedRequestException) {
$title = $ex->getTitle(); $title = $ex->getTitle();
} else { } else {
@@ -122,12 +152,20 @@ final class AphrontUnhandledExceptionResponse
} }
protected function buildPlainTextResponseString() { 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( return pht(
'%s: %s', '%s: %s',
get_class($ex), get_class($exception),
$ex->getMessage()); $exception->getMessage());
} }
} }

View File

@@ -5,10 +5,6 @@ final class PhabricatorAuthChangePasswordAction
const TYPECONST = 'auth.password'; const TYPECONST = 'auth.password';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() { public function getScoreThreshold() {
return 20 / phutil_units('1 hour in seconds'); 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'; const TYPECONST = 'auth.factor.new';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() { public function getScoreThreshold() {
return 60 / phutil_units('1 hour in seconds'); return 60 / phutil_units('1 hour in seconds');
} }

View File

@@ -4,10 +4,6 @@ final class PhabricatorAuthTestSMSAction extends PhabricatorSystemAction {
const TYPECONST = 'auth.sms.test'; const TYPECONST = 'auth.sms.test';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() { public function getScoreThreshold() {
return 60 / phutil_units('1 hour in seconds'); 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'; const TYPECONST = 'auth.factor';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() { public function getScoreThreshold() {
return 10 / phutil_units('1 hour in seconds'); 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', 'PhabricatorAuthMessageListController',
$this->getEditRoutePattern('edit/') => $this->getEditRoutePattern('edit/') =>
'PhabricatorAuthMessageEditController', 'PhabricatorAuthMessageEditController',
'(?P<id>[1-9]\d*)/' => '(?P<id>[^/]+)/' =>
'PhabricatorAuthMessageViewController', 'PhabricatorAuthMessageViewController',
), ),

View File

@@ -53,6 +53,14 @@ final class PhabricatorEmailLoginController
// it expensive to fish for valid email addresses while giving the user // it expensive to fish for valid email addresses while giving the user
// a better error if they goof their email. // 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( $target_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s', 'address = %s',
$v_email); $v_email);
@@ -94,29 +102,40 @@ final class PhabricatorEmailLoginController
} }
if (!$errors) { if (!$errors) {
$body = $this->newAccountLoginMailBody( $target_address = new PhutilEmailAddress($target_email->getAddress());
$target_user,
$is_logged_in); $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) { if ($is_logged_in) {
$subject = pht('[Phabricator] Account Password Link');
$instructions = pht( $instructions = pht(
'An email has been sent containing a link you can use to set '. 'An email has been sent containing a link you can use to set '.
'a password for your account.'); 'a password for your account.');
} else { } else {
$subject = pht('[Phabricator] Account Login Link');
$instructions = pht( $instructions = pht(
'An email has been sent containing a link you can use to log '. 'An email has been sent containing a link you can use to log '.
'in to your account.'); 'in to your account.');
} }
$mail = id(new PhabricatorMetaMTAMail())
->setSubject($subject)
->setForceDelivery(true)
->addRawTos(array($target_email->getAddress()))
->setBody($body)
->saveAndSend();
return $this->newDialog() return $this->newDialog()
->setTitle(pht('Check Your Email')) ->setTitle(pht('Check Your Email'))
->setShortTitle(pht('Email Sent')) ->setShortTitle(pht('Email Sent'))
@@ -182,55 +201,6 @@ final class PhabricatorEmailLoginController
->addSubmitButton(pht('Send Email')); ->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() { private function isPasswordAuthEnabled() {
return (bool)PhabricatorPasswordAuthProvider::getPasswordProvider(); return (bool)PhabricatorPasswordAuthProvider::getPasswordProvider();
} }

View File

@@ -79,6 +79,7 @@ final class PhabricatorAuthEditController
} }
$errors = array(); $errors = array();
$validation_exception = null;
$v_login = $config->getShouldAllowLogin(); $v_login = $config->getShouldAllowLogin();
$v_registration = $config->getShouldAllowRegistration(); $v_registration = $config->getShouldAllowRegistration();
@@ -153,12 +154,16 @@ final class PhabricatorAuthEditController
$editor = id(new PhabricatorAuthProviderConfigEditor()) $editor = id(new PhabricatorAuthProviderConfigEditor())
->setActor($viewer) ->setActor($viewer)
->setContentSourceFromRequest($request) ->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true) ->setContinueOnNoEffect(true);
->applyTransactions($config, $xactions);
try {
$editor->applyTransactions($config, $xactions);
$next_uri = $config->getURI(); $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 { } else {
$properties = $provider->readFormValuesFromProvider(); $properties = $provider->readFormValuesFromProvider();
@@ -325,12 +330,35 @@ final class PhabricatorAuthEditController
$provider->extendEditForm($request, $form, $properties, $issues); $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 $form
->appendChild( ->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri) ->addCancelButton($cancel_uri)
->setDisabled($is_locked)
->setValue($button)); ->setValue($button));
$help = $provider->getConfigurationHelp(); $help = $provider->getConfigurationHelp();
if ($help) { if ($help) {
$form->appendChild(id(new PHUIFormDividerControl())); $form->appendChild(id(new PHUIFormDividerControl()));
@@ -346,12 +374,16 @@ final class PhabricatorAuthEditController
$form_box = id(new PHUIObjectBoxView()) $form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Provider')) ->setHeaderText(pht('Provider'))
->setFormErrors($errors) ->setFormErrors($errors)
->setValidationException($validation_exception)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form); ->setForm($form);
$view = id(new PHUITwoColumnView()) $view = id(new PHUITwoColumnView())
->setHeader($header) ->setHeader($header)
->setFooter(array( ->setFooter(array(
$locked_warning,
$form_box, $form_box,
$footer, $footer,
)); ));

View File

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

View File

@@ -9,6 +9,27 @@ final class PhabricatorAuthNewController
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$cancel_uri = $this->getApplicationURI(); $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(); $providers = PhabricatorAuthProvider::getAllBaseProviders();

View File

@@ -114,6 +114,86 @@ final class PhabricatorAuthProviderViewController
pht('Provider Type'), pht('Provider Type'),
$config->getProvider()->getProviderName()); $config->getProvider()->getProviderName());
$status = $this->buildStatus($config);
$view->addProperty(pht('Status'), $status);
return $view; 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(); $list = new PHUIObjectItemListView();
foreach ($types as $type) { foreach ($types as $type) {
$message = idx($messages, $type->getMessageTypeKey()); $message = idx($messages, $type->getMessageTypeKey());
if ($message) { if ($message) {
$href = $message->getURI(); $href = $message->getURI();
$name = $message->getMessageTypeDisplayName(); $name = $message->getMessageTypeDisplayName();
} else { } else {
$href = '/auth/message/edit/?messageKey='.$type->getMessageTypeKey(); $href = urisprintf(
'/auth/message/%s/',
$type->getMessageTypeKey());
$name = $type->getDisplayName(); $name = $type->getDisplayName();
} }

View File

@@ -9,26 +9,61 @@ final class PhabricatorAuthMessageViewController
$this->requireApplicationCapability( $this->requireApplicationCapability(
AuthManageProvidersCapability::CAPABILITY); AuthManageProvidersCapability::CAPABILITY);
// 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()) $message = id(new PhabricatorAuthMessageQuery())
->setViewer($viewer) ->setViewer($viewer)
->withIDs(array($request->getURIData('id'))) ->withIDs(array($id))
->executeOne(); ->executeOne();
if (!$message) { if (!$message) {
return new Aphront404Response(); 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() $crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($message->getObjectName()) ->addTextCrumb($message->getMessageType()->getDisplayName())
->setBorder(true); ->setBorder(true);
$header = $this->buildHeaderView($message); $header = $this->buildHeaderView($message);
$properties = $this->buildPropertiesView($message); $properties = $this->buildPropertiesView($message);
$curtain = $this->buildCurtain($message); $curtain = $this->buildCurtain($message);
if ($message->getID()) {
$timeline = $this->buildTransactionTimeline( $timeline = $this->buildTransactionTimeline(
$message, $message,
new PhabricatorAuthMessageTransactionQuery()); new PhabricatorAuthMessageTransactionQuery());
$timeline->setShouldTerminate(true); $timeline->setShouldTerminate(true);
} else {
$timeline = null;
}
$view = id(new PHUITwoColumnView()) $view = id(new PHUITwoColumnView())
->setHeader($header) ->setHeader($header)
@@ -62,19 +97,36 @@ final class PhabricatorAuthMessageViewController
private function buildPropertiesView(PhabricatorAuthMessage $message) { private function buildPropertiesView(PhabricatorAuthMessage $message) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$message_type = $message->getMessageType();
$view = id(new PHUIPropertyListView()) $view = id(new PHUIPropertyListView())
->setViewer($viewer); ->setViewer($viewer);
$view->addProperty( $full_description = $message_type->getFullDescription();
pht('Description'), if (strlen($full_description)) {
$message->getMessageType()->getShortDescription()); $view->addTextContent(new PHUIRemarkupView($viewer, $full_description));
} else {
$short_description = $message_type->getShortDescription();
$view->addProperty(pht('Description'), $short_description);
}
$message_text = $message->getMessageText();
if (strlen($message_text)) {
$view->addSectionHeader( $view->addSectionHeader(
pht('Message Preview'), pht('Message Preview'),
PHUIPropertyListView::ICON_SUMMARY); PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent( $view->addTextContent(new PHUIRemarkupView($viewer, $message_text));
new PHUIRemarkupView($viewer, $message->getMessageText())); }
$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; return $view;
} }
@@ -88,13 +140,27 @@ final class PhabricatorAuthMessageViewController
$message, $message,
PhabricatorPolicyCapability::CAN_EDIT); 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 = $this->newCurtainView($message);
$curtain->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Edit Message')) ->setName($edit_name)
->setIcon('fa-pencil') ->setIcon('fa-pencil')
->setHref($this->getApplicationURI("message/edit/{$id}/")) ->setHref($edit_uri)
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)); ->setWorkflow(!$can_edit));

View File

@@ -125,4 +125,25 @@ final class PhabricatorAuthProviderConfigEditor
return parent::mergeTransactions($u, $v); 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, null,
$identity_phid, $identity_phid,
($partial ($partial
? PhabricatorUserLog::ACTION_LOGIN_PARTIAL ? PhabricatorPartialLoginUserLogType::LOGTYPE
: PhabricatorUserLog::ACTION_LOGIN)); : PhabricatorLoginUserLogType::LOGTYPE));
$log->setDetails( $log->setDetails(
array( array(
@@ -366,7 +366,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
$user, $user,
$user->getPHID(), $user->getPHID(),
PhabricatorUserLog::ACTION_LOGOUT); PhabricatorLogoutUserLogType::LOGTYPE);
$log->save(); $log->save();
$extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions(); $extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions();
@@ -688,13 +688,13 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
$viewer, $viewer,
$viewer->getPHID(), $viewer->getPHID(),
PhabricatorUserLog::ACTION_ENTER_HISEC); PhabricatorEnterHisecUserLogType::LOGTYPE);
$log->save(); $log->save();
} else { } else {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
$viewer, $viewer,
$viewer->getPHID(), $viewer->getPHID(),
PhabricatorUserLog::ACTION_FAIL_HISEC); PhabricatorFailHisecUserLogType::LOGTYPE);
$log->save(); $log->save();
} }
} }
@@ -831,7 +831,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
$viewer, $viewer,
$viewer->getPHID(), $viewer->getPHID(),
PhabricatorUserLog::ACTION_EXIT_HISEC); PhabricatorExitHisecUserLogType::LOGTYPE);
$log->save(); $log->save();
} }
@@ -872,7 +872,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
$viewer, $viewer,
$viewer->getPHID(), $viewer->getPHID(),
PhabricatorUserLog::ACTION_LOGIN_FULL); PhabricatorFullLoginUserLogType::LOGTYPE);
$log->save(); $log->save();
unset($unguarded); unset($unguarded);
} }
@@ -917,7 +917,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
$viewer, $viewer,
$viewer->getPHID(), $viewer->getPHID(),
PhabricatorUserLog::ACTION_LOGIN_LEGALPAD); PhabricatorSignDocumentsUserLogType::LOGTYPE);
$log->save(); $log->save();
} }
unset($unguarded); unset($unguarded);

View File

@@ -194,6 +194,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$control = id(new PHUIFormNumberControl()) $control = id(new PHUIFormNumberControl())
->setName($name) ->setName($name)
->setDisableAutocomplete(true) ->setDisableAutocomplete(true)
->setAutofocus(true)
->setValue($value) ->setValue($value)
->setError($error); ->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 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'; const MESSAGEKEY = 'mail.welcome';
public function getDisplayName() { public function getDisplayName() {
return pht('Welcome Email Body'); return pht('Mail Body: Welcome');
} }
public function getShortDescription() { public function getShortDescription() {

View File

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

View File

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

View File

@@ -282,48 +282,29 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
$viewer = $request->getUser(); $viewer = $request->getUser();
$content_source = PhabricatorContentSource::newFromRequest($request); $content_source = PhabricatorContentSource::newFromRequest($request);
$captcha_limit = 5; $rate_actor = PhabricatorSystemActionEngine::newActorFromRequest($request);
$hard_limit = 32;
$limit_window = phutil_units('15 minutes in seconds');
$failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( PhabricatorSystemActionEngine::willTakeAction(
PhabricatorUserLog::ACTION_LOGIN_FAILURE, array($rate_actor),
$limit_window); new PhabricatorAuthTryPasswordAction(),
1);
// If the same remote address has submitted several failed login attempts // If the same remote address has submitted several failed login attempts
// recently, require they provide a CAPTCHA response for new attempts. // recently, require they provide a CAPTCHA response for new attempts.
$require_captcha = false; $require_captcha = false;
$captcha_valid = false; $captcha_valid = false;
if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) { if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) {
if (count($failed_attempts) > $captcha_limit) { try {
PhabricatorSystemActionEngine::willTakeAction(
array($rate_actor),
new PhabricatorAuthTryPasswordWithoutCAPTCHAAction(),
1);
} catch (PhabricatorSystemActionRateLimitException $ex) {
$require_captcha = true; $require_captcha = true;
$captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request); $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; $response = null;
$account = null; $account = null;
$log_user = null; $log_user = null;
@@ -364,7 +345,7 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
null, null,
$log_user ? $log_user->getPHID() : null, $log_user ? $log_user->getPHID() : null,
PhabricatorUserLog::ACTION_LOGIN_FAILURE); PhabricatorLoginFailureUserLogType::LOGTYPE);
$log->save(); $log->save();
} }

View File

@@ -45,7 +45,7 @@ final class PhabricatorAuthMessage
} }
public function getURI() { public function getURI() {
return urisprintf('/auth/message/%s', $this->getID()); return urisprintf('/auth/message/%s/', $this->getID());
} }
public function attachMessageType(PhabricatorAuthMessageType $type) { public function attachMessageType(PhabricatorAuthMessageType $type) {
@@ -75,12 +75,16 @@ final class PhabricatorAuthMessage
$message_key) { $message_key) {
$message = self::loadMessage($viewer, $message_key); $message = self::loadMessage($viewer, $message_key);
if ($message) {
if (!$message) { $message_text = $message->getMessageText();
return null; 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'; const PROPERTY_KEY = 'auth:property';
private $provider;
public function setProvider(PhabricatorAuthProvider $provider) {
$this->provider = $provider;
return $this;
}
public function getProvider() { public function getProvider() {
return $this->provider; return $this->getObject()->getProvider();
} }
public function getApplicationName() { public function getApplicationName() {

View File

@@ -481,7 +481,7 @@ abstract class PhabricatorController extends AphrontController {
protected function buildTransactionTimeline( protected function buildTransactionTimeline(
PhabricatorApplicationTransactionInterface $object, PhabricatorApplicationTransactionInterface $object,
PhabricatorApplicationTransactionQuery $query, PhabricatorApplicationTransactionQuery $query = null,
PhabricatorMarkupEngine $engine = null, PhabricatorMarkupEngine $engine = null,
$view_data = array()) { $view_data = array()) {
@@ -489,6 +489,17 @@ abstract class PhabricatorController extends AphrontController {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$xaction = $object->getApplicationTransactionTemplate(); $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()) $pager = id(new AphrontCursorPagerView())
->readFromRequest($request) ->readFromRequest($request)
->setURI(new PhutilURI( ->setURI(new PhutilURI(

View File

@@ -30,4 +30,8 @@ final class PhabricatorMarkupCache extends PhabricatorCacheDAO {
) + parent::getConfiguration(); ) + 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) { protected function execute(ConduitAPIRequest $request) {
$failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( $failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP(
PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE_FAILURE, PhabricatorConduitCertificateFailureUserLogType::LOGTYPE,
60 * 5); 60 * 5);
if (count($failed_attempts) > 5) { if (count($failed_attempts) > 5) {
@@ -61,7 +61,7 @@ final class ConduitGetCertificateConduitAPIMethod extends ConduitAPIMethod {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
$request->getUser(), $request->getUser(),
$info->getUserPHID(), $info->getUserPHID(),
PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE) PhabricatorConduitCertificateUserLogType::LOGTYPE)
->save(); ->save();
} }
@@ -85,7 +85,7 @@ final class ConduitGetCertificateConduitAPIMethod extends ConduitAPIMethod {
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
$request->getUser(), $request->getUser(),
$info ? $info->getUserPHID() : '-', $info ? $info->getUserPHID() : '-',
PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE_FAILURE) PhabricatorConduitCertificateFailureUserLogType::LOGTYPE)
->save(); ->save();
} }

View File

@@ -3,8 +3,24 @@
final class ConduitEpochParameterType final class ConduitEpochParameterType
extends ConduitParameterType { 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) { protected function getParameterValue(array $request, $key, $strict) {
$value = parent::getParameterValue($request, $key, $strict); $value = parent::getParameterValue($request, $key, $strict);
if ($this->allowNull && ($value === null)) {
return $value;
}
$value = $this->parseIntValue($request, $key, $value, $strict); $value = $this->parseIntValue($request, $key, $value, $strict);
if ($value <= 0) { if ($value <= 0) {

View File

@@ -536,6 +536,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'differential.whitespace-matters' => pht( 'differential.whitespace-matters' => pht(
'Whitespace rendering is now handled automatically.'), 'Whitespace rendering is now handled automatically.'),
'phd.pid-directory' => pht(
'Phabricator daemons no longer use PID files.'),
); );
return $ancient_config; 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) { public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$argv = $args->getArg('args'); $argv = $args->getArg('args');
if (count($argv) == 0) { if (!$argv) {
throw new PhutilArgumentUsageException( 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'); $is_stdin = $args->getArg('stdin');
@@ -45,7 +44,8 @@ final class PhabricatorConfigManagementSetWorkflow
if (count($argv) > 1) { if (count($argv) > 1) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
pht( 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...'))); fprintf(STDERR, tsprintf("%s\n", pht('Reading value from stdin...')));
@@ -54,7 +54,8 @@ final class PhabricatorConfigManagementSetWorkflow
if (count($argv) == 1) { if (count($argv) == 1) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
pht( 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)); $key));
} }
@@ -67,14 +68,13 @@ final class PhabricatorConfigManagementSetWorkflow
$value = $argv[1]; $value = $argv[1];
} }
$options = PhabricatorApplicationConfigOptions::loadAllOptions(); $options = PhabricatorApplicationConfigOptions::loadAllOptions();
if (empty($options[$key])) { if (empty($options[$key])) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
pht( pht(
"No such configuration key '%s'! Use `%s` to list all keys.", 'Configuration key "%s" is unknown. Use "bin/config list" to list '.
$key, 'all known keys.',
'config list')); $key));
} }
$option = $options[$key]; $option = $options[$key];
@@ -99,7 +99,7 @@ final class PhabricatorConfigManagementSetWorkflow
switch ($type) { switch ($type) {
default: default:
$message = pht( $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, $key,
$type); $type);
break; break;
@@ -128,7 +128,6 @@ final class PhabricatorConfigManagementSetWorkflow
} }
if ($use_database) { if ($use_database) {
$config_type = 'database';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key); $config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$config_entry->setValue($value); $config_entry->setValue($value);
@@ -136,15 +135,28 @@ final class PhabricatorConfigManagementSetWorkflow
$config_entry->setIsDeleted(0); $config_entry->setIsDeleted(0);
$config_entry->save(); $config_entry->save();
$write_message = pht(
'Wrote configuration key "%s" to database storage.',
$key);
} else { } else {
$config_type = 'local'; $config_source = id(new PhabricatorConfigLocalSource())
id(new PhabricatorConfigLocalSource())
->setKeys(array($key => $value)); ->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( echo tsprintf(
"%s\n", "<bg:green>** %s **</bg> %s\n",
pht("Set '%s' in %s configuration.", $key, $config_type)); pht('DONE'),
$write_message);
return 0;
} }
} }

View File

@@ -21,10 +21,6 @@ final class PhabricatorPHDConfigOptions
public function getOptions() { public function getOptions() {
return array( 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') $this->newOption('phd.log-directory', 'string', '/var/tmp/phd/log')
->setLocked(true) ->setLocked(true)
->setDescription( ->setDescription(

View File

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

View File

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

View File

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

View File

@@ -6,101 +6,52 @@ final class PhabricatorDaemonManagementStatusWorkflow
protected function didConstruct() { protected function didConstruct() {
$this $this
->setName('status') ->setName('status')
->setSynopsis(pht('Show status of running daemons.')) ->setSynopsis(pht('Show daemon processes on this host.'));
->setArguments(
array(
array(
'name' => 'local',
'help' => pht('Show only local daemons.'),
),
));
} }
public function execute(PhutilArgumentParser $args) { public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole(); $process_refs = $this->getOverseerProcessRefs();
if ($args->getArg('local')) { if (!$process_refs) {
$daemons = $this->loadRunningDaemons(); $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 { } else {
$daemons = $this->loadAllRunningDaemons(); $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; return 1;
} }
$status = 0;
$table = id(new PhutilConsoleTable()) $table = id(new PhutilConsoleTable())
->addColumns(array( ->addColumns(
'id' => array( array(
'title' => pht('Log'),
),
'daemonID' => array(
'title' => pht('Daemon'),
),
'host' => array(
'title' => pht('Host'),
),
'pid' => array( 'pid' => array(
'title' => pht('Overseer'), 'title' => pht('PID'),
), ),
'started' => array( 'command' => array(
'title' => pht('Started'), 'title' => pht('Command'),
),
'daemon' => array(
'title' => pht('Class'),
),
'argv' => array(
'title' => pht('Arguments'),
), ),
)); ));
foreach ($daemons as $daemon) { foreach ($process_refs as $process_ref) {
if ($daemon instanceof PhabricatorDaemonLog) { $table->addRow(
$table->addRow(array( array(
'id' => $daemon->getID(), 'pid' => $process_ref->getPID(),
'daemonID' => $daemon->getDaemonID(), 'command' => $process_ref->getCommand(),
'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()),
)); ));
} 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()),
));
}
} }
$table->draw(); $table->draw();
return 0;
} }
} }

View File

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

View File

@@ -12,11 +12,6 @@ abstract class PhabricatorDaemonManagementWorkflow
->selectSymbolsWithoutLoading(); ->selectSymbolsWithoutLoading();
} }
final protected function getPIDDirectory() {
$path = PhabricatorEnv::getEnvConfig('phd.pid-directory');
return $this->getControlDirectory($path);
}
final protected function getLogDirectory() { final protected function getLogDirectory() {
$path = PhabricatorEnv::getEnvConfig('phd.log-directory'); $path = PhabricatorEnv::getEnvConfig('phd.log-directory');
return $this->getControlDirectory($path); return $this->getControlDirectory($path);
@@ -30,56 +25,16 @@ abstract class PhabricatorDaemonManagementWorkflow
pht( pht(
"%s requires the directory '%s' to exist, but it does not exist ". "%s requires the directory '%s' to exist, but it does not exist ".
"and could not be created. Create this directory or update ". "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.", "directory.",
'phd', 'phd',
$path, $path,
'phd.pid-directory',
'phd.log-directory')); 'phd.log-directory'));
} }
} }
return $path; 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) { private function findDaemonClass($substring) {
$symbols = $this->loadAvailableDaemonClasses(); $symbols = $this->loadAvailableDaemonClasses();
@@ -169,7 +124,7 @@ abstract class PhabricatorDaemonManagementWorkflow
$flags[] = '--verbose'; $flags[] = '--verbose';
} }
$instance = PhabricatorEnv::getEnvConfig('cluster.instance'); $instance = $this->getInstance();
if ($instance) { if ($instance) {
$flags[] = '-l'; $flags[] = '-l';
$flags[] = $instance; $flags[] = $instance;
@@ -185,14 +140,6 @@ abstract class PhabricatorDaemonManagementWorkflow
$config['log'] = $this->getLogDirectory().'/daemons.log'; $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; $config['daemons'] = $daemons;
$command = csprintf('./phd-daemon %Ls', $flags); $command = csprintf('./phd-daemon %Ls', $flags);
@@ -324,28 +271,31 @@ abstract class PhabricatorDaemonManagementWorkflow
$console = PhutilConsole::getConsole(); $console = PhutilConsole::getConsole();
if (!idx($options, 'force')) { 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 fprintf(STDERR, '%s', "\n");
// running; check that we actually have active daemons before failing. foreach ($process_refs as $process_ref) {
foreach ($running as $daemon) { fprintf(
if ($daemon->isRunning()) { STDERR,
$message = pht( '%s',
"phd start: Unable to start daemons because daemons are already ". tsprintf(
"running.\n\n". " %s %s\n",
"You can view running daemons with '%s'.\n". $process_ref->getPID(),
"You can stop running daemons with '%s'.\n". $process_ref->getCommand()));
"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");
$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; return 0;
} }
final protected function executeStopCommand( final protected function executeStopCommand(array $options) {
array $pids,
array $options) {
$console = PhutilConsole::getConsole();
$grace_period = idx($options, 'graceful', 15); $grace_period = idx($options, 'graceful', 15);
$force = idx($options, 'force'); $force = idx($options, 'force');
$gently = idx($options, 'gently');
if ($gently && $force) { $query = id(new PhutilProcessQuery())
throw new PhutilArgumentUsageException( ->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( pht(
'You can not specify conflicting options %s and %s together.', 'There are no running daemons for the current instance ("%s"). '.
'--gently', 'Use "--force" to stop daemons for all instances.',
'--force')); $instance));
} else {
$this->logInfo(
pht('NO DAEMONS'),
pht('There are no running daemons.'));
} }
$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; return 0;
} }
$stop_pids = $this->selectDaemonPIDs($daemons, $pids); $process_refs = mpull($process_refs, null, 'getPID');
if (!$stop_pids) { $stop_pids = array_keys($process_refs);
$console->writeErr("%s\n", pht('No daemons to kill.')); $live_pids = $this->sendStopSignals($stop_pids, $grace_period);
return 0;
$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(
'Stopped PID %d ("%s")',
$dead_pid,
$dead_ref->getCommand()));
} }
$survivors = $this->sendStopSignals($stop_pids, $grace_period); foreach ($live_pids as $live_pid) {
$live_ref = $process_refs[$live_pid];
// Try to clean up PID files for daemons we killed. $this->logFail(
$remove = array(); pht('SURVIVED'),
foreach ($daemons as $daemon) { pht(
$pid = $daemon->getPID(); 'Unable to stop PID %d ("%s").',
if (empty($stop_pids[$pid])) { $live_pid,
// We did not try to stop this overseer. $live_ref->getCommand()));
continue;
} }
if (isset($survivors[$pid])) { if ($live_pids) {
// We weren't able to stop this overseer. $this->logWarn(
continue; pht('SURVIVORS'),
} pht(
'Unable to stop all daemon processes. You may need to run this '.
if (!$daemon->getPIDFile()) { 'command as root with "sudo".'));
// 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);
} }
return 0; return 0;
} }
final protected function executeReloadCommand(array $pids) { 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; return 0;
} }
$reload_pids = $this->selectDaemonPIDs($daemons, $pids); foreach ($process_refs as $process_ref) {
if (!$reload_pids) { $pid = $process_ref->getPID();
$console->writeErr(
"%s\n",
pht('No daemons to reload.'));
return 0;
}
foreach ($reload_pids as $pid) { $this->logInfo(
$console->writeOut( pht('RELOAD'),
"%s\n",
pht('Reloading process %d...', $pid)); pht('Reloading process %d...', $pid));
posix_kill($pid, SIGHUP); posix_kill($pid, SIGHUP);
} }
return 0; 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) { private function sendStopSignals($pids, $grace_period) {
// If we're doing a graceful shutdown, try SIGINT first. // If we're doing a graceful shutdown, try SIGINT first.
if ($grace_period) { if ($grace_period) {
@@ -674,4 +591,21 @@ abstract class PhabricatorDaemonManagementWorkflow
return $select_pids; 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; break;
} }
$source_path = $request->getValue('sourcePath');
$source_path = $this->normalizeSourcePath($source_path);
$diff_data_dict = array( $diff_data_dict = array(
'sourcePath' => $request->getValue('sourcePath'), 'sourcePath' => $source_path,
'sourceMachine' => $request->getValue('sourceMachine'), 'sourceMachine' => $request->getValue('sourceMachine'),
'branch' => $request->getValue('branch'), 'branch' => $request->getValue('branch'),
'creationMethod' => $request->getValue('creationMethod'), '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']); $map['createdEnd']);
} }
if ($map['modifiedStart'] || $map['modifiedEnd']) {
$query->withUpdatedEpochBetween(
$map['modifiedStart'],
$map['modifiedEnd']);
}
return $query; return $query;
} }
@@ -100,6 +106,18 @@ final class DifferentialRevisionSearchEngine
->setKey('createdEnd') ->setKey('createdEnd')
->setDescription( ->setDescription(
pht('Find revisions created at or before a particular time.')), 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?', 'repositoryPHID' => 'phid?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'authorPHID' => array( 'authorPHID' => array(
'columns' => array('authorPHID', 'status'), 'columns' => array('authorPHID', 'status'),
), ),
@@ -131,6 +126,9 @@ final class DifferentialRevision extends DifferentialDAO
'key_status' => array( 'key_status' => array(
'columns' => array('status', 'phid'), 'columns' => array('status', 'phid'),
), ),
'key_modified' => array(
'columns' => array('dateModified'),
),
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }

View File

@@ -7,17 +7,6 @@ abstract class DiffusionQueryConduitAPIMethod
return true; 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 $diffusionRequest;
private $repository; private $repository;

View File

@@ -145,9 +145,22 @@ final class DiffusionRepositoryController extends DiffusionController {
->setRight(array($this->branchButton, $actions_button, $clone_button)) ->setRight(array($this->branchButton, $actions_button, $clone_button))
->addClass('diffusion-action-bar'); ->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()) $view = id(new PHUITwoColumnView())
->setHeader($header) ->setHeader($header)
->setFooter(array( ->setFooter(
array(
$status_view,
$bar, $bar,
$description, $description,
$content, $content,
@@ -327,6 +340,8 @@ final class DiffusionRepositoryController extends DiffusionController {
if (!$repository->isTracked()) { if (!$repository->isTracked()) {
$header->setStatus('fa-ban', 'dark', pht('Inactive')); $header->setStatus('fa-ban', 'dark', pht('Inactive'));
} else if ($repository->isReadOnly()) {
$header->setStatus('fa-wrench', 'indigo', pht('Under Maintenance'));
} else if ($repository->isImporting()) { } else if ($repository->isImporting()) {
$ratio = $repository->loadImportProgress(); $ratio = $repository->loadImportProgress();
$percentage = sprintf('%.2f%%', 100 * $ratio); $percentage = sprintf('%.2f%%', 100 * $ratio);

View File

@@ -17,32 +17,31 @@ final class DiffusionRepositoryEditDeleteController
->setRepository($repository) ->setRepository($repository)
->getPanelURI(); ->getPanelURI();
$dialog = new AphrontDialogView(); $doc_uri = PhabricatorEnv::getDoclink(
$text_1 = pht( 'Permanently Destroying Data');
'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),
));
return $this->newDialog() return $this->newDialog()
->setTitle(pht('Really want to delete the repository?')) ->setTitle(pht('Delete Repository'))
->appendChild($body) ->appendParagraph(
->addCancelButton($panel_uri, pht('Okay')); 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 ($is_push) {
if ($repository->isReadOnly()) {
return new PhabricatorVCSResponse(
503,
$repository->getReadOnlyMessageForDisplay());
}
$can_write = $can_write =
$repository->canServeProtocol($proto_https, true) || $repository->canServeProtocol($proto_https, true) ||
$repository->canServeProtocol($proto_http, true); $repository->canServeProtocol($proto_http, true);

View File

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

View File

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

View File

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

View File

@@ -47,7 +47,7 @@ final class DiffusionPatternSearchView extends DiffusionView {
$offset = $match[1]; $offset = $match[1];
if ($cursor != $offset) { if ($cursor != $offset) {
$output[] = array( $output[] = array(
'text' => substr($string, $cursor, $offset), 'text' => substr($string, $cursor, ($offset - $cursor)),
'highlight' => false, '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']); $query->withOwnerPHIDs($map['ownerPHIDs']);
} }
if ($map['resourcePHIDs']) {
$query->withResourcePHIDs($map['resourcePHIDs']);
}
return $query; return $query;
} }
@@ -58,6 +62,11 @@ final class DrydockLeaseSearchEngine
->setKey('ownerPHIDs') ->setKey('ownerPHIDs')
->setAliases(array('owner', 'owners', 'ownerPHID')) ->setAliases(array('owner', 'owners', 'ownerPHID'))
->setDescription(pht('Search leases by owner.')), ->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) { if ($this->ids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'id IN (%Ld)', 'resource.id IN (%Ld)',
$this->ids); $this->ids);
} }
if ($this->phids !== null) { if ($this->phids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'phid IN (%Ls)', 'resource.phid IN (%Ls)',
$this->phids); $this->phids);
} }
if ($this->types !== null) { if ($this->types !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'type IN (%Ls)', 'resource.type IN (%Ls)',
$this->types); $this->types);
} }
if ($this->statuses !== null) { if ($this->statuses !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'status IN (%Ls)', 'resource.status IN (%Ls)',
$this->statuses); $this->statuses);
} }
if ($this->blueprintPHIDs !== null) { if ($this->blueprintPHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'blueprintPHID IN (%Ls)', 'resource.blueprintPHID IN (%Ls)',
$this->blueprintPHIDs); $this->blueprintPHIDs);
} }
if ($this->datasourceQuery !== null) { if ($this->datasourceQuery !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'name LIKE %>', 'resource.name LIKE %>',
$this->datasourceQuery); $this->datasourceQuery);
} }
return $where; return $where;
} }
protected function getPrimaryTableAlias() {
return 'resource';
}
} }

View File

@@ -40,6 +40,10 @@ final class DrydockResourceSearchEngine
$query->withStatuses($map['statuses']); $query->withStatuses($map['statuses']);
} }
if ($map['blueprintPHIDs']) {
$query->withBlueprintPHIDs($map['blueprintPHIDs']);
}
return $query; return $query;
} }
@@ -49,6 +53,12 @@ final class DrydockResourceSearchEngine
->setLabel(pht('Statuses')) ->setLabel(pht('Statuses'))
->setKey('statuses') ->setKey('statuses')
->setOptions(DrydockResourceStatus::getStatusMap()), ->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 <?php
final class DrydockResource extends DrydockDAO final class DrydockResource extends DrydockDAO
implements PhabricatorPolicyInterface { implements
PhabricatorPolicyInterface,
PhabricatorConduitResultInterface {
protected $id; protected $id;
protected $phid; protected $phid;
@@ -340,4 +342,38 @@ final class DrydockResource extends DrydockDAO
public function describeAutomaticCapability($capability) { public function describeAutomaticCapability($capability) {
return pht('Resources inherit the policies of their blueprints.'); 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'; const TYPECONST = 'files.outbound';
public function getActionConstant() {
return self::TYPECONST;
}
public function getScoreThreshold() { public function getScoreThreshold() {
return 60 / phutil_units('1 hour in seconds'); return 60 / phutil_units('1 hour in seconds');
} }

View File

@@ -4,41 +4,25 @@ final class PhabricatorFilesManagementCompactWorkflow
extends PhabricatorFilesManagementWorkflow { extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() { protected function didConstruct() {
$arguments = $this->newIteratorArguments();
$arguments[] = array(
'name' => 'dry-run',
'help' => pht('Show what would be compacted.'),
);
$this $this
->setName('compact') ->setName('compact')
->setSynopsis( ->setSynopsis(
pht( pht(
'Merge identical files to share the same storage. In some cases, '. 'Merge identical files to share the same storage. In some cases, '.
'this can repair files with missing data.')) 'this can repair files with missing data.'))
->setArguments( ->setArguments($arguments);
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,
),
));
} }
public function execute(PhutilArgumentParser $args) { public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole(); $console = PhutilConsole::getConsole();
$iterator = $this->buildIterator($args); $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'); $is_dry_run = $args->getArg('dry-run');
foreach ($iterator as $file) { foreach ($iterator as $file) {

View File

@@ -4,36 +4,22 @@ final class PhabricatorFilesManagementCycleWorkflow
extends PhabricatorFilesManagementWorkflow { extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() { protected function didConstruct() {
$arguments = $this->newIteratorArguments();
$arguments[] = array(
'name' => 'key',
'param' => 'keyname',
'help' => pht('Select a specific storage key to cycle to.'),
);
$this $this
->setName('cycle') ->setName('cycle')
->setSynopsis( ->setSynopsis(
pht('Cycle master key for encrypted files.')) pht('Cycle master key for encrypted files.'))
->setArguments( ->setArguments($arguments);
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,
),
));
} }
public function execute(PhutilArgumentParser $args) { public function execute(PhutilArgumentParser $args) {
$iterator = $this->buildIterator($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(); $format_map = PhabricatorFileStorageFormat::getAllFormats();
$engines = PhabricatorFileStorageEngine::loadAllEngines(); $engines = PhabricatorFileStorageEngine::loadAllEngines();

View File

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

View File

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

View File

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

View File

@@ -4,45 +4,33 @@ final class PhabricatorFilesManagementRebuildWorkflow
extends PhabricatorFilesManagementWorkflow { extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() { 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 $this
->setName('rebuild') ->setName('rebuild')
->setSynopsis(pht('Rebuild metadata of old files.')) ->setSynopsis(pht('Rebuild metadata of old files.'))
->setArguments( ->setArguments($arguments);
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,
),
));
} }
public function execute(PhutilArgumentParser $args) { public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole(); $console = PhutilConsole::getConsole();
$iterator = $this->buildIterator($args); $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( $update = array(
'mime' => $args->getArg('rebuild-mime'), 'mime' => $args->getArg('rebuild-mime'),

View File

@@ -3,11 +3,30 @@
abstract class PhabricatorFilesManagementWorkflow abstract class PhabricatorFilesManagementWorkflow
extends PhabricatorManagementWorkflow { 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) { protected function buildIterator(PhutilArgumentParser $args) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$names = $args->getArg('names');
$is_all = $args->getArg('all'); $is_all = $args->getArg('all');
$names = $args->getArg('names');
$from_engine = $args->getArg('from-engine'); $from_engine = $args->getArg('from-engine');
$any_constraint = ($from_engine || $names); $any_constraint = ($from_engine || $names);
@@ -15,15 +34,16 @@ abstract class PhabricatorFilesManagementWorkflow
if (!$is_all && !$any_constraint) { if (!$is_all && !$any_constraint) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
pht( pht(
'Use "--all" to migrate all files, or choose files to migrate '. 'Specify which files to operate on, or use "--all" to operate on '.
'with "--names" or "--from-engine".')); 'all files.'));
} }
if ($is_all && $any_constraint) { if ($is_all && $any_constraint) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
pht( pht(
'You can not migrate all files with "--all" and also migrate only '. 'You can not operate on all files with "--all" and also operate '.
'a subset of files with "--from-engine" or "--names".')); '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 // 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_COLOR = 'color';
const GROUP_NONE = 'none'; const GROUP_NONE = 'none';
private $ids;
private $ownerPHIDs; private $ownerPHIDs;
private $types; private $types;
private $objectPHIDs; private $objectPHIDs;
@@ -15,6 +16,11 @@ final class PhabricatorFlagQuery
private $needHandles; private $needHandles;
private $needObjects; private $needObjects;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withOwnerPHIDs(array $owner_phids) { public function withOwnerPHIDs(array $owner_phids) {
$this->ownerPHIDs = $owner_phids; $this->ownerPHIDs = $owner_phids;
return $this; return $this;
@@ -126,6 +132,13 @@ final class PhabricatorFlagQuery
protected function buildWhereClause(AphrontDatabaseConnection $conn) { protected function buildWhereClause(AphrontDatabaseConnection $conn) {
$where = array(); $where = array();
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'flag.id IN (%Ld)',
$this->ids);
}
if ($this->ownerPHIDs) { if ($this->ownerPHIDs) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,

View File

@@ -24,26 +24,27 @@ final class HarbormasterBuildableActionController
$issuable = array(); $issuable = array();
foreach ($buildable->getBuilds() as $build) { $builds = $buildable->getBuilds();
foreach ($builds as $key => $build) {
switch ($action) { switch ($action) {
case HarbormasterBuildCommand::COMMAND_RESTART: case HarbormasterBuildCommand::COMMAND_RESTART:
if ($build->canRestartBuild()) { if ($build->canRestartBuild()) {
$issuable[] = $build; $issuable[$key] = $build;
} }
break; break;
case HarbormasterBuildCommand::COMMAND_PAUSE: case HarbormasterBuildCommand::COMMAND_PAUSE:
if ($build->canPauseBuild()) { if ($build->canPauseBuild()) {
$issuable[] = $build; $issuable[$key] = $build;
} }
break; break;
case HarbormasterBuildCommand::COMMAND_RESUME: case HarbormasterBuildCommand::COMMAND_RESUME:
if ($build->canResumeBuild()) { if ($build->canResumeBuild()) {
$issuable[] = $build; $issuable[$key] = $build;
} }
break; break;
case HarbormasterBuildCommand::COMMAND_ABORT: case HarbormasterBuildCommand::COMMAND_ABORT:
if ($build->canAbortBuild()) { if ($build->canAbortBuild()) {
$issuable[] = $build; $issuable[$key] = $build;
} }
break; break;
default: 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(); $return_uri = '/'.$buildable->getMonogram();
if ($request->isDialogFormPost() && $issuable) { if ($request->isDialogFormPost() && $issuable) {
$editor = id(new HarbormasterBuildableTransactionEditor()) $editor = id(new HarbormasterBuildableTransactionEditor())
@@ -89,34 +98,137 @@ final class HarbormasterBuildableActionController
return id(new AphrontRedirectResponse())->setURI($return_uri); return id(new AphrontRedirectResponse())->setURI($return_uri);
} }
$width = AphrontDialogView::WIDTH_DEFAULT;
switch ($action) { switch ($action) {
case HarbormasterBuildCommand::COMMAND_RESTART: 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) { if ($issuable) {
$title = pht('Really restart builds?'); $title = pht('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?');
}
$submit = pht('Restart Builds'); $submit = pht('Restart Builds');
} else { } else {
$title = pht('Unable to Restart Builds'); $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) { 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 { } else {
$body = pht('No builds can be restarted.'); if ($restricted) {
$body[] = pht('You do not have permission to restart any builds.');
} else {
$body[] = pht('No builds can be restarted.');
} }
} }
break; break;
case HarbormasterBuildCommand::COMMAND_PAUSE: case HarbormasterBuildCommand::COMMAND_PAUSE:
if ($issuable) { if ($issuable) {
@@ -193,6 +305,7 @@ final class HarbormasterBuildableActionController
$dialog = id(new AphrontDialogView()) $dialog = id(new AphrontDialogView())
->setUser($viewer) ->setUser($viewer)
->setWidth($width)
->setTitle($title) ->setTitle($title)
->appendChild($body) ->appendChild($body)
->addCancelButton($return_uri); ->addCancelButton($return_uri);

View File

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

View File

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

View File

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

View File

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

View File

@@ -264,6 +264,7 @@ EODOCS
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
$commit_type = ManiphestTaskHasCommitEdgeType::EDGECONST;
$src_phid = $object->getPHID(); $src_phid = $object->getPHID();
if ($src_phid) { if ($src_phid) {
@@ -273,6 +274,7 @@ EODOCS
array( array(
$parent_type, $parent_type,
$subtask_type, $subtask_type,
$commit_type,
)); ));
$edge_query->execute(); $edge_query->execute();
@@ -283,9 +285,14 @@ EODOCS
$subtask_phids = $edge_query->getDestinationPHIDs( $subtask_phids = $edge_query->getDestinationPHIDs(
array($src_phid), array($src_phid),
array($subtask_type)); array($subtask_type));
$commit_phids = $edge_query->getDestinationPHIDs(
array($src_phid),
array($commit_type));
} else { } else {
$parent_phids = array(); $parent_phids = array();
$subtask_phids = array(); $subtask_phids = array();
$commit_phids = array();
} }
$fields[] = id(new PhabricatorHandlesEditField()) $fields[] = id(new PhabricatorHandlesEditField())
@@ -310,7 +317,19 @@ EODOCS
->setIsFormField(false) ->setIsFormField(false)
->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $subtask_type) ->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; return $fields;
} }
@@ -437,7 +456,7 @@ EODOCS
$engine = id(new PhabricatorBoardResponseEngine()) $engine = id(new PhabricatorBoardResponseEngine())
->setViewer($viewer) ->setViewer($viewer)
->setBoardPHID($board_phid) ->setBoardPHID($board_phid)
->setObjectPHID($object_phid) ->setUpdatePHIDs(array($object_phid))
->setVisiblePHIDs($visible_phids); ->setVisiblePHIDs($visible_phids);
if ($ordering) { if ($ordering) {

View File

@@ -3,6 +3,7 @@
final class ManiphestTransactionEditor final class ManiphestTransactionEditor
extends PhabricatorApplicationTransactionEditor { extends PhabricatorApplicationTransactionEditor {
private $oldProjectPHIDs;
private $moreValidationErrors = array(); private $moreValidationErrors = array();
public function getEditorApplicationClass() { public function getEditorApplicationClass() {
@@ -378,6 +379,11 @@ final class ManiphestTransactionEditor
} }
} }
$send_notifications = PhabricatorNotificationClient::isEnabled();
if ($send_notifications) {
$this->oldProjectPHIDs = $this->loadProjectPHIDs($object);
}
return $results; return $results;
} }
@@ -859,4 +865,71 @@ final class ManiphestTransactionEditor
return array_values($phid_list); 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 { final class PhabricatorMetaMTAErrorMailAction extends PhabricatorSystemAction {
public function getActionConstant() { const TYPECONST = 'email.error';
return 'email.error';
}
public function getScoreThreshold() { public function getScoreThreshold() {
return 6 / phutil_units('1 hour in seconds'); 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); $header = $this->buildHeaderView($client);
$actions = $this->buildActionView($client);
$properties = $this->buildPropertyListView($client); $properties = $this->buildPropertyListView($client);
$properties->setActionList($actions);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs()
$crumbs->addTextCrumb($client->getName()); ->addTextCrumb($client->getName())
->setBorder(true);
$timeline = $this->buildTransactionTimeline( $timeline = $this->buildTransactionTimeline(
$client, $client,
@@ -28,19 +27,27 @@ final class PhabricatorOAuthClientViewController
$timeline->setShouldTerminate(true); $timeline->setShouldTerminate(true);
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
->setHeader($header) ->setHeaderText(pht('Details'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($properties); ->addPropertyList($properties);
$title = pht('OAuth Application: %s', $client->getName()); $title = pht('OAuth Application: %s', $client->getName());
return $this->newPage() $curtain = $this->buildCurtain($client);
->setCrumbs($crumbs)
->setTitle($title) $columns = id(new PHUITwoColumnView())
->appendChild( ->setHeader($header)
->setCurtain($curtain)
->setMainColumn(
array( array(
$box, $box,
$timeline, $timeline,
)); ));
return $this->newPage()
->setCrumbs($crumbs)
->setTitle($title)
->appendChild($columns);
} }
private function buildHeaderView(PhabricatorOAuthServerClient $client) { private function buildHeaderView(PhabricatorOAuthServerClient $client) {
@@ -60,8 +67,9 @@ final class PhabricatorOAuthClientViewController
return $header; return $header;
} }
private function buildActionView(PhabricatorOAuthServerClient $client) { private function buildCurtain(PhabricatorOAuthServerClient $client) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$actions = array();
$can_edit = PhabricatorPolicyFilter::hasCapability( $can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer, $viewer,
@@ -70,24 +78,19 @@ final class PhabricatorOAuthClientViewController
$id = $client->getID(); $id = $client->getID();
$view = id(new PhabricatorActionListView()) $actions[] = id(new PhabricatorActionView())
->setUser($viewer);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Application')) ->setName(pht('Edit Application'))
->setIcon('fa-pencil') ->setIcon('fa-pencil')
->setWorkflow(!$can_edit) ->setWorkflow(!$can_edit)
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setHref($client->getEditURI())); ->setHref($client->getEditURI());
$view->addAction( $actions[] = id(new PhabricatorActionView())
id(new PhabricatorActionView())
->setName(pht('Show Application Secret')) ->setName(pht('Show Application Secret'))
->setIcon('fa-eye') ->setIcon('fa-eye')
->setHref($this->getApplicationURI("client/secret/{$id}/")) ->setHref($this->getApplicationURI("client/secret/{$id}/"))
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setWorkflow(true)); ->setWorkflow(true);
$is_disabled = $client->getIsDisabled(); $is_disabled = $client->getIsDisabled();
if ($is_disabled) { if ($is_disabled) {
@@ -100,22 +103,26 @@ final class PhabricatorOAuthClientViewController
$disable_uri = $this->getApplicationURI("client/disable/{$id}/"); $disable_uri = $this->getApplicationURI("client/disable/{$id}/");
$view->addAction( $actions[] = id(new PhabricatorActionView())
id(new PhabricatorActionView())
->setName($disable_text) ->setName($disable_text)
->setIcon($disable_icon) ->setIcon($disable_icon)
->setWorkflow(true) ->setWorkflow(true)
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setHref($disable_uri)); ->setHref($disable_uri);
$view->addAction( $actions[] = id(new PhabricatorActionView())
id(new PhabricatorActionView())
->setName(pht('Generate Test Token')) ->setName(pht('Generate Test Token'))
->setIcon('fa-plus') ->setIcon('fa-plus')
->setWorkflow(true) ->setWorkflow(true)
->setHref($this->getApplicationURI("client/test/{$id}/"))); ->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) { private function buildPropertyListView(PhabricatorOAuthServerClient $client) {
@@ -132,10 +139,6 @@ final class PhabricatorOAuthClientViewController
pht('Redirect URI'), pht('Redirect URI'),
$client->getRedirectURI()); $client->getRedirectURI());
$view->addProperty(
pht('Created'),
phabricator_datetime($client->getDateCreated(), $viewer));
return $view; return $view;
} }
} }

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