diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -8,8 +8,10 @@ abstract protected function getObjectNamePrefix(); abstract protected function loadObjects(array $ids); + private $whitelist; + public function getPriority() { - return 450.0; + return 350.0; } protected function getObjectNamePrefixBeginsWithWordCharacter() { @@ -28,7 +30,7 @@ protected function loadHandles(array $objects) { $phids = mpull($objects, 'getPHID'); - $viewer = $this->getEngine()->getConfig('viewer'); + $viewer = $this->getEngine()->getConfig('viewer'); $handles = $viewer->loadHandles($phids); $handles = iterator_to_array($handles); @@ -53,7 +55,7 @@ return $uri; } - protected function renderObjectRefForAnyMedia ( + protected function renderObjectRefForAnyMedia( $object, PhabricatorObjectHandle $handle, $anchor, @@ -94,8 +96,8 @@ } $attr = array( - 'phid' => $handle->getPHID(), - 'closed' => ($handle->getStatus() == $status_closed), + 'phid' => $handle->getPHID(), + 'closed' => $handle->getStatus() == $status_closed, ); return $this->renderHovertag($text, $href, $attr); @@ -128,8 +130,8 @@ $href = $handle->getURI(); $status_closed = PhabricatorObjectHandle::STATUS_CLOSED; $attr = array( - 'phid' => $handle->getPHID(), - 'closed' => ($handle->getStatus() == $status_closed), + 'phid' => $handle->getPHID(), + 'closed' => $handle->getStatus() == $status_closed, ); return $this->renderHovertag($name, $href, $attr); @@ -141,9 +143,9 @@ PhabricatorObjectHandle $handle) { $status_closed = PhabricatorObjectHandle::STATUS_CLOSED; - $strikethrough = $handle->getStatus() == $status_closed ? - 'text-decoration: line-through;' : - 'text-decoration: none;'; + $strikethrough = $handle->getStatus() == $status_closed + ? 'text-decoration: line-through;' + : 'text-decoration: none;'; return phutil_tag( 'a', @@ -172,6 +174,11 @@ public function apply($text) { $text = preg_replace_callback( + $this->getObjectHyperlinkReferencePattern(), + array($this, 'markupObjectHyperlinkReference'), + $text); + + $text = preg_replace_callback( $this->getObjectEmbedPattern(), array($this, 'markupObjectEmbed'), $text); @@ -185,9 +192,8 @@ } private function getObjectEmbedPattern() { - $prefix = $this->getObjectNamePrefix(); - $prefix = preg_quote($prefix); - $id = $this->getObjectIDPattern(); + $prefix = preg_quote($this->getObjectNamePrefix()); + $id = $this->getObjectIDPattern(); return '(\B{'.$prefix.'('.$id.')([,\s](?:[^}\\\\]|\\\\.)*)?}\B)u'; } @@ -211,13 +217,16 @@ // The "(?getObjectHyperlinkReferencePattern(), + $text, + $hyperlink_ref_matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + $embed_matches = null; preg_match_all( $this->getObjectEmbedPattern(), @@ -246,8 +262,9 @@ $results = array(); $sets = array( 'embed' => $embed_matches, - 'ref' => $ref_matches, + 'ref' => array_merge($hyperlink_ref_matches, $ref_matches), ); + foreach ($sets as $type => $matches) { $formatted = array(); foreach ($matches as $match) { @@ -255,11 +272,14 @@ 'offset' => $match[1][1], 'id' => $match[1][0], ); + if (isset($match[2][0])) { $format['tail'] = $match[2][0]; } + $formatted[] = $format; } + $results[$type] = $formatted; } @@ -292,25 +312,56 @@ )); } + public function markupObjectHyperlinkReference(array $matches) { + if (!$this->isFlatText($matches[0])) { + return $matches[0]; + } + + $uri = new PhutilURI($matches[1]); + + $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); + $base_uri = new PhutilURI($base_uri); + + if (strtolower($uri->getDomain()) != strtolower($base_uri->getDomain())) { + return $matches[0]; + } + + $path = substr($uri->getPath(), 1); + + if (preg_match($this->getObjectEmbedPattern(), $path)) { + return $this->apply($path); + } + + if (preg_match($this->getObjectReferencePattern(), $path)) { + return $this->apply($path); + } + + $rule = id(new PhutilRemarkupHyperlinkRule()) + ->setEngine($this->getEngine()); + $rule->apply($matches[0]); + return $matches[0]; + } + private function markupObject(array $params) { if (!$this->shouldMarkupObject($params)) { return $params['original']; } - $regex = trim( - PhabricatorEnv::getEnvConfig('remarkup.ignored-object-names')); + $regex = PhabricatorEnv::getEnvConfig('remarkup.ignored-object-names'); + $regex = trim($regex); + if ($regex && preg_match($regex, $params['original'])) { return $params['original']; } $engine = $this->getEngine(); - $token = $engine->storeText('x'); + $token = $engine->storeText('x'); $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); $metadata = $engine->getTextMetadata($metadata_key, array()); $metadata[] = array( - 'token' => $token, + 'token' => $token, ) + $params; $engine->setTextMetadata($metadata_key, $metadata); @@ -327,21 +378,18 @@ 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']); + $engine->overwriteStoredText($spec['token'], $spec['original']); unset($metadata[$key]); } } @@ -358,13 +406,13 @@ $object = $objects[$spec['id']]; switch ($spec['type']) { case 'ref': - $view = $this->renderObjectRefForAnyMedia( $object, $handle, $spec['anchor'], $spec['id']); break; + case 'embed': $spec['options'] = $this->assertFlatText($spec['options']); $view = $this->renderObjectEmbedForAnyMedia(