From 5e11eb7f7216d07af545eaa072c75c25e08bfe0b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 26 Feb 2013 14:59:31 -0800 Subject: [PATCH] Prepare for hovercards Summary: - Unify all the reference/embed Remarkup rules for Differential, Maniphest, Paste and Ponder. - Add rules for Pholio. - Does not yet unify Diffusion or Files (both are a bit more involved). - Prepare for hovercards. Test Plan: {F33894} Reviewers: chad, vrana Reviewed By: vrana CC: aran Differential Revision: https://secure.phabricator.com/D5120 --- src/__phutil_library_map__.php | 10 +- .../base/PhabricatorApplication.php | 4 + .../PhabricatorApplicationDifferential.php | 6 + .../query/DifferentialRevisionQuery.php | 10 + ...bricatorRemarkupRuleDifferentialHandle.php | 21 -- .../maniphest/ManiphestTaskQuery.php | 9 + .../PhabricatorApplicationManiphest.php | 8 +- ...PhabricatorRemarkupRuleManiphestHandle.php | 21 -- .../PhabricatorApplicationPaste.php | 6 + .../PhabricatorApplicationPholio.php | 6 + .../pholio/remarkup/PholioRemarkupRule.php | 23 +++ .../PhabricatorApplicationPonder.php | 6 + .../markup/PhabricatorMarkupEngine.php | 17 +- .../rule/PhabricatorRemarkupRuleObject.php | 187 ++++++++++++++++++ .../PhabricatorRemarkupRuleObjectHandle.php | 66 ------- 15 files changed, 274 insertions(+), 126 deletions(-) delete mode 100644 src/applications/differential/remarkup/PhabricatorRemarkupRuleDifferentialHandle.php delete mode 100644 src/applications/maniphest/remarkup/PhabricatorRemarkupRuleManiphestHandle.php create mode 100644 src/applications/pholio/remarkup/PholioRemarkupRule.php create mode 100644 src/infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php delete mode 100644 src/infrastructure/markup/rule/PhabricatorRemarkupRuleObjectHandle.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9e7b6bd26d..404a26f1e7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1181,13 +1181,11 @@ phutil_register_library_map(array( 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', 'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php', - 'PhabricatorRemarkupRuleDifferentialHandle' => 'applications/differential/remarkup/PhabricatorRemarkupRuleDifferentialHandle.php', 'PhabricatorRemarkupRuleEmbedFile' => 'applications/files/remarkup/PhabricatorRemarkupRuleEmbedFile.php', 'PhabricatorRemarkupRuleImageMacro' => 'applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php', - 'PhabricatorRemarkupRuleManiphestHandle' => 'applications/maniphest/remarkup/PhabricatorRemarkupRuleManiphestHandle.php', 'PhabricatorRemarkupRuleMeme' => 'applications/macro/remarkup/PhabricatorRemarkupRuleMeme.php', 'PhabricatorRemarkupRuleMention' => 'applications/people/remarkup/PhabricatorRemarkupRuleMention.php', - 'PhabricatorRemarkupRuleObjectHandle' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleObjectHandle.php', + 'PhabricatorRemarkupRuleObject' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php', 'PhabricatorRemarkupRuleObjectName' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleObjectName.php', 'PhabricatorRemarkupRuleYoutube' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleYoutube.php', 'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php', @@ -1462,6 +1460,7 @@ phutil_register_library_map(array( 'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php', 'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php', 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', + 'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php', 'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php', 'PholioSearchIndexer' => 'applications/pholio/search/PholioSearchIndexer.php', 'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php', @@ -2657,13 +2656,11 @@ phutil_register_library_map(array( 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', 'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl', - 'PhabricatorRemarkupRuleDifferentialHandle' => 'PhabricatorRemarkupRuleObjectHandle', 'PhabricatorRemarkupRuleEmbedFile' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule', - 'PhabricatorRemarkupRuleManiphestHandle' => 'PhabricatorRemarkupRuleObjectHandle', 'PhabricatorRemarkupRuleMeme' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleMention' => 'PhutilRemarkupRule', - 'PhabricatorRemarkupRuleObjectHandle' => 'PhutilRemarkupRule', + 'PhabricatorRemarkupRuleObject' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleObjectName' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleYoutube' => 'PhutilRemarkupRule', 'PhabricatorRepository' => @@ -2948,6 +2945,7 @@ phutil_register_library_map(array( 'PholioMockListController' => 'PholioController', 'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PholioMockViewController' => 'PholioController', + 'PholioRemarkupRule' => 'PhabricatorRemarkupRuleObject', 'PholioReplyHandler' => 'PhabricatorMailReplyHandler', 'PholioSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PholioTransaction' => 'PhabricatorApplicationTransaction', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 934b39865c..daa9e67cfd 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -152,6 +152,10 @@ abstract class PhabricatorApplication { } } + public function getRemarkupRules() { + return array(); + } + /* -( URI Routing )-------------------------------------------------------- */ diff --git a/src/applications/differential/application/PhabricatorApplicationDifferential.php b/src/applications/differential/application/PhabricatorApplicationDifferential.php index 1ba3d29185..37509c221f 100644 --- a/src/applications/differential/application/PhabricatorApplicationDifferential.php +++ b/src/applications/differential/application/PhabricatorApplicationDifferential.php @@ -73,6 +73,12 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication { return 0.100; } + public function getRemarkupRules() { + return array( + new DifferentialRemarkupRule(), + ); + } + public function loadStatus(PhabricatorUser $user) { $revisions = id(new DifferentialRevisionQuery()) ->withResponsibleUsers(array($user->getPHID())) diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 02975b5b4a..1e5f490ed0 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -60,6 +60,16 @@ final class DifferentialRevisionQuery { private $needDiffIDs = false; private $needCommitPHIDs = false; private $needHashes = false; + private $viewer; + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } /* -( Query Configuration )------------------------------------------------ */ diff --git a/src/applications/differential/remarkup/PhabricatorRemarkupRuleDifferentialHandle.php b/src/applications/differential/remarkup/PhabricatorRemarkupRuleDifferentialHandle.php deleted file mode 100644 index 93941672c3..0000000000 --- a/src/applications/differential/remarkup/PhabricatorRemarkupRuleDifferentialHandle.php +++ /dev/null @@ -1,21 +0,0 @@ -load($id); - if ($revision) { - return $revision->getPHID(); - } - return null; - } - -} diff --git a/src/applications/maniphest/ManiphestTaskQuery.php b/src/applications/maniphest/ManiphestTaskQuery.php index 221113abfd..696dcf34bf 100644 --- a/src/applications/maniphest/ManiphestTaskQuery.php +++ b/src/applications/maniphest/ManiphestTaskQuery.php @@ -58,7 +58,16 @@ final class ManiphestTaskQuery extends PhabricatorQuery { private $rowCount = null; private $groupByProjectResults = null; // See comment at bottom for details + private $viewer; + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } public function withAuthors(array $authors) { $this->authorPHIDs = $authors; diff --git a/src/applications/maniphest/application/PhabricatorApplicationManiphest.php b/src/applications/maniphest/application/PhabricatorApplicationManiphest.php index 9701393878..038ec46bf4 100644 --- a/src/applications/maniphest/application/PhabricatorApplicationManiphest.php +++ b/src/applications/maniphest/application/PhabricatorApplicationManiphest.php @@ -38,7 +38,13 @@ final class PhabricatorApplicationManiphest extends PhabricatorApplication { public function getEventListeners() { return array( - new ManiphestPeopleMenuEventListener() + new ManiphestPeopleMenuEventListener(), + ); + } + + public function getRemarkupRules() { + return array( + new ManiphestRemarkupRule(), ); } diff --git a/src/applications/maniphest/remarkup/PhabricatorRemarkupRuleManiphestHandle.php b/src/applications/maniphest/remarkup/PhabricatorRemarkupRuleManiphestHandle.php deleted file mode 100644 index ffee967ff1..0000000000 --- a/src/applications/maniphest/remarkup/PhabricatorRemarkupRuleManiphestHandle.php +++ /dev/null @@ -1,21 +0,0 @@ -load($id); - if ($task) { - return $task->getPHID(); - } - return null; - } - -} diff --git a/src/applications/paste/application/PhabricatorApplicationPaste.php b/src/applications/paste/application/PhabricatorApplicationPaste.php index 8b0f9c6dd5..5e377fb509 100644 --- a/src/applications/paste/application/PhabricatorApplicationPaste.php +++ b/src/applications/paste/application/PhabricatorApplicationPaste.php @@ -22,6 +22,12 @@ final class PhabricatorApplicationPaste extends PhabricatorApplication { return $this->getBaseURI().'create/'; } + public function getRemarkupRules() { + return array( + new PhabricatorPasteRemarkupRule(), + ); + } + public function getRoutes() { return array( '/P(?P[1-9]\d*)' => 'PhabricatorPasteViewController', diff --git a/src/applications/pholio/application/PhabricatorApplicationPholio.php b/src/applications/pholio/application/PhabricatorApplicationPholio.php index 27a193414e..ca05103eb1 100644 --- a/src/applications/pholio/application/PhabricatorApplicationPholio.php +++ b/src/applications/pholio/application/PhabricatorApplicationPholio.php @@ -34,6 +34,12 @@ final class PhabricatorApplicationPholio extends PhabricatorApplication { return true; } + public function getRemarkupRules() { + return array( + new PholioRemarkupRule(), + ); + } + public function getRoutes() { return array( '/M(?P[1-9]\d*)' => 'PholioMockViewController', diff --git a/src/applications/pholio/remarkup/PholioRemarkupRule.php b/src/applications/pholio/remarkup/PholioRemarkupRule.php new file mode 100644 index 0000000000..7506be3298 --- /dev/null +++ b/src/applications/pholio/remarkup/PholioRemarkupRule.php @@ -0,0 +1,23 @@ +getEngine()->getConfig('viewer'); + + if (!$viewer) { + return array(); + } + + return id(new PholioMockQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + } + +} diff --git a/src/applications/ponder/application/PhabricatorApplicationPonder.php b/src/applications/ponder/application/PhabricatorApplicationPonder.php index 5fb8ff55aa..0d3840f412 100644 --- a/src/applications/ponder/application/PhabricatorApplicationPonder.php +++ b/src/applications/ponder/application/PhabricatorApplicationPonder.php @@ -31,6 +31,12 @@ final class PhabricatorApplicationPonder extends PhabricatorApplication { return $status; } + public function getRemarkupRules() { + return array( + new PonderRemarkupRule(), + ); + } + public function getApplicationGroup() { return self::GROUP_COMMUNICATION; } diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index b06cdb0f14..d95b4b308d 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -400,23 +400,18 @@ final class PhabricatorMarkupEngine { $rules[] = new PhutilRemarkupRuleHyperlink(); $rules[] = new PhrictionRemarkupRule(); - $rules[] = new PhabricatorRemarkupRuleDifferentialHandle(); - if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) { - $rules[] = new PhabricatorRemarkupRuleManiphestHandle(); - } - $rules[] = new PhabricatorRemarkupRuleEmbedFile(); - $rules[] = new DifferentialRemarkupRule(); $rules[] = new DiffusionRemarkupRule(); - if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) { - $rules[] = new ManiphestRemarkupRule(); - } - $rules[] = new PhabricatorPasteRemarkupRule(); $rules[] = new PhabricatorCountdownRemarkupRule(); - $rules[] = new PonderRemarkupRule(); + $applications = PhabricatorApplication::getAllInstalledApplications(); + foreach ($applications as $application) { + foreach ($application->getRemarkupRules() as $rule) { + $rules[] = $rule; + } + } if ($options['macros']) { $rules[] = new PhabricatorRemarkupRuleImageMacro(); diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php new file mode 100644 index 0000000000..74dc9ec151 --- /dev/null +++ b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php @@ -0,0 +1,187 @@ +getEngine()->getConfig('viewer'); + if ($viewer) { + $query->setViewer($viewer); + } + $handles = $query->loadHandles(); + + $result = array(); + foreach ($objects as $id => $object) { + $result[$id] = $handles[$object->getPHID()]; + } + return $result; + } + + protected function renderObjectRef($object, $handle, $anchor) { + $href = $handle->getURI(); + $text = $this->getObjectNamePrefix().$object->getID(); + if ($anchor) { + $matches = null; + if (preg_match('@^#(?:comment-)?(\d{1,7})$@', $anchor, $matches)) { + // Maximum length is 7 because 12345678 could be a file hash in + // Differential. + $href = $href."#comment-".$matches[1]; + $text = $text."#".$matches[1]; + } else { + $href = $href.$anchor; + $text = $text.$anchor; + } + } + + $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; + + $attr = array( + 'phid' => $handle->getPHID(), + 'closed' => ($handle->getStatus() == $status_closed), + ); + + return $this->renderHovertag($text, $href, $attr); + } + + protected function renderObjectEmbed($object, $handle, $options) { + $name = $handle->getFullName(); + $href = $handle->getURI(); + $attr = array( + 'phid' => $handle->getPHID(), + ); + + return $this->renderHovertag($name, $href, $attr); + } + + protected function renderHovertag($name, $href, array $attr = array()) { + return id(new PhabricatorTagView()) + ->setName($name) + ->setHref($href) + ->setType(PhabricatorTagView::TYPE_OBJECT) + ->setPHID(idx($attr, 'phid')) + ->setClosed(idx($attr, 'closed')) + ->render(); + } + + public function apply($text) { + $prefix = $this->getObjectNamePrefix(); + $prefix = preg_quote($prefix, '@'); + $id = $this->getObjectIDPattern(); + + $text = preg_replace_callback( + '@\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B@', + array($this, 'markupObjectEmbed'), + $text); + + $text = preg_replace_callback( + '@\b'.$prefix.'('.$id.')(?:#([-\w\d]+))?\b@', + array($this, 'markupObjectReference'), + $text); + + return $text; + } + + public function markupObjectEmbed($matches) { + return $this->markupObject(array( + 'type' => 'embed', + 'id' => $matches[1], + 'options' => idx($matches, 2), + 'original' => $matches[0], + )); + } + + public function markupObjectReference($matches) { + return $this->markupObject(array( + 'type' => 'ref', + 'id' => $matches[1], + 'anchor' => idx($matches, 2), + 'original' => $matches[0], + )); + } + + private function markupObject(array $params) { + if (!$this->shouldMarkupObject($params)) { + return $params['original']; + } + + $engine = $this->getEngine(); + $token = $engine->storeText('x'); + + $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); + $metadata = $engine->getTextMetadata($metadata_key, array()); + + $metadata[] = array( + 'token' => $token, + ) + $params; + + $engine->setTextMetadata($metadata_key, $metadata); + + return $token; + } + + public function didMarkupText() { + $engine = $this->getEngine(); + $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); + $metadata = $engine->getTextMetadata($metadata_key, array()); + + if (!$metadata) { + return; + } + + + $ids = ipull($metadata, 'id'); + $objects = $this->loadObjects($ids); + + // For objects that are invalid or which the user can't see, just render + // the original text. + + // TODO: We should probably distinguish between these cases and render a + // "you can't see this" state for nonvisible objects. + + foreach ($metadata as $key => $spec) { + if (empty($objects[$spec['id']])) { + $engine->overwriteStoredText( + $spec['token'], + $spec['original']); + unset($metadata[$key]); + } + } + + $handles = $this->loadHandles($objects); + foreach ($metadata as $key => $spec) { + $handle = $handles[$spec['id']]; + $object = $objects[$spec['id']]; + switch ($spec['type']) { + case 'ref': + $view = $this->renderObjectRef($object, $handle, $spec['anchor']); + break; + case 'embed': + $view = $this->renderObjectEmbed($object, $handle, $spec['options']); + break; + } + $engine->overwriteStoredText($spec['token'], $view); + } + + $engine->setTextMetadata($metadata_key, array()); + } + +} diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObjectHandle.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObjectHandle.php deleted file mode 100644 index c47681a7f7..0000000000 --- a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObjectHandle.php +++ /dev/null @@ -1,66 +0,0 @@ -getObjectNamePrefix(); - return preg_replace_callback( - "@\B{{$prefix}(\d+)}\B@", - array($this, 'markupObjectHandle'), - $text); - } - - protected function markupObjectHandle($matches) { - // TODO: These are single gets but should be okay for now, they're behind - // the cache. - $phid = $this->loadObjectPHID($matches[1]); - if (!$phid) { - return $matches[0]; - } - - $engine = $this->getEngine(); - $token = $engine->storeText(''); - - $metadata_key = self::KEY_RULE_HANDLE; - $metadata = $engine->getTextMetadata($metadata_key, array()); - if (empty($metadata[$phid])) { - $metadata[$phid] = array(); - } - $metadata[$phid][] = $token; - $engine->setTextMetadata($metadata_key, $metadata); - - return $token; - } - - public function didMarkupText() { - $engine = $this->getEngine(); - - $metadata_key = self::KEY_RULE_HANDLE; - $metadata = $engine->getTextMetadata($metadata_key, array()); - if (empty($metadata)) { - return; - } - - $handles = id(new PhabricatorObjectHandleData(array_keys($metadata))) - ->loadHandles(); - - foreach ($metadata as $phid => $tokens) { - $link = $handles[$phid]->renderLink(); - foreach ($tokens as $token) { - $engine->overwriteStoredText($token, $link); - } - } - - $engine->setTextMetadata($metadata_key, array()); - } - -}