[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/infrastructure/markup/rule/ -> PhabricatorObjectRemarkupRule.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1