@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at upstream/main 288 lines 8.1 kB view raw
1<?php 2 3final class PhrictionRemarkupRule extends PhutilRemarkupRule { 4 5 const KEY_RULE_PHRICTION_LINK = 'phriction.link'; 6 7 public function getPriority() { 8 return 175.0; 9 } 10 11 public function apply($text) { 12 return preg_replace_callback( 13 '@\B\\[\\[([^|\\]]+)(?:\\|([^\\]]+))?\\]\\]\B@U', 14 array($this, 'markupDocumentLink'), 15 $text); 16 } 17 18 public function markupDocumentLink(array $matches) { 19 $name = trim(idx($matches, 2, '')); 20 if (empty($matches[2])) { 21 $name = null; 22 } 23 24 $path = trim($matches[1]); 25 26 if (!$this->isFlatText($name)) { 27 return $matches[0]; 28 } 29 30 if (!$this->isFlatText($path)) { 31 return $matches[0]; 32 } 33 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; 42 } 43 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); 57 } 58 } else { 59 array_push($base_parts, $part); 60 } 61 } 62 $link = implode('/', $base_parts).'/'; 63 } 64 } 65 66 // Link is now used for slug detection, so append a slash if one 67 // is needed. 68 $link = rtrim($link, '/').'/'; 69 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, 80 ); 81 $engine->setTextMetadata(self::KEY_RULE_PHRICTION_LINK, $metadata); 82 83 return $token; 84 } 85 86 public function didMarkupText() { 87 $engine = $this->getEngine(); 88 $metadata = $engine->getTextMetadata( 89 self::KEY_RULE_PHRICTION_LINK, 90 array()); 91 92 if (!$metadata) { 93 return; 94 } 95 96 $viewer = $engine->getConfig('viewer'); 97 98 $slugs = ipull($metadata, 'link'); 99 100 $load_map = array(); 101 foreach ($slugs as $key => $raw_slug) { 102 $lookup = PhabricatorSlug::normalize($raw_slug); 103 $load_map[$lookup][] = $key; 104 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. 110 111 $lookup = phutil_unescape_uri_path_component($raw_slug); 112 $lookup = phutil_utf8ize($lookup); 113 $lookup = PhabricatorSlug::normalize($lookup); 114 $load_map[$lookup][] = $key; 115 } 116 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; 128 } 129 130 foreach ($keys as $key) { 131 $document_map[$key] = array( 132 'visible' => true, 133 'document' => $visible, 134 ); 135 } 136 137 unset($load_map[$lookup]); 138 } 139 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]); 148 } 149 } 150 151 if (!$keys) { 152 unset($load_map[$lookup]); 153 continue; 154 } 155 156 $load_map[$lookup] = $keys; 157 } 158 159 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'); 170 171 foreach ($load_map as $lookup => $keys) { 172 $existent = idx($existent_documents, $lookup); 173 if (!$existent) { 174 continue; 175 } 176 177 foreach ($keys as $key) { 178 $document_map[$key] = array( 179 'visible' => false, 180 'document' => null, 181 ); 182 } 183 } 184 } 185 186 foreach ($metadata as $key => $spec) { 187 $link = $spec['link']; 188 $slug = PhabricatorSlug::normalize($link); 189 $name = $spec['explicitName']; 190 $class = 'phriction-link'; 191 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 = phutil_nonempty_string($name); 195 196 $target = idx($document_map, $key, null); 197 198 if ($target === null) { 199 // The target document doesn't exist. 200 if ($name === null) { 201 $name = explode('/', trim($link, '/')); 202 $name = end($name); 203 } 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); 210 } 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(); 218 219 $is_interesting_name = true; 220 } 221 } 222 223 $uri = new PhutilURI($link); 224 $slug = $uri->getPath(); 225 $slug = PhabricatorSlug::normalize($slug); 226 $slug = PhrictionDocument::getSlugURI($slug); 227 228 $anchor = idx($spec, 'anchor'); 229 $href = (string)id(new PhutilURI($slug))->setFragment($anchor); 230 231 $text_mode = $this->getEngine()->isTextMode(); 232 $mail_mode = $this->getEngine()->isHTMLMailMode(); 233 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); 242 } 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', 250 ), 251 ''), 252 ' ', 253 $name, 254 ); 255 } 256 $text = $this->newTag( 257 'a', 258 array( 259 'href' => $href, 260 'class' => $class, 261 ), 262 $name); 263 } 264 265 $this->getEngine()->overwriteStoredText($spec['token'], $text); 266 } 267 } 268 269 private function getRelativeBaseURI() { 270 $context = $this->getEngine()->getConfig('contextObject'); 271 272 if (!$context) { 273 return null; 274 } 275 276 if ($context instanceof PhrictionContent) { 277 return $context->getSlug(); 278 } 279 280 if ($context instanceof PhrictionDocument) { 281 return $context->getContent()->getSlug(); 282 } 283 284 return null; 285 } 286 287 288}