diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1860,6 +1860,7 @@ 'PhabricatorFileFilePHIDType' => 'applications/files/phid/PhabricatorFileFilePHIDType.php', 'PhabricatorFileHasObjectEdgeType' => 'applications/files/edge/PhabricatorFileHasObjectEdgeType.php', 'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php', + 'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php', 'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php', 'PhabricatorFileLinkListView' => 'view/layout/PhabricatorFileLinkListView.php', 'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php', @@ -1873,10 +1874,13 @@ 'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php', 'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php', 'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php', + 'PhabricatorFileThumbnailTransform' => 'applications/files/transform/PhabricatorFileThumbnailTransform.php', 'PhabricatorFileTransaction' => 'applications/files/storage/PhabricatorFileTransaction.php', 'PhabricatorFileTransactionComment' => 'applications/files/storage/PhabricatorFileTransactionComment.php', 'PhabricatorFileTransactionQuery' => 'applications/files/query/PhabricatorFileTransactionQuery.php', + 'PhabricatorFileTransform' => 'applications/files/transform/PhabricatorFileTransform.php', 'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php', + 'PhabricatorFileTransformListController' => 'applications/files/controller/PhabricatorFileTransformListController.php', 'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php', 'PhabricatorFileUploadDialogController' => 'applications/files/controller/PhabricatorFileUploadDialogController.php', 'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php', @@ -5262,6 +5266,7 @@ 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', ), + 'PhabricatorFileImageTransform' => 'PhabricatorFileTransform', 'PhabricatorFileInfoController' => 'PhabricatorFileController', 'PhabricatorFileLinkListView' => 'AphrontView', 'PhabricatorFileLinkView' => 'AphrontView', @@ -5274,10 +5279,13 @@ 'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorFileTestCase' => 'PhabricatorTestCase', 'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator', + 'PhabricatorFileThumbnailTransform' => 'PhabricatorFileImageTransform', 'PhabricatorFileTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorFileTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorFileTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorFileTransform' => 'Phobject', 'PhabricatorFileTransformController' => 'PhabricatorFileController', + 'PhabricatorFileTransformListController' => 'PhabricatorFileController', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileUploadDialogController' => 'PhabricatorFileController', 'PhabricatorFileUploadException' => 'Exception', diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php --- a/src/applications/files/application/PhabricatorFilesApplication.php +++ b/src/applications/files/application/PhabricatorFilesApplication.php @@ -91,6 +91,8 @@ '(?P[^/]+)/'. '(?P[^/]+)/' => 'PhabricatorFileTransformController', + 'transforms/(?P[1-9]\d*)/' => + 'PhabricatorFileTransformListController', 'uploaddialog/' => 'PhabricatorFileUploadDialogController', 'download/(?P[^/]+)/' => 'PhabricatorFileDialogController', ), diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -170,6 +170,12 @@ ->setWorkflow(true) ->setDisabled(!$can_edit)); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Transforms')) + ->setIcon('fa-crop') + ->setHref($this->getApplicationURI("/transforms/{$id}/"))); + return $view; } @@ -267,7 +273,6 @@ $user->renderHandleList($phids)); } - if ($file->isViewableImage()) { $image = phutil_tag( 'img', diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -48,21 +48,33 @@ // protection. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - switch ($transform) { - case 'thumb-profile': - $xformed_file = $this->executeThumbTransform($file, 50, 50); - break; - case 'thumb-280x210': - $xformed_file = $this->executeThumbTransform($file, 280, 210); - break; - case 'preview-100': - $xformed_file = $this->executePreviewTransform($file, 100); - break; - case 'preview-220': - $xformed_file = $this->executePreviewTransform($file, 220); - break; - default: - return new Aphront400Response(); + $xformed_file = null; + + $xforms = PhabricatorFileTransform::getAllTransforms(); + if (isset($xforms[$transform])) { + $xform = $xforms[$transform]; + if ($xform->canApplyTransform($file)) { + $xformed_file = $xforms[$transform]->applyTransform($file); + } + } + + if (!$xformed_file) { + switch ($transform) { + case 'thumb-profile': + $xformed_file = $this->executeThumbTransform($file, 50, 50); + break; + case 'thumb-280x210': + $xformed_file = $this->executeThumbTransform($file, 280, 210); + break; + case 'preview-100': + $xformed_file = $this->executePreviewTransform($file, 100); + break; + case 'preview-220': + $xformed_file = $this->executePreviewTransform($file, 220); + break; + default: + return new Aphront400Response(); + } } if (!$xformed_file) { diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php new file mode 100644 --- /dev/null +++ b/src/applications/files/controller/PhabricatorFileTransformListController.php @@ -0,0 +1,137 @@ +getViewer(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$file) { + return new Aphront404Response(); + } + + $monogram = $file->getMonogram(); + + $xdst = id(new PhabricatorTransformedFile())->loadAllWhere( + 'transformedPHID = %s', + $file->getPHID()); + + $dst_rows = array(); + foreach ($xdst as $source) { + $dst_rows[] = array( + $source->getTransform(), + $viewer->renderHandle($source->getOriginalPHID()), + ); + } + $dst_table = id(new AphrontTableView($dst_rows)) + ->setHeaders( + array( + pht('Key'), + pht('Source'), + )) + ->setColumnClasses( + array( + '', + 'wide', + )) + ->setNoDataString( + pht( + 'This file was not created by transforming another file.')); + + $xsrc = id(new PhabricatorTransformedFile())->loadAllWhere( + 'originalPHID = %s', + $file->getPHID()); + $xsrc = mpull($xsrc, 'getTransformedPHID', 'getTransform'); + + $src_rows = array(); + $xforms = PhabricatorFileTransform::getAllTransforms(); + foreach ($xforms as $xform) { + $dst_phid = idx($xsrc, $xform->getTransformKey()); + + if ($xform->canApplyTransform($file)) { + $can_apply = pht('Yes'); + $view_href = $file->getURIForTransform($xform); + if ($dst_phid) { + $view_text = pht('View Transform'); + } else { + $view_text = pht('Generate Transform'); + } + $view_link = phutil_tag( + 'a', + array( + 'class' => 'small grey button', + 'href' => $view_href, + ), + $view_text); + } else { + $can_apply = phutil_tag('em', array(), pht('No')); + $view_link = phutil_tag('em', array(), pht('None')); + } + + if ($dst_phid) { + $dst_link = $viewer->renderHandle($dst_phid); + } else { + $dst_link = phutil_tag('em', array(), pht('None')); + } + + $src_rows[] = array( + $xform->getTransformName(), + $xform->getTransformKey(), + $can_apply, + $dst_link, + $view_link, + ); + } + + $src_table = id(new AphrontTableView($src_rows)) + ->setHeaders( + array( + pht('Name'), + pht('Key'), + pht('Supported'), + pht('Transform'), + pht('View'), + )) + ->setColumnClasses( + array( + 'wide', + '', + '', + '', + 'action', + )); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($monogram, '/'.$monogram); + $crumbs->addTextCrumb(pht('Transforms')); + + $dst_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('File Sources')) + ->appendChild($dst_table); + + $src_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Available Transforms')) + ->appendChild($src_table); + + return $this->buildApplicationPage( + array( + $crumbs, + $dst_box, + $src_box, + ), + array( + 'title' => array( + pht('%s %s', $monogram, $file->getName()), + pht('Tranforms'), + ), + )); + } +} diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -760,6 +760,10 @@ return (string) $uri; } + public function getURIForTransform(PhabricatorFileTransform $transform) { + return $this->getTransformedURI($transform->getTransformKey()); + } + private function getTransformedURI($transform) { $parts = array(); $parts[] = 'file'; diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php new file mode 100644 --- /dev/null +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -0,0 +1,17 @@ +isViewableImage()) { + return false; + } + + if (!$file->isTransformableImage()) { + return false; + } + + return true; + } + +} diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php new file mode 100644 --- /dev/null +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -0,0 +1,74 @@ +name = $name; + return $this; + } + + public function setKey($key) { + $this->key = $key; + return $this; + } + + public function setDimensions($x, $y) { + $this->dstX = $x; + $this->dstY = $y; + return $this; + } + + public function getTransformName() { + return $this->name; + } + + public function getTransformKey() { + return $this->key; + } + + public function generateTransforms() { + return array( + id(new PhabricatorFileThumbnailTransform()) + ->setName(pht("Profile (100px \xC3\x97 100px)")) + ->setKey(self::TRANSFORM_PROFILE) + ->setDimensions(100, 100), + id(new PhabricatorFileThumbnailTransform()) + ->setName(pht("Pinboard (280px \xC3\x97 210px)")) + ->setKey(self::TRANSFORM_PINBOARD) + ->setDimensions(280, 210), + id(new PhabricatorFileThumbnailTransform()) + ->setName(pht('Thumbgrid (100px)')) + ->setKey(self::TRANSFORM_THUMBGRID) + ->setDimensions(100, null), + id(new PhabricatorFileThumbnailTransform()) + ->setName(pht('Preview (220px)')) + ->setKey(self::TRANSFORM_PREVIEW) + ->setDimensions(220, null), + ); + } + + public function applyTransform(PhabricatorFile $file) { + $x = $this->dstX; + $y = $this->dstY; + + $xformer = new PhabricatorImageTransformer(); + + if ($y === null) { + return $xformer->executePreviewTransform($file, $x); + } else { + return $xformer->executeThumbTransform($file, $x, $y); + } + } + +} diff --git a/src/applications/files/transform/PhabricatorFileTransform.php b/src/applications/files/transform/PhabricatorFileTransform.php new file mode 100644 --- /dev/null +++ b/src/applications/files/transform/PhabricatorFileTransform.php @@ -0,0 +1,44 @@ +setAncestorClass(__CLASS__) + ->loadObjects(); + + $result = array(); + foreach ($xforms as $xform_template) { + foreach ($xform_template->generateTransforms() as $xform) { + $key = $xform->getTransformKey(); + if (isset($result[$key])) { + throw new Exception( + pht( + 'Two %s objects define the same transform key ("%s"), but '. + 'each transform must have a unique key.', + __CLASS__, + $key)); + } + $result[$key] = $xform; + } + } + + $map = $result; + } + + return $map; + } + +}