1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-15 18:10:53 +01:00

(stable) Promote 2017 Week 17

This commit is contained in:
epriestley 2017-04-29 04:56:25 -07:00
commit 53bc57ecef
31 changed files with 919 additions and 192 deletions

View file

@ -9,8 +9,8 @@ return array(
'names' => array(
'conpherence.pkg.css' => 'ff161f2d',
'conpherence.pkg.js' => 'b5b51108',
'core.pkg.css' => '005d943f',
'core.pkg.js' => '47a69358',
'core.pkg.css' => '84ce260a',
'core.pkg.js' => 'fffe0122',
'darkconsole.pkg.js' => '1f9a31bc',
'differential.pkg.css' => '90b30783',
'differential.pkg.js' => 'ddfeb49b',
@ -32,7 +32,7 @@ return array(
'rsrc/css/aphront/notification.css' => '3f6c89c9',
'rsrc/css/aphront/panel-view.css' => '8427b78d',
'rsrc/css/aphront/phabricator-nav-view.css' => 'faf6a6fc',
'rsrc/css/aphront/table-view.css' => '6ca8e057',
'rsrc/css/aphront/table-view.css' => '34cf86b4',
'rsrc/css/aphront/tokenizer.css' => '9a8cb501',
'rsrc/css/aphront/tooltip.css' => '173b9431',
'rsrc/css/aphront/typeahead-browse.css' => '8904346a',
@ -42,7 +42,7 @@ return array(
'rsrc/css/application/base/main-menu-view.css' => '5294060f',
'rsrc/css/application/base/notification-menu.css' => '6a697e43',
'rsrc/css/application/base/phui-theme.css' => '9f261c6b',
'rsrc/css/application/base/standard-page-view.css' => '89da5a9c',
'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5',
'rsrc/css/application/chatlog/chatlog.css' => 'd295b020',
'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
'rsrc/css/application/config/config-options.css' => '0ede4c9b',
@ -116,7 +116,7 @@ return array(
'rsrc/css/core/core.css' => '9f4cb463',
'rsrc/css/core/remarkup.css' => '17c0fb37',
'rsrc/css/core/syntax.css' => 'cae95e89',
'rsrc/css/core/z-index.css' => '5e72c4e0',
'rsrc/css/core/z-index.css' => '0233d039',
'rsrc/css/diviner/diviner-shared.css' => '896f1d43',
'rsrc/css/font/font-awesome.css' => 'e838e088',
'rsrc/css/font/font-lato.css' => 'c7ccd872',
@ -131,7 +131,7 @@ return array(
'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77',
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'f12cbc9f',
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => '5c383524',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => '7c8ec27a',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea',
'rsrc/css/phui/phui-action-list.css' => 'c01858f4',
'rsrc/css/phui/phui-action-panel.css' => '91c7b835',
@ -146,7 +146,7 @@ return array(
'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad',
'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb',
'rsrc/css/phui/phui-curtain-view.css' => '679743bb',
'rsrc/css/phui/phui-document-pro.css' => 'f56738ed',
'rsrc/css/phui/phui-document-pro.css' => '62c4dcbf',
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
'rsrc/css/phui/phui-document.css' => 'c32e8dec',
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
@ -155,7 +155,7 @@ return array(
'rsrc/css/phui/phui-form.css' => 'a5570f70',
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
'rsrc/css/phui/phui-header-view.css' => 'e082678d',
'rsrc/css/phui/phui-hovercard.css' => 'ae091fc5',
'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf',
'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee',
'rsrc/css/phui/phui-icon.css' => '12b387a1',
'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c',
@ -508,7 +508,7 @@ return array(
'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
'rsrc/js/core/behavior-phabricator-nav.js' => '08675c6d',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '0ca788bd',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee',
'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207',
'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b',
'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e',
@ -536,7 +536,7 @@ return array(
'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b',
'rsrc/js/phuix/PHUIXAutocomplete.js' => 'd713a2c5',
'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50',
'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
@ -549,7 +549,7 @@ return array(
'aphront-list-filter-view-css' => '5d6f0526',
'aphront-multi-column-view-css' => '84cc6640',
'aphront-panel-view-css' => '8427b78d',
'aphront-table-view-css' => '6ca8e057',
'aphront-table-view-css' => '34cf86b4',
'aphront-tokenizer-control-css' => '9a8cb501',
'aphront-tooltip-css' => '173b9431',
'aphront-typeahead-control-css' => '8a84cc7d',
@ -673,7 +673,7 @@ return array(
'javelin-behavior-phabricator-notification-example' => '8ce821c5',
'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f',
'javelin-behavior-phabricator-oncopy' => '2926fff2',
'javelin-behavior-phabricator-remarkup-assist' => '0ca788bd',
'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee',
'javelin-behavior-phabricator-reveal-content' => '60821bc7',
'javelin-behavior-phabricator-search-typeahead' => 'eded9ee8',
'javelin-behavior-phabricator-show-older-transactions' => '94c65b72',
@ -809,7 +809,7 @@ return array(
'phabricator-shaped-request' => '7cbe244b',
'phabricator-slowvote-css' => 'a94b7230',
'phabricator-source-code-view-css' => '4383192f',
'phabricator-standard-page-view' => '89da5a9c',
'phabricator-standard-page-view' => 'eb5b80c5',
'phabricator-textareautils' => '320810c8',
'phabricator-title' => '485aaa6c',
'phabricator-tooltip' => '8fadb715',
@ -824,7 +824,7 @@ return array(
'phabricator-uiexample-reactor-select' => 'a155550f',
'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
'phabricator-zindex-css' => '5e72c4e0',
'phabricator-zindex-css' => '0233d039',
'phame-css' => 'b3a0b3a3',
'pholio-css' => 'ca89d380',
'pholio-edit-css' => '07676f51',
@ -853,7 +853,7 @@ return array(
'phui-curtain-view-css' => '679743bb',
'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => 'c32e8dec',
'phui-document-view-pro-css' => 'f56738ed',
'phui-document-view-pro-css' => '62c4dcbf',
'phui-feed-story-css' => '44a9c8e9',
'phui-font-icon-base-css' => '870a7360',
'phui-fontkit-css' => '1320ed01',
@ -862,7 +862,7 @@ return array(
'phui-head-thing-view-css' => 'fd311e5f',
'phui-header-view-css' => 'e082678d',
'phui-hovercard' => '1bd28176',
'phui-hovercard-view-css' => 'ae091fc5',
'phui-hovercard-view-css' => 'f0592bcf',
'phui-icon-set-selector-css' => '87db8fee',
'phui-icon-view-css' => '12b387a1',
'phui-image-mask-css' => 'a8498f9c',
@ -877,7 +877,7 @@ return array(
'phui-oi-color-css' => 'cd2b9b77',
'phui-oi-drag-ui-css' => 'f12cbc9f',
'phui-oi-flush-ui-css' => '9d9685d6',
'phui-oi-list-view-css' => '5c383524',
'phui-oi-list-view-css' => '7c8ec27a',
'phui-oi-simple-ui-css' => 'a8beebea',
'phui-pager-css' => '77d8a794',
'phui-pinboard-view-css' => '2495140e',
@ -896,7 +896,7 @@ return array(
'phui-workpanel-view-css' => 'a3a63478',
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => 'b3465b9b',
'phuix-autocomplete' => 'd713a2c5',
'phuix-autocomplete' => 'f6699267',
'phuix-dropdown-menu' => '8018ee50',
'phuix-form-control-view' => '83e03671',
'phuix-icon-view' => 'bff6884b',
@ -988,17 +988,6 @@ return array(
'javelin-dom',
'javelin-router',
),
'0ca788bd' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-phtize',
'phabricator-textareautils',
'javelin-workflow',
'javelin-vector',
'phuix-autocomplete',
'javelin-mask',
),
'0f764c35' => array(
'javelin-install',
'javelin-util',
@ -1822,6 +1811,17 @@ return array(
'javelin-util',
'phabricator-busy',
),
'acd29eee' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-phtize',
'phabricator-textareautils',
'javelin-workflow',
'javelin-vector',
'phuix-autocomplete',
'javelin-mask',
),
'b003d4fb' => array(
'javelin-behavior',
'javelin-stratcom',
@ -2087,12 +2087,6 @@ return array(
'javelin-json',
'phabricator-prefab',
),
'd713a2c5' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'd7a74243' => array(
'javelin-behavior',
'javelin-stratcom',
@ -2220,6 +2214,12 @@ return array(
'javelin-util',
'javelin-reactor',
),
'f6699267' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'f7fc67ec' => array(
'javelin-install',
'javelin-typeahead',

View file

@ -1,34 +1,4 @@
<?php
// Rebuild all Conpherence Room images to profile standards
//
$table = new ConpherenceThread();
$conn = $table->establishConnection('w');
$table_name = 'conpherence_thread';
foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) {
$images = phutil_json_decode($row['imagePHIDs']);
if (!$images) {
continue;
}
$file_phid = idx($images, 'original');
$file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($file_phid))
->executeOne();
$xform = PhabricatorFileTransform::getTransformByKey(
PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
$xformed = $xform->executeTransform($file);
$new_phid = $xformed->getPHID();
queryfx(
$conn,
'UPDATE %T SET profileImagePHID = %s WHERE id = %d',
$table->getTableName(),
$new_phid,
$row['id']);
}
// This migration once resized room images for Conpherence, but the File table
// later changed significantly. See T12628.

View file

@ -0,0 +1,34 @@
<?php
$table = new PhabricatorUser();
$conn = $table->establishConnection('w');
foreach (new LiskMigrationIterator($table) as $user) {
// Ignore users who are verified.
if ($user->getIsEmailVerified()) {
continue;
}
// Ignore unverified users with missing (rare) or unverified (common)
// primary emails: it's correct that their accounts are not verified.
$primary = $user->loadPrimaryEmail();
if (!$primary) {
continue;
}
if (!$primary->getIsVerified()) {
continue;
}
queryfx(
$conn,
'UPDATE %T SET isEmailVerified = 1 WHERE id = %d',
$table->getTableName(),
$user->getID());
echo tsprintf(
"%s\n",
pht(
'Corrected account verification state for user "%s".',
$user->getUsername()));
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_owners.owners_package
DROP originalName;

File diff suppressed because one or more lines are too long

View file

@ -3204,6 +3204,7 @@ phutil_register_library_map(array(
'PhabricatorOwnersDefaultViewCapability' => 'applications/owners/capability/PhabricatorOwnersDefaultViewCapability.php',
'PhabricatorOwnersDetailController' => 'applications/owners/controller/PhabricatorOwnersDetailController.php',
'PhabricatorOwnersEditController' => 'applications/owners/controller/PhabricatorOwnersEditController.php',
'PhabricatorOwnersHovercardEngineExtension' => 'applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php',
'PhabricatorOwnersListController' => 'applications/owners/controller/PhabricatorOwnersListController.php',
'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php',
'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php',
@ -8406,6 +8407,7 @@ phutil_register_library_map(array(
'PhabricatorOwnersDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController',
'PhabricatorOwnersEditController' => 'PhabricatorOwnersController',
'PhabricatorOwnersHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PhabricatorOwnersListController' => 'PhabricatorOwnersController',
'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO',
'PhabricatorOwnersPackage' => array(

View file

@ -205,9 +205,8 @@ abstract class AphrontApplicationConfiguration extends Phobject {
DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log);
// Add points to the rate limits for this request.
if (isset($_SERVER['REMOTE_ADDR'])) {
$user_ip = $_SERVER['REMOTE_ADDR'];
$rate_token = PhabricatorStartup::getRateLimitToken();
if ($rate_token !== null) {
// The base score for a request allows users to make 30 requests per
// minute.
$score = (1000 / 30);
@ -217,7 +216,7 @@ abstract class AphrontApplicationConfiguration extends Phobject {
$score = $score / 5;
}
PhabricatorStartup::addRateLimitScore($user_ip, $score);
PhabricatorStartup::addRateLimitScore($rate_token, $score);
}
if ($processing_exception) {

View file

@ -11,7 +11,7 @@ final class PhabricatorBadgesApplication extends PhabricatorApplication {
}
public function getShortDescription() {
return pht('Achievements and Notority');
return pht('Achievements and Notoriety');
}
public function getIcon() {

View file

@ -35,6 +35,7 @@ final class PhabricatorAccessLogConfigOptions
'P' => pht('The logged-in user PHID, if one is logged in.'),
'i' => pht('Request input, in bytes.'),
'o' => pht('Request output, in bytes.'),
'I' => pht('Cluster instance name, if configured.'),
);
$http_map = $common_map + array(

View file

@ -559,8 +559,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
return false;
}
// TODO: For now, this is only supported for Git.
if (!$repository->isGit()) {
if (!$repository->supportsSynchronization()) {
return false;
}

View file

@ -2,9 +2,33 @@
final class PhabricatorFileTransformTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testGetAllTransforms() {
PhabricatorFileTransform::getAllTransforms();
$this->assertTrue(true);
}
public function testThumbTransformDefaults() {
$xforms = PhabricatorFileTransform::getAllTransforms();
$file = new PhabricatorFile();
foreach ($xforms as $xform) {
if (!($xform instanceof PhabricatorFileThumbnailTransform)) {
continue;
}
// For thumbnails, generate the default thumbnail. This should be able
// to generate something rather than throwing an exception because we
// forgot to add a default file to the builtin resources. See T12614.
$xform->getDefaultTransform($file);
$this->assertTrue(true);
}
}
}

View file

@ -320,13 +320,16 @@ final class LegalpadDocumentSignController extends LegalpadController {
$crumbs->setBorder(true);
$crumbs->addTextCrumb($document->getMonogram());
$box = id(new PHUITwoColumnView())
->setFooter($signature_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($document->getPHID()))
->appendChild(array(
$content,
$signature_box,
$box,
));
}

View file

@ -24,24 +24,52 @@ final class ManiphestHovercardEngineExtension
$task,
$data) {
$viewer = $this->getViewer();
require_celerity_resource('phui-workcard-view-css');
$hovercard
->setTitle($task->getMonogram())
->setDetail($task->getTitle());
$id = $task->getID();
$task = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withIDs(array($id))
->needProjectPHIDs(true)
->executeOne();
$phids = array();
$owner_phid = $task->getOwnerPHID();
if ($owner_phid) {
$phids[$owner_phid] = $owner_phid;
}
foreach ($task->getProjectPHIDs() as $phid) {
$phids[$phid] = $phid;
}
$handles = $viewer->loadHandles($phids);
$handles = iterator_to_array($handles);
$card = id(new ProjectBoardTaskCard())
->setViewer($viewer)
->setTask($task)
->setCanEdit(false);
$owner_phid = $task->getOwnerPHID();
if ($owner_phid) {
$owner = $viewer->renderHandle($owner_phid);
} else {
$owner = phutil_tag('em', array(), pht('None'));
$owner_handle = $handles[$owner_phid];
$card->setOwner($owner_handle);
}
$hovercard->addField(pht('Assigned To'), $owner);
$hovercard->addField(
pht('Priority'),
ManiphestTaskPriority::getTaskPriorityName($task->getPriority()));
$project_phids = $task->getProjectPHIDs();
$project_handles = array_select_keys($handles, $project_phids);
if ($project_handles) {
$card->setProjectHandles($project_handles);
}
$item = $card->getItem();
$card = id(new PHUIObjectItemListView())
->setFlush(true)
->setItemClass('phui-workcard')
->addClass('hovercard-task-view')
->addItem($item);
$hovercard->appendChild($card);
$hovercard->addTag(ManiphestView::renderTagForTask($task));
}
}

View file

@ -323,6 +323,10 @@ final class PhabricatorOwnersDetailController
'wide',
));
if ($info) {
$table->setNotice($info);
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Paths'))
->setHeaderIcon('fa-folder-open');
@ -332,10 +336,6 @@ final class PhabricatorOwnersDetailController
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
if ($info) {
$box->setInfoView($info);
}
return $box;
}

View file

@ -0,0 +1,89 @@
<?php
final class PhabricatorOwnersHovercardEngineExtension
extends PhabricatorHovercardEngineExtension {
const EXTENSIONKEY = 'owners';
public function isExtensionEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorOwnersApplication');
}
public function getExtensionName() {
return pht('Owner Packages');
}
public function canRenderObjectHovercard($object) {
return ($object instanceof PhabricatorOwnersPackage);
}
public function willRenderHovercards(array $objects) {
$viewer = $this->getViewer();
$phids = mpull($objects, 'getPHID');
$packages = id(new PhabricatorOwnersPackageQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
$packages = mpull($packages, null, 'getPHID');
return array(
'packages' => $packages,
);
}
public function renderHovercard(
PHUIHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$object,
$data) {
$viewer = $this->getViewer();
$package = idx($data['packages'], $object->getPHID());
if (!$package) {
return;
}
$title = pht('%s: %s', 'O'.$package->getID(), $package->getName());
$hovercard->setTitle($title);
$dominion = $package->getDominion();
$dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap();
$spec = idx($dominion_map, $dominion, array());
$name = idx($spec, 'short', $dominion);
$hovercard->addField(pht('Dominion'), $name);
$auto = $package->getAutoReview();
$autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
$spec = idx($autoreview_map, $auto, array());
$name = idx($spec, 'name', $auto);
$hovercard->addField(pht('Auto Review'), $name);
if ($package->isArchived()) {
$tag = id(new PHUITagView())
->setName(pht('Archived'))
->setShade(PHUITagView::COLOR_INDIGO)
->setType(PHUITagView::TYPE_OBJECT);
$hovercard->addTag($tag);
}
$owner_phids = $package->getOwnerPHIDs();
$hovercard->addField(
pht('Owners'),
$viewer->renderHandleList($owner_phids)->setAsInline(true));
$description = $package->getDescription();
if (strlen($description)) {
$description = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(120)
->truncateString($description);
$hovercard->addField(pht('Description'), $description);
}
}
}

View file

@ -12,7 +12,6 @@ final class PhabricatorOwnersPackage
PhabricatorNgramsInterface {
protected $name;
protected $originalName;
protected $auditingEnabled;
protected $autoReview;
protected $description;
@ -105,8 +104,7 @@ final class PhabricatorOwnersPackage
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort128',
'originalName' => 'text255',
'name' => 'sort',
'description' => 'text',
'primaryOwnerPHID' => 'phid?',
'auditingEnabled' => 'bool',
@ -137,9 +135,6 @@ final class PhabricatorOwnersPackage
public function setName($name) {
$this->name = $name;
if (!$this->getID()) {
$this->originalName = $name;
}
return $this;
}

View file

@ -540,6 +540,14 @@ final class PhabricatorUserEditor extends PhabricatorEditor {
$email->setIsPrimary(1);
$email->save();
// If the user doesn't have the verified flag set on their account
// yet, set it. We've made sure the email is verified above. See
// T12635 for discussion.
if (!$user->getIsEmailVerified()) {
$user->setIsEmailVerified(1);
$user->save();
}
$log = PhabricatorUserLog::initializeNewLog(
$actor,
$user->getPHID(),

View file

@ -1929,10 +1929,31 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
'Cluster hosts must correctly route their intracluster requests.'));
}
if (count($results) > 1) {
if (!$this->supportsSynchronization()) {
throw new Exception(
pht(
'Repository "%s" is bound to multiple active repository hosts, '.
'but this repository does not support cluster synchronization. '.
'Declusterize this repository or move it to a service with only '.
'one host.',
$this->getDisplayName()));
}
}
shuffle($results);
return head($results);
}
public function supportsSynchronization() {
// TODO: For now, this is only supported for Git.
if (!$this->isGit()) {
return false;
}
return true;
}
public function getAlmanacServiceCacheKey() {
$service_phid = $this->getAlmanacServicePHID();
if (!$service_phid) {

View file

@ -1,8 +1,7 @@
@title Configuring a Preamble Script
@group config
Adjust environmental settings (SSL, remote IP, rate limiting) using a preamble
script.
Adjust environmental settings (SSL, remote IPs) using a preamble script.
Overview
========

View file

@ -30,6 +30,7 @@ final class PhabricatorAccessLog extends Phobject {
'h' => php_uname('n'),
'p' => getmypid(),
'e' => time(),
'I' => PhabricatorEnv::getEnvConfig('cluster.instance'),
));
self::$log = $log;

View file

@ -20,6 +20,7 @@ final class PhabricatorSSHLog extends Phobject {
'h' => php_uname('n'),
'p' => getmypid(),
'e' => time(),
'I' => PhabricatorEnv::getEnvConfig('cluster.instance'),
);
$sudo_user = PhabricatorEnv::getEnvConfig('phd.user');

View file

@ -112,14 +112,17 @@ final class PHUIHovercardView extends AphrontTagView {
$body = array();
$body_title = null;
if ($this->detail) {
$body_title = $this->detail;
} else {
} else if (!$this->fields) {
// Fallback for object handles
$body_title = $handle->getFullName();
}
if ($body_title) {
$body[] = phutil_tag_div('phui-hovercard-body-header', $body_title);
}
foreach ($this->fields as $field) {
$item = array(

View file

@ -50,6 +50,7 @@ final class PhabricatorStartup {
// iterate on it a bit for Conduit, some of the specific score levels, and
// to deal with NAT'd offices.
private static $maximumRate = 0;
private static $rateLimitToken;
/* -( Accessing Request Information )-------------------------------------- */
@ -137,8 +138,9 @@ final class PhabricatorStartup {
// we can switch over to relying on our own exception recovery mechanisms.
ini_set('display_errors', 0);
if (isset($_SERVER['REMOTE_ADDR'])) {
self::rateLimitRequest($_SERVER['REMOTE_ADDR']);
$rate_token = self::getRateLimitToken();
if ($rate_token !== null) {
self::rateLimitRequest($rate_token);
}
self::normalizeInput();
@ -680,6 +682,36 @@ final class PhabricatorStartup {
}
/**
* Set a token to identify the client for purposes of rate limiting.
*
* By default, the `REMOTE_ADDR` is used. If your install is behind a load
* balancer, you may want to parse `X-Forwarded-For` and use that address
* instead.
*
* @param string Client identity for rate limiting.
*/
public static function setRateLimitToken($token) {
self::$rateLimitToken = $token;
}
/**
* Get the current client identity for rate limiting.
*/
public static function getRateLimitToken() {
if (self::$rateLimitToken !== null) {
return self::$rateLimitToken;
}
if (isset($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
}
return null;
}
/**
* Check if the user (identified by `$user_identity`) has issued too many
* requests recently. If they have, end the request with a 429 error code.
@ -699,13 +731,18 @@ final class PhabricatorStartup {
}
$score = self::getRateLimitScore($user_identity);
if ($score > (self::$maximumRate * self::getRateLimitBucketCount())) {
$limit = self::$maximumRate * self::getRateLimitBucketCount();
if ($score > $limit) {
// Give the user some bonus points for getting rate limited. This keeps
// bad actors who keep slamming the 429 page locked out completely,
// instead of letting them get a burst of requests through every minute
// after a bucket expires.
self::addRateLimitScore($user_identity, 50);
self::didRateLimit($user_identity);
$penalty = 50;
self::addRateLimitScore($user_identity, $penalty);
$score += $penalty;
self::didRateLimit($user_identity, $score, $limit);
}
}
@ -729,15 +766,21 @@ final class PhabricatorStartup {
return;
}
$is_apcu = (bool)function_exists('apcu_fetch');
$current = self::getRateLimitBucket();
// There's a bit of a race here, if a second process reads the bucket before
// this one writes it, but it's fine if we occasionally fail to record a
// user's score. If they're making requests fast enough to hit rate
// limiting, we'll get them soon.
// There's a bit of a race here, if a second process reads the bucket
// before this one writes it, but it's fine if we occasionally fail to
// record a user's score. If they're making requests fast enough to hit
// rate limiting, we'll get them soon enough.
$bucket_key = self::getRateLimitBucketKey($current);
if ($is_apcu) {
$bucket = apcu_fetch($bucket_key);
} else {
$bucket = apc_fetch($bucket_key);
}
if (!is_array($bucket)) {
$bucket = array();
}
@ -747,8 +790,13 @@ final class PhabricatorStartup {
}
$bucket[$user_identity] += $score;
if ($is_apcu) {
apcu_store($bucket_key, $bucket);
} else {
apc_store($bucket_key, $bucket);
}
}
/**
@ -761,11 +809,12 @@ final class PhabricatorStartup {
* @task ratelimit
*/
private static function canRateLimit() {
if (!self::$maximumRate) {
return false;
}
if (!function_exists('apc_fetch')) {
if (!function_exists('apc_fetch') && !function_exists('apcu_fetch')) {
return false;
}
@ -826,16 +875,26 @@ final class PhabricatorStartup {
* @task ratelimit
*/
private static function getRateLimitScore($user_identity) {
$is_apcu = (bool)function_exists('apcu_fetch');
$min_key = self::getRateLimitMinKey();
// Identify the oldest bucket stored in APC.
$cur = self::getRateLimitBucket();
if ($is_apcu) {
$min = apcu_fetch($min_key);
} else {
$min = apc_fetch($min_key);
}
// If we don't have any buckets stored yet, store the current bucket as
// the oldest bucket.
if (!$min) {
if ($is_apcu) {
apcu_store($min_key, $cur);
} else {
apc_store($min_key, $cur);
}
$min = $cur;
}
@ -844,14 +903,25 @@ final class PhabricatorStartup {
// up an old bucket once per minute.
$count = self::getRateLimitBucketCount();
for ($cursor = $min; $cursor < ($cur - $count); $cursor++) {
apc_delete(self::getRateLimitBucketKey($cursor));
$bucket_key = self::getRateLimitBucketKey($cursor);
if ($is_apcu) {
apcu_delete($bucket_key);
apcu_store($min_key, $cursor + 1);
} else {
apc_delete($bucket_key);
apc_store($min_key, $cursor + 1);
}
}
// Now, sum up the user's scores in all of the active buckets.
$score = 0;
for (; $cursor <= $cur; $cursor++) {
$bucket = apc_fetch(self::getRateLimitBucketKey($cursor));
$bucket_key = self::getRateLimitBucketKey($cursor);
if ($is_apcu) {
$bucket = apcu_fetch($bucket_key);
} else {
$bucket = apc_fetch($bucket_key);
}
if (isset($bucket[$user_identity])) {
$score += $bucket[$user_identity];
}
@ -868,12 +938,11 @@ final class PhabricatorStartup {
* @return exit This method **does not return**.
* @task ratelimit
*/
private static function didRateLimit() {
private static function didRateLimit($user_identity, $score, $limit) {
$message =
"TOO MANY REQUESTS\n".
"You are issuing too many requests too quickly.\n".
"To adjust limits, see \"Configuring a Preamble Script\" in the ".
"documentation.";
"You (\"{$user_identity}\") are issuing too many requests ".
"too quickly.\n";
header(
'Content-Type: text/plain; charset=utf-8',

View file

@ -29,6 +29,10 @@
border-bottom: 1px solid {$thinblueborder};
}
.phui-two-column-view .aphront-table-notice .phui-info-view {
margin: 0;
}
.aphront-table-view tr.alt {
background: {$lightgreybackground};
}

View file

@ -61,9 +61,10 @@ body.white-background {
}
.keyboard-focus-focus-reticle {
background: #ffffd3;
background: rgba(255, 255, 211, 0.15);
position: absolute;
border: 1px solid #999900;
border: 1px solid {$yellow};
pointer-events: none;
}
a.handle-status-closed {

View file

@ -2,16 +2,16 @@
* @provides phabricator-zindex-css
*/
.keyboard-focus-focus-reticle {
z-index: 1;
}
.device .phabricator-action-list-view.phabricator-action-list-toggle,
.device-desktop .phui-document-content
.phabricator-action-list-view.phabricator-action-list-toggle {
z-index: 1;
}
.keyboard-focus-focus-reticle {
z-index: 2;
}
.device-desktop .phui-timeline-minor-event .phui-timeline-image {
z-index: 2;
}

View file

@ -302,6 +302,7 @@ ul.phui-oi-list-view {
display: inline-block;
color: {$greytext};
vertical-align: top;
margin-right: 4px;
}
.phui-oi-attribute-spacer {

View file

@ -74,7 +74,7 @@ body.printable {
a.button.phui-document-toc {
display: inline-block;
height: 16px;
width: 16px;
width: 20px;
padding: 3px 8px 4px 8px;
}
@ -88,7 +88,7 @@ a.button.phui-document-toc {
z-index: 30;
background-color: #fff;
top: 52px;
left: -44px;
left: -40px;
}
.device .phui-document-view-pro .phui-document-toc {

View file

@ -105,3 +105,11 @@
.phui-hovercard-tail a.button {
margin: 3px;
}
.phui-hovercard-wrapper .hovercard-task-view {
box-shadow: 0px 4px 16px rgba(0,0,0,.2);
}
.hovercard-task-view .phui-oi-disabled.phui-workcard {
background-color: #fff;
}

View file

@ -393,6 +393,12 @@ JX.behavior('phabricator-remarkup-assist', function(config) {
return;
}
// Let other listeners (particularly the inline autocomplete) have a
// chance to handle this event.
if (JX.Stratcom.pass()) {
return;
}
var raw = e.getRawEvent();
if (raw.shiftKey) {
// If the shift key is pressed, let the browser write a newline into
@ -412,8 +418,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) {
// This allows 'workflow' and similar actions to take effect.
// Such as pontificate in Conpherence
var form = e.getNode('tag:form');
var r = JX.DOM.invoke(form, 'didSyntheticSubmit');
JX.DOM.invoke(form, 'didSyntheticSubmit');
});
}

View file

@ -127,7 +127,7 @@ JX.install('PHUIXAutocomplete', {
}
// Get all the text on the current line. If the line only contains
// whitespace, don't actiavte: the user is probably typing code or a
// whitespace, don't activate: the user is probably typing code or a
// numbered list.
var line = area.value.substring(0, head - 1);
line = line.split('\n');
@ -454,7 +454,7 @@ JX.install('PHUIXAutocomplete', {
// If the user hasn't typed any text yet after typing the character
// which can summon the autocomplete, deactivate and let the keystroke
// through. For example, We hit this when a line ends with an
// through. For example, we hit this when a line ends with an
// autocomplete character and the user is trying to type a newline.
if (range.start == this._cursorHead) {
this._deactivate();
@ -529,9 +529,13 @@ JX.install('PHUIXAutocomplete', {
}
}
// Deactivate immediately if the user types an ignored token like ":)",
// the smiley face emoticon. Note that we test against "text", not
// "trim", because the ignore list and suffix list can otherwise
// interact destructively.
var ignore = this._getIgnoreList();
for (ii = 0; ii < ignore.length; ii++) {
if (trim.indexOf(ignore[ii]) === 0) {
if (text.indexOf(ignore[ii]) === 0) {
this._deactivate();
return;
}