diff --git a/src/applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php b/src/applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php index 51c3602a0d..7952ab6d7d 100644 --- a/src/applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php +++ b/src/applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php @@ -1,26 +1,30 @@ getEngine()->getConfig('viewer'); return id(new PhabricatorCountdownQuery()) ->setViewer($viewer) ->withIDs($ids) ->execute(); } - protected function renderObjectEmbed($object, $handle, $options) { + protected function renderObjectEmbed( + $object, + PhabricatorObjectHandle $handle, + $options) { + $viewer = $this->getEngine()->getConfig('viewer'); return id(new PhabricatorCountdownView()) ->setCountdown($object) ->setUser($viewer); } } diff --git a/src/applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php b/src/applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php index 4ec24724fc..9870795f25 100644 --- a/src/applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php +++ b/src/applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php @@ -1,30 +1,33 @@ getEngine()->getConfig('viewer'); return id(new PhabricatorDashboardPanelQuery()) ->setViewer($viewer) ->withIDs($ids) ->execute(); - } - protected function renderObjectEmbed($object, $handle, $options) { + protected function renderObjectEmbed( + $object, + PhabricatorObjectHandle $handle, + $options) { + $viewer = $this->getEngine()->getConfig('viewer'); return id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setPanel($object) ->setParentPanelPHIDs(array()) ->renderPanel(); } } diff --git a/src/applications/diviner/markup/DivinerSymbolRemarkupRule.php b/src/applications/diviner/markup/DivinerSymbolRemarkupRule.php index bb4fafd11e..7fdb4cb37e 100644 --- a/src/applications/diviner/markup/DivinerSymbolRemarkupRule.php +++ b/src/applications/diviner/markup/DivinerSymbolRemarkupRule.php @@ -1,167 +1,167 @@ [^:]+?):)?'. '(?P[^}|]+?)'. '(?:[|](?P[^}]+))?'. '}/', array($this, 'markupSymbol'), $text); } - public function markupSymbol($matches) { + public function markupSymbol(array $matches) { if (!$this->isFlatText($matches[0])) { return $matches[0]; } $type = (string)idx($matches, 'type'); $name = (string)$matches['name']; $title = (string)idx($matches, 'title'); // Collapse sequences of whitespace into a single space. $type = preg_replace('/\s+/', ' ', trim($type)); $name = preg_replace('/\s+/', ' ', trim($name)); $title = preg_replace('/\s+/', ' ', trim($title)); $ref = array(); if (strpos($type, '@') !== false) { list($type, $book) = explode('@', $type, 2); $ref['type'] = trim($type); $ref['book'] = trim($book); } else { $ref['type'] = $type; } if (strpos($name, '@') !== false) { list($name, $context) = explode('@', $name, 2); $ref['name'] = trim($name); $ref['context'] = trim($context); } else { $ref['name'] = $name; } $ref['title'] = nonempty($title, $name); foreach ($ref as $key => $value) { if ($value === '') { unset($ref[$key]); } } $engine = $this->getEngine(); $token = $engine->storeText(''); $key = self::KEY_RULE_ATOM_REF; $data = $engine->getTextMetadata($key, array()); $data[$token] = $ref; $engine->setTextMetadata($key, $data); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $key = self::KEY_RULE_ATOM_REF; $data = $engine->getTextMetadata($key, array()); $renderer = $engine->getConfig('diviner.renderer'); foreach ($data as $token => $ref_dict) { $ref = DivinerAtomRef::newFromDictionary($ref_dict); $title = $ref->getTitle(); $href = null; if ($renderer) { // Here, we're generating documentation. If possible, we want to find // the real atom ref so we can render the correct default title and // render invalid links in an alternate style. $ref = $renderer->normalizeAtomRef($ref); if ($ref) { $title = nonempty($ref->getTitle(), $ref->getName()); $href = $renderer->getHrefForAtomRef($ref); } } else { // Here, we're generating comment text or something like that. Just // link to Diviner and let it sort things out. $href = id(new PhutilURI('/diviner/find/')) ->setQueryParams( array( 'book' => $ref->getBook(), 'name' => $ref->getName(), 'type' => $ref->getType(), 'context' => $ref->getContext(), 'jump' => true, )); } // TODO: This probably is not the best place to do this. Move it somewhere // better when it becomes more clear where it should actually go. if ($ref) { switch ($ref->getType()) { case 'function': case 'method': $title = $title.'()'; break; } } if ($this->getEngine()->isTextMode()) { if ($href) { $link = $title.' <'.PhabricatorEnv::getProductionURI($href).'>'; } else { $link = $title; } } else if ($href) { if ($this->getEngine()->isHTMLMailMode()) { $href = PhabricatorEnv::getProductionURI($href); } $link = $this->newTag( 'a', array( 'class' => 'atom-ref', 'href' => $href, ), $title); } else { $link = $this->newTag( 'span', array( 'class' => 'atom-ref-invalid', ), $title); } $engine->overwriteStoredText($token, $link); } } } diff --git a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php index 2572c440b7..fe0bb1da7a 100644 --- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php +++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php @@ -1,224 +1,228 @@ <?php final class PhabricatorEmbedFileRemarkupRule extends PhabricatorObjectRemarkupRule { const KEY_EMBED_FILE_PHIDS = 'phabricator.embedded-file-phids'; protected function getObjectNamePrefix() { return 'F'; } protected function loadObjects(array $ids) { $engine = $this->getEngine(); $viewer = $engine->getConfig('viewer'); $objects = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withIDs($ids) ->execute(); $phids_key = self::KEY_EMBED_FILE_PHIDS; $phids = $engine->getTextMetadata($phids_key, array()); foreach (mpull($objects, 'getPHID') as $phid) { $phids[] = $phid; } $engine->setTextMetadata($phids_key, $phids); return $objects; } - protected function renderObjectEmbed($object, $handle, $options) { + protected function renderObjectEmbed( + $object, + PhabricatorObjectHandle $handle, + $options) { + $options = $this->getFileOptions($options) + array( 'name' => $object->getName(), ); $is_viewable_image = $object->isViewableImage(); $is_audio = $object->isAudio(); $force_link = ($options['layout'] == 'link'); $options['viewable'] = ($is_viewable_image || $is_audio); if ($is_viewable_image && !$force_link) { return $this->renderImageFile($object, $handle, $options); } else if ($is_audio && !$force_link) { return $this->renderAudioFile($object, $handle, $options); } else { return $this->renderFileLink($object, $handle, $options); } } private function getFileOptions($option_string) { $options = array( 'size' => null, 'layout' => 'left', 'float' => false, 'width' => null, 'height' => null, 'alt' => null, ); if ($option_string) { $option_string = trim($option_string, ', '); $parser = new PhutilSimpleOptions(); $options = $parser->parse($option_string) + $options; } return $options; } private function renderImageFile( PhabricatorFile $file, PhabricatorObjectHandle $handle, array $options) { require_celerity_resource('lightbox-attachment-css'); $attrs = array(); $image_class = null; $use_size = true; if (!$options['size']) { $width = $this->parseDimension($options['width']); $height = $this->parseDimension($options['height']); if ($width || $height) { $use_size = false; $attrs += array( 'src' => $file->getBestURI(), 'width' => $width, 'height' => $height, ); } } if ($use_size) { switch ((string)$options['size']) { case 'full': $attrs += array( 'src' => $file->getBestURI(), 'height' => $file->getImageHeight(), 'width' => $file->getImageWidth(), ); $image_class = 'phabricator-remarkup-embed-image-full'; break; case 'thumb': default: $attrs['src'] = $file->getPreview220URI(); $dimensions = PhabricatorImageTransformer::getPreviewDimensions($file, 220); $attrs['width'] = $dimensions['sdx']; $attrs['height'] = $dimensions['sdy']; $image_class = 'phabricator-remarkup-embed-image'; break; } } if (isset($options['alt'])) { $attrs['alt'] = $options['alt']; } $img = phutil_tag('img', $attrs); $embed = javelin_tag( 'a', array( 'href' => $file->getBestURI(), 'class' => $image_class, 'sigil' => 'lightboxable', 'meta' => array( 'phid' => $file->getPHID(), 'uri' => $file->getBestURI(), 'dUri' => $file->getDownloadURI(), 'viewable' => true, ), ), $img); switch ($options['layout']) { case 'right': case 'center': case 'inline': case 'left': $layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout']; break; default: $layout_class = 'phabricator-remarkup-embed-layout-left'; break; } if ($options['float']) { switch ($options['layout']) { case 'center': case 'inline': break; case 'right': $layout_class .= ' phabricator-remarkup-embed-float-right'; break; case 'left': default: $layout_class .= ' phabricator-remarkup-embed-float-left'; break; } } return phutil_tag( ($options['layout'] == 'inline' ? 'span' : 'div'), array( 'class' => $layout_class, ), $embed); } private function renderAudioFile( PhabricatorFile $file, PhabricatorObjectHandle $handle, array $options) { if (idx($options, 'autoplay')) { $preload = 'auto'; $autoplay = 'autoplay'; } else { $preload = 'none'; $autoplay = null; } return $this->newTag( 'audio', array( 'controls' => 'controls', 'preload' => $preload, 'autoplay' => $autoplay, 'loop' => idx($options, 'loop') ? 'loop' : null, ), $this->newTag( 'source', array( 'src' => $file->getBestURI(), 'type' => $file->getMimeType(), ))); } private function renderFileLink( PhabricatorFile $file, PhabricatorObjectHandle $handle, array $options) { return id(new PhabricatorFileLinkView()) ->setFilePHID($file->getPHID()) ->setFileName($this->assertFlatText($options['name'])) ->setFileDownloadURI($file->getDownloadURI()) ->setFileViewURI($file->getBestURI()) ->setFileViewable((bool)$options['viewable']); } private function parseDimension($string) { $string = trim($string); if (preg_match('/^(?:\d*\\.)?\d+%?$/', $string)) { return $string; } return null; } } diff --git a/src/applications/macro/markup/PhabricatorEmojiRemarkupRule.php b/src/applications/macro/markup/PhabricatorEmojiRemarkupRule.php index b1e24c23ff..9bb8d7f9f4 100644 --- a/src/applications/macro/markup/PhabricatorEmojiRemarkupRule.php +++ b/src/applications/macro/markup/PhabricatorEmojiRemarkupRule.php @@ -1,903 +1,903 @@ <?php final class PhabricatorEmojiRemarkupRule extends PhutilRemarkupRule { public function getPriority() { return 200.0; } public function apply($text) { return preg_replace_callback( '(\B:(\S+):\B)', array($this, 'markupEmoji'), $text); } - public function markupEmoji($matches) { + public function markupEmoji(array $matches) { if (!$this->isFlatText($matches[0])) { return $matches[0]; } static $map = array( 'watch' => "\xE2\x8C\x9A", 'hourglass' => "\xE2\x8C\x9B", 'fast_forward' => "\xE2\x8F\xA9", 'rewind' => "\xE2\x8F\xAA", 'arrow_double_up' => "\xE2\x8F\xAB", 'arrow_double_down' => "\xE2\x8F\xAC", 'alarm_clock' => "\xE2\x8F\xB0", 'hourglass_flowing_sand' => "\xE2\x8F\xB3", 'white_medium_small_square' => "\xE2\x97\xBD", 'black_medium_small_square' => "\xE2\x97\xBE", 'umbrella' => "\xE2\x98\x94", 'coffee' => "\xE2\x98\x95", 'aries' => "\xE2\x99\x88", 'taurus' => "\xE2\x99\x89", 'gemini' => "\xE2\x99\x8A", 'cancer' => "\xE2\x99\x8B", 'leo' => "\xE2\x99\x8C", 'virgo' => "\xE2\x99\x8D", 'libra' => "\xE2\x99\x8E", 'scorpius' => "\xE2\x99\x8F", 'sagittarius' => "\xE2\x99\x90", 'capricorn' => "\xE2\x99\x91", 'aquarius' => "\xE2\x99\x92", 'pisces' => "\xE2\x99\x93", 'wheelchair' => "\xE2\x99\xBF", 'anchor' => "\xE2\x9A\x93", 'zap' => "\xE2\x9A\xA1", 'white_circle' => "\xE2\x9A\xAA", 'black_circle' => "\xE2\x9A\xAB", 'soccer' => "\xE2\x9A\xBD", 'snowman' => "\xE2\x9B\x84", 'partly_sunny' => "\xE2\x9B\x85", 'ophiuchus' => "\xE2\x9B\x8E", 'no_entry' => "\xE2\x9B\x94", 'church' => "\xE2\x9B\xAA", 'fountain' => "\xE2\x9B\xB2", 'golf' => "\xE2\x9B\xB3", 'sailboat' => "\xE2\x9B\xB5", 'boat' => "\xE2\x9B\xB5", 'tent' => "\xE2\x9B\xBA", 'fuelpump' => "\xE2\x9B\xBD", 'white_check_mark' => "\xE2\x9C\x85", 'fist' => "\xE2\x9C\x8A", 'hand' => "\xE2\x9C\x8B", 'raised_hand' => "\xE2\x9C\x8B", 'sparkles' => "\xE2\x9C\xA8", 'x' => "\xE2\x9D\x8C", 'negative_squared_cross_mark' => "\xE2\x9D\x8E", 'question' => "\xE2\x9D\x93", 'grey_question' => "\xE2\x9D\x94", 'grey_exclamation' => "\xE2\x9D\x95", 'exclamation' => "\xE2\x9D\x97", 'heavy_exclamation_mark' => "\xE2\x9D\x97", 'heavy_plus_sign' => "\xE2\x9E\x95", 'heavy_minus_sign' => "\xE2\x9E\x96", 'heavy_division_sign' => "\xE2\x9E\x97", 'curly_loop' => "\xE2\x9E\xB0", 'loop' => "\xE2\x9E\xBF", 'black_large_square' => "\xE2\xAC\x9B", 'white_large_square' => "\xE2\xAC\x9C", 'star' => "\xE2\xAD\x90", 'o' => "\xE2\xAD\x95", 'mahjong' => "\xF0\x9F\x80\x84", 'black_joker' => "\xF0\x9F\x83\x8F", 'ab' => "\xF0\x9F\x86\x8E", 'cl' => "\xF0\x9F\x86\x91", 'cool' => "\xF0\x9F\x86\x92", 'free' => "\xF0\x9F\x86\x93", 'id' => "\xF0\x9F\x86\x94", 'new' => "\xF0\x9F\x86\x95", 'ng' => "\xF0\x9F\x86\x96", 'ok' => "\xF0\x9F\x86\x97", 'sos' => "\xF0\x9F\x86\x98", 'up' => "\xF0\x9F\x86\x99", 'vs' => "\xF0\x9F\x86\x9A", 'koko' => "\xF0\x9F\x88\x81", 'u7121' => "\xF0\x9F\x88\x9A", 'u6307' => "\xF0\x9F\x88\xAF", 'u7981' => "\xF0\x9F\x88\xB2", 'u7a7a' => "\xF0\x9F\x88\xB3", 'u5408' => "\xF0\x9F\x88\xB4", 'u6e80' => "\xF0\x9F\x88\xB5", 'u6709' => "\xF0\x9F\x88\xB6", 'u7533' => "\xF0\x9F\x88\xB8", 'u5272' => "\xF0\x9F\x88\xB9", 'u55b6' => "\xF0\x9F\x88\xBA", 'ideograph_advantage' => "\xF0\x9F\x89\x90", 'accept' => "\xF0\x9F\x89\x91", 'cyclone' => "\xF0\x9F\x8C\x80", 'foggy' => "\xF0\x9F\x8C\x81", 'closed_umbrella' => "\xF0\x9F\x8C\x82", 'night_with_stars' => "\xF0\x9F\x8C\x83", 'sunrise_over_mountains' => "\xF0\x9F\x8C\x84", 'sunrise' => "\xF0\x9F\x8C\x85", 'city_sunset' => "\xF0\x9F\x8C\x86", 'city_sunrise' => "\xF0\x9F\x8C\x87", 'rainbow' => "\xF0\x9F\x8C\x88", 'bridge_at_night' => "\xF0\x9F\x8C\x89", 'ocean' => "\xF0\x9F\x8C\x8A", 'volcano' => "\xF0\x9F\x8C\x8B", 'milky_way' => "\xF0\x9F\x8C\x8C", 'earth_africa' => "\xF0\x9F\x8C\x8D", 'earth_americas' => "\xF0\x9F\x8C\x8E", 'earth_asia' => "\xF0\x9F\x8C\x8F", 'globe_with_meridians' => "\xF0\x9F\x8C\x90", 'new_moon' => "\xF0\x9F\x8C\x91", 'waxing_crescent_moon' => "\xF0\x9F\x8C\x92", 'first_quarter_moon' => "\xF0\x9F\x8C\x93", 'waxing_gibbous_moon' => "\xF0\x9F\x8C\x94", 'moon' => "\xF0\x9F\x8C\x94", 'full_moon' => "\xF0\x9F\x8C\x95", 'waning_gibbous_moon' => "\xF0\x9F\x8C\x96", 'last_quarter_moon' => "\xF0\x9F\x8C\x97", 'waning_crescent_moon' => "\xF0\x9F\x8C\x98", 'crescent_moon' => "\xF0\x9F\x8C\x99", 'new_moon_with_face' => "\xF0\x9F\x8C\x9A", 'first_quarter_moon_with_face' => "\xF0\x9F\x8C\x9B", 'last_quarter_moon_with_face' => "\xF0\x9F\x8C\x9C", 'full_moon_with_face' => "\xF0\x9F\x8C\x9D", 'sun_with_face' => "\xF0\x9F\x8C\x9E", 'star2' => "\xF0\x9F\x8C\x9F", 'stars' => "\xF0\x9F\x8C\xA0", 'chestnut' => "\xF0\x9F\x8C\xB0", 'seedling' => "\xF0\x9F\x8C\xB1", 'evergreen_tree' => "\xF0\x9F\x8C\xB2", 'deciduous_tree' => "\xF0\x9F\x8C\xB3", 'palm_tree' => "\xF0\x9F\x8C\xB4", 'cactus' => "\xF0\x9F\x8C\xB5", 'tulip' => "\xF0\x9F\x8C\xB7", 'cherry_blossom' => "\xF0\x9F\x8C\xB8", 'rose' => "\xF0\x9F\x8C\xB9", 'hibiscus' => "\xF0\x9F\x8C\xBA", 'sunflower' => "\xF0\x9F\x8C\xBB", 'blossom' => "\xF0\x9F\x8C\xBC", 'corn' => "\xF0\x9F\x8C\xBD", 'ear_of_rice' => "\xF0\x9F\x8C\xBE", 'herb' => "\xF0\x9F\x8C\xBF", 'four_leaf_clover' => "\xF0\x9F\x8D\x80", 'maple_leaf' => "\xF0\x9F\x8D\x81", 'fallen_leaf' => "\xF0\x9F\x8D\x82", 'leaves' => "\xF0\x9F\x8D\x83", 'mushroom' => "\xF0\x9F\x8D\x84", 'tomato' => "\xF0\x9F\x8D\x85", 'eggplant' => "\xF0\x9F\x8D\x86", 'grapes' => "\xF0\x9F\x8D\x87", 'melon' => "\xF0\x9F\x8D\x88", 'watermelon' => "\xF0\x9F\x8D\x89", 'tangerine' => "\xF0\x9F\x8D\x8A", 'lemon' => "\xF0\x9F\x8D\x8B", 'banana' => "\xF0\x9F\x8D\x8C", 'pineapple' => "\xF0\x9F\x8D\x8D", 'apple' => "\xF0\x9F\x8D\x8E", 'green_apple' => "\xF0\x9F\x8D\x8F", 'pear' => "\xF0\x9F\x8D\x90", 'peach' => "\xF0\x9F\x8D\x91", 'cherries' => "\xF0\x9F\x8D\x92", 'strawberry' => "\xF0\x9F\x8D\x93", 'hamburger' => "\xF0\x9F\x8D\x94", 'pizza' => "\xF0\x9F\x8D\x95", 'meat_on_bone' => "\xF0\x9F\x8D\x96", 'poultry_leg' => "\xF0\x9F\x8D\x97", 'rice_cracker' => "\xF0\x9F\x8D\x98", 'rice_ball' => "\xF0\x9F\x8D\x99", 'rice' => "\xF0\x9F\x8D\x9A", 'curry' => "\xF0\x9F\x8D\x9B", 'ramen' => "\xF0\x9F\x8D\x9C", 'spaghetti' => "\xF0\x9F\x8D\x9D", 'bread' => "\xF0\x9F\x8D\x9E", 'fries' => "\xF0\x9F\x8D\x9F", 'sweet_potato' => "\xF0\x9F\x8D\xA0", 'dango' => "\xF0\x9F\x8D\xA1", 'oden' => "\xF0\x9F\x8D\xA2", 'sushi' => "\xF0\x9F\x8D\xA3", 'fried_shrimp' => "\xF0\x9F\x8D\xA4", 'fish_cake' => "\xF0\x9F\x8D\xA5", 'icecream' => "\xF0\x9F\x8D\xA6", 'shaved_ice' => "\xF0\x9F\x8D\xA7", 'ice_cream' => "\xF0\x9F\x8D\xA8", 'doughnut' => "\xF0\x9F\x8D\xA9", 'cookie' => "\xF0\x9F\x8D\xAA", 'chocolate_bar' => "\xF0\x9F\x8D\xAB", 'candy' => "\xF0\x9F\x8D\xAC", 'lollipop' => "\xF0\x9F\x8D\xAD", 'custard' => "\xF0\x9F\x8D\xAE", 'honey_pot' => "\xF0\x9F\x8D\xAF", 'cake' => "\xF0\x9F\x8D\xB0", 'bento' => "\xF0\x9F\x8D\xB1", 'stew' => "\xF0\x9F\x8D\xB2", 'egg' => "\xF0\x9F\x8D\xB3", 'fork_and_knife' => "\xF0\x9F\x8D\xB4", 'tea' => "\xF0\x9F\x8D\xB5", 'sake' => "\xF0\x9F\x8D\xB6", 'wine_glass' => "\xF0\x9F\x8D\xB7", 'cocktail' => "\xF0\x9F\x8D\xB8", 'tropical_drink' => "\xF0\x9F\x8D\xB9", 'beer' => "\xF0\x9F\x8D\xBA", 'beers' => "\xF0\x9F\x8D\xBB", 'baby_bottle' => "\xF0\x9F\x8D\xBC", 'ribbon' => "\xF0\x9F\x8E\x80", 'gift' => "\xF0\x9F\x8E\x81", 'birthday' => "\xF0\x9F\x8E\x82", 'jack_o_lantern' => "\xF0\x9F\x8E\x83", 'christmas_tree' => "\xF0\x9F\x8E\x84", 'santa' => "\xF0\x9F\x8E\x85", 'fireworks' => "\xF0\x9F\x8E\x86", 'sparkler' => "\xF0\x9F\x8E\x87", 'balloon' => "\xF0\x9F\x8E\x88", 'tada' => "\xF0\x9F\x8E\x89", 'confetti_ball' => "\xF0\x9F\x8E\x8A", 'tanabata_tree' => "\xF0\x9F\x8E\x8B", 'crossed_flags' => "\xF0\x9F\x8E\x8C", 'bamboo' => "\xF0\x9F\x8E\x8D", 'dolls' => "\xF0\x9F\x8E\x8E", 'flags' => "\xF0\x9F\x8E\x8F", 'wind_chime' => "\xF0\x9F\x8E\x90", 'rice_scene' => "\xF0\x9F\x8E\x91", 'school_satchel' => "\xF0\x9F\x8E\x92", 'mortar_board' => "\xF0\x9F\x8E\x93", 'carousel_horse' => "\xF0\x9F\x8E\xA0", 'ferris_wheel' => "\xF0\x9F\x8E\xA1", 'roller_coaster' => "\xF0\x9F\x8E\xA2", 'fishing_pole_and_fish' => "\xF0\x9F\x8E\xA3", 'microphone' => "\xF0\x9F\x8E\xA4", 'movie_camera' => "\xF0\x9F\x8E\xA5", 'cinema' => "\xF0\x9F\x8E\xA6", 'headphones' => "\xF0\x9F\x8E\xA7", 'art' => "\xF0\x9F\x8E\xA8", 'tophat' => "\xF0\x9F\x8E\xA9", 'circus_tent' => "\xF0\x9F\x8E\xAA", 'ticket' => "\xF0\x9F\x8E\xAB", 'clapper' => "\xF0\x9F\x8E\xAC", 'performing_arts' => "\xF0\x9F\x8E\xAD", 'video_game' => "\xF0\x9F\x8E\xAE", 'dart' => "\xF0\x9F\x8E\xAF", 'slot_machine' => "\xF0\x9F\x8E\xB0", '8ball' => "\xF0\x9F\x8E\xB1", 'game_die' => "\xF0\x9F\x8E\xB2", 'bowling' => "\xF0\x9F\x8E\xB3", 'flower_playing_cards' => "\xF0\x9F\x8E\xB4", 'musical_note' => "\xF0\x9F\x8E\xB5", 'notes' => "\xF0\x9F\x8E\xB6", 'saxophone' => "\xF0\x9F\x8E\xB7", 'guitar' => "\xF0\x9F\x8E\xB8", 'musical_keyboard' => "\xF0\x9F\x8E\xB9", 'trumpet' => "\xF0\x9F\x8E\xBA", 'violin' => "\xF0\x9F\x8E\xBB", 'musical_score' => "\xF0\x9F\x8E\xBC", 'running_shirt_with_sash' => "\xF0\x9F\x8E\xBD", 'tennis' => "\xF0\x9F\x8E\xBE", 'ski' => "\xF0\x9F\x8E\xBF", 'basketball' => "\xF0\x9F\x8F\x80", 'checkered_flag' => "\xF0\x9F\x8F\x81", 'snowboarder' => "\xF0\x9F\x8F\x82", 'runner' => "\xF0\x9F\x8F\x83", 'running' => "\xF0\x9F\x8F\x83", 'surfer' => "\xF0\x9F\x8F\x84", 'trophy' => "\xF0\x9F\x8F\x86", 'horse_racing' => "\xF0\x9F\x8F\x87", 'football' => "\xF0\x9F\x8F\x88", 'rugby_football' => "\xF0\x9F\x8F\x89", 'swimmer' => "\xF0\x9F\x8F\x8A", 'house' => "\xF0\x9F\x8F\xA0", 'house_with_garden' => "\xF0\x9F\x8F\xA1", 'office' => "\xF0\x9F\x8F\xA2", 'post_office' => "\xF0\x9F\x8F\xA3", 'european_post_office' => "\xF0\x9F\x8F\xA4", 'hospital' => "\xF0\x9F\x8F\xA5", 'bank' => "\xF0\x9F\x8F\xA6", 'atm' => "\xF0\x9F\x8F\xA7", 'hotel' => "\xF0\x9F\x8F\xA8", 'love_hotel' => "\xF0\x9F\x8F\xA9", 'convenience_store' => "\xF0\x9F\x8F\xAA", 'school' => "\xF0\x9F\x8F\xAB", 'department_store' => "\xF0\x9F\x8F\xAC", 'factory' => "\xF0\x9F\x8F\xAD", 'lantern' => "\xF0\x9F\x8F\xAE", 'izakaya_lantern' => "\xF0\x9F\x8F\xAE", 'japanese_castle' => "\xF0\x9F\x8F\xAF", 'european_castle' => "\xF0\x9F\x8F\xB0", 'rat' => "\xF0\x9F\x90\x80", 'mouse2' => "\xF0\x9F\x90\x81", 'ox' => "\xF0\x9F\x90\x82", 'water_buffalo' => "\xF0\x9F\x90\x83", 'cow2' => "\xF0\x9F\x90\x84", 'tiger2' => "\xF0\x9F\x90\x85", 'leopard' => "\xF0\x9F\x90\x86", 'rabbit2' => "\xF0\x9F\x90\x87", 'cat2' => "\xF0\x9F\x90\x88", 'dragon' => "\xF0\x9F\x90\x89", 'crocodile' => "\xF0\x9F\x90\x8A", 'whale2' => "\xF0\x9F\x90\x8B", 'snail' => "\xF0\x9F\x90\x8C", 'snake' => "\xF0\x9F\x90\x8D", 'racehorse' => "\xF0\x9F\x90\x8E", 'ram' => "\xF0\x9F\x90\x8F", 'goat' => "\xF0\x9F\x90\x90", 'sheep' => "\xF0\x9F\x90\x91", 'monkey' => "\xF0\x9F\x90\x92", 'rooster' => "\xF0\x9F\x90\x93", 'chicken' => "\xF0\x9F\x90\x94", 'dog2' => "\xF0\x9F\x90\x95", 'pig2' => "\xF0\x9F\x90\x96", 'boar' => "\xF0\x9F\x90\x97", 'elephant' => "\xF0\x9F\x90\x98", 'octopus' => "\xF0\x9F\x90\x99", 'shell' => "\xF0\x9F\x90\x9A", 'bug' => "\xF0\x9F\x90\x9B", 'ant' => "\xF0\x9F\x90\x9C", 'bee' => "\xF0\x9F\x90\x9D", 'honeybee' => "\xF0\x9F\x90\x9D", 'beetle' => "\xF0\x9F\x90\x9E", 'fish' => "\xF0\x9F\x90\x9F", 'tropical_fish' => "\xF0\x9F\x90\xA0", 'blowfish' => "\xF0\x9F\x90\xA1", 'turtle' => "\xF0\x9F\x90\xA2", 'hatching_chick' => "\xF0\x9F\x90\xA3", 'baby_chick' => "\xF0\x9F\x90\xA4", 'hatched_chick' => "\xF0\x9F\x90\xA5", 'bird' => "\xF0\x9F\x90\xA6", 'penguin' => "\xF0\x9F\x90\xA7", 'koala' => "\xF0\x9F\x90\xA8", 'poodle' => "\xF0\x9F\x90\xA9", 'dromedary_camel' => "\xF0\x9F\x90\xAA", 'camel' => "\xF0\x9F\x90\xAB", 'dolphin' => "\xF0\x9F\x90\xAC", 'flipper' => "\xF0\x9F\x90\xAC", 'mouse' => "\xF0\x9F\x90\xAD", 'cow' => "\xF0\x9F\x90\xAE", 'tiger' => "\xF0\x9F\x90\xAF", 'rabbit' => "\xF0\x9F\x90\xB0", 'cat' => "\xF0\x9F\x90\xB1", 'dragon_face' => "\xF0\x9F\x90\xB2", 'whale' => "\xF0\x9F\x90\xB3", 'horse' => "\xF0\x9F\x90\xB4", 'monkey_face' => "\xF0\x9F\x90\xB5", 'dog' => "\xF0\x9F\x90\xB6", 'pig' => "\xF0\x9F\x90\xB7", 'frog' => "\xF0\x9F\x90\xB8", 'hamster' => "\xF0\x9F\x90\xB9", 'wolf' => "\xF0\x9F\x90\xBA", 'bear' => "\xF0\x9F\x90\xBB", 'panda_face' => "\xF0\x9F\x90\xBC", 'pig_nose' => "\xF0\x9F\x90\xBD", 'paw_prints' => "\xF0\x9F\x90\xBE", 'feet' => "\xF0\x9F\x90\xBE", 'eyes' => "\xF0\x9F\x91\x80", 'ear' => "\xF0\x9F\x91\x82", 'nose' => "\xF0\x9F\x91\x83", 'lips' => "\xF0\x9F\x91\x84", 'tongue' => "\xF0\x9F\x91\x85", 'point_up_2' => "\xF0\x9F\x91\x86", 'point_down' => "\xF0\x9F\x91\x87", 'point_left' => "\xF0\x9F\x91\x88", 'point_right' => "\xF0\x9F\x91\x89", 'punch' => "\xF0\x9F\x91\x8A", 'facepunch' => "\xF0\x9F\x91\x8A", 'wave' => "\xF0\x9F\x91\x8B", 'ok_hand' => "\xF0\x9F\x91\x8C", '+1' => "\xF0\x9F\x91\x8D", 'thumbsup' => "\xF0\x9F\x91\x8D", '_1' => "\xF0\x9F\x91\x8E", 'thumbsdown' => "\xF0\x9F\x91\x8E", 'clap' => "\xF0\x9F\x91\x8F", 'open_hands' => "\xF0\x9F\x91\x90", 'crown' => "\xF0\x9F\x91\x91", 'womans_hat' => "\xF0\x9F\x91\x92", 'eyeglasses' => "\xF0\x9F\x91\x93", 'necktie' => "\xF0\x9F\x91\x94", 'tshirt' => "\xF0\x9F\x91\x95", 'shirt' => "\xF0\x9F\x91\x95", 'jeans' => "\xF0\x9F\x91\x96", 'dress' => "\xF0\x9F\x91\x97", 'kimono' => "\xF0\x9F\x91\x98", 'bikini' => "\xF0\x9F\x91\x99", 'womans_clothes' => "\xF0\x9F\x91\x9A", 'purse' => "\xF0\x9F\x91\x9B", 'handbag' => "\xF0\x9F\x91\x9C", 'pouch' => "\xF0\x9F\x91\x9D", 'mans_shoe' => "\xF0\x9F\x91\x9E", 'shoe' => "\xF0\x9F\x91\x9E", 'athletic_shoe' => "\xF0\x9F\x91\x9F", 'high_heel' => "\xF0\x9F\x91\xA0", 'sandal' => "\xF0\x9F\x91\xA1", 'boot' => "\xF0\x9F\x91\xA2", 'footprints' => "\xF0\x9F\x91\xA3", 'bust_in_silhouette' => "\xF0\x9F\x91\xA4", 'busts_in_silhouette' => "\xF0\x9F\x91\xA5", 'boy' => "\xF0\x9F\x91\xA6", 'girl' => "\xF0\x9F\x91\xA7", 'man' => "\xF0\x9F\x91\xA8", 'woman' => "\xF0\x9F\x91\xA9", 'family' => "\xF0\x9F\x91\xAA", 'couple' => "\xF0\x9F\x91\xAB", 'two_men_holding_hands' => "\xF0\x9F\x91\xAC", 'two_women_holding_hands' => "\xF0\x9F\x91\xAD", 'cop' => "\xF0\x9F\x91\xAE", 'dancers' => "\xF0\x9F\x91\xAF", 'bride_with_veil' => "\xF0\x9F\x91\xB0", 'person_with_blond_hair' => "\xF0\x9F\x91\xB1", 'man_with_gua_pi_mao' => "\xF0\x9F\x91\xB2", 'man_with_turban' => "\xF0\x9F\x91\xB3", 'older_man' => "\xF0\x9F\x91\xB4", 'older_woman' => "\xF0\x9F\x91\xB5", 'baby' => "\xF0\x9F\x91\xB6", 'construction_worker' => "\xF0\x9F\x91\xB7", 'princess' => "\xF0\x9F\x91\xB8", 'japanese_ogre' => "\xF0\x9F\x91\xB9", 'japanese_goblin' => "\xF0\x9F\x91\xBA", 'ghost' => "\xF0\x9F\x91\xBB", 'angel' => "\xF0\x9F\x91\xBC", 'alien' => "\xF0\x9F\x91\xBD", 'space_invader' => "\xF0\x9F\x91\xBE", 'imp' => "\xF0\x9F\x91\xBF", 'skull' => "\xF0\x9F\x92\x80", 'information_desk_person' => "\xF0\x9F\x92\x81", 'guardsman' => "\xF0\x9F\x92\x82", 'dancer' => "\xF0\x9F\x92\x83", 'lipstick' => "\xF0\x9F\x92\x84", 'nail_care' => "\xF0\x9F\x92\x85", 'massage' => "\xF0\x9F\x92\x86", 'haircut' => "\xF0\x9F\x92\x87", 'barber' => "\xF0\x9F\x92\x88", 'syringe' => "\xF0\x9F\x92\x89", 'pill' => "\xF0\x9F\x92\x8A", 'kiss' => "\xF0\x9F\x92\x8B", 'love_letter' => "\xF0\x9F\x92\x8C", 'ring' => "\xF0\x9F\x92\x8D", 'gem' => "\xF0\x9F\x92\x8E", 'couplekiss' => "\xF0\x9F\x92\x8F", 'bouquet' => "\xF0\x9F\x92\x90", 'couple_with_heart' => "\xF0\x9F\x92\x91", 'wedding' => "\xF0\x9F\x92\x92", 'heartbeat' => "\xF0\x9F\x92\x93", 'broken_heart' => "\xF0\x9F\x92\x94", 'two_hearts' => "\xF0\x9F\x92\x95", 'sparkling_heart' => "\xF0\x9F\x92\x96", 'heartpulse' => "\xF0\x9F\x92\x97", 'cupid' => "\xF0\x9F\x92\x98", 'blue_heart' => "\xF0\x9F\x92\x99", 'green_heart' => "\xF0\x9F\x92\x9A", 'yellow_heart' => "\xF0\x9F\x92\x9B", 'purple_heart' => "\xF0\x9F\x92\x9C", 'gift_heart' => "\xF0\x9F\x92\x9D", 'revolving_hearts' => "\xF0\x9F\x92\x9E", 'heart_decoration' => "\xF0\x9F\x92\x9F", 'diamond_shape_with_a_dot_inside' => "\xF0\x9F\x92\xA0", 'bulb' => "\xF0\x9F\x92\xA1", 'anger' => "\xF0\x9F\x92\xA2", 'bomb' => "\xF0\x9F\x92\xA3", 'zzz' => "\xF0\x9F\x92\xA4", 'boom' => "\xF0\x9F\x92\xA5", 'collision' => "\xF0\x9F\x92\xA5", 'sweat_drops' => "\xF0\x9F\x92\xA6", 'droplet' => "\xF0\x9F\x92\xA7", 'dash' => "\xF0\x9F\x92\xA8", 'poop' => "\xF0\x9F\x92\xA9", 'shit' => "\xF0\x9F\x92\xA9", 'hankey' => "\xF0\x9F\x92\xA9", 'muscle' => "\xF0\x9F\x92\xAA", 'dizzy' => "\xF0\x9F\x92\xAB", 'speech_balloon' => "\xF0\x9F\x92\xAC", 'thought_balloon' => "\xF0\x9F\x92\xAD", 'white_flower' => "\xF0\x9F\x92\xAE", '100' => "\xF0\x9F\x92\xAF", 'moneybag' => "\xF0\x9F\x92\xB0", 'currency_exchange' => "\xF0\x9F\x92\xB1", 'heavy_dollar_sign' => "\xF0\x9F\x92\xB2", 'credit_card' => "\xF0\x9F\x92\xB3", 'yen' => "\xF0\x9F\x92\xB4", 'dollar' => "\xF0\x9F\x92\xB5", 'euro' => "\xF0\x9F\x92\xB6", 'pound' => "\xF0\x9F\x92\xB7", 'money_with_wings' => "\xF0\x9F\x92\xB8", 'chart' => "\xF0\x9F\x92\xB9", 'seat' => "\xF0\x9F\x92\xBA", 'computer' => "\xF0\x9F\x92\xBB", 'briefcase' => "\xF0\x9F\x92\xBC", 'minidisc' => "\xF0\x9F\x92\xBD", 'floppy_disk' => "\xF0\x9F\x92\xBE", 'cd' => "\xF0\x9F\x92\xBF", 'dvd' => "\xF0\x9F\x93\x80", 'file_folder' => "\xF0\x9F\x93\x81", 'open_file_folder' => "\xF0\x9F\x93\x82", 'page_with_curl' => "\xF0\x9F\x93\x83", 'page_facing_up' => "\xF0\x9F\x93\x84", 'date' => "\xF0\x9F\x93\x85", 'calendar' => "\xF0\x9F\x93\x86", 'card_index' => "\xF0\x9F\x93\x87", 'chart_with_upwards_trend' => "\xF0\x9F\x93\x88", 'chart_with_downwards_trend' => "\xF0\x9F\x93\x89", 'bar_chart' => "\xF0\x9F\x93\x8A", 'clipboard' => "\xF0\x9F\x93\x8B", 'pushpin' => "\xF0\x9F\x93\x8C", 'round_pushpin' => "\xF0\x9F\x93\x8D", 'paperclip' => "\xF0\x9F\x93\x8E", 'straight_ruler' => "\xF0\x9F\x93\x8F", 'triangular_ruler' => "\xF0\x9F\x93\x90", 'bookmark_tabs' => "\xF0\x9F\x93\x91", 'ledger' => "\xF0\x9F\x93\x92", 'notebook' => "\xF0\x9F\x93\x93", 'notebook_with_decorative_cover' => "\xF0\x9F\x93\x94", 'closed_book' => "\xF0\x9F\x93\x95", 'book' => "\xF0\x9F\x93\x96", 'open_book' => "\xF0\x9F\x93\x96", 'green_book' => "\xF0\x9F\x93\x97", 'blue_book' => "\xF0\x9F\x93\x98", 'orange_book' => "\xF0\x9F\x93\x99", 'books' => "\xF0\x9F\x93\x9A", 'name_badge' => "\xF0\x9F\x93\x9B", 'scroll' => "\xF0\x9F\x93\x9C", 'pencil' => "\xF0\x9F\x93\x9D", 'memo' => "\xF0\x9F\x93\x9D", 'telephone_receiver' => "\xF0\x9F\x93\x9E", 'pager' => "\xF0\x9F\x93\x9F", 'fax' => "\xF0\x9F\x93\xA0", 'satellite' => "\xF0\x9F\x93\xA1", 'loudspeaker' => "\xF0\x9F\x93\xA2", 'mega' => "\xF0\x9F\x93\xA3", 'outbox_tray' => "\xF0\x9F\x93\xA4", 'inbox_tray' => "\xF0\x9F\x93\xA5", 'package' => "\xF0\x9F\x93\xA6", 'e_mail' => "\xF0\x9F\x93\xA7", 'incoming_envelope' => "\xF0\x9F\x93\xA8", 'envelope_with_arrow' => "\xF0\x9F\x93\xA9", 'mailbox_closed' => "\xF0\x9F\x93\xAA", 'mailbox' => "\xF0\x9F\x93\xAB", 'mailbox_with_mail' => "\xF0\x9F\x93\xAC", 'mailbox_with_no_mail' => "\xF0\x9F\x93\xAD", 'postbox' => "\xF0\x9F\x93\xAE", 'postal_horn' => "\xF0\x9F\x93\xAF", 'newspaper' => "\xF0\x9F\x93\xB0", 'iphone' => "\xF0\x9F\x93\xB1", 'calling' => "\xF0\x9F\x93\xB2", 'vibration_mode' => "\xF0\x9F\x93\xB3", 'mobile_phone_off' => "\xF0\x9F\x93\xB4", 'no_mobile_phones' => "\xF0\x9F\x93\xB5", 'signal_strength' => "\xF0\x9F\x93\xB6", 'camera' => "\xF0\x9F\x93\xB7", 'video_camera' => "\xF0\x9F\x93\xB9", 'tv' => "\xF0\x9F\x93\xBA", 'radio' => "\xF0\x9F\x93\xBB", 'vhs' => "\xF0\x9F\x93\xBC", 'twisted_rightwards_arrows' => "\xF0\x9F\x94\x80", 'repeat' => "\xF0\x9F\x94\x81", 'repeat_one' => "\xF0\x9F\x94\x82", 'arrows_clockwise' => "\xF0\x9F\x94\x83", 'arrows_counterclockwise' => "\xF0\x9F\x94\x84", 'low_brightness' => "\xF0\x9F\x94\x85", 'high_brightness' => "\xF0\x9F\x94\x86", 'mute' => "\xF0\x9F\x94\x87", 'speaker' => "\xF0\x9F\x94\x88", 'sound' => "\xF0\x9F\x94\x89", 'loud_sound' => "\xF0\x9F\x94\x8A", 'battery' => "\xF0\x9F\x94\x8B", 'electric_plug' => "\xF0\x9F\x94\x8C", 'mag' => "\xF0\x9F\x94\x8D", 'mag_right' => "\xF0\x9F\x94\x8E", 'lock_with_ink_pen' => "\xF0\x9F\x94\x8F", 'closed_lock_with_key' => "\xF0\x9F\x94\x90", 'key' => "\xF0\x9F\x94\x91", 'lock' => "\xF0\x9F\x94\x92", 'unlock' => "\xF0\x9F\x94\x93", 'bell' => "\xF0\x9F\x94\x94", 'no_bell' => "\xF0\x9F\x94\x95", 'bookmark' => "\xF0\x9F\x94\x96", 'link' => "\xF0\x9F\x94\x97", 'radio_button' => "\xF0\x9F\x94\x98", 'back' => "\xF0\x9F\x94\x99", 'end' => "\xF0\x9F\x94\x9A", 'on' => "\xF0\x9F\x94\x9B", 'soon' => "\xF0\x9F\x94\x9C", 'top' => "\xF0\x9F\x94\x9D", 'underage' => "\xF0\x9F\x94\x9E", 'keycap_ten' => "\xF0\x9F\x94\x9F", 'capital_abcd' => "\xF0\x9F\x94\xA0", 'abcd' => "\xF0\x9F\x94\xA1", '1234' => "\xF0\x9F\x94\xA2", 'symbols' => "\xF0\x9F\x94\xA3", 'abc' => "\xF0\x9F\x94\xA4", 'fire' => "\xF0\x9F\x94\xA5", 'flashlight' => "\xF0\x9F\x94\xA6", 'wrench' => "\xF0\x9F\x94\xA7", 'hammer' => "\xF0\x9F\x94\xA8", 'nut_and_bolt' => "\xF0\x9F\x94\xA9", 'knife' => "\xF0\x9F\x94\xAA", 'hocho' => "\xF0\x9F\x94\xAA", 'gun' => "\xF0\x9F\x94\xAB", 'microscope' => "\xF0\x9F\x94\xAC", 'telescope' => "\xF0\x9F\x94\xAD", 'crystal_ball' => "\xF0\x9F\x94\xAE", 'six_pointed_star' => "\xF0\x9F\x94\xAF", 'beginner' => "\xF0\x9F\x94\xB0", 'trident' => "\xF0\x9F\x94\xB1", 'black_square_button' => "\xF0\x9F\x94\xB2", 'white_square_button' => "\xF0\x9F\x94\xB3", 'red_circle' => "\xF0\x9F\x94\xB4", 'large_blue_circle' => "\xF0\x9F\x94\xB5", 'large_orange_diamond' => "\xF0\x9F\x94\xB6", 'large_blue_diamond' => "\xF0\x9F\x94\xB7", 'small_orange_diamond' => "\xF0\x9F\x94\xB8", 'small_blue_diamond' => "\xF0\x9F\x94\xB9", 'small_red_triangle' => "\xF0\x9F\x94\xBA", 'small_red_triangle_down' => "\xF0\x9F\x94\xBB", 'arrow_up_small' => "\xF0\x9F\x94\xBC", 'arrow_down_small' => "\xF0\x9F\x94\xBD", 'clock1' => "\xF0\x9F\x95\x90", 'clock2' => "\xF0\x9F\x95\x91", 'clock3' => "\xF0\x9F\x95\x92", 'clock4' => "\xF0\x9F\x95\x93", 'clock5' => "\xF0\x9F\x95\x94", 'clock6' => "\xF0\x9F\x95\x95", 'clock7' => "\xF0\x9F\x95\x96", 'clock8' => "\xF0\x9F\x95\x97", 'clock9' => "\xF0\x9F\x95\x98", 'clock10' => "\xF0\x9F\x95\x99", 'clock11' => "\xF0\x9F\x95\x9A", 'clock12' => "\xF0\x9F\x95\x9B", 'clock130' => "\xF0\x9F\x95\x9C", 'clock230' => "\xF0\x9F\x95\x9D", 'clock330' => "\xF0\x9F\x95\x9E", 'clock430' => "\xF0\x9F\x95\x9F", 'clock530' => "\xF0\x9F\x95\xA0", 'clock630' => "\xF0\x9F\x95\xA1", 'clock730' => "\xF0\x9F\x95\xA2", 'clock830' => "\xF0\x9F\x95\xA3", 'clock930' => "\xF0\x9F\x95\xA4", 'clock1030' => "\xF0\x9F\x95\xA5", 'clock1130' => "\xF0\x9F\x95\xA6", 'clock1230' => "\xF0\x9F\x95\xA7", 'mount_fuji' => "\xF0\x9F\x97\xBB", 'tokyo_tower' => "\xF0\x9F\x97\xBC", 'statue_of_liberty' => "\xF0\x9F\x97\xBD", 'japan' => "\xF0\x9F\x97\xBE", 'moyai' => "\xF0\x9F\x97\xBF", 'grinning' => "\xF0\x9F\x98\x80", 'grin' => "\xF0\x9F\x98\x81", 'joy' => "\xF0\x9F\x98\x82", 'smiley' => "\xF0\x9F\x98\x83", 'smile' => "\xF0\x9F\x98\x84", 'sweat_smile' => "\xF0\x9F\x98\x85", 'satisfied' => "\xF0\x9F\x98\x86", 'laughing' => "\xF0\x9F\x98\x86", 'innocent' => "\xF0\x9F\x98\x87", 'smiling_imp' => "\xF0\x9F\x98\x88", 'wink' => "\xF0\x9F\x98\x89", 'blush' => "\xF0\x9F\x98\x8A", 'yum' => "\xF0\x9F\x98\x8B", 'relieved' => "\xF0\x9F\x98\x8C", 'heart_eyes' => "\xF0\x9F\x98\x8D", 'sunglasses' => "\xF0\x9F\x98\x8E", 'smirk' => "\xF0\x9F\x98\x8F", 'neutral_face' => "\xF0\x9F\x98\x90", 'expressionless' => "\xF0\x9F\x98\x91", 'unamused' => "\xF0\x9F\x98\x92", 'sweat' => "\xF0\x9F\x98\x93", 'pensive' => "\xF0\x9F\x98\x94", 'confused' => "\xF0\x9F\x98\x95", 'confounded' => "\xF0\x9F\x98\x96", 'kissing' => "\xF0\x9F\x98\x97", 'kissing_heart' => "\xF0\x9F\x98\x98", 'kissing_smiling_eyes' => "\xF0\x9F\x98\x99", 'kissing_closed_eyes' => "\xF0\x9F\x98\x9A", 'stuck_out_tongue' => "\xF0\x9F\x98\x9B", 'stuck_out_tongue_winking_eye' => "\xF0\x9F\x98\x9C", 'stuck_out_tongue_closed_eyes' => "\xF0\x9F\x98\x9D", 'disappointed' => "\xF0\x9F\x98\x9E", 'worried' => "\xF0\x9F\x98\x9F", 'angry' => "\xF0\x9F\x98\xA0", 'rage' => "\xF0\x9F\x98\xA1", 'cry' => "\xF0\x9F\x98\xA2", 'persevere' => "\xF0\x9F\x98\xA3", 'triumph' => "\xF0\x9F\x98\xA4", 'disappointed_relieved' => "\xF0\x9F\x98\xA5", 'frowning' => "\xF0\x9F\x98\xA6", 'anguished' => "\xF0\x9F\x98\xA7", 'fearful' => "\xF0\x9F\x98\xA8", 'weary' => "\xF0\x9F\x98\xA9", 'sleepy' => "\xF0\x9F\x98\xAA", 'tired_face' => "\xF0\x9F\x98\xAB", 'grimacing' => "\xF0\x9F\x98\xAC", 'sob' => "\xF0\x9F\x98\xAD", 'open_mouth' => "\xF0\x9F\x98\xAE", 'hushed' => "\xF0\x9F\x98\xAF", 'cold_sweat' => "\xF0\x9F\x98\xB0", 'scream' => "\xF0\x9F\x98\xB1", 'astonished' => "\xF0\x9F\x98\xB2", 'flushed' => "\xF0\x9F\x98\xB3", 'sleeping' => "\xF0\x9F\x98\xB4", 'dizzy_face' => "\xF0\x9F\x98\xB5", 'no_mouth' => "\xF0\x9F\x98\xB6", 'mask' => "\xF0\x9F\x98\xB7", 'smile_cat' => "\xF0\x9F\x98\xB8", 'joy_cat' => "\xF0\x9F\x98\xB9", 'smiley_cat' => "\xF0\x9F\x98\xBA", 'heart_eyes_cat' => "\xF0\x9F\x98\xBB", 'smirk_cat' => "\xF0\x9F\x98\xBC", 'kissing_cat' => "\xF0\x9F\x98\xBD", 'pouting_cat' => "\xF0\x9F\x98\xBE", 'crying_cat_face' => "\xF0\x9F\x98\xBF", 'scream_cat' => "\xF0\x9F\x99\x80", 'no_good' => "\xF0\x9F\x99\x85", 'ok_woman' => "\xF0\x9F\x99\x86", 'bow' => "\xF0\x9F\x99\x87", 'see_no_evil' => "\xF0\x9F\x99\x88", 'hear_no_evil' => "\xF0\x9F\x99\x89", 'speak_no_evil' => "\xF0\x9F\x99\x8A", 'raising_hand' => "\xF0\x9F\x99\x8B", 'raised_hands' => "\xF0\x9F\x99\x8C", 'person_frowning' => "\xF0\x9F\x99\x8D", 'person_with_pouting_face' => "\xF0\x9F\x99\x8E", 'pray' => "\xF0\x9F\x99\x8F", 'rocket' => "\xF0\x9F\x9A\x80", 'helicopter' => "\xF0\x9F\x9A\x81", 'steam_locomotive' => "\xF0\x9F\x9A\x82", 'railway_car' => "\xF0\x9F\x9A\x83", 'bullettrain_side' => "\xF0\x9F\x9A\x84", 'bullettrain_front' => "\xF0\x9F\x9A\x85", 'train2' => "\xF0\x9F\x9A\x86", 'metro' => "\xF0\x9F\x9A\x87", 'light_rail' => "\xF0\x9F\x9A\x88", 'station' => "\xF0\x9F\x9A\x89", 'tram' => "\xF0\x9F\x9A\x8A", 'train' => "\xF0\x9F\x9A\x8B", 'bus' => "\xF0\x9F\x9A\x8C", 'oncoming_bus' => "\xF0\x9F\x9A\x8D", 'trolleybus' => "\xF0\x9F\x9A\x8E", 'busstop' => "\xF0\x9F\x9A\x8F", 'minibus' => "\xF0\x9F\x9A\x90", 'ambulance' => "\xF0\x9F\x9A\x91", 'fire_engine' => "\xF0\x9F\x9A\x92", 'police_car' => "\xF0\x9F\x9A\x93", 'oncoming_police_car' => "\xF0\x9F\x9A\x94", 'taxi' => "\xF0\x9F\x9A\x95", 'oncoming_taxi' => "\xF0\x9F\x9A\x96", 'red_car' => "\xF0\x9F\x9A\x97", 'car' => "\xF0\x9F\x9A\x97", 'oncoming_automobile' => "\xF0\x9F\x9A\x98", 'blue_car' => "\xF0\x9F\x9A\x99", 'truck' => "\xF0\x9F\x9A\x9A", 'articulated_lorry' => "\xF0\x9F\x9A\x9B", 'tractor' => "\xF0\x9F\x9A\x9C", 'monorail' => "\xF0\x9F\x9A\x9D", 'mountain_railway' => "\xF0\x9F\x9A\x9E", 'suspension_railway' => "\xF0\x9F\x9A\x9F", 'mountain_cableway' => "\xF0\x9F\x9A\xA0", 'aerial_tramway' => "\xF0\x9F\x9A\xA1", 'ship' => "\xF0\x9F\x9A\xA2", 'rowboat' => "\xF0\x9F\x9A\xA3", 'speedboat' => "\xF0\x9F\x9A\xA4", 'traffic_light' => "\xF0\x9F\x9A\xA5", 'vertical_traffic_light' => "\xF0\x9F\x9A\xA6", 'construction' => "\xF0\x9F\x9A\xA7", 'rotating_light' => "\xF0\x9F\x9A\xA8", 'triangular_flag_on_post' => "\xF0\x9F\x9A\xA9", 'door' => "\xF0\x9F\x9A\xAA", 'no_entry_sign' => "\xF0\x9F\x9A\xAB", 'smoking' => "\xF0\x9F\x9A\xAC", 'no_smoking' => "\xF0\x9F\x9A\xAD", 'put_litter_in_its_place' => "\xF0\x9F\x9A\xAE", 'do_not_litter' => "\xF0\x9F\x9A\xAF", 'potable_water' => "\xF0\x9F\x9A\xB0", 'non_potable_water' => "\xF0\x9F\x9A\xB1", 'bike' => "\xF0\x9F\x9A\xB2", 'no_bicycles' => "\xF0\x9F\x9A\xB3", 'bicyclist' => "\xF0\x9F\x9A\xB4", 'mountain_bicyclist' => "\xF0\x9F\x9A\xB5", 'walking' => "\xF0\x9F\x9A\xB6", 'no_pedestrians' => "\xF0\x9F\x9A\xB7", 'children_crossing' => "\xF0\x9F\x9A\xB8", 'mens' => "\xF0\x9F\x9A\xB9", 'womens' => "\xF0\x9F\x9A\xBA", 'restroom' => "\xF0\x9F\x9A\xBB", 'baby_symbol' => "\xF0\x9F\x9A\xBC", 'toilet' => "\xF0\x9F\x9A\xBD", 'wc' => "\xF0\x9F\x9A\xBE", 'shower' => "\xF0\x9F\x9A\xBF", 'bath' => "\xF0\x9F\x9B\x80", 'bathtub' => "\xF0\x9F\x9B\x81", 'passport_control' => "\xF0\x9F\x9B\x82", 'customs' => "\xF0\x9F\x9B\x83", 'baggage_claim' => "\xF0\x9F\x9B\x84", 'left_luggage' => "\xF0\x9F\x9B\x85", 'copyright' => "\xC2\xA9\xEF\xB8\x8F", 'registered' => "\xC2\xAE\xEF\xB8\x8F", 'bangbang' => "\xE2\x80\xBC\xEF\xB8\x8F", 'interrobang' => "\xE2\x81\x89\xEF\xB8\x8F", 'tm' => "\xE2\x84\xA2\xEF\xB8\x8F", 'information_source' => "\xE2\x84\xB9\xEF\xB8\x8F", 'left_right_arrow' => "\xE2\x86\x94\xEF\xB8\x8F", 'arrow_up_down' => "\xE2\x86\x95\xEF\xB8\x8F", 'arrow_upper_left' => "\xE2\x86\x96\xEF\xB8\x8F", 'arrow_upper_right' => "\xE2\x86\x97\xEF\xB8\x8F", 'arrow_lower_right' => "\xE2\x86\x98\xEF\xB8\x8F", 'arrow_lower_left' => "\xE2\x86\x99\xEF\xB8\x8F", 'leftwards_arrow_with_hook' => "\xE2\x86\xA9\xEF\xB8\x8F", 'arrow_right_hook' => "\xE2\x86\xAA\xEF\xB8\x8F", 'm' => "\xE2\x93\x82\xEF\xB8\x8F", 'black_small_square' => "\xE2\x96\xAA\xEF\xB8\x8F", 'white_small_square' => "\xE2\x96\xAB\xEF\xB8\x8F", 'arrow_forward' => "\xE2\x96\xB6\xEF\xB8\x8F", 'arrow_backward' => "\xE2\x97\x80\xEF\xB8\x8F", 'white_medium_square' => "\xE2\x97\xBB\xEF\xB8\x8F", 'black_medium_square' => "\xE2\x97\xBC\xEF\xB8\x8F", 'sunny' => "\xE2\x98\x80\xEF\xB8\x8F", 'cloud' => "\xE2\x98\x81\xEF\xB8\x8F", 'telephone' => "\xE2\x98\x8E\xEF\xB8\x8F", 'phone' => "\xE2\x98\x8E\xEF\xB8\x8F", 'ballot_box_with_check' => "\xE2\x98\x91\xEF\xB8\x8F", 'point_up' => "\xE2\x98\x9D\xEF\xB8\x8F", 'relaxed' => "\xE2\x98\xBA\xEF\xB8\x8F", 'spades' => "\xE2\x99\xA0\xEF\xB8\x8F", 'clubs' => "\xE2\x99\xA3\xEF\xB8\x8F", 'hearts' => "\xE2\x99\xA5\xEF\xB8\x8F", 'diamonds' => "\xE2\x99\xA6\xEF\xB8\x8F", 'hotsprings' => "\xE2\x99\xA8\xEF\xB8\x8F", 'recycle' => "\xE2\x99\xBB\xEF\xB8\x8F", 'warning' => "\xE2\x9A\xA0\xEF\xB8\x8F", 'baseball' => "\xE2\x9A\xBE\xEF\xB8\x8F", 'scissors' => "\xE2\x9C\x82\xEF\xB8\x8F", 'airplane' => "\xE2\x9C\x88\xEF\xB8\x8F", 'email' => "\xE2\x9C\x89\xEF\xB8\x8F", 'envelope' => "\xE2\x9C\x89\xEF\xB8\x8F", 'v' => "\xE2\x9C\x8C\xEF\xB8\x8F", 'pencil2' => "\xE2\x9C\x8F\xEF\xB8\x8F", 'black_nib' => "\xE2\x9C\x92\xEF\xB8\x8F", 'heavy_check_mark' => "\xE2\x9C\x94\xEF\xB8\x8F", 'heavy_multiplication_x' => "\xE2\x9C\x96\xEF\xB8\x8F", 'eight_spoked_asterisk' => "\xE2\x9C\xB3\xEF\xB8\x8F", 'eight_pointed_black_star' => "\xE2\x9C\xB4\xEF\xB8\x8F", 'snowflake' => "\xE2\x9D\x84\xEF\xB8\x8F", 'sparkle' => "\xE2\x9D\x87\xEF\xB8\x8F", 'heart' => "\xE2\x9D\xA4\xEF\xB8\x8F", 'arrow_right' => "\xE2\x9E\xA1\xEF\xB8\x8F", 'arrow_heading_up' => "\xE2\xA4\xB4\xEF\xB8\x8F", 'arrow_heading_down' => "\xE2\xA4\xB5\xEF\xB8\x8F", 'arrow_left' => "\xE2\xAC\x85\xEF\xB8\x8F", 'arrow_up' => "\xE2\xAC\x86\xEF\xB8\x8F", 'arrow_down' => "\xE2\xAC\x87\xEF\xB8\x8F", 'wavy_dash' => "\xE3\x80\xB0\xEF\xB8\x8F", 'part_alternation_mark' => "\xE3\x80\xBD\xEF\xB8\x8F", 'congratulations' => "\xE3\x8A\x97\xEF\xB8\x8F", 'secret' => "\xE3\x8A\x99\xEF\xB8\x8F", 'hash' => "\x23\xEF\xB8\x8F\xE2\x83\xA3", 'zero' => "\x30\xEF\xB8\x8F\xE2\x83\xA3", 'one' => "\x31\xEF\xB8\x8F\xE2\x83\xA3", 'two' => "\x32\xEF\xB8\x8F\xE2\x83\xA3", 'three' => "\x33\xEF\xB8\x8F\xE2\x83\xA3", 'four' => "\x34\xEF\xB8\x8F\xE2\x83\xA3", 'five' => "\x35\xEF\xB8\x8F\xE2\x83\xA3", 'six' => "\x36\xEF\xB8\x8F\xE2\x83\xA3", 'seven' => "\x37\xEF\xB8\x8F\xE2\x83\xA3", 'eight' => "\x38\xEF\xB8\x8F\xE2\x83\xA3", 'nine' => "\x39\xEF\xB8\x8F\xE2\x83\xA3", 'a' => "\xF0\x9F\x85\xB0\xEF\xB8\x8F", 'b' => "\xF0\x9F\x85\xB1\xEF\xB8\x8F", 'o2' => "\xF0\x9F\x85\xBE\xEF\xB8\x8F", 'parking' => "\xF0\x9F\x85\xBF\xEF\xB8\x8F", 'sa' => "\xF0\x9F\x88\x82\xEF\xB8\x8F", 'u6708' => "\xF0\x9F\x88\xB7\xEF\xB8\x8F", 'cn' => "\xF0\x9F\x87\xA8\xF0\x9F\x87\xB3", 'de' => "\xF0\x9F\x87\xA9\xF0\x9F\x87\xAA", 'es' => "\xF0\x9F\x87\xAA\xF0\x9F\x87\xB8", 'fr' => "\xF0\x9F\x87\xAB\xF0\x9F\x87\xB7", 'uk' => "\xF0\x9F\x87\xAC\xF0\x9F\x87\xA7", 'gb' => "\xF0\x9F\x87\xAC\xF0\x9F\x87\xA7", 'it' => "\xF0\x9F\x87\xAE\xF0\x9F\x87\xB9", 'jp' => "\xF0\x9F\x87\xAF\xF0\x9F\x87\xB5", 'kr' => "\xF0\x9F\x87\xB0\xF0\x9F\x87\xB7", 'ru' => "\xF0\x9F\x87\xB7\xF0\x9F\x87\xBA", 'us' => "\xF0\x9F\x87\xBA\xF0\x9F\x87\xB8", ); $matches[1] = str_replace('-', '_', $matches[1]); if (isset($map[$matches[1]])) { return $map[$matches[1]]; } return $matches[0]; } } diff --git a/src/applications/macro/markup/PhabricatorIconRemarkupRule.php b/src/applications/macro/markup/PhabricatorIconRemarkupRule.php index 9daf9b6f0c..ce1212b0fc 100644 --- a/src/applications/macro/markup/PhabricatorIconRemarkupRule.php +++ b/src/applications/macro/markup/PhabricatorIconRemarkupRule.php @@ -1,80 +1,79 @@ <?php final class PhabricatorIconRemarkupRule extends PhutilRemarkupRule { public function getPriority() { return 200.0; } public function apply($text) { return preg_replace_callback( '@{icon\b((?:[^}\\\\]+|\\\\.)*)}@m', array($this, 'markupIcon'), $text); } - public function markupIcon($matches) { + public function markupIcon(array $matches) { $engine = $this->getEngine(); $text_mode = $engine->isTextMode(); $mail_mode = $engine->isHTMLMailMode(); if (!$this->isFlatText($matches[0]) || $text_mode || $mail_mode) { return $matches[0]; } $extra = idx($matches, 1); // We allow various forms, like these: // // {icon} // {icon camera} // {icon,camera} // {icon camera color=red} // {icon, camera, color=red} $extra = ltrim($extra, ", \n"); $extra = preg_split('/[\s,]+/', $extra, 2); // Choose some arbitrary default icon so that previews render in a mostly // reasonable way as you're typing the syntax. $icon = idx($extra, 0, 'paw'); $defaults = array( 'color' => null, ); $options = idx($extra, 1, ''); $parser = new PhutilSimpleOptions(); $options = $parser->parse($options) + $defaults; // NOTE: We're validating icon and color names to prevent users from // adding arbitrary CSS classes to the document. Although this probably // isn't dangerous, it's safer to validate. static $icon_names; if (!$icon_names) { $icon_names = array_fuse(PHUIIconView::getFontIcons()); } static $color_names; if (!$color_names) { $color_names = array_fuse(PHUIIconView::getFontIconColors()); } if (empty($icon_names['fa-'.$icon])) { $icon = 'paw'; } $color = $options['color']; if (empty($color_names[$color])) { $color = null; } $icon_view = id(new PHUIIconView()) ->setIconFont('fa-'.$icon, $color); - return $this->getEngine()->storeText($icon_view); } } diff --git a/src/applications/macro/markup/PhabricatorImageMacroRemarkupRule.php b/src/applications/macro/markup/PhabricatorImageMacroRemarkupRule.php index ec0ced4c1f..8911181a77 100644 --- a/src/applications/macro/markup/PhabricatorImageMacroRemarkupRule.php +++ b/src/applications/macro/markup/PhabricatorImageMacroRemarkupRule.php @@ -1,157 +1,157 @@ <?php final class PhabricatorImageMacroRemarkupRule extends PhutilRemarkupRule { private $macros; const KEY_RULE_MACRO = 'rule.macro'; public function apply($text) { return preg_replace_callback( '@^\s*([a-zA-Z0-9:_\-]+)$@m', array($this, 'markupImageMacro'), $text); } - public function markupImageMacro($matches) { + public function markupImageMacro(array $matches) { if ($this->macros === null) { $this->macros = array(); $viewer = $this->getEngine()->getConfig('viewer'); $rows = id(new PhabricatorMacroQuery()) ->setViewer($viewer) ->withStatus(PhabricatorMacroQuery::STATUS_ACTIVE) ->execute(); $this->macros = mpull($rows, 'getPHID', 'getName'); } $name = (string)$matches[1]; if (empty($this->macros[$name])) { return $matches[1]; } $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_MACRO; $metadata = $engine->getTextMetadata($metadata_key, array()); $token = $engine->storeText('<macro>'); $metadata[] = array( 'token' => $token, 'phid' => $this->macros[$name], 'original' => $name, ); $engine->setTextMetadata($metadata_key, $metadata); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_MACRO; $metadata = $engine->getTextMetadata($metadata_key, array()); if (!$metadata) { return; } $phids = ipull($metadata, 'phid'); $viewer = $this->getEngine()->getConfig('viewer'); // Load all the macros. $macros = id(new PhabricatorMacroQuery()) ->setViewer($viewer) ->withStatus(PhabricatorMacroQuery::STATUS_ACTIVE) ->withPHIDs($phids) ->execute(); $macros = mpull($macros, null, 'getPHID'); // Load all the images and audio. $file_phids = array_merge( array_values(mpull($macros, 'getFilePHID')), array_values(mpull($macros, 'getAudioPHID'))); $file_phids = array_filter($file_phids); $files = array(); if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); } // Replace any macros that we couldn't load the macro or image for with // the original text. foreach ($metadata as $key => $spec) { $macro = idx($macros, $spec['phid']); if ($macro) { $file = idx($files, $macro->getFilePHID()); if ($file) { continue; } } $engine->overwriteStoredText($spec['token'], $spec['original']); unset($metadata[$key]); } foreach ($metadata as $spec) { $macro = $macros[$spec['phid']]; $file = $files[$macro->getFilePHID()]; $src_uri = $file->getBestURI(); if ($this->getEngine()->isTextMode()) { $result = $spec['original'].' <'.$src_uri.'>'; $engine->overwriteStoredText($spec['token'], $result); continue; } else if ($this->getEngine()->isHTMLMailMode()) { $src_uri = PhabricatorEnv::getProductionURI($src_uri); } $id = null; $audio = idx($files, $macro->getAudioPHID()); $should_play = ($audio && $macro->getAudioBehavior() != PhabricatorFileImageMacro::AUDIO_BEHAVIOR_NONE); if ($should_play) { $id = celerity_generate_unique_node_id(); $loop = null; switch ($macro->getAudioBehavior()) { case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: $loop = true; break; } Javelin::initBehavior( 'audio-source', array( 'sourceID' => $id, 'audioURI' => $audio->getBestURI(), 'loop' => $loop, )); } $result = $this->newTag( 'img', array( 'id' => $id, 'src' => $src_uri, 'alt' => $spec['original'], 'title' => $spec['original'], 'height' => $file->getImageHeight(), 'width' => $file->getImageWidth(), 'class' => 'phabricator-remarkup-macro', )); $engine->overwriteStoredText($spec['token'], $result); } $engine->setTextMetadata($metadata_key, array()); } } diff --git a/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php b/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php index df15548e54..0c949e130c 100644 --- a/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php +++ b/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php @@ -1,65 +1,65 @@ <?php final class PhabricatorMemeRemarkupRule extends PhutilRemarkupRule { private $images; public function getPriority() { return 200.0; } public function apply($text) { return preg_replace_callback( '@{meme,((?:[^}\\\\]+|\\\\.)+)}$@m', array($this, 'markupMeme'), $text); } - public function markupMeme($matches) { + public function markupMeme(array $matches) { if (!$this->isFlatText($matches[0])) { return $matches[0]; } $options = array( 'src' => null, 'above' => null, 'below' => null, ); $parser = new PhutilSimpleOptions(); $options = $parser->parse($matches[1]) + $options; $uri = id(new PhutilURI('/macro/meme/')) ->alter('macro', $options['src']) ->alter('uppertext', $options['above']) ->alter('lowertext', $options['below']); if ($this->getEngine()->isHTMLMailMode()) { $uri = PhabricatorEnv::getProductionURI($uri); } if ($this->getEngine()->isTextMode()) { $img = ($options['above'] != '' ? "\"{$options['above']}\"\n" : ''). $options['src'].' <'.PhabricatorEnv::getProductionURI($uri).'>'. ($options['below'] != '' ? "\n\"{$options['below']}\"" : ''); } else { $alt_text = pht( 'Macro %s: %s %s', $options['src'], $options['above'], $options['below']); $img = $this->newTag( 'img', array( 'src' => $uri, 'alt' => $alt_text, 'class' => 'phabricator-remarkup-macro', )); } return $this->getEngine()->storeText($img); } } diff --git a/src/applications/paste/remarkup/PhabricatorPasteRemarkupRule.php b/src/applications/paste/remarkup/PhabricatorPasteRemarkupRule.php index 9e8822b175..e8e2624977 100644 --- a/src/applications/paste/remarkup/PhabricatorPasteRemarkupRule.php +++ b/src/applications/paste/remarkup/PhabricatorPasteRemarkupRule.php @@ -1,56 +1,60 @@ <?php final class PhabricatorPasteRemarkupRule extends PhabricatorObjectRemarkupRule { protected function getObjectNamePrefix() { return 'P'; } protected function loadObjects(array $ids) { $viewer = $this->getEngine()->getConfig('viewer'); return id(new PhabricatorPasteQuery()) ->setViewer($viewer) ->withIDs($ids) ->needContent(true) ->execute(); } - protected function renderObjectEmbed($object, $handle, $options) { + protected function renderObjectEmbed( + $object, + PhabricatorObjectHandle $handle, + $options) { + $embed_paste = id(new PasteEmbedView()) ->setPaste($object) ->setHandle($handle); if (strlen($options)) { $parser = new PhutilSimpleOptions(); $opts = $parser->parse(substr($options, 1)); foreach ($opts as $key => $value) { if ($key == 'lines') { $embed_paste->setLines(preg_replace('/[^0-9]/', '', $value)); } else if ($key == 'highlight') { $highlights = preg_split('/,|&/', preg_replace('/\s+/', '', $value)); $to_highlight = array(); foreach ($highlights as $highlight) { $highlight = explode('-', $highlight); if (!empty($highlight)) { sort($highlight); $to_highlight = array_merge( $to_highlight, range(head($highlight), last($highlight))); } } $embed_paste->setHighlights(array_unique($to_highlight)); } } } return $embed_paste; } } diff --git a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php index 6750e34f0d..0451e29312 100644 --- a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php +++ b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php @@ -1,194 +1,194 @@ <?php final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule { const KEY_RULE_MENTION = 'rule.mention'; const KEY_RULE_MENTION_ORIGINAL = 'rule.mention.original'; const KEY_MENTIONED = 'phabricator.mentioned-user-phids'; // NOTE: The negative lookbehind prevents matches like "mail@lists", while // allowing constructs like "@tomo/@mroch". Since we now allow periods in // usernames, we can't resonably distinguish that "@company.com" isn't a // username, so we'll incorrectly pick it up, but there's little to be done // about that. We forbid terminal periods so that we can correctly capture // "@joe" instead of "@joe." in "Hey, @joe.". // // We disallow "@@joe" because it creates a false positive in the common // construction "l@@k", made popular by eBay. const REGEX = '/(?<!\w|@)@([a-zA-Z0-9._-]*[a-zA-Z0-9_-])/'; public function apply($text) { return preg_replace_callback( self::REGEX, array($this, 'markupMention'), $text); } - protected function markupMention($matches) { + protected function markupMention(array $matches) { $engine = $this->getEngine(); if ($engine->isTextMode()) { return $engine->storeText($matches[0]); } $token = $engine->storeText(''); // Store the original text exactly so we can preserve casing if it doesn't // resolve into a username. $original_key = self::KEY_RULE_MENTION_ORIGINAL; $original = $engine->getTextMetadata($original_key, array()); $original[$token] = $matches[1]; $engine->setTextMetadata($original_key, $original); $metadata_key = self::KEY_RULE_MENTION; $metadata = $engine->getTextMetadata($metadata_key, array()); $username = strtolower($matches[1]); if (empty($metadata[$username])) { $metadata[$username] = array(); } $metadata[$username][] = $token; $engine->setTextMetadata($metadata_key, $metadata); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_MENTION; $metadata = $engine->getTextMetadata($metadata_key, array()); if (empty($metadata)) { // No mentions, or we already processed them. return; } $original_key = self::KEY_RULE_MENTION_ORIGINAL; $original = $engine->getTextMetadata($original_key, array()); $usernames = array_keys($metadata); $users = id(new PhabricatorPeopleQuery()) ->setViewer($this->getEngine()->getConfig('viewer')) ->withUsernames($usernames) ->execute(); if ($users) { $user_statuses = id(new PhabricatorCalendarEvent()) ->loadCurrentStatuses(mpull($users, 'getPHID')); $user_statuses = mpull($user_statuses, null, 'getUserPHID'); } else { $user_statuses = array(); } $actual_users = array(); $mentioned_key = self::KEY_MENTIONED; $mentioned = $engine->getTextMetadata($mentioned_key, array()); foreach ($users as $row) { $actual_users[strtolower($row->getUserName())] = $row; $mentioned[$row->getPHID()] = $row->getPHID(); } $engine->setTextMetadata($mentioned_key, $mentioned); $context_object = $engine->getConfig('contextObject'); foreach ($metadata as $username => $tokens) { $exists = isset($actual_users[$username]); $user_has_no_permission = false; if ($exists) { $user = $actual_users[$username]; Javelin::initBehavior('phabricator-hovercards'); // Check if the user has view access to the object she was mentioned in if ($context_object && $context_object instanceof PhabricatorPolicyInterface) { if (!PhabricatorPolicyFilter::hasCapability( $user, $context_object, PhabricatorPolicyCapability::CAN_VIEW)) { // User mentioned has no permission to this object $user_has_no_permission = true; } } $user_href = '/p/'.$user->getUserName().'/'; if ($engine->isHTMLMailMode()) { $user_href = PhabricatorEnv::getProductionURI($user_href); if ($user_has_no_permission) { $colors = ' border-color: #92969D; color: #92969D; background-color: #F7F7F7;'; } else { $colors = ' border-color: #f1f7ff; color: #19558d; background-color: #f1f7ff;'; } $tag = phutil_tag( 'a', array( 'href' => $user_href, 'style' => $colors.' border: 1px solid transparent; border-radius: 3px; font-weight: bold; padding: 0 4px;', ), '@'.$user->getUserName()); } else { $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_PERSON) ->setPHID($user->getPHID()) ->setName('@'.$user->getUserName()) ->setHref($user_href); if ($user_has_no_permission) { $tag->addClass('phabricator-remarkup-mention-nopermission'); } if (!$user->isUserActivated()) { $tag->setDotColor(PHUITagView::COLOR_GREY); } else { $status = idx($user_statuses, $user->getPHID()); if ($status) { $status = $status->getStatus(); if ($status == PhabricatorCalendarEvent::STATUS_AWAY) { $tag->setDotColor(PHUITagView::COLOR_RED); } else if ($status == PhabricatorCalendarEvent::STATUS_AWAY) { $tag->setDotColor(PHUITagView::COLOR_ORANGE); } } } } foreach ($tokens as $token) { $engine->overwriteStoredText($token, $tag); } } else { // NOTE: The structure here is different from the 'exists' branch, // because we want to preserve the original text capitalization and it // may differ for each token. foreach ($tokens as $token) { $tag = phutil_tag( 'span', array( 'class' => 'phabricator-remarkup-mention-unknown', ), '@'.idx($original, $token, $username)); $engine->overwriteStoredText($token, $tag); } } } // Don't re-process these mentions. $engine->setTextMetadata($metadata_key, array()); } } diff --git a/src/applications/pholio/remarkup/PholioRemarkupRule.php b/src/applications/pholio/remarkup/PholioRemarkupRule.php index a28ef858d7..e8c8f00a17 100644 --- a/src/applications/pholio/remarkup/PholioRemarkupRule.php +++ b/src/applications/pholio/remarkup/PholioRemarkupRule.php @@ -1,73 +1,81 @@ <?php final class PholioRemarkupRule extends PhabricatorObjectRemarkupRule { protected function getObjectNamePrefix() { return 'M'; } protected function getObjectIDPattern() { // Match "M123", "M123/456", and "M123/456/". Users can hit the latter // forms when clicking comment anchors on a mock page. return '[1-9]\d*(?:/[1-9]\d*/?)?'; } - protected function getObjectHref($object, $handle, $id) { + protected function getObjectHref( + $object, + PhabricatorObjectHandle $handle, + $id) { + $href = $handle->getURI(); // If the ID has a `M123/456` component, link to that specific image. $id = explode('/', $id); if (isset($id[1])) { $href = $href.'/'.$id[1].'/'; } return $href; } protected function loadObjects(array $ids) { // Strip off any image ID components of the URI. $map = array(); foreach ($ids as $id) { $map[head(explode('/', $id))][] = $id; } $viewer = $this->getEngine()->getConfig('viewer'); $mocks = id(new PholioMockQuery()) ->setViewer($viewer) ->needCoverFiles(true) ->needImages(true) ->needTokenCounts(true) ->withIDs(array_keys($map)) ->execute(); $results = array(); foreach ($mocks as $mock) { $ids = idx($map, $mock->getID(), array()); foreach ($ids as $id) { $results[$id] = $mock; } } return $results; } - protected function renderObjectEmbed($object, $handle, $options) { + protected function renderObjectEmbed( + $object, + PhabricatorObjectHandle $handle, + $options) { + $embed_mock = id(new PholioMockEmbedView()) ->setMock($object); if (strlen($options)) { $parser = new PhutilSimpleOptions(); $opts = $parser->parse(substr($options, 1)); if (isset($opts['image'])) { $images = array_unique( explode('&', preg_replace('/\s+/', '', $opts['image']))); $embed_mock->setImages($images); } } return $embed_mock->render(); } } diff --git a/src/applications/phriction/markup/PhrictionRemarkupRule.php b/src/applications/phriction/markup/PhrictionRemarkupRule.php index ba740e5f7b..6b66dd95d1 100644 --- a/src/applications/phriction/markup/PhrictionRemarkupRule.php +++ b/src/applications/phriction/markup/PhrictionRemarkupRule.php @@ -1,52 +1,51 @@ <?php final class PhrictionRemarkupRule extends PhutilRemarkupRule { public function getPriority() { return 175.0; } public function apply($text) { return preg_replace_callback( '@\B\\[\\[([^|\\]]+)(?:\\|([^\\]]+))?\\]\\]\B@U', array($this, 'markupDocumentLink'), $text); } - public function markupDocumentLink($matches) { - + public function markupDocumentLink(array $matches) { $link = trim($matches[1]); $name = trim(idx($matches, 2, $link)); if (empty($matches[2])) { $name = explode('/', trim($name, '/')); $name = end($name); } $uri = new PhutilURI($link); $slug = $uri->getPath(); $fragment = $uri->getFragment(); $slug = PhabricatorSlug::normalize($slug); $slug = PhrictionDocument::getSlugURI($slug); $href = (string)id(new PhutilURI($slug))->setFragment($fragment); $text_mode = $this->getEngine()->isTextMode(); $mail_mode = $this->getEngine()->isHTMLMailMode(); if ($this->getEngine()->getState('toc')) { $text = $name; } else if ($text_mode || $mail_mode) { return PhabricatorEnv::getProductionURI($href); } else { $text = $this->newTag( 'a', array( 'href' => $href, 'class' => 'phriction-link', ), $name); } return $this->getEngine()->storeText($text); } } diff --git a/src/applications/project/remarkup/ProjectRemarkupRule.php b/src/applications/project/remarkup/ProjectRemarkupRule.php index fa0552593f..41ea2e026f 100644 --- a/src/applications/project/remarkup/ProjectRemarkupRule.php +++ b/src/applications/project/remarkup/ProjectRemarkupRule.php @@ -1,76 +1,81 @@ <?php final class ProjectRemarkupRule extends PhabricatorObjectRemarkupRule { protected function getObjectNamePrefix() { return '#'; } - protected function renderObjectRef($object, $handle, $anchor, $id) { + protected function renderObjectRef( + $object, + PhabricatorObjectHandle $handle, + $anchor, + $id) { + if ($this->getEngine()->isTextMode()) { return '#'.$id; } return $handle->renderTag(); } protected function getObjectIDPattern() { // NOTE: This explicitly does not match strings which contain only // digits, because digit strings like "#123" are used to reference tasks at // Facebook and are somewhat conventional in general. // The latter half of this rule matches monograms with internal periods, // like `#domain.com`, but does not match monograms with terminal periods, // because they're probably just puncutation. // Broadly, this will not match every possible project monogram, and we // accept some false negatives -- like `#1` or `#dot.` -- in order to avoid // a bunch of false positives on general use of the `#` character. // In other contexts, the PhabricatorProjectProjectPHIDType pattern is // controlling and these names should parse correctly. // These characters may never appear anywhere in a hashtag. $never = '\s?!,:;{}#\\(\\)"\''; // These characters may not appear at the beginning. $never_first = '.\d'; // These characters may not appear at the end. $never_last = '.'; return '[^'.$never_first.$never.']+'. '(?:'. '[^'.$never.']*'. '[^'.$never_last.$never.']+'. ')*'; } protected function loadObjects(array $ids) { $viewer = $this->getEngine()->getConfig('viewer'); // Put the "#" back on the front of these IDs. $names = array(); foreach ($ids as $id) { $names[] = '#'.$id; } // Issue a query by object name. $query = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames($names); $query->execute(); $projects = $query->getNamedResults(); // Slice the "#" off again. $result = array(); foreach ($projects as $name => $project) { $result[substr($name, 1)] = $project; } return $result; } } diff --git a/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php b/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php index 6f4c6b6a21..c93757315b 100644 --- a/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php +++ b/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php @@ -1,31 +1,35 @@ <?php final class SlowvoteRemarkupRule extends PhabricatorObjectRemarkupRule { protected function getObjectNamePrefix() { return 'V'; } protected function loadObjects(array $ids) { $viewer = $this->getEngine()->getConfig('viewer'); return id(new PhabricatorSlowvoteQuery()) ->setViewer($viewer) ->withIDs($ids) ->needOptions(true) ->needChoices(true) ->needViewerChoices(true) ->execute(); } - protected function renderObjectEmbed($object, $handle, $options) { + protected function renderObjectEmbed( + $object, + PhabricatorObjectHandle $handle, + $options) { + $viewer = $this->getEngine()->getConfig('viewer'); $embed = id(new SlowvoteEmbedView()) ->setUser($viewer) ->setPoll($object); return $embed; } } diff --git a/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php index 03c9b320a0..857a0c10bd 100644 --- a/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php @@ -1,112 +1,112 @@ <?php final class PhabricatorNavigationRemarkupRule extends PhutilRemarkupRule { public function getPriority() { return 200.0; } public function apply($text) { return preg_replace_callback( '@{nav\b((?:[^}\\\\]+|\\\\.)*)}@m', array($this, 'markupNavigation'), $text); } - public function markupNavigation($matches) { + public function markupNavigation(array $matches) { if (!$this->isFlatText($matches[0])) { return $matches[0]; } $elements = ltrim($matches[1], ", \n"); $elements = explode('>', $elements); $defaults = array( 'name' => null, 'type' => 'link', 'href' => null, 'icon' => null, ); $sequence = array(); $parser = new PhutilSimpleOptions(); foreach ($elements as $element) { if (strpos($element, '=') === false) { $sequence[] = array( 'name' => trim($element), ) + $defaults; } else { $sequence[] = $parser->parse($element) + $defaults; } } if ($this->getEngine()->isTextMode()) { return implode(' > ', ipull($sequence, 'name')); } static $icon_names; if (!$icon_names) { $icon_names = array_fuse(PHUIIconView::getFontIcons()); } $out = array(); foreach ($sequence as $item) { $item_name = $item['name']; $item_color = PHUITagView::COLOR_GREY; if ($item['type'] == 'instructions') { $item_name = phutil_tag('em', array(), $item_name); $item_color = PHUITagView::COLOR_INDIGO; } $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setShade($item_color) ->setName($item_name); if ($item['icon']) { $icon_name = 'fa-'.$item['icon']; if (isset($icon_names[$icon_name])) { $tag->setIcon($icon_name); } } if ($item['href'] !== null) { if (PhabricatorEnv::isValidRemoteURIForLink($item['href'])) { $tag->setHref($item['href']); $tag->setExternal(true); } } $out[] = $tag; } if ($this->getEngine()->isHTMLMailMode()) { $arrow_attr = array( 'style' => 'color: #92969D;', ); $nav_attr = array(); } else { $arrow_attr = array( 'class' => 'remarkup-nav-sequence-arrow', ); $nav_attr = array( 'class' => 'remarkup-nav-sequence', ); } $joiner = phutil_tag( 'span', $arrow_attr, " \xE2\x86\x92 "); $out = phutil_implode_html($joiner, $out); $out = phutil_tag( 'span', $nav_attr, $out); return $this->getEngine()->storeText($out); } } diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php index 7f211c3654..64f6684ce6 100644 --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -1,359 +1,373 @@ <?php abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { const KEY_RULE_OBJECT = 'rule.object'; const KEY_MENTIONED_OBJECTS = 'rule.object.mentioned'; abstract protected function getObjectNamePrefix(); abstract protected function loadObjects(array $ids); public function getPriority() { return 450.0; } protected function getObjectNamePrefixBeginsWithWordCharacter() { $prefix = $this->getObjectNamePrefix(); return preg_match('/^\w/', $prefix); } protected function getObjectIDPattern() { return '[1-9]\d*'; } protected function shouldMarkupObject(array $params) { return true; } protected function loadHandles(array $objects) { $phids = mpull($objects, 'getPHID'); $handles = id(new PhabricatorHandleQuery($phids)) ->withPHIDs($phids) ->setViewer($this->getEngine()->getConfig('viewer')) ->execute(); $result = array(); foreach ($objects as $id => $object) { $result[$id] = $handles[$object->getPHID()]; } return $result; } protected function getObjectHref($object, $handle, $id) { return $handle->getURI(); } protected function renderObjectRefForAnyMedia ( - $object, - $handle, - $anchor, - $id) { + $object, + PhabricatorObjectHandle $handle, + $anchor, + $id) { + $href = $this->getObjectHref($object, $handle, $id); $text = $this->getObjectNamePrefix().$id; if ($anchor) { $href = $href.'#'.$anchor; $text = $text.'#'.$anchor; } if ($this->getEngine()->isTextMode()) { return PhabricatorEnv::getProductionURI($href); } else if ($this->getEngine()->isHTMLMailMode()) { $href = PhabricatorEnv::getProductionURI($href); return $this->renderObjectTagForMail($text, $href, $handle); } return $this->renderObjectRef($object, $handle, $anchor, $id); } - protected function renderObjectRef($object, $handle, $anchor, $id) { + protected function renderObjectRef( + $object, + PhabricatorObjectHandle $handle, + $anchor, + $id) { + $href = $this->getObjectHref($object, $handle, $id); $text = $this->getObjectNamePrefix().$id; $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; if ($anchor) { $href = $href.'#'.$anchor; $text = $text.'#'.$anchor; } $attr = array( 'phid' => $handle->getPHID(), 'closed' => ($handle->getStatus() == $status_closed), ); return $this->renderHovertag($text, $href, $attr); } - protected function renderObjectEmbedForAnyMedia($object, $handle, $options) { + protected function renderObjectEmbedForAnyMedia( + $object, + PhabricatorObjectHandle $handle, + $options) { + $name = $handle->getFullName(); $href = $handle->getURI(); if ($this->getEngine()->isTextMode()) { return $name.' <'.PhabricatorEnv::getProductionURI($href).'>'; } else if ($this->getEngine()->isHTMLMailMode()) { $href = PhabricatorEnv::getProductionURI($href); return $this->renderObjectTagForMail($name, $href, $handle); } return $this->renderObjectEmbed($object, $handle, $options); } - protected function renderObjectEmbed($object, $handle, $options) { + protected function renderObjectEmbed( + $object, + PhabricatorObjectHandle $handle, + $options) { + $name = $handle->getFullName(); $href = $handle->getURI(); $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; $attr = array( 'phid' => $handle->getPHID(), 'closed' => ($handle->getStatus() == $status_closed), ); return $this->renderHovertag($name, $href, $attr); } protected function renderObjectTagForMail( $text, $href, - $handle) { + PhabricatorObjectHandle $handle) { $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; $strikethrough = $handle->getStatus() == $status_closed ? 'text-decoration: line-through;' : 'text-decoration: none;'; return phutil_tag( 'a', array( 'href' => $href, 'style' => 'background-color: #e7e7e7; border-color: #e7e7e7; border-radius: 3px; padding: 0 4px; font-weight: bold; color: black;' .$strikethrough, ), $text); } protected function renderHovertag($name, $href, array $attr = array()) { return id(new PHUITagView()) ->setName($name) ->setHref($href) ->setType(PHUITagView::TYPE_OBJECT) ->setPHID(idx($attr, 'phid')) ->setClosed(idx($attr, 'closed')) ->render(); } public function apply($text) { $text = preg_replace_callback( $this->getObjectEmbedPattern(), array($this, 'markupObjectEmbed'), $text); $text = preg_replace_callback( $this->getObjectReferencePattern(), array($this, 'markupObjectReference'), $text); return $text; } private function getObjectEmbedPattern() { $prefix = $this->getObjectNamePrefix(); $prefix = preg_quote($prefix); $id = $this->getObjectIDPattern(); return '(\B{'.$prefix.'('.$id.')([,\s](?:[^}\\\\]|\\\\.)*)?}\B)u'; } private function getObjectReferencePattern() { $prefix = $this->getObjectNamePrefix(); $prefix = preg_quote($prefix); $id = $this->getObjectIDPattern(); // If the prefix starts with a word character (like "D"), we want to // require a word boundary so that we don't match "XD1" as "D1". If the // prefix does not start with a word character, we want to require no word // boundary for the same reasons. Test if the prefix starts with a word // character. if ($this->getObjectNamePrefixBeginsWithWordCharacter()) { $boundary = '\\b'; } else { $boundary = '\\B'; } // The "(?<![#-])" prevents us from linking "#abcdef" or similar, and // "ABC-T1" (see T5714). // The "\b" allows us to link "(abcdef)" or similar without linking things // in the middle of words. return '((?<![#-])'.$boundary.$prefix.'('.$id.')(?:#([-\w\d]+))?(?!\w))u'; } /** * Extract matched object references from a block of text. * * This is intended to make it easy to write unit tests for object remarkup * rules. Production code is not normally expected to call this method. * * @param string Text to match rules against. * @return wild Matches, suitable for writing unit tests against. */ public function extractReferences($text) { $embed_matches = null; preg_match_all( $this->getObjectEmbedPattern(), $text, $embed_matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); $ref_matches = null; preg_match_all( $this->getObjectReferencePattern(), $text, $ref_matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); $results = array(); $sets = array( 'embed' => $embed_matches, 'ref' => $ref_matches, ); foreach ($sets as $type => $matches) { $formatted = array(); foreach ($matches as $match) { $format = array( 'offset' => $match[1][1], 'id' => $match[1][0], ); if (isset($match[2][0])) { $format['tail'] = $match[2][0]; } $formatted[] = $format; } $results[$type] = $formatted; } return $results; } - public function markupObjectEmbed($matches) { + public function markupObjectEmbed(array $matches) { if (!$this->isFlatText($matches[0])) { return $matches[0]; } return $this->markupObject(array( 'type' => 'embed', 'id' => $matches[1], 'options' => idx($matches, 2), 'original' => $matches[0], )); } - public function markupObjectReference($matches) { + public function markupObjectReference(array $matches) { if (!$this->isFlatText($matches[0])) { return $matches[0]; } return $this->markupObject(array( 'type' => 'ref', 'id' => $matches[1], 'anchor' => idx($matches, 2), 'original' => $matches[0], )); } private function markupObject(array $params) { if (!$this->shouldMarkupObject($params)) { return $params['original']; } $regex = trim( PhabricatorEnv::getEnvConfig('remarkup.ignored-object-names')); if ($regex && preg_match($regex, $params['original'])) { return $params['original']; } $engine = $this->getEngine(); $token = $engine->storeText('x'); $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); $metadata = $engine->getTextMetadata($metadata_key, array()); $metadata[] = array( 'token' => $token, ) + $params; $engine->setTextMetadata($metadata_key, $metadata); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); $metadata = $engine->getTextMetadata($metadata_key, array()); if (!$metadata) { return; } $ids = ipull($metadata, 'id'); $objects = $this->loadObjects($ids); // For objects that are invalid or which the user can't see, just render // the original text. // TODO: We should probably distinguish between these cases and render a // "you can't see this" state for nonvisible objects. foreach ($metadata as $key => $spec) { if (empty($objects[$spec['id']])) { $engine->overwriteStoredText( $spec['token'], $spec['original']); unset($metadata[$key]); } } $phids = $engine->getTextMetadata(self::KEY_MENTIONED_OBJECTS, array()); foreach ($objects as $object) { $phids[$object->getPHID()] = $object->getPHID(); } $engine->setTextMetadata(self::KEY_MENTIONED_OBJECTS, $phids); $handles = $this->loadHandles($objects); foreach ($metadata as $key => $spec) { $handle = $handles[$spec['id']]; $object = $objects[$spec['id']]; switch ($spec['type']) { case 'ref': $view = $this->renderObjectRefForAnyMedia( $object, $handle, $spec['anchor'], $spec['id']); break; case 'embed': $spec['options'] = $this->assertFlatText($spec['options']); $view = $this->renderObjectEmbedForAnyMedia( $object, $handle, $spec['options']); break; } $engine->overwriteStoredText($spec['token'], $view); } $engine->setTextMetadata($metadata_key, array()); } }