getViewer(); $id = $request->getURIData('id'); $phid = $request->getURIData('phid'); if ($phid) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) ->withIsDeleted(false) ->executeOne(); if (!$file) { return new Aphront404Response(); } return id(new AphrontRedirectResponse())->setURI($file->getInfoURI()); } $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->withIsDeleted(false) ->executeOne(); if (!$file) { return new Aphront404Response(); } $phid = $file->getPHID(); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($file) ->setHeader($file->getName()) ->setHeaderIcon('fa-file-o'); $ttl = $file->getTTL(); if ($ttl !== null) { $ttl_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setColor(PHUITagView::COLOR_YELLOW) ->setName(pht('Temporary')); $header->addTag($ttl_tag); } $partial = $file->getIsPartial(); if ($partial) { $partial_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setColor(PHUITagView::COLOR_ORANGE) ->setName(pht('Partial Upload')); $header->addTag($partial_tag); } $curtain = $this->buildCurtainView($file); $timeline = $this->buildTransactionView($file); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( $file->getMonogram(), $file->getInfoURI()); $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('File Metadata')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $this->buildPropertyViews($object_box, $file); $title = $file->getName(); $file_content = $this->newFileContent($file); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn( array( $file_content, $object_box, $timeline, )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($file->getPHID())) ->appendChild($view); } private function buildTransactionView(PhabricatorFile $file) { $viewer = $this->getViewer(); $timeline = $this->buildTransactionTimeline( $file, new PhabricatorFileTransactionQuery()); $comment_view = id(new PhabricatorFileEditEngine()) ->setViewer($viewer) ->buildEditEngineCommentView($file); $monogram = $file->getMonogram(); $timeline->setQuoteRef($monogram); $comment_view->setTransactionTimeline($timeline); return array( $timeline, $comment_view, ); } private function buildCurtainView(PhabricatorFile $file) { $viewer = $this->getViewer(); $id = $file->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $file, PhabricatorPolicyCapability::CAN_EDIT); $curtain = $this->newCurtainView($file); $can_download = !$file->getIsPartial(); if ($file->isViewableInBrowser()) { $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View File')) ->setIcon('fa-file-o') ->setHref($file->getViewURI()) ->setDisabled(!$can_download) ->setWorkflow(!$can_download)); } else { $curtain->addAction( id(new PhabricatorActionView()) ->setUser($viewer) ->setDownload($can_download) ->setName(pht('Download File')) ->setIcon('fa-download') ->setHref($file->getDownloadURI()) ->setDisabled(!$can_download) ->setWorkflow(!$can_download)); } $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit File')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("/edit/{$id}/")) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete File')) ->setIcon('fa-times') ->setHref($this->getApplicationURI("/delete/{$id}/")) ->setWorkflow(true) ->setDisabled(!$can_edit)); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Transforms')) ->setIcon('fa-crop') ->setHref($this->getApplicationURI("/transforms/{$id}/"))); $phids = array(); $viewer_phid = $viewer->getPHID(); $author_phid = $file->getAuthorPHID(); if ($author_phid) { $phids[] = $author_phid; } $handles = $viewer->loadHandles($phids); if ($author_phid) { $author_refs = id(new PHUICurtainObjectRefListView()) ->setViewer($viewer); $author_ref = $author_refs->newObjectRefView() ->setHandle($handles[$author_phid]) ->setEpoch($file->getDateCreated()) ->setHighlighted($author_phid === $viewer_phid); $curtain->newPanel() ->setHeaderText(pht('Authored By')) ->appendChild($author_refs); } $curtain->newPanel() ->setHeaderText(pht('Size')) ->appendChild(phutil_format_bytes($file->getByteSize())); $width = $file->getImageWidth(); $height = $file->getImageHeight(); if ($width || $height) { $curtain->newPanel() ->setHeaderText(pht('Dimensions')) ->appendChild( pht( "%spx \xC3\x97 %spx", new PhutilNumber($width), new PhutilNumber($height))); } return $curtain; } private function buildPropertyViews( PHUIObjectBoxView $box, PhabricatorFile $file) { $request = $this->getRequest(); $viewer = $request->getUser(); $tab_group = id(new PHUITabGroupView()); $box->addTabGroup($tab_group); $finfo = new PHUIPropertyListView(); $tab_group->addTab( id(new PHUITabView()) ->setName(pht('Details')) ->setKey('details') ->appendChild($finfo)); $finfo->addProperty( pht('Mime Type'), $file->getMimeType()); $ttl = $file->getTtl(); if ($ttl) { $delta = $ttl - PhabricatorTime::getNow(); $finfo->addProperty( pht('Expires'), pht( '%s (%s)', phabricator_datetime($ttl, $viewer), phutil_format_relative_time_detailed($delta))); } $is_image = $file->isViewableImage(); if ($is_image) { $image_string = pht('Yes'); $cache_string = $file->getCanCDN() ? pht('Yes') : pht('No'); } else { $image_string = pht('No'); $cache_string = pht('Not Applicable'); } $types = array(); if ($file->isViewableImage()) { $types[] = pht('Image'); } if ($file->isVideo()) { $types[] = pht('Video'); } if ($file->isAudio()) { $types[] = pht('Audio'); } if ($file->getCanCDN()) { $types[] = pht('Can CDN'); } $builtin = $file->getBuiltinName(); if ($builtin !== null) { $types[] = pht('Builtin ("%s")', $builtin); } if ($file->getIsProfileImage()) { $types[] = pht('Profile'); } if ($types) { $types = implode(', ', $types); $finfo->addProperty(pht('Attributes'), $types); } $finfo->addProperty( pht('Storage Engine'), $file->getStorageEngine()); $engine = $this->loadStorageEngine($file); if ($engine && $engine->isChunkEngine()) { $format_name = pht('Chunks'); } else { $format_key = $file->getStorageFormat(); $format = PhabricatorFileStorageFormat::getFormat($format_key); if ($format) { $format_name = $format->getStorageFormatName(); } else { $format_name = pht('Unknown ("%s")', $format_key); } } $finfo->addProperty(pht('Storage Format'), $format_name); $finfo->addProperty( pht('Storage Handle'), $file->getStorageHandle()); $custom_alt = $file->getCustomAltText(); if (phutil_nonempty_string($custom_alt)) { $finfo->addProperty(pht('Custom Alt Text'), $custom_alt); } $default_alt = $file->getDefaultAltText(); if (strlen($default_alt)) { $finfo->addProperty(pht('Default Alt Text'), $default_alt); } $attachments_table = $this->newAttachmentsView($file); $tab_group->addTab( id(new PHUITabView()) ->setName(pht('Attached')) ->setKey('attached') ->appendChild($attachments_table)); $engine = $this->loadStorageEngine($file); if ($engine) { if ($engine->isChunkEngine()) { $chunkinfo = new PHUIPropertyListView(); $tab_group->addTab( id(new PHUITabView()) ->setName(pht('Chunks')) ->setKey('chunks') ->appendChild($chunkinfo)); $chunks = id(new PhabricatorFileChunkQuery()) ->setViewer($viewer) ->withChunkHandles(array($file->getStorageHandle())) ->execute(); $chunks = msort($chunks, 'getByteStart'); $rows = array(); $completed = array(); foreach ($chunks as $chunk) { $is_complete = $chunk->getDataFilePHID(); $rows[] = array( $chunk->getByteStart(), $chunk->getByteEnd(), ($is_complete ? pht('Yes') : pht('No')), ); if ($is_complete) { $completed[] = $chunk; } } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Offset'), pht('End'), pht('Complete'), )) ->setColumnClasses( array( '', '', 'wide', )); $chunkinfo->addProperty( pht('Total Chunks'), count($chunks)); $chunkinfo->addProperty( pht('Completed Chunks'), count($completed)); $chunkinfo->addRawContent($table); } } } private function loadStorageEngine(PhabricatorFile $file) { $engine = null; try { $engine = $file->instantiateStorageEngine(); } catch (Exception $ex) { // Don't bother raising this anywhere for now. } return $engine; } private function newFileContent(PhabricatorFile $file) { $request = $this->getRequest(); $ref = id(new PhabricatorDocumentRef()) ->setFile($file); $engine = id(new PhabricatorFileDocumentRenderingEngine()) ->setRequest($request); return $engine->newDocumentView($ref); } private function newAttachmentsView(PhabricatorFile $file) { $viewer = $this->getViewer(); $attachments = id(new PhabricatorFileAttachmentQuery()) ->setViewer($viewer) ->withFilePHIDs(array($file->getPHID())) ->execute(); $handles = $viewer->loadHandles(mpull($attachments, 'getObjectPHID')); $rows = array(); $mode_map = PhabricatorFileAttachment::getModeNameMap(); $mode_attach = PhabricatorFileAttachment::MODE_ATTACH; foreach ($attachments as $attachment) { $object_phid = $attachment->getObjectPHID(); $handle = $handles[$object_phid]; $attachment_mode = $attachment->getAttachmentMode(); $mode_name = idx($mode_map, $attachment_mode); if ($mode_name === null) { $mode_name = pht('Unknown ("%s")', $attachment_mode); } $detach_uri = urisprintf( '/file/ui/detach/%s/%s/', $object_phid, $file->getPHID()); $is_disabled = !$attachment->canDetach(); $detach_button = id(new PHUIButtonView()) ->setHref($detach_uri) ->setTag('a') ->setWorkflow(true) ->setDisabled($is_disabled) ->setColor(PHUIButtonView::GREY) ->setSize(PHUIButtonView::SMALL) ->setText(pht('Detach File')); javelin_tag( 'a', array( 'href' => $detach_uri, 'sigil' => 'workflow', 'disabled' => true, 'class' => 'small button button-grey disabled', ), pht('Detach File')); $rows[] = array( $handle->renderLink(), $mode_name, $detach_button, ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Attached To'), pht('Mode'), null, )) ->setColumnClasses( array( 'pri wide', null, null, )); return $table; } }