@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
at upstream/main 221 lines 5.4 kB view raw
1<?php 2 3final class DiffusionSourceLinkRemarkupRule 4 extends PhutilRemarkupRule { 5 6 const KEY_SOURCELINKS = 'diffusion.links'; 7 8 public function getPriority() { 9 return 200.0; 10 } 11 12 public function apply($text) { 13 return preg_replace_callback( 14 '@{(?:src|source)\b((?:[^}\\\\]+|\\\\.)*)}@m', 15 array($this, 'markupSourceLink'), 16 $text); 17 } 18 19 public function markupSourceLink(array $matches) { 20 $engine = $this->getEngine(); 21 $text_mode = $engine->isTextMode(); 22 $mail_mode = $engine->isHTMLMailMode(); 23 24 if (!$this->isFlatText($matches[0]) || $text_mode || $mail_mode) { 25 // We could do better than this in text mode and mail mode, but focus 26 // on web mode first. 27 return $matches[0]; 28 } 29 30 $metadata_key = self::KEY_SOURCELINKS; 31 $metadata = $engine->getTextMetadata($metadata_key, array()); 32 33 $token = $engine->storeText($matches[0]); 34 35 $metadata[] = array( 36 'token' => $token, 37 'raw' => $matches[0], 38 'input' => $matches[1], 39 ); 40 41 $engine->setTextMetadata($metadata_key, $metadata); 42 43 return $token; 44 } 45 46 public function didMarkupText() { 47 $engine = $this->getEngine(); 48 $metadata_key = self::KEY_SOURCELINKS; 49 $metadata = $engine->getTextMetadata($metadata_key, array()); 50 51 if (!$metadata) { 52 return; 53 } 54 55 $viewer = $engine->getConfig('viewer'); 56 if (!$viewer) { 57 return; 58 } 59 60 $defaults = array( 61 'repository' => null, 62 'line' => null, 63 'commit' => null, 64 'ref' => null, 65 ); 66 67 $tags = array(); 68 foreach ($metadata as $ref) { 69 $token = $ref['token']; 70 $raw = $ref['raw']; 71 $input = $ref['input']; 72 73 $pattern = 74 '(^'. 75 '[\s,]*'. 76 '(?:"(?P<quotedpath>(?:[^\\\\"]+|\\.)+)"|(?P<rawpath>[^\s,]+))'. 77 '[\s,]*'. 78 '(?P<options>.*)'. 79 '\z)'; 80 $matches = null; 81 if (!preg_match($pattern, $input, $matches)) { 82 $hint_text = pht( 83 'Missing path, expected "{src path ...}" in: %s', 84 $raw); 85 $hint = $this->newSyntaxHint($hint_text); 86 87 $engine->overwriteStoredText($token, $hint); 88 continue; 89 } 90 91 $path = idx($matches, 'rawpath'); 92 if (!strlen($path)) { 93 $path = idx($matches, 'quotedpath'); 94 $path = stripcslashes($path); 95 } 96 97 $parts = explode(':', $path, 2); 98 if (count($parts) == 2) { 99 $repository = nonempty($parts[0], null); 100 $path = $parts[1]; 101 } else { 102 $repository = null; 103 $path = $parts[0]; 104 } 105 106 $options = $matches['options']; 107 108 $parser = new PhutilSimpleOptions(); 109 $options = $parser->parse($options) + $defaults; 110 111 foreach ($options as $key => $value) { 112 if (!array_key_exists($key, $defaults)) { 113 $hint_text = pht( 114 'Unknown option "%s" in: %s', 115 $key, 116 $raw); 117 $hint = $this->newSyntaxHint($hint_text); 118 119 $engine->overwriteStoredText($token, $hint); 120 continue 2; 121 } 122 } 123 124 if ($options['repository'] !== null) { 125 $repository = $options['repository']; 126 } 127 128 if ($repository === null) { 129 $hint_text = pht( 130 'Missing repository, expected "{src repository:path ...}" '. 131 'or "{src path repository=...}" in: %s', 132 $raw); 133 $hint = $this->newSyntaxHint($hint_text); 134 135 $engine->overwriteStoredText($token, $hint); 136 continue; 137 } 138 139 $tags[] = array( 140 'token' => $token, 141 'raw' => $raw, 142 'identifier' => $repository, 143 'path' => $path, 144 'options' => $options, 145 ); 146 } 147 148 if (!$tags) { 149 return; 150 } 151 152 $query = id(new PhabricatorRepositoryQuery()) 153 ->setViewer($viewer) 154 ->withIdentifiers(ipull($tags, 'identifier')); 155 156 $query->execute(); 157 158 $repository_map = $query->getIdentifierMap(); 159 160 foreach ($tags as $tag) { 161 $token = $tag['token']; 162 163 $identifier = $tag['identifier']; 164 $repository = idx($repository_map, $identifier); 165 if (!$repository) { 166 // For now, just bail out here. Ideally, we should distingiush between 167 // restricted and invalid repositories. 168 continue; 169 } 170 171 $drequest = DiffusionRequest::newFromDictionary( 172 array( 173 'user' => $viewer, 174 'repository' => $repository, 175 )); 176 177 $options = $tag['options']; 178 179 $line = $options['line']; 180 $commit = $options['commit']; 181 $ref_name = $options['ref']; 182 183 $link_uri = $drequest->generateURI( 184 array( 185 'action' => 'browse', 186 'path' => $tag['path'], 187 'commit' => $commit, 188 'line' => $line, 189 'branch' => $ref_name, 190 )); 191 192 $view = id(new DiffusionSourceLinkView()) 193 ->setRepository($repository) 194 ->setPath($tag['path']) 195 ->setURI($link_uri); 196 197 if ($line !== null) { 198 $view->setLine($line); 199 } 200 201 if ($commit !== null) { 202 $view->setCommit($commit); 203 } 204 205 if ($ref_name !== null) { 206 $view->setRefName($ref_name); 207 } 208 209 $engine->overwriteStoredText($token, $view); 210 } 211 } 212 213 private function newSyntaxHint($text) { 214 return id(new PHUITagView()) 215 ->setType(PHUITagView::TYPE_SHADE) 216 ->setColor('red') 217 ->setIcon('fa-exclamation-triangle') 218 ->setName($text); 219 } 220 221}