[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { 4 5 const KEY_RULE_OBJECT = 'rule.object'; 6 const KEY_MENTIONED_OBJECTS = 'rule.object.mentioned'; 7 8 abstract protected function getObjectNamePrefix(); 9 abstract protected function loadObjects(array $ids); 10 11 public function getPriority() { 12 return 450.0; 13 } 14 15 protected function getObjectNamePrefixBeginsWithWordCharacter() { 16 $prefix = $this->getObjectNamePrefix(); 17 return preg_match('/^\w/', $prefix); 18 } 19 20 protected function getObjectIDPattern() { 21 return '[1-9]\d*'; 22 } 23 24 protected function shouldMarkupObject(array $params) { 25 return true; 26 } 27 28 protected function loadHandles(array $objects) { 29 $phids = mpull($objects, 'getPHID'); 30 31 $handles = id(new PhabricatorHandleQuery($phids)) 32 ->withPHIDs($phids) 33 ->setViewer($this->getEngine()->getConfig('viewer')) 34 ->execute(); 35 36 $result = array(); 37 foreach ($objects as $id => $object) { 38 $result[$id] = $handles[$object->getPHID()]; 39 } 40 return $result; 41 } 42 43 protected function getObjectHref($object, $handle, $id) { 44 return $handle->getURI(); 45 } 46 47 protected function renderObjectRefForAnyMedia ( 48 $object, 49 $handle, 50 $anchor, 51 $id) { 52 $href = $this->getObjectHref($object, $handle, $id); 53 $text = $this->getObjectNamePrefix().$id; 54 55 if ($anchor) { 56 $href = $href.'#'.$anchor; 57 $text = $text.'#'.$anchor; 58 } 59 60 if ($this->getEngine()->isTextMode()) { 61 return PhabricatorEnv::getProductionURI($href); 62 } else if ($this->getEngine()->isHTMLMailMode()) { 63 $href = PhabricatorEnv::getProductionURI($href); 64 return $this->renderObjectTagForMail($text, $href, $handle); 65 } 66 67 return $this->renderObjectRef($object, $handle, $anchor, $id); 68 69 } 70 71 protected function renderObjectRef($object, $handle, $anchor, $id) { 72 $href = $this->getObjectHref($object, $handle, $id); 73 $text = $this->getObjectNamePrefix().$id; 74 $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; 75 76 if ($anchor) { 77 $href = $href.'#'.$anchor; 78 $text = $text.'#'.$anchor; 79 } 80 81 $attr = array( 82 'phid' => $handle->getPHID(), 83 'closed' => ($handle->getStatus() == $status_closed), 84 ); 85 86 return $this->renderHovertag($text, $href, $attr); 87 } 88 89 protected function renderObjectEmbedForAnyMedia($object, $handle, $options) { 90 $name = $handle->getFullName(); 91 $href = $handle->getURI(); 92 93 if ($this->getEngine()->isTextMode()) { 94 return $name.' <'.PhabricatorEnv::getProductionURI($href).'>'; 95 } else if ($this->getEngine()->isHTMLMailMode()) { 96 $href = PhabricatorEnv::getProductionURI($href); 97 return $this->renderObjectTagForMail($name, $href, $handle); 98 } 99 100 return $this->renderObjectEmbed($object, $handle, $options); 101 } 102 103 protected function renderObjectEmbed($object, $handle, $options) { 104 $name = $handle->getFullName(); 105 $href = $handle->getURI(); 106 $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; 107 $attr = array( 108 'phid' => $handle->getPHID(), 109 'closed' => ($handle->getStatus() == $status_closed), 110 ); 111 112 return $this->renderHovertag($name, $href, $attr); 113 } 114 115 protected function renderObjectTagForMail( 116 $text, 117 $href, 118 $handle) { 119 120 $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; 121 $strikethrough = $handle->getStatus() == $status_closed ? 122 'text-decoration: line-through;' : 123 'text-decoration: none;'; 124 125 return phutil_tag( 126 'a', 127 array( 128 'href' => $href, 129 'style' => 'background-color: #e7e7e7; 130 border-color: #e7e7e7; 131 border-radius: 3px; 132 padding: 0 4px; 133 font-weight: bold; 134 color: black;' 135 .$strikethrough, 136 ), 137 $text); 138 } 139 140 protected function renderHovertag($name, $href, array $attr = array()) { 141 return id(new PHUITagView()) 142 ->setName($name) 143 ->setHref($href) 144 ->setType(PHUITagView::TYPE_OBJECT) 145 ->setPHID(idx($attr, 'phid')) 146 ->setClosed(idx($attr, 'closed')) 147 ->render(); 148 } 149 150 public function apply($text) { 151 $text = preg_replace_callback( 152 $this->getObjectEmbedPattern(), 153 array($this, 'markupObjectEmbed'), 154 $text); 155 156 $text = preg_replace_callback( 157 $this->getObjectReferencePattern(), 158 array($this, 'markupObjectReference'), 159 $text); 160 161 return $text; 162 } 163 164 private function getObjectEmbedPattern() { 165 $prefix = $this->getObjectNamePrefix(); 166 $prefix = preg_quote($prefix); 167 $id = $this->getObjectIDPattern(); 168 169 return '(\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B)u'; 170 } 171 172 private function getObjectReferencePattern() { 173 $prefix = $this->getObjectNamePrefix(); 174 $prefix = preg_quote($prefix); 175 176 $id = $this->getObjectIDPattern(); 177 178 // If the prefix starts with a word character (like "D"), we want to 179 // require a word boundary so that we don't match "XD1" as "D1". If the 180 // prefix does not start with a word character, we want to require no word 181 // boundary for the same reasons. Test if the prefix starts with a word 182 // character. 183 if ($this->getObjectNamePrefixBeginsWithWordCharacter()) { 184 $boundary = '\\b'; 185 } else { 186 $boundary = '\\B'; 187 } 188 189 // The "(?<![#-])" prevents us from linking "#abcdef" or similar, and 190 // "ABC-T1" (see T5714). 191 192 // The "\b" allows us to link "(abcdef)" or similar without linking things 193 // in the middle of words. 194 195 return '((?<![#-])'.$boundary.$prefix.'('.$id.')(?:#([-\w\d]+))?(?!\w))u'; 196 } 197 198 199 /** 200 * Extract matched object references from a block of text. 201 * 202 * This is intended to make it easy to write unit tests for object remarkup 203 * rules. Production code is not normally expected to call this method. 204 * 205 * @param string Text to match rules against. 206 * @return wild Matches, suitable for writing unit tests against. 207 */ 208 public function extractReferences($text) { 209 $embed_matches = null; 210 preg_match_all( 211 $this->getObjectEmbedPattern(), 212 $text, 213 $embed_matches, 214 PREG_OFFSET_CAPTURE | PREG_SET_ORDER); 215 216 $ref_matches = null; 217 preg_match_all( 218 $this->getObjectReferencePattern(), 219 $text, 220 $ref_matches, 221 PREG_OFFSET_CAPTURE | PREG_SET_ORDER); 222 223 $results = array(); 224 $sets = array( 225 'embed' => $embed_matches, 226 'ref' => $ref_matches, 227 ); 228 foreach ($sets as $type => $matches) { 229 $formatted = array(); 230 foreach ($matches as $match) { 231 $format = array( 232 'offset' => $match[1][1], 233 'id' => $match[1][0], 234 ); 235 if (isset($match[2][0])) { 236 $format['tail'] = $match[2][0]; 237 } 238 $formatted[] = $format; 239 } 240 $results[$type] = $formatted; 241 } 242 243 return $results; 244 } 245 246 public function markupObjectEmbed($matches) { 247 if (!$this->isFlatText($matches[0])) { 248 return $matches[0]; 249 } 250 251 return $this->markupObject(array( 252 'type' => 'embed', 253 'id' => $matches[1], 254 'options' => idx($matches, 2), 255 'original' => $matches[0], 256 )); 257 } 258 259 public function markupObjectReference($matches) { 260 if (!$this->isFlatText($matches[0])) { 261 return $matches[0]; 262 } 263 264 return $this->markupObject(array( 265 'type' => 'ref', 266 'id' => $matches[1], 267 'anchor' => idx($matches, 2), 268 'original' => $matches[0], 269 )); 270 } 271 272 private function markupObject(array $params) { 273 if (!$this->shouldMarkupObject($params)) { 274 return $params['original']; 275 } 276 277 $regex = trim( 278 PhabricatorEnv::getEnvConfig('remarkup.ignored-object-names')); 279 if ($regex && preg_match($regex, $params['original'])) { 280 return $params['original']; 281 } 282 283 $engine = $this->getEngine(); 284 $token = $engine->storeText('x'); 285 286 $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); 287 $metadata = $engine->getTextMetadata($metadata_key, array()); 288 289 $metadata[] = array( 290 'token' => $token, 291 ) + $params; 292 293 $engine->setTextMetadata($metadata_key, $metadata); 294 295 return $token; 296 } 297 298 public function didMarkupText() { 299 $engine = $this->getEngine(); 300 $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); 301 $metadata = $engine->getTextMetadata($metadata_key, array()); 302 303 if (!$metadata) { 304 return; 305 } 306 307 308 $ids = ipull($metadata, 'id'); 309 $objects = $this->loadObjects($ids); 310 311 // For objects that are invalid or which the user can't see, just render 312 // the original text. 313 314 // TODO: We should probably distinguish between these cases and render a 315 // "you can't see this" state for nonvisible objects. 316 317 foreach ($metadata as $key => $spec) { 318 if (empty($objects[$spec['id']])) { 319 $engine->overwriteStoredText( 320 $spec['token'], 321 $spec['original']); 322 unset($metadata[$key]); 323 } 324 } 325 326 $phids = $engine->getTextMetadata(self::KEY_MENTIONED_OBJECTS, array()); 327 foreach ($objects as $object) { 328 $phids[$object->getPHID()] = $object->getPHID(); 329 } 330 $engine->setTextMetadata(self::KEY_MENTIONED_OBJECTS, $phids); 331 332 $handles = $this->loadHandles($objects); 333 foreach ($metadata as $key => $spec) { 334 $handle = $handles[$spec['id']]; 335 $object = $objects[$spec['id']]; 336 switch ($spec['type']) { 337 case 'ref': 338 339 $view = $this->renderObjectRefForAnyMedia( 340 $object, 341 $handle, 342 $spec['anchor'], 343 $spec['id']); 344 break; 345 case 'embed': 346 $spec['options'] = $this->assertFlatText($spec['options']); 347 $view = $this->renderObjectEmbedForAnyMedia( 348 $object, 349 $handle, 350 $spec['options']); 351 break; 352 } 353 $engine->overwriteStoredText($spec['token'], $view); 354 } 355 356 $engine->setTextMetadata($metadata_key, array()); 357 } 358 359 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |