1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-19 11:11:10 +01:00

Merge branch 'master' into phutil_tag

(Final final sync.)
This commit is contained in:
epriestley 2013-02-08 17:29:15 -08:00
commit 7ec8e885e1
35 changed files with 498 additions and 201 deletions

View file

@ -882,6 +882,10 @@ return array(
'image/vnd.microsoft.icon' => true,
),
// Configuration option for enabling imagemagick
// to resize animated profile pictures (gif)
'files.enable-imagemagick' => false,
// -- Storage --------------------------------------------------------------- //
// Phabricator allows users to upload files, and can keep them in various

View file

@ -1866,7 +1866,7 @@ celerity_register_resource_map(array(
),
'javelin-behavior-pholio-mock-view' =>
array(
'uri' => '/res/518a169e/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
'uri' => '/res/e5f432ac/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
'type' => 'js',
'requires' =>
array(
@ -2591,7 +2591,7 @@ celerity_register_resource_map(array(
),
'phabricator-core-css' =>
array(
'uri' => '/res/03c97d00/rsrc/css/core/core.css',
'uri' => '/res/2a055ecb/rsrc/css/core/core.css',
'type' => 'css',
'requires' =>
array(
@ -3400,7 +3400,7 @@ celerity_register_resource_map(array(
), array(
'packages' =>
array(
'4b569463' =>
'acc46105' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@ -3443,7 +3443,7 @@ celerity_register_resource_map(array(
35 => 'phabricator-object-item-list-view-css',
36 => 'global-drag-and-drop-css',
),
'uri' => '/res/pkg/4b569463/core.pkg.css',
'uri' => '/res/pkg/acc46105/core.pkg.css',
'type' => 'css',
),
'bc0774e5' =>
@ -3631,17 +3631,17 @@ celerity_register_resource_map(array(
'reverse' =>
array(
'aphront-attached-file-view-css' => 'e30a3fa8',
'aphront-crumbs-view-css' => '4b569463',
'aphront-dialog-view-css' => '4b569463',
'aphront-error-view-css' => '4b569463',
'aphront-form-view-css' => '4b569463',
'aphront-list-filter-view-css' => '4b569463',
'aphront-pager-view-css' => '4b569463',
'aphront-panel-view-css' => '4b569463',
'aphront-table-view-css' => '4b569463',
'aphront-tokenizer-control-css' => '4b569463',
'aphront-tooltip-css' => '4b569463',
'aphront-typeahead-control-css' => '4b569463',
'aphront-crumbs-view-css' => 'acc46105',
'aphront-dialog-view-css' => 'acc46105',
'aphront-error-view-css' => 'acc46105',
'aphront-form-view-css' => 'acc46105',
'aphront-list-filter-view-css' => 'acc46105',
'aphront-pager-view-css' => 'acc46105',
'aphront-panel-view-css' => 'acc46105',
'aphront-table-view-css' => 'acc46105',
'aphront-tokenizer-control-css' => 'acc46105',
'aphront-tooltip-css' => 'acc46105',
'aphront-typeahead-control-css' => 'acc46105',
'differential-changeset-view-css' => '8aaacd1b',
'differential-core-view-css' => '8aaacd1b',
'differential-inline-comment-editor' => '95d0d865',
@ -3655,7 +3655,7 @@ celerity_register_resource_map(array(
'differential-table-of-contents-css' => '8aaacd1b',
'diffusion-commit-view-css' => 'c8ce2d88',
'diffusion-icons-css' => 'c8ce2d88',
'global-drag-and-drop-css' => '4b569463',
'global-drag-and-drop-css' => 'acc46105',
'inline-comment-summary-css' => '8aaacd1b',
'javelin-aphlict' => 'bc0774e5',
'javelin-behavior' => 'd466c034',
@ -3724,48 +3724,48 @@ celerity_register_resource_map(array(
'javelin-util' => 'd466c034',
'javelin-vector' => 'd466c034',
'javelin-workflow' => 'd466c034',
'lightbox-attachment-css' => '4b569463',
'lightbox-attachment-css' => 'acc46105',
'maniphest-task-summary-css' => 'e30a3fa8',
'maniphest-transaction-detail-css' => 'e30a3fa8',
'phabricator-busy' => 'bc0774e5',
'phabricator-content-source-view-css' => '8aaacd1b',
'phabricator-core-buttons-css' => '4b569463',
'phabricator-core-css' => '4b569463',
'phabricator-crumbs-view-css' => '4b569463',
'phabricator-directory-css' => '4b569463',
'phabricator-core-buttons-css' => 'acc46105',
'phabricator-core-css' => 'acc46105',
'phabricator-crumbs-view-css' => 'acc46105',
'phabricator-directory-css' => 'acc46105',
'phabricator-drag-and-drop-file-upload' => '95d0d865',
'phabricator-dropdown-menu' => 'bc0774e5',
'phabricator-file-upload' => 'bc0774e5',
'phabricator-filetree-view-css' => '4b569463',
'phabricator-flag-css' => '4b569463',
'phabricator-form-view-css' => '4b569463',
'phabricator-header-view-css' => '4b569463',
'phabricator-jump-nav' => '4b569463',
'phabricator-filetree-view-css' => 'acc46105',
'phabricator-flag-css' => 'acc46105',
'phabricator-form-view-css' => 'acc46105',
'phabricator-header-view-css' => 'acc46105',
'phabricator-jump-nav' => 'acc46105',
'phabricator-keyboard-shortcut' => 'bc0774e5',
'phabricator-keyboard-shortcut-manager' => 'bc0774e5',
'phabricator-main-menu-view' => '4b569463',
'phabricator-main-menu-view' => 'acc46105',
'phabricator-menu-item' => 'bc0774e5',
'phabricator-nav-view-css' => '4b569463',
'phabricator-nav-view-css' => 'acc46105',
'phabricator-notification' => 'bc0774e5',
'phabricator-notification-css' => '4b569463',
'phabricator-notification-menu-css' => '4b569463',
'phabricator-object-item-list-view-css' => '4b569463',
'phabricator-notification-css' => 'acc46105',
'phabricator-notification-menu-css' => 'acc46105',
'phabricator-object-item-list-view-css' => 'acc46105',
'phabricator-object-selector-css' => '8aaacd1b',
'phabricator-paste-file-upload' => 'bc0774e5',
'phabricator-prefab' => 'bc0774e5',
'phabricator-project-tag-css' => 'e30a3fa8',
'phabricator-remarkup-css' => '4b569463',
'phabricator-remarkup-css' => 'acc46105',
'phabricator-shaped-request' => '95d0d865',
'phabricator-side-menu-view-css' => '4b569463',
'phabricator-standard-page-view' => '4b569463',
'phabricator-side-menu-view-css' => 'acc46105',
'phabricator-standard-page-view' => 'acc46105',
'phabricator-textareautils' => 'bc0774e5',
'phabricator-tooltip' => 'bc0774e5',
'phabricator-transaction-view-css' => '4b569463',
'phabricator-zindex-css' => '4b569463',
'sprite-apps-large-css' => '4b569463',
'sprite-gradient-css' => '4b569463',
'sprite-icon-css' => '4b569463',
'sprite-menu-css' => '4b569463',
'syntax-highlighting-css' => '4b569463',
'phabricator-transaction-view-css' => 'acc46105',
'phabricator-zindex-css' => 'acc46105',
'sprite-apps-large-css' => 'acc46105',
'sprite-gradient-css' => 'acc46105',
'sprite-icon-css' => 'acc46105',
'sprite-menu-css' => 'acc46105',
'syntax-highlighting-css' => 'acc46105',
),
));

View file

@ -1259,6 +1259,7 @@ phutil_register_library_map(array(
'PhabricatorSetupCheckExtraConfig' => 'applications/config/check/PhabricatorSetupCheckExtraConfig.php',
'PhabricatorSetupCheckFacebook' => 'applications/config/check/PhabricatorSetupCheckFacebook.php',
'PhabricatorSetupCheckGD' => 'applications/config/check/PhabricatorSetupCheckGD.php',
'PhabricatorSetupCheckImagemagick' => 'applications/config/check/PhabricatorSetupCheckImagemagick.php',
'PhabricatorSetupCheckInvalidConfig' => 'applications/config/check/PhabricatorSetupCheckInvalidConfig.php',
'PhabricatorSetupCheckMail' => 'applications/config/check/PhabricatorSetupCheckMail.php',
'PhabricatorSetupCheckMySQL' => 'applications/config/check/PhabricatorSetupCheckMySQL.php',
@ -1409,6 +1410,7 @@ phutil_register_library_map(array(
'PholioController' => 'applications/pholio/controller/PholioController.php',
'PholioDAO' => 'applications/pholio/storage/PholioDAO.php',
'PholioImage' => 'applications/pholio/storage/PholioImage.php',
'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php',
'PholioInlineSaveController' => 'applications/pholio/controller/PholioInlineSaveController.php',
'PholioMock' => 'applications/pholio/storage/PholioMock.php',
'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php',
@ -2673,6 +2675,7 @@ phutil_register_library_map(array(
'PhabricatorSetupCheckExtraConfig' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckFacebook' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckGD' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckImagemagick' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckInvalidConfig' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckMail' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckMySQL' => 'PhabricatorSetupCheck',
@ -2827,6 +2830,7 @@ phutil_register_library_map(array(
0 => 'PholioDAO',
1 => 'PhabricatorMarkupInterface',
),
'PholioInlineController' => 'PholioController',
'PholioInlineSaveController' => 'PholioController',
'PholioMock' =>
array(

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorSetupCheckImagemagick extends PhabricatorSetupCheck {
protected function executeChecks() {
$imagemagick = PhabricatorEnv::getEnvConfig('files.enable-imagemagick');
if ($imagemagick) {
list($err) = exec_manual('which convert');
if ($err) {
$message = pht(
'You have enabled Imagemagick in your config, but the \'convert\''.
' binary is not in the webserver\'s $PATH. Disable imagemagick'.
' or make it available to the webserver.');
$this->newIssue('files.enable-imagemagick')
->setName(pht(
"'convert' binary not found or Imagemagick is not installed."))
->setMessage($message)
->addPhabricatorConfig('files.enable-imagemagick')
->addPhabricatorConfig('environment.append-paths');
}
}
}
}

View file

@ -64,6 +64,7 @@ final class ConpherenceUpdateController extends
$top = $request->getInt('image_y');
$left = $request->getInt('image_x');
$file_id = $request->getInt('file_id');
$title = $request->getStr('title');
if ($file_id) {
$orig_file = id(new PhabricatorFileQuery())
->setViewer($user)
@ -101,6 +102,8 @@ final class ConpherenceUpdateController extends
pht('This server only supports these image formats: %s.',
implode(', ', $supported_formats));
}
// use the existing title in this image upload case
$title = $conpherence->getTitle();
} else if ($top !== null || $left !== null) {
$file = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG);
$xformer = new PhabricatorImageTransformer();
@ -119,7 +122,6 @@ final class ConpherenceUpdateController extends
)
->setNewValue($image_phid);
}
$title = $request->getStr('title');
if ($title != $conpherence->getTitle()) {
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(ConpherenceTransactionType::TYPE_TITLE)

View file

@ -5,6 +5,16 @@
*/
final class ConpherenceReplyHandler extends PhabricatorMailReplyHandler {
private $mailAddedParticipantPHIDs;
public function setMailAddedParticipantPHIDs(array $phids) {
$this->mailAddedParticipantPHIDs = $phids;
return $this;
}
public function getMailAddedParticipantPHIDs() {
return $this->mailAddedParticipantPHIDs;
}
public function validateMailReceiver($mail_receiver) {
if (!($mail_receiver instanceof ConpherenceThread)) {
throw new Exception("Mail receiver is not a ConpherenceThread!");
@ -67,14 +77,25 @@ final class ConpherenceReplyHandler extends PhabricatorMailReplyHandler {
$file_phids,
'{F%d}'
);
$xactions = $editor->generateTransactionsFromText(
$xactions = array();
if ($this->getMailAddedParticipantPHIDs()) {
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(ConpherenceTransactionType::TYPE_PARTICIPANTS)
->setNewValue(array('+' => $this->getMailAddedParticipantPHIDs()));
}
$xactions = array_merge(
$xactions,
$editor->generateTransactionsFromText(
$conpherence,
$body
)
);
$editor->applyTransactions($conpherence, $xactions);
return null;
return $conpherence;
}
}

View file

@ -46,12 +46,17 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction {
switch ($this->getTransactionType()) {
case ConpherenceTransactionType::TYPE_TITLE:
if ($old) {
if ($old && $new) {
$title = pht(
'%s renamed this conpherence from "%s" to "%s".',
$this->renderHandleLink($author_phid),
phutil_escape_html($old),
phutil_escape_html($new));
} else if ($old) {
$title = pht(
'%s deleted the conpherence name "%s".',
$this->renderHandleLink($author_phid),
phutil_escape_html($old));
} else {
$title = pht(
'%s named this conpherence "%s".',

View file

@ -172,7 +172,7 @@ class DifferentialReplyHandler extends PhabricatorMailReplyHandler {
DifferentialRevisionEditor::removeCCAndUpdateRevision(
$revision,
$user->getPHID(),
$user->getPHID());
$user);
}

View file

@ -59,13 +59,13 @@ final class DifferentialSubscribeController extends DifferentialController {
DifferentialRevisionEditor::addCCAndUpdateRevision(
$revision,
$phid,
$phid);
$user);
break;
case 'rem':
DifferentialRevisionEditor::removeCCAndUpdateRevision(
$revision,
$phid,
$phid);
$user);
break;
default:
return new Aphront400Response();

View file

@ -496,12 +496,13 @@ final class DifferentialRevisionEditor extends PhabricatorEditor {
public static function addCCAndUpdateRevision(
$revision,
$phid,
$reason) {
PhabricatorUser $actor) {
self::addCC($revision, $phid, $reason);
self::addCC($revision, $phid, $actor->getPHID());
$type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_UNSUBSCRIBER;
id(new PhabricatorEdgeEditor())
->setActor($actor)
->removeEdge($revision->getPHID(), $type, $phid)
->save();
}
@ -509,12 +510,13 @@ final class DifferentialRevisionEditor extends PhabricatorEditor {
public static function removeCCAndUpdateRevision(
$revision,
$phid,
$reason) {
PhabricatorUser $actor) {
self::removeCC($revision, $phid, $reason);
self::removeCC($revision, $phid, $actor->getPHID());
$type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_UNSUBSCRIBER;
id(new PhabricatorEdgeEditor())
->setActor($actor)
->addEdge($revision->getPHID(), $type, $phid)
->save();
}

View file

@ -253,10 +253,15 @@ final class DifferentialChangesetListView extends AphrontView {
$repository = $this->repository;
if ($repository) {
$meta['diffusionURI'] = (string)$repository->getDiffusionBrowseURIForPath(
try {
$meta['diffusionURI'] =
(string)$repository->getDiffusionBrowseURIForPath(
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
idx($changeset->getMetadata(), 'line:first'),
$this->getBranch());
} catch (DiffusionSetupException $e) {
// Ignore
}
}
$change = $changeset->getChangeType();

View file

@ -2,6 +2,9 @@
final class DivinerGenerateWorkflow extends DivinerWorkflow {
private $config;
private $atomCache;
public function didConstruct() {
$this
->setName('generate')
@ -12,10 +15,37 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
'name' => 'clean',
'help' => 'Clear the caches before generating documentation.',
),
array(
'name' => 'book',
'param' => 'path',
'help' => 'Path to a Diviner book configuration.',
),
));
}
protected function getConfig($key, $default = null) {
return idx($this->config, $key, $default);
}
protected function getAtomCache() {
if (!$this->atomCache) {
$book_root = $this->getConfig('root');
$book_name = $this->getConfig('name');
$cache_directory = $book_root.'/.divinercache/'.$book_name;
$this->atomCache = new DivinerAtomCache($cache_directory);
}
return $this->atomCache;
}
protected function log($message) {
$console = PhutilConsole::getConsole();
$console->getServer()->setEnableLog(true);
$console->writeLog($message."\n");
}
public function execute(PhutilArgumentParser $args) {
$this->readBookConfiguration($args);
if ($args->getArg('clean')) {
$this->log(pht('CLEARING CACHES'));
$this->getAtomCache()->delete();
@ -170,7 +200,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
private function findFilesInProject() {
$file_hashes = id(new FileFinder($this->getRoot()))
$file_hashes = id(new FileFinder($this->getConfig('root')))
->excludePath('*/.*')
->withType('f')
->setGenerateChecksums(true)
@ -222,11 +252,11 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
foreach ($atomizers as $class => $files) {
foreach (array_chunk($files, 32) as $chunk) {
$future = new ExecFuture(
'%s atomize --atomizer %s -- %Ls',
'%s atomize --ugly --atomizer %s -- %Ls',
dirname(phutil_get_library_root('phabricator')).'/bin/diviner',
$class,
$chunk);
$future->setCWD($this->getRoot());
$future->setCWD($this->getConfig('root'));
$futures[] = $future;
}
@ -381,4 +411,42 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
return md5(serialize($inputs)).'G';
}
private function readBookConfiguration(PhutilArgumentParser $args) {
$book_path = $args->getArg('book');
if ($book_path === null) {
throw new PhutilArgumentUsageException(
"Specify a Diviner book configuration file with --book.");
}
$book_data = Filesystem::readFile($book_path);
$book = json_decode($book_data, true);
if (!is_array($book)) {
throw new PhutilArgumentUsageException(
"Book configuration '{$book_path}' is not in JSON format.");
}
// If the book specifies a "root", resolve it; otherwise, use the directory
// the book configuration file lives in.
$full_path = dirname(Filesystem::resolvePath($book_path));
if (empty($book['root'])) {
$book['root'] = '.';
}
$book['root'] = Filesystem::resolvePath($book['root'], $full_path);
// Make sure we have a valid book name.
if (!isset($book['name'])) {
throw new PhutilArgumentUsageException(
"Book configuration '{$book_path}' is missing required ".
"property 'name'.");
}
if (!preg_match('/^[a-z][a-z-]*$/', $book['name'])) {
$name = $book['name'];
throw new PhutilArgumentUsageException(
"Book configuration '{$book_path}' has name '{$name}', but book names ".
"must include only lowercase letters and hyphens.");
}
$this->config = $book;
}
}

View file

@ -2,32 +2,8 @@
abstract class DivinerWorkflow extends PhutilArgumentWorkflow {
private $atomCache;
public function isExecutable() {
return true;
}
protected function getRoot() {
return getcwd();
}
protected function getConfig($key, $default = null) {
return $default;
}
protected function getAtomCache() {
if (!$this->atomCache) {
$cache_directory = $this->getRoot().'/.divinercache';
$this->atomCache = new DivinerAtomCache($cache_directory);
}
return $this->atomCache;
}
protected function log($message) {
$console = PhutilConsole::getConsole();
$console->getServer()->setEnableLog(true);
$console->writeLog($message."\n");
}
}

View file

@ -95,8 +95,14 @@ final class PhabricatorImageTransformer {
$scaled_y = $min_y;
}
$cropped = $this->applyScaleWithImagemagick($file, $x, $scaled_y);
if ($cropped != null) {
return $cropped;
}
$img = $this->applyScaleTo(
$img,
$file,
$x,
$scaled_y);
@ -131,11 +137,13 @@ final class PhabricatorImageTransformer {
* Very crudely scale an image up or down to an exact size.
*/
private function crudelyScaleTo(PhabricatorFile $file, $dx, $dy) {
$data = $file->loadFileData();
$src = imagecreatefromstring($data);
$scaled = $this->applyScaleWithImagemagick($file, $dx, $dy);
$dst = $this->applyScaleTo($src, $dx, $dy);
if ($scaled != null) {
return $scaled;
}
$dst = $this->applyScaleTo($file, $dx, $dy);
return $this->saveImageDataInAnyFormat($dst, $file->getMimeType());
}
@ -147,17 +155,22 @@ final class PhabricatorImageTransformer {
return $dst;
}
private function applyScaleTo($src, $dx, $dy) {
private function applyScaleTo(PhabricatorFile $file, $dx, $dy) {
$data = $file->loadFileData();
$src = imagecreatefromstring($data);
$x = imagesx($src);
$y = imagesy($src);
$scale = min(($dx / $x), ($dy / $y), 1);
$dst = $this->getBlankDestinationFile($dx, $dy);
$sdx = $scale * $x;
$sdy = $scale * $y;
$dst = $this->getBlankDestinationFile($dx, $dy);
imagesavealpha($dst, true);
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
imagecopyresampled(
$dst,
$src,
@ -167,6 +180,7 @@ final class PhabricatorImageTransformer {
$x, $y);
return $dst;
}
public static function getPreviewDimensions(PhabricatorFile $file, $size) {
@ -337,7 +351,7 @@ final class PhabricatorImageTransformer {
private function saveImageDataInAnyFormat($data, $preferred_mime = '') {
switch ($preferred_mime) {
case 'image/gif': // GIF doesn't support true color.
case 'image/gif': // Gif doesn't support true color
case 'image/png':
if (function_exists('imagepng')) {
ob_start();
@ -368,4 +382,43 @@ final class PhabricatorImageTransformer {
return $img;
}
private function applyScaleWithImagemagick(PhabricatorFile $file, $dx, $dy) {
$img_type = $file->getMimeType();
$imagemagick = PhabricatorEnv::getEnvConfig('files.enable-imagemagick');
if ($img_type != 'image/gif' || $imagemagick == false) {
return null;
}
$data = $file->loadFileData();
$src = imagecreatefromstring($data);
$x = imagesx($src);
$y = imagesy($src);
$scale = min(($dx / $x), ($dy / $y), 1);
$sdx = $scale * $x;
$sdy = $scale * $y;
$input = new TempFile();
Filesystem::writeFile($input, $data);
$resized = new TempFile();
list($err) = exec_manual(
'convert %s -coalesce -resize %sX%s\! %s'
, $input, $sdx, $sdy, $resized
);
if (!$err) {
$new_data = Filesystem::readFile($resized);
return $new_data;
} else {
return null;
}
}
}

View file

@ -119,6 +119,16 @@ final class PhabricatorFilesConfigOptions
"value and the UI will then reflect the actual configured ".
"limit."))
->addExample('10M', pht("Valid setting.")),
$this->newOption('files.enable-imagemagick', 'bool', false)
->setBoolOptions(
array(
pht('Enable'),
pht('Disable')
))->setDescription(
pht("This option will enable animated gif images".
"to be set as profile pictures. The \'convert\' binary ".
"should be available to the webserver for this to work")),
);
}

View file

@ -101,7 +101,8 @@ final class PhabricatorApplicationLaunchView extends AphrontView {
'span',
array(
'class' => implode(' ', $classes),
));
),
'');
$create_button = phutil_tag(
'a',

View file

@ -78,7 +78,10 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
/**
* Parses "to" addresses, looking for a public create email address
* first and if not found parsing the "to" address for reply handler
* information: receiver name, user id, and hash.
* information: receiver name, user id, and hash. If nothing can be
* found, it then loads user phids for as many to: email addresses as
* it can, theoretically falling back to create a conpherence amongst
* those users.
*/
private function getPhabricatorToInformation() {
// Only one "public" create address so far
@ -99,6 +102,8 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
$receiver_name = null;
$user_id = null;
$hash = null;
$user_phids = array();
$user_names = array();
foreach ($this->getToAddresses() as $address) {
if ($address == $create_task) {
$phabricator_address = $address;
@ -121,13 +126,31 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
$hash = $matches[3];
break;
}
$parts = explode('@', $address);
$maybe_name = trim($parts[0]);
$maybe_domain = trim($parts[1]);
$mail_domain = PhabricatorEnv::getEnvConfig('metamta.domain');
if ($mail_domain == $maybe_domain &&
PhabricatorUser::validateUsername($maybe_name)) {
$user_names[] = $maybe_name;
}
}
// since we haven't found a phabricator address, maybe this is
// someone trying to create a conpherence?
if (!$phabricator_address && $user_names) {
$users = id(new PhabricatorUser())
->loadAllWhere('userName IN (%Ls)', $user_names);
$user_phids = mpull($users, 'getPHID');
}
return array(
$phabricator_address,
$receiver_name,
$user_id,
$hash
$hash,
$user_phids
);
}
@ -170,8 +193,9 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
list($to,
$receiver_name,
$user_id,
$hash) = $this->getPhabricatorToInformation();
if (!$to) {
$hash,
$user_phids) = $this->getPhabricatorToInformation();
if (!$to && !$user_phids) {
$raw_to = idx($this->headers, 'to');
return $this->setMessage("Unrecognized 'to' format: {$raw_to}")->save();
}
@ -186,7 +210,7 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
if ($create_task && $to == $create_task) {
$receiver = new ManiphestTask();
$user = $this->lookupPublicUser();
$user = $this->lookupSender();
if ($user) {
$this->setAuthorPHID($user->getPHID());
} else {
@ -230,12 +254,32 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
return $this->save();
}
// means we're creating a conpherence...!
if ($user_phids) {
// we must have a valid user who created this conpherence
$user = $this->lookupSender();
if (!$user) {
return $this->setMessage("Invalid public user '{$from}'.")->save();
}
$conpherence = id(new ConpherenceReplyHandler())
->setMailReceiver(new ConpherenceThread())
->setMailAddedParticipantPHIDs($user_phids)
->setActor($user)
->setExcludeMailRecipientPHIDs($this->loadExcludeMailRecipientPHIDs())
->processEmail($this);
$this->setRelatedPHID($conpherence->getPHID());
$this->setMessage('OK');
return $this->save();
}
if ($user_id == 'public') {
if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
return $this->setMessage("Public replies not enabled.")->save();
}
$user = $this->lookupPublicUser();
$user = $this->lookupSender();
if (!$user) {
return $this->setMessage("Invalid public user '{$from}'.")->save();
@ -373,7 +417,7 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
return array_filter($raw_addresses);
}
private function lookupPublicUser() {
private function lookupSender() {
$from = idx($this->headers, 'from');
$from = $this->getRawEmailAddress($from);

View file

@ -43,7 +43,8 @@ final class PhabricatorApplicationPholio extends PhabricatorApplication {
'new/' => 'PholioMockEditController',
'edit/(?P<id>\d+)/' => 'PholioMockEditController',
'comment/(?P<id>\d+)/' => 'PholioMockCommentController',
'inline/(?P<id>\d+)/' => 'PholioInlineSaveController',
'inline/(?P<id>\d+)/' => 'PholioInlineController',
'inline/save/' => 'PholioInlineSaveController',
),
);
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @group pholio
*/
final class PholioInlineController extends PholioController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$inline_comments = id(new PholioTransactionComment())->loadAllWhere(
'imageid = %d AND transactionphid IS NOT NULL',
$this->id
);
$inlines = array();
foreach ($inline_comments as $inline_comment) {
$inlines[] = array(
'phid' => $inline_comment->getPHID(),
'imageID' => $inline_comment->getImageID(),
'x' => $inline_comment->getX(),
'y' => $inline_comment->getY(),
'width' => $inline_comment->getWidth(),
'height' => $inline_comment->getHeight(),
'content' => $inline_comment->getContent());
}
return id(new AphrontAjaxResponse())->setContent($inlines);
}
}

View file

@ -3,7 +3,7 @@
final class PhabricatorApplicationPhriction extends PhabricatorApplication {
public function getShortDescription() {
return 'Wiki';
return pht('Wiki');
}
public function getBaseURI() {

View file

@ -9,7 +9,7 @@ abstract class PhrictionController extends PhabricatorController {
$page = $this->buildStandardPageView();
$page->setApplicationName('Phriction');
$page->setApplicationName(pht('Phriction'));
$page->setBaseURI('/w/');
$page->setTitle(idx($data, 'title'));
$page->setGlyph("\xE2\x9A\xA1");
@ -32,7 +32,7 @@ abstract class PhrictionController extends PhabricatorController {
$nav->addFilter('', pht('Create Document'), '/phriction/new');
}
$nav->addLabel('Filters');
$nav->addLabel(pht('Filters'));
$nav->addFilter('active', pht('Active Documents'));
$nav->addFilter('all', pht('All Documents'));
$nav->addFilter('updates', pht('Recently Updated'));

View file

@ -32,11 +32,11 @@ final class PhrictionDeleteController extends PhrictionController {
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle('Delete document?')
->setTitle(pht('Delete document?'))
->appendChild(
'Really delete this document? You can recover it later by reverting '.
'to a previous version.')
->addSubmitButton('Delete')
pht('Really delete this document? You can recover it later by '.
'reverting to a previous version.'))
->addSubmitButton(pht('Delete'))
->addCancelButton($document_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);

View file

@ -138,9 +138,9 @@ final class PhrictionDiffController
array(
'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1),
),
"\xC2\xAB Previous Change");
pht("\xC2\xAB Previous Change"));
} else {
$link_l = 'Original Change';
$link_l = pht('Original Change');
}
$link_r = null;
@ -150,9 +150,9 @@ final class PhrictionDiffController
array(
'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1),
),
"Next Change \xC2\xBB");
pht("Next Change \xC2\xBB"));
} else {
$link_r = 'Most Recent Change';
$link_r = pht('Most Recent Change');
}
$navigation_table =
@ -184,7 +184,7 @@ final class PhrictionDiffController
$output,
),
array(
'title' => 'Document History',
'title' => pht('Document History'),
));
}
@ -208,7 +208,7 @@ final class PhrictionDiffController
'href' => '/phriction/edit/'.$document_id.'/',
'class' => 'button',
),
'Edit Current Version');
pht('Edit Current Version'));
}
@ -218,7 +218,7 @@ final class PhrictionDiffController
'href' => '/phriction/edit/'.$document_id.'/?revert='.$version,
'class' => 'button',
),
'Revert to Version '.$version.'...');
pht('Revert to Version %s...', $version));
}
private function renderComparisonTable(array $content) {
@ -244,11 +244,11 @@ final class PhrictionDiffController
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Date',
'Time',
'Version',
'Author',
'Description',
pht('Date'),
pht('Time'),
pht('Version'),
pht('Author'),
pht('Description'),
));
$table->setColumnClasses(
array(

View file

@ -45,7 +45,6 @@ final class PhrictionDocumentController
}
}
$create_uri = '/phriction/edit/?slug='.$slug;
$page_content = hsprintf(
'<div class="phriction-content">'.
'<em>No content here!</em><br />'.
@ -54,7 +53,7 @@ final class PhrictionDocumentController
'</div>',
$slug,
$create_uri);
$page_title = 'Page Not Found';
$page_title = pht('Page Not Found');
} else {
$version = $request->getInt('v');
if ($version) {
@ -67,13 +66,13 @@ final class PhrictionDocumentController
}
if ($content->getID() != $document->getContentID()) {
$vdate = phabricator_datetime($content->getDateCreated(), $user);
$version_note = new AphrontErrorView();
$version_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$version_note->setTitle('Older Version');
$version_note->appendChild(
'You are viewing an older version of this document, as it '.
'appeared on '.
phabricator_datetime($content->getDateCreated(), $user).'.');
pht('You are viewing an older version of this document, as it '.
'appeared on %s.', $vdate));
}
} else {
$content = id(new PhrictionContent())->load($document->getContentID());
@ -139,8 +138,8 @@ final class PhrictionDocumentController
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle('Document Deleted');
$notice->appendChild(
'This document has been deleted. You can edit it to put new content '.
'here, or use history to revert to an earlier version.');
pht('This document has been deleted. You can edit it to put new '.
'content here, or use history to revert to an earlier version.'));
$core_content = $notice->render();
} else {
throw new Exception("Unknown document status '{$doc_status}'!");
@ -323,20 +322,22 @@ final class PhrictionDocumentController
}
}
if ($more_children) {
$list[] = '<li>More...</li>';
$list[] = '<li>'.pht('More...').'</li>';
}
$list[] = '</ul>';
$list = implode("\n", $list);
return
'<div class="phriction-children">'.
'<div class="phriction-children-header">Document Hierarchy</div>'.
'<div class="phriction-children-header">'.
pht('Document Hierarchy').
'</div>'.
$list.
'</div>';
}
private function renderChildDocumentLink(array $info) {
$title = nonempty($info['title'], '(Untitled Document)');
$title = nonempty($info['title'], pht('(Untitled Document)'));
$item = phutil_tag(
'a',
array(

View file

@ -95,8 +95,8 @@ final class PhrictionEditController
$notes = $request->getStr('description');
if (!strlen($title)) {
$e_title = 'Required';
$errors[] = 'Document title is required.';
$e_title = pht('Required');
$errors[] = pht('Document title is required.');
} else {
$e_title = null;
}
@ -107,9 +107,9 @@ final class PhrictionEditController
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle('No Edits');
$dialog->setTitle(pht('No Edits'));
$dialog->appendChild(
'<p>You did not make any changes to the document.</p>');
'<p>'.pht('You did not make any changes to the document.').'</p>');
$dialog->addCancelButton($request->getRequestURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
@ -121,9 +121,9 @@ final class PhrictionEditController
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle('Empty Page');
$dialog->setTitle(pht('Empty Page'));
$dialog->appendChild(
'<p>You can not create an empty document.</p>');
'<p>'.pht('You can not create an empty document.').'</p>');
$dialog->addCancelButton($request->getRequestURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
@ -150,16 +150,16 @@ final class PhrictionEditController
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle('Form Errors')
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
if ($document->getID()) {
$panel_header = 'Edit Phriction Document';
$submit_button = 'Save Changes';
$panel_header = pht('Edit Phriction Document');
$submit_button = pht('Save Changes');
} else {
$panel_header = 'Create New Phriction Document';
$submit_button = 'Create Document';
$panel_header = pht('Create New Phriction Document');
$submit_button = pht('Create Document');
}
$uri = $document->getSlug();
@ -178,7 +178,7 @@ final class PhrictionEditController
array(
'href' => $request->getRequestURI()->alter('nodraft', true),
),
'discard this draft');
pht('discard this draft'));
$draft_note = new AphrontErrorView();
$draft_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
@ -199,17 +199,17 @@ final class PhrictionEditController
->addHiddenInput('nodraft', $request->getBool('nodraft'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Title')
->setLabel(pht('Title'))
->setValue($content->getTitle())
->setError($e_title)
->setName('title'))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('URI')
->setLabel(pht('URI'))
->setValue($uri))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel('Content')
->setLabel(pht('Content'))
->setValue($content_text)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setName('content')
@ -217,7 +217,7 @@ final class PhrictionEditController
->setUser($user))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Edit Notes')
->setLabel(pht('Edit Notes'))
->setValue($notes)
->setError(null)
->setName('description'))
@ -227,18 +227,18 @@ final class PhrictionEditController
->setValue($submit_button));
$panel = id(new AphrontPanelView())
->setWidth(AphrontPanelView::WIDTH_WIDE)
->setNoBackground()
->setHeader($panel_header)
->appendChild($form);
$preview_panel =
'<div class="aphront-panel-preview aphront-panel-preview-wide">
<div class="phriction-document-preview-header">
Document Preview
'.pht('Document Preview').'
</div>
<div id="document-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
'.pht('Loading preview...').'
</div>
</div>
</div>';
@ -259,7 +259,7 @@ final class PhrictionEditController
$preview_panel,
),
array(
'title' => 'Edit Document',
'title' => pht('Edit Document'),
));
}

View file

@ -49,7 +49,7 @@ final class PhrictionHistoryController
$diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/');
$vs_previous = '<em>Created</em>';
$vs_previous = '<em>'.pht('Created').'</em>';
if ($content->getVersion() != 1) {
$uri = $diff_uri
->alter('l', $content->getVersion() - 1)
@ -59,10 +59,10 @@ final class PhrictionHistoryController
array(
'href' => $uri,
),
'Show Change');
pht('Show Change'));
}
$vs_head = '<em>Current</em>';
$vs_head = '<em>'.pht('Current').'</em>';
if ($content->getID() != $document->getContentID()) {
$uri = $diff_uri
->alter('l', $content->getVersion())
@ -73,7 +73,7 @@ final class PhrictionHistoryController
array(
'href' => $uri,
),
'Show Later Changes');
pht('Show Later Changes'));
}
$change_type = PhrictionChangeType::getChangeTypeLabel(
@ -87,7 +87,7 @@ final class PhrictionHistoryController
array(
'href' => $slug_uri.'?v='.$version,
),
'Version '.$version),
pht('Version %s', $version)),
$handles[$content->getAuthorPHID()]->renderLink(),
$change_type,
phutil_escape_html($content->getDescription()),
@ -99,14 +99,14 @@ final class PhrictionHistoryController
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Date',
'Time',
'Version',
'Author',
'Type',
'Description',
'Against Previous',
'Against Current',
pht('Date'),
pht('Time'),
pht('Version'),
pht('Author'),
pht('Type'),
pht('Description'),
pht('Against Previous'),
pht('Against Current'),
));
$table->setColumnClasses(
array(
@ -132,7 +132,8 @@ final class PhrictionHistoryController
PhrictionDocument::getSlugURI($document->getSlug(), 'history')));
$panel = new AphrontPanelView();
$panel->setHeader('Document History');
$panel->setHeader(pht('Document History'));
$panel->setNoBackground();
$panel->appendChild($table);
$panel->appendChild($pager);
@ -142,7 +143,7 @@ final class PhrictionHistoryController
$panel,
),
array(
'title' => 'Document History',
'title' => pht('Document History'),
'device' => true,
));

View file

@ -67,10 +67,10 @@ final class PhrictionListController
$document_table = new AphrontTableView($rows);
$document_table->setHeaders(
array(
'Last Editor',
'Title',
'Last Update',
'Time',
pht('Last Editor'),
pht('Title'),
pht('Last Update'),
pht('Time'),
));
$document_table->setColumnClasses(
@ -84,6 +84,7 @@ final class PhrictionListController
$view_header = $views[$this->view];
$panel = new AphrontPanelView();
$panel->setNoBackground();
$panel->appendChild($document_table);
$panel->appendChild($pager);

View file

@ -78,7 +78,7 @@ final class PhrictionContent extends PhrictionDAO
$toc =
'<div class="phabricator-remarkup-toc">'.
'<div class="phabricator-remarkup-toc-header">'.
'Table of Contents'.
pht('Table of Contents').
'</div>'.
$toc.
'</div>';

4
src/docs/book/user.book Normal file
View file

@ -0,0 +1,4 @@
{
"name" : "phabricator",
"root" : "../../../"
}

View file

@ -67,7 +67,7 @@ NOTE: This is cumbersome. There will be better testing tools at some point.
You can run `aphlict` in the foreground to get output to your console:
phabricator/ $ ./bin/aphlict --foreground
phabricator/ $ sudo ./bin/aphlict --foreground
You can run `support/aphlict/client/aphlict_test_client.php` to connect to the
Aphlict server from the command line. Messages the client receives will be

View file

@ -9,16 +9,16 @@ extends PhabricatorBaseProtocolAdapter {
private $readHandles;
private $multiHandle;
private $active;
private $rooms;
private $inRooms = array();
public function connect() {
$this->server = idx($this->config, 'server');
$this->authtoken = idx($this->config, 'authtoken');
$ssl = idx($this->config, 'ssl', false);
$this->rooms = idx($this->config, 'join');
$rooms = idx($this->config, 'join');
// First, join the room
if (!$this->rooms) {
if (!$rooms) {
throw new Exception("Not configured to join any rooms!");
}
@ -29,7 +29,7 @@ extends PhabricatorBaseProtocolAdapter {
$this->multiHandle = curl_multi_init();
$this->readHandles = array();
foreach ($this->rooms as $room_id) {
foreach ($rooms as $room_id) {
$this->joinRoom($room_id);
// Set up the curl stream for reading
@ -138,10 +138,12 @@ extends PhabricatorBaseProtocolAdapter {
private function joinRoom($room_id) {
$this->performPost("/room/{$room_id}/join.json");
$this->inRooms[$room_id] = true;
}
private function leaveRoom($room_id) {
$this->performPost("/room/{$room_id}/leave.json");
unset($this->inRooms[$room_id]);
}
private function speak($message, $room_id) {
@ -154,48 +156,42 @@ extends PhabricatorBaseProtocolAdapter {
}
private function performPost($endpoint, $data = Null) {
$url = $this->server.$endpoint;
$uri = new PhutilURI($this->server);
$uri->setPath($endpoint);
$payload = json_encode($data);
// cURL init & config
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_USERPWD, $this->authtoken . ':x');
curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array("Content-type: application/json"));
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
$output = curl_exec($ch);
curl_close($ch);
list($output) = id(new HTTPSFuture($uri))
->setMethod('POST')
->addHeader('Content-Type', 'application/json')
->addHeader('Authorization', $this->getAuthorizationHeader())
->setData($payload)
->resolvex();
$output = trim($output);
if (strlen($output)) {
return json_decode($output);
return json_decode($output, true);
}
return true;
}
public function __destruct() {
if ($this->rooms) {
foreach ($this->rooms as $room_id) {
foreach ($this->inRooms as $room_id => $ignored) {
$this->leaveRoom($room_id);
}
}
if ($this->readHandles) {
foreach ($this->readHandles as $read_handle) {
curl_multi_remove_handle($this->multiHandle, $read_handle);
curl_close($read_handle);
}
}
curl_multi_close($this->multiHandle);
}
private function getAuthorizationHeader() {
return 'Basic '.base64_encode($this->authtoken.':x');
}
}

View file

@ -25,6 +25,7 @@ final class AphrontFormSubmitControl extends AphrontFormControl {
$submit_button = phutil_tag(
'button',
array(
'type' => 'submit',
'name' => '__submit__',
'disabled' => $this->getDisabled() ? 'disabled' : null,
),

View file

@ -7,6 +7,9 @@ body {
scrollbar to a page with a scrollbar doesn't make content jump a few
pixels left when the viewport narrows. */
overflow-y: scroll;
/* reset behavior in ie7, as it will add an extra scrollbar regardless
selector * targets ie6 and ie7 only */
*overflow-y: auto;
}
.device-phone {

View file

@ -35,6 +35,7 @@ JX.behavior('pholio-mock-view', function(config) {
main.src = data.fullSizeURI;
JX.DOM.setContent(wrapper,main);
load_inline_comments();
});
@ -123,7 +124,7 @@ JX.behavior('pholio-mock-view', function(config) {
selection_fill.title = comment;
var saveURL = "/pholio/inline/" + imageData['imageID'] + "/";
var saveURL = "/pholio/inline/save/";
var inlineComment = new JX.Request(saveURL, function(r) {
@ -144,6 +145,36 @@ JX.behavior('pholio-mock-view', function(config) {
});
function load_inline_comments() {
var data = JX.Stratcom.getData(JX.$(config.mainID));
var inline_comments_url = "/pholio/inline/" + data['imageID'] + "/";
var inline_comments = new JX.Request(inline_comments_url, function(r) {
if (r.length > 0) {
for(i=0; i < r.length; i++) {
var inlineSelection = JX.$N(
'div',
{
id: r[i].phid,
className: 'pholio-mock-select-border',
title: r[i].content
});
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);
}
}
});
inline_comments.send();
}
load_inline_comments();
});