Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php
12241 views
1
<?php
2
3
final class PhutilRemarkupDocumentLinkRule extends PhutilRemarkupRule {
4
5
public function getPriority() {
6
return 150.0;
7
}
8
9
public function apply($text) {
10
// Handle mediawiki-style links: [[ href | name ]]
11
$text = preg_replace_callback(
12
'@\B\\[\\[([^|\\]]+)(?:\\|([^\\]]+))?\\]\\]\B@U',
13
array($this, 'markupDocumentLink'),
14
$text);
15
16
// Handle markdown-style links: [name](href)
17
$text = preg_replace_callback(
18
'@'.
19
'\B'.
20
'\\[([^\\]]+)\\]'.
21
'\\('.
22
'(\s*'.
23
// See T12343. This is making some kind of effort to implement
24
// parenthesis balancing rules. It won't get nested parentheses
25
// right, but should do OK for Wikipedia pages, which seem to be
26
// the most important use case.
27
28
// Match zero or more non-parenthesis, non-space characters.
29
'[^\s()]*'.
30
// Match zero or more sequences of "(...)", where two balanced
31
// parentheses enclose zero or more normal characters. If we
32
// match some, optionally match more stuff at the end.
33
'(?:(?:\\([^ ()]*\\))+[^\s()]*)?'.
34
'\s*)'.
35
'\\)'.
36
'\B'.
37
'@U',
38
array($this, 'markupAlternateLink'),
39
$text);
40
41
return $text;
42
}
43
44
protected function renderHyperlink($link, $name) {
45
$engine = $this->getEngine();
46
47
$is_anchor = false;
48
if (strncmp($link, '/', 1) == 0) {
49
$base = phutil_string_cast($engine->getConfig('uri.base'));
50
$base = rtrim($base, '/');
51
$link = $base.$link;
52
} else if (strncmp($link, '#', 1) == 0) {
53
$here = $engine->getConfig('uri.here');
54
$link = $here.$link;
55
56
$is_anchor = true;
57
}
58
59
if ($engine->isTextMode()) {
60
// If present, strip off "mailto:" or "tel:".
61
$link = preg_replace('/^(?:mailto|tel):/', '', $link);
62
63
if (!strlen($name)) {
64
return $link;
65
}
66
67
return $name.' <'.$link.'>';
68
}
69
70
if (!strlen($name)) {
71
$name = $link;
72
$name = preg_replace('/^(?:mailto|tel):/', '', $name);
73
}
74
75
if ($engine->getState('toc')) {
76
return $name;
77
}
78
79
$same_window = $engine->getConfig('uri.same-window', false);
80
if ($same_window) {
81
$target = null;
82
} else {
83
$target = '_blank';
84
}
85
86
// For anchors on the same page, always stay here.
87
if ($is_anchor) {
88
$target = null;
89
}
90
91
return phutil_tag(
92
'a',
93
array(
94
'href' => $link,
95
'class' => 'remarkup-link',
96
'target' => $target,
97
'rel' => 'noreferrer',
98
),
99
$name);
100
}
101
102
public function markupAlternateLink(array $matches) {
103
$uri = trim($matches[2]);
104
105
if (!strlen($uri)) {
106
return $matches[0];
107
}
108
109
// NOTE: We apply some special rules to avoid false positives here. The
110
// major concern is that we do not want to convert `x[0][1](y)` in a
111
// discussion about C source code into a link. To this end, we:
112
//
113
// - Don't match at word boundaries;
114
// - require the URI to contain a "/" character or "@" character; and
115
// - reject URIs which being with a quote character.
116
117
if ($uri[0] == '"' || $uri[0] == "'" || $uri[0] == '`') {
118
return $matches[0];
119
}
120
121
if (strpos($uri, '/') === false &&
122
strpos($uri, '@') === false &&
123
strncmp($uri, 'tel:', 4)) {
124
return $matches[0];
125
}
126
127
return $this->markupDocumentLink(
128
array(
129
$matches[0],
130
$matches[2],
131
$matches[1],
132
));
133
}
134
135
public function markupDocumentLink(array $matches) {
136
$uri = trim($matches[1]);
137
$name = trim(idx($matches, 2, ''));
138
139
if (!$this->isFlatText($uri)) {
140
return $matches[0];
141
}
142
143
if (!$this->isFlatText($name)) {
144
return $matches[0];
145
}
146
147
// If whatever is being linked to begins with "/" or "#", or has "://",
148
// or is "mailto:" or "tel:", treat it as a URI instead of a wiki page.
149
$is_uri = preg_match('@(^/)|(://)|(^#)|(^(?:mailto|tel):)@', $uri);
150
151
if ($is_uri && strncmp('/', $uri, 1) && strncmp('#', $uri, 1)) {
152
$protocols = $this->getEngine()->getConfig(
153
'uri.allowed-protocols',
154
array());
155
156
try {
157
$protocol = id(new PhutilURI($uri))->getProtocol();
158
if (!idx($protocols, $protocol)) {
159
// Don't treat this as a URI if it's not an allowed protocol.
160
$is_uri = false;
161
}
162
} catch (Exception $ex) {
163
// We can end up here if we try to parse an ambiguous URI, see
164
// T12796.
165
$is_uri = false;
166
}
167
}
168
169
// As a special case, skip "[[ / ]]" so that Phriction picks it up as a
170
// link to the Phriction root. It is more useful to be able to use this
171
// syntax to link to the root document than the home page of the install.
172
if ($uri == '/') {
173
$is_uri = false;
174
}
175
176
if (!$is_uri) {
177
return $matches[0];
178
}
179
180
return $this->getEngine()->storeText($this->renderHyperlink($uri, $name));
181
}
182
183
}
184
185