Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php
index d215d2f67a..0a33f6f77c 100644
--- a/src/aphront/configuration/AphrontApplicationConfiguration.php
+++ b/src/aphront/configuration/AphrontApplicationConfiguration.php
@@ -1,466 +1,467 @@
<?php
/**
* @task routing URI Routing
*/
abstract class AphrontApplicationConfiguration extends Phobject {
private $request;
private $host;
private $path;
private $console;
abstract public function getApplicationName();
abstract public function buildRequest();
abstract public function build404Controller();
abstract public function buildRedirectController($uri, $external);
final public function setRequest(AphrontRequest $request) {
$this->request = $request;
return $this;
}
final public function getRequest() {
return $this->request;
}
final public function getConsole() {
return $this->console;
}
final public function setConsole($console) {
$this->console = $console;
return $this;
}
final public function setHost($host) {
$this->host = $host;
return $this;
}
final public function getHost() {
return $this->host;
}
final public function setPath($path) {
$this->path = $path;
return $this;
}
final public function getPath() {
return $this->path;
}
public function willBuildRequest() {}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public static function runHTTPRequest(AphrontHTTPSink $sink) {
$multimeter = MultimeterControl::newInstance();
$multimeter->setEventContext('<http-init>');
$multimeter->setEventViewer('<none>');
// Build a no-op write guard for the setup phase. We'll replace this with a
// real write guard later on, but we need to survive setup and build a
// request object first.
$write_guard = new AphrontWriteGuard('id');
PhabricatorEnv::initializeWebEnvironment();
$multimeter->setSampleRate(
PhabricatorEnv::getEnvConfig('debug.sample-rate'));
$debug_time_limit = PhabricatorEnv::getEnvConfig('debug.time-limit');
if ($debug_time_limit) {
PhabricatorStartup::setDebugTimeLimit($debug_time_limit);
}
// This is the earliest we can get away with this, we need env config first.
PhabricatorAccessLog::init();
$access_log = PhabricatorAccessLog::getLog();
PhabricatorStartup::setAccessLog($access_log);
$access_log->setData(
array(
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
'r' => idx($_SERVER, 'REMOTE_ADDR', '-'),
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
));
DarkConsoleXHProfPluginAPI::hookProfiler();
DarkConsoleErrorLogPluginAPI::registerErrorHandler();
$response = PhabricatorSetupCheck::willProcessRequest();
if ($response) {
PhabricatorStartup::endOutputCapture();
$sink->writeResponse($response);
return;
}
$host = AphrontRequest::getHTTPHeader('Host');
$path = $_REQUEST['__path__'];
switch ($host) {
default:
$config_key = 'aphront.default-application-configuration-class';
$application = PhabricatorEnv::newObjectFromConfig($config_key);
break;
}
$application->setHost($host);
$application->setPath($path);
$application->willBuildRequest();
$request = $application->buildRequest();
// Now that we have a request, convert the write guard into one which
// actually checks CSRF tokens.
$write_guard->dispose();
$write_guard = new AphrontWriteGuard(array($request, 'validateCSRF'));
// Build the server URI implied by the request headers. If an administrator
// has not configured "phabricator.base-uri" yet, we'll use this to generate
// links.
$request_protocol = ($request->isHTTPS() ? 'https' : 'http');
$request_base_uri = "{$request_protocol}://{$host}/";
PhabricatorEnv::setRequestBaseURI($request_base_uri);
$access_log->setData(
array(
'U' => (string)$request->getRequestURI()->getPath(),
));
$processing_exception = null;
try {
$response = $application->processRequest(
$request,
$access_log,
$sink,
$multimeter);
$response_code = $response->getHTTPResponseCode();
} catch (Exception $ex) {
$processing_exception = $ex;
$response_code = 500;
}
$write_guard->dispose();
$access_log->setData(
array(
'c' => $response_code,
'T' => PhabricatorStartup::getMicrosecondsSinceStart(),
));
$multimeter->newEvent(
MultimeterEvent::TYPE_REQUEST_TIME,
$multimeter->getEventContext(),
PhabricatorStartup::getMicrosecondsSinceStart());
$access_log->write();
$multimeter->saveEvents();
DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log);
// Add points to the rate limits for this request.
if (isset($_SERVER['REMOTE_ADDR'])) {
$user_ip = $_SERVER['REMOTE_ADDR'];
// The base score for a request allows users to make 30 requests per
// minute.
$score = (1000 / 30);
// If the user was logged in, let them make more requests.
if ($request->getUser() && $request->getUser()->getPHID()) {
$score = $score / 5;
}
PhabricatorStartup::addRateLimitScore($user_ip, $score);
}
if ($processing_exception) {
throw $processing_exception;
}
}
public function processRequest(
AphrontRequest $request,
PhutilDeferredLog $access_log,
AphrontHTTPSink $sink,
MultimeterControl $multimeter) {
$this->setRequest($request);
list($controller, $uri_data) = $this->buildController();
$controller_class = get_class($controller);
$access_log->setData(
array(
'C' => $controller_class,
));
$multimeter->setEventContext('web.'.$controller_class);
$request->setURIMap($uri_data);
$controller->setRequest($request);
// If execution throws an exception and then trying to render that
// exception throws another exception, we want to show the original
// exception, as it is likely the root cause of the rendering exception.
$original_exception = null;
try {
$response = $controller->willBeginExecution();
if ($request->getUser() && $request->getUser()->getPHID()) {
$access_log->setData(
array(
'u' => $request->getUser()->getUserName(),
'P' => $request->getUser()->getPHID(),
));
$multimeter->setEventViewer('user.'.$request->getUser()->getPHID());
}
if (!$response) {
$controller->willProcessRequest($uri_data);
$response = $controller->handleRequest($request);
}
} catch (Exception $ex) {
$original_exception = $ex;
$response = $this->handleException($ex);
}
try {
$response = $controller->didProcessRequest($response);
$response = $this->willSendResponse($response, $controller);
$response->setRequest($request);
$unexpected_output = PhabricatorStartup::endOutputCapture();
if ($unexpected_output) {
$unexpected_output = pht(
"Unexpected output:\n\n%s",
$unexpected_output);
phlog($unexpected_output);
if ($response instanceof AphrontWebpageResponse) {
echo phutil_tag(
'div',
- array('style' =>
- 'background: #eeddff;'.
- 'white-space: pre-wrap;'.
- 'z-index: 200000;'.
- 'position: relative;'.
- 'padding: 8px;'.
- 'font-family: monospace',
+ array(
+ 'style' =>
+ 'background: #eeddff;'.
+ 'white-space: pre-wrap;'.
+ 'z-index: 200000;'.
+ 'position: relative;'.
+ 'padding: 8px;'.
+ 'font-family: monospace',
),
$unexpected_output);
}
}
$sink->writeResponse($response);
} catch (Exception $ex) {
if ($original_exception) {
throw $original_exception;
}
throw $ex;
}
return $response;
}
/* -( URI Routing )-------------------------------------------------------- */
/**
* Using builtin and application routes, build the appropriate
* @{class:AphrontController} class for the request. To route a request, we
* first test if the HTTP_HOST is configured as a valid Phabricator URI. If
* it isn't, we do a special check to see if it's a custom domain for a blog
* in the Phame application and if that fails we error. Otherwise, we test
* against all application routes from installed
* @{class:PhabricatorApplication}s.
*
* If we match a route, we construct the controller it points at, build it,
* and return it.
*
* If we fail to match a route, but the current path is missing a trailing
* "/", we try routing the same path with a trailing "/" and do a redirect
* if that has a valid route. The idea is to canoncalize URIs for consistency,
* but avoid breaking noncanonical URIs that we can easily salvage.
*
* NOTE: We only redirect on GET. On POST, we'd drop parameters and most
* likely mutate the request implicitly, and a bad POST usually indicates a
* programming error rather than a sloppy typist.
*
* If the failing path already has a trailing "/", or we can't route the
* version with a "/", we call @{method:build404Controller}, which build a
* fallback @{class:AphrontController}.
*
* @return pair<AphrontController,dict> Controller and dictionary of request
* parameters.
* @task routing
*/
final public function buildController() {
$request = $this->getRequest();
// If we're configured to operate in cluster mode, reject requests which
// were not received on a cluster interface.
//
// For example, a host may have an internal address like "170.0.0.1", and
// also have a public address like "51.23.95.16". Assuming the cluster
// is configured on a range like "170.0.0.0/16", we want to reject the
// requests received on the public interface.
//
// Ideally, nodes in a cluster should only be listening on internal
// interfaces, but they may be configured in such a way that they also
// listen on external interfaces, since this is easy to forget about or
// get wrong. As a broad security measure, reject requests received on any
// interfaces which aren't on the whitelist.
$cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses');
if ($cluster_addresses) {
$server_addr = idx($_SERVER, 'SERVER_ADDR');
if (!$server_addr) {
if (php_sapi_name() == 'cli') {
// This is a command line script (probably something like a unit
// test) so it's fine that we don't have SERVER_ADDR defined.
} else {
throw new AphrontUsageException(
pht('No %s', 'SERVER_ADDR'),
pht(
'Phabricator is configured to operate in cluster mode, but '.
'%s is not defined in the request context. Your webserver '.
'configuration needs to forward %s to PHP so Phabricator can '.
'reject requests received on external interfaces.',
'SERVER_ADDR',
'SERVER_ADDR'));
}
} else {
if (!PhabricatorEnv::isClusterAddress($server_addr)) {
throw new AphrontUsageException(
pht('External Interface'),
pht(
'Phabricator is configured in cluster mode and the address '.
'this request was received on ("%s") is not whitelisted as '.
'a cluster address.',
$server_addr));
}
}
}
$site = $this->buildSiteForRequest($request);
if ($site->shouldRequireHTTPS()) {
if (!$request->isHTTPS()) {
$https_uri = $request->getRequestURI();
$https_uri->setDomain($request->getHost());
$https_uri->setProtocol('https');
// In this scenario, we'll be redirecting to HTTPS using an absolute
// URI, so we need to permit an external redirect.
return $this->buildRedirectController($https_uri, true);
}
}
// TODO: Really, the Site should get more control here and be able to
// do its own routing logic if it wants, but we don't need that for now.
$path = $site->getPathForRouting($request);
list($controller, $uri_data) = $this->buildControllerForPath($path);
if (!$controller) {
if (!preg_match('@/$@', $path)) {
// If we failed to match anything but don't have a trailing slash, try
// to add a trailing slash and issue a redirect if that resolves.
list($controller, $uri_data) = $this->buildControllerForPath($path.'/');
// NOTE: For POST, just 404 instead of redirecting, since the redirect
// will be a GET without parameters.
if ($controller && !$request->isHTTPPost()) {
$slash_uri = $request->getRequestURI()->setPath($path.'/');
$external = strlen($request->getRequestURI()->getDomain());
return $this->buildRedirectController($slash_uri, $external);
}
}
return $this->build404Controller();
}
return array($controller, $uri_data);
}
/**
* Map a specific path to the corresponding controller. For a description
* of routing, see @{method:buildController}.
*
* @return pair<AphrontController,dict> Controller and dictionary of request
* parameters.
* @task routing
*/
final public function buildControllerForPath($path) {
$maps = array();
$applications = PhabricatorApplication::getAllInstalledApplications();
foreach ($applications as $application) {
$maps[] = array($application, $application->getRoutes());
}
$current_application = null;
$controller_class = null;
foreach ($maps as $map_info) {
list($application, $map) = $map_info;
$mapper = new AphrontURIMapper($map);
list($controller_class, $uri_data) = $mapper->mapPath($path);
if ($controller_class) {
if ($application) {
$current_application = $application;
}
break;
}
}
if (!$controller_class) {
return array(null, null);
}
$request = $this->getRequest();
$controller = newv($controller_class, array());
if ($current_application) {
$controller->setCurrentApplication($current_application);
}
return array($controller, $uri_data);
}
private function buildSiteForRequest(AphrontRequest $request) {
$sites = PhabricatorSite::getAllSites();
$site = null;
foreach ($sites as $candidate) {
$site = $candidate->newSiteForRequest($request);
if ($site) {
break;
}
}
if (!$site) {
$path = $request->getPath();
$host = $request->getHost();
throw new Exception(
pht(
'This request asked for "%s" on host "%s", but no site is '.
'configured which can serve this request.',
$path,
$host));
}
return $site;
}
}
diff --git a/src/applications/conpherence/__tests__/ConpherenceTestCase.php b/src/applications/conpherence/__tests__/ConpherenceTestCase.php
index 8de135f24e..ce9dc8ceee 100644
--- a/src/applications/conpherence/__tests__/ConpherenceTestCase.php
+++ b/src/applications/conpherence/__tests__/ConpherenceTestCase.php
@@ -1,75 +1,77 @@
<?php
abstract class ConpherenceTestCase extends PhabricatorTestCase {
protected function addParticipants(
PhabricatorUser $actor,
ConpherenceThread $conpherence,
array $participant_phids) {
- $xactions = array(id(new ConpherenceTransaction())
- ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS)
- ->setNewValue(array('+' => $participant_phids)),
+ $xactions = array(
+ id(new ConpherenceTransaction())
+ ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS)
+ ->setNewValue(array('+' => $participant_phids)),
);
$editor = id(new ConpherenceEditor())
->setActor($actor)
->setContentSource(PhabricatorContentSource::newConsoleSource())
->applyTransactions($conpherence, $xactions);
}
protected function removeParticipants(
PhabricatorUser $actor,
ConpherenceThread $conpherence,
array $participant_phids) {
- $xactions = array(id(new ConpherenceTransaction())
- ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS)
- ->setNewValue(array('-' => $participant_phids)),
+ $xactions = array(
+ id(new ConpherenceTransaction())
+ ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS)
+ ->setNewValue(array('-' => $participant_phids)),
);
$editor = id(new ConpherenceEditor())
->setActor($actor)
->setContentSource(PhabricatorContentSource::newConsoleSource())
->applyTransactions($conpherence, $xactions);
}
protected function addMessageWithFile(
PhabricatorUser $actor,
ConpherenceThread $conpherence) {
$file = $this->generateTestFile($actor);
$message = Filesystem::readRandomCharacters(64).
sprintf(' {%s} ', $file->getMonogram());
$editor = id(new ConpherenceEditor())
->setActor($actor)
->setContentSource(PhabricatorContentSource::newConsoleSource());
$xactions = $editor->generateTransactionsFromText(
$actor,
$conpherence,
$message);
return $editor->applyTransactions($conpherence, $xactions);
}
private function generateTestFile(PhabricatorUser $actor) {
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$params = array(
'name' => 'test.'.$actor->getPHID(),
'viewPolicy' => $actor->getPHID(),
'authorPHID' => $actor->getPHID(),
'storageEngines' => array(
$engine,
),
);
$file = PhabricatorFile::newFromFileData($data, $params);
$file->save();
return $file;
}
}
diff --git a/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php
index 4a5b749268..41f5f34770 100644
--- a/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php
+++ b/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php
@@ -1,167 +1,168 @@
<?php
final class DifferentialCreateDiffConduitAPIMethod
extends DifferentialConduitAPIMethod {
public function getAPIMethodName() {
return 'differential.creatediff';
}
public function getMethodDescription() {
return pht('Create a new Differential diff.');
}
protected function defineParamTypes() {
$vcs_const = $this->formatStringConstants(
array(
'svn',
'git',
'hg',
));
$status_const = $this->formatStringConstants(
array(
'none',
'skip',
'okay',
'warn',
'fail',
'postponed',
));
return array(
'changes' => 'required list<dict>',
'sourceMachine' => 'required string',
'sourcePath' => 'required string',
'branch' => 'required string',
'bookmark' => 'optional string',
'sourceControlSystem' => 'required '.$vcs_const,
'sourceControlPath' => 'required string',
'sourceControlBaseRevision' => 'required string',
'creationMethod' => 'optional string',
'lintStatus' => 'required '.$status_const,
'unitStatus' => 'required '.$status_const,
'repositoryPHID' => 'optional phid',
'parentRevisionID' => 'deprecated',
'authorPHID' => 'deprecated',
'repositoryUUID' => 'deprecated',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function execute(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$change_data = $request->getValue('changes');
$changes = array();
foreach ($change_data as $dict) {
$changes[] = ArcanistDiffChange::newFromDictionary($dict);
}
$diff = DifferentialDiff::newFromRawChanges($viewer, $changes);
// TODO: Remove repository UUID eventually; for now continue writing
// the UUID. Note that we'll overwrite it below if we identify a
// repository, and `arc` no longer sends it. This stuff is retained for
// backward compatibility.
$repository_uuid = $request->getValue('repositoryUUID');
$repository_phid = $request->getValue('repositoryPHID');
if ($repository_phid) {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withPHIDs(array($repository_phid))
->executeOne();
if ($repository) {
$repository_phid = $repository->getPHID();
$repository_uuid = $repository->getUUID();
}
}
switch ($request->getValue('lintStatus')) {
case 'skip':
$lint_status = DifferentialLintStatus::LINT_SKIP;
break;
case 'okay':
$lint_status = DifferentialLintStatus::LINT_OKAY;
break;
case 'warn':
$lint_status = DifferentialLintStatus::LINT_WARN;
break;
case 'fail':
$lint_status = DifferentialLintStatus::LINT_FAIL;
break;
case 'postponed':
$lint_status = DifferentialLintStatus::LINT_POSTPONED;
break;
case 'none':
default:
$lint_status = DifferentialLintStatus::LINT_NONE;
break;
}
switch ($request->getValue('unitStatus')) {
case 'skip':
$unit_status = DifferentialUnitStatus::UNIT_SKIP;
break;
case 'okay':
$unit_status = DifferentialUnitStatus::UNIT_OKAY;
break;
case 'warn':
$unit_status = DifferentialUnitStatus::UNIT_WARN;
break;
case 'fail':
$unit_status = DifferentialUnitStatus::UNIT_FAIL;
break;
case 'postponed':
$unit_status = DifferentialUnitStatus::UNIT_POSTPONED;
break;
case 'none':
default:
$unit_status = DifferentialUnitStatus::UNIT_NONE;
break;
}
$diff_data_dict = array(
'sourcePath' => $request->getValue('sourcePath'),
'sourceMachine' => $request->getValue('sourceMachine'),
'branch' => $request->getValue('branch'),
'creationMethod' => $request->getValue('creationMethod'),
'authorPHID' => $viewer->getPHID(),
'bookmark' => $request->getValue('bookmark'),
'repositoryUUID' => $repository_uuid,
'repositoryPHID' => $repository_phid,
'sourceControlSystem' => $request->getValue('sourceControlSystem'),
'sourceControlPath' => $request->getValue('sourceControlPath'),
'sourceControlBaseRevision' =>
$request->getValue('sourceControlBaseRevision'),
'lintStatus' => $lint_status,
'unitStatus' => $unit_status,
);
- $xactions = array(id(new DifferentialTransaction())
- ->setTransactionType(DifferentialDiffTransaction::TYPE_DIFF_CREATE)
- ->setNewValue($diff_data_dict),
+ $xactions = array(
+ id(new DifferentialTransaction())
+ ->setTransactionType(DifferentialDiffTransaction::TYPE_DIFF_CREATE)
+ ->setNewValue($diff_data_dict),
);
id(new DifferentialDiffEditor())
->setActor($viewer)
->setContentSourceFromConduitRequest($request)
->setContinueOnNoEffect(true)
->applyTransactions($diff, $xactions);
$path = '/differential/diff/'.$diff->getID().'/';
$uri = PhabricatorEnv::getURI($path);
return array(
'diffid' => $diff->getID(),
'phid' => $diff->getPHID(),
'uri' => $uri,
);
}
}
diff --git a/src/applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php
index e8dd29f86a..c69a06bd9a 100644
--- a/src/applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php
+++ b/src/applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php
@@ -1,75 +1,76 @@
<?php
final class DifferentialCreateRawDiffConduitAPIMethod
extends DifferentialConduitAPIMethod {
public function getAPIMethodName() {
return 'differential.createrawdiff';
}
public function getMethodDescription() {
return pht('Create a new Differential diff from a raw diff source.');
}
protected function defineParamTypes() {
return array(
'diff' => 'required string',
'repositoryPHID' => 'optional string',
'viewPolicy' => 'optional string',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function execute(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$raw_diff = $request->getValue('diff');
$repository_phid = $request->getValue('repositoryPHID');
if ($repository_phid) {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withPHIDs(array($repository_phid))
->executeOne();
if (!$repository) {
throw new Exception(
pht('No such repository "%s"!', $repository_phid));
}
}
$parser = new ArcanistDiffParser();
$changes = $parser->parseDiff($raw_diff);
$diff = DifferentialDiff::newFromRawChanges($viewer, $changes);
$diff_data_dict = array(
'creationMethod' => 'web',
'authorPHID' => $viewer->getPHID(),
'repositoryPHID' => $repository_phid,
'lintStatus' => DifferentialLintStatus::LINT_SKIP,
'unitStatus' => DifferentialUnitStatus::UNIT_SKIP,
);
- $xactions = array(id(new DifferentialTransaction())
- ->setTransactionType(DifferentialDiffTransaction::TYPE_DIFF_CREATE)
- ->setNewValue($diff_data_dict),
+ $xactions = array(
+ id(new DifferentialTransaction())
+ ->setTransactionType(DifferentialDiffTransaction::TYPE_DIFF_CREATE)
+ ->setNewValue($diff_data_dict),
);
if ($request->getValue('viewPolicy')) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($request->getValue('viewPolicy'));
}
id(new DifferentialDiffEditor())
->setActor($viewer)
->setContentSourceFromConduitRequest($request)
->setContinueOnNoEffect(true)
->setLookupRepository(false) // respect user choice
->applyTransactions($diff, $xactions);
return $this->buildDiffInfoDictionary($diff);
}
}
diff --git a/src/applications/differential/parser/DifferentialHunkParser.php b/src/applications/differential/parser/DifferentialHunkParser.php
index 8903c85f93..22b82e002e 100644
--- a/src/applications/differential/parser/DifferentialHunkParser.php
+++ b/src/applications/differential/parser/DifferentialHunkParser.php
@@ -1,675 +1,675 @@
<?php
final class DifferentialHunkParser extends Phobject {
private $oldLines;
private $newLines;
private $intraLineDiffs;
private $visibleLinesMask;
private $whitespaceMode;
/**
* Get a map of lines on which hunks start, other than line 1. This
* datastructure is used to determine when to render "Context not available."
* in diffs with multiple hunks.
*
* @return dict<int, bool> Map of lines where hunks start, other than line 1.
*/
public function getHunkStartLines(array $hunks) {
assert_instances_of($hunks, 'DifferentialHunk');
$map = array();
foreach ($hunks as $hunk) {
$line = $hunk->getOldOffset();
if ($line > 1) {
$map[$line] = true;
}
}
return $map;
}
private function setVisibleLinesMask($mask) {
$this->visibleLinesMask = $mask;
return $this;
}
public function getVisibleLinesMask() {
if ($this->visibleLinesMask === null) {
throw new PhutilInvalidStateException('generateVisibileLinesMask');
}
return $this->visibleLinesMask;
}
private function setIntraLineDiffs($intra_line_diffs) {
$this->intraLineDiffs = $intra_line_diffs;
return $this;
}
public function getIntraLineDiffs() {
if ($this->intraLineDiffs === null) {
throw new PhutilInvalidStateException('generateIntraLineDiffs');
}
return $this->intraLineDiffs;
}
private function setNewLines($new_lines) {
$this->newLines = $new_lines;
return $this;
}
public function getNewLines() {
if ($this->newLines === null) {
throw new PhutilInvalidStateException('parseHunksForLineData');
}
return $this->newLines;
}
private function setOldLines($old_lines) {
$this->oldLines = $old_lines;
return $this;
}
public function getOldLines() {
if ($this->oldLines === null) {
throw new PhutilInvalidStateException('parseHunksForLineData');
}
return $this->oldLines;
}
public function getOldLineTypeMap() {
$map = array();
$old = $this->getOldLines();
foreach ($old as $o) {
if (!$o) {
continue;
}
$map[$o['line']] = $o['type'];
}
return $map;
}
public function setOldLineTypeMap(array $map) {
$lines = $this->getOldLines();
foreach ($lines as $key => $data) {
$lines[$key]['type'] = idx($map, $data['line']);
}
$this->oldLines = $lines;
return $this;
}
public function getNewLineTypeMap() {
$map = array();
$new = $this->getNewLines();
foreach ($new as $n) {
if (!$n) {
continue;
}
$map[$n['line']] = $n['type'];
}
return $map;
}
public function setNewLineTypeMap(array $map) {
$lines = $this->getNewLines();
foreach ($lines as $key => $data) {
$lines[$key]['type'] = idx($map, $data['line']);
}
$this->newLines = $lines;
return $this;
}
public function setWhitespaceMode($white_space_mode) {
$this->whitespaceMode = $white_space_mode;
return $this;
}
private function getWhitespaceMode() {
if ($this->whitespaceMode === null) {
throw new Exception(
pht(
'You must %s before accessing this data.',
'setWhitespaceMode'));
}
return $this->whitespaceMode;
}
public function getIsDeleted() {
foreach ($this->getNewLines() as $line) {
if ($line) {
// At least one new line, so the entire file wasn't deleted.
return false;
}
}
foreach ($this->getOldLines() as $line) {
if ($line) {
// No new lines, at least one old line; the entire file was deleted.
return true;
}
}
// This is an empty file.
return false;
}
/**
* Returns true if the hunks change any text, not just whitespace.
*/
public function getHasTextChanges() {
return $this->getHasChanges('text');
}
/**
* Returns true if the hunks change anything, including whitespace.
*/
public function getHasAnyChanges() {
return $this->getHasChanges('any');
}
private function getHasChanges($filter) {
if ($filter !== 'any' && $filter !== 'text') {
throw new Exception(pht("Unknown change filter '%s'.", $filter));
}
$old = $this->getOldLines();
$new = $this->getNewLines();
$is_any = ($filter === 'any');
foreach ($old as $key => $o) {
$n = $new[$key];
if ($o === null || $n === null) {
// One side is missing, and it's impossible for both sides to be null,
// so the other side must have something, and thus the two sides are
// different and the file has been changed under any type of filter.
return true;
}
if ($o['type'] !== $n['type']) {
// The types are different, so either the underlying text is actually
// different or whatever whitespace rules we're using consider them
// different.
return true;
}
if ($o['text'] !== $n['text']) {
if ($is_any) {
// The text is different, so there's a change.
return true;
} else if (trim($o['text']) !== trim($n['text'])) {
return true;
}
}
}
// No changes anywhere in the file.
return false;
}
/**
* This function takes advantage of the parsing work done in
* @{method:parseHunksForLineData} and continues the struggle to hammer this
* data into something we can display to a user.
*
* In particular, this function re-parses the hunks to make them equivalent
* in length for easy rendering, adding `null` as necessary to pad the
* length.
*
* Anyhoo, this function is not particularly well-named but I try.
*
* NOTE: this function must be called after
* @{method:parseHunksForLineData}.
*/
public function reparseHunksForSpecialAttributes() {
$rebuild_old = array();
$rebuild_new = array();
$old_lines = array_reverse($this->getOldLines());
$new_lines = array_reverse($this->getNewLines());
while (count($old_lines) || count($new_lines)) {
$old_line_data = array_pop($old_lines);
$new_line_data = array_pop($new_lines);
if ($old_line_data) {
$o_type = $old_line_data['type'];
} else {
$o_type = null;
}
if ($new_line_data) {
$n_type = $new_line_data['type'];
} else {
$n_type = null;
}
// This line does not exist in the new file.
if (($o_type != null) && ($n_type == null)) {
$rebuild_old[] = $old_line_data;
$rebuild_new[] = null;
if ($new_line_data) {
array_push($new_lines, $new_line_data);
}
continue;
}
// This line does not exist in the old file.
if (($n_type != null) && ($o_type == null)) {
$rebuild_old[] = null;
$rebuild_new[] = $new_line_data;
if ($old_line_data) {
array_push($old_lines, $old_line_data);
}
continue;
}
$rebuild_old[] = $old_line_data;
$rebuild_new[] = $new_line_data;
}
$this->setOldLines($rebuild_old);
$this->setNewLines($rebuild_new);
$this->updateChangeTypesForWhitespaceMode();
return $this;
}
private function updateChangeTypesForWhitespaceMode() {
$mode = $this->getWhitespaceMode();
$mode_show_all = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
if ($mode === $mode_show_all) {
// If we're showing all whitespace, we don't need to perform any updates.
return;
}
$mode_trailing = DifferentialChangesetParser::WHITESPACE_IGNORE_TRAILING;
$is_trailing = ($mode === $mode_trailing);
$new = $this->getNewLines();
$old = $this->getOldLines();
foreach ($old as $key => $o) {
$n = $new[$key];
if (!$o || !$n) {
continue;
}
if ($is_trailing) {
// In "trailing" mode, we need to identify lines which are marked
// changed but differ only by trailing whitespace. We mark these lines
// unchanged.
if ($o['type'] != $n['type']) {
if (rtrim($o['text']) === rtrim($n['text'])) {
$old[$key]['type'] = null;
$new[$key]['type'] = null;
}
}
} else {
// In "ignore most" and "ignore all" modes, we need to identify lines
// which are marked unchanged but have internal whitespace changes.
// We want to ignore leading and trailing whitespace changes only, not
// internal whitespace changes (`diff` doesn't have a mode for this, so
// we have to fix it here). If the text is marked unchanged but the
// old and new text differs by internal space, mark the lines changed.
if ($o['type'] === null && $n['type'] === null) {
if ($o['text'] !== $n['text']) {
if (trim($o['text']) !== trim($n['text'])) {
$old[$key]['type'] = '-';
$new[$key]['type'] = '+';
}
}
}
}
}
$this->setOldLines($old);
$this->setNewLines($new);
return $this;
}
public function generateIntraLineDiffs() {
$old = $this->getOldLines();
$new = $this->getNewLines();
$diffs = array();
foreach ($old as $key => $o) {
$n = $new[$key];
if (!$o || !$n) {
continue;
}
if ($o['type'] != $n['type']) {
$diffs[$key] = ArcanistDiffUtils::generateIntralineDiff(
$o['text'],
$n['text']);
}
}
$this->setIntraLineDiffs($diffs);
return $this;
}
public function generateVisibileLinesMask() {
$lines_context = DifferentialChangesetParser::LINES_CONTEXT;
$old = $this->getOldLines();
$new = $this->getNewLines();
$max_length = max(count($old), count($new));
$visible = false;
$last = 0;
$mask = array();
for ($cursor = -$lines_context; $cursor < $max_length; $cursor++) {
$offset = $cursor + $lines_context;
if ((isset($old[$offset]) && $old[$offset]['type']) ||
(isset($new[$offset]) && $new[$offset]['type'])) {
$visible = true;
$last = $offset;
} else if ($cursor > $last + $lines_context) {
$visible = false;
}
if ($visible && $cursor > 0) {
$mask[$cursor] = 1;
}
}
$this->setVisibleLinesMask($mask);
return $this;
}
public function getOldCorpus() {
return $this->getCorpus($this->getOldLines());
}
public function getNewCorpus() {
return $this->getCorpus($this->getNewLines());
}
private function getCorpus(array $lines) {
$corpus = array();
foreach ($lines as $l) {
if ($l['type'] != '\\') {
if ($l['text'] === null) {
// There's no text on this side of the diff, but insert a placeholder
// newline so the highlighted line numbers match up.
$corpus[] = "\n";
} else {
$corpus[] = $l['text'];
}
}
}
return $corpus;
}
public function parseHunksForLineData(array $hunks) {
assert_instances_of($hunks, 'DifferentialHunk');
$old_lines = array();
$new_lines = array();
foreach ($hunks as $hunk) {
$lines = $hunk->getSplitLines();
$line_type_map = array();
$line_text = array();
foreach ($lines as $line_index => $line) {
if (isset($line[0])) {
$char = $line[0];
switch ($char) {
case ' ':
$line_type_map[$line_index] = null;
$line_text[$line_index] = substr($line, 1);
break;
case "\r":
case "\n":
// NOTE: Normally, the first character is a space, plus, minus or
// backslash, but it may be a newline if it used to be a space and
// trailing whitespace has been stripped via email transmission or
// some similar mechanism. In these cases, we essentially pretend
// the missing space is still there.
$line_type_map[$line_index] = null;
$line_text[$line_index] = $line;
break;
case '+':
case '-':
case '\\':
$line_type_map[$line_index] = $char;
$line_text[$line_index] = substr($line, 1);
break;
default:
throw new Exception(
pht(
'Unexpected leading character "%s" at line index %s!',
$char,
$line_index));
}
} else {
$line_type_map[$line_index] = null;
$line_text[$line_index] = '';
}
}
$old_line = $hunk->getOldOffset();
$new_line = $hunk->getNewOffset();
$num_lines = count($lines);
for ($cursor = 0; $cursor < $num_lines; $cursor++) {
$type = $line_type_map[$cursor];
$data = array(
'type' => $type,
'text' => $line_text[$cursor],
'line' => $new_line,
);
if ($type == '\\') {
$type = $line_type_map[$cursor - 1];
$data['text'] = ltrim($data['text']);
}
switch ($type) {
case '+':
$new_lines[] = $data;
++$new_line;
break;
case '-':
$data['line'] = $old_line;
$old_lines[] = $data;
++$old_line;
break;
default:
$new_lines[] = $data;
$data['line'] = $old_line;
$old_lines[] = $data;
++$new_line;
++$old_line;
break;
}
}
}
$this->setOldLines($old_lines);
$this->setNewLines($new_lines);
return $this;
}
public function parseHunksForHighlightMasks(
array $changeset_hunks,
array $old_hunks,
array $new_hunks) {
assert_instances_of($changeset_hunks, 'DifferentialHunk');
assert_instances_of($old_hunks, 'DifferentialHunk');
assert_instances_of($new_hunks, 'DifferentialHunk');
// Put changes side by side.
$olds = array();
$news = array();
$olds_cursor = -1;
$news_cursor = -1;
foreach ($changeset_hunks as $hunk) {
$n_old = $hunk->getOldOffset();
$n_new = $hunk->getNewOffset();
$changes = $hunk->getSplitLines();
foreach ($changes as $line) {
$diff_type = $line[0]; // Change type in diff of diffs.
$orig_type = $line[1]; // Change type in the original diff.
if ($diff_type == ' ') {
// Use the same key for lines that are next to each other.
if ($olds_cursor > $news_cursor) {
$key = $olds_cursor + 1;
} else {
$key = $news_cursor + 1;
}
$olds[$key] = null;
$news[$key] = null;
$olds_cursor = $key;
$news_cursor = $key;
} else if ($diff_type == '-') {
$olds[] = array($n_old, $orig_type);
$olds_cursor++;
} else if ($diff_type == '+') {
$news[] = array($n_new, $orig_type);
$news_cursor++;
}
if (($diff_type == '-' || $diff_type == ' ') && $orig_type != '-') {
$n_old++;
}
if (($diff_type == '+' || $diff_type == ' ') && $orig_type != '-') {
$n_new++;
}
}
}
$offsets_old = $this->computeOffsets($old_hunks);
$offsets_new = $this->computeOffsets($new_hunks);
// Highlight lines that were added on each side or removed on the other
// side.
$highlight_old = array();
$highlight_new = array();
$last = max(last_key($olds), last_key($news));
for ($i = 0; $i <= $last; $i++) {
if (isset($olds[$i])) {
list($n, $type) = $olds[$i];
if ($type == '+' ||
($type == ' ' && isset($news[$i]) && $news[$i][1] != ' ')) {
$highlight_old[] = $offsets_old[$n];
}
}
if (isset($news[$i])) {
list($n, $type) = $news[$i];
if ($type == '+' ||
($type == ' ' && isset($olds[$i]) && $olds[$i][1] != ' ')) {
$highlight_new[] = $offsets_new[$n];
}
}
}
return array($highlight_old, $highlight_new);
}
public function makeContextDiff(
array $hunks,
$is_new,
$line_number,
$line_length,
$add_context) {
assert_instances_of($hunks, 'DifferentialHunk');
$context = array();
if ($is_new) {
$prefix = '+';
} else {
$prefix = '-';
}
foreach ($hunks as $hunk) {
if ($is_new) {
$offset = $hunk->getNewOffset();
$length = $hunk->getNewLen();
} else {
$offset = $hunk->getOldOffset();
$length = $hunk->getOldLen();
}
$start = $line_number - $offset;
$end = $start + $line_length;
// We need to go in if $start == $length, because the last line
// might be a "\No newline at end of file" marker, which we want
// to show if the additional context is > 0.
if ($start <= $length && $end >= 0) {
$start = $start - $add_context;
$end = $end + $add_context;
$hunk_content = array();
- $hunk_pos = array( '-' => 0, '+' => 0 );
- $hunk_offset = array( '-' => null, '+' => null );
- $hunk_last = array( '-' => null, '+' => null );
+ $hunk_pos = array('-' => 0, '+' => 0);
+ $hunk_offset = array('-' => null, '+' => null);
+ $hunk_last = array('-' => null, '+' => null);
foreach (explode("\n", $hunk->getChanges()) as $line) {
$in_common = strncmp($line, ' ', 1) === 0;
$in_old = strncmp($line, '-', 1) === 0 || $in_common;
$in_new = strncmp($line, '+', 1) === 0 || $in_common;
$in_selected = strncmp($line, $prefix, 1) === 0;
$skip = !$in_selected && !$in_common;
if ($hunk_pos[$prefix] <= $end) {
if ($start <= $hunk_pos[$prefix]) {
if (!$skip || ($hunk_pos[$prefix] != $start &&
$hunk_pos[$prefix] != $end)) {
if ($in_old) {
if ($hunk_offset['-'] === null) {
$hunk_offset['-'] = $hunk_pos['-'];
}
$hunk_last['-'] = $hunk_pos['-'];
}
if ($in_new) {
if ($hunk_offset['+'] === null) {
$hunk_offset['+'] = $hunk_pos['+'];
}
$hunk_last['+'] = $hunk_pos['+'];
}
$hunk_content[] = $line;
}
}
if ($in_old) { ++$hunk_pos['-']; }
if ($in_new) { ++$hunk_pos['+']; }
}
}
if ($hunk_offset['-'] !== null || $hunk_offset['+'] !== null) {
$header = '@@';
if ($hunk_offset['-'] !== null) {
$header .= ' -'.($hunk->getOldOffset() + $hunk_offset['-']).
','.($hunk_last['-'] - $hunk_offset['-'] + 1);
}
if ($hunk_offset['+'] !== null) {
$header .= ' +'.($hunk->getNewOffset() + $hunk_offset['+']).
','.($hunk_last['+'] - $hunk_offset['+'] + 1);
}
$header .= ' @@';
$context[] = $header;
$context[] = implode("\n", $hunk_content);
}
}
}
return implode("\n", $context);
}
private function computeOffsets(array $hunks) {
assert_instances_of($hunks, 'DifferentialHunk');
$offsets = array();
$n = 1;
foreach ($hunks as $hunk) {
$new_length = $hunk->getNewLen();
$new_offset = $hunk->getNewOffset();
for ($i = 0; $i < $new_length; $i++) {
$offsets[$n] = $new_offset + $i;
$n++;
}
}
return $offsets;
}
}
diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php
index 2442d69bd8..6307374c7f 100644
--- a/src/applications/home/controller/PhabricatorHomeMainController.php
+++ b/src/applications/home/controller/PhabricatorHomeMainController.php
@@ -1,423 +1,423 @@
<?php
final class PhabricatorHomeMainController extends PhabricatorHomeController {
private $minipanels = array();
public function shouldAllowPublic() {
return true;
}
public function isGlobalDragAndDropUploadEnabled() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
$dashboard = PhabricatorDashboardInstall::getDashboard(
$user,
$user->getPHID(),
get_class($this->getCurrentApplication()));
if (!$dashboard) {
$dashboard = PhabricatorDashboardInstall::getDashboard(
$user,
PhabricatorHomeApplication::DASHBOARD_DEFAULT,
get_class($this->getCurrentApplication()));
}
if ($dashboard) {
$content = id(new PhabricatorDashboardRenderingEngine())
->setViewer($user)
->setDashboard($dashboard)
->renderDashboard();
} else {
$project_query = new PhabricatorProjectQuery();
$project_query->setViewer($user);
$project_query->withMemberPHIDs(array($user->getPHID()));
$projects = $project_query->execute();
$content = $this->buildMainResponse($projects);
}
if (!$request->getURIData('only')) {
$nav = $this->buildNav();
$nav->appendChild(
array(
$content,
id(new PhabricatorGlobalUploadTargetView())->setUser($user),
));
$content = $nav;
}
return $this->buildApplicationPage(
$content,
array(
'title' => 'Phabricator',
));
}
private function buildMainResponse(array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$viewer = $this->getRequest()->getUser();
$has_maniphest = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorManiphestApplication',
$viewer);
$has_audit = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorAuditApplication',
$viewer);
$has_differential = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorDifferentialApplication',
$viewer);
if ($has_maniphest) {
$unbreak_panel = $this->buildUnbreakNowPanel();
$triage_panel = $this->buildNeedsTriagePanel($projects);
$tasks_panel = $this->buildTasksPanel();
} else {
$unbreak_panel = null;
$triage_panel = null;
$tasks_panel = null;
}
if ($has_audit) {
$audit_panel = $this->buildAuditPanel();
$commit_panel = $this->buildCommitPanel();
} else {
$audit_panel = null;
$commit_panel = null;
}
if (PhabricatorEnv::getEnvConfig('welcome.html') !== null) {
$welcome_panel = $this->buildWelcomePanel();
} else {
$welcome_panel = null;
}
if ($has_differential) {
$revision_panel = $this->buildRevisionPanel();
} else {
$revision_panel = null;
}
$home = phutil_tag(
'div',
array(
'class' => 'homepage-panel',
),
array(
$welcome_panel,
$unbreak_panel,
$triage_panel,
$revision_panel,
$tasks_panel,
$audit_panel,
$commit_panel,
$this->minipanels,
));
return $home;
}
private function buildUnbreakNowPanel() {
$unbreak_now = PhabricatorEnv::getEnvConfig(
'maniphest.priorities.unbreak-now');
if (!$unbreak_now) {
return null;
}
$user = $this->getRequest()->getUser();
$task_query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->withPriorities(array($unbreak_now))
->needProjectPHIDs(true)
->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
pht('No "Unbreak Now!" Tasks'),
pht('Nothing appears to be critically broken right now.'));
}
$href = urisprintf(
'/maniphest/?statuses=open()&priorities=%s#R',
$unbreak_now);
$title = pht('Unbreak Now!');
$panel = new PHUIObjectBoxView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->setObjectList($this->buildTaskListView($tasks));
return $panel;
}
private function buildNeedsTriagePanel(array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$needs_triage = PhabricatorEnv::getEnvConfig(
'maniphest.priorities.needs-triage');
if (!$needs_triage) {
return null;
}
$user = $this->getRequest()->getUser();
if (!$user->isLoggedIn()) {
return null;
}
if ($projects) {
$task_query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->withPriorities(array($needs_triage))
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_OR,
mpull($projects, 'getPHID'))
->needProjectPHIDs(true)
->setLimit(10);
$tasks = $task_query->execute();
} else {
$tasks = array();
}
if (!$tasks) {
return $this->renderMiniPanel(
pht('No "Needs Triage" Tasks'),
pht('No tasks in projects you are a member of need triage.'));
}
$title = pht('Needs Triage');
$href = urisprintf(
'/maniphest/?statuses=open()&priorities=%s&projects=projects(%s)#R',
$needs_triage,
$user->getPHID());
$panel = new PHUIObjectBoxView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->setObjectList($this->buildTaskListView($tasks));
return $panel;
}
private function buildRevisionPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$revision_query = id(new DifferentialRevisionQuery())
->setViewer($user)
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->withResponsibleUsers(array($user_phid))
->needRelationships(true)
->needFlags(true)
->needDrafts(true);
$revisions = $revision_query->execute();
- list($blocking, $active, ) = DifferentialRevisionQuery::splitResponsible(
+ list($blocking, $active,) = DifferentialRevisionQuery::splitResponsible(
$revisions,
array($user_phid));
if (!$blocking && !$active) {
return $this->renderMiniPanel(
pht('No Waiting Revisions'),
pht('No revisions are waiting on you.'));
}
$title = pht('Revisions Waiting on You');
$href = '/differential';
$panel = new PHUIObjectBoxView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$revision_view = id(new DifferentialRevisionListView())
->setHighlightAge(true)
->setRevisions(array_merge($blocking, $active))
->setUser($user);
$phids = array_merge(
array($user_phid),
$revision_view->getRequiredHandlePHIDs());
$handles = $this->loadViewerHandles($phids);
$revision_view->setHandles($handles);
$list_view = $revision_view->render();
$panel->setObjectList($list_view);
return $panel;
}
private function buildWelcomePanel() {
$panel = new PHUIObjectBoxView();
$panel->setHeaderText(pht('Welcome'));
$panel->appendChild(
phutil_safe_html(
PhabricatorEnv::getEnvConfig('welcome.html')));
return $panel;
}
private function buildTasksPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$task_query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY)
->withOwners(array($user_phid))
->needProjectPHIDs(true)
->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
pht('No Assigned Tasks'),
pht('You have no assigned tasks.'));
}
$title = pht('Assigned Tasks');
$href = '/maniphest/query/assigned/';
$panel = new PHUIObjectBoxView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->setObjectList($this->buildTaskListView($tasks));
return $panel;
}
private function buildTaskListView(array $tasks) {
assert_instances_of($tasks, 'ManiphestTask');
$user = $this->getRequest()->getUser();
$phids = array_merge(
array_filter(mpull($tasks, 'getOwnerPHID')),
array_mergev(mpull($tasks, 'getProjectPHIDs')));
$handles = $this->loadViewerHandles($phids);
$view = new ManiphestTaskListView();
$view->setTasks($tasks);
$view->setUser($user);
$view->setHandles($handles);
return $view;
}
private function renderSectionHeader($title, $href) {
$title = phutil_tag(
'a',
array(
'href' => $href,
),
$title);
$icon = id(new PHUIIconView())
->setIconFont('fa-search')
->setHref($href);
$header = id(new PHUIHeaderView())
->setHeader($title)
->addActionIcon($icon);
return $header;
}
private function renderMiniPanel($title, $body) {
$panel = new PHUIInfoView();
$panel->setSeverity(PHUIInfoView::SEVERITY_NODATA);
$panel->appendChild(
phutil_tag(
'p',
array(
),
array(
phutil_tag('strong', array(), $title.': '),
$body,
)));
$this->minipanels[] = $panel;
}
public function buildAuditPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$query = id(new DiffusionCommitQuery())
->setViewer($user)
->withAuditorPHIDs($phids)
->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN)
->withAuditAwaitingUser($user)
->needAuditRequests(true)
->needCommitData(true)
->setLimit(10);
$commits = $query->execute();
if (!$commits) {
return $this->renderMinipanel(
pht('No Audits'),
pht('No commits are waiting for you to audit them.'));
}
$view = id(new PhabricatorAuditListView())
->setCommits($commits)
->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$title = pht('Audits');
$href = '/audit/';
$panel = new PHUIObjectBoxView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->setObjectList($view);
return $panel;
}
public function buildCommitPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = array($user->getPHID());
$query = id(new DiffusionCommitQuery())
->setViewer($user)
->withAuthorPHIDs($phids)
->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN)
->needCommitData(true)
->needAuditRequests(true)
->setLimit(10);
$commits = $query->execute();
if (!$commits) {
return $this->renderMinipanel(
pht('No Problem Commits'),
pht('No one has raised concerns with your commits.'));
}
$view = id(new PhabricatorAuditListView())
->setCommits($commits)
->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$title = pht('Problem Commits');
$href = '/audit/';
$panel = new PHUIObjectBoxView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->setObjectList($view);
return $panel;
}
}
diff --git a/src/applications/passphrase/controller/PassphraseCredentialRevealController.php b/src/applications/passphrase/controller/PassphraseCredentialRevealController.php
index c61086b0e7..fb784fb5b8 100644
--- a/src/applications/passphrase/controller/PassphraseCredentialRevealController.php
+++ b/src/applications/passphrase/controller/PassphraseCredentialRevealController.php
@@ -1,105 +1,106 @@
<?php
final class PassphraseCredentialRevealController
extends PassphraseController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$credential = id(new PassphraseCredentialQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->needSecrets(true)
->executeOne();
if (!$credential) {
return new Aphront404Response();
}
$view_uri = '/K'.$credential->getID();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$view_uri);
$is_locked = $credential->getIsLocked();
if ($is_locked) {
return $this->newDialog()
->setUser($viewer)
->setTitle(pht('Credential is locked'))
->appendChild(
pht(
'This credential can not be shown, because it is locked.'))
->addCancelButton($view_uri);
}
if ($request->isFormPost()) {
$secret = $credential->getSecret();
if (!$secret) {
$body = pht('This credential has no associated secret.');
} else if (!strlen($secret->openEnvelope())) {
$body = pht('This credential has an empty secret.');
} else {
$body = id(new PHUIFormLayoutView())
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Plaintext'))
->setReadOnly(true)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setValue($secret->openEnvelope()));
}
// NOTE: Disable workflow on the cancel button to reload the page so
// the viewer can see that their view was logged.
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Credential Secret (%s)', $credential->getMonogram()))
->appendChild($body)
->setDisableWorkflowOnCancel(true)
->addCancelButton($view_uri, pht('Done'));
$type_secret = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET;
- $xactions = array(id(new PassphraseCredentialTransaction())
- ->setTransactionType($type_secret)
- ->setNewValue(true),
+ $xactions = array(
+ id(new PassphraseCredentialTransaction())
+ ->setTransactionType($type_secret)
+ ->setNewValue(true),
);
$editor = id(new PassphraseCredentialTransactionEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->applyTransactions($credential, $xactions);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
$body = pht(
'The secret associated with this credential will be shown in plain '.
'text on your screen.');
} else {
$body = pht(
'The secret associated with this credential will be shown in plain '.
'text on your screen. Before continuing, wrap your arms around '.
'your monitor to create a human shield, keeping it safe from '.
'prying eyes. Protect company secrets!');
}
return $this->newDialog()
->setUser($viewer)
->setTitle(pht('Really show secret?'))
->appendChild($body)
->addSubmitButton(pht('Show Secret'))
->addCancelButton($view_uri);
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectColumnHideController.php b/src/applications/project/controller/PhabricatorProjectColumnHideController.php
index 167cbfbd2e..ae39e783bb 100644
--- a/src/applications/project/controller/PhabricatorProjectColumnHideController.php
+++ b/src/applications/project/controller/PhabricatorProjectColumnHideController.php
@@ -1,106 +1,107 @@
<?php
final class PhabricatorProjectColumnHideController
extends PhabricatorProjectBoardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$project_id = $request->getURIData('projectID');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($project_id))
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
$column = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$column) {
return new Aphront404Response();
}
$column_phid = $column->getPHID();
$view_uri = $this->getApplicationURI('/board/'.$project_id.'/');
$view_uri = new PhutilURI($view_uri);
foreach ($request->getPassthroughRequestData() as $key => $value) {
$view_uri->setQueryParam($key, $value);
}
if ($column->isDefaultColumn()) {
return $this->newDialog()
->setTitle(pht('Can Not Hide Default Column'))
->appendParagraph(
pht('You can not hide the default/backlog column on a board.'))
->addCancelButton($view_uri, pht('Okay'));
}
if ($request->isFormPost()) {
if ($column->isHidden()) {
$new_status = PhabricatorProjectColumn::STATUS_ACTIVE;
} else {
$new_status = PhabricatorProjectColumn::STATUS_HIDDEN;
}
$type_status = PhabricatorProjectColumnTransaction::TYPE_STATUS;
- $xactions = array(id(new PhabricatorProjectColumnTransaction())
- ->setTransactionType($type_status)
- ->setNewValue($new_status),
+ $xactions = array(
+ id(new PhabricatorProjectColumnTransaction())
+ ->setTransactionType($type_status)
+ ->setNewValue($new_status),
);
$editor = id(new PhabricatorProjectColumnTransactionEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->applyTransactions($column, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
if ($column->isHidden()) {
$title = pht('Show Column');
} else {
$title = pht('Hide Column');
}
if ($column->isHidden()) {
$body = pht(
'Are you sure you want to show this column?');
} else {
$body = pht(
'Are you sure you want to hide this column? It will no longer '.
'appear on the workboard.');
}
$dialog = $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle($title)
->appendChild($body)
->setDisableWorkflowOnCancel(true)
->addCancelButton($view_uri)
->addSubmitButton($title);
foreach ($request->getPassthroughRequestData() as $key => $value) {
$dialog->addHiddenInput($key, $value);
}
return $dialog;
}
}
diff --git a/src/applications/search/engine/PhabricatorElasticSearchEngine.php b/src/applications/search/engine/PhabricatorElasticSearchEngine.php
index fa09e64a9c..e99160839f 100644
--- a/src/applications/search/engine/PhabricatorElasticSearchEngine.php
+++ b/src/applications/search/engine/PhabricatorElasticSearchEngine.php
@@ -1,440 +1,440 @@
<?php
final class PhabricatorElasticSearchEngine extends PhabricatorSearchEngine {
private $uri;
private $index;
private $timeout;
public function __construct() {
$this->uri = PhabricatorEnv::getEnvConfig('search.elastic.host');
$this->index = PhabricatorEnv::getEnvConfig('search.elastic.namespace');
}
public function getEngineIdentifier() {
return 'elasticsearch';
}
public function getEnginePriority() {
return 10;
}
public function isEnabled() {
return (bool)$this->uri;
}
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function setIndex($index) {
$this->index = $index;
return $this;
}
public function setTimeout($timeout) {
$this->timeout = $timeout;
return $this;
}
public function getURI() {
return $this->uri;
}
public function getIndex() {
return $this->index;
}
public function getTimeout() {
return $this->timeout;
}
public function reindexAbstractDocument(
PhabricatorSearchAbstractDocument $doc) {
$type = $doc->getDocumentType();
$phid = $doc->getPHID();
$handle = id(new PhabricatorHandleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($phid))
->executeOne();
// URL is not used internally but it can be useful externally.
$spec = array(
'title' => $doc->getDocumentTitle(),
'url' => PhabricatorEnv::getProductionURI($handle->getURI()),
'dateCreated' => $doc->getDocumentCreated(),
'_timestamp' => $doc->getDocumentModified(),
'field' => array(),
'relationship' => array(),
);
foreach ($doc->getFieldData() as $field) {
$spec['field'][] = array_combine(array('type', 'corpus', 'aux'), $field);
}
foreach ($doc->getRelationshipData() as $relationship) {
list($rtype, $to_phid, $to_type, $time) = $relationship;
$spec['relationship'][$rtype][] = array(
'phid' => $to_phid,
'phidType' => $to_type,
'when' => $time,
);
}
$this->executeRequest("/{$type}/{$phid}/", $spec, 'PUT');
}
public function reconstructDocument($phid) {
$type = phid_get_type($phid);
$response = $this->executeRequest("/{$type}/{$phid}", array());
if (empty($response['exists'])) {
return null;
}
$hit = $response['_source'];
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($phid);
$doc->setDocumentType($response['_type']);
$doc->setDocumentTitle($hit['title']);
$doc->setDocumentCreated($hit['dateCreated']);
$doc->setDocumentModified($hit['_timestamp']);
foreach ($hit['field'] as $fdef) {
$doc->addField($fdef['type'], $fdef['corpus'], $fdef['aux']);
}
foreach ($hit['relationship'] as $rtype => $rships) {
foreach ($rships as $rship) {
$doc->addRelationship(
$rtype,
$rship['phid'],
$rship['phidType'],
$rship['when']);
}
}
return $doc;
}
private function buildSpec(PhabricatorSavedQuery $query) {
$spec = array();
$filter = array();
$title_spec = array();
if (strlen($query->getParameter('query'))) {
$spec[] = array(
'simple_query_string' => array(
'query' => $query->getParameter('query'),
'fields' => array('field.corpus'),
),
);
$title_spec = array(
'simple_query_string' => array(
'query' => $query->getParameter('query'),
'fields' => array('title'),
),
);
}
$exclude = $query->getParameter('exclude');
if ($exclude) {
$filter[] = array(
'not' => array(
'ids' => array(
'values' => array($exclude),
),
),
);
}
$relationship_map = array(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR =>
$query->getParameter('authorPHIDs', array()),
PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER =>
$query->getParameter('subscriberPHIDs', array()),
PhabricatorSearchRelationship::RELATIONSHIP_PROJECT =>
$query->getParameter('projectPHIDs', array()),
PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY =>
$query->getParameter('repositoryPHIDs', array()),
);
$statuses = $query->getParameter('statuses', array());
$statuses = array_fuse($statuses);
$rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN;
$rel_closed = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED;
$rel_unowned = PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED;
$include_open = !empty($statuses[$rel_open]);
$include_closed = !empty($statuses[$rel_closed]);
if ($include_open && !$include_closed) {
$relationship_map[$rel_open] = true;
} else if (!$include_open && $include_closed) {
$relationship_map[$rel_closed] = true;
}
if ($query->getParameter('withUnowned')) {
$relationship_map[$rel_unowned] = true;
}
$rel_owner = PhabricatorSearchRelationship::RELATIONSHIP_OWNER;
if ($query->getParameter('withAnyOwner')) {
$relationship_map[$rel_owner] = true;
} else {
$owner_phids = $query->getParameter('ownerPHIDs', array());
$relationship_map[$rel_owner] = $owner_phids;
}
foreach ($relationship_map as $field => $param) {
if (is_array($param) && $param) {
$should = array();
foreach ($param as $val) {
$should[] = array(
'match' => array(
"relationship.{$field}.phid" => array(
'query' => $val,
'type' => 'phrase',
),
),
);
}
// We couldn't solve it by minimum_number_should_match because it can
// match multiple owners without matching author.
$spec[] = array('bool' => array('should' => $should));
} else if ($param) {
$filter[] = array(
'exists' => array(
'field' => "relationship.{$field}.phid",
),
);
}
}
if ($spec) {
$spec = array('query' => array('bool' => array('must' => $spec)));
if ($title_spec) {
$spec['query']['bool']['should'] = $title_spec;
}
}
if ($filter) {
$filter = array('filter' => array('and' => $filter));
if (!$spec) {
$spec = array('query' => array('match_all' => new stdClass()));
}
$spec = array(
'query' => array(
'filtered' => $spec + $filter,
),
);
}
if (!$query->getParameter('query')) {
$spec['sort'] = array(
array('dateCreated' => 'desc'),
);
}
$spec['from'] = (int)$query->getParameter('offset', 0);
$spec['size'] = (int)$query->getParameter('limit', 25);
return $spec;
}
public function executeSearch(PhabricatorSavedQuery $query) {
$types = $query->getParameter('types');
if (!$types) {
$types = array_keys(
PhabricatorSearchApplicationSearchEngine::getIndexableDocumentTypes());
}
// Don't use '/_search' for the case that there is something
// else in the index (for example if 'phabricator' is only an alias to
// some bigger index). Use '/$types/_search' instead.
$uri = '/'.implode(',', $types).'/_search';
try {
$response = $this->executeRequest($uri, $this->buildSpec($query));
} catch (HTTPFutureHTTPResponseStatus $ex) {
// elasticsearch probably uses Lucene query syntax:
// http://lucene.apache.org/core/3_6_1/queryparsersyntax.html
// Try literal search if operator search fails.
if (!strlen($query->getParameter('query'))) {
throw $ex;
}
$query = clone $query;
$query->setParameter(
'query',
addcslashes(
$query->getParameter('query'), '+-&|!(){}[]^"~*?:\\'));
$response = $this->executeRequest($uri, $this->buildSpec($query));
}
$phids = ipull($response['hits']['hits'], '_id');
return $phids;
}
public function indexExists() {
try {
return (bool)$this->executeRequest('/_status/', array());
} catch (HTTPFutureHTTPResponseStatus $e) {
if ($e->getStatusCode() == 404) {
return false;
}
throw $e;
}
}
private function getIndexConfiguration() {
$data = array();
$data['settings'] = array(
'index' => array(
'auto_expand_replicas' => '0-2',
'analysis' => array(
'filter' => array(
'trigrams_filter' => array(
'min_gram' => 3,
'type' => 'ngram',
'max_gram' => 3,
),
),
'analyzer' => array(
'custom_trigrams' => array(
'type' => 'custom',
'filter' => array(
'lowercase',
'kstem',
'trigrams_filter',
),
'tokenizer' => 'standard',
),
),
),
),
);
$types = array_keys(
PhabricatorSearchApplicationSearchEngine::getIndexableDocumentTypes());
foreach ($types as $type) {
// Use the custom trigram analyzer for the corpus of text
$data['mappings'][$type]['properties']['field']['properties']['corpus'] =
- array( 'type' => 'string', 'analyzer' => 'custom_trigrams' );
+ array('type' => 'string', 'analyzer' => 'custom_trigrams');
// Ensure we have dateCreated since the default query requires it
$data['mappings'][$type]['properties']['dateCreated']['type'] = 'string';
}
return $data;
}
public function indexIsSane() {
if (!$this->indexExists()) {
return false;
}
$cur_mapping = $this->executeRequest('/_mapping/', array());
$cur_settings = $this->executeRequest('/_settings/', array());
$actual = array_merge($cur_settings[$this->index],
$cur_mapping[$this->index]);
return $this->check($actual, $this->getIndexConfiguration());
}
/**
* Recursively check if two Elasticsearch configuration arrays are equal
*
* @param $actual
* @param $required array
* @return bool
*/
private function check($actual, $required) {
foreach ($required as $key => $value) {
if (!array_key_exists($key, $actual)) {
if ($key === '_all') {
// The _all field never comes back so we just have to assume it
// is set correctly.
continue;
}
return false;
}
if (is_array($value)) {
if (!is_array($actual[$key])) {
return false;
}
if (!$this->check($actual[$key], $value)) {
return false;
}
continue;
}
$actual[$key] = self::normalizeConfigValue($actual[$key]);
$value = self::normalizeConfigValue($value);
if ($actual[$key] != $value) {
return false;
}
}
return true;
}
/**
* Normalize a config value for comparison. Elasticsearch accepts all kinds
* of config values but it tends to throw back 'true' for true and 'false' for
* false so we normalize everything. Sometimes, oddly, it'll throw back false
* for false....
*
* @param mixed $value config value
* @return mixed value normalized
*/
private static function normalizeConfigValue($value) {
if ($value === true) {
return 'true';
} else if ($value === false) {
return 'false';
}
return $value;
}
public function initIndex() {
if ($this->indexExists()) {
$this->executeRequest('/', array(), 'DELETE');
}
$data = $this->getIndexConfiguration();
$this->executeRequest('/', $data, 'PUT');
}
private function executeRequest($path, array $data, $method = 'GET') {
$uri = new PhutilURI($this->uri);
$uri->setPath($this->index);
$uri->appendPath($path);
$data = json_encode($data);
$future = new HTTPSFuture($uri, $data);
if ($method != 'GET') {
$future->setMethod($method);
}
if ($this->getTimeout()) {
$future->setTimeout($this->getTimeout());
}
list($body) = $future->resolvex();
if ($method != 'GET') {
return null;
}
try {
return phutil_json_decode($body);
} catch (PhutilJSONParserException $ex) {
throw new PhutilProxyException(
pht('ElasticSearch server returned invalid JSON!'),
$ex);
}
}
}
diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php
index 216a4a4406..06cf369ba2 100644
--- a/src/applications/uiexample/examples/PHUIButtonExample.php
+++ b/src/applications/uiexample/examples/PHUIButtonExample.php
@@ -1,233 +1,234 @@
<?php
final class PHUIButtonExample extends PhabricatorUIExample {
public function getName() {
return pht('Buttons');
}
public function getDescription() {
return pht(
'Use %s to render buttons.',
phutil_tag('tt', array(), '&lt;button&gt;'));
}
public function renderExample() {
$request = $this->getRequest();
$user = $request->getUser();
$colors = array('', 'green', 'grey', 'disabled');
$sizes = array('', 'small');
$tags = array('a', 'button');
// phutil_tag
$column = array();
foreach ($tags as $tag) {
foreach ($colors as $color) {
foreach ($sizes as $key => $size) {
$class = implode(' ', array($color, $size));
if ($tag == 'a') {
$class .= ' button';
}
$column[$key][] = phutil_tag(
$tag,
array(
'class' => $class,
),
phutil_utf8_ucwords($size.' '.$color.' '.$tag));
$column[$key][] = hsprintf('<br /><br />');
}
}
}
$column3 = array();
foreach ($colors as $color) {
$caret = phutil_tag('span', array('class' => 'caret'), '');
$column3[] = phutil_tag(
'a',
array(
'class' => $color.' button dropdown',
),
array(
phutil_utf8_ucwords($color.' Dropdown'),
$caret,
));
$column3[] = hsprintf('<br /><br />');
}
$layout1 = id(new AphrontMultiColumnView())
->addColumn($column[0])
->addColumn($column[1])
->addColumn($column3)
->setFluidLayout(true)
->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM);
// PHUIButtonView
- $colors = array(null,
+ $colors = array(
+ null,
PHUIButtonView::GREEN,
PHUIButtonView::GREY,
PHUIButtonView::DISABLED,
);
$sizes = array(null, PHUIButtonView::SMALL);
$column = array();
foreach ($colors as $color) {
foreach ($sizes as $key => $size) {
$column[$key][] = id(new PHUIButtonView())
->setColor($color)
->setSize($size)
->setTag('a')
->setText(pht('Clicky'));
$column[$key][] = hsprintf('<br /><br />');
}
}
foreach ($colors as $color) {
$column[2][] = id(new PHUIButtonView())
->setColor($color)
->setTag('button')
->setText(pht('Button'))
->setDropdown(true);
$column[2][] = hsprintf('<br /><br />');
}
$layout2 = id(new AphrontMultiColumnView())
->addColumn($column[0])
->addColumn($column[1])
->addColumn($column[2])
->setFluidLayout(true)
->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM);
// Icon Buttons
$column = array();
$icons = array(
'Comment' => 'fa-comment',
'Give Token' => 'fa-trophy',
'Reverse Time' => 'fa-clock-o',
'Implode Earth' => 'fa-exclamation-triangle red',
);
foreach ($icons as $text => $icon) {
$image = id(new PHUIIconView())
->setIconFont($icon);
$column[] = id(new PHUIButtonView())
->setTag('a')
->setColor(PHUIButtonView::GREY)
->setIcon($image)
->setText($text)
->addClass(PHUI::MARGIN_SMALL_RIGHT);
}
$layout3 = id(new AphrontMultiColumnView())
->addColumn($column)
->setFluidLayout(true)
->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM);
$icons = array(
'Subscribe' => 'fa-check-circle bluegrey',
'Edit' => 'fa-pencil bluegrey',
);
$colors = array(
PHUIButtonView::SIMPLE,
PHUIButtonView::SIMPLE_YELLOW,
PHUIButtonView::SIMPLE_GREY,
PHUIButtonView::SIMPLE_BLUE,
);
$column = array();
foreach ($colors as $color) {
foreach ($icons as $text => $icon) {
$image = id(new PHUIIconView())
->setIconFont($icon);
$column[] = id(new PHUIButtonView())
->setTag('a')
->setColor($color)
->setIcon($image)
->setText($text)
->addClass(PHUI::MARGIN_SMALL_RIGHT);
}
}
$layout4 = id(new AphrontMultiColumnView())
->addColumn($column)
->setFluidLayout(true)
->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM);
// Baby Got Back Buttons
$column = array();
$icons = array('Asana', 'Github', 'Facebook', 'Google', 'LDAP');
foreach ($icons as $icon) {
$image = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_LOGIN)
->setSpriteIcon($icon);
$column[] = id(new PHUIButtonView())
->setTag('a')
->setSize(PHUIButtonView::BIG)
->setColor(PHUIButtonView::GREY)
->setIcon($image)
->setText(pht('Login or Register'))
->setSubtext($icon)
->addClass(PHUI::MARGIN_MEDIUM_RIGHT);
}
$layout5 = id(new AphrontMultiColumnView())
->addColumn($column)
->setFluidLayout(true)
->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM);
// Set it and forget it
$head1 = id(new PHUIHeaderView())
->setHeader('phutil_tag');
$head2 = id(new PHUIHeaderView())
->setHeader('PHUIButtonView');
$head3 = id(new PHUIHeaderView())
->setHeader(pht('Icon Buttons'));
$head4 = id(new PHUIHeaderView())
->setHeader(pht('Simple Buttons'));
$head5 = id(new PHUIHeaderView())
->setHeader(pht('Big Icon Buttons'));
$wrap1 = id(new PHUIBoxView())
->appendChild($layout1)
->addMargin(PHUI::MARGIN_LARGE);
$wrap2 = id(new PHUIBoxView())
->appendChild($layout2)
->addMargin(PHUI::MARGIN_LARGE);
$wrap3 = id(new PHUIBoxView())
->appendChild($layout3)
->addMargin(PHUI::MARGIN_LARGE);
$wrap4 = id(new PHUIBoxView())
->appendChild($layout4)
->addMargin(PHUI::MARGIN_LARGE);
$wrap5 = id(new PHUIBoxView())
->appendChild($layout5)
->addMargin(PHUI::MARGIN_LARGE);
return array(
$head1,
$wrap1,
$head2,
$wrap2,
$head3,
$wrap3,
$head4,
$wrap4,
$head5,
$wrap5,
);
}
}
diff --git a/src/infrastructure/sms/worker/PhabricatorSMSWorker.php b/src/infrastructure/sms/worker/PhabricatorSMSWorker.php
index 145a371e7d..7b8f851eaa 100644
--- a/src/infrastructure/sms/worker/PhabricatorSMSWorker.php
+++ b/src/infrastructure/sms/worker/PhabricatorSMSWorker.php
@@ -1,6 +1,4 @@
<?php
abstract class PhabricatorSMSWorker
- extends PhabricatorWorker {
-
-}
+ extends PhabricatorWorker {}
diff --git a/src/infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php b/src/infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php
index 720784cfd5..8ce7608a8b 100644
--- a/src/infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php
+++ b/src/infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php
@@ -1,55 +1,66 @@
<?php
final class LiskChunkTestCase extends PhabricatorTestCase {
public function testSQLChunking() {
$fragments = array(
- 'a', 'a',
- 'b', 'b',
+ 'a',
+ 'a',
+ 'b',
+ 'b',
'ccc',
'dd',
'e',
);
$this->assertEqual(
array(
'aa',
'bb',
'ccc',
'dd',
'e',
),
PhabricatorLiskDAO::chunkSQL($fragments, '', 2));
$fragments = array(
- 'a', 'a', 'a', 'XX', 'a', 'a', 'a', 'a',
+ 'a',
+ 'a',
+ 'a',
+ 'XX',
+ 'a',
+ 'a',
+ 'a',
+ 'a',
);
$this->assertEqual(
array(
'a, a, a',
'XX, a, a',
'a, a',
),
PhabricatorLiskDAO::chunkSQL($fragments, ', ', 8));
$fragments = array(
'xxxxxxxxxx',
'yyyyyyyyyy',
- 'a', 'b', 'c',
+ 'a',
+ 'b',
+ 'c',
'zzzzzzzzzz',
);
$this->assertEqual(
array(
'xxxxxxxxxx',
'yyyyyyyyyy',
'a, b, c',
'zzzzzzzzzz',
),
PhabricatorLiskDAO::chunkSQL($fragments, ', ', 8));
}
}
diff --git a/src/infrastructure/util/__tests__/PhabricatorHashTestCase.php b/src/infrastructure/util/__tests__/PhabricatorHashTestCase.php
index c6839dae09..b89515701d 100644
--- a/src/infrastructure/util/__tests__/PhabricatorHashTestCase.php
+++ b/src/infrastructure/util/__tests__/PhabricatorHashTestCase.php
@@ -1,41 +1,55 @@
<?php
final class PhabricatorHashTestCase extends PhabricatorTestCase {
public function testHashForIndex() {
$map = array(
'dog' => 'Aliif7Qjd5ct',
'cat' => 'toudDsue3Uv8',
'rat' => 'RswaKgTjqOuj',
'bat' => 'rAkJKyX4YdYm',
);
foreach ($map as $input => $expect) {
$this->assertEqual(
$expect,
PhabricatorHash::digestForIndex($input),
pht('Input: %s', $input));
}
// Test that the encoding produces 6 bits of entropy per byte.
$entropy = array(
- 'dog', 'cat', 'rat', 'bat', 'dig', 'fig', 'cot',
- 'cut', 'fog', 'rig', 'rug', 'dug', 'mat', 'pat',
- 'eat', 'tar', 'pot',
+ 'dog',
+ 'cat',
+ 'rat',
+ 'bat',
+ 'dig',
+ 'fig',
+ 'cot',
+ 'cut',
+ 'fog',
+ 'rig',
+ 'rug',
+ 'dug',
+ 'mat',
+ 'pat',
+ 'eat',
+ 'tar',
+ 'pot',
);
$seen = array();
foreach ($entropy as $input) {
$chars = preg_split('//', PhabricatorHash::digestForIndex($input));
foreach ($chars as $char) {
$seen[$char] = true;
}
}
$this->assertEqual(
(1 << 6),
count($seen),
pht('Distinct characters in hash of: %s', $input));
}
}
diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php
index 19210c312d..ca866b11a8 100644
--- a/src/view/AphrontDialogView.php
+++ b/src/view/AphrontDialogView.php
@@ -1,373 +1,374 @@
<?php
final class AphrontDialogView extends AphrontView {
private $title;
private $shortTitle;
private $submitButton;
private $cancelURI;
private $cancelText = 'Cancel';
private $submitURI;
private $hidden = array();
private $class;
private $renderAsForm = true;
private $formID;
private $footers = array();
private $isStandalone;
private $method = 'POST';
private $disableWorkflowOnSubmit;
private $disableWorkflowOnCancel;
private $width = 'default';
private $errors = array();
private $flush;
private $validationException;
const WIDTH_DEFAULT = 'default';
const WIDTH_FORM = 'form';
const WIDTH_FULL = 'full';
public function setMethod($method) {
$this->method = $method;
return $this;
}
public function setIsStandalone($is_standalone) {
$this->isStandalone = $is_standalone;
return $this;
}
public function setErrors(array $errors) {
$this->errors = $errors;
return $this;
}
public function getIsStandalone() {
return $this->isStandalone;
}
public function setSubmitURI($uri) {
$this->submitURI = $uri;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function setShortTitle($short_title) {
$this->shortTitle = $short_title;
return $this;
}
public function getShortTitle() {
return $this->shortTitle;
}
public function addSubmitButton($text = null) {
if (!$text) {
$text = pht('Okay');
}
$this->submitButton = $text;
return $this;
}
public function addCancelButton($uri, $text = null) {
if (!$text) {
$text = pht('Cancel');
}
$this->cancelURI = $uri;
$this->cancelText = $text;
return $this;
}
public function addFooter($footer) {
$this->footers[] = $footer;
return $this;
}
public function addHiddenInput($key, $value) {
if (is_array($value)) {
foreach ($value as $hidden_key => $hidden_value) {
$this->hidden[] = array($key.'['.$hidden_key.']', $hidden_value);
}
} else {
$this->hidden[] = array($key, $value);
}
return $this;
}
public function setClass($class) {
$this->class = $class;
return $this;
}
public function setFlush($flush) {
$this->flush = $flush;
return $this;
}
public function setRenderDialogAsDiv() {
// TODO: This API is awkward.
$this->renderAsForm = false;
return $this;
}
public function setFormID($id) {
$this->formID = $id;
return $this;
}
public function setWidth($width) {
$this->width = $width;
return $this;
}
public function appendParagraph($paragraph) {
return $this->appendChild(
phutil_tag(
'p',
array(
'class' => 'aphront-dialog-view-paragraph',
),
$paragraph));
}
public function appendList(array $items) {
$listitems = array();
foreach ($items as $item) {
$listitems[] = phutil_tag(
'li',
array(
'class' => 'remarkup-list-item',
),
$item);
}
return $this->appendChild(
phutil_tag(
'ul',
array(
'class' => 'remarkup-list',
),
$listitems));
}
public function appendForm(AphrontFormView $form) {
return $this->appendChild($form->buildLayoutView());
}
public function setDisableWorkflowOnSubmit($disable_workflow_on_submit) {
$this->disableWorkflowOnSubmit = $disable_workflow_on_submit;
return $this;
}
public function getDisableWorkflowOnSubmit() {
return $this->disableWorkflowOnSubmit;
}
public function setDisableWorkflowOnCancel($disable_workflow_on_cancel) {
$this->disableWorkflowOnCancel = $disable_workflow_on_cancel;
return $this;
}
public function getDisableWorkflowOnCancel() {
return $this->disableWorkflowOnCancel;
}
public function setValidationException(
PhabricatorApplicationTransactionValidationException $ex = null) {
$this->validationException = $ex;
return $this;
}
public function render() {
require_celerity_resource('aphront-dialog-view-css');
$buttons = array();
if ($this->submitButton) {
$meta = array();
if ($this->disableWorkflowOnSubmit) {
$meta['disableWorkflow'] = true;
}
$buttons[] = javelin_tag(
'button',
array(
'name' => '__submit__',
'sigil' => '__default__',
'type' => 'submit',
'meta' => $meta,
),
$this->submitButton);
}
if ($this->cancelURI) {
$meta = array();
if ($this->disableWorkflowOnCancel) {
$meta['disableWorkflow'] = true;
}
$buttons[] = javelin_tag(
'a',
array(
'href' => $this->cancelURI,
'class' => 'button grey',
'name' => '__cancel__',
'sigil' => 'jx-workflow-button',
'meta' => $meta,
),
$this->cancelText);
}
if (!$this->user) {
throw new Exception(
pht(
'You must call %s when rendering an %s.',
'setUser()',
__CLASS__));
}
$more = $this->class;
if ($this->flush) {
$more .= ' aphront-dialog-flush';
}
switch ($this->width) {
case self::WIDTH_FORM:
case self::WIDTH_FULL:
$more .= ' aphront-dialog-view-width-'.$this->width;
break;
case self::WIDTH_DEFAULT:
break;
default:
throw new Exception(
pht(
"Unknown dialog width '%s'!",
$this->width));
}
if ($this->isStandalone) {
$more .= ' aphront-dialog-view-standalone';
}
$attributes = array(
'class' => 'aphront-dialog-view '.$more,
'sigil' => 'jx-dialog',
);
$form_attributes = array(
'action' => $this->submitURI,
'method' => $this->method,
'id' => $this->formID,
);
$hidden_inputs = array();
$hidden_inputs[] = phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => '__dialog__',
'value' => '1',
));
foreach ($this->hidden as $desc) {
list($key, $value) = $desc;
$hidden_inputs[] = javelin_tag(
'input',
array(
'type' => 'hidden',
'name' => $key,
'value' => $value,
'sigil' => 'aphront-dialog-application-input',
));
}
if (!$this->renderAsForm) {
- $buttons = array(phabricator_form(
- $this->user,
- $form_attributes,
- array_merge($hidden_inputs, $buttons)),
+ $buttons = array(
+ phabricator_form(
+ $this->user,
+ $form_attributes,
+ array_merge($hidden_inputs, $buttons)),
);
}
$children = $this->renderChildren();
$errors = $this->errors;
$ex = $this->validationException;
$exception_errors = null;
if ($ex) {
foreach ($ex->getErrors() as $error) {
$errors[] = $error->getMessage();
}
}
if ($errors) {
$children = array(
id(new PHUIInfoView())->setErrors($errors),
$children,
);
}
$header = new PHUIHeaderView();
$header->setHeader($this->title);
$footer = null;
if ($this->footers) {
$footer = phutil_tag(
'div',
array(
'class' => 'aphront-dialog-foot',
),
$this->footers);
}
$tail = null;
if ($buttons || $footer) {
$tail = phutil_tag(
'div',
array(
'class' => 'aphront-dialog-tail grouped',
),
array(
$buttons,
$footer,
));
}
$content = array(
phutil_tag(
'div',
array(
'class' => 'aphront-dialog-head',
),
$header),
phutil_tag('div',
array(
'class' => 'aphront-dialog-body phabricator-remarkup grouped',
),
$children),
$tail,
);
if ($this->renderAsForm) {
return phabricator_form(
$this->user,
$form_attributes + $attributes,
array($hidden_inputs, $content));
} else {
return javelin_tag(
'div',
$attributes,
$content);
}
}
}
diff --git a/src/view/__tests__/PhabricatorUnitsTestCase.php b/src/view/__tests__/PhabricatorUnitsTestCase.php
index bd0f535008..aae9598ddf 100644
--- a/src/view/__tests__/PhabricatorUnitsTestCase.php
+++ b/src/view/__tests__/PhabricatorUnitsTestCase.php
@@ -1,130 +1,130 @@
<?php
final class PhabricatorUnitsTestCase extends PhabricatorTestCase {
// NOTE: Keep tests below PHP_INT_MAX on 32-bit systems, since if you write
// larger numeric literals they'll evaluate to nonsense.
public function testByteFormatting() {
$tests = array(
1 => '1 B',
1024 => '1 KB',
1024 * 1024 => '1 MB',
10 * 1024 * 1024 => '10 MB',
100 * 1024 * 1024 => '100 MB',
1024 * 1024 * 1024 => '1 GB',
999 => '999 B',
);
foreach ($tests as $input => $expect) {
$this->assertEqual(
$expect,
phutil_format_bytes($input),
'phutil_format_bytes('.$input.')');
}
}
public function testByteParsing() {
$tests = array(
'1' => 1,
'1k' => 1024,
'1K' => 1024,
'1kB' => 1024,
'1Kb' => 1024,
'1KB' => 1024,
'1MB' => 1024 * 1024,
'1GB' => 1024 * 1024 * 1024,
'1.5M' => (int)(1024 * 1024 * 1.5),
'1 000' => 1000,
'1,234.56 KB' => (int)(1024 * 1234.56),
);
foreach ($tests as $input => $expect) {
$this->assertEqual(
$expect,
phutil_parse_bytes($input),
'phutil_parse_bytes('.$input.')');
}
$this->tryTestCases(
array('string' => 'string'),
array(false),
'phutil_parse_bytes');
}
public function testDetailedDurationFormatting() {
$expected_zero = 'now';
- $tests = array (
+ $tests = array(
12095939 => '19 w, 6 d',
-12095939 => '19 w, 6 d ago',
3380521 => '5 w, 4 d',
-3380521 => '5 w, 4 d ago',
0 => $expected_zero,
);
foreach ($tests as $duration => $expect) {
$this->assertEqual(
$expect,
phutil_format_relative_time_detailed($duration),
'phutil_format_relative_time_detailed('.$duration.')');
}
$tests = array(
3380521 => array(
-1 => '5 w',
0 => '5 w',
1 => '5 w',
2 => '5 w, 4 d',
3 => '5 w, 4 d, 3 h',
4 => '5 w, 4 d, 3 h, 2 m',
5 => '5 w, 4 d, 3 h, 2 m, 1 s',
6 => '5 w, 4 d, 3 h, 2 m, 1 s',
),
-3380521 => array(
-1 => '5 w ago',
0 => '5 w ago',
1 => '5 w ago',
2 => '5 w, 4 d ago',
3 => '5 w, 4 d, 3 h ago',
4 => '5 w, 4 d, 3 h, 2 m ago',
5 => '5 w, 4 d, 3 h, 2 m, 1 s ago',
6 => '5 w, 4 d, 3 h, 2 m, 1 s ago',
),
0 => array(
-1 => $expected_zero,
0 => $expected_zero,
1 => $expected_zero,
2 => $expected_zero,
3 => $expected_zero,
4 => $expected_zero,
5 => $expected_zero,
6 => $expected_zero,
),
);
foreach ($tests as $duration => $sub_tests) {
if (is_array($sub_tests)) {
foreach ($sub_tests as $levels => $expect) {
$this->assertEqual(
$expect,
phutil_format_relative_time_detailed($duration, $levels),
'phutil_format_relative_time_detailed('.$duration.',
'.$levels.')');
}
} else {
$expect = $sub_tests;
$this->assertEqual(
$expect,
phutil_format_relative_time_detailed($duration),
'phutil_format_relative_time_detailed('.$duration.')');
}
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Feb 4, 6:30 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
738967
Default Alt Text
(102 KB)

Event Timeline