@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
at upstream/main 481 lines 12 kB view raw
1<?php 2 3final class PhabricatorFileViewController extends PhabricatorFileController { 4 5 public function shouldAllowPublic() { 6 return true; 7 } 8 9 public function handleRequest(AphrontRequest $request) { 10 $viewer = $request->getViewer(); 11 $id = $request->getURIData('id'); 12 $phid = $request->getURIData('phid'); 13 14 if ($phid) { 15 $file = id(new PhabricatorFileQuery()) 16 ->setViewer($viewer) 17 ->withPHIDs(array($phid)) 18 ->withIsDeleted(false) 19 ->executeOne(); 20 21 if (!$file) { 22 return new Aphront404Response(); 23 } 24 return id(new AphrontRedirectResponse())->setURI($file->getInfoURI()); 25 } 26 27 $file = id(new PhabricatorFileQuery()) 28 ->setViewer($viewer) 29 ->withIDs(array($id)) 30 ->withIsDeleted(false) 31 ->executeOne(); 32 if (!$file) { 33 return new Aphront404Response(); 34 } 35 36 $phid = $file->getPHID(); 37 38 $header = id(new PHUIHeaderView()) 39 ->setViewer($viewer) 40 ->setPolicyObject($file) 41 ->setHeader($file->getName()) 42 ->setHeaderIcon('fa-file-o'); 43 44 $ttl = $file->getTTL(); 45 if ($ttl !== null) { 46 $ttl_tag = id(new PHUITagView()) 47 ->setType(PHUITagView::TYPE_SHADE) 48 ->setColor(PHUITagView::COLOR_YELLOW) 49 ->setName(pht('Temporary')); 50 $header->addTag($ttl_tag); 51 } 52 53 $partial = $file->getIsPartial(); 54 if ($partial) { 55 $partial_tag = id(new PHUITagView()) 56 ->setType(PHUITagView::TYPE_SHADE) 57 ->setColor(PHUITagView::COLOR_ORANGE) 58 ->setName(pht('Partial Upload')); 59 $header->addTag($partial_tag); 60 } 61 62 $curtain = $this->buildCurtainView($file); 63 $timeline = $this->buildTransactionView($file); 64 $crumbs = $this->buildApplicationCrumbs(); 65 $crumbs->addTextCrumb( 66 $file->getMonogram(), 67 $file->getInfoURI()); 68 $crumbs->setBorder(true); 69 70 $object_box = id(new PHUIObjectBoxView()) 71 ->setHeaderText(pht('File Metadata')) 72 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); 73 74 $this->buildPropertyViews($object_box, $file); 75 $title = $file->getName(); 76 77 $file_content = $this->newFileContent($file); 78 79 $view = id(new PHUITwoColumnView()) 80 ->setHeader($header) 81 ->setCurtain($curtain) 82 ->setMainColumn( 83 array( 84 $file_content, 85 $object_box, 86 $timeline, 87 )); 88 89 return $this->newPage() 90 ->setTitle($title) 91 ->setCrumbs($crumbs) 92 ->setPageObjectPHIDs(array($file->getPHID())) 93 ->appendChild($view); 94 } 95 96 private function buildTransactionView(PhabricatorFile $file) { 97 $viewer = $this->getViewer(); 98 99 $timeline = $this->buildTransactionTimeline( 100 $file, 101 new PhabricatorFileTransactionQuery()); 102 103 $comment_view = id(new PhabricatorFileEditEngine()) 104 ->setViewer($viewer) 105 ->buildEditEngineCommentView($file); 106 107 $monogram = $file->getMonogram(); 108 109 $timeline->setQuoteRef($monogram); 110 $comment_view->setTransactionTimeline($timeline); 111 112 return array( 113 $timeline, 114 $comment_view, 115 ); 116 } 117 118 private function buildCurtainView(PhabricatorFile $file) { 119 $viewer = $this->getViewer(); 120 121 $id = $file->getID(); 122 123 $can_edit = PhabricatorPolicyFilter::hasCapability( 124 $viewer, 125 $file, 126 PhabricatorPolicyCapability::CAN_EDIT); 127 128 $curtain = $this->newCurtainView($file); 129 130 $can_download = !$file->getIsPartial(); 131 132 if ($file->isViewableInBrowser()) { 133 $curtain->addAction( 134 id(new PhabricatorActionView()) 135 ->setName(pht('View File')) 136 ->setIcon('fa-file-o') 137 ->setHref($file->getViewURI()) 138 ->setDisabled(!$can_download) 139 ->setWorkflow(!$can_download)); 140 } else { 141 $curtain->addAction( 142 id(new PhabricatorActionView()) 143 ->setViewer($viewer) 144 ->setDownload($can_download) 145 ->setName(pht('Download File')) 146 ->setIcon('fa-download') 147 ->setHref($file->getDownloadURI()) 148 ->setDisabled(!$can_download) 149 ->setWorkflow(!$can_download)); 150 } 151 152 $curtain->addAction( 153 id(new PhabricatorActionView()) 154 ->setName(pht('Edit File')) 155 ->setIcon('fa-pencil') 156 ->setHref($this->getApplicationURI("/edit/{$id}/")) 157 ->setWorkflow(!$can_edit) 158 ->setDisabled(!$can_edit)); 159 160 $curtain->addAction( 161 id(new PhabricatorActionView()) 162 ->setName(pht('Delete File')) 163 ->setIcon('fa-times') 164 ->setHref($this->getApplicationURI("/delete/{$id}/")) 165 ->setWorkflow(true) 166 ->setDisabled(!$can_edit)); 167 168 $curtain->addAction( 169 id(new PhabricatorActionView()) 170 ->setName(pht('View Transforms')) 171 ->setIcon('fa-crop') 172 ->setHref($this->getApplicationURI("/transforms/{$id}/"))); 173 174 $phids = array(); 175 176 $viewer_phid = $viewer->getPHID(); 177 $author_phid = $file->getAuthorPHID(); 178 if ($author_phid) { 179 $phids[] = $author_phid; 180 } 181 182 $handles = $viewer->loadHandles($phids); 183 184 if ($author_phid) { 185 $author_refs = id(new PHUICurtainObjectRefListView()) 186 ->setViewer($viewer); 187 188 $author_ref = $author_refs->newObjectRefView() 189 ->setHandle($handles[$author_phid]) 190 ->setEpoch($file->getDateCreated()) 191 ->setHighlighted($author_phid === $viewer_phid); 192 193 $curtain->newPanel() 194 ->setHeaderText(pht('Authored By')) 195 ->appendChild($author_refs); 196 } 197 198 $curtain->newPanel() 199 ->setHeaderText(pht('Size')) 200 ->appendChild(phutil_format_bytes($file->getByteSize())); 201 202 $width = $file->getImageWidth(); 203 $height = $file->getImageHeight(); 204 205 if ($width || $height) { 206 $curtain->newPanel() 207 ->setHeaderText(pht('Dimensions')) 208 ->appendChild( 209 pht( 210 "%spx \xC3\x97 %spx", 211 new PhutilNumber($width), 212 new PhutilNumber($height))); 213 } 214 215 return $curtain; 216 } 217 218 private function buildPropertyViews( 219 PHUIObjectBoxView $box, 220 PhabricatorFile $file) { 221 $request = $this->getRequest(); 222 $viewer = $request->getUser(); 223 224 $tab_group = new PHUITabGroupView(); 225 $box->addTabGroup($tab_group); 226 227 $finfo = new PHUIPropertyListView(); 228 229 $tab_group->addTab( 230 id(new PHUITabView()) 231 ->setName(pht('Details')) 232 ->setKey('details') 233 ->appendChild($finfo)); 234 235 $finfo->addProperty( 236 pht('Mime Type'), 237 $file->getMimeType()); 238 239 $ttl = $file->getTtl(); 240 if ($ttl) { 241 $delta = $ttl - PhabricatorTime::getNow(); 242 243 $finfo->addProperty( 244 pht('Expires'), 245 pht( 246 '%s (%s)', 247 phabricator_datetime($ttl, $viewer), 248 phutil_format_relative_time_detailed($delta))); 249 } 250 251 $is_image = $file->isViewableImage(); 252 if ($is_image) { 253 $image_string = pht('Yes'); 254 $cache_string = $file->getCanCDN() ? pht('Yes') : pht('No'); 255 } else { 256 $image_string = pht('No'); 257 $cache_string = pht('Not Applicable'); 258 } 259 260 $types = array(); 261 if ($file->isViewableImage()) { 262 $types[] = pht('Image'); 263 } 264 265 if ($file->isVideo()) { 266 $types[] = pht('Video'); 267 } 268 269 if ($file->isAudio()) { 270 $types[] = pht('Audio'); 271 } 272 273 if ($file->getCanCDN()) { 274 $types[] = pht('Can CDN'); 275 } 276 277 $builtin = $file->getBuiltinName(); 278 if ($builtin !== null) { 279 $types[] = pht('Builtin ("%s")', $builtin); 280 } 281 282 if ($file->getIsProfileImage()) { 283 $types[] = pht('Profile'); 284 } 285 286 if ($types) { 287 $types = implode(', ', $types); 288 $finfo->addProperty(pht('Attributes'), $types); 289 } 290 291 $finfo->addProperty( 292 pht('Storage Engine'), 293 $file->getStorageEngine()); 294 295 $engine = $this->loadStorageEngine($file); 296 if ($engine && $engine->isChunkEngine()) { 297 $format_name = pht('Chunks'); 298 } else { 299 $format_key = $file->getStorageFormat(); 300 $format = PhabricatorFileStorageFormat::getFormat($format_key); 301 if ($format) { 302 $format_name = $format->getStorageFormatName(); 303 } else { 304 $format_name = pht('Unknown ("%s")', $format_key); 305 } 306 } 307 $finfo->addProperty(pht('Storage Format'), $format_name); 308 309 $finfo->addProperty( 310 pht('Storage Handle'), 311 $file->getStorageHandle()); 312 313 $custom_alt = $file->getCustomAltText(); 314 if (phutil_nonempty_string($custom_alt)) { 315 $finfo->addProperty(pht('Custom Alt Text'), $custom_alt); 316 } 317 318 $default_alt = $file->getDefaultAltText(); 319 if (phutil_nonempty_string($default_alt)) { 320 $finfo->addProperty(pht('Default Alt Text'), $default_alt); 321 } 322 323 $attachments_table = $this->newAttachmentsView($file); 324 325 $tab_group->addTab( 326 id(new PHUITabView()) 327 ->setName(pht('Attached')) 328 ->setKey('attached') 329 ->appendChild($attachments_table)); 330 331 $engine = $this->loadStorageEngine($file); 332 if ($engine) { 333 if ($engine->isChunkEngine()) { 334 $chunkinfo = new PHUIPropertyListView(); 335 336 $tab_group->addTab( 337 id(new PHUITabView()) 338 ->setName(pht('Chunks')) 339 ->setKey('chunks') 340 ->appendChild($chunkinfo)); 341 342 $chunks = id(new PhabricatorFileChunkQuery()) 343 ->setViewer($viewer) 344 ->withChunkHandles(array($file->getStorageHandle())) 345 ->execute(); 346 $chunks = msort($chunks, 'getByteStart'); 347 348 $rows = array(); 349 $completed = array(); 350 foreach ($chunks as $chunk) { 351 $is_complete = $chunk->getDataFilePHID(); 352 353 $rows[] = array( 354 $chunk->getByteStart(), 355 $chunk->getByteEnd(), 356 ($is_complete ? pht('Yes') : pht('No')), 357 ); 358 359 if ($is_complete) { 360 $completed[] = $chunk; 361 } 362 } 363 364 $table = id(new AphrontTableView($rows)) 365 ->setHeaders( 366 array( 367 pht('Offset'), 368 pht('End'), 369 pht('Complete'), 370 )) 371 ->setColumnClasses( 372 array( 373 '', 374 '', 375 'wide', 376 )); 377 378 $chunkinfo->addProperty( 379 pht('Total Chunks'), 380 count($chunks)); 381 382 $chunkinfo->addProperty( 383 pht('Completed Chunks'), 384 count($completed)); 385 386 $chunkinfo->addRawContent($table); 387 } 388 } 389 390 } 391 392 private function loadStorageEngine(PhabricatorFile $file) { 393 394 try { 395 return $file->instantiateStorageEngine(); 396 } catch (Throwable $ex) { 397 // Don't bother raising this anywhere for now. 398 } 399 400 return null; 401 } 402 403 private function newFileContent(PhabricatorFile $file) { 404 $request = $this->getRequest(); 405 406 $ref = id(new PhabricatorDocumentRef()) 407 ->setFile($file); 408 409 $engine = id(new PhabricatorFileDocumentRenderingEngine()) 410 ->setRequest($request); 411 412 return $engine->newDocumentView($ref); 413 } 414 415 private function newAttachmentsView(PhabricatorFile $file) { 416 $viewer = $this->getViewer(); 417 418 $attachments = id(new PhabricatorFileAttachmentQuery()) 419 ->setViewer($viewer) 420 ->withFilePHIDs(array($file->getPHID())) 421 ->execute(); 422 423 $handles = $viewer->loadHandles(mpull($attachments, 'getObjectPHID')); 424 425 $rows = array(); 426 427 $mode_map = PhabricatorFileAttachment::getModeNameMap(); 428 429 foreach ($attachments as $attachment) { 430 $object_phid = $attachment->getObjectPHID(); 431 $handle = $handles[$object_phid]; 432 433 $attachment_mode = $attachment->getAttachmentMode(); 434 435 $mode_name = idx($mode_map, $attachment_mode); 436 if ($mode_name === null) { 437 $mode_name = pht('Unknown ("%s")', $attachment_mode); 438 } 439 440 $detach_uri = urisprintf( 441 '/file/ui/detach/%s/%s/', 442 $object_phid, 443 $file->getPHID()); 444 445 $is_disabled = !$attachment->canDetach(); 446 447 $detach_button = id(new PHUIButtonView()) 448 ->setHref($detach_uri) 449 ->setTag('a') 450 ->setWorkflow(true) 451 ->setDisabled($is_disabled) 452 ->setColor(PHUIButtonView::GREY) 453 ->setSize(PHUIButtonView::SMALL) 454 ->setText(pht('Detach File')); 455 456 $rows[] = array( 457 $handle->renderLink(), 458 $mode_name, 459 $detach_button, 460 ); 461 } 462 463 $table = id(new AphrontTableView($rows)) 464 ->setHeaders( 465 array( 466 pht('Attached To'), 467 pht('Mode'), 468 null, 469 )) 470 ->setColumnClasses( 471 array( 472 'pri wide', 473 null, 474 null, 475 )); 476 477 return $table; 478 } 479 480 481}