Path: blob/master/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php
12241 views
<?php12final class PhutilRemarkupDocumentLinkRule extends PhutilRemarkupRule {34public function getPriority() {5return 150.0;6}78public function apply($text) {9// Handle mediawiki-style links: [[ href | name ]]10$text = preg_replace_callback(11'@\B\\[\\[([^|\\]]+)(?:\\|([^\\]]+))?\\]\\]\B@U',12array($this, 'markupDocumentLink'),13$text);1415// Handle markdown-style links: [name](href)16$text = preg_replace_callback(17'@'.18'\B'.19'\\[([^\\]]+)\\]'.20'\\('.21'(\s*'.22// See T12343. This is making some kind of effort to implement23// parenthesis balancing rules. It won't get nested parentheses24// right, but should do OK for Wikipedia pages, which seem to be25// the most important use case.2627// Match zero or more non-parenthesis, non-space characters.28'[^\s()]*'.29// Match zero or more sequences of "(...)", where two balanced30// parentheses enclose zero or more normal characters. If we31// match some, optionally match more stuff at the end.32'(?:(?:\\([^ ()]*\\))+[^\s()]*)?'.33'\s*)'.34'\\)'.35'\B'.36'@U',37array($this, 'markupAlternateLink'),38$text);3940return $text;41}4243protected function renderHyperlink($link, $name) {44$engine = $this->getEngine();4546$is_anchor = false;47if (strncmp($link, '/', 1) == 0) {48$base = phutil_string_cast($engine->getConfig('uri.base'));49$base = rtrim($base, '/');50$link = $base.$link;51} else if (strncmp($link, '#', 1) == 0) {52$here = $engine->getConfig('uri.here');53$link = $here.$link;5455$is_anchor = true;56}5758if ($engine->isTextMode()) {59// If present, strip off "mailto:" or "tel:".60$link = preg_replace('/^(?:mailto|tel):/', '', $link);6162if (!strlen($name)) {63return $link;64}6566return $name.' <'.$link.'>';67}6869if (!strlen($name)) {70$name = $link;71$name = preg_replace('/^(?:mailto|tel):/', '', $name);72}7374if ($engine->getState('toc')) {75return $name;76}7778$same_window = $engine->getConfig('uri.same-window', false);79if ($same_window) {80$target = null;81} else {82$target = '_blank';83}8485// For anchors on the same page, always stay here.86if ($is_anchor) {87$target = null;88}8990return phutil_tag(91'a',92array(93'href' => $link,94'class' => 'remarkup-link',95'target' => $target,96'rel' => 'noreferrer',97),98$name);99}100101public function markupAlternateLink(array $matches) {102$uri = trim($matches[2]);103104if (!strlen($uri)) {105return $matches[0];106}107108// NOTE: We apply some special rules to avoid false positives here. The109// major concern is that we do not want to convert `x[0][1](y)` in a110// discussion about C source code into a link. To this end, we:111//112// - Don't match at word boundaries;113// - require the URI to contain a "/" character or "@" character; and114// - reject URIs which being with a quote character.115116if ($uri[0] == '"' || $uri[0] == "'" || $uri[0] == '`') {117return $matches[0];118}119120if (strpos($uri, '/') === false &&121strpos($uri, '@') === false &&122strncmp($uri, 'tel:', 4)) {123return $matches[0];124}125126return $this->markupDocumentLink(127array(128$matches[0],129$matches[2],130$matches[1],131));132}133134public function markupDocumentLink(array $matches) {135$uri = trim($matches[1]);136$name = trim(idx($matches, 2, ''));137138if (!$this->isFlatText($uri)) {139return $matches[0];140}141142if (!$this->isFlatText($name)) {143return $matches[0];144}145146// If whatever is being linked to begins with "/" or "#", or has "://",147// or is "mailto:" or "tel:", treat it as a URI instead of a wiki page.148$is_uri = preg_match('@(^/)|(://)|(^#)|(^(?:mailto|tel):)@', $uri);149150if ($is_uri && strncmp('/', $uri, 1) && strncmp('#', $uri, 1)) {151$protocols = $this->getEngine()->getConfig(152'uri.allowed-protocols',153array());154155try {156$protocol = id(new PhutilURI($uri))->getProtocol();157if (!idx($protocols, $protocol)) {158// Don't treat this as a URI if it's not an allowed protocol.159$is_uri = false;160}161} catch (Exception $ex) {162// We can end up here if we try to parse an ambiguous URI, see163// T12796.164$is_uri = false;165}166}167168// As a special case, skip "[[ / ]]" so that Phriction picks it up as a169// link to the Phriction root. It is more useful to be able to use this170// syntax to link to the root document than the home page of the install.171if ($uri == '/') {172$is_uri = false;173}174175if (!$is_uri) {176return $matches[0];177}178179return $this->getEngine()->storeText($this->renderHyperlink($uri, $name));180}181182}183184185