Provide a rudimentary "Attached Files" curtain UI panel
[phabricator.git] / src / applications / files / controller / PhabricatorFileViewController.php
blob145f066492c4d17f6901f08d614016accad8352f
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 (strlen($custom_alt)) {
315 $finfo->addProperty(pht('Custom Alt Text'), $custom_alt);
318 $default_alt = $file->getDefaultAltText();
319 if (strlen($default_alt)) {
320 $finfo->addProperty(pht('Default Alt Text'), $default_alt);
323 $phids = $file->getObjectPHIDs();
324 if ($phids) {
325 $attached = new PHUIPropertyListView();
327 $tab_group->addTab(
328 id(new PHUITabView())
329 ->setName(pht('Attached'))
330 ->setKey('attached')
331 ->appendChild($attached));
333 $attached->addProperty(
334 pht('Attached To'),
335 $viewer->renderHandleList($phids));
338 $engine = $this->loadStorageEngine($file);
339 if ($engine) {
340 if ($engine->isChunkEngine()) {
341 $chunkinfo = new PHUIPropertyListView();
343 $tab_group->addTab(
344 id(new PHUITabView())
345 ->setName(pht('Chunks'))
346 ->setKey('chunks')
347 ->appendChild($chunkinfo));
349 $chunks = id(new PhabricatorFileChunkQuery())
350 ->setViewer($viewer)
351 ->withChunkHandles(array($file->getStorageHandle()))
352 ->execute();
353 $chunks = msort($chunks, 'getByteStart');
355 $rows = array();
356 $completed = array();
357 foreach ($chunks as $chunk) {
358 $is_complete = $chunk->getDataFilePHID();
360 $rows[] = array(
361 $chunk->getByteStart(),
362 $chunk->getByteEnd(),
363 ($is_complete ? pht('Yes') : pht('No')),
366 if ($is_complete) {
367 $completed[] = $chunk;
371 $table = id(new AphrontTableView($rows))
372 ->setHeaders(
373 array(
374 pht('Offset'),
375 pht('End'),
376 pht('Complete'),
378 ->setColumnClasses(
379 array(
382 'wide',
385 $chunkinfo->addProperty(
386 pht('Total Chunks'),
387 count($chunks));
389 $chunkinfo->addProperty(
390 pht('Completed Chunks'),
391 count($completed));
393 $chunkinfo->addRawContent($table);
399 private function loadStorageEngine(PhabricatorFile $file) {
400 $engine = null;
402 try {
403 $engine = $file->instantiateStorageEngine();
404 } catch (Exception $ex) {
405 // Don't bother raising this anywhere for now.
408 return $engine;
411 private function newFileContent(PhabricatorFile $file) {
412 $request = $this->getRequest();
414 $ref = id(new PhabricatorDocumentRef())
415 ->setFile($file);
417 $engine = id(new PhabricatorFileDocumentRenderingEngine())
418 ->setRequest($request);
420 return $engine->newDocumentView($ref);