Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2871549
D25118.1736864958.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
9 KB
Referenced Files
None
Subscribers
None
D25118.1736864958.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D25118: Remarkup: make less internal links open in new tabs
Attached
Detach File
Event Timeline
Log In to Comment