Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / applications / files / controller / PhabricatorFileViewController.php
blob5c429d57d9c2bb51e68bb4875e756bbb4d469d2e
1 <?php
3 final class PhabricatorFileViewController extends PhabricatorFileController {
5 public function shouldAllowPublic() {
6 return true;
9 public function handleRequest(AphrontRequest $request) {
10 $viewer = $request->getViewer();
11 $id = $request->getURIData('id');
12 $phid = $request->getURIData('phid');
14 if ($phid) {
15 $file = id(new PhabricatorFileQuery())
16 ->setViewer($viewer)
17 ->withPHIDs(array($phid))
18 ->withIsDeleted(false)
19 ->executeOne();
21 if (!$file) {
22 return new Aphront404Response();
24 return id(new AphrontRedirectResponse())->setURI($file->getInfoURI());
27 $file = id(new PhabricatorFileQuery())
28 ->setViewer($viewer)
29 ->withIDs(array($id))
30 ->withIsDeleted(false)
31 ->executeOne();
32 if (!$file) {
33 return new Aphront404Response();
36 $phid = $file->getPHID();
38 $header = id(new PHUIHeaderView())
39 ->setUser($viewer)
40 ->setPolicyObject($file)
41 ->setHeader($file->getName())
42 ->setHeaderIcon('fa-file-o');
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);
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);
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);
70 $object_box = id(new PHUIObjectBoxView())
71 ->setHeaderText(pht('File Metadata'))
72 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
74 $this->buildPropertyViews($object_box, $file);
75 $title = $file->getName();
77 $file_content = $this->newFileContent($file);
79 $view = id(new PHUITwoColumnView())
80 ->setHeader($header)
81 ->setCurtain($curtain)
82 ->setMainColumn(
83 array(
84 $file_content,
85 $object_box,
86 $timeline,
87 ));
89 return $this->newPage()
90 ->setTitle($title)
91 ->setCrumbs($crumbs)
92 ->setPageObjectPHIDs(array($file->getPHID()))
93 ->appendChild($view);
96 private function buildTransactionView(PhabricatorFile $file) {
97 $viewer = $this->getViewer();
99 $timeline = $this->buildTransactionTimeline(
100 $file,
101 new PhabricatorFileTransactionQuery());
103 $comment_view = id(new PhabricatorFileEditEngine())
104 ->setViewer($viewer)
105 ->buildEditEngineCommentView($file);
107 $monogram = $file->getMonogram();
109 $timeline->setQuoteRef($monogram);
110 $comment_view->setTransactionTimeline($timeline);
112 return array(
113 $timeline,
114 $comment_view,
118 private function buildCurtainView(PhabricatorFile $file) {
119 $viewer = $this->getViewer();
121 $id = $file->getID();
123 $can_edit = PhabricatorPolicyFilter::hasCapability(
124 $viewer,
125 $file,
126 PhabricatorPolicyCapability::CAN_EDIT);
128 $curtain = $this->newCurtainView($file);
130 $can_download = !$file->getIsPartial();
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 ->setUser($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));
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));
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));
168 $curtain->addAction(
169 id(new PhabricatorActionView())
170 ->setName(pht('View Transforms'))
171 ->setIcon('fa-crop')
172 ->setHref($this->getApplicationURI("/transforms/{$id}/")));
174 $phids = array();
176 $viewer_phid = $viewer->getPHID();
177 $author_phid = $file->getAuthorPHID();
178 if ($author_phid) {
179 $phids[] = $author_phid;
182 $handles = $viewer->loadHandles($phids);
184 if ($author_phid) {
185 $author_refs = id(new PHUICurtainObjectRefListView())
186 ->setViewer($viewer);
188 $author_ref = $author_refs->newObjectRefView()
189 ->setHandle($handles[$author_phid])
190 ->setEpoch($file->getDateCreated())
191 ->setHighlighted($author_phid === $viewer_phid);
193 $curtain->newPanel()
194 ->setHeaderText(pht('Authored By'))
195 ->appendChild($author_refs);
198 $curtain->newPanel()
199 ->setHeaderText(pht('Size'))
200 ->appendChild(phutil_format_bytes($file->getByteSize()));
202 $width = $file->getImageWidth();
203 $height = $file->getImageHeight();
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)));
215 return $curtain;
218 private function buildPropertyViews(
219 PHUIObjectBoxView $box,
220 PhabricatorFile $file) {
221 $request = $this->getRequest();
222 $viewer = $request->getUser();
224 $tab_group = id(new PHUITabGroupView());
225 $box->addTabGroup($tab_group);
227 $finfo = new PHUIPropertyListView();
229 $tab_group->addTab(
230 id(new PHUITabView())
231 ->setName(pht('Details'))
232 ->setKey('details')
233 ->appendChild($finfo));
235 $finfo->addProperty(
236 pht('Mime Type'),
237 $file->getMimeType());
239 $ttl = $file->getTtl();
240 if ($ttl) {
241 $delta = $ttl - PhabricatorTime::getNow();
243 $finfo->addProperty(
244 pht('Expires'),
245 pht(
246 '%s (%s)',
247 phabricator_datetime($ttl, $viewer),
248 phutil_format_relative_time_detailed($delta)));
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');
260 $types = array();
261 if ($file->isViewableImage()) {
262 $types[] = pht('Image');
265 if ($file->isVideo()) {
266 $types[] = pht('Video');
269 if ($file->isAudio()) {
270 $types[] = pht('Audio');
273 if ($file->getCanCDN()) {
274 $types[] = pht('Can CDN');
277 $builtin = $file->getBuiltinName();
278 if ($builtin !== null) {
279 $types[] = pht('Builtin ("%s")', $builtin);
282 if ($file->getIsProfileImage()) {
283 $types[] = pht('Profile');
286 if ($types) {
287 $types = implode(', ', $types);
288 $finfo->addProperty(pht('Attributes'), $types);
291 $finfo->addProperty(
292 pht('Storage Engine'),
293 $file->getStorageEngine());
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);
307 $finfo->addProperty(pht('Storage Format'), $format_name);
309 $finfo->addProperty(
310 pht('Storage Handle'),
311 $file->getStorageHandle());
313 $custom_alt = $file->getCustomAltText();
314 if ($custom_alt !== null && strlen($custom_alt)) {
315 $finfo->addProperty(pht('Custom Alt Text'), $custom_alt);
318 $default_alt = $file->getDefaultAltText();
319 if ($default_alt !== null && strlen($default_alt)) {
320 $finfo->addProperty(pht('Default Alt Text'), $default_alt);
323 $attachments_table = $this->newAttachmentsView($file);
325 $tab_group->addTab(
326 id(new PHUITabView())
327 ->setName(pht('Attached'))
328 ->setKey('attached')
329 ->appendChild($attachments_table));
331 $engine = $this->loadStorageEngine($file);
332 if ($engine) {
333 if ($engine->isChunkEngine()) {
334 $chunkinfo = new PHUIPropertyListView();
336 $tab_group->addTab(
337 id(new PHUITabView())
338 ->setName(pht('Chunks'))
339 ->setKey('chunks')
340 ->appendChild($chunkinfo));
342 $chunks = id(new PhabricatorFileChunkQuery())
343 ->setViewer($viewer)
344 ->withChunkHandles(array($file->getStorageHandle()))
345 ->execute();
346 $chunks = msort($chunks, 'getByteStart');
348 $rows = array();
349 $completed = array();
350 foreach ($chunks as $chunk) {
351 $is_complete = $chunk->getDataFilePHID();
353 $rows[] = array(
354 $chunk->getByteStart(),
355 $chunk->getByteEnd(),
356 ($is_complete ? pht('Yes') : pht('No')),
359 if ($is_complete) {
360 $completed[] = $chunk;
364 $table = id(new AphrontTableView($rows))
365 ->setHeaders(
366 array(
367 pht('Offset'),
368 pht('End'),
369 pht('Complete'),
371 ->setColumnClasses(
372 array(
375 'wide',
378 $chunkinfo->addProperty(
379 pht('Total Chunks'),
380 count($chunks));
382 $chunkinfo->addProperty(
383 pht('Completed Chunks'),
384 count($completed));
386 $chunkinfo->addRawContent($table);
392 private function loadStorageEngine(PhabricatorFile $file) {
393 $engine = null;
395 try {
396 $engine = $file->instantiateStorageEngine();
397 } catch (Exception $ex) {
398 // Don't bother raising this anywhere for now.
401 return $engine;
404 private function newFileContent(PhabricatorFile $file) {
405 $request = $this->getRequest();
407 $ref = id(new PhabricatorDocumentRef())
408 ->setFile($file);
410 $engine = id(new PhabricatorFileDocumentRenderingEngine())
411 ->setRequest($request);
413 return $engine->newDocumentView($ref);
416 private function newAttachmentsView(PhabricatorFile $file) {
417 $viewer = $this->getViewer();
419 $attachments = id(new PhabricatorFileAttachmentQuery())
420 ->setViewer($viewer)
421 ->withFilePHIDs(array($file->getPHID()))
422 ->execute();
424 $handles = $viewer->loadHandles(mpull($attachments, 'getObjectPHID'));
426 $rows = array();
428 $mode_map = PhabricatorFileAttachment::getModeNameMap();
429 $mode_attach = PhabricatorFileAttachment::MODE_ATTACH;
431 foreach ($attachments as $attachment) {
432 $object_phid = $attachment->getObjectPHID();
433 $handle = $handles[$object_phid];
435 $attachment_mode = $attachment->getAttachmentMode();
437 $mode_name = idx($mode_map, $attachment_mode);
438 if ($mode_name === null) {
439 $mode_name = pht('Unknown ("%s")', $attachment_mode);
442 $detach_uri = urisprintf(
443 '/file/ui/detach/%s/%s/',
444 $object_phid,
445 $file->getPHID());
447 $is_disabled = !$attachment->canDetach();
449 $detach_button = id(new PHUIButtonView())
450 ->setHref($detach_uri)
451 ->setTag('a')
452 ->setWorkflow(true)
453 ->setDisabled($is_disabled)
454 ->setColor(PHUIButtonView::GREY)
455 ->setSize(PHUIButtonView::SMALL)
456 ->setText(pht('Detach File'));
458 javelin_tag(
459 'a',
460 array(
461 'href' => $detach_uri,
462 'sigil' => 'workflow',
463 'disabled' => true,
464 'class' => 'small button button-grey disabled',
466 pht('Detach File'));
468 $rows[] = array(
469 $handle->renderLink(),
470 $mode_name,
471 $detach_button,
475 $table = id(new AphrontTableView($rows))
476 ->setHeaders(
477 array(
478 pht('Attached To'),
479 pht('Mode'),
480 null,
482 ->setColumnClasses(
483 array(
484 'pri wide',
485 null,
486 null,
489 return $table;