Remove product literal strings in "pht()", part 18
[phabricator.git] / src / applications / phriction / markup / PhrictionRemarkupRule.php
blob8394b232189cdf2760cb9c888e3e32688398247a
1 <?php
3 final class PhrictionRemarkupRule extends PhutilRemarkupRule {
5 const KEY_RULE_PHRICTION_LINK = 'phriction.link';
7 public function getPriority() {
8 return 175.0;
11 public function apply($text) {
12 return preg_replace_callback(
13 '@\B\\[\\[([^|\\]]+)(?:\\|([^\\]]+))?\\]\\]\B@U',
14 array($this, 'markupDocumentLink'),
15 $text);
18 public function markupDocumentLink(array $matches) {
19 $name = trim(idx($matches, 2, ''));
20 if (empty($matches[2])) {
21 $name = null;
24 $path = trim($matches[1]);
26 if (!$this->isFlatText($name)) {
27 return $matches[0];
30 if (!$this->isFlatText($path)) {
31 return $matches[0];
34 // If the link contains an anchor, separate that off first.
35 $parts = explode('#', $path, 2);
36 if (count($parts) == 2) {
37 $link = $parts[0];
38 $anchor = $parts[1];
39 } else {
40 $link = $parts[0];
41 $anchor = null;
44 // Handle relative links.
45 if ((substr($link, 0, 2) === './') || (substr($link, 0, 3) === '../')) {
46 $base = $this->getRelativeBaseURI();
47 if ($base !== null) {
48 $base_parts = explode('/', rtrim($base, '/'));
49 $rel_parts = explode('/', rtrim($link, '/'));
50 foreach ($rel_parts as $part) {
51 if ($part === '.') {
52 // Consume standalone dots in a relative path, and do
53 // nothing with them.
54 } else if ($part === '..') {
55 if (count($base_parts) > 0) {
56 array_pop($base_parts);
58 } else {
59 array_push($base_parts, $part);
62 $link = implode('/', $base_parts).'/';
66 // Link is now used for slug detection, so append a slash if one
67 // is needed.
68 $link = rtrim($link, '/').'/';
70 $engine = $this->getEngine();
71 $token = $engine->storeText('x');
72 $metadata = $engine->getTextMetadata(
73 self::KEY_RULE_PHRICTION_LINK,
74 array());
75 $metadata[] = array(
76 'token' => $token,
77 'link' => $link,
78 'anchor' => $anchor,
79 'explicitName' => $name,
81 $engine->setTextMetadata(self::KEY_RULE_PHRICTION_LINK, $metadata);
83 return $token;
86 public function didMarkupText() {
87 $engine = $this->getEngine();
88 $metadata = $engine->getTextMetadata(
89 self::KEY_RULE_PHRICTION_LINK,
90 array());
92 if (!$metadata) {
93 return;
96 $viewer = $engine->getConfig('viewer');
98 $slugs = ipull($metadata, 'link');
100 $load_map = array();
101 foreach ($slugs as $key => $raw_slug) {
102 $lookup = PhabricatorSlug::normalize($raw_slug);
103 $load_map[$lookup][] = $key;
105 // Also try to lookup the slug with URL decoding applied. The right
106 // way to link to a page titled "$cash" is to write "[[ $cash ]]" (and
107 // not the URL encoded form "[[ %24cash ]]"), but users may reasonably
108 // have copied URL encoded variations out of their browser location
109 // bar or be skeptical that "[[ $cash ]]" will actually work.
111 $lookup = phutil_unescape_uri_path_component($raw_slug);
112 $lookup = phutil_utf8ize($lookup);
113 $lookup = PhabricatorSlug::normalize($lookup);
114 $load_map[$lookup][] = $key;
117 $visible_documents = id(new PhrictionDocumentQuery())
118 ->setViewer($viewer)
119 ->withSlugs(array_keys($load_map))
120 ->needContent(true)
121 ->execute();
122 $visible_documents = mpull($visible_documents, null, 'getSlug');
123 $document_map = array();
124 foreach ($load_map as $lookup => $keys) {
125 $visible = idx($visible_documents, $lookup);
126 if (!$visible) {
127 continue;
130 foreach ($keys as $key) {
131 $document_map[$key] = array(
132 'visible' => true,
133 'document' => $visible,
137 unset($load_map[$lookup]);
140 // For each document we found, remove all remaining requests for it from
141 // the load map. If we remove all requests for a slug, remove the slug.
142 // This stops us from doing unnecessary lookups on alternate names for
143 // documents we already found.
144 foreach ($load_map as $lookup => $keys) {
145 foreach ($keys as $lookup_key => $key) {
146 if (isset($document_map[$key])) {
147 unset($keys[$lookup_key]);
151 if (!$keys) {
152 unset($load_map[$lookup]);
153 continue;
156 $load_map[$lookup] = $keys;
160 // If we still have links we haven't found a document for, do another
161 // query with the omnipotent viewer so we can distinguish between pages
162 // which do not exist and pages which exist but which the viewer does not
163 // have permission to see.
164 if ($load_map) {
165 $existent_documents = id(new PhrictionDocumentQuery())
166 ->setViewer(PhabricatorUser::getOmnipotentUser())
167 ->withSlugs(array_keys($load_map))
168 ->execute();
169 $existent_documents = mpull($existent_documents, null, 'getSlug');
171 foreach ($load_map as $lookup => $keys) {
172 $existent = idx($existent_documents, $lookup);
173 if (!$existent) {
174 continue;
177 foreach ($keys as $key) {
178 $document_map[$key] = array(
179 'visible' => false,
180 'document' => null,
186 foreach ($metadata as $key => $spec) {
187 $link = $spec['link'];
188 $slug = PhabricatorSlug::normalize($link);
189 $name = $spec['explicitName'];
190 $class = 'phriction-link';
192 // If the name is something meaningful to humans, we'll render this
193 // in text as: "Title" <link>. Otherwise, we'll just render: <link>.
194 $is_interesting_name = (bool)strlen($name);
196 $target = idx($document_map, $key, null);
198 if ($target === null) {
199 // The target document doesn't exist.
200 if ($name === null) {
201 $name = explode('/', trim($link, '/'));
202 $name = end($name);
204 $class = 'phriction-link-missing';
205 } else if (!$target['visible']) {
206 // The document exists, but the user can't see it.
207 if ($name === null) {
208 $name = explode('/', trim($link, '/'));
209 $name = end($name);
211 $class = 'phriction-link-lock';
212 } else {
213 if ($name === null) {
214 // Use the title of the document if no name is set.
215 $name = $target['document']
216 ->getContent()
217 ->getTitle();
219 $is_interesting_name = true;
223 $uri = new PhutilURI($link);
224 $slug = $uri->getPath();
225 $slug = PhabricatorSlug::normalize($slug);
226 $slug = PhrictionDocument::getSlugURI($slug);
228 $anchor = idx($spec, 'anchor');
229 $href = (string)id(new PhutilURI($slug))->setFragment($anchor);
231 $text_mode = $this->getEngine()->isTextMode();
232 $mail_mode = $this->getEngine()->isHTMLMailMode();
234 if ($this->getEngine()->getState('toc')) {
235 $text = $name;
236 } else if ($text_mode || $mail_mode) {
237 $href = PhabricatorEnv::getProductionURI($href);
238 if ($is_interesting_name) {
239 $text = pht('"%s" <%s>', $name, $href);
240 } else {
241 $text = pht('<%s>', $href);
243 } else {
244 if ($class === 'phriction-link-lock') {
245 $name = array(
246 $this->newTag(
247 'i',
248 array(
249 'class' => 'phui-icon-view phui-font-fa fa-lock',
251 ''),
252 ' ',
253 $name,
256 $text = $this->newTag(
257 'a',
258 array(
259 'href' => $href,
260 'class' => $class,
262 $name);
265 $this->getEngine()->overwriteStoredText($spec['token'], $text);
269 private function getRelativeBaseURI() {
270 $context = $this->getEngine()->getConfig('contextObject');
272 if (!$context) {
273 return null;
276 if ($context instanceof PhrictionContent) {
277 return $context->getSlug();
280 if ($context instanceof PhrictionDocument) {
281 return $context->getContent()->getSlug();
284 return null;