1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-16 02:20:54 +01:00

(stable) Promote 2017 Week 11

This commit is contained in:
epriestley 2017-03-17 17:20:29 -07:00
commit da80ed415f
38 changed files with 844 additions and 155 deletions

View file

@ -656,6 +656,10 @@ class PHPMailerLite {
$addr_str .= implode(', ', $addresses); $addr_str .= implode(', ', $addresses);
$addr_str .= $this->LE; $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; return $addr_str;
} }

View file

@ -7,9 +7,9 @@
*/ */
return array( return array(
'names' => array( 'names' => array(
'conpherence.pkg.css' => '6875302f', 'conpherence.pkg.css' => '32f2c040',
'conpherence.pkg.js' => '6249a1cf', 'conpherence.pkg.js' => '6249a1cf',
'core.pkg.css' => '35645dec', 'core.pkg.css' => '491d7018',
'core.pkg.js' => '1fa7c0c5', 'core.pkg.js' => '1fa7c0c5',
'darkconsole.pkg.js' => 'e7393ebb', 'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '90b30783', 'differential.pkg.css' => '90b30783',
@ -21,7 +21,7 @@ return array(
'maniphest.pkg.js' => '5ab2753f', 'maniphest.pkg.js' => '5ab2753f',
'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
'rsrc/css/aphront/dark-console.css' => 'f54bf286', '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/list-filter-view.css' => '5d6f0526',
'rsrc/css/aphront/multi-column.css' => '84cc6640', 'rsrc/css/aphront/multi-column.css' => '84cc6640',
'rsrc/css/aphront/notification.css' => '3f6c89c9', 'rsrc/css/aphront/notification.css' => '3f6c89c9',
@ -48,7 +48,7 @@ return array(
'rsrc/css/application/conpherence/durable-column.css' => '292c71f0', 'rsrc/css/application/conpherence/durable-column.css' => '292c71f0',
'rsrc/css/application/conpherence/header-pane.css' => 'db93ebc6', 'rsrc/css/application/conpherence/header-pane.css' => 'db93ebc6',
'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c', '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/notification.css' => '965db05b',
'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02', 'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02',
'rsrc/css/application/conpherence/transaction.css' => '85129c68', 'rsrc/css/application/conpherence/transaction.css' => '85129c68',
@ -146,7 +146,7 @@ return array(
'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-document.css' => 'c32e8dec',
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
'rsrc/css/phui/phui-fontkit.css' => 'b78a0059', '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-form.css' => 'b62c01d8',
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
'rsrc/css/phui/phui-header-view.css' => 'fef6a54e', '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-info-view.css' => 'ec92802a',
'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0',
'rsrc/css/phui/phui-lightbox.css' => '0a035e40', '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-object-box.css' => '8b289e3d',
'rsrc/css/phui/phui-pager.css' => '77d8a794', 'rsrc/css/phui/phui-pager.css' => '77d8a794',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
@ -548,7 +548,7 @@ return array(
'almanac-css' => 'dbb9b3af', 'almanac-css' => 'dbb9b3af',
'aphront-bars' => '231ac33c', 'aphront-bars' => '231ac33c',
'aphront-dark-console-css' => 'f54bf286', 'aphront-dark-console-css' => 'f54bf286',
'aphront-dialog-view-css' => '5e5aa60b', 'aphront-dialog-view-css' => '685c7e2d',
'aphront-list-filter-view-css' => '5d6f0526', 'aphront-list-filter-view-css' => '5d6f0526',
'aphront-multi-column-view-css' => '84cc6640', 'aphront-multi-column-view-css' => '84cc6640',
'aphront-panel-view-css' => '8427b78d', 'aphront-panel-view-css' => '8427b78d',
@ -566,7 +566,7 @@ return array(
'conpherence-durable-column-view' => '292c71f0', 'conpherence-durable-column-view' => '292c71f0',
'conpherence-header-pane-css' => 'db93ebc6', 'conpherence-header-pane-css' => 'db93ebc6',
'conpherence-menu-css' => '3d8e5c9c', 'conpherence-menu-css' => '3d8e5c9c',
'conpherence-message-pane-css' => 'b085d40d', 'conpherence-message-pane-css' => 'd1fc13e1',
'conpherence-notification-css' => '965db05b', 'conpherence-notification-css' => '965db05b',
'conpherence-participant-pane-css' => '604a8b02', 'conpherence-participant-pane-css' => '604a8b02',
'conpherence-thread-manager' => 'c8b5ee6f', 'conpherence-thread-manager' => 'c8b5ee6f',
@ -859,7 +859,7 @@ return array(
'phui-font-icon-base-css' => '870a7360', 'phui-font-icon-base-css' => '870a7360',
'phui-fontkit-css' => 'b78a0059', 'phui-fontkit-css' => 'b78a0059',
'phui-form-css' => 'b62c01d8', 'phui-form-css' => 'b62c01d8',
'phui-form-view-css' => 'adca31ce', 'phui-form-view-css' => 'cf198e10',
'phui-head-thing-view-css' => 'fd311e5f', 'phui-head-thing-view-css' => 'fd311e5f',
'phui-header-view-css' => 'fef6a54e', 'phui-header-view-css' => 'fef6a54e',
'phui-hovercard' => '1bd28176', 'phui-hovercard' => '1bd28176',
@ -872,7 +872,7 @@ return array(
'phui-inline-comment-view-css' => 'be663c95', 'phui-inline-comment-view-css' => 'be663c95',
'phui-invisible-character-view-css' => '6993d9f0', 'phui-invisible-character-view-css' => '6993d9f0',
'phui-lightbox-css' => '0a035e40', 'phui-lightbox-css' => '0a035e40',
'phui-list-view-css' => '9da2aa00', 'phui-list-view-css' => 'a3ec3cf1',
'phui-object-box-css' => '8b289e3d', 'phui-object-box-css' => '8b289e3d',
'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-big-ui-css' => '19f9369b',
'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-color-css' => 'cd2b9b77',

View 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};

View file

@ -491,6 +491,7 @@ phutil_register_library_map(array(
'DifferentialRevertPlanCommitMessageField' => 'applications/differential/field/DifferentialRevertPlanCommitMessageField.php', 'DifferentialRevertPlanCommitMessageField' => 'applications/differential/field/DifferentialRevertPlanCommitMessageField.php',
'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php', 'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php',
'DifferentialReviewedByCommitMessageField' => 'applications/differential/field/DifferentialReviewedByCommitMessageField.php', 'DifferentialReviewedByCommitMessageField' => 'applications/differential/field/DifferentialReviewedByCommitMessageField.php',
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php', 'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php',
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php', 'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
'DifferentialReviewerProxy' => 'applications/differential/storage/DifferentialReviewerProxy.php', 'DifferentialReviewerProxy' => 'applications/differential/storage/DifferentialReviewerProxy.php',
@ -2495,6 +2496,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php', 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php',
'PhabricatorDashboardIconSet' => 'applications/dashboard/icon/PhabricatorDashboardIconSet.php', 'PhabricatorDashboardIconSet' => 'applications/dashboard/icon/PhabricatorDashboardIconSet.php',
'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php', 'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php',
'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/PhabricatorDashboardInstallController.php',
'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php', 'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php',
'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php', 'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php',
'PhabricatorDashboardManageController' => 'applications/dashboard/controller/PhabricatorDashboardManageController.php', 'PhabricatorDashboardManageController' => 'applications/dashboard/controller/PhabricatorDashboardManageController.php',
@ -3761,6 +3763,7 @@ phutil_register_library_map(array(
'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php', 'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php',
'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php', 'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php',
'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php', 'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php',
'PhabricatorSearchConstraintException' => 'applications/search/exception/PhabricatorSearchConstraintException.php',
'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php', 'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php',
'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php', 'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php',
'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php', 'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php',
@ -4064,6 +4067,7 @@ phutil_register_library_map(array(
'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php', 'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php',
'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php', 'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php', 'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
'PhabricatorUserBadgesCacheType' => 'applications/people/cache/PhabricatorUserBadgesCacheType.php',
'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php', 'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php',
'PhabricatorUserCache' => 'applications/people/storage/PhabricatorUserCache.php', 'PhabricatorUserCache' => 'applications/people/storage/PhabricatorUserCache.php',
'PhabricatorUserCacheType' => 'applications/people/cache/PhabricatorUserCacheType.php', 'PhabricatorUserCacheType' => 'applications/people/cache/PhabricatorUserCacheType.php',
@ -5247,6 +5251,7 @@ phutil_register_library_map(array(
'DifferentialRevertPlanCommitMessageField' => 'DifferentialCommitMessageCustomField', 'DifferentialRevertPlanCommitMessageField' => 'DifferentialCommitMessageCustomField',
'DifferentialRevertPlanField' => 'DifferentialStoredCustomField', 'DifferentialRevertPlanField' => 'DifferentialStoredCustomField',
'DifferentialReviewedByCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialReviewedByCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialReviewer' => 'DifferentialDAO',
'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
'DifferentialReviewerProxy' => 'Phobject', 'DifferentialReviewerProxy' => 'Phobject',
@ -7560,6 +7565,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardEditController' => 'PhabricatorDashboardController', 'PhabricatorDashboardEditController' => 'PhabricatorDashboardController',
'PhabricatorDashboardIconSet' => 'PhabricatorIconSet', 'PhabricatorDashboardIconSet' => 'PhabricatorIconSet',
'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO', 'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO',
'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController',
'PhabricatorDashboardLayoutConfig' => 'Phobject', 'PhabricatorDashboardLayoutConfig' => 'Phobject',
'PhabricatorDashboardListController' => 'PhabricatorDashboardController', 'PhabricatorDashboardListController' => 'PhabricatorDashboardController',
'PhabricatorDashboardManageController' => 'PhabricatorDashboardProfileController', 'PhabricatorDashboardManageController' => 'PhabricatorDashboardProfileController',
@ -9068,6 +9074,7 @@ phutil_register_library_map(array(
'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchBaseController' => 'PhabricatorController',
'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField', 'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField',
'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorSearchConstraintException' => 'Exception',
'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField', 'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField',
'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO',
@ -9409,6 +9416,7 @@ phutil_register_library_map(array(
'PhabricatorFulltextInterface', 'PhabricatorFulltextInterface',
'PhabricatorConduitResultInterface', 'PhabricatorConduitResultInterface',
), ),
'PhabricatorUserBadgesCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField', 'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField',
'PhabricatorUserCache' => 'PhabricatorUserDAO', 'PhabricatorUserCache' => 'PhabricatorUserDAO',
'PhabricatorUserCacheType' => 'Phobject', 'PhabricatorUserCacheType' => 'Phobject',

View file

@ -306,6 +306,18 @@ final class PhabricatorAuditEditor
$field_key = DifferentialAuditorsCommitMessageField::FIELDKEY; $field_key = DifferentialAuditorsCommitMessageField::FIELDKEY;
$phids = idx($result, $field_key, null); $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) { if (!$phids) {
return array(); return array();
} }

View file

@ -118,4 +118,45 @@ final class PhabricatorBadgesEditor
return pht('[Badge]'); 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;
}
} }

View file

@ -6,7 +6,7 @@ final class PhabricatorBadgesAwardQuery
private $badgePHIDs; private $badgePHIDs;
private $recipientPHIDs; private $recipientPHIDs;
private $awarderPHIDs; private $awarderPHIDs;
private $badgeStatuses = null;
protected function willFilterPage(array $awards) { protected function willFilterPage(array $awards) {
$badge_phids = array(); $badge_phids = array();
@ -22,6 +22,11 @@ final class PhabricatorBadgesAwardQuery
$badges = mpull($badges, null, 'getPHID'); $badges = mpull($badges, null, 'getPHID');
foreach ($awards as $key => $award) { foreach ($awards as $key => $award) {
$award_badge = idx($badges, $award->getBadgePHID()); $award_badge = idx($badges, $award->getBadgePHID());
if (!$award_badge) {
unset($awards[$key]);
$this->didRejectResult($award);
continue;
}
$award->attachBadge($award_badge); $award->attachBadge($award_badge);
} }
@ -43,6 +48,15 @@ final class PhabricatorBadgesAwardQuery
return $this; return $this;
} }
public function withBadgeStatuses(array $statuses) {
$this->badgeStatuses = $statuses;
return $this;
}
private function shouldJoinBadge() {
return (bool)$this->badgeStatuses;
}
protected function loadPage() { protected function loadPage() {
return $this->loadStandardPage($this->newResultObject()); return $this->loadStandardPage($this->newResultObject());
} }
@ -51,33 +65,59 @@ final class PhabricatorBadgesAwardQuery
return new PhabricatorBadgesAward(); return new PhabricatorBadgesAward();
} }
protected function getPrimaryTableAlias() {
return 'badges_award';
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn); $where = parent::buildWhereClauseParts($conn);
if ($this->badgePHIDs !== null) { if ($this->badgePHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'badgePHID IN (%Ls)', 'badges_award.badgePHID IN (%Ls)',
$this->badgePHIDs); $this->badgePHIDs);
} }
if ($this->recipientPHIDs !== null) { if ($this->recipientPHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'recipientPHID IN (%Ls)', 'badges_award.recipientPHID IN (%Ls)',
$this->recipientPHIDs); $this->recipientPHIDs);
} }
if ($this->awarderPHIDs !== null) { if ($this->awarderPHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'awarderPHID IN (%Ls)', 'badges_award.awarderPHID IN (%Ls)',
$this->awarderPHIDs); $this->awarderPHIDs);
} }
if ($this->badgeStatuses !== null) {
$where[] = qsprintf(
$conn,
'badges_badge.status IN (%Ls)',
$this->badgeStatuses);
}
return $where; 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() { public function getQueryApplicationClass() {
return 'PhabricatorBadgesApplication'; return 'PhabricatorBadgesApplication';
} }

View file

@ -36,7 +36,7 @@ final class PhabricatorBadgesRecipientsListView extends AphrontView {
$award_button = id(new PHUIButtonView()) $award_button = id(new PHUIButtonView())
->setTag('a') ->setTag('a')
->setIcon('fa-plus') ->setIcon('fa-plus')
->setText(pht('Add Recipents')) ->setText(pht('Add Recipients'))
->setWorkflow(true) ->setWorkflow(true)
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setHref('/badges/recipients/'.$badge->getID().'/add/'); ->setHref('/badges/recipients/'.$badge->getID().'/add/');

View file

@ -158,6 +158,10 @@ final class PhabricatorConfigManagementSetWorkflow
$config_type = 'database'; $config_type = 'database';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key); $config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$config_entry->setValue($value); $config_entry->setValue($value);
// If the entry has been deleted, resurrect it.
$config_entry->setIsDeleted(0);
$config_entry->save(); $config_entry->save();
} else { } else {
$config_type = 'local'; $config_type = 'local';

View file

@ -66,6 +66,21 @@ EOTEXT
, ,
PhabricatorEnv::getDoclink('Configuring Encryption'))); 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( return array(
$this->newOption('security.alternate-file-domain', 'string', null) $this->newOption('security.alternate-file-domain', 'string', null)
->setLocked(true) ->setLocked(true)
@ -132,13 +147,7 @@ EOTEXT
->setLocked(true) ->setLocked(true)
->setSummary( ->setSummary(
pht('Require all users to configure multi-factor authentication.')) pht('Require all users to configure multi-factor authentication.'))
->setDescription( ->setDescription($require_mfa_description)
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.'))
->setBoolOptions( ->setBoolOptions(
array( array(
pht('Multi-Factor Required'), pht('Multi-Factor Required'),

View file

@ -30,6 +30,7 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
'arrange/(?P<id>\d+)/' => 'PhabricatorDashboardArrangeController', 'arrange/(?P<id>\d+)/' => 'PhabricatorDashboardArrangeController',
'create/' => 'PhabricatorDashboardEditController', 'create/' => 'PhabricatorDashboardEditController',
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardEditController', 'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardEditController',
'install/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardInstallController',
'addpanel/(?P<id>\d+)/' => 'PhabricatorDashboardAddPanelController', 'addpanel/(?P<id>\d+)/' => 'PhabricatorDashboardAddPanelController',
'movepanel/(?P<id>\d+)/' => 'PhabricatorDashboardMovePanelController', 'movepanel/(?P<id>\d+)/' => 'PhabricatorDashboardMovePanelController',
'removepanel/(?P<id>\d+)/' 'removepanel/(?P<id>\d+)/'

View file

@ -51,6 +51,14 @@ final class PhabricatorDashboardArrangeController
->addClass('dashboard-preview-box') ->addClass('dashboard-preview-box')
->appendChild($rendered_dashboard); ->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()) $view = id(new PHUITwoColumnView())
->setHeader($header) ->setHeader($header)
->setFooter(array( ->setFooter(array(

View file

@ -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'));
}
}

View file

@ -43,6 +43,14 @@ final class PhabricatorDashboardViewController
$navigation = $this->buildSideNavView('view'); $navigation = $this->buildSideNavView('view');
$header = $this->buildHeaderView(); $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()) $view = id(new PHUITwoColumnView())
->setHeader($header) ->setHeader($header)
->setFooter(array( ->setFooter(array(

View file

@ -50,24 +50,10 @@ final class DifferentialRevisionIDCommitMessageField
$uri = new PhutilURI($uri_string); $uri = new PhutilURI($uri_string);
$path = $uri->getPath(); $path = $uri->getPath();
$matches = null; if (PhabricatorEnv::isSelfURI($uri_string)) {
if (preg_match('#^/D(\d+)$#', $path, $matches)) { $matches = null;
$id = (int)$matches[1]; if (preg_match('#^/D(\d+)$#', $path, $matches)) {
return (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;
}
} }
} }

View file

@ -13,6 +13,7 @@ final class DifferentialCommitMessageFieldTestCase
"D123\nSome-Custom-Field: The End" => 123, "D123\nSome-Custom-Field: The End" => 123,
"{$base_uri}D123" => 123, "{$base_uri}D123" => 123,
"{$base_uri}D123\nSome-Custom-Field: The End" => 123, "{$base_uri}D123\nSome-Custom-Field: The End" => 123,
'https://www.other.com/D123' => null,
); );
$env = PhabricatorEnv::beginScopedEnv(); $env = PhabricatorEnv::beginScopedEnv();

View file

@ -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();
}
}

View file

@ -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(); $src_phid = $revision->getPHID();
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST; $edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
@ -131,6 +134,42 @@ abstract class DifferentialRevisionReviewTransaction
} }
$editor->save(); $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.
}
}
}
}
} }
} }

View file

@ -106,6 +106,9 @@ final class DifferentialRevisionReviewersTransaction
public function applyExternalEffects($object, $value) { public function applyExternalEffects($object, $value) {
$src_phid = $object->getPHID(); $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); $old = $this->generateOldValue($object);
$new = $value; $new = $value;
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST; $edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
@ -138,6 +141,51 @@ final class DifferentialRevisionReviewersTransaction
} }
$editor->save(); $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() { public function getTitle() {

View 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;
}
}

View file

@ -84,15 +84,14 @@ final class PhabricatorPeopleProfileBadgesController
$awards = id(new PhabricatorBadgesAwardQuery()) $awards = id(new PhabricatorBadgesAwardQuery())
->setViewer($viewer) ->setViewer($viewer)
->withRecipientPHIDs(array($user->getPHID())) ->withRecipientPHIDs(array($user->getPHID()))
->withBadgeStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE))
->execute(); ->execute();
$awards = mpull($awards, null, 'getBadgePHID'); $awards = mpull($awards, null, 'getBadgePHID');
$badges = array(); $badges = array();
foreach ($awards as $award) { foreach ($awards as $award) {
$badge = $award->getBadge(); $badge = $award->getBadge();
if ($badge->getStatus() == PhabricatorBadgesBadge::STATUS_ACTIVE) { $badges[$award->getBadgePHID()] = $badge;
$badges[$award->getBadgePHID()] = $badge;
}
} }
if (count($badges)) { if (count($badges)) {

View file

@ -18,11 +18,13 @@ final class PhabricatorPeopleQuery
private $nameLike; private $nameLike;
private $nameTokens; private $nameTokens;
private $namePrefixes; private $namePrefixes;
private $isEnrolledInMultiFactor;
private $needPrimaryEmail; private $needPrimaryEmail;
private $needProfile; private $needProfile;
private $needProfileImage; private $needProfileImage;
private $needAvailability; private $needAvailability;
private $needBadgeAwards;
private $cacheKeys = array(); private $cacheKeys = array();
public function withIDs(array $ids) { public function withIDs(array $ids) {
@ -100,6 +102,11 @@ final class PhabricatorPeopleQuery
return $this; return $this;
} }
public function withIsEnrolledInMultiFactor($enrolled) {
$this->isEnrolledInMultiFactor = $enrolled;
return $this;
}
public function needPrimaryEmail($need) { public function needPrimaryEmail($need) {
$this->needPrimaryEmail = $need; $this->needPrimaryEmail = $need;
return $this; return $this;
@ -139,6 +146,18 @@ final class PhabricatorPeopleQuery
return $this; 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() { public function newResultObject() {
return new PhabricatorUser(); return new PhabricatorUser();
} }
@ -337,6 +356,13 @@ final class PhabricatorPeopleQuery
$this->nameLike); $this->nameLike);
} }
if ($this->isEnrolledInMultiFactor !== null) {
$where[] = qsprintf(
$conn,
'user.isEnrolledInMultiFactor = %d',
(int)$this->isEnrolledInMultiFactor);
}
return $where; return $where;
} }

View file

@ -18,7 +18,7 @@ final class PhabricatorPeopleSearchEngine
} }
protected function buildCustomSearchFields() { protected function buildCustomSearchFields() {
return array( $fields = array(
id(new PhabricatorSearchStringListField()) id(new PhabricatorSearchStringListField())
->setLabel(pht('Usernames')) ->setLabel(pht('Usernames'))
->setKey('usernames') ->setKey('usernames')
@ -84,18 +84,36 @@ final class PhabricatorPeopleSearchEngine
pht( pht(
'Pass true to find only users awaiting administrative approval, '. 'Pass true to find only users awaiting administrative approval, '.
'or false to omit these users.')), '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() { protected function getDefaultFieldOrder() {
@ -151,6 +169,19 @@ final class PhabricatorPeopleSearchEngine
$query->withIsApproved(!$map['needsApproval']); $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']) { if ($map['createdStart']) {
$query->withDateCreatedAfter($map['createdStart']); $query->withDateCreatedAfter($map['createdStart']);
} }
@ -254,6 +285,12 @@ final class PhabricatorPeopleSearchEngine
$item->addIcon('fa-envelope-o', pht('Mailing List')); $item->addIcon('fa-envelope-o', pht('Mailing List'));
} }
if ($viewer->getIsAdmin()) {
if ($user->getIsEnrolledInMultiFactor()) {
$item->addIcon('fa-lock', pht('Has MFA'));
}
}
if ($viewer->getIsAdmin()) { if ($viewer->getIsAdmin()) {
$user_id = $user->getID(); $user_id = $user->getID();
if ($is_approval) { if ($is_approval) {

View file

@ -848,6 +848,11 @@ final class PhabricatorUser
return $this->requireCacheData($message_key); return $this->requireCacheData($message_key);
} }
public function getRecentBadgeAwards() {
$badges_key = PhabricatorUserBadgesCacheType::KEY_BADGES;
return $this->requireCacheData($badges_key);
}
public function getFullName() { public function getFullName() {
if (strlen($this->getRealName())) { if (strlen($this->getRealName())) {
return $this->getUsername().' ('.$this->getRealName().')'; return $this->getUsername().' ('.$this->getRealName().')';

View file

@ -120,6 +120,7 @@ final class PhabricatorRepositoryPullEngine
pht( pht(
'Updating the working copy for repository "%s".', 'Updating the working copy for repository "%s".',
$repository->getDisplayName())); $repository->getDisplayName()));
if ($is_git) { if ($is_git) {
$this->verifyGitOrigin($repository); $this->verifyGitOrigin($repository);
$this->executeGitUpdate(); $this->executeGitUpdate();
@ -157,7 +158,7 @@ final class PhabricatorRepositoryPullEngine
} }
private function skipPull($message) { private function skipPull($message) {
$this->log('%s', $message); $this->log($message);
$this->donePull(); $this->donePull();
} }
@ -172,7 +173,7 @@ final class PhabricatorRepositoryPullEngine
} }
private function logPull($message) { private function logPull($message) {
$this->log('%s', $message); $this->log($message);
} }
private function donePull() { private function donePull() {
@ -190,7 +191,7 @@ final class PhabricatorRepositoryPullEngine
} }
private function installHook($path, array $hook_argv = array()) { 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(); $repository = $this->getRepository();
$identifier = $this->getHookContextIdentifier($repository); $identifier = $this->getHookContextIdentifier($repository);
@ -339,44 +340,36 @@ final class PhabricatorRepositoryPullEngine
throw new Exception($message); throw new Exception($message);
} }
$retry = false; $remote_refs = $this->loadGitRemoteRefs($repository);
do { $local_refs = $this->loadGitLocalRefs($repository);
// This is a local command, but needs credentials. if ($remote_refs === $local_refs) {
if ($repository->isWorkingCopyBare()) { $this->log(
// For bare working copies, we need this magic incantation. pht(
$future = $repository->getRemoteCommandFuture( 'Skipping fetch because local and remote refs are already '.
'fetch origin %s --prune', 'identical.'));
'+refs/*:refs/*'); return false;
} else { }
$future = $repository->getRemoteCommandFuture(
'fetch --all --prune');
}
$future->setCWD($path); $this->logRefDifferences($remote_refs, $local_refs);
list($err, $stdout, $stderr) = $future->resolve();
if ($err && !$retry && $repository->canDestroyWorkingCopy()) { // Force the "origin" URI to the configured value.
$retry = true; $repository->execxLocalCommand(
// Fix remote origin url if it doesn't match our configuration 'remote set-url origin -- %P',
$origin_url = $repository->execLocalCommand( $repository->getRemoteURIEnvelope());
'config --get remote.origin.url');
$remote_uri = $repository->getRemoteURIEnvelope(); if ($repository->isWorkingCopyBare()) {
if ($origin_url != $remote_uri->openEnvelope()) { // For bare working copies, we need this magic incantation.
$repository->execLocalCommand( $future = $repository->getRemoteCommandFuture(
'remote set-url origin %P', 'fetch origin %s --prune',
$remote_uri); '+refs/*:refs/*');
} } else {
} else if ($err) { $future = $repository->getRemoteCommandFuture(
throw new CommandException( 'fetch --all --prune');
pht('Failed to fetch changes!'), }
$future->getCommand(),
$err, $future
$stdout, ->setCWD($path)
$stderr); ->resolvex();
} else {
$retry = false;
}
} while ($retry);
} }
@ -396,6 +389,75 @@ final class PhabricatorRepositoryPullEngine
$this->installHook($root.$path); $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 )----------------------------------- */ /* -( Pulling Mercurial Working Copies )----------------------------------- */

View file

@ -331,6 +331,8 @@ final class PhabricatorApplicationSearchController
'query parameters and correct errors.'); 'query parameters and correct errors.');
} catch (PhutilSearchQueryCompilerSyntaxException $ex) { } catch (PhutilSearchQueryCompilerSyntaxException $ex) {
$exec_errors[] = $ex->getMessage(); $exec_errors[] = $ex->getMessage();
} catch (PhabricatorSearchConstraintException $ex) {
$exec_errors[] = $ex->getMessage();
} }
// The engine may have encountered additional errors during rendering; // The engine may have encountered additional errors during rendering;

View file

@ -0,0 +1,4 @@
<?php
final class PhabricatorSearchConstraintException
extends Exception {}

View file

@ -133,11 +133,13 @@ final class PhabricatorDashboardProfileMenuItem
$icon = $dashboard->getIcon(); $icon = $dashboard->getIcon();
$name = $this->getDisplayName($config); $name = $this->getDisplayName($config);
$href = $this->getItemViewURI($config); $href = $this->getItemViewURI($config);
$action_href = '/dashboard/arrange/'.$dashboard->getID().'/';
$item = $this->newItem() $item = $this->newItem()
->setHref($href) ->setHref($href)
->setName($name) ->setName($name)
->setIcon($icon); ->setIcon($icon)
->setActionIcon('fa-pencil', $action_href);
return array( return array(
$item, $item,

View file

@ -525,23 +525,18 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
return null; return null;
} }
$awards = id(new PhabricatorBadgesAwardQuery()) // Pull Badges from UserCache
->setViewer($this->getUser()) $badges = $user->getRecentBadgeAwards();
->withRecipientPHIDs(array($user->getPHID()))
->setLimit(2)
->execute();
$badge_view = null; $badge_view = null;
if ($awards) { if ($badges) {
$badges = mpull($awards, 'getBadge');
$badge_list = array(); $badge_list = array();
foreach ($badges as $badge) { foreach ($badges as $badge) {
$badge_view = id(new PHUIBadgeMiniView()) $badge_view = id(new PHUIBadgeMiniView())
->setIcon($badge->getIcon()) ->setIcon($badge['icon'])
->setQuality($badge->getQuality()) ->setQuality($badge['quality'])
->setHeader($badge->getName()) ->setHeader($badge['name'])
->setTipDirection('E') ->setTipDirection('E')
->setHref('/badges/view/'.$badge->getID()); ->setHref('/badges/view/'.$badge['id'].'/');
$badge_list[] = $badge_view; $badge_list[] = $badge_view;
} }

View file

@ -97,7 +97,7 @@ final class PHUIBadgeExample extends PhabricatorUIExample {
$badges2[] = id(new PHUIBadgeView()) $badges2[] = id(new PHUIBadgeView())
->setIcon('fa-user') ->setIcon('fa-user')
->setHeader(pht('Adminstrator')) ->setHeader(pht('Administrator'))
->setSubhead(pht('Drew the short stick')) ->setSubhead(pht('Drew the short stick'))
->setQuality(PhabricatorBadgesQuality::LEGENDARY) ->setQuality(PhabricatorBadgesQuality::LEGENDARY)
->setSource(pht('People (automatic)')) ->setSource(pht('People (automatic)'))

View file

@ -414,21 +414,44 @@ final class PhabricatorEnv extends Phobject {
return rtrim($production_domain, '/').$path; return rtrim($production_domain, '/').$path;
} }
public static function getAllowedURIs($path) {
$uri = new PhutilURI($path); public static function isSelfURI($raw_uri) {
if ($uri->getDomain()) { $uri = new PhutilURI($raw_uri);
return $path;
$host = $uri->getDomain();
if (!strlen($host)) {
return false;
} }
$allowed_uris = self::getEnvConfig('phabricator.allowed-uris'); $host = phutil_utf8_strtolower($host);
$return = array();
foreach ($allowed_uris as $allowed_uri) {
$return[] = rtrim($allowed_uri, '/').$path;
}
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. * Get the fully-qualified production URI for a static resource path.

View file

@ -218,4 +218,39 @@ final class PhabricatorEnvTestCase extends PhabricatorTestCase {
$this->assertFalse($caught instanceof Exception); $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));
}
}
} }

View file

@ -31,6 +31,8 @@ final class PHUIListItemView extends AphrontTagView {
private $icons = array(); private $icons = array();
private $openInNewWindow = false; private $openInNewWindow = false;
private $tooltip; private $tooltip;
private $actionIcon;
private $actionIconHref;
public function setOpenInNewWindow($open_in_new_window) { public function setOpenInNewWindow($open_in_new_window) {
$this->openInNewWindow = $open_in_new_window; $this->openInNewWindow = $open_in_new_window;
@ -154,6 +156,12 @@ final class PHUIListItemView extends AphrontTagView {
return $this->name; return $this->name;
} }
public function setActionIcon($icon, $href) {
$this->actionIcon = $icon;
$this->actionIconHref = $href;
return $this;
}
public function setIsExternal($is_external) { public function setIsExternal($is_external) {
$this->isExternal = $is_external; $this->isExternal = $is_external;
return $this; return $this;
@ -207,6 +215,10 @@ final class PHUIListItemView extends AphrontTagView {
$classes[] = $this->statusColor; $classes[] = $this->statusColor;
} }
if ($this->actionIcon) {
$classes[] = 'phui-list-item-has-action-icon';
}
return array( return array(
'class' => implode(' ', $classes), 'class' => implode(' ', $classes),
); );
@ -311,9 +323,23 @@ final class PHUIListItemView extends AphrontTagView {
$classes[] = 'phui-list-item-indented'; $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(); $icons = $this->getIcons();
return javelin_tag( $list_item = javelin_tag(
$this->href ? 'a' : 'div', $this->href ? 'a' : 'div',
array( array(
'href' => $this->href, 'href' => $this->href,
@ -329,6 +355,8 @@ final class PHUIListItemView extends AphrontTagView {
$this->renderChildren(), $this->renderChildren(),
$name, $name,
)); ));
return array($list_item, $action_link);
} }
} }

View file

@ -220,7 +220,6 @@ final class PHUITimelineView extends AphrontView {
} }
$user_phid_type = PhabricatorPeopleUserPHIDType::TYPECONST; $user_phid_type = PhabricatorPeopleUserPHIDType::TYPECONST;
$badge_edge_type = PhabricatorRecipientHasBadgeEdgeType::EDGECONST;
$user_phids = array(); $user_phids = array();
foreach ($events as $key => $event) { foreach ($events as $key => $event) {
@ -244,38 +243,26 @@ final class PHUITimelineView extends AphrontView {
return; return;
} }
$users = id(new PhabricatorPeopleQuery())
$awards = id(new PhabricatorBadgesAwardQuery()) ->setViewer($viewer)
->setViewer($this->getViewer()) ->withPHIDs($user_phids)
->withRecipientPHIDs($user_phids) ->needBadgeAwards(true)
->execute(); ->execute();
$users = mpull($users, null, 'getPHID');
$awards = mgroup($awards, 'getRecipientPHID');
foreach ($events as $event) { foreach ($events as $event) {
$user_phid = $event->getAuthorPHID();
$author_awards = idx($awards, $event->getAuthorPHID(), array()); if (!array_key_exists($user_phid, $users)) {
continue;
$badges = array();
foreach ($author_awards as $award) {
$badge = $award->getBadge();
if ($badge->getStatus() == PhabricatorBadgesBadge::STATUS_ACTIVE) {
$badges[$award->getBadgePHID()] = $badge;
}
} }
$badges = $users[$user_phid]->getRecentBadgeAwards();
// TODO: Pick the "best" badges in some smart way. For now, just pick
// the first two.
$badges = array_slice($badges, 0, 2);
foreach ($badges as $badge) { foreach ($badges as $badge) {
$badge_view = id(new PHUIBadgeMiniView()) $badge_view = id(new PHUIBadgeMiniView())
->setIcon($badge->getIcon()) ->setIcon($badge['icon'])
->setQuality($badge->getQuality()) ->setQuality($badge['quality'])
->setHeader($badge->getName()) ->setHeader($badge['name'])
->setTipDirection('E') ->setTipDirection('E')
->setHref('/badges/view/'.$badge->getID()); ->setHref('/badges/view/'.$badge['id'].'/');
$event->addBadge($badge_view); $event->addBadge($badge_view);
} }
} }

View file

@ -3,7 +3,7 @@
*/ */
.aphront-dialog-view { .aphront-dialog-view {
width: 540px; width: 560px;
margin: 32px auto 16px; margin: 32px auto 16px;
border: 1px solid {$lightblueborder}; border: 1px solid {$lightblueborder};
border-radius: 3px; border-radius: 3px;
@ -32,7 +32,7 @@
} }
.aphront-dialog-view-width-form { .aphront-dialog-view-width-form {
width: 600px; width: 640px;
} }
.aphront-dialog-view-width-full { .aphront-dialog-view-width-full {

View file

@ -181,13 +181,14 @@
border: none; border: none;
} }
.device .remarkup-assist-button, .device .conpherence-message-pane .remarkup-assist-button,
.device .remarkup-assist-separator { .device .conpherence-message-pane .remarkup-assist-separator {
display: none; display: none;
} }
.device .remarkup-assist-button.remarkup-assist-upload { .device .conpherence-message-pane
display: block; .remarkup-assist-button.remarkup-assist-upload {
display: block;
} }
.device .conpherence-message-pane .phui-form-view { .device .conpherence-message-pane .phui-form-view {
@ -343,7 +344,7 @@
padding: 2px 0 8px 0; padding: 2px 0 8px 0;
} }
.conpherence-message-pane .aphront-form-control { body .conpherence-message-pane .aphront-form-control {
padding: 0; padding: 0;
} }

View file

@ -208,14 +208,13 @@
table.aphront-form-control-radio-layout, table.aphront-form-control-radio-layout,
table.aphront-form-control-checkbox-layout { table.aphront-form-control-checkbox-layout {
margin-top: 3px; margin-top: 4px !important;
font-size: {$normalfontsize}; font-size: {$normalfontsize};
} }
table.aphront-form-control-radio-layout th { table.aphront-form-control-radio-layout th {
padding-top: 3px;
padding-left: 8px; padding-left: 8px;
padding-bottom: 4px; padding-bottom: 8px;
font-weight: bold; font-weight: bold;
color: {$darkgreytext}; color: {$darkgreytext};
} }

View file

@ -2,6 +2,10 @@
* @provides phui-list-view-css * @provides phui-list-view-css
*/ */
.phui-list-item-view {
position: relative;
}
.phui-list-item-header, .phui-list-item-header,
.phui-list-item-header a { .phui-list-item-header a {
color: {$bluetext}; color: {$bluetext};
@ -188,3 +192,39 @@
margin-top: 16px; margin-top: 16px;
border-top: 1px solid {$thinblueborder}; 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;
}