@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 370 lines 10 kB view raw
1<?php 2 3final 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 /** 108 * @param string $option_string File display options. See "Embedding Images" 109 * in @{article:Remarkup Reference} 110 */ 111 private function getFileOptions($option_string) { 112 $options = array( 113 'size' => null, 114 'layout' => 'left', 115 'float' => false, 116 'width' => null, 117 'height' => null, 118 'alt' => null, 119 'media' => null, 120 'autoplay' => null, 121 'loop' => null, 122 ); 123 124 if ($option_string) { 125 $option_string = trim($option_string, ', '); 126 $parser = new PhutilSimpleOptions(); 127 $options = $parser->parse($option_string) + $options; 128 } 129 130 return $options; 131 } 132 133 private function renderImageFile( 134 PhabricatorFile $file, 135 PhabricatorObjectHandle $handle, 136 array $options) { 137 138 require_celerity_resource('phui-lightbox-css'); 139 140 $attrs = array(); 141 $image_class = 'phabricator-remarkup-embed-image'; 142 143 $use_size = true; 144 if (!$options['size']) { 145 $width = $this->parseDimension($options['width']); 146 $height = $this->parseDimension($options['height']); 147 if ($width || $height) { 148 $use_size = false; 149 $attrs += array( 150 'src' => $file->getBestURI(), 151 'width' => $width, 152 'height' => $height, 153 ); 154 } 155 } 156 157 if ($use_size) { 158 switch ((string)$options['size']) { 159 case 'full': 160 $attrs += array( 161 'src' => $file->getBestURI(), 162 'height' => $file->getImageHeight(), 163 'width' => $file->getImageWidth(), 164 'loading' => 'lazy', 165 ); 166 $image_class = 'phabricator-remarkup-embed-image-full'; 167 break; 168 // Displays "full" in normal Remarkup, "wide" in Documents 169 case 'wide': 170 $attrs += array( 171 'src' => $file->getBestURI(), 172 'width' => $file->getImageWidth(), 173 ); 174 $image_class = 'phabricator-remarkup-embed-image-wide'; 175 break; 176 case 'thumb': 177 default: 178 $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW; 179 $xform = PhabricatorFileTransform::getTransformByKey($preview_key); 180 181 $existing_xform = $file->getTransform($preview_key); 182 if ($existing_xform) { 183 $xform_uri = $existing_xform->getCDNURI('data'); 184 } else { 185 $xform_uri = $file->getURIForTransform($xform); 186 } 187 188 $attrs['src'] = $xform_uri; 189 190 $dimensions = $xform->getTransformedDimensions($file); 191 if ($dimensions) { 192 list($x, $y) = $dimensions; 193 $attrs['width'] = $x; 194 $attrs['height'] = $y; 195 } 196 break; 197 } 198 } 199 200 $alt = null; 201 if (isset($options['alt'])) { 202 $alt = $options['alt']; 203 } 204 205 // PhutilSimpleOptions returns a bool if the option is set without a value 206 if (is_bool($alt) || !phutil_nonempty_string($alt)) { 207 $alt = $file->getAltText(); 208 } 209 210 $attrs['alt'] = $alt; 211 212 $img = phutil_tag('img', $attrs); 213 214 $embed = javelin_tag( 215 'a', 216 array( 217 'href' => $file->getBestURI(), 218 'class' => $image_class, 219 'sigil' => 'lightboxable', 220 'meta' => array( 221 'phid' => $file->getPHID(), 222 'uri' => $file->getBestURI(), 223 'dUri' => $file->getDownloadURI(), 224 'alt' => $alt, 225 'viewable' => true, 226 'monogram' => $file->getMonogram(), 227 ), 228 ), 229 $img); 230 231 switch ($options['layout']) { 232 case 'right': 233 case 'center': 234 case 'inline': 235 case 'left': 236 $layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout']; 237 break; 238 default: 239 $layout_class = 'phabricator-remarkup-embed-layout-left'; 240 break; 241 } 242 243 if ($options['float']) { 244 switch ($options['layout']) { 245 case 'center': 246 case 'inline': 247 break; 248 case 'right': 249 $layout_class .= ' phabricator-remarkup-embed-float-right'; 250 break; 251 case 'left': 252 default: 253 $layout_class .= ' phabricator-remarkup-embed-float-left'; 254 break; 255 } 256 } 257 258 return phutil_tag( 259 ($options['layout'] == 'inline' ? 'span' : 'div'), 260 array( 261 'class' => $layout_class, 262 ), 263 $embed); 264 } 265 266 private function renderAudioFile( 267 PhabricatorFile $file, 268 PhabricatorObjectHandle $handle, 269 array $options) { 270 return $this->renderMediaFile('audio', $file, $handle, $options); 271 } 272 273 private function renderVideoFile( 274 PhabricatorFile $file, 275 PhabricatorObjectHandle $handle, 276 array $options) { 277 return $this->renderMediaFile('video', $file, $handle, $options); 278 } 279 280 private function renderMediaFile( 281 $tag, 282 PhabricatorFile $file, 283 PhabricatorObjectHandle $handle, 284 array $options) { 285 286 $is_video = ($tag == 'video'); 287 288 if (idx($options, 'autoplay')) { 289 $preload = 'auto'; 290 $autoplay = 'autoplay'; 291 } else { 292 // If we don't preload video, the user can't see the first frame and 293 // has no clue what they're looking at, so always preload. 294 if ($is_video) { 295 $preload = 'auto'; 296 } else { 297 $preload = 'none'; 298 } 299 $autoplay = null; 300 } 301 302 // Rendering contexts like feed can disable autoplay. 303 $engine = $this->getEngine(); 304 if ($engine->getConfig('autoplay.disable')) { 305 $autoplay = null; 306 } 307 308 if ($is_video) { 309 // See T13135. Chrome refuses to play videos with type "video/quicktime", 310 // even though it may actually be able to play them. The least awful fix 311 // based on available information is to simply omit the "type" attribute 312 // from `<source />` tags. This causes Chrome to try to play the video 313 // and realize it can, and does not appear to produce any bad behavior in 314 // any other browser. 315 $mime_type = null; 316 } else { 317 $mime_type = $file->getMimeType(); 318 } 319 320 $thumb_class = null; 321 if (isset($options['size']) && $options['size'] == 'thumb') { 322 $thumb_class = ' video-thumb'; 323 } 324 325 return $this->newTag( 326 $tag, 327 array( 328 'controls' => 'controls', 329 'preload' => $preload, 330 'autoplay' => $autoplay, 331 'loop' => idx($options, 'loop') ? 'loop' : null, 332 'alt' => $options['alt'], 333 'class' => 'phabricator-media'.$thumb_class, 334 ), 335 $this->newTag( 336 'source', 337 array( 338 'src' => $file->getBestURI(), 339 'type' => $mime_type, 340 ))); 341 } 342 343 private function renderFileLink( 344 PhabricatorFile $file, 345 PhabricatorObjectHandle $handle, 346 array $options) { 347 348 return id(new PhabricatorFileLinkView()) 349 ->setViewer($this->viewer) 350 ->setFilePHID($file->getPHID()) 351 ->setFileName($this->assertFlatText($options['name'])) 352 ->setFileDownloadURI($file->getDownloadURI()) 353 ->setFileViewURI($file->getBestURI()) 354 ->setFileViewable((bool)$options['viewable']) 355 ->setFileSize(phutil_format_bytes($file->getByteSize())) 356 ->setFileMonogram($file->getMonogram()); 357 } 358 359 private function parseDimension($string) { 360 if ($string !== null) { 361 $string = trim($string); 362 if (preg_match('/^(?:\d*\\.)?\d+%?$/', $string)) { 363 return $string; 364 } 365 } 366 367 return null; 368 } 369 370}