Page MenuHomePhorge

D25118.1736864958.diff
No OneTemporary

D25118.1736864958.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -5789,6 +5789,8 @@
'PhutilTranslatedHTMLTestCase' => 'infrastructure/markup/__tests__/PhutilTranslatedHTMLTestCase.php',
'PhutilTwitchAuthAdapter' => 'applications/auth/adapter/PhutilTwitchAuthAdapter.php',
'PhutilTwitterAuthAdapter' => 'applications/auth/adapter/PhutilTwitterAuthAdapter.php',
+ 'PhutilURIGoodie' => 'infrastructure/parser/PhutilURIGoodie.php',
+ 'PhutilURIGoodieTestCase' => 'infrastructure/parser/__tests__/PhutilURIGoodieTestCase.php',
'PhutilWordPressAuthAdapter' => 'applications/auth/adapter/PhutilWordPressAuthAdapter.php',
'PhutilXHPASTSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php',
'PhutilXHPASTSyntaxHighlighterFuture' => 'infrastructure/markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php',
@@ -12675,6 +12677,8 @@
'PhutilTranslatedHTMLTestCase' => 'PhutilTestCase',
'PhutilTwitchAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilTwitterAuthAdapter' => 'PhutilOAuth1AuthAdapter',
+ 'PhutilURIGoodie' => 'Phobject',
+ 'PhutilURIGoodieTestCase' => 'PhabricatorTestCase',
'PhutilWordPressAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilXHPASTSyntaxHighlighter' => 'Phobject',
'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy',
diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php
--- a/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php
+++ b/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php
@@ -44,16 +44,15 @@
protected function renderHyperlink($link, $name) {
$engine = $this->getEngine();
- $is_anchor = false;
- if (strncmp($link, '/', 1) == 0) {
+ $uri = new PhutilURIGoodie($link);
+ $is_anchor = $uri->isAnchor();
+ if ($uri->isStartingWithSlash()) {
+ $here = $engine->getConfig('uri.here');
+ $link = $here.$link;
+ } else if ($is_anchor) {
$base = phutil_string_cast($engine->getConfig('uri.base'));
$base = rtrim($base, '/');
$link = $base.$link;
- } else if (strncmp($link, '#', 1) == 0) {
- $here = $engine->getConfig('uri.here');
- $link = $here.$link;
-
- $is_anchor = true;
}
if ($engine->isTextMode()) {
@@ -76,7 +75,8 @@
return $name;
}
- $same_window = $engine->getConfig('uri.same-window', false);
+ $is_internal = $uri->isInternal();
+ $same_window = $engine->getConfig('uri.same-window', $is_internal);
if ($same_window) {
$target = null;
} else {
@@ -92,7 +92,7 @@
'a',
array(
'href' => $link,
- 'class' => 'remarkup-link',
+ 'class' => $this->getRemarkupLinkClass($is_internal),
'target' => $target,
'rel' => 'noreferrer',
),
diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php
--- a/src/infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php
+++ b/src/infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php
@@ -116,7 +116,9 @@
$engine = $this->getEngine();
- $same_window = $engine->getConfig('uri.same-window', false);
+ $uri = new PhutilURIGoodie($link);
+ $is_internal = $uri->isInternal();
+ $same_window = $engine->getConfig('uri.same-window', $is_internal);
if ($same_window) {
$target = null;
} else {
@@ -127,7 +129,7 @@
'a',
array(
'href' => $link,
- 'class' => 'remarkup-link',
+ 'class' => $this->getRemarkupLinkClass($is_internal),
'target' => $target,
'rel' => 'noreferrer',
),
diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php
--- a/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php
+++ b/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php
@@ -106,4 +106,20 @@
return (strpos($text, PhutilRemarkupBlockStorage::MAGIC_BYTE) === false);
}
+ /**
+ * Get the CSS class="" attribute for a Remarkup link.
+ * It's just "remarkup-link" for all cases, plus the possibility for
+ * designers to style external links differently.
+ * @param boolean $is_internal Whenever the link was internal or not.
+ * @return string
+ */
+ protected function getRemarkupLinkClass($is_internal) {
+ // Allow developers to style esternal links differently
+ $classes = array('remarkup-link');
+ if (!$is_internal) {
+ $classes[] = 'remarkup-link-ext';
+ }
+ return implode(' ', $classes);
+ }
+
}
diff --git a/src/infrastructure/parser/PhutilURIGoodie.php b/src/infrastructure/parser/PhutilURIGoodie.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/parser/PhutilURIGoodie.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * Wrapper for the PhutilURI class to be aware of the relative/absolute context
+ * and maybe other things in the future.
+ */
+final class PhutilURIGoodie extends Phobject {
+
+ /**
+ * Dependency injection with the URI object.
+ * @var PhutilURI
+ */
+ private $uriObj;
+
+ /**
+ * The original URI, untouched.
+ * @var string
+ */
+ private $uriOriginalStr;
+
+ /**
+ * Base URI of Phorge itself, as handy object.
+ * The base URI is supposed to never change across requests. So, static.
+ * Possible values:
+ * - null: Startup. Empty cache.
+ * - PhutilURI: Cached value.
+ * - false: Cached no value.
+ * @var PhutilURI|null|false
+ */
+ private static $siteBaseUriObj;
+
+ /**
+ * Constructor by string.
+ * @param string $uri Partial or complete URI.
+ */
+ public function __construct($uri) {
+ $this->uriObj = new PhutilURI($uri);
+ $this->uriOriginalStr = $uri;
+ }
+
+ /**
+ * Get the original URI as string.
+ * @return string
+ */
+ public function getOriginalURI() {
+ return $this->uriOriginalStr;
+ }
+
+ /**
+ * Get the full URI as string.
+ * @return object
+ */
+ public function getURI() {
+ return $this->uriObj;
+ }
+
+ /**
+ * Check whenever an URI *is* just a simple HTML anchor.
+ * @param string $uri
+ * @return bool
+ */
+ public function isAnchor() {
+ return $this->isStartingWithChar('#');
+ }
+
+ /**
+ * Check whenever an URI starts with a slash (no protocol, etc.)
+ * @param string $uri
+ * @return bool
+ */
+ public function isStartingWithSlash() {
+ return $this->isStartingWithChar('/');
+ }
+
+ /**
+ * Check if this URI points to Phorge itself.
+ * This is a simple compromise between performance and a sane logic.
+ * At the moment we don't read the crystal ball, so these are
+ * considered different websites:
+ * - http://example.com (the input)
+ * - https://example.com (the base-uri)
+ * @param string $uri Example 'http://example.com/bar/'
+ * @return bool
+ */
+ public function isInternal() {
+
+ // If this URL has not a domain, indeed its the current domain.
+ // Probably this is a simple anchor, or just '/something/' etc.
+ $uri = $this->uriObj;
+ if ($uri->getProtocol() === '' && $uri->getDomain() === '') {
+ return true;
+ }
+
+ // Assume a safe default in case of missing base URI.
+ $base = self::baseURIObj();
+ if (!$base) {
+ return false;
+ }
+
+ return $uri->getDomain() === $base->getDomain()
+ && $uri->getProtocol() === $base->getProtocol()
+ && $uri->getPort() === $base->getPort();
+ }
+
+ /**
+ * Get a fresh URI object, cached.
+ * This is safe to be called a lot of times.
+ * @return PhutilURI|null
+ */
+ public static function baseURIObj() {
+ if (self::$siteBaseUriObj === null) {
+ self::$siteBaseUriObj = self::baseURIObjUncached();
+ }
+ return self::$siteBaseUriObj;
+ }
+
+ /**
+ * Get a fresh URI object (if any), uncached.
+ * @return PhutilURI|false
+ */
+ private static function baseURIObjUncached() {
+ $base = PhabricatorEnv::getEnvConfigIfExists('phabricator.base-uri');
+ if ($base) {
+ return new PhutilURI($base);
+ }
+ return false;
+ }
+
+ /**
+ * Check whenever the URI starts with the provided character.
+ * @param string $char String that MUST be of length = 1.
+ * @return boolean
+ */
+ private function isStartingWithChar($char) {
+ return strncmp($this->uriOriginalStr, $char, 1) === 0;
+ }
+
+}
diff --git a/src/infrastructure/parser/__tests__/PhutilURIGoodieTestCase.php b/src/infrastructure/parser/__tests__/PhutilURIGoodieTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/parser/__tests__/PhutilURIGoodieTestCase.php
@@ -0,0 +1,34 @@
+<?php
+
+final class PhutilURIGoodieTestCase extends PhabricatorTestCase {
+
+ public function testURIInternal() {
+ $tests = array(
+ // test name, value, expected "is internal?"
+ array('internal anchor', '#asd', true),
+ array('internal relative dir', '/foo/', true),
+ array('internal relative dir also', 'foo/', true),
+ array('internal root dir', '/', true),
+ array('external uri', 'https://gnu.org/', false),
+ );
+
+ // If the base URI is set, also try against ourself.
+ $base_uri =
+ PhabricatorEnv::getEnvConfigIfExists('phabricator.base-uri');
+ if ($base_uri) {
+ // test name, value, expected "is internal?"
+ $tests[] = array('base uri', $base_uri, true);
+ }
+
+ foreach ($tests as $test) {
+ $test_name = $test[0];
+ $test_value = $test[1];
+ $test_expected = $test[2];
+
+ $tested_value = id(new PhutilURIGoodie($test_value))
+ ->isInternal();
+
+ $this->assertEqual($tested_value, $test_expected, $test_name);
+ }
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Tue, Jan 14, 14:29 (3 d, 19 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1114211
Default Alt Text
D25118.1736864958.diff (9 KB)

Event Timeline