mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-15 18:10:53 +01:00
(stable) Promote 2017 Week 11
This commit is contained in:
commit
da80ed415f
38 changed files with 844 additions and 155 deletions
4
externals/phpmailer/class.phpmailer-lite.php
vendored
4
externals/phpmailer/class.phpmailer-lite.php
vendored
|
@ -656,6 +656,10 @@ class PHPMailerLite {
|
|||
$addr_str .= implode(', ', $addresses);
|
||||
$addr_str .= $this->LE;
|
||||
|
||||
// NOTE: This is a narrow hack to fix an issue with 1000+ characters of
|
||||
// recipients, described in T12372.
|
||||
$addr_str = wordwrap($addr_str, 75, "\n ");
|
||||
|
||||
return $addr_str;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'conpherence.pkg.css' => '6875302f',
|
||||
'conpherence.pkg.css' => '32f2c040',
|
||||
'conpherence.pkg.js' => '6249a1cf',
|
||||
'core.pkg.css' => '35645dec',
|
||||
'core.pkg.css' => '491d7018',
|
||||
'core.pkg.js' => '1fa7c0c5',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '90b30783',
|
||||
|
@ -21,7 +21,7 @@ return array(
|
|||
'maniphest.pkg.js' => '5ab2753f',
|
||||
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
|
||||
'rsrc/css/aphront/dark-console.css' => 'f54bf286',
|
||||
'rsrc/css/aphront/dialog-view.css' => '5e5aa60b',
|
||||
'rsrc/css/aphront/dialog-view.css' => '685c7e2d',
|
||||
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
|
||||
'rsrc/css/aphront/multi-column.css' => '84cc6640',
|
||||
'rsrc/css/aphront/notification.css' => '3f6c89c9',
|
||||
|
@ -48,7 +48,7 @@ return array(
|
|||
'rsrc/css/application/conpherence/durable-column.css' => '292c71f0',
|
||||
'rsrc/css/application/conpherence/header-pane.css' => 'db93ebc6',
|
||||
'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c',
|
||||
'rsrc/css/application/conpherence/message-pane.css' => 'b085d40d',
|
||||
'rsrc/css/application/conpherence/message-pane.css' => 'd1fc13e1',
|
||||
'rsrc/css/application/conpherence/notification.css' => '965db05b',
|
||||
'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02',
|
||||
'rsrc/css/application/conpherence/transaction.css' => '85129c68',
|
||||
|
@ -146,7 +146,7 @@ return array(
|
|||
'rsrc/css/phui/phui-document.css' => 'c32e8dec',
|
||||
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
|
||||
'rsrc/css/phui/phui-fontkit.css' => 'b78a0059',
|
||||
'rsrc/css/phui/phui-form-view.css' => 'adca31ce',
|
||||
'rsrc/css/phui/phui-form-view.css' => 'cf198e10',
|
||||
'rsrc/css/phui/phui-form.css' => 'b62c01d8',
|
||||
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
|
||||
'rsrc/css/phui/phui-header-view.css' => 'fef6a54e',
|
||||
|
@ -158,7 +158,7 @@ return array(
|
|||
'rsrc/css/phui/phui-info-view.css' => 'ec92802a',
|
||||
'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0',
|
||||
'rsrc/css/phui/phui-lightbox.css' => '0a035e40',
|
||||
'rsrc/css/phui/phui-list.css' => '9da2aa00',
|
||||
'rsrc/css/phui/phui-list.css' => 'a3ec3cf1',
|
||||
'rsrc/css/phui/phui-object-box.css' => '8b289e3d',
|
||||
'rsrc/css/phui/phui-pager.css' => '77d8a794',
|
||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
||||
|
@ -548,7 +548,7 @@ return array(
|
|||
'almanac-css' => 'dbb9b3af',
|
||||
'aphront-bars' => '231ac33c',
|
||||
'aphront-dark-console-css' => 'f54bf286',
|
||||
'aphront-dialog-view-css' => '5e5aa60b',
|
||||
'aphront-dialog-view-css' => '685c7e2d',
|
||||
'aphront-list-filter-view-css' => '5d6f0526',
|
||||
'aphront-multi-column-view-css' => '84cc6640',
|
||||
'aphront-panel-view-css' => '8427b78d',
|
||||
|
@ -566,7 +566,7 @@ return array(
|
|||
'conpherence-durable-column-view' => '292c71f0',
|
||||
'conpherence-header-pane-css' => 'db93ebc6',
|
||||
'conpherence-menu-css' => '3d8e5c9c',
|
||||
'conpherence-message-pane-css' => 'b085d40d',
|
||||
'conpherence-message-pane-css' => 'd1fc13e1',
|
||||
'conpherence-notification-css' => '965db05b',
|
||||
'conpherence-participant-pane-css' => '604a8b02',
|
||||
'conpherence-thread-manager' => 'c8b5ee6f',
|
||||
|
@ -859,7 +859,7 @@ return array(
|
|||
'phui-font-icon-base-css' => '870a7360',
|
||||
'phui-fontkit-css' => 'b78a0059',
|
||||
'phui-form-css' => 'b62c01d8',
|
||||
'phui-form-view-css' => 'adca31ce',
|
||||
'phui-form-view-css' => 'cf198e10',
|
||||
'phui-head-thing-view-css' => 'fd311e5f',
|
||||
'phui-header-view-css' => 'fef6a54e',
|
||||
'phui-hovercard' => '1bd28176',
|
||||
|
@ -872,7 +872,7 @@ return array(
|
|||
'phui-inline-comment-view-css' => 'be663c95',
|
||||
'phui-invisible-character-view-css' => '6993d9f0',
|
||||
'phui-lightbox-css' => '0a035e40',
|
||||
'phui-list-view-css' => '9da2aa00',
|
||||
'phui-list-view-css' => 'a3ec3cf1',
|
||||
'phui-object-box-css' => '8b289e3d',
|
||||
'phui-oi-big-ui-css' => '19f9369b',
|
||||
'phui-oi-color-css' => 'cd2b9b77',
|
||||
|
|
9
resources/sql/autopatches/20170313.reviewers.01.sql
Normal file
9
resources/sql/autopatches/20170313.reviewers.01.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE {$NAMESPACE}_differential.differential_reviewer (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
revisionPHID VARBINARY(64) NOT NULL,
|
||||
reviewerPHID VARBINARY(64) NOT NULL,
|
||||
reviewerStatus VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_revision` (revisionPHID, reviewerPHID)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -491,6 +491,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevertPlanCommitMessageField' => 'applications/differential/field/DifferentialRevertPlanCommitMessageField.php',
|
||||
'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php',
|
||||
'DifferentialReviewedByCommitMessageField' => 'applications/differential/field/DifferentialReviewedByCommitMessageField.php',
|
||||
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
|
||||
'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php',
|
||||
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
|
||||
'DifferentialReviewerProxy' => 'applications/differential/storage/DifferentialReviewerProxy.php',
|
||||
|
@ -2495,6 +2496,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php',
|
||||
'PhabricatorDashboardIconSet' => 'applications/dashboard/icon/PhabricatorDashboardIconSet.php',
|
||||
'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php',
|
||||
'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/PhabricatorDashboardInstallController.php',
|
||||
'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php',
|
||||
'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php',
|
||||
'PhabricatorDashboardManageController' => 'applications/dashboard/controller/PhabricatorDashboardManageController.php',
|
||||
|
@ -3761,6 +3763,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php',
|
||||
'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php',
|
||||
'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php',
|
||||
'PhabricatorSearchConstraintException' => 'applications/search/exception/PhabricatorSearchConstraintException.php',
|
||||
'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php',
|
||||
'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php',
|
||||
'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php',
|
||||
|
@ -4064,6 +4067,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php',
|
||||
'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
|
||||
'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
|
||||
'PhabricatorUserBadgesCacheType' => 'applications/people/cache/PhabricatorUserBadgesCacheType.php',
|
||||
'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php',
|
||||
'PhabricatorUserCache' => 'applications/people/storage/PhabricatorUserCache.php',
|
||||
'PhabricatorUserCacheType' => 'applications/people/cache/PhabricatorUserCacheType.php',
|
||||
|
@ -5247,6 +5251,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevertPlanCommitMessageField' => 'DifferentialCommitMessageCustomField',
|
||||
'DifferentialRevertPlanField' => 'DifferentialStoredCustomField',
|
||||
'DifferentialReviewedByCommitMessageField' => 'DifferentialCommitMessageField',
|
||||
'DifferentialReviewer' => 'DifferentialDAO',
|
||||
'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
|
||||
'DifferentialReviewerProxy' => 'Phobject',
|
||||
|
@ -7560,6 +7565,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDashboardEditController' => 'PhabricatorDashboardController',
|
||||
'PhabricatorDashboardIconSet' => 'PhabricatorIconSet',
|
||||
'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO',
|
||||
'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController',
|
||||
'PhabricatorDashboardLayoutConfig' => 'Phobject',
|
||||
'PhabricatorDashboardListController' => 'PhabricatorDashboardController',
|
||||
'PhabricatorDashboardManageController' => 'PhabricatorDashboardProfileController',
|
||||
|
@ -9068,6 +9074,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSearchBaseController' => 'PhabricatorController',
|
||||
'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField',
|
||||
'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorSearchConstraintException' => 'Exception',
|
||||
'PhabricatorSearchController' => 'PhabricatorSearchBaseController',
|
||||
'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField',
|
||||
'PhabricatorSearchDAO' => 'PhabricatorLiskDAO',
|
||||
|
@ -9409,6 +9416,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFulltextInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
),
|
||||
'PhabricatorUserBadgesCacheType' => 'PhabricatorUserCacheType',
|
||||
'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField',
|
||||
'PhabricatorUserCache' => 'PhabricatorUserDAO',
|
||||
'PhabricatorUserCacheType' => 'Phobject',
|
||||
|
|
|
@ -306,6 +306,18 @@ final class PhabricatorAuditEditor
|
|||
|
||||
$field_key = DifferentialAuditorsCommitMessageField::FIELDKEY;
|
||||
$phids = idx($result, $field_key, null);
|
||||
|
||||
if (!$phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// If a commit lists its author as an auditor, just pretend it does not.
|
||||
foreach ($phids as $key => $phid) {
|
||||
if ($phid == $commit->getAuthorPHID()) {
|
||||
unset($phids[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$phids) {
|
||||
return array();
|
||||
}
|
||||
|
|
|
@ -118,4 +118,45 @@ final class PhabricatorBadgesEditor
|
|||
return pht('[Badge]');
|
||||
}
|
||||
|
||||
protected function applyFinalEffects(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
||||
$badge_phid = $object->getPHID();
|
||||
$user_phids = array();
|
||||
$clear_everything = false;
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorBadgesBadgeAwardTransaction::TRANSACTIONTYPE:
|
||||
case PhabricatorBadgesBadgeRevokeTransaction::TRANSACTIONTYPE:
|
||||
foreach ($xaction->getNewValue() as $user_phid) {
|
||||
$user_phids[] = $user_phid;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$clear_everything = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($clear_everything) {
|
||||
$awards = id(new PhabricatorBadgesAwardQuery())
|
||||
->setViewer($this->getActor())
|
||||
->withBadgePHIDs(array($badge_phid))
|
||||
->execute();
|
||||
foreach ($awards as $award) {
|
||||
$user_phids[] = $award->getRecipientPHID();
|
||||
}
|
||||
}
|
||||
|
||||
if ($user_phids) {
|
||||
PhabricatorUserCache::clearCaches(
|
||||
PhabricatorUserBadgesCacheType::KEY_BADGES,
|
||||
$user_phids);
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ final class PhabricatorBadgesAwardQuery
|
|||
private $badgePHIDs;
|
||||
private $recipientPHIDs;
|
||||
private $awarderPHIDs;
|
||||
|
||||
private $badgeStatuses = null;
|
||||
|
||||
protected function willFilterPage(array $awards) {
|
||||
$badge_phids = array();
|
||||
|
@ -22,6 +22,11 @@ final class PhabricatorBadgesAwardQuery
|
|||
$badges = mpull($badges, null, 'getPHID');
|
||||
foreach ($awards as $key => $award) {
|
||||
$award_badge = idx($badges, $award->getBadgePHID());
|
||||
if (!$award_badge) {
|
||||
unset($awards[$key]);
|
||||
$this->didRejectResult($award);
|
||||
continue;
|
||||
}
|
||||
$award->attachBadge($award_badge);
|
||||
}
|
||||
|
||||
|
@ -43,6 +48,15 @@ final class PhabricatorBadgesAwardQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withBadgeStatuses(array $statuses) {
|
||||
$this->badgeStatuses = $statuses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function shouldJoinBadge() {
|
||||
return (bool)$this->badgeStatuses;
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
@ -51,33 +65,59 @@ final class PhabricatorBadgesAwardQuery
|
|||
return new PhabricatorBadgesAward();
|
||||
}
|
||||
|
||||
protected function getPrimaryTableAlias() {
|
||||
return 'badges_award';
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->badgePHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'badgePHID IN (%Ls)',
|
||||
'badges_award.badgePHID IN (%Ls)',
|
||||
$this->badgePHIDs);
|
||||
}
|
||||
|
||||
if ($this->recipientPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'recipientPHID IN (%Ls)',
|
||||
'badges_award.recipientPHID IN (%Ls)',
|
||||
$this->recipientPHIDs);
|
||||
}
|
||||
|
||||
if ($this->awarderPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'awarderPHID IN (%Ls)',
|
||||
'badges_award.awarderPHID IN (%Ls)',
|
||||
$this->awarderPHIDs);
|
||||
}
|
||||
|
||||
if ($this->badgeStatuses !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'badges_badge.status IN (%Ls)',
|
||||
$this->badgeStatuses);
|
||||
}
|
||||
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$join = parent::buildJoinClauseParts($conn);
|
||||
$badges = new PhabricatorBadgesBadge();
|
||||
|
||||
if ($this->shouldJoinBadge()) {
|
||||
$join[] = qsprintf(
|
||||
$conn,
|
||||
'JOIN %T badges_badge ON badges_award.badgePHID = badges_badge.phid',
|
||||
$badges->getTableName());
|
||||
}
|
||||
|
||||
return $join;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorBadgesApplication';
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ final class PhabricatorBadgesRecipientsListView extends AphrontView {
|
|||
$award_button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-plus')
|
||||
->setText(pht('Add Recipents'))
|
||||
->setText(pht('Add Recipients'))
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit)
|
||||
->setHref('/badges/recipients/'.$badge->getID().'/add/');
|
||||
|
|
|
@ -158,6 +158,10 @@ final class PhabricatorConfigManagementSetWorkflow
|
|||
$config_type = 'database';
|
||||
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
|
||||
$config_entry->setValue($value);
|
||||
|
||||
// If the entry has been deleted, resurrect it.
|
||||
$config_entry->setIsDeleted(0);
|
||||
|
||||
$config_entry->save();
|
||||
} else {
|
||||
$config_type = 'local';
|
||||
|
|
|
@ -66,6 +66,21 @@ EOTEXT
|
|||
,
|
||||
PhabricatorEnv::getDoclink('Configuring Encryption')));
|
||||
|
||||
$require_mfa_description = $this->deformat(pht(<<<EOTEXT
|
||||
By default, Phabricator allows users to add multi-factor authentication to
|
||||
their accounts, but does not require it. By enabling this option, you can
|
||||
force all users to add at least one authentication factor before they can use
|
||||
their accounts.
|
||||
|
||||
Administrators can query a list of users who do not have MFA configured in
|
||||
{nav People}:
|
||||
|
||||
- **[[ %s | %s ]]**
|
||||
EOTEXT
|
||||
,
|
||||
'/people/?mfa=false',
|
||||
pht('List of Users Without MFA')));
|
||||
|
||||
return array(
|
||||
$this->newOption('security.alternate-file-domain', 'string', null)
|
||||
->setLocked(true)
|
||||
|
@ -132,13 +147,7 @@ EOTEXT
|
|||
->setLocked(true)
|
||||
->setSummary(
|
||||
pht('Require all users to configure multi-factor authentication.'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'By default, Phabricator allows users to add multi-factor '.
|
||||
'authentication to their accounts, but does not require it. '.
|
||||
'By enabling this option, you can force all users to add '.
|
||||
'at least one authentication factor before they can use their '.
|
||||
'accounts.'))
|
||||
->setDescription($require_mfa_description)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
pht('Multi-Factor Required'),
|
||||
|
|
|
@ -30,6 +30,7 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
|
|||
'arrange/(?P<id>\d+)/' => 'PhabricatorDashboardArrangeController',
|
||||
'create/' => 'PhabricatorDashboardEditController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardEditController',
|
||||
'install/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardInstallController',
|
||||
'addpanel/(?P<id>\d+)/' => 'PhabricatorDashboardAddPanelController',
|
||||
'movepanel/(?P<id>\d+)/' => 'PhabricatorDashboardMovePanelController',
|
||||
'removepanel/(?P<id>\d+)/'
|
||||
|
|
|
@ -51,6 +51,14 @@ final class PhabricatorDashboardArrangeController
|
|||
->addClass('dashboard-preview-box')
|
||||
->appendChild($rendered_dashboard);
|
||||
|
||||
$install_button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setText('Install Dashboard')
|
||||
->setIcon('fa-plus')
|
||||
->setWorkflow(true)
|
||||
->setHref($this->getApplicationURI("/install/{$id}/"));
|
||||
$header->addActionLink($install_button);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDashboardInstallController
|
||||
extends PhabricatorDashboardController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$dashboard = id(new PhabricatorDashboardQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$dashboard) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$cancel_uri = $this->getApplicationURI(
|
||||
'view/'.$dashboard->getID().'/');
|
||||
|
||||
$home_app = new PhabricatorHomeApplication();
|
||||
|
||||
$options = array();
|
||||
$options['home'] = array(
|
||||
'personal' =>
|
||||
array(
|
||||
'capability' => PhabricatorPolicyCapability::CAN_VIEW,
|
||||
'application' => $home_app,
|
||||
'name' => pht('Personal Dashboard'),
|
||||
'value' => 'personal',
|
||||
'description' => pht('Places this dashboard as a menu item on home '.
|
||||
'as a personal menu item. It will only be on your personal '.
|
||||
'home.'),
|
||||
),
|
||||
'global' =>
|
||||
array(
|
||||
'capability' => PhabricatorPolicyCapability::CAN_EDIT,
|
||||
'application' => $home_app,
|
||||
'name' => pht('Global Dashboard'),
|
||||
'value' => 'global',
|
||||
'description' => pht('Places this dashboard as a menu item on home '.
|
||||
'as a global menu item. It will be available to all users.'),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
$errors = array();
|
||||
$v_name = null;
|
||||
if ($request->isFormPost()) {
|
||||
$menuitem = new PhabricatorDashboardProfileMenuItem();
|
||||
$dashboard_phid = $dashboard->getPHID();
|
||||
$home = new PhabricatorHomeApplication();
|
||||
$v_name = $request->getStr('name');
|
||||
$v_home = $request->getStr('home');
|
||||
|
||||
if ($v_home) {
|
||||
$application = $options['home'][$v_home]['application'];
|
||||
$capability = $options['home'][$v_home]['capability'];
|
||||
|
||||
$can_edit_home = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$application,
|
||||
$capability);
|
||||
|
||||
if (!$can_edit_home) {
|
||||
$errors[] = pht(
|
||||
'You do not have permission to install a dashboard on home.');
|
||||
}
|
||||
} else {
|
||||
$errors[] = pht(
|
||||
'You must select a destination to install this dashboard.');
|
||||
}
|
||||
|
||||
$v_phid = $viewer->getPHID();
|
||||
if ($v_home == 'global') {
|
||||
$v_phid = null;
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$install = PhabricatorProfileMenuItemConfiguration::initializeNewItem(
|
||||
$home,
|
||||
$menuitem,
|
||||
$v_phid);
|
||||
|
||||
$install->setMenuItemProperty('dashboardPHID', $dashboard_phid);
|
||||
$install->setMenuItemProperty('name', $v_name);
|
||||
$install->setMenuItemOrder(1);
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$editor = id(new PhabricatorProfileMenuEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request);
|
||||
|
||||
$editor->applyTransactions($install, $xactions);
|
||||
|
||||
$view_uri = '/home/menu/view/'.$install->getID().'/';
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||
}
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Menu Label'))
|
||||
->setName('name')
|
||||
->setValue($v_name));
|
||||
|
||||
$radio = id(new AphrontFormRadioButtonControl())
|
||||
->setLabel(pht('Home Menu'))
|
||||
->setName('home');
|
||||
|
||||
foreach ($options['home'] as $type => $option) {
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$option['application'],
|
||||
$option['capability']);
|
||||
if ($can_edit) {
|
||||
$radio->addButton(
|
||||
$option['value'],
|
||||
$option['name'],
|
||||
$option['description']);
|
||||
}
|
||||
}
|
||||
|
||||
$form->appendChild($radio);
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Install Dashboard'))
|
||||
->setErrors($errors)
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->appendChild($form->buildLayoutView())
|
||||
->addCancelButton($cancel_uri)
|
||||
->addSubmitButton(pht('Install Dashboard'));
|
||||
}
|
||||
|
||||
}
|
|
@ -43,6 +43,14 @@ final class PhabricatorDashboardViewController
|
|||
$navigation = $this->buildSideNavView('view');
|
||||
$header = $this->buildHeaderView();
|
||||
|
||||
$install_button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setText('Install Dashboard')
|
||||
->setIcon('fa-plus')
|
||||
->setWorkflow(true)
|
||||
->setHref($this->getApplicationURI("/install/{$id}/"));
|
||||
$header->addActionLink($install_button);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
|
|
|
@ -50,24 +50,10 @@ final class DifferentialRevisionIDCommitMessageField
|
|||
$uri = new PhutilURI($uri_string);
|
||||
$path = $uri->getPath();
|
||||
|
||||
$matches = null;
|
||||
if (preg_match('#^/D(\d+)$#', $path, $matches)) {
|
||||
$id = (int)$matches[1];
|
||||
|
||||
$prod_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/D'.$id));
|
||||
|
||||
// Make sure the URI is the same as our URI. Basically, we want to ignore
|
||||
// commits from other Phabricator installs.
|
||||
if ($uri->getDomain() == $prod_uri->getDomain()) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
$allowed_uris = PhabricatorEnv::getAllowedURIs('/D'.$id);
|
||||
|
||||
foreach ($allowed_uris as $allowed_uri) {
|
||||
if ($uri_string == $allowed_uri) {
|
||||
return $id;
|
||||
}
|
||||
if (PhabricatorEnv::isSelfURI($uri_string)) {
|
||||
$matches = null;
|
||||
if (preg_match('#^/D(\d+)$#', $path, $matches)) {
|
||||
return (int)$matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ final class DifferentialCommitMessageFieldTestCase
|
|||
"D123\nSome-Custom-Field: The End" => 123,
|
||||
"{$base_uri}D123" => 123,
|
||||
"{$base_uri}D123\nSome-Custom-Field: The End" => 123,
|
||||
'https://www.other.com/D123' => null,
|
||||
);
|
||||
|
||||
$env = PhabricatorEnv::beginScopedEnv();
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialReviewer
|
||||
extends DifferentialDAO {
|
||||
|
||||
protected $revisionPHID;
|
||||
protected $reviewerPHID;
|
||||
protected $reviewerStatus;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'reviewerStatus' => 'text64',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_revision' => array(
|
||||
'columns' => array('revisionPHID', 'reviewerPHID'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
}
|
|
@ -116,6 +116,9 @@ abstract class DifferentialRevisionReviewTransaction
|
|||
);
|
||||
}
|
||||
|
||||
// This is currently double-writing: to the old (edge) store and the new
|
||||
// (reviewer) store. Do the old edge write first.
|
||||
|
||||
$src_phid = $revision->getPHID();
|
||||
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
|
||||
|
||||
|
@ -131,6 +134,42 @@ abstract class DifferentialRevisionReviewTransaction
|
|||
}
|
||||
|
||||
$editor->save();
|
||||
|
||||
// Now, do the new write.
|
||||
|
||||
if ($map) {
|
||||
$table = new DifferentialReviewer();
|
||||
|
||||
$reviewers = $table->loadAllWhere(
|
||||
'revisionPHID = %s AND reviewerPHID IN (%Ls)',
|
||||
$src_phid,
|
||||
array_keys($map));
|
||||
$reviewers = mpull($reviewers, null, 'getReviewerPHID');
|
||||
|
||||
foreach ($map as $dst_phid => $edge_data) {
|
||||
$reviewer = idx($reviewers, $dst_phid);
|
||||
if (!$reviewer) {
|
||||
$reviewer = id(new DifferentialReviewer())
|
||||
->setRevisionPHID($src_phid)
|
||||
->setReviewerPHID($dst_phid);
|
||||
}
|
||||
|
||||
$reviewer->setReviewerStatus($status);
|
||||
|
||||
if ($status == DifferentialReviewerStatus::STATUS_RESIGNED) {
|
||||
if ($reviewer->getID()) {
|
||||
$reviewer->delete();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$reviewer->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
// At least for now, just ignore it if we lost a race.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -106,6 +106,9 @@ final class DifferentialRevisionReviewersTransaction
|
|||
public function applyExternalEffects($object, $value) {
|
||||
$src_phid = $object->getPHID();
|
||||
|
||||
// This is currently double-writing: to the old (edge) store and the new
|
||||
// (reviewer) store. Do the old edge write first.
|
||||
|
||||
$old = $this->generateOldValue($object);
|
||||
$new = $value;
|
||||
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
|
||||
|
@ -138,6 +141,51 @@ final class DifferentialRevisionReviewersTransaction
|
|||
}
|
||||
|
||||
$editor->save();
|
||||
|
||||
// Now, do the new write.
|
||||
|
||||
$table = new DifferentialReviewer();
|
||||
$table_name = $table->getTableName();
|
||||
$conn = $table->establishConnection('w');
|
||||
|
||||
if ($rem) {
|
||||
queryfx(
|
||||
$conn,
|
||||
'DELETE FROM %T WHERE revisionPHID = %s AND reviewerPHID IN (%Ls)',
|
||||
$table_name,
|
||||
$src_phid,
|
||||
array_keys($rem));
|
||||
}
|
||||
|
||||
if ($new) {
|
||||
$reviewers = $table->loadAllWhere(
|
||||
'revisionPHID = %s AND reviewerPHID IN (%Ls)',
|
||||
$src_phid,
|
||||
array_keys($new));
|
||||
$reviewers = mpull($reviewers, null, 'getReviewerPHID');
|
||||
|
||||
foreach ($new as $dst_phid => $status) {
|
||||
$old_status = idx($old, $dst_phid);
|
||||
if ($old_status === $status) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reviewer = idx($reviewers, $dst_phid);
|
||||
if (!$reviewer) {
|
||||
$reviewer = id(new DifferentialReviewer())
|
||||
->setRevisionPHID($src_phid)
|
||||
->setReviewerPHID($dst_phid);
|
||||
}
|
||||
|
||||
$reviewer->setReviewerStatus($status);
|
||||
|
||||
try {
|
||||
$reviewer->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
// At least for now, just ignore it if we lost a race.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
|
|
61
src/applications/people/cache/PhabricatorUserBadgesCacheType.php
vendored
Normal file
61
src/applications/people/cache/PhabricatorUserBadgesCacheType.php
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorUserBadgesCacheType
|
||||
extends PhabricatorUserCacheType {
|
||||
|
||||
const CACHETYPE = 'badges.award';
|
||||
|
||||
const KEY_BADGES = 'user.badge.award.v1';
|
||||
|
||||
const BADGE_COUNT = 2;
|
||||
|
||||
public function getAutoloadKeys() {
|
||||
return array(
|
||||
self::KEY_BADGES,
|
||||
);
|
||||
}
|
||||
|
||||
public function canManageKey($key) {
|
||||
return ($key === self::KEY_BADGES);
|
||||
}
|
||||
|
||||
public function getValueFromStorage($value) {
|
||||
return phutil_json_decode($value);
|
||||
}
|
||||
|
||||
public function newValueForUsers($key, array $users) {
|
||||
if (!$users) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$user_phids = mpull($users, 'getPHID');
|
||||
|
||||
$results = array();
|
||||
foreach ($user_phids as $user_phid) {
|
||||
$awards = id(new PhabricatorBadgesAwardQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withRecipientPHIDs(array($user_phid))
|
||||
->withBadgeStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE))
|
||||
->setLimit(self::BADGE_COUNT)
|
||||
->execute();
|
||||
|
||||
$award_data = array();
|
||||
if ($awards) {
|
||||
foreach ($awards as $award) {
|
||||
$badge = $award->getBadge();
|
||||
$award_data[] = array(
|
||||
'icon' => $badge->getIcon(),
|
||||
'name' => $badge->getName(),
|
||||
'quality' => $badge->getQuality(),
|
||||
'id' => $badge->getID(),
|
||||
);
|
||||
}
|
||||
}
|
||||
$results[$user_phid] = phutil_json_encode($award_data);
|
||||
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
|
@ -84,15 +84,14 @@ final class PhabricatorPeopleProfileBadgesController
|
|||
$awards = id(new PhabricatorBadgesAwardQuery())
|
||||
->setViewer($viewer)
|
||||
->withRecipientPHIDs(array($user->getPHID()))
|
||||
->withBadgeStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE))
|
||||
->execute();
|
||||
$awards = mpull($awards, null, 'getBadgePHID');
|
||||
|
||||
$badges = array();
|
||||
foreach ($awards as $award) {
|
||||
$badge = $award->getBadge();
|
||||
if ($badge->getStatus() == PhabricatorBadgesBadge::STATUS_ACTIVE) {
|
||||
$badges[$award->getBadgePHID()] = $badge;
|
||||
}
|
||||
$badges[$award->getBadgePHID()] = $badge;
|
||||
}
|
||||
|
||||
if (count($badges)) {
|
||||
|
|
|
@ -18,11 +18,13 @@ final class PhabricatorPeopleQuery
|
|||
private $nameLike;
|
||||
private $nameTokens;
|
||||
private $namePrefixes;
|
||||
private $isEnrolledInMultiFactor;
|
||||
|
||||
private $needPrimaryEmail;
|
||||
private $needProfile;
|
||||
private $needProfileImage;
|
||||
private $needAvailability;
|
||||
private $needBadgeAwards;
|
||||
private $cacheKeys = array();
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
|
@ -100,6 +102,11 @@ final class PhabricatorPeopleQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withIsEnrolledInMultiFactor($enrolled) {
|
||||
$this->isEnrolledInMultiFactor = $enrolled;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needPrimaryEmail($need) {
|
||||
$this->needPrimaryEmail = $need;
|
||||
return $this;
|
||||
|
@ -139,6 +146,18 @@ final class PhabricatorPeopleQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function needBadgeAwards($need) {
|
||||
$cache_key = PhabricatorUserBadgesCacheType::KEY_BADGES;
|
||||
|
||||
if ($need) {
|
||||
$this->cacheKeys[$cache_key] = true;
|
||||
} else {
|
||||
unset($this->cacheKeys[$cache_key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhabricatorUser();
|
||||
}
|
||||
|
@ -337,6 +356,13 @@ final class PhabricatorPeopleQuery
|
|||
$this->nameLike);
|
||||
}
|
||||
|
||||
if ($this->isEnrolledInMultiFactor !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'user.isEnrolledInMultiFactor = %d',
|
||||
(int)$this->isEnrolledInMultiFactor);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ final class PhabricatorPeopleSearchEngine
|
|||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
$fields = array(
|
||||
id(new PhabricatorSearchStringListField())
|
||||
->setLabel(pht('Usernames'))
|
||||
->setKey('usernames')
|
||||
|
@ -84,18 +84,36 @@ final class PhabricatorPeopleSearchEngine
|
|||
pht(
|
||||
'Pass true to find only users awaiting administrative approval, '.
|
||||
'or false to omit these users.')),
|
||||
id(new PhabricatorSearchDateField())
|
||||
->setKey('createdStart')
|
||||
->setLabel(pht('Joined After'))
|
||||
->setDescription(
|
||||
pht('Find user accounts created after a given time.')),
|
||||
id(new PhabricatorSearchDateField())
|
||||
->setKey('createdEnd')
|
||||
->setLabel(pht('Joined Before'))
|
||||
->setDescription(
|
||||
pht('Find user accounts created before a given time.')),
|
||||
|
||||
);
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
if ($viewer->getIsAdmin()) {
|
||||
$fields[] = id(new PhabricatorSearchThreeStateField())
|
||||
->setLabel(pht('Has MFA'))
|
||||
->setKey('mfa')
|
||||
->setOptions(
|
||||
pht('(Show All)'),
|
||||
pht('Show Only Users With MFA'),
|
||||
pht('Hide Users With MFA'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'Pass true to find only users who are enrolled in MFA, or false '.
|
||||
'to omit these users.'));
|
||||
}
|
||||
|
||||
$fields[] = id(new PhabricatorSearchDateField())
|
||||
->setKey('createdStart')
|
||||
->setLabel(pht('Joined After'))
|
||||
->setDescription(
|
||||
pht('Find user accounts created after a given time.'));
|
||||
|
||||
$fields[] = id(new PhabricatorSearchDateField())
|
||||
->setKey('createdEnd')
|
||||
->setLabel(pht('Joined Before'))
|
||||
->setDescription(
|
||||
pht('Find user accounts created before a given time.'));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
protected function getDefaultFieldOrder() {
|
||||
|
@ -151,6 +169,19 @@ final class PhabricatorPeopleSearchEngine
|
|||
$query->withIsApproved(!$map['needsApproval']);
|
||||
}
|
||||
|
||||
if (idx($map, 'mfa') !== null) {
|
||||
$viewer = $this->requireViewer();
|
||||
if (!$viewer->getIsAdmin()) {
|
||||
throw new PhabricatorSearchConstraintException(
|
||||
pht(
|
||||
'The "Has MFA" query constraint may only be used by '.
|
||||
'administrators, to prevent attackers from using it to target '.
|
||||
'weak accounts.'));
|
||||
}
|
||||
|
||||
$query->withIsEnrolledInMultiFactor($map['mfa']);
|
||||
}
|
||||
|
||||
if ($map['createdStart']) {
|
||||
$query->withDateCreatedAfter($map['createdStart']);
|
||||
}
|
||||
|
@ -254,6 +285,12 @@ final class PhabricatorPeopleSearchEngine
|
|||
$item->addIcon('fa-envelope-o', pht('Mailing List'));
|
||||
}
|
||||
|
||||
if ($viewer->getIsAdmin()) {
|
||||
if ($user->getIsEnrolledInMultiFactor()) {
|
||||
$item->addIcon('fa-lock', pht('Has MFA'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($viewer->getIsAdmin()) {
|
||||
$user_id = $user->getID();
|
||||
if ($is_approval) {
|
||||
|
|
|
@ -848,6 +848,11 @@ final class PhabricatorUser
|
|||
return $this->requireCacheData($message_key);
|
||||
}
|
||||
|
||||
public function getRecentBadgeAwards() {
|
||||
$badges_key = PhabricatorUserBadgesCacheType::KEY_BADGES;
|
||||
return $this->requireCacheData($badges_key);
|
||||
}
|
||||
|
||||
public function getFullName() {
|
||||
if (strlen($this->getRealName())) {
|
||||
return $this->getUsername().' ('.$this->getRealName().')';
|
||||
|
|
|
@ -120,6 +120,7 @@ final class PhabricatorRepositoryPullEngine
|
|||
pht(
|
||||
'Updating the working copy for repository "%s".',
|
||||
$repository->getDisplayName()));
|
||||
|
||||
if ($is_git) {
|
||||
$this->verifyGitOrigin($repository);
|
||||
$this->executeGitUpdate();
|
||||
|
@ -157,7 +158,7 @@ final class PhabricatorRepositoryPullEngine
|
|||
}
|
||||
|
||||
private function skipPull($message) {
|
||||
$this->log('%s', $message);
|
||||
$this->log($message);
|
||||
$this->donePull();
|
||||
}
|
||||
|
||||
|
@ -172,7 +173,7 @@ final class PhabricatorRepositoryPullEngine
|
|||
}
|
||||
|
||||
private function logPull($message) {
|
||||
$this->log('%s', $message);
|
||||
$this->log($message);
|
||||
}
|
||||
|
||||
private function donePull() {
|
||||
|
@ -190,7 +191,7 @@ final class PhabricatorRepositoryPullEngine
|
|||
}
|
||||
|
||||
private function installHook($path, array $hook_argv = array()) {
|
||||
$this->log('%s', pht('Installing commit hook to "%s"...', $path));
|
||||
$this->log(pht('Installing commit hook to "%s"...', $path));
|
||||
|
||||
$repository = $this->getRepository();
|
||||
$identifier = $this->getHookContextIdentifier($repository);
|
||||
|
@ -339,44 +340,36 @@ final class PhabricatorRepositoryPullEngine
|
|||
throw new Exception($message);
|
||||
}
|
||||
|
||||
$retry = false;
|
||||
do {
|
||||
// This is a local command, but needs credentials.
|
||||
if ($repository->isWorkingCopyBare()) {
|
||||
// For bare working copies, we need this magic incantation.
|
||||
$future = $repository->getRemoteCommandFuture(
|
||||
'fetch origin %s --prune',
|
||||
'+refs/*:refs/*');
|
||||
} else {
|
||||
$future = $repository->getRemoteCommandFuture(
|
||||
'fetch --all --prune');
|
||||
}
|
||||
$remote_refs = $this->loadGitRemoteRefs($repository);
|
||||
$local_refs = $this->loadGitLocalRefs($repository);
|
||||
if ($remote_refs === $local_refs) {
|
||||
$this->log(
|
||||
pht(
|
||||
'Skipping fetch because local and remote refs are already '.
|
||||
'identical.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$future->setCWD($path);
|
||||
list($err, $stdout, $stderr) = $future->resolve();
|
||||
$this->logRefDifferences($remote_refs, $local_refs);
|
||||
|
||||
if ($err && !$retry && $repository->canDestroyWorkingCopy()) {
|
||||
$retry = true;
|
||||
// Fix remote origin url if it doesn't match our configuration
|
||||
$origin_url = $repository->execLocalCommand(
|
||||
'config --get remote.origin.url');
|
||||
$remote_uri = $repository->getRemoteURIEnvelope();
|
||||
if ($origin_url != $remote_uri->openEnvelope()) {
|
||||
$repository->execLocalCommand(
|
||||
'remote set-url origin %P',
|
||||
$remote_uri);
|
||||
}
|
||||
} else if ($err) {
|
||||
throw new CommandException(
|
||||
pht('Failed to fetch changes!'),
|
||||
$future->getCommand(),
|
||||
$err,
|
||||
$stdout,
|
||||
$stderr);
|
||||
} else {
|
||||
$retry = false;
|
||||
}
|
||||
} while ($retry);
|
||||
// Force the "origin" URI to the configured value.
|
||||
$repository->execxLocalCommand(
|
||||
'remote set-url origin -- %P',
|
||||
$repository->getRemoteURIEnvelope());
|
||||
|
||||
if ($repository->isWorkingCopyBare()) {
|
||||
// For bare working copies, we need this magic incantation.
|
||||
$future = $repository->getRemoteCommandFuture(
|
||||
'fetch origin %s --prune',
|
||||
'+refs/*:refs/*');
|
||||
} else {
|
||||
$future = $repository->getRemoteCommandFuture(
|
||||
'fetch --all --prune');
|
||||
}
|
||||
|
||||
$future
|
||||
->setCWD($path)
|
||||
->resolvex();
|
||||
}
|
||||
|
||||
|
||||
|
@ -396,6 +389,75 @@ final class PhabricatorRepositoryPullEngine
|
|||
$this->installHook($root.$path);
|
||||
}
|
||||
|
||||
private function loadGitRemoteRefs(PhabricatorRepository $repository) {
|
||||
$remote_envelope = $repository->getRemoteURIEnvelope();
|
||||
|
||||
list($stdout) = $repository->execxRemoteCommand(
|
||||
'ls-remote -- %P',
|
||||
$remote_envelope);
|
||||
|
||||
$map = array();
|
||||
$lines = phutil_split_lines($stdout, false);
|
||||
foreach ($lines as $line) {
|
||||
list($hash, $name) = preg_split('/\s+/', $line, 2);
|
||||
|
||||
// If the remote has a HEAD, just ignore it.
|
||||
if ($name == 'HEAD') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the remote ref is itself a remote ref, ignore it.
|
||||
if (preg_match('(^refs/remotes/)', $name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map[$name] = $hash;
|
||||
}
|
||||
|
||||
ksort($map);
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
private function loadGitLocalRefs(PhabricatorRepository $repository) {
|
||||
$refs = id(new DiffusionLowLevelGitRefQuery())
|
||||
->setRepository($repository)
|
||||
->execute();
|
||||
|
||||
$map = array();
|
||||
foreach ($refs as $ref) {
|
||||
$fields = $ref->getRawFields();
|
||||
$map[idx($fields, 'refname')] = $ref->getCommitIdentifier();
|
||||
}
|
||||
|
||||
ksort($map);
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
private function logRefDifferences(array $remote, array $local) {
|
||||
$all = $local + $remote;
|
||||
|
||||
$differences = array();
|
||||
foreach ($all as $key => $ignored) {
|
||||
$remote_ref = idx($remote, $key, pht('<null>'));
|
||||
$local_ref = idx($local, $key, pht('<null>'));
|
||||
if ($remote_ref !== $local_ref) {
|
||||
$differences[] = pht(
|
||||
'%s (remote: "%s", local: "%s")',
|
||||
$key,
|
||||
$remote_ref,
|
||||
$local_ref);
|
||||
}
|
||||
}
|
||||
|
||||
$this->log(
|
||||
pht(
|
||||
"Updating repository after detecting ref differences:\n%s",
|
||||
implode("\n", $differences)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -( Pulling Mercurial Working Copies )----------------------------------- */
|
||||
|
||||
|
|
|
@ -331,6 +331,8 @@ final class PhabricatorApplicationSearchController
|
|||
'query parameters and correct errors.');
|
||||
} catch (PhutilSearchQueryCompilerSyntaxException $ex) {
|
||||
$exec_errors[] = $ex->getMessage();
|
||||
} catch (PhabricatorSearchConstraintException $ex) {
|
||||
$exec_errors[] = $ex->getMessage();
|
||||
}
|
||||
|
||||
// The engine may have encountered additional errors during rendering;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSearchConstraintException
|
||||
extends Exception {}
|
|
@ -133,11 +133,13 @@ final class PhabricatorDashboardProfileMenuItem
|
|||
$icon = $dashboard->getIcon();
|
||||
$name = $this->getDisplayName($config);
|
||||
$href = $this->getItemViewURI($config);
|
||||
$action_href = '/dashboard/arrange/'.$dashboard->getID().'/';
|
||||
|
||||
$item = $this->newItem()
|
||||
->setHref($href)
|
||||
->setName($name)
|
||||
->setIcon($icon);
|
||||
->setIcon($icon)
|
||||
->setActionIcon('fa-pencil', $action_href);
|
||||
|
||||
return array(
|
||||
$item,
|
||||
|
|
|
@ -525,23 +525,18 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
|
|||
return null;
|
||||
}
|
||||
|
||||
$awards = id(new PhabricatorBadgesAwardQuery())
|
||||
->setViewer($this->getUser())
|
||||
->withRecipientPHIDs(array($user->getPHID()))
|
||||
->setLimit(2)
|
||||
->execute();
|
||||
|
||||
// Pull Badges from UserCache
|
||||
$badges = $user->getRecentBadgeAwards();
|
||||
$badge_view = null;
|
||||
if ($awards) {
|
||||
$badges = mpull($awards, 'getBadge');
|
||||
if ($badges) {
|
||||
$badge_list = array();
|
||||
foreach ($badges as $badge) {
|
||||
$badge_view = id(new PHUIBadgeMiniView())
|
||||
->setIcon($badge->getIcon())
|
||||
->setQuality($badge->getQuality())
|
||||
->setHeader($badge->getName())
|
||||
->setIcon($badge['icon'])
|
||||
->setQuality($badge['quality'])
|
||||
->setHeader($badge['name'])
|
||||
->setTipDirection('E')
|
||||
->setHref('/badges/view/'.$badge->getID());
|
||||
->setHref('/badges/view/'.$badge['id'].'/');
|
||||
|
||||
$badge_list[] = $badge_view;
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ final class PHUIBadgeExample extends PhabricatorUIExample {
|
|||
|
||||
$badges2[] = id(new PHUIBadgeView())
|
||||
->setIcon('fa-user')
|
||||
->setHeader(pht('Adminstrator'))
|
||||
->setHeader(pht('Administrator'))
|
||||
->setSubhead(pht('Drew the short stick'))
|
||||
->setQuality(PhabricatorBadgesQuality::LEGENDARY)
|
||||
->setSource(pht('People (automatic)'))
|
||||
|
|
43
src/infrastructure/env/PhabricatorEnv.php
vendored
43
src/infrastructure/env/PhabricatorEnv.php
vendored
|
@ -414,21 +414,44 @@ final class PhabricatorEnv extends Phobject {
|
|||
return rtrim($production_domain, '/').$path;
|
||||
}
|
||||
|
||||
public static function getAllowedURIs($path) {
|
||||
$uri = new PhutilURI($path);
|
||||
if ($uri->getDomain()) {
|
||||
return $path;
|
||||
|
||||
public static function isSelfURI($raw_uri) {
|
||||
$uri = new PhutilURI($raw_uri);
|
||||
|
||||
$host = $uri->getDomain();
|
||||
if (!strlen($host)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowed_uris = self::getEnvConfig('phabricator.allowed-uris');
|
||||
$return = array();
|
||||
foreach ($allowed_uris as $allowed_uri) {
|
||||
$return[] = rtrim($allowed_uri, '/').$path;
|
||||
}
|
||||
$host = phutil_utf8_strtolower($host);
|
||||
|
||||
return $return;
|
||||
$self_map = self::getSelfURIMap();
|
||||
return isset($self_map[$host]);
|
||||
}
|
||||
|
||||
private static function getSelfURIMap() {
|
||||
$self_uris = array();
|
||||
$self_uris[] = self::getProductionURI('/');
|
||||
$self_uris[] = self::getURI('/');
|
||||
|
||||
$allowed_uris = self::getEnvConfig('phabricator.allowed-uris');
|
||||
foreach ($allowed_uris as $allowed_uri) {
|
||||
$self_uris[] = $allowed_uri;
|
||||
}
|
||||
|
||||
$self_map = array();
|
||||
foreach ($self_uris as $self_uri) {
|
||||
$host = id(new PhutilURI($self_uri))->getDomain();
|
||||
if (!strlen($host)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$host = phutil_utf8_strtolower($host);
|
||||
$self_map[$host] = $host;
|
||||
}
|
||||
|
||||
return $self_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully-qualified production URI for a static resource path.
|
||||
|
|
|
@ -218,4 +218,39 @@ final class PhabricatorEnvTestCase extends PhabricatorTestCase {
|
|||
$this->assertFalse($caught instanceof Exception);
|
||||
}
|
||||
|
||||
public function testSelfURI() {
|
||||
$base_uri = 'https://allowed.example.com/';
|
||||
|
||||
$allowed_uris = array(
|
||||
'https://old.example.com/',
|
||||
);
|
||||
|
||||
$env = PhabricatorEnv::beginScopedEnv();
|
||||
$env->overrideEnvConfig('phabricator.base-uri', $base_uri);
|
||||
$env->overrideEnvConfig('phabricator.allowed-uris', $allowed_uris);
|
||||
|
||||
$map = array(
|
||||
'https://allowed.example.com/' => true,
|
||||
'https://allowed.example.com' => true,
|
||||
'https://allowed.EXAMPLE.com' => true,
|
||||
'http://allowed.example.com/' => true,
|
||||
'https://allowed.example.com/path/to/resource.png' => true,
|
||||
|
||||
'https://old.example.com/' => true,
|
||||
'https://old.example.com' => true,
|
||||
'https://old.EXAMPLE.com' => true,
|
||||
'http://old.example.com/' => true,
|
||||
'https://old.example.com/path/to/resource.png' => true,
|
||||
|
||||
'https://other.example.com/' => false,
|
||||
);
|
||||
|
||||
foreach ($map as $input => $expect) {
|
||||
$this->assertEqual(
|
||||
$expect,
|
||||
PhabricatorEnv::isSelfURI($input),
|
||||
pht('Is self URI? %s', $input));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ final class PHUIListItemView extends AphrontTagView {
|
|||
private $icons = array();
|
||||
private $openInNewWindow = false;
|
||||
private $tooltip;
|
||||
private $actionIcon;
|
||||
private $actionIconHref;
|
||||
|
||||
public function setOpenInNewWindow($open_in_new_window) {
|
||||
$this->openInNewWindow = $open_in_new_window;
|
||||
|
@ -154,6 +156,12 @@ final class PHUIListItemView extends AphrontTagView {
|
|||
return $this->name;
|
||||
}
|
||||
|
||||
public function setActionIcon($icon, $href) {
|
||||
$this->actionIcon = $icon;
|
||||
$this->actionIconHref = $href;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setIsExternal($is_external) {
|
||||
$this->isExternal = $is_external;
|
||||
return $this;
|
||||
|
@ -207,6 +215,10 @@ final class PHUIListItemView extends AphrontTagView {
|
|||
$classes[] = $this->statusColor;
|
||||
}
|
||||
|
||||
if ($this->actionIcon) {
|
||||
$classes[] = 'phui-list-item-has-action-icon';
|
||||
}
|
||||
|
||||
return array(
|
||||
'class' => implode(' ', $classes),
|
||||
);
|
||||
|
@ -311,9 +323,23 @@ final class PHUIListItemView extends AphrontTagView {
|
|||
$classes[] = 'phui-list-item-indented';
|
||||
}
|
||||
|
||||
$action_link = null;
|
||||
if ($this->actionIcon) {
|
||||
$action_icon = id(new PHUIIconView())
|
||||
->setIcon($this->actionIcon)
|
||||
->addClass('phui-list-item-action-icon');
|
||||
$action_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->actionIconHref,
|
||||
'class' => 'phui-list-item-action-href',
|
||||
),
|
||||
$action_icon);
|
||||
}
|
||||
|
||||
$icons = $this->getIcons();
|
||||
|
||||
return javelin_tag(
|
||||
$list_item = javelin_tag(
|
||||
$this->href ? 'a' : 'div',
|
||||
array(
|
||||
'href' => $this->href,
|
||||
|
@ -329,6 +355,8 @@ final class PHUIListItemView extends AphrontTagView {
|
|||
$this->renderChildren(),
|
||||
$name,
|
||||
));
|
||||
|
||||
return array($list_item, $action_link);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -220,7 +220,6 @@ final class PHUITimelineView extends AphrontView {
|
|||
}
|
||||
|
||||
$user_phid_type = PhabricatorPeopleUserPHIDType::TYPECONST;
|
||||
$badge_edge_type = PhabricatorRecipientHasBadgeEdgeType::EDGECONST;
|
||||
|
||||
$user_phids = array();
|
||||
foreach ($events as $key => $event) {
|
||||
|
@ -244,38 +243,26 @@ final class PHUITimelineView extends AphrontView {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
$awards = id(new PhabricatorBadgesAwardQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withRecipientPHIDs($user_phids)
|
||||
$users = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($user_phids)
|
||||
->needBadgeAwards(true)
|
||||
->execute();
|
||||
|
||||
$awards = mgroup($awards, 'getRecipientPHID');
|
||||
$users = mpull($users, null, 'getPHID');
|
||||
|
||||
foreach ($events as $event) {
|
||||
|
||||
$author_awards = idx($awards, $event->getAuthorPHID(), array());
|
||||
|
||||
$badges = array();
|
||||
foreach ($author_awards as $award) {
|
||||
$badge = $award->getBadge();
|
||||
if ($badge->getStatus() == PhabricatorBadgesBadge::STATUS_ACTIVE) {
|
||||
$badges[$award->getBadgePHID()] = $badge;
|
||||
}
|
||||
$user_phid = $event->getAuthorPHID();
|
||||
if (!array_key_exists($user_phid, $users)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Pick the "best" badges in some smart way. For now, just pick
|
||||
// the first two.
|
||||
$badges = array_slice($badges, 0, 2);
|
||||
|
||||
$badges = $users[$user_phid]->getRecentBadgeAwards();
|
||||
foreach ($badges as $badge) {
|
||||
$badge_view = id(new PHUIBadgeMiniView())
|
||||
->setIcon($badge->getIcon())
|
||||
->setQuality($badge->getQuality())
|
||||
->setHeader($badge->getName())
|
||||
->setIcon($badge['icon'])
|
||||
->setQuality($badge['quality'])
|
||||
->setHeader($badge['name'])
|
||||
->setTipDirection('E')
|
||||
->setHref('/badges/view/'.$badge->getID());
|
||||
|
||||
->setHref('/badges/view/'.$badge['id'].'/');
|
||||
$event->addBadge($badge_view);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
.aphront-dialog-view {
|
||||
width: 540px;
|
||||
width: 560px;
|
||||
margin: 32px auto 16px;
|
||||
border: 1px solid {$lightblueborder};
|
||||
border-radius: 3px;
|
||||
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
|
||||
.aphront-dialog-view-width-form {
|
||||
width: 600px;
|
||||
width: 640px;
|
||||
}
|
||||
|
||||
.aphront-dialog-view-width-full {
|
||||
|
|
|
@ -181,13 +181,14 @@
|
|||
border: none;
|
||||
}
|
||||
|
||||
.device .remarkup-assist-button,
|
||||
.device .remarkup-assist-separator {
|
||||
.device .conpherence-message-pane .remarkup-assist-button,
|
||||
.device .conpherence-message-pane .remarkup-assist-separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device .remarkup-assist-button.remarkup-assist-upload {
|
||||
display: block;
|
||||
.device .conpherence-message-pane
|
||||
.remarkup-assist-button.remarkup-assist-upload {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.device .conpherence-message-pane .phui-form-view {
|
||||
|
@ -343,7 +344,7 @@
|
|||
padding: 2px 0 8px 0;
|
||||
}
|
||||
|
||||
.conpherence-message-pane .aphront-form-control {
|
||||
body .conpherence-message-pane .aphront-form-control {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -208,14 +208,13 @@
|
|||
|
||||
table.aphront-form-control-radio-layout,
|
||||
table.aphront-form-control-checkbox-layout {
|
||||
margin-top: 3px;
|
||||
margin-top: 4px !important;
|
||||
font-size: {$normalfontsize};
|
||||
}
|
||||
|
||||
table.aphront-form-control-radio-layout th {
|
||||
padding-top: 3px;
|
||||
padding-left: 8px;
|
||||
padding-bottom: 4px;
|
||||
padding-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: {$darkgreytext};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
* @provides phui-list-view-css
|
||||
*/
|
||||
|
||||
.phui-list-item-view {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phui-list-item-header,
|
||||
.phui-list-item-header a {
|
||||
color: {$bluetext};
|
||||
|
@ -188,3 +192,39 @@
|
|||
margin-top: 16px;
|
||||
border-top: 1px solid {$thinblueborder};
|
||||
}
|
||||
|
||||
/* - Action Icon ----------------------------------------------------------- */
|
||||
|
||||
.phui-list-item-has-action-icon .phui-list-item-action-href {
|
||||
position: absolute;
|
||||
width: 28px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
line-height: 28px;
|
||||
background-color: transparent;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.phui-list-item-has-action-icon.phui-list-item-selected .phui-list-item-href {
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.phui-list-item-has-action-icon.phui-list-item-selected
|
||||
.phui-list-item-action-href {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.phui-list-item-has-action-icon .phui-list-item-action-href:hover {
|
||||
background-color: rgba({$alphablack},.05);
|
||||
}
|
||||
|
||||
.phui-list-item-has-action-icon .phui-list-item-action-icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.phui-list-item-has-action-icon .phui-list-item-action-href:hover
|
||||
.phui-list-item-action-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue