Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
12241 views
1
<?php
2
3
final class PhabricatorEmbedFileRemarkupRule
4
extends PhabricatorObjectRemarkupRule {
5
6
private $viewer;
7
8
const KEY_ATTACH_INTENT_FILE_PHIDS = 'files.attach-intent';
9
10
protected function getObjectNamePrefix() {
11
return 'F';
12
}
13
14
protected function loadObjects(array $ids) {
15
$engine = $this->getEngine();
16
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,
24
))
25
->execute();
26
$objects = mpull($objects, null, 'getID');
27
28
29
// Identify files embedded in the block with "attachment intent", i.e.
30
// those files which the user appears to want to attach to the object.
31
// Files referenced inside quoted blocks are not considered to have this
32
// attachment intent.
33
34
$metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();
35
$metadata = $engine->getTextMetadata($metadata_key, array());
36
37
$attach_key = self::KEY_ATTACH_INTENT_FILE_PHIDS;
38
$attach_phids = $engine->getTextMetadata($attach_key, array());
39
40
foreach ($metadata as $item) {
41
42
// If this reference was inside a quoted block, don't count it. Quoting
43
// someone else doesn't establish an intent to attach a file.
44
$depth = idx($item, 'quote.depth');
45
if ($depth > 0) {
46
continue;
47
}
48
49
$id = $item['id'];
50
$file = idx($objects, $id);
51
52
if (!$file) {
53
continue;
54
}
55
56
$attach_phids[] = $file->getPHID();
57
}
58
59
$attach_phids = array_fuse($attach_phids);
60
$attach_phids = array_keys($attach_phids);
61
62
$engine->setTextMetadata($attach_key, $attach_phids);
63
64
65
return $objects;
66
}
67
68
protected function renderObjectEmbed(
69
$object,
70
PhabricatorObjectHandle $handle,
71
$options) {
72
73
$options = $this->getFileOptions($options) + array(
74
'name' => $object->getName(),
75
);
76
77
$is_viewable_image = $object->isViewableImage();
78
$is_audio = $object->isAudio();
79
$is_video = $object->isVideo();
80
$force_link = ($options['layout'] == 'link');
81
82
// If a file is both audio and video, as with "application/ogg" by default,
83
// render it as video but allow the user to specify `media=audio` if they
84
// want to force it to render as audio.
85
if ($is_audio && $is_video) {
86
$media = $options['media'];
87
if ($media == 'audio') {
88
$is_video = false;
89
} else {
90
$is_audio = false;
91
}
92
}
93
94
$options['viewable'] = ($is_viewable_image || $is_audio || $is_video);
95
96
if ($is_viewable_image && !$force_link) {
97
return $this->renderImageFile($object, $handle, $options);
98
} else if ($is_video && !$force_link) {
99
return $this->renderVideoFile($object, $handle, $options);
100
} else if ($is_audio && !$force_link) {
101
return $this->renderAudioFile($object, $handle, $options);
102
} else {
103
return $this->renderFileLink($object, $handle, $options);
104
}
105
}
106
107
private function getFileOptions($option_string) {
108
$options = array(
109
'size' => null,
110
'layout' => 'left',
111
'float' => false,
112
'width' => null,
113
'height' => null,
114
'alt' => null,
115
'media' => null,
116
'autoplay' => null,
117
'loop' => null,
118
);
119
120
if ($option_string) {
121
$option_string = trim($option_string, ', ');
122
$parser = new PhutilSimpleOptions();
123
$options = $parser->parse($option_string) + $options;
124
}
125
126
return $options;
127
}
128
129
private function renderImageFile(
130
PhabricatorFile $file,
131
PhabricatorObjectHandle $handle,
132
array $options) {
133
134
require_celerity_resource('phui-lightbox-css');
135
136
$attrs = array();
137
$image_class = 'phabricator-remarkup-embed-image';
138
139
$use_size = true;
140
if (!$options['size']) {
141
$width = $this->parseDimension($options['width']);
142
$height = $this->parseDimension($options['height']);
143
if ($width || $height) {
144
$use_size = false;
145
$attrs += array(
146
'src' => $file->getBestURI(),
147
'width' => $width,
148
'height' => $height,
149
);
150
}
151
}
152
153
if ($use_size) {
154
switch ((string)$options['size']) {
155
case 'full':
156
$attrs += array(
157
'src' => $file->getBestURI(),
158
'height' => $file->getImageHeight(),
159
'width' => $file->getImageWidth(),
160
);
161
$image_class = 'phabricator-remarkup-embed-image-full';
162
break;
163
// Displays "full" in normal Remarkup, "wide" in Documents
164
case 'wide':
165
$attrs += array(
166
'src' => $file->getBestURI(),
167
'width' => $file->getImageWidth(),
168
);
169
$image_class = 'phabricator-remarkup-embed-image-wide';
170
break;
171
case 'thumb':
172
default:
173
$preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW;
174
$xform = PhabricatorFileTransform::getTransformByKey($preview_key);
175
176
$existing_xform = $file->getTransform($preview_key);
177
if ($existing_xform) {
178
$xform_uri = $existing_xform->getCDNURI('data');
179
} else {
180
$xform_uri = $file->getURIForTransform($xform);
181
}
182
183
$attrs['src'] = $xform_uri;
184
185
$dimensions = $xform->getTransformedDimensions($file);
186
if ($dimensions) {
187
list($x, $y) = $dimensions;
188
$attrs['width'] = $x;
189
$attrs['height'] = $y;
190
}
191
break;
192
}
193
}
194
195
$alt = null;
196
if (isset($options['alt'])) {
197
$alt = $options['alt'];
198
}
199
200
if ($alt === null || !strlen($alt)) {
201
$alt = $file->getAltText();
202
}
203
204
$attrs['alt'] = $alt;
205
206
$img = phutil_tag('img', $attrs);
207
208
$embed = javelin_tag(
209
'a',
210
array(
211
'href' => $file->getBestURI(),
212
'class' => $image_class,
213
'sigil' => 'lightboxable',
214
'meta' => array(
215
'phid' => $file->getPHID(),
216
'uri' => $file->getBestURI(),
217
'dUri' => $file->getDownloadURI(),
218
'alt' => $alt,
219
'viewable' => true,
220
'monogram' => $file->getMonogram(),
221
),
222
),
223
$img);
224
225
switch ($options['layout']) {
226
case 'right':
227
case 'center':
228
case 'inline':
229
case 'left':
230
$layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout'];
231
break;
232
default:
233
$layout_class = 'phabricator-remarkup-embed-layout-left';
234
break;
235
}
236
237
if ($options['float']) {
238
switch ($options['layout']) {
239
case 'center':
240
case 'inline':
241
break;
242
case 'right':
243
$layout_class .= ' phabricator-remarkup-embed-float-right';
244
break;
245
case 'left':
246
default:
247
$layout_class .= ' phabricator-remarkup-embed-float-left';
248
break;
249
}
250
}
251
252
return phutil_tag(
253
($options['layout'] == 'inline' ? 'span' : 'div'),
254
array(
255
'class' => $layout_class,
256
),
257
$embed);
258
}
259
260
private function renderAudioFile(
261
PhabricatorFile $file,
262
PhabricatorObjectHandle $handle,
263
array $options) {
264
return $this->renderMediaFile('audio', $file, $handle, $options);
265
}
266
267
private function renderVideoFile(
268
PhabricatorFile $file,
269
PhabricatorObjectHandle $handle,
270
array $options) {
271
return $this->renderMediaFile('video', $file, $handle, $options);
272
}
273
274
private function renderMediaFile(
275
$tag,
276
PhabricatorFile $file,
277
PhabricatorObjectHandle $handle,
278
array $options) {
279
280
$is_video = ($tag == 'video');
281
282
if (idx($options, 'autoplay')) {
283
$preload = 'auto';
284
$autoplay = 'autoplay';
285
} else {
286
// If we don't preload video, the user can't see the first frame and
287
// has no clue what they're looking at, so always preload.
288
if ($is_video) {
289
$preload = 'auto';
290
} else {
291
$preload = 'none';
292
}
293
$autoplay = null;
294
}
295
296
// Rendering contexts like feed can disable autoplay.
297
$engine = $this->getEngine();
298
if ($engine->getConfig('autoplay.disable')) {
299
$autoplay = null;
300
}
301
302
if ($is_video) {
303
// See T13135. Chrome refuses to play videos with type "video/quicktime",
304
// even though it may actually be able to play them. The least awful fix
305
// based on available information is to simply omit the "type" attribute
306
// from `<source />` tags. This causes Chrome to try to play the video
307
// and realize it can, and does not appear to produce any bad behavior in
308
// any other browser.
309
$mime_type = null;
310
} else {
311
$mime_type = $file->getMimeType();
312
}
313
314
return $this->newTag(
315
$tag,
316
array(
317
'controls' => 'controls',
318
'preload' => $preload,
319
'autoplay' => $autoplay,
320
'loop' => idx($options, 'loop') ? 'loop' : null,
321
'alt' => $options['alt'],
322
'class' => 'phabricator-media',
323
),
324
$this->newTag(
325
'source',
326
array(
327
'src' => $file->getBestURI(),
328
'type' => $mime_type,
329
)));
330
}
331
332
private function renderFileLink(
333
PhabricatorFile $file,
334
PhabricatorObjectHandle $handle,
335
array $options) {
336
337
return id(new PhabricatorFileLinkView())
338
->setViewer($this->viewer)
339
->setFilePHID($file->getPHID())
340
->setFileName($this->assertFlatText($options['name']))
341
->setFileDownloadURI($file->getDownloadURI())
342
->setFileViewURI($file->getBestURI())
343
->setFileViewable((bool)$options['viewable'])
344
->setFileSize(phutil_format_bytes($file->getByteSize()))
345
->setFileMonogram($file->getMonogram());
346
}
347
348
private function parseDimension($string) {
349
if ($string === null || !strlen($string)) {
350
return null;
351
}
352
$string = trim($string);
353
354
if (preg_match('/^(?:\d*\\.)?\d+%?$/', $string)) {
355
return $string;
356
}
357
358
return null;
359
}
360
361
}
362
363