Remove product literal strings in "pht()", part 5
[phabricator.git] / src / applications / files / markup / PhabricatorEmbedFileRemarkupRule.php
blob8e2d0cf0c9209cbecd9a4fcc4b180b85a6bda3ee
1 <?php
3 final class PhabricatorEmbedFileRemarkupRule
4 extends PhabricatorObjectRemarkupRule {
6 private $viewer;
8 const KEY_EMBED_FILE_PHIDS = 'phabricator.embedded-file-phids';
10 protected function getObjectNamePrefix() {
11 return 'F';
14 protected function loadObjects(array $ids) {
15 $engine = $this->getEngine();
17 $this->viewer = $engine->getConfig('viewer');
18 $objects = id(new PhabricatorFileQuery())
19 ->setViewer($this->viewer)
20 ->withIDs($ids)
21 ->needTransforms(
22 array(
23 PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW,
25 ->execute();
27 $phids_key = self::KEY_EMBED_FILE_PHIDS;
28 $phids = $engine->getTextMetadata($phids_key, array());
29 foreach (mpull($objects, 'getPHID') as $phid) {
30 $phids[] = $phid;
32 $engine->setTextMetadata($phids_key, $phids);
34 return $objects;
37 protected function renderObjectEmbed(
38 $object,
39 PhabricatorObjectHandle $handle,
40 $options) {
42 $options = $this->getFileOptions($options) + array(
43 'name' => $object->getName(),
46 $is_viewable_image = $object->isViewableImage();
47 $is_audio = $object->isAudio();
48 $is_video = $object->isVideo();
49 $force_link = ($options['layout'] == 'link');
51 // If a file is both audio and video, as with "application/ogg" by default,
52 // render it as video but allow the user to specify `media=audio` if they
53 // want to force it to render as audio.
54 if ($is_audio && $is_video) {
55 $media = $options['media'];
56 if ($media == 'audio') {
57 $is_video = false;
58 } else {
59 $is_audio = false;
63 $options['viewable'] = ($is_viewable_image || $is_audio || $is_video);
65 if ($is_viewable_image && !$force_link) {
66 return $this->renderImageFile($object, $handle, $options);
67 } else if ($is_video && !$force_link) {
68 return $this->renderVideoFile($object, $handle, $options);
69 } else if ($is_audio && !$force_link) {
70 return $this->renderAudioFile($object, $handle, $options);
71 } else {
72 return $this->renderFileLink($object, $handle, $options);
76 private function getFileOptions($option_string) {
77 $options = array(
78 'size' => null,
79 'layout' => 'left',
80 'float' => false,
81 'width' => null,
82 'height' => null,
83 'alt' => null,
84 'media' => null,
85 'autoplay' => null,
86 'loop' => null,
89 if ($option_string) {
90 $option_string = trim($option_string, ', ');
91 $parser = new PhutilSimpleOptions();
92 $options = $parser->parse($option_string) + $options;
95 return $options;
98 private function renderImageFile(
99 PhabricatorFile $file,
100 PhabricatorObjectHandle $handle,
101 array $options) {
103 require_celerity_resource('phui-lightbox-css');
105 $attrs = array();
106 $image_class = 'phabricator-remarkup-embed-image';
108 $use_size = true;
109 if (!$options['size']) {
110 $width = $this->parseDimension($options['width']);
111 $height = $this->parseDimension($options['height']);
112 if ($width || $height) {
113 $use_size = false;
114 $attrs += array(
115 'src' => $file->getBestURI(),
116 'width' => $width,
117 'height' => $height,
122 if ($use_size) {
123 switch ((string)$options['size']) {
124 case 'full':
125 $attrs += array(
126 'src' => $file->getBestURI(),
127 'height' => $file->getImageHeight(),
128 'width' => $file->getImageWidth(),
130 $image_class = 'phabricator-remarkup-embed-image-full';
131 break;
132 // Displays "full" in normal Remarkup, "wide" in Documents
133 case 'wide':
134 $attrs += array(
135 'src' => $file->getBestURI(),
136 'width' => $file->getImageWidth(),
138 $image_class = 'phabricator-remarkup-embed-image-wide';
139 break;
140 case 'thumb':
141 default:
142 $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW;
143 $xform = PhabricatorFileTransform::getTransformByKey($preview_key);
145 $existing_xform = $file->getTransform($preview_key);
146 if ($existing_xform) {
147 $xform_uri = $existing_xform->getCDNURI('data');
148 } else {
149 $xform_uri = $file->getURIForTransform($xform);
152 $attrs['src'] = $xform_uri;
154 $dimensions = $xform->getTransformedDimensions($file);
155 if ($dimensions) {
156 list($x, $y) = $dimensions;
157 $attrs['width'] = $x;
158 $attrs['height'] = $y;
160 break;
164 $alt = null;
165 if (isset($options['alt'])) {
166 $alt = $options['alt'];
169 if (!strlen($alt)) {
170 $alt = $file->getAltText();
173 $attrs['alt'] = $alt;
175 $img = phutil_tag('img', $attrs);
177 $embed = javelin_tag(
178 'a',
179 array(
180 'href' => $file->getBestURI(),
181 'class' => $image_class,
182 'sigil' => 'lightboxable',
183 'meta' => array(
184 'phid' => $file->getPHID(),
185 'uri' => $file->getBestURI(),
186 'dUri' => $file->getDownloadURI(),
187 'alt' => $alt,
188 'viewable' => true,
189 'monogram' => $file->getMonogram(),
192 $img);
194 switch ($options['layout']) {
195 case 'right':
196 case 'center':
197 case 'inline':
198 case 'left':
199 $layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout'];
200 break;
201 default:
202 $layout_class = 'phabricator-remarkup-embed-layout-left';
203 break;
206 if ($options['float']) {
207 switch ($options['layout']) {
208 case 'center':
209 case 'inline':
210 break;
211 case 'right':
212 $layout_class .= ' phabricator-remarkup-embed-float-right';
213 break;
214 case 'left':
215 default:
216 $layout_class .= ' phabricator-remarkup-embed-float-left';
217 break;
221 return phutil_tag(
222 ($options['layout'] == 'inline' ? 'span' : 'div'),
223 array(
224 'class' => $layout_class,
226 $embed);
229 private function renderAudioFile(
230 PhabricatorFile $file,
231 PhabricatorObjectHandle $handle,
232 array $options) {
233 return $this->renderMediaFile('audio', $file, $handle, $options);
236 private function renderVideoFile(
237 PhabricatorFile $file,
238 PhabricatorObjectHandle $handle,
239 array $options) {
240 return $this->renderMediaFile('video', $file, $handle, $options);
243 private function renderMediaFile(
244 $tag,
245 PhabricatorFile $file,
246 PhabricatorObjectHandle $handle,
247 array $options) {
249 $is_video = ($tag == 'video');
251 if (idx($options, 'autoplay')) {
252 $preload = 'auto';
253 $autoplay = 'autoplay';
254 } else {
255 // If we don't preload video, the user can't see the first frame and
256 // has no clue what they're looking at, so always preload.
257 if ($is_video) {
258 $preload = 'auto';
259 } else {
260 $preload = 'none';
262 $autoplay = null;
265 // Rendering contexts like feed can disable autoplay.
266 $engine = $this->getEngine();
267 if ($engine->getConfig('autoplay.disable')) {
268 $autoplay = null;
271 if ($is_video) {
272 // See T13135. Chrome refuses to play videos with type "video/quicktime",
273 // even though it may actually be able to play them. The least awful fix
274 // based on available information is to simply omit the "type" attribute
275 // from `<source />` tags. This causes Chrome to try to play the video
276 // and realize it can, and does not appear to produce any bad behavior in
277 // any other browser.
278 $mime_type = null;
279 } else {
280 $mime_type = $file->getMimeType();
283 return $this->newTag(
284 $tag,
285 array(
286 'controls' => 'controls',
287 'preload' => $preload,
288 'autoplay' => $autoplay,
289 'loop' => idx($options, 'loop') ? 'loop' : null,
290 'alt' => $options['alt'],
291 'class' => 'phabricator-media',
293 $this->newTag(
294 'source',
295 array(
296 'src' => $file->getBestURI(),
297 'type' => $mime_type,
298 )));
301 private function renderFileLink(
302 PhabricatorFile $file,
303 PhabricatorObjectHandle $handle,
304 array $options) {
306 return id(new PhabricatorFileLinkView())
307 ->setViewer($this->viewer)
308 ->setFilePHID($file->getPHID())
309 ->setFileName($this->assertFlatText($options['name']))
310 ->setFileDownloadURI($file->getDownloadURI())
311 ->setFileViewURI($file->getBestURI())
312 ->setFileViewable((bool)$options['viewable'])
313 ->setFileSize(phutil_format_bytes($file->getByteSize()))
314 ->setFileMonogram($file->getMonogram());
317 private function parseDimension($string) {
318 $string = trim($string);
320 if (preg_match('/^(?:\d*\\.)?\d+%?$/', $string)) {
321 return $string;
324 return null;