Path: blob/master/src/applications/files/markup/PhabricatorImageRemarkupRule.php
12241 views
<?php12final class PhabricatorImageRemarkupRule extends PhutilRemarkupRule {34const KEY_RULE_EXTERNAL_IMAGE = 'rule.external-image';56public function getPriority() {7return 200.0;8}910public function apply($text) {11return preg_replace_callback(12'@{(image|img) ((?:[^}\\\\]+|\\\\.)*)}@m',13array($this, 'markupImage'),14$text);15}1617public function markupImage(array $matches) {18if (!$this->isFlatText($matches[0])) {19return $matches[0];20}2122$args = array();23$defaults = array(24'uri' => null,25'alt' => null,26'width' => null,27'height' => null,28);2930$trimmed_match = trim($matches[2]);31if ($this->isURI($trimmed_match)) {32$args['uri'] = $trimmed_match;33} else {34$parser = new PhutilSimpleOptions();35$keys = $parser->parse($trimmed_match);3637$uri_key = '';38foreach (array('src', 'uri', 'url') as $key) {39if (array_key_exists($key, $keys)) {40$uri_key = $key;41}42}43if ($uri_key) {44$args['uri'] = $keys[$uri_key];45}46$args += $keys;47}4849$args += $defaults;5051$uri_arg = $args['uri'];52if ($uri_arg === null || !strlen($uri_arg)) {53return $matches[0];54}5556// Make sure this is something that looks roughly like a real URI. We'll57// validate it more carefully before proxying it, but if whatever the user58// has typed isn't even close, just decline to activate the rule behavior.59try {60$uri = new PhutilURI($uri_arg);6162if ($uri->getProtocol() === null || !strlen($uri->getProtocol())) {63return $matches[0];64}6566$args['uri'] = (string)$uri;67} catch (Exception $ex) {68return $matches[0];69}7071$engine = $this->getEngine();72$metadata_key = self::KEY_RULE_EXTERNAL_IMAGE;73$metadata = $engine->getTextMetadata($metadata_key, array());7475$token = $engine->storeText('<img>');7677$metadata[] = array(78'token' => $token,79'args' => $args,80);8182$engine->setTextMetadata($metadata_key, $metadata);8384return $token;85}8687public function didMarkupText() {88$engine = $this->getEngine();89$metadata_key = self::KEY_RULE_EXTERNAL_IMAGE;90$images = $engine->getTextMetadata($metadata_key, array());91$engine->setTextMetadata($metadata_key, array());9293if (!$images) {94return;95}9697// Look for images we've already successfully fetched that aren't about98// to get eaten by the GC. For any we find, we can just emit a normal99// "<img />" tag pointing directly to the file.100101// For files which we don't hit in the cache, we emit a placeholder102// instead and use AJAX to actually perform the fetch.103104$digests = array();105foreach ($images as $image) {106$uri = $image['args']['uri'];107$digests[] = PhabricatorHash::digestForIndex($uri);108}109110$caches = id(new PhabricatorFileExternalRequest())->loadAllWhere(111'uriIndex IN (%Ls) AND isSuccessful = 1 AND ttl > %d',112$digests,113PhabricatorTime::getNow() + phutil_units('1 hour in seconds'));114115$file_phids = array();116foreach ($caches as $cache) {117$file_phids[$cache->getFilePHID()] = $cache->getURI();118}119120$file_map = array();121if ($file_phids) {122$files = id(new PhabricatorFileQuery())123->setViewer(PhabricatorUser::getOmnipotentUser())124->withPHIDs(array_keys($file_phids))125->execute();126foreach ($files as $file) {127$phid = $file->getPHID();128129$file_remote_uri = $file_phids[$phid];130$file_view_uri = $file->getViewURI();131132$file_map[$file_remote_uri] = $file_view_uri;133}134}135136foreach ($images as $image) {137$args = $image['args'];138$uri = $args['uri'];139140$direct_uri = idx($file_map, $uri);141if ($direct_uri) {142$img = phutil_tag(143'img',144array(145'src' => $direct_uri,146'alt' => $args['alt'],147'width' => $args['width'],148'height' => $args['height'],149));150} else {151$src_uri = id(new PhutilURI('/file/imageproxy/'))152->replaceQueryParam('uri', $uri);153154$img = id(new PHUIRemarkupImageView())155->setURI($src_uri)156->setAlt($args['alt'])157->setWidth($args['width'])158->setHeight($args['height']);159}160161$engine->overwriteStoredText($image['token'], $img);162}163}164165private function isURI($uri_string) {166// Very simple check to make sure it starts with either http or https.167// If it does, we'll try to treat it like a valid URI168return preg_match('~^https?\:\/\/.*\z~i', $uri_string);169}170171}172173174