Path: blob/master/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
12241 views
<?php12final class PhabricatorEmbedFileRemarkupRule3extends PhabricatorObjectRemarkupRule {45private $viewer;67const KEY_ATTACH_INTENT_FILE_PHIDS = 'files.attach-intent';89protected function getObjectNamePrefix() {10return 'F';11}1213protected function loadObjects(array $ids) {14$engine = $this->getEngine();1516$this->viewer = $engine->getConfig('viewer');17$objects = id(new PhabricatorFileQuery())18->setViewer($this->viewer)19->withIDs($ids)20->needTransforms(21array(22PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW,23))24->execute();25$objects = mpull($objects, null, 'getID');262728// Identify files embedded in the block with "attachment intent", i.e.29// those files which the user appears to want to attach to the object.30// Files referenced inside quoted blocks are not considered to have this31// attachment intent.3233$metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();34$metadata = $engine->getTextMetadata($metadata_key, array());3536$attach_key = self::KEY_ATTACH_INTENT_FILE_PHIDS;37$attach_phids = $engine->getTextMetadata($attach_key, array());3839foreach ($metadata as $item) {4041// If this reference was inside a quoted block, don't count it. Quoting42// someone else doesn't establish an intent to attach a file.43$depth = idx($item, 'quote.depth');44if ($depth > 0) {45continue;46}4748$id = $item['id'];49$file = idx($objects, $id);5051if (!$file) {52continue;53}5455$attach_phids[] = $file->getPHID();56}5758$attach_phids = array_fuse($attach_phids);59$attach_phids = array_keys($attach_phids);6061$engine->setTextMetadata($attach_key, $attach_phids);626364return $objects;65}6667protected function renderObjectEmbed(68$object,69PhabricatorObjectHandle $handle,70$options) {7172$options = $this->getFileOptions($options) + array(73'name' => $object->getName(),74);7576$is_viewable_image = $object->isViewableImage();77$is_audio = $object->isAudio();78$is_video = $object->isVideo();79$force_link = ($options['layout'] == 'link');8081// If a file is both audio and video, as with "application/ogg" by default,82// render it as video but allow the user to specify `media=audio` if they83// want to force it to render as audio.84if ($is_audio && $is_video) {85$media = $options['media'];86if ($media == 'audio') {87$is_video = false;88} else {89$is_audio = false;90}91}9293$options['viewable'] = ($is_viewable_image || $is_audio || $is_video);9495if ($is_viewable_image && !$force_link) {96return $this->renderImageFile($object, $handle, $options);97} else if ($is_video && !$force_link) {98return $this->renderVideoFile($object, $handle, $options);99} else if ($is_audio && !$force_link) {100return $this->renderAudioFile($object, $handle, $options);101} else {102return $this->renderFileLink($object, $handle, $options);103}104}105106private function getFileOptions($option_string) {107$options = array(108'size' => null,109'layout' => 'left',110'float' => false,111'width' => null,112'height' => null,113'alt' => null,114'media' => null,115'autoplay' => null,116'loop' => null,117);118119if ($option_string) {120$option_string = trim($option_string, ', ');121$parser = new PhutilSimpleOptions();122$options = $parser->parse($option_string) + $options;123}124125return $options;126}127128private function renderImageFile(129PhabricatorFile $file,130PhabricatorObjectHandle $handle,131array $options) {132133require_celerity_resource('phui-lightbox-css');134135$attrs = array();136$image_class = 'phabricator-remarkup-embed-image';137138$use_size = true;139if (!$options['size']) {140$width = $this->parseDimension($options['width']);141$height = $this->parseDimension($options['height']);142if ($width || $height) {143$use_size = false;144$attrs += array(145'src' => $file->getBestURI(),146'width' => $width,147'height' => $height,148);149}150}151152if ($use_size) {153switch ((string)$options['size']) {154case 'full':155$attrs += array(156'src' => $file->getBestURI(),157'height' => $file->getImageHeight(),158'width' => $file->getImageWidth(),159);160$image_class = 'phabricator-remarkup-embed-image-full';161break;162// Displays "full" in normal Remarkup, "wide" in Documents163case 'wide':164$attrs += array(165'src' => $file->getBestURI(),166'width' => $file->getImageWidth(),167);168$image_class = 'phabricator-remarkup-embed-image-wide';169break;170case 'thumb':171default:172$preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW;173$xform = PhabricatorFileTransform::getTransformByKey($preview_key);174175$existing_xform = $file->getTransform($preview_key);176if ($existing_xform) {177$xform_uri = $existing_xform->getCDNURI('data');178} else {179$xform_uri = $file->getURIForTransform($xform);180}181182$attrs['src'] = $xform_uri;183184$dimensions = $xform->getTransformedDimensions($file);185if ($dimensions) {186list($x, $y) = $dimensions;187$attrs['width'] = $x;188$attrs['height'] = $y;189}190break;191}192}193194$alt = null;195if (isset($options['alt'])) {196$alt = $options['alt'];197}198199if ($alt === null || !strlen($alt)) {200$alt = $file->getAltText();201}202203$attrs['alt'] = $alt;204205$img = phutil_tag('img', $attrs);206207$embed = javelin_tag(208'a',209array(210'href' => $file->getBestURI(),211'class' => $image_class,212'sigil' => 'lightboxable',213'meta' => array(214'phid' => $file->getPHID(),215'uri' => $file->getBestURI(),216'dUri' => $file->getDownloadURI(),217'alt' => $alt,218'viewable' => true,219'monogram' => $file->getMonogram(),220),221),222$img);223224switch ($options['layout']) {225case 'right':226case 'center':227case 'inline':228case 'left':229$layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout'];230break;231default:232$layout_class = 'phabricator-remarkup-embed-layout-left';233break;234}235236if ($options['float']) {237switch ($options['layout']) {238case 'center':239case 'inline':240break;241case 'right':242$layout_class .= ' phabricator-remarkup-embed-float-right';243break;244case 'left':245default:246$layout_class .= ' phabricator-remarkup-embed-float-left';247break;248}249}250251return phutil_tag(252($options['layout'] == 'inline' ? 'span' : 'div'),253array(254'class' => $layout_class,255),256$embed);257}258259private function renderAudioFile(260PhabricatorFile $file,261PhabricatorObjectHandle $handle,262array $options) {263return $this->renderMediaFile('audio', $file, $handle, $options);264}265266private function renderVideoFile(267PhabricatorFile $file,268PhabricatorObjectHandle $handle,269array $options) {270return $this->renderMediaFile('video', $file, $handle, $options);271}272273private function renderMediaFile(274$tag,275PhabricatorFile $file,276PhabricatorObjectHandle $handle,277array $options) {278279$is_video = ($tag == 'video');280281if (idx($options, 'autoplay')) {282$preload = 'auto';283$autoplay = 'autoplay';284} else {285// If we don't preload video, the user can't see the first frame and286// has no clue what they're looking at, so always preload.287if ($is_video) {288$preload = 'auto';289} else {290$preload = 'none';291}292$autoplay = null;293}294295// Rendering contexts like feed can disable autoplay.296$engine = $this->getEngine();297if ($engine->getConfig('autoplay.disable')) {298$autoplay = null;299}300301if ($is_video) {302// See T13135. Chrome refuses to play videos with type "video/quicktime",303// even though it may actually be able to play them. The least awful fix304// based on available information is to simply omit the "type" attribute305// from `<source />` tags. This causes Chrome to try to play the video306// and realize it can, and does not appear to produce any bad behavior in307// any other browser.308$mime_type = null;309} else {310$mime_type = $file->getMimeType();311}312313return $this->newTag(314$tag,315array(316'controls' => 'controls',317'preload' => $preload,318'autoplay' => $autoplay,319'loop' => idx($options, 'loop') ? 'loop' : null,320'alt' => $options['alt'],321'class' => 'phabricator-media',322),323$this->newTag(324'source',325array(326'src' => $file->getBestURI(),327'type' => $mime_type,328)));329}330331private function renderFileLink(332PhabricatorFile $file,333PhabricatorObjectHandle $handle,334array $options) {335336return id(new PhabricatorFileLinkView())337->setViewer($this->viewer)338->setFilePHID($file->getPHID())339->setFileName($this->assertFlatText($options['name']))340->setFileDownloadURI($file->getDownloadURI())341->setFileViewURI($file->getBestURI())342->setFileViewable((bool)$options['viewable'])343->setFileSize(phutil_format_bytes($file->getByteSize()))344->setFileMonogram($file->getMonogram());345}346347private function parseDimension($string) {348if ($string === null || !strlen($string)) {349return null;350}351$string = trim($string);352353if (preg_match('/^(?:\d*\\.)?\d+%?$/', $string)) {354return $string;355}356357return null;358}359360}361362363