mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-28 00:10:57 +01:00
Merge branch 'master' into phutil_tag
Auditors: vrana
This commit is contained in:
commit
4bd2ad9270
49 changed files with 855 additions and 241 deletions
|
@ -1174,7 +1174,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-dark-console' =>
|
||||
array(
|
||||
'uri' => '/res/ae7f15ce/rsrc/js/application/core/behavior-dark-console.js',
|
||||
'uri' => '/res/89aeb6c0/rsrc/js/application/core/behavior-dark-console.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1864,6 +1864,18 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/phame/phame-post-preview.js',
|
||||
),
|
||||
'javelin-behavior-pholio-edit-inline-comment' =>
|
||||
array(
|
||||
'uri' => '/res/61759cd8/rsrc/js/application/pholio/behavior-pholio-edit-inline-comment.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-stratcom',
|
||||
2 => 'javelin-dom',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/pholio/behavior-pholio-edit-inline-comment.js',
|
||||
),
|
||||
'javelin-behavior-pholio-mock-view' =>
|
||||
array(
|
||||
'uri' => '/res/e5f432ac/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
|
||||
|
@ -3488,7 +3500,7 @@ celerity_register_resource_map(array(
|
|||
'uri' => '/res/pkg/bc0774e5/core.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'3e0098ea' =>
|
||||
'dca4a03d' =>
|
||||
array(
|
||||
'name' => 'darkconsole.pkg.js',
|
||||
'symbols' =>
|
||||
|
@ -3496,7 +3508,7 @@ celerity_register_resource_map(array(
|
|||
0 => 'javelin-behavior-dark-console',
|
||||
1 => 'javelin-behavior-error-log',
|
||||
),
|
||||
'uri' => '/res/pkg/3e0098ea/darkconsole.pkg.js',
|
||||
'uri' => '/res/pkg/dca4a03d/darkconsole.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'8aaacd1b' =>
|
||||
|
@ -3666,7 +3678,7 @@ celerity_register_resource_map(array(
|
|||
'javelin-behavior-aphront-drag-and-drop-textarea' => '95d0d865',
|
||||
'javelin-behavior-aphront-form-disable-on-submit' => 'bc0774e5',
|
||||
'javelin-behavior-audit-preview' => 'f96657b8',
|
||||
'javelin-behavior-dark-console' => '3e0098ea',
|
||||
'javelin-behavior-dark-console' => 'dca4a03d',
|
||||
'javelin-behavior-device' => 'bc0774e5',
|
||||
'javelin-behavior-differential-accept-with-errors' => '95d0d865',
|
||||
'javelin-behavior-differential-add-reviewers-and-ccs' => '95d0d865',
|
||||
|
@ -3682,7 +3694,7 @@ celerity_register_resource_map(array(
|
|||
'javelin-behavior-differential-user-select' => '95d0d865',
|
||||
'javelin-behavior-diffusion-commit-graph' => 'f96657b8',
|
||||
'javelin-behavior-diffusion-pull-lastmodified' => 'f96657b8',
|
||||
'javelin-behavior-error-log' => '3e0098ea',
|
||||
'javelin-behavior-error-log' => 'dca4a03d',
|
||||
'javelin-behavior-global-drag-and-drop' => 'bc0774e5',
|
||||
'javelin-behavior-konami' => 'bc0774e5',
|
||||
'javelin-behavior-lightbox-attachments' => 'bc0774e5',
|
||||
|
|
|
@ -146,6 +146,7 @@ phutil_register_library_map(array(
|
|||
'ConduitAPI_file_download_Method' => 'applications/files/conduit/ConduitAPI_file_download_Method.php',
|
||||
'ConduitAPI_file_info_Method' => 'applications/files/conduit/ConduitAPI_file_info_Method.php',
|
||||
'ConduitAPI_file_upload_Method' => 'applications/files/conduit/ConduitAPI_file_upload_Method.php',
|
||||
'ConduitAPI_file_uploadhash_Method' => 'applications/files/conduit/ConduitAPI_file_uploadhash_Method.php',
|
||||
'ConduitAPI_flag_Method' => 'applications/flag/conduit/ConduitAPI_flag_Method.php',
|
||||
'ConduitAPI_flag_delete_Method' => 'applications/flag/conduit/ConduitAPI_flag_delete_Method.php',
|
||||
'ConduitAPI_flag_edit_Method' => 'applications/flag/conduit/ConduitAPI_flag_edit_Method.php',
|
||||
|
@ -626,6 +627,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationAudit' => 'applications/audit/application/PhabricatorApplicationAudit.php',
|
||||
'PhabricatorApplicationAuth' => 'applications/auth/application/PhabricatorApplicationAuth.php',
|
||||
'PhabricatorApplicationCalendar' => 'applications/calendar/application/PhabricatorApplicationCalendar.php',
|
||||
'PhabricatorApplicationChatLog' => 'applications/chatlog/applications/PhabricatorApplicationChatLog.php',
|
||||
'PhabricatorApplicationConduit' => 'applications/conduit/application/PhabricatorApplicationConduit.php',
|
||||
'PhabricatorApplicationConfig' => 'applications/config/application/PhabricatorApplicationConfig.php',
|
||||
'PhabricatorApplicationConfigOptions' => 'applications/config/option/PhabricatorApplicationConfigOptions.php',
|
||||
|
@ -1641,6 +1643,7 @@ phutil_register_library_map(array(
|
|||
'ConduitAPI_file_download_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_file_info_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_file_uploadhash_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_flag_Method' => 'ConduitAPIMethod',
|
||||
'ConduitAPI_flag_delete_Method' => 'ConduitAPI_flag_Method',
|
||||
'ConduitAPI_flag_edit_Method' => 'ConduitAPI_flag_Method',
|
||||
|
@ -2068,6 +2071,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationAudit' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationAuth' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationCalendar' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationChatLog' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationConduit' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationConfig' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationConfigOptions' => 'Phobject',
|
||||
|
|
|
@ -199,15 +199,7 @@ final class AphrontRequest {
|
|||
// No token in the request, check the HTTP header which is added for Ajax
|
||||
// requests.
|
||||
if (empty($token)) {
|
||||
|
||||
// PHP mangles HTTP headers by uppercasing them and replacing hyphens with
|
||||
// underscores, then prepending 'HTTP_'.
|
||||
$php_index = self::getCSRFHeaderName();
|
||||
$php_index = strtoupper($php_index);
|
||||
$php_index = str_replace('-', '_', $php_index);
|
||||
$php_index = 'HTTP_'.$php_index;
|
||||
|
||||
$token = idx($_SERVER, $php_index);
|
||||
$token = self::getHTTPHeader(self::getCSRFHeaderName());
|
||||
}
|
||||
|
||||
$valid = $this->getUser()->validateCSRFToken($token);
|
||||
|
@ -430,4 +422,14 @@ final class AphrontRequest {
|
|||
}
|
||||
|
||||
|
||||
public static function getHTTPHeader($name, $default = null) {
|
||||
// PHP mangles HTTP headers by uppercasing them and replacing hyphens with
|
||||
// underscores, then prepending 'HTTP_'.
|
||||
$php_index = strtoupper($name);
|
||||
$php_index = str_replace('-', '_', $php_index);
|
||||
$php_index = 'HTTP_'.$php_index;
|
||||
|
||||
return idx($_SERVER, $php_index, $default);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -122,6 +122,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
protected function getResourceURIMapRules() {
|
||||
return array(
|
||||
'/res/' => array(
|
||||
'(?:(?P<mtime>[0-9]+)T/)?'.
|
||||
'(?P<package>pkg/)?'.
|
||||
'(?P<hash>[a-f0-9]{8})/'.
|
||||
'(?P<path>.+\.(?:css|js|jpg|png|swf|gif))'
|
||||
|
|
|
@ -101,8 +101,7 @@ final class DarkConsoleXHProfPlugin extends DarkConsolePlugin {
|
|||
|
||||
|
||||
public function willShutdown() {
|
||||
if (DarkConsoleXHProfPluginAPI::isProfilerRequested() &&
|
||||
(DarkConsoleXHProfPluginAPI::isProfilerRequested() !== 'all')) {
|
||||
if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
|
||||
$this->xhprofID = DarkConsoleXHProfPluginAPI::stopProfiler();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,25 +13,46 @@ final class DarkConsoleXHProfPluginAPI {
|
|||
return extension_loaded('xhprof');
|
||||
}
|
||||
|
||||
public static function getProfilerHeader() {
|
||||
return 'X-Phabricator-Profiler';
|
||||
}
|
||||
|
||||
public static function isProfilerRequested() {
|
||||
if (!empty($_REQUEST['__profile__'])) {
|
||||
return $_REQUEST['__profile__'];
|
||||
}
|
||||
|
||||
static $profilerRequested = null;
|
||||
$header = AphrontRequest::getHTTPHeader(self::getProfilerHeader());
|
||||
if ($header) {
|
||||
return $header;
|
||||
}
|
||||
|
||||
if (!isset($profilerRequested)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function shouldStartProfiler() {
|
||||
if (self::isProfilerRequested()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static $sample_request = null;
|
||||
|
||||
if ($sample_request === null) {
|
||||
if (PhabricatorEnv::getEnvConfig('debug.profile-rate')) {
|
||||
$rate = PhabricatorEnv::getEnvConfig('debug.profile-rate');
|
||||
if (mt_rand(1, $rate) == 1) {
|
||||
$profilerRequested = true;
|
||||
$sample_request = true;
|
||||
} else {
|
||||
$profilerRequested = false;
|
||||
$sample_request = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $profilerRequested;
|
||||
return $sample_request;
|
||||
}
|
||||
|
||||
public static function isProfilerStarted() {
|
||||
return self::$profilerStarted;
|
||||
}
|
||||
|
||||
public static function includeXHProfLib() {
|
||||
|
@ -47,8 +68,40 @@ final class DarkConsoleXHProfPluginAPI {
|
|||
}
|
||||
|
||||
|
||||
public static function saveProfilerSample(
|
||||
AphrontRequest $request,
|
||||
$access_log) {
|
||||
|
||||
if (!self::isProfilerStarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$profile = DarkConsoleXHProfPluginAPI::stopProfiler();
|
||||
$profile_sample = id(new PhabricatorXHProfSample())
|
||||
->setFilePHID($profile);
|
||||
|
||||
if (self::isProfilerRequested()) {
|
||||
$sample_rate = 0;
|
||||
} else {
|
||||
$sample_rate = PhabricatorEnv::getEnvConfig('debug.profile-rate');
|
||||
}
|
||||
|
||||
$profile_sample->setSampleRate($sample_rate);
|
||||
|
||||
if ($access_log) {
|
||||
$profile_sample
|
||||
->setUsTotal($access_log->getData('T'))
|
||||
->setHostname($access_log->getData('h'))
|
||||
->setRequestPath($access_log->getData('U'))
|
||||
->setController($access_log->getData('C'))
|
||||
->setUserPHID($request->getUser()->getPHID());
|
||||
}
|
||||
|
||||
$profile_sample->save();
|
||||
}
|
||||
|
||||
public static function hookProfiler() {
|
||||
if (!self::isProfilerRequested()) {
|
||||
if (!self::shouldStartProfiler()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -70,49 +123,49 @@ final class DarkConsoleXHProfPluginAPI {
|
|||
}
|
||||
|
||||
public static function stopProfiler() {
|
||||
if (self::$profilerStarted) {
|
||||
$data = xhprof_disable();
|
||||
$data = serialize($data);
|
||||
$file_class = 'PhabricatorFile';
|
||||
|
||||
// Since these happen on GET we can't do guarded writes. These also
|
||||
// sometimes happen after we've disposed of the write guard; in this
|
||||
// case we need to disable the whole mechanism.
|
||||
|
||||
$use_scope = AphrontWriteGuard::isGuardActive();
|
||||
if ($use_scope) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
} else {
|
||||
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
|
||||
}
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$file = call_user_func(
|
||||
array($file_class, 'newFromFileData'),
|
||||
$data,
|
||||
array(
|
||||
'mime-type' => 'application/xhprof',
|
||||
'name' => 'profile.xhprof',
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
if ($use_scope) {
|
||||
unset($unguarded);
|
||||
} else {
|
||||
AphrontWriteGuard::allowDangerousUnguardedWrites(false);
|
||||
}
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
} else {
|
||||
return $file->getPHID();
|
||||
}
|
||||
} else {
|
||||
if (!self::isProfilerStarted()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = xhprof_disable();
|
||||
$data = serialize($data);
|
||||
$file_class = 'PhabricatorFile';
|
||||
|
||||
// Since these happen on GET we can't do guarded writes. These also
|
||||
// sometimes happen after we've disposed of the write guard; in this
|
||||
// case we need to disable the whole mechanism.
|
||||
|
||||
$use_scope = AphrontWriteGuard::isGuardActive();
|
||||
if ($use_scope) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
} else {
|
||||
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
|
||||
}
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$file = call_user_func(
|
||||
array($file_class, 'newFromFileData'),
|
||||
$data,
|
||||
array(
|
||||
'mime-type' => 'application/xhprof',
|
||||
'name' => 'profile.xhprof',
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
if ($use_scope) {
|
||||
unset($unguarded);
|
||||
} else {
|
||||
AphrontWriteGuard::allowDangerousUnguardedWrites(false);
|
||||
}
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
} else {
|
||||
return $file->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorApplicationChatLog extends PhabricatorApplication {
|
||||
|
||||
public function getBaseURI() {
|
||||
return '/chatlog/';
|
||||
}
|
||||
|
||||
public function getShortDescription() {
|
||||
return 'Chat Log';
|
||||
}
|
||||
|
||||
public function getIconName() {
|
||||
return 'chatlog';
|
||||
}
|
||||
|
||||
public function isBeta() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getTitleGlyph() {
|
||||
return "\xE0\xBC\x84";
|
||||
}
|
||||
|
||||
public function getApplicationGroup() {
|
||||
return self::GROUP_COMMUNICATION;
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/chatlog/' => array(
|
||||
'' => 'PhabricatorChatLogChannelListController',
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -10,6 +10,10 @@ final class PhabricatorApplicationConduit extends PhabricatorApplication {
|
|||
return 'conduit';
|
||||
}
|
||||
|
||||
public function canUninstall() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getHelpURI() {
|
||||
return PhabricatorEnv::getDoclink(
|
||||
'article/Conduit_Technical_Documentation.html');
|
||||
|
|
|
@ -5,7 +5,7 @@ final class PhabricatorSetupCheckBaseURI extends PhabricatorSetupCheck {
|
|||
protected function executeChecks() {
|
||||
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
|
||||
|
||||
if (strpos($_SERVER['HTTP_HOST'], '.') === false) {
|
||||
if (strpos(AphrontRequest::getHTTPHeader('Host'), '.') === false) {
|
||||
$summary = pht(
|
||||
'The domain does not contain a dot. This is necessary for some web '.
|
||||
'browsers to be able to set cookies.');
|
||||
|
|
|
@ -330,6 +330,7 @@ final class PhabricatorConfigEditController
|
|||
case 'int':
|
||||
case 'string':
|
||||
case 'enum':
|
||||
case 'class':
|
||||
return $value;
|
||||
case 'bool':
|
||||
return $value ? 'true' : 'false';
|
||||
|
|
|
@ -227,6 +227,11 @@ final class ConpherenceThreadQuery
|
|||
$conpherence_pic_phids[$conpherence->getPHID()] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$conpherence_pic_phids) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($conpherence_pic_phids)
|
||||
|
|
|
@ -71,10 +71,6 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||
break;
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
$comment = $transaction->getComment();
|
||||
$file_ids =
|
||||
PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
|
||||
array($comment->getContent())
|
||||
);
|
||||
$content = $this->markupEngine->getOutput(
|
||||
$comment,
|
||||
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
|
||||
|
|
|
@ -117,7 +117,7 @@ final class DifferentialLintFieldSpecification
|
|||
'show' => $show,
|
||||
);
|
||||
|
||||
if (isset($message['locations'])) {
|
||||
if (!empty($message['locations'])) {
|
||||
$locations = array();
|
||||
foreach ($message['locations'] as $location) {
|
||||
$other_line = idx($location, 'line');
|
||||
|
|
|
@ -420,14 +420,6 @@ final class DifferentialChangesetParser {
|
|||
}
|
||||
|
||||
private function getHighlightFuture($corpus) {
|
||||
if (preg_match('/\r(?!\n)/', $corpus)) {
|
||||
// TODO: Pygments converts "\r" newlines into "\n" newlines, so we can't
|
||||
// use it on files with "\r" newlines. If we have "\r" not followed by
|
||||
// "\n" in the file, skip highlighting.
|
||||
$result = phutil_escape_html($corpus);
|
||||
return new ImmediateFuture($result);
|
||||
}
|
||||
|
||||
return $this->highlightEngine->getHighlightFuture(
|
||||
$this->highlightEngine->getLanguageFromFilename($this->filename),
|
||||
$corpus);
|
||||
|
|
|
@ -12,9 +12,7 @@ final class DiffusionGitRequest extends DiffusionRequest {
|
|||
protected function didInitialize() {
|
||||
$repository = $this->getRepository();
|
||||
|
||||
if (!Filesystem::pathExists($repository->getLocalPath())) {
|
||||
$this->raiseCloneException();
|
||||
}
|
||||
$this->validateWorkingCopy($repository->getLocalPath());
|
||||
|
||||
if (!$this->commit) {
|
||||
return;
|
||||
|
|
|
@ -12,9 +12,7 @@ final class DiffusionMercurialRequest extends DiffusionRequest {
|
|||
protected function didInitialize() {
|
||||
$repository = $this->getRepository();
|
||||
|
||||
if (!Filesystem::pathExists($repository->getLocalPath())) {
|
||||
$this->raiseCloneException();
|
||||
}
|
||||
$this->validateWorkingCopy($repository->getLocalPath());
|
||||
|
||||
// Expand abbreviated hashes to full hashes so "/rXnnnn" (i.e., fewer than
|
||||
// 40 characters) works correctly.
|
||||
|
|
|
@ -544,6 +544,30 @@ abstract class DiffusionRequest {
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the working copy of the repository is present and readable.
|
||||
*
|
||||
* @param string Path to the working copy.
|
||||
*/
|
||||
protected function validateWorkingCopy($path) {
|
||||
if (!is_readable(dirname($path))) {
|
||||
$this->raisePermissionException();
|
||||
}
|
||||
|
||||
if (!Filesystem::pathExists($path)) {
|
||||
$this->raiseCloneException();
|
||||
}
|
||||
}
|
||||
|
||||
protected function raisePermissionException() {
|
||||
$host = php_uname('n');
|
||||
$callsign = $this->getRepository()->getCallsign();
|
||||
throw new DiffusionSetupException(
|
||||
"The clone of this repository ('{$callsign}') on the local machine " .
|
||||
"('{$host}') could not be read. Ensure that the repository is in a " .
|
||||
"location where the web server has read permissions.");
|
||||
}
|
||||
|
||||
protected function raiseCloneException() {
|
||||
$host = php_uname('n');
|
||||
$callsign = $this->getRepository()->getCallsign();
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group conduit
|
||||
*/
|
||||
final class ConduitAPI_file_uploadhash_Method extends ConduitAPIMethod {
|
||||
|
||||
public function getMethodDescription() {
|
||||
return "Upload a file to the server using content hash.";
|
||||
}
|
||||
|
||||
public function defineParamTypes() {
|
||||
return array(
|
||||
'hash' => 'required nonempty string',
|
||||
'name' => 'required nonempty string',
|
||||
);
|
||||
}
|
||||
|
||||
public function defineReturnType() {
|
||||
return 'phid or null';
|
||||
}
|
||||
|
||||
public function defineErrorTypes() {
|
||||
return array(
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(ConduitAPIRequest $request) {
|
||||
$hash = $request->getValue('hash');
|
||||
$name = $request->getValue('name');
|
||||
$user = $request->getUser();
|
||||
|
||||
$file = PhabricatorFile::newFileFromContentHash(
|
||||
$hash,
|
||||
array(
|
||||
'name' => $name,
|
||||
'authorPHID' => $user->getPHID(),
|
||||
));
|
||||
|
||||
if ($file) {
|
||||
return $file->getPHID();
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ final class PhabricatorTestStorageEngine
|
|||
public function writeFile($data, array $params) {
|
||||
AphrontWriteGuard::willWrite();
|
||||
self::$storage[self::$nextHandle] = $data;
|
||||
return self::$nextHandle++;
|
||||
return (string)self::$nextHandle++;
|
||||
}
|
||||
|
||||
public function readFile($handle) {
|
||||
|
|
|
@ -132,8 +132,46 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
return $file;
|
||||
}
|
||||
|
||||
public static function newFileFromContentHash($hash, $params) {
|
||||
|
||||
public static function newFromFileData($data, array $params = array()) {
|
||||
// Check to see if a file with same contentHash exist
|
||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||
'contentHash = %s LIMIT 1', $hash);
|
||||
|
||||
if ($file) {
|
||||
// copy storageEngine, storageHandle, storageFormat
|
||||
$copy_of_storage_engine = $file->getStorageEngine();
|
||||
$copy_of_storage_handle = $file->getStorageHandle();
|
||||
$copy_of_storage_format = $file->getStorageFormat();
|
||||
$copy_of_byteSize = $file->getByteSize();
|
||||
$copy_of_mimeType = $file->getMimeType();
|
||||
|
||||
$file_name = idx($params, 'name');
|
||||
$file_name = self::normalizeFileName($file_name);
|
||||
$authorPHID = idx($params, 'authorPHID');
|
||||
|
||||
$new_file = new PhabricatorFile();
|
||||
|
||||
$new_file->setName($file_name);
|
||||
$new_file->setByteSize($copy_of_byteSize);
|
||||
$new_file->setAuthorPHID($authorPHID);
|
||||
|
||||
$new_file->setContentHash($hash);
|
||||
$new_file->setStorageEngine($copy_of_storage_engine);
|
||||
$new_file->setStorageHandle($copy_of_storage_handle);
|
||||
$new_file->setStorageFormat($copy_of_storage_format);
|
||||
$new_file->setMimeType($copy_of_mimeType);
|
||||
|
||||
$new_file->save();
|
||||
|
||||
return $new_file;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private static function buildFromFileData($data, array $params = array()) {
|
||||
$selector = PhabricatorEnv::newObjectFromConfig('storage.engine-selector');
|
||||
|
||||
if (isset($params['storageEngines'])) {
|
||||
$engines = $params['storageEngines'];
|
||||
|
@ -221,6 +259,17 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
return $file;
|
||||
}
|
||||
|
||||
public static function newFromFileData($data, array $params = array()) {
|
||||
$hash = self::hashFileContent($data);
|
||||
$file = self::newFileFromContentHash($hash, $params);
|
||||
|
||||
if ($file) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return self::buildFromFileData($data, $params);
|
||||
}
|
||||
|
||||
public function migrateToEngine(PhabricatorFileStorageEngine $engine) {
|
||||
if (!$this->getID() || !$this->getStorageHandle()) {
|
||||
throw new Exception(
|
||||
|
@ -305,15 +354,28 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
}
|
||||
|
||||
public function delete() {
|
||||
$engine = $this->instantiateStorageEngine();
|
||||
// Check to see if other files are using storage
|
||||
$other_file = id(new PhabricatorFile())->loadAllWhere(
|
||||
'storageEngine = %s AND storageHandle = %s AND
|
||||
storageFormat = %s AND id != %d LIMIT 1', $this->getStorageEngine(),
|
||||
$this->getStorageHandle(), $this->getStorageFormat(),
|
||||
$this->getID());
|
||||
|
||||
// If this is the only file using the storage, delete storage
|
||||
if (count($other_file) == 0) {
|
||||
$engine = $this->instantiateStorageEngine();
|
||||
$engine->deleteFile($this->getStorageHandle());
|
||||
}
|
||||
|
||||
$ret = parent::delete();
|
||||
|
||||
$engine->deleteFile($this->getStorageHandle());
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function hashFileContent($data) {
|
||||
return PhabricatorHash::digest($data);
|
||||
}
|
||||
|
||||
public function loadFileData() {
|
||||
|
||||
$engine = $this->instantiateStorageEngine();
|
||||
|
|
|
@ -31,6 +31,55 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase {
|
|||
$this->assertEqual($data, $file->loadFileData());
|
||||
}
|
||||
|
||||
public function testFileStorageUploadDifferentFiles() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
|
||||
$data = Filesystem::readRandomCharacters(64);
|
||||
$other_data = Filesystem::readRandomCharacters(64);
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
);
|
||||
|
||||
$first_file = PhabricatorFile::newFromFileData($data, $params);
|
||||
|
||||
$second_file = PhabricatorFile::newFromFileData($other_data, $params);
|
||||
|
||||
// Test that the the second file uses different storage handle from
|
||||
// the first file.
|
||||
$first_handle = $first_file->getStorageHandle();
|
||||
$second_handle = $second_file->getStorageHandle();
|
||||
|
||||
$this->assertEqual(true, ($first_handle != $second_handle));
|
||||
}
|
||||
|
||||
|
||||
public function testFileStorageUploadSameFile() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
|
||||
$data = Filesystem::readRandomCharacters(64);
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
);
|
||||
|
||||
$first_file = PhabricatorFile::newFromFileData($data, $params);
|
||||
|
||||
$second_file = PhabricatorFile::newFromFileData($data, $params);
|
||||
|
||||
// Test that the the second file uses the same storage handle as
|
||||
// the first file.
|
||||
$handle = $first_file->getStorageHandle();
|
||||
$second_handle = $second_file->getStorageHandle();
|
||||
|
||||
$this->assertEqual($handle, $second_handle);
|
||||
}
|
||||
|
||||
public function testFileStorageDelete() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
|
@ -58,4 +107,23 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase {
|
|||
$this->assertEqual(true, $caught instanceof Exception);
|
||||
}
|
||||
|
||||
public function testFileStorageDeleteSharedHandle() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
|
||||
$data = Filesystem::readRandomCharacters(64);
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
);
|
||||
|
||||
$first_file = PhabricatorFile::newFromFileData($data, $params);
|
||||
$second_file = PhabricatorFile::newFromFileData($data, $params);
|
||||
$first_file->delete();
|
||||
|
||||
$this->assertEqual($data, $second_file->loadFileData());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,13 +9,14 @@ abstract class PhabricatorPasteController extends PhabricatorController {
|
|||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI('filter/')));
|
||||
|
||||
if ($for_app) {
|
||||
$nav->addFilter('', 'Create Paste', $this->getApplicationURI('/create/'));
|
||||
$nav->addFilter('', pht('Create Paste'),
|
||||
$this->getApplicationURI('/create/'));
|
||||
}
|
||||
|
||||
$nav->addLabel('Filters');
|
||||
$nav->addFilter('all', 'All Pastes');
|
||||
$nav->addLabel(pht('Filters'));
|
||||
$nav->addFilter('all', pht('All Pastes'));
|
||||
if ($user->isLoggedIn()) {
|
||||
$nav->addFilter('my', 'My Pastes');
|
||||
$nav->addFilter('my', pht('My Pastes'));
|
||||
}
|
||||
|
||||
$nav->selectFilter($filter, 'all');
|
||||
|
|
|
@ -64,8 +64,8 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
|
|||
if ($is_create) {
|
||||
$text = $request->getStr('text');
|
||||
if (!strlen($text)) {
|
||||
$e_text = 'Required';
|
||||
$errors[] = 'The paste may not be blank.';
|
||||
$e_text = pht('Required');
|
||||
$errors[] = pht('The paste may not be blank.');
|
||||
} else {
|
||||
$e_text = null;
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
|
|||
}
|
||||
} else {
|
||||
if ($is_create && $parent) {
|
||||
$paste->setTitle('Fork of '.$parent->getFullName());
|
||||
$paste->setTitle(pht('Fork of %s', $parent->getFullName()));
|
||||
$paste->setLanguage($parent->getLanguage());
|
||||
$text = $parent->getRawContent();
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
|
|||
$error_view = null;
|
||||
if ($errors) {
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setTitle('A fatal omission!')
|
||||
->setTitle(pht('A Fatal Omission!'))
|
||||
->setErrors($errors);
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
|
|||
$form->setFlexible(true);
|
||||
|
||||
$langs = array(
|
||||
'' => '(Detect From Filename in Title)',
|
||||
'' => pht('(Detect From Filename in Title)'),
|
||||
) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices');
|
||||
|
||||
$form
|
||||
|
@ -119,12 +119,12 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
|
|||
->addHiddenInput('parent', $parent_id)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Title')
|
||||
->setLabel(pht('Title'))
|
||||
->setValue($paste->getTitle())
|
||||
->setName('title'))
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setLabel('Language')
|
||||
->setLabel(pht('Language'))
|
||||
->setName('language')
|
||||
->setValue($paste->getLanguage())
|
||||
->setOptions($langs));
|
||||
|
@ -146,7 +146,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
|
|||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel('Text')
|
||||
->setLabel(pht('Text'))
|
||||
->setError($e_text)
|
||||
->setValue($text)
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
|
||||
|
@ -158,13 +158,13 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
|
|||
array(
|
||||
'href' => $this->getApplicationURI('?parent='.$paste->getID())
|
||||
),
|
||||
'Fork'
|
||||
pht('Fork')
|
||||
);
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel('Text')
|
||||
->setValue(hsprintf(
|
||||
->setLabel(pht('Text'))
|
||||
->setValue(pht(
|
||||
'Paste text can not be edited. %s to create a new paste.',
|
||||
$fork_link)));
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
|
|||
public function getFullName() {
|
||||
$title = $this->getTitle();
|
||||
if (!$title) {
|
||||
$title = '(An Untitled Masterwork)';
|
||||
$title = pht('(An Untitled Masterwork)');
|
||||
}
|
||||
return 'P'.$this->getID().' '.$title;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
|
|||
$this->setSession(idx($_COOKIE, 'phsid'));
|
||||
}
|
||||
$this->details['host'] = php_uname('n');
|
||||
$this->details['user_agent'] = idx($_SERVER, 'HTTP_USER_AGENT');
|
||||
$this->details['user_agent'] = AphrontRequest::getHTTPHeader('User-Agent');
|
||||
|
||||
return parent::save();
|
||||
}
|
||||
|
|
|
@ -20,10 +20,27 @@ final class PholioInlineController extends PholioController {
|
|||
$this->id
|
||||
);
|
||||
|
||||
$inline_comments = array_merge(
|
||||
$inline_comments,
|
||||
id(new PholioTransactionComment())->loadAllWhere(
|
||||
'imageid = %d AND authorphid = %s AND transactionphid IS NULL',
|
||||
$this->id,
|
||||
$user->getPHID()));
|
||||
|
||||
$inlines = array();
|
||||
foreach ($inline_comments as $inline_comment) {
|
||||
$author = id(new PhabricatorUser())->loadOneWhere(
|
||||
'phid = %s',
|
||||
$inline_comment->getAuthorPHID()
|
||||
);
|
||||
$inlines[] = array(
|
||||
'phid' => $inline_comment->getPHID(),
|
||||
'userphid' => $author->getPHID(),
|
||||
'username' => $author->getUserName(),
|
||||
'canEdit' => ($inline_comment->
|
||||
getEditPolicy(PhabricatorPolicyCapability::CAN_EDIT) ==
|
||||
$user->getPHID()) ? true : false,
|
||||
'transactionphid' => $inline_comment->getTransactionPHID(),
|
||||
'imageID' => $inline_comment->getImageID(),
|
||||
'x' => $inline_comment->getX(),
|
||||
'y' => $inline_comment->getY(),
|
||||
|
|
|
@ -13,8 +13,7 @@ final class PholioInlineSaveController extends PholioController {
|
|||
->setViewer($user)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
PhabricatorPolicyCapability::CAN_VIEW
|
||||
))
|
||||
->withIDs(array($request->getInt('mockID')))
|
||||
->executeOne();
|
||||
|
|
|
@ -47,12 +47,22 @@ final class PholioMockImagesView extends AphrontView {
|
|||
$main_image_tag
|
||||
);
|
||||
|
||||
|
||||
$inline_comments_holder = javelin_tag(
|
||||
'div',
|
||||
array(
|
||||
'id' => 'mock-inline-comments',
|
||||
'sigil' => 'mock-inline-comments',
|
||||
'class' => 'pholio-mock-inline-comments'
|
||||
),
|
||||
"");
|
||||
|
||||
$mockview[] = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'pholio-mock-image-container',
|
||||
),
|
||||
$main_image_tag);
|
||||
array($main_image_tag, $inline_comments_holder));
|
||||
|
||||
if (count($this->mock->getImages()) > 1) {
|
||||
$thumbnails = array();
|
||||
|
|
|
@ -249,7 +249,7 @@ final class PhrictionEditController
|
|||
'uri' => '/phriction/preview/?draftkey='.$draft_key,
|
||||
));
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$draft_note,
|
||||
$error_view,
|
||||
|
@ -258,6 +258,7 @@ final class PhrictionEditController
|
|||
),
|
||||
array(
|
||||
'title' => pht('Edit Document'),
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
final class PhabricatorApplicationProject extends PhabricatorApplication {
|
||||
|
||||
public function getName() {
|
||||
return 'Projects';
|
||||
return pht('Projects');
|
||||
}
|
||||
|
||||
public function getShortDescription() {
|
||||
return 'Organize Work';
|
||||
return pht('Organize Work');
|
||||
}
|
||||
|
||||
public function getBaseURI() {
|
||||
|
|
|
@ -5,7 +5,7 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||
public function buildStandardPageResponse($view, array $data) {
|
||||
$page = $this->buildStandardPageView();
|
||||
|
||||
$page->setApplicationName('Project');
|
||||
$page->setApplicationName(pht('Project'));
|
||||
$page->setBaseURI('/project/');
|
||||
$page->setTitle(idx($data, 'title'));
|
||||
$page->setGlyph("\xE2\x98\xA3");
|
||||
|
@ -30,29 +30,29 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||
$edit_uri = '/project/edit/'.$id.'/';
|
||||
$members_uri = '/project/members/'.$id.'/';
|
||||
|
||||
$nav_view->addFilter('dashboard', 'Dashboard');
|
||||
$nav_view->addFilter('feed', 'Feed');
|
||||
$nav_view->addFilter(null, 'Tasks '.$external_arrow, $tasks_uri);
|
||||
$nav_view->addFilter(null, 'Wiki '.$external_arrow, $phriction_uri);
|
||||
$nav_view->addFilter('people', 'People');
|
||||
$nav_view->addFilter('about', 'About');
|
||||
$nav_view->addFilter('dashboard', pht('Dashboard'));
|
||||
$nav_view->addFilter('feed', pht('Feed'));
|
||||
$nav_view->addFilter(null, pht('Tasks').' '.$external_arrow, $tasks_uri);
|
||||
$nav_view->addFilter(null, pht('Wiki').' '.$external_arrow, $phriction_uri);
|
||||
$nav_view->addFilter('people', pht('People'));
|
||||
$nav_view->addFilter('about', pht('About'));
|
||||
|
||||
$user = $this->getRequest()->getUser();
|
||||
$can_edit = PhabricatorPolicyCapability::CAN_EDIT;
|
||||
|
||||
if (PhabricatorPolicyFilter::hasCapability($user, $project, $can_edit)) {
|
||||
$nav_view->addFilter('edit', "Edit Project\xE2\x80\xA6", $edit_uri);
|
||||
$nav_view->addFilter('members', "Edit Members\xE2\x80\xA6", $members_uri);
|
||||
$nav_view->addFilter('edit', pht("Edit Project"), $edit_uri);
|
||||
$nav_view->addFilter('members', pht("Edit Members"), $members_uri);
|
||||
} else {
|
||||
$nav_view->addFilter(
|
||||
'edit',
|
||||
"Edit Project\xE2\x80\xA6",
|
||||
pht("Edit Project"),
|
||||
$edit_uri,
|
||||
$relative = false,
|
||||
'disabled');
|
||||
$nav_view->addFilter(
|
||||
'members',
|
||||
"Edit Members\xE2\x80\xA6",
|
||||
pht("Edit Members"),
|
||||
$members_uri,
|
||||
$relative = false,
|
||||
'disabled');
|
||||
|
@ -61,4 +61,40 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||
return $nav_view;
|
||||
}
|
||||
|
||||
public function buildApplicationMenu() {
|
||||
return $this->buildSideNavView(null, true)->getMenu();
|
||||
}
|
||||
|
||||
public function buildSideNavView($filter = null, $for_app = false) {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav
|
||||
->setBaseURI(new PhutilURI('/project/filter/'))
|
||||
->addLabel(pht('User'))
|
||||
->addFilter('active', pht('Active'))
|
||||
->addLabel(pht('All'))
|
||||
->addFilter('all', pht('All Projects'))
|
||||
->addFilter('allactive', pht('Active Projects'))
|
||||
->selectFilter($filter, 'active');
|
||||
|
||||
if ($for_app) {
|
||||
$nav->addFilter('create/', pht('Create Project'));
|
||||
}
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
public function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$crumbs->addAction(
|
||||
id(new PhabricatorMenuItemView())
|
||||
->setName(pht('Create Project'))
|
||||
->setHref($this->getApplicationURI('create/'))
|
||||
->setIcon('create'));
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ final class PhabricatorProjectCreateController
|
|||
$error_view = null;
|
||||
if ($errors) {
|
||||
$error_view = new AphrontErrorView();
|
||||
$error_view->setTitle('Form Errors');
|
||||
$error_view->setTitle(pht('Form Errors'));
|
||||
$error_view->setErrors($errors);
|
||||
}
|
||||
|
||||
|
@ -77,13 +77,13 @@ final class PhabricatorProjectCreateController
|
|||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Name')
|
||||
->setLabel(pht('Name'))
|
||||
->setName('name')
|
||||
->setValue($project->getName())
|
||||
->setError($e_name))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel('Blurb')
|
||||
->setLabel(pht('Blurb'))
|
||||
->setName('blurb')
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
|
||||
->setValue($profile->getBlurb()));
|
||||
|
@ -92,10 +92,10 @@ final class PhabricatorProjectCreateController
|
|||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($user)
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setTitle('Create a New Project')
|
||||
->setTitle(pht('Create a New Project'))
|
||||
->appendChild($error_view)
|
||||
->appendChild($form)
|
||||
->addSubmitButton('Create Project')
|
||||
->addSubmitButton(pht('Create Project'))
|
||||
->addCancelButton('/project/');
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
|
@ -104,22 +104,32 @@ final class PhabricatorProjectCreateController
|
|||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue('Create')
|
||||
->setValue(pht('Create'))
|
||||
->addCancelButton('/project/'));
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel
|
||||
->setWidth(AphrontPanelView::WIDTH_FORM)
|
||||
->setHeader('Create a New Project')
|
||||
->setHeader(pht('Create a New Project'))
|
||||
->setNoBackground()
|
||||
->appendChild($form);
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Create Project'))
|
||||
->setHref($this->getApplicationURI().'create/')
|
||||
);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$error_view,
|
||||
$panel,
|
||||
),
|
||||
array(
|
||||
'title' => 'Create new Project',
|
||||
'title' => pht('Create New Project'),
|
||||
'device' => true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,14 +12,7 @@ final class PhabricatorProjectListController
|
|||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav
|
||||
->setBaseURI(new PhutilURI('/project/filter/'))
|
||||
->addLabel('User')
|
||||
->addFilter('active', 'Active')
|
||||
->addLabel('All')
|
||||
->addFilter('all', 'All Projects')
|
||||
->addFilter('allactive','Active Projects');
|
||||
$nav = $this->buildSideNavView($this->filter);
|
||||
$this->filter = $nav->selectFilter($this->filter, 'active');
|
||||
|
||||
$pager = new AphrontPagerView();
|
||||
|
@ -33,21 +26,19 @@ final class PhabricatorProjectListController
|
|||
|
||||
$view_phid = $request->getUser()->getPHID();
|
||||
|
||||
$status_filter = PhabricatorProjectQuery::STATUS_ANY;
|
||||
|
||||
switch ($this->filter) {
|
||||
case 'active':
|
||||
$table_header = 'Your Projects';
|
||||
$table_header = pht('Your Projects');
|
||||
$query->withMemberPHIDs(array($view_phid));
|
||||
$query->withStatus(PhabricatorProjectQuery::STATUS_ACTIVE);
|
||||
break;
|
||||
case 'allactive':
|
||||
$status_filter = PhabricatorProjectQuery::STATUS_ACTIVE;
|
||||
$table_header = 'Active Projects';
|
||||
// fallthrough
|
||||
$table_header = pht('Active Projects');
|
||||
$query->withStatus(PhabricatorProjectQuery::STATUS_ACTIVE);
|
||||
break;
|
||||
case 'all':
|
||||
$table_header = 'All Projects';
|
||||
$query->withStatus($status_filter);
|
||||
$table_header = pht('All Projects');
|
||||
$query->withStatus(PhabricatorProjectQuery::STATUS_ANY);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -121,11 +112,11 @@ final class PhabricatorProjectListController
|
|||
$table = new AphrontTableView($rows);
|
||||
$table->setHeaders(
|
||||
array(
|
||||
'Project',
|
||||
'Status',
|
||||
'Description',
|
||||
'Population',
|
||||
'Open Tasks',
|
||||
pht('Project'),
|
||||
pht('Status'),
|
||||
pht('Description'),
|
||||
pht('Population'),
|
||||
pht('Open Tasks'),
|
||||
));
|
||||
$table->setColumnClasses(
|
||||
array(
|
||||
|
@ -138,16 +129,25 @@ final class PhabricatorProjectListController
|
|||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader($table_header);
|
||||
$panel->setCreateButton('Create New Project', '/project/create/');
|
||||
$panel->appendChild($table);
|
||||
$panel->setNoBackground();
|
||||
$panel->appendChild($pager);
|
||||
|
||||
$nav->appendChild($panel);
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName($table_header)
|
||||
->setHref($this->getApplicationURI())
|
||||
);
|
||||
$nav->setCrumbs($crumbs);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$nav,
|
||||
array(
|
||||
'title' => 'Projects',
|
||||
'title' => pht('Projects'),
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,8 +83,8 @@ final class PhabricatorProjectMembersEditController
|
|||
);
|
||||
}
|
||||
|
||||
$header_name = 'Edit Members';
|
||||
$title = 'Edit Members';
|
||||
$header_name = pht('Edit Members');
|
||||
$title = pht('Edit Members');
|
||||
|
||||
$list = $this->renderMemberList($handles);
|
||||
|
||||
|
@ -94,23 +94,24 @@ final class PhabricatorProjectMembersEditController
|
|||
->appendChild(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setName('phids')
|
||||
->setLabel('Add Members')
|
||||
->setLabel(pht('Add Members'))
|
||||
->setDatasource('/typeahead/common/users/'))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton('/project/view/'.$project->getID().'/')
|
||||
->setValue('Add Members'));
|
||||
->setValue(pht('Add Members')));
|
||||
$faux_form = id(new AphrontFormLayoutView())
|
||||
->setBackgroundShading(true)
|
||||
->setPadded(true)
|
||||
->appendChild(
|
||||
id(new AphrontFormInsetView())
|
||||
->setTitle('Current Members ('.count($handles).')')
|
||||
->setTitle(pht('Current Members (%d)', count($handles)))
|
||||
->appendChild($list));
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader($header_name);
|
||||
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||
$panel->setNoBackground();
|
||||
$panel->appendChild($form);
|
||||
$panel->appendChild(phutil_tag('br'));
|
||||
$panel->appendChild($faux_form);
|
||||
|
@ -119,10 +120,24 @@ final class PhabricatorProjectMembersEditController
|
|||
$nav->selectFilter('members');
|
||||
$nav->appendChild($panel);
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName($project->getName())
|
||||
->setHref('/project/view/'.$project->getID().'/')
|
||||
);
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Edit Members'))
|
||||
->setHref($this->getApplicationURI())
|
||||
);
|
||||
$nav->setCrumbs($crumbs);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$nav,
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ final class PhabricatorProjectProfileController
|
|||
|
||||
private $id;
|
||||
private $page;
|
||||
private $project;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = idx($data, 'id');
|
||||
|
@ -24,6 +25,7 @@ final class PhabricatorProjectProfileController
|
|||
}
|
||||
|
||||
$project = $query->executeOne();
|
||||
$this->project = $project;
|
||||
if (!$project) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
@ -96,7 +98,7 @@ final class PhabricatorProjectProfileController
|
|||
array(
|
||||
'class' => $class,
|
||||
),
|
||||
'Join Project'));
|
||||
pht('Join Project')));
|
||||
} else {
|
||||
$action = javelin_tag(
|
||||
'a',
|
||||
|
@ -105,7 +107,7 @@ final class PhabricatorProjectProfileController
|
|||
'sigil' => 'workflow',
|
||||
'class' => 'grey button',
|
||||
),
|
||||
'Leave Project...');
|
||||
pht('Leave Project...'));
|
||||
}
|
||||
|
||||
$header->addAction($action);
|
||||
|
@ -115,10 +117,10 @@ final class PhabricatorProjectProfileController
|
|||
$content = hsprintf('<div style="padding: 1em;">%s</div>', $content);
|
||||
$header->appendChild($content);
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
return $this->buildApplicationPage(
|
||||
$nav_view,
|
||||
array(
|
||||
'title' => $project->getName().' Project',
|
||||
'title' => pht('%s Project', $project->getName()),
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -143,11 +145,11 @@ final class PhabricatorProjectProfileController
|
|||
<div class="phabricator-profile-info-pane">
|
||||
<table class="phabricator-profile-info-table">
|
||||
<tr>
|
||||
<th>Creator</th>
|
||||
<th>%s</th>
|
||||
<td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created</th>
|
||||
<th>%s</th>
|
||||
<td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -155,15 +157,18 @@ final class PhabricatorProjectProfileController
|
|||
<td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Blurb</th>
|
||||
<th>%s</th>
|
||||
<td>%s</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>',
|
||||
pht('Creator'),
|
||||
$handles[$project->getAuthorPHID()]->renderLink(),
|
||||
pht('Created'),
|
||||
$timestamp,
|
||||
$project->getPHID(),
|
||||
pht('Blurb'),
|
||||
$blurb);
|
||||
|
||||
return $about;
|
||||
|
@ -190,9 +195,10 @@ final class PhabricatorProjectProfileController
|
|||
|
||||
return hsprintf(
|
||||
'<div class="phabricator-profile-info-group">'.
|
||||
'<h1 class="phabricator-profile-info-header">People</h1>'.
|
||||
'<h1 class="phabricator-profile-info-header">%s</h1>'.
|
||||
'<div class="phabricator-profile-info-pane">%s</div>'.
|
||||
'</div>',
|
||||
pht('People'),
|
||||
$affiliated);
|
||||
}
|
||||
|
||||
|
@ -207,7 +213,7 @@ final class PhabricatorProjectProfileController
|
|||
$stories = $query->execute();
|
||||
|
||||
if (!$stories) {
|
||||
return 'There are no stories about this project.';
|
||||
return pht('There are no stories about this project.');
|
||||
}
|
||||
|
||||
return $this->renderStories($stories);
|
||||
|
@ -222,9 +228,10 @@ final class PhabricatorProjectProfileController
|
|||
|
||||
return hsprintf(
|
||||
'<div class="phabricator-profile-info-group">'.
|
||||
'<h1 class="phabricator-profile-info-header">Activity Feed</h1>'.
|
||||
'<h1 class="phabricator-profile-info-header">%s</h1>'.
|
||||
'<div class="phabricator-profile-info-pane">%s</div>'.
|
||||
'</div>',
|
||||
pht('Activity Feed'),
|
||||
$view->render());
|
||||
}
|
||||
|
||||
|
@ -268,17 +275,17 @@ final class PhabricatorProjectProfileController
|
|||
array(
|
||||
'href' => '/maniphest/view/all/?projects='.$project->getPHID(),
|
||||
),
|
||||
"View All Open Tasks \xC2\xBB");
|
||||
pht("View All Open Tasks \xC2\xBB"));
|
||||
|
||||
$content = hsprintf(
|
||||
'<div class="phabricator-profile-info-group">
|
||||
<h1 class="phabricator-profile-info-header">Open Tasks (%s)</h1>'.
|
||||
<h1 class="phabricator-profile-info-header">%s</h1>'.
|
||||
'<div class="phabricator-profile-info-pane">'.
|
||||
'%s'.
|
||||
'<div class="phabricator-profile-info-pane-more-link">%s</div>'.
|
||||
'</div>
|
||||
</div>',
|
||||
$open,
|
||||
pht('Open Tasks (%s)', $open),
|
||||
$task_views,
|
||||
$more_link);
|
||||
|
||||
|
|
|
@ -86,8 +86,8 @@ final class PhabricatorProjectProfileEditController
|
|||
$profile->setBlurb($request->getStr('blurb'));
|
||||
|
||||
if (!strlen($project->getName())) {
|
||||
$e_name = 'Required';
|
||||
$errors[] = 'Project name is required.';
|
||||
$e_name = pht('Required');
|
||||
$errors[] = pht('Project name is required.');
|
||||
} else {
|
||||
$e_name = null;
|
||||
}
|
||||
|
@ -112,9 +112,9 @@ final class PhabricatorProjectProfileEditController
|
|||
$y = 50);
|
||||
$profile->setProfileImagePHID($xformed->getPHID());
|
||||
} else {
|
||||
$e_image = 'Not Supported';
|
||||
$e_image = pht('Not Supported');
|
||||
$errors[] =
|
||||
'This server only supports these image formats: '.
|
||||
pht('This server only supports these image formats:').' '.
|
||||
implode(', ', $supported_formats).'.';
|
||||
}
|
||||
}
|
||||
|
@ -132,12 +132,12 @@ final class PhabricatorProjectProfileEditController
|
|||
$error_view = null;
|
||||
if ($errors) {
|
||||
$error_view = new AphrontErrorView();
|
||||
$error_view->setTitle('Form Errors');
|
||||
$error_view->setTitle(pht('Form Errors'));
|
||||
$error_view->setErrors($errors);
|
||||
}
|
||||
|
||||
$header_name = 'Edit Project';
|
||||
$title = 'Edit Project';
|
||||
$header_name = pht('Edit Project');
|
||||
$title = pht('Edit Project');
|
||||
$action = '/project/edit/'.$project->getID().'/';
|
||||
|
||||
$policies = id(new PhabricatorPolicyQuery())
|
||||
|
@ -153,30 +153,30 @@ final class PhabricatorProjectProfileEditController
|
|||
->setEncType('multipart/form-data')
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Name')
|
||||
->setLabel(pht('Name'))
|
||||
->setName('name')
|
||||
->setValue($project->getName())
|
||||
->setError($e_name))
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setLabel('Project Status')
|
||||
->setLabel(pht('Project Status'))
|
||||
->setName('status')
|
||||
->setOptions($options)
|
||||
->setValue($project->getStatus()))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel('Blurb')
|
||||
->setLabel(pht('Blurb'))
|
||||
->setName('blurb')
|
||||
->setValue($profile->getBlurb()))
|
||||
->appendChild(hsprintf(
|
||||
'<p class="aphront-form-instructions">NOTE: Policy settings are not '.
|
||||
'yet fully implemented. Some interfaces still ignore these settings, '.
|
||||
'particularly "Visible To".</p>'))
|
||||
->appendChild('<p class="aphront-form-instructions">'.
|
||||
pht('NOTE: Policy settings are not yet fully implemented. '.
|
||||
'Some interfaces still ignore these settings, '.
|
||||
'particularly "Visible To".').'</p>')
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setUser($user)
|
||||
->setName('can_view')
|
||||
->setCaption('Members can always view a project.')
|
||||
->setCaption(pht('Members can always view a project.'))
|
||||
->setPolicyObject($project)
|
||||
->setPolicies($policies)
|
||||
->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
|
||||
|
@ -192,13 +192,13 @@ final class PhabricatorProjectProfileEditController
|
|||
->setUser($user)
|
||||
->setName('can_join')
|
||||
->setCaption(
|
||||
'Users who can edit a project can always join a project.')
|
||||
pht('Users who can edit a project can always join a project.'))
|
||||
->setPolicyObject($project)
|
||||
->setPolicies($policies)
|
||||
->setCapability(PhabricatorPolicyCapability::CAN_JOIN))
|
||||
->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel('Profile Image')
|
||||
->setLabel(pht('Profile Image'))
|
||||
->setValue(
|
||||
phutil_tag(
|
||||
'img',
|
||||
|
@ -207,18 +207,20 @@ final class PhabricatorProjectProfileEditController
|
|||
))))
|
||||
->appendChild(
|
||||
id(new AphrontFormImageControl())
|
||||
->setLabel('Change Image')
|
||||
->setLabel(pht('Change Image'))
|
||||
->setName('image')
|
||||
->setError($e_image)
|
||||
->setCaption('Supported formats: '.implode(', ', $supported_formats)))
|
||||
->setCaption(
|
||||
pht('Supported formats:').' '.implode(', ', $supported_formats)))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton('/project/view/'.$project->getID().'/')
|
||||
->setValue('Save'));
|
||||
->setValue(pht('Save')));
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader($header_name);
|
||||
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||
$panel->setNoBackground();
|
||||
$panel->appendChild($form);
|
||||
|
||||
$nav = $this->buildLocalNavigation($project);
|
||||
|
@ -229,10 +231,24 @@ final class PhabricatorProjectProfileEditController
|
|||
$panel,
|
||||
));
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName($project->getName())
|
||||
->setHref('/project/view/'.$project->getID().'/')
|
||||
);
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Edit Project'))
|
||||
->setHref($this->getApplicationURI())
|
||||
);
|
||||
$nav->setCrumbs($crumbs);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$nav,
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,12 +61,12 @@ final class PhabricatorProjectUpdateController
|
|||
case 'leave':
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setUser($user);
|
||||
$dialog->setTitle('Really leave project?');
|
||||
$dialog->setTitle(pht('Really leave project?'));
|
||||
$dialog->appendChild(phutil_tag('p', array(), pht(
|
||||
'Your tremendous contributions to this project will be sorely '.
|
||||
'missed. Are you sure you want to leave?')));
|
||||
$dialog->addCancelButton($project_uri);
|
||||
$dialog->addSubmitButton('Leave Project');
|
||||
$dialog->addSubmitButton(pht('Leave Project'));
|
||||
break;
|
||||
default:
|
||||
return new Aphront404Response();
|
||||
|
|
|
@ -164,7 +164,8 @@ final class PhabricatorProjectEditor extends PhabricatorEditor {
|
|||
|
||||
if ($slug == '/') {
|
||||
throw new PhabricatorProjectNameCollisionException(
|
||||
"Project names must be unique and contain some letters or numbers.");
|
||||
pht("Project names must be unique and contain some ".
|
||||
"letters or numbers."));
|
||||
}
|
||||
|
||||
$id = $project->getID();
|
||||
|
|
|
@ -44,8 +44,10 @@ final class PhabricatorSettingsPanelDiffPreferences
|
|||
1 => pht('Enable Filetree'),
|
||||
))
|
||||
->setCaption(
|
||||
pht("When looking at a revision or commit, show affected files ".
|
||||
"in a sidebar.")))
|
||||
pht("When looking at a revision or commit, enable a sidebar ".
|
||||
"showing affected files. You can press %s to show or hide ".
|
||||
"the sidebar.",
|
||||
phutil_tag('tt', array(), 'f'))))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Save Preferences')));
|
||||
|
|
|
@ -34,7 +34,7 @@ abstract class CelerityResourceController extends PhabricatorController {
|
|||
throw new Exception("Only static resources may be served.");
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
|
||||
if (AphrontRequest::getHTTPHeader('If-Modified-Since') &&
|
||||
!PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
|
||||
// Return a "304 Not Modified". We don't care about the value of this
|
||||
// field since we never change what resource is served by a given URI.
|
||||
|
|
|
@ -104,7 +104,7 @@ final class CelerityStaticResourceResponse {
|
|||
}
|
||||
|
||||
private function renderResource(array $resource) {
|
||||
$uri = PhabricatorEnv::getCDNURI($resource['uri']);
|
||||
$uri = $this->getURI($resource);
|
||||
switch ($resource['type']) {
|
||||
case 'css':
|
||||
return phutil_tag(
|
||||
|
@ -148,6 +148,7 @@ final class CelerityStaticResourceResponse {
|
|||
$higher_priority_names = array(
|
||||
'refresh-csrf',
|
||||
'aphront-basic-tokenizer',
|
||||
'dark-console',
|
||||
);
|
||||
|
||||
$higher_priority_behaviors = array_select_keys(
|
||||
|
@ -207,7 +208,7 @@ final class CelerityStaticResourceResponse {
|
|||
$this->resolveResources();
|
||||
$resources = array();
|
||||
foreach ($this->packaged as $resource) {
|
||||
$resources[] = PhabricatorEnv::getCDNURI($resource['uri']);
|
||||
$resources[] = $this->getURI($resource);
|
||||
}
|
||||
if ($resources) {
|
||||
$response['javelin_resources'] = $resources;
|
||||
|
@ -216,4 +217,32 @@ final class CelerityStaticResourceResponse {
|
|||
return $response;
|
||||
}
|
||||
|
||||
private function getURI($resource) {
|
||||
$uri = $resource['uri'];
|
||||
|
||||
// In developer mode, we dump file modification times into the URI. When a
|
||||
// page is reloaded in the browser, any resources brought in by Ajax calls
|
||||
// do not trigger revalidation, so without this it's very difficult to get
|
||||
// changes to Ajaxed-in CSS to work (you must clear your cache or rerun
|
||||
// the map script). In production, we can assume the map script gets run
|
||||
// after changes, and safely skip this.
|
||||
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
|
||||
$root = dirname(phutil_get_library_root('phabricator')).'/webroot/';
|
||||
if (isset($resource['disk'])) {
|
||||
$mtime = (int)filemtime($root.$resource['disk']);
|
||||
} else {
|
||||
$mtime = 0;
|
||||
foreach ($resource['symbols'] as $symbol) {
|
||||
$map = CelerityResourceMap::getInstance();
|
||||
$symbol_info = $map->lookupSymbolInformation($symbol);
|
||||
$mtime = max($mtime, (int)filemtime($root.$symbol_info['disk']));
|
||||
}
|
||||
}
|
||||
|
||||
$uri = preg_replace('@^/res/@', '/res/'.$mtime.'T/', $uri);
|
||||
}
|
||||
|
||||
return PhabricatorEnv::getCDNURI($uri);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ final class PhabricatorBot extends PhabricatorDaemon {
|
|||
|
||||
private function routeMessage(PhabricatorBotMessage $message) {
|
||||
$ignore = $this->getConfig('ignore');
|
||||
if ($ignore && in_array($message->getSenderNickName(), $ignore)) {
|
||||
if ($ignore && in_array($message->getSender(), $ignore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -118,6 +118,7 @@ extends PhabricatorBaseProtocolAdapter {
|
|||
|
||||
return id(new PhabricatorBotMessage())
|
||||
->setCommand('MESSAGE')
|
||||
->setSender($m_obj['user_id'])
|
||||
->setTarget($m_obj['room_id'])
|
||||
->setBody($m_obj['body']);
|
||||
}
|
||||
|
@ -128,11 +129,23 @@ extends PhabricatorBaseProtocolAdapter {
|
|||
|
||||
public function writeMessage(PhabricatorBotMessage $message) {
|
||||
switch ($message->getCommand()) {
|
||||
case 'MESSAGE':
|
||||
$this->speak(
|
||||
$message->getBody(),
|
||||
$message->getTarget());
|
||||
break;
|
||||
case 'MESSAGE':
|
||||
$this->speak(
|
||||
$message->getBody(),
|
||||
$message->getTarget());
|
||||
break;
|
||||
case 'SOUND':
|
||||
$this->speak(
|
||||
$message->getBody(),
|
||||
$message->getTarget(),
|
||||
'SoundMessage');
|
||||
break;
|
||||
case 'PASTE':
|
||||
$this->speak(
|
||||
$message->getBody(),
|
||||
$message->getTarget(),
|
||||
'PasteMessage');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,12 +159,12 @@ extends PhabricatorBaseProtocolAdapter {
|
|||
unset($this->inRooms[$room_id]);
|
||||
}
|
||||
|
||||
private function speak($message, $room_id) {
|
||||
private function speak($message, $room_id, $type='TextMessage') {
|
||||
$this->performPost(
|
||||
"/room/{$room_id}/speak.json",
|
||||
array(
|
||||
'message' => array(
|
||||
'type' => 'TextMessage',
|
||||
'type' => $type,
|
||||
'body' => $message)));
|
||||
}
|
||||
|
||||
|
|
|
@ -161,12 +161,19 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
|
|||
|
||||
if ($console) {
|
||||
require_celerity_resource('aphront-dark-console-css');
|
||||
|
||||
$headers = array();
|
||||
if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
|
||||
$headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
|
||||
}
|
||||
|
||||
Javelin::initBehavior(
|
||||
'dark-console',
|
||||
array(
|
||||
'uri' => $request ? (string)$request->getRequestURI() : '?',
|
||||
'selected' => $user ? $user->getConsoleTab() : null,
|
||||
'visible' => $user ? (int)$user->getConsoleVisible() : true,
|
||||
'headers' => $headers,
|
||||
));
|
||||
|
||||
// Change this to initBehavior when there is some behavior to initialize
|
||||
|
@ -359,7 +366,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
|
|||
$classes[] = 'phabricator-chromeless-page';
|
||||
}
|
||||
|
||||
$agent = idx($_SERVER, 'HTTP_USER_AGENT');
|
||||
$agent = AphrontRequest::getHTTPHeader('User-Agent');
|
||||
|
||||
// Try to guess the device resolution based on UA strings to avoid a flash
|
||||
// of incorrectly-styled content.
|
||||
|
|
|
@ -17,6 +17,7 @@ final class PhabricatorStartup {
|
|||
|
||||
private static $startTime;
|
||||
private static $globals = array();
|
||||
private static $capturingOutput;
|
||||
|
||||
|
||||
/* -( Accessing Request Information )-------------------------------------- */
|
||||
|
@ -78,6 +79,8 @@ final class PhabricatorStartup {
|
|||
self::verifyRewriteRules();
|
||||
|
||||
self::detectPostMaxSizeTriggered();
|
||||
|
||||
self::beginOutputCapture();
|
||||
}
|
||||
|
||||
|
||||
|
@ -149,6 +152,26 @@ final class PhabricatorStartup {
|
|||
phutil_load_library($phabricator_root.'/src');
|
||||
}
|
||||
|
||||
/* -( Output Capture )----------------------------------------------------- */
|
||||
|
||||
|
||||
public static function beginOutputCapture() {
|
||||
if (self::$capturingOutput) {
|
||||
self::didFatal("Already capturing output!");
|
||||
}
|
||||
self::$capturingOutput = true;
|
||||
ob_start();
|
||||
}
|
||||
|
||||
|
||||
public static function endOutputCapture() {
|
||||
if (!self::$capturingOutput) {
|
||||
return null;
|
||||
}
|
||||
self::$capturingOutput = false;
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
/* -( In Case of Apocalypse )---------------------------------------------- */
|
||||
|
||||
|
@ -157,6 +180,7 @@ final class PhabricatorStartup {
|
|||
* @task apocalypse
|
||||
*/
|
||||
public static function didFatal($message) {
|
||||
self::endOutputCapture();
|
||||
$access_log = self::getGlobal('log.access');
|
||||
|
||||
if ($access_log) {
|
||||
|
|
|
@ -15,7 +15,7 @@ try {
|
|||
PhabricatorStartup::setGlobal('log.access', $access_log);
|
||||
$access_log->setData(
|
||||
array(
|
||||
'R' => idx($_SERVER, 'HTTP_REFERER', '-'),
|
||||
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
|
||||
'r' => idx($_SERVER, 'REMOTE_ADDR', '-'),
|
||||
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
|
||||
));
|
||||
|
@ -30,11 +30,12 @@ try {
|
|||
|
||||
$response = PhabricatorSetupCheck::willProcessRequest();
|
||||
if ($response) {
|
||||
PhabricatorStartup::endOutputCapture();
|
||||
$sink->writeResponse($response);
|
||||
return;
|
||||
}
|
||||
|
||||
$host = $_SERVER['HTTP_HOST'];
|
||||
$host = AphrontRequest::getHTTPHeader('Host');
|
||||
$path = $_REQUEST['__path__'];
|
||||
|
||||
switch ($host) {
|
||||
|
@ -102,8 +103,21 @@ try {
|
|||
$response = $application->willSendResponse($response, $controller);
|
||||
$response->setRequest($request);
|
||||
|
||||
$sink->writeResponse($response);
|
||||
$unexpected_output = PhabricatorStartup::endOutputCapture();
|
||||
if ($unexpected_output) {
|
||||
$unexpected_output = "Unexpected output:\n\n{$unexpected_output}";
|
||||
phlog($unexpected_output);
|
||||
|
||||
if ($response instanceof AphrontWebpageResponse) {
|
||||
echo hsprintf(
|
||||
'<div style="background: #eeddff; white-space: pre-wrap;
|
||||
z-index: 200000; position: relative; padding: 8px;
|
||||
font-family: monospace;">%s</div>',
|
||||
$unexpected_output);
|
||||
}
|
||||
}
|
||||
|
||||
$sink->writeResponse($response);
|
||||
} catch (Exception $ex) {
|
||||
$write_guard->dispose();
|
||||
if ($access_log) {
|
||||
|
@ -132,25 +146,7 @@ try {
|
|||
$access_log->write();
|
||||
}
|
||||
|
||||
if (DarkConsoleXHProfPluginAPI::isProfilerRequested()) {
|
||||
$profile = DarkConsoleXHProfPluginAPI::stopProfiler();
|
||||
$profile_sample = id(new PhabricatorXHProfSample())
|
||||
->setFilePHID($profile);
|
||||
if (empty($_REQUEST['__profile__'])) {
|
||||
$sample_rate = PhabricatorEnv::getEnvConfig('debug.profile-rate');
|
||||
} else {
|
||||
$sample_rate = 0;
|
||||
}
|
||||
$profile_sample->setSampleRate($sample_rate);
|
||||
if ($access_log) {
|
||||
$profile_sample->setUsTotal($access_log->getData('T'))
|
||||
->setHostname($access_log->getData('h'))
|
||||
->setRequestPath($access_log->getData('U'))
|
||||
->setController($access_log->getData('C'))
|
||||
->setUserPHID($request->getUser()->getPHID());
|
||||
}
|
||||
$profile_sample->save();
|
||||
}
|
||||
DarkConsoleXHProfPluginAPI::saveProfilerSample($request, $access_log);
|
||||
|
||||
} catch (Exception $ex) {
|
||||
PhabricatorStartup::didFatal("[Exception] ".$ex->getMessage());
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
.pholio-mock-image-container {
|
||||
background-color: #282828;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pholio-mock-carousel {
|
||||
|
@ -43,3 +44,35 @@
|
|||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
|
||||
.pholio-mock-inline-comments {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
text-align: left;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.pholio-inline-comment {
|
||||
border: 1px solid #aa8;
|
||||
background: #f9f9f1;
|
||||
margin-bottom: 2px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.pholio-inline-comment-header {
|
||||
border-bottom: 1px solid #cca;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
padding-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pholio-mock-inline-comment-highlight {
|
||||
background-color: #F0B160;
|
||||
}
|
||||
|
||||
.pholio-inline-head-links a {
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ JX.behavior('dark-console', function(config, statics) {
|
|||
config.key = config.key || root.getAttribute('data-console-key');
|
||||
add_request(config);
|
||||
|
||||
|
||||
// Do first-time setup.
|
||||
function setup_console() {
|
||||
statics.root = JX.$('darkconsole');
|
||||
|
@ -42,6 +41,16 @@ JX.behavior('dark-console', function(config, statics) {
|
|||
|
||||
install_shortcut();
|
||||
|
||||
if (config.headers) {
|
||||
// If the main page had profiling enabled, also enable it for any Ajax
|
||||
// requests.
|
||||
JX.Request.listen('open', function(r) {
|
||||
for (var k in config.headers) {
|
||||
r.getTransport().setRequestHeader(k, config.headers[k]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return statics.root;
|
||||
}
|
||||
|
||||
|
|
|
@ -139,14 +139,56 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||
endY: Math.max(startPos.y,endPos.y),
|
||||
comment: comment};
|
||||
|
||||
|
||||
inlineComment.addData(commentToAdd);
|
||||
inlineComment.send();
|
||||
|
||||
load_inline_comments();
|
||||
});
|
||||
|
||||
function forge_inline_comment(data) {
|
||||
var comment_head = data.username;
|
||||
if (data.transactionphid == null) comment_head += " (draft)";
|
||||
|
||||
var links = null;
|
||||
if (data.canEdit && data.transactionphid == null) {
|
||||
links = JX.$N(
|
||||
'span',
|
||||
{
|
||||
className: 'pholio-inline-head-links'
|
||||
},
|
||||
[
|
||||
JX.$N('a',{href: 'http://www.google.fi'},'Edit'),
|
||||
JX.$N('a',{href: 'http://www.google.fi'},'Delete')
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
var comment_header = JX.$N(
|
||||
'div',
|
||||
{
|
||||
className: 'pholio-inline-comment-header'
|
||||
},
|
||||
[comment_head, links]);
|
||||
|
||||
var comment_body = JX.$N(
|
||||
'div',
|
||||
{},
|
||||
data.content);
|
||||
|
||||
var inline_comment = JX.$N(
|
||||
'div',
|
||||
{
|
||||
id: data.phid + "_comment",
|
||||
className: 'pholio-inline-comment'
|
||||
},
|
||||
[comment_header, comment_body]);
|
||||
|
||||
return inline_comment;
|
||||
}
|
||||
|
||||
function load_inline_comments() {
|
||||
var data = JX.Stratcom.getData(JX.$(config.mainID));
|
||||
var comment_holder = JX.$('mock-inline-comments');
|
||||
JX.DOM.setContent(comment_holder, '');
|
||||
|
||||
var inline_comments_url = "/pholio/inline/" + data['imageID'] + "/";
|
||||
var inline_comments = new JX.Request(inline_comments_url, function(r) {
|
||||
|
@ -156,25 +198,68 @@ JX.behavior('pholio-mock-view', function(config) {
|
|||
var inlineSelection = JX.$N(
|
||||
'div',
|
||||
{
|
||||
id: r[i].phid,
|
||||
id: r[i].phid + "_selection",
|
||||
className: 'pholio-mock-select-border',
|
||||
title: r[i].content
|
||||
});
|
||||
|
||||
JX.Stratcom.addData(
|
||||
inlineSelection,
|
||||
{phid: r[i].phid});
|
||||
|
||||
JX.Stratcom.addSigil(inlineSelection, "image_selection");
|
||||
JX.DOM.appendContent(comment_holder, forge_inline_comment(r[i]));
|
||||
|
||||
JX.DOM.appendContent(wrapper, inlineSelection);
|
||||
|
||||
JX.$V(r[i].x, r[i].y).setPos(inlineSelection);
|
||||
JX.$V(r[i].width, r[i].height)
|
||||
.setDim(inlineSelection);
|
||||
JX.$V(r[i].width, r[i].height).setDim(inlineSelection);
|
||||
|
||||
if (r[i].transactionphid == null) {
|
||||
|
||||
var inlineDraft = JX.$N(
|
||||
'div',{className: 'pholio-mock-select-fill'});
|
||||
|
||||
JX.$V(r[i].x, r[i].y).setPos(inlineDraft);
|
||||
JX.$V(r[i].width, r[i].height).setDim(inlineDraft);
|
||||
|
||||
JX.Stratcom.addData(
|
||||
inlineDraft,
|
||||
{phid: r[i].phid});
|
||||
|
||||
JX.Stratcom.addSigil(inlineDraft, "image_selection");
|
||||
JX.DOM.appendContent(wrapper, inlineDraft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'mouseover',
|
||||
'image_selection',
|
||||
function(e) {
|
||||
var data = e.getNodeData('image_selection');
|
||||
|
||||
var inline_comment = JX.$(data.phid + "_comment");
|
||||
JX.DOM.alterClass(inline_comment,
|
||||
'pholio-mock-inline-comment-highlight', true);
|
||||
});
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'mouseout',
|
||||
'image_selection',
|
||||
function(e) {
|
||||
var data = e.getNodeData('image_selection');
|
||||
|
||||
var inline_comment = JX.$(data.phid + "_comment");
|
||||
JX.DOM.alterClass(inline_comment,
|
||||
'pholio-mock-inline-comment-highlight', false);
|
||||
});
|
||||
});
|
||||
|
||||
inline_comments.send();
|
||||
}
|
||||
|
||||
load_inline_comments();
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue