From ccd4ae56382762fa2631e1c0a521e4ebaa5f2926 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Sat, 7 Dec 2013 13:25:22 +1100 Subject: [PATCH] Implement "Download ZIP" controller for Phragment Summary: Depends on D7727. This adds support for downloading a fragment and all it's children as a ZIP file. Fragments that have children automatically become directories in the ZIP file. Test Plan: Downloaded a fragment as a ZIP and was able to extract the contents successfully. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley CC: Korvin, epriestley, aran Maniphest Tasks: T4205 Differential Revision: https://secure.phabricator.com/D7728 --- src/__phutil_library_map__.php | 2 + .../PhabricatorApplicationPhragment.php | 1 + .../controller/PhragmentController.php | 6 + .../controller/PhragmentZIPController.php | 111 ++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 src/applications/phragment/controller/PhragmentZIPController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 80add0fa0a..b5107ee6ae 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2187,6 +2187,7 @@ phutil_register_library_map(array( 'PhragmentPHIDTypeFragmentVersion' => 'applications/phragment/phid/PhragmentPHIDTypeFragmentVersion.php', 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php', 'PhragmentUpdateController' => 'applications/phragment/controller/PhragmentUpdateController.php', + 'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php', 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', 'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php', @@ -4785,6 +4786,7 @@ phutil_register_library_map(array( 'PhragmentPHIDTypeFragmentVersion' => 'PhabricatorPHIDType', 'PhragmentPatchUtil' => 'Phobject', 'PhragmentUpdateController' => 'PhragmentController', + 'PhragmentZIPController' => 'PhragmentController', 'PhrequentController' => 'PhabricatorController', 'PhrequentDAO' => 'PhabricatorLiskDAO', 'PhrequentListController' => diff --git a/src/applications/phragment/application/PhabricatorApplicationPhragment.php b/src/applications/phragment/application/PhabricatorApplicationPhragment.php index 001dfc6846..7b4772d59a 100644 --- a/src/applications/phragment/application/PhabricatorApplicationPhragment.php +++ b/src/applications/phragment/application/PhabricatorApplicationPhragment.php @@ -38,6 +38,7 @@ final class PhabricatorApplicationPhragment extends PhabricatorApplication { 'create/(?P.*)' => 'PhragmentCreateController', 'update/(?P.*)' => 'PhragmentUpdateController', 'history/(?P.*)' => 'PhragmentHistoryController', + 'zip/(?P.*)' => 'PhragmentZIPController', ), ); } diff --git a/src/applications/phragment/controller/PhragmentController.php b/src/applications/phragment/controller/PhragmentController.php index f5a9c7a569..22eab7fddb 100644 --- a/src/applications/phragment/controller/PhragmentController.php +++ b/src/applications/phragment/controller/PhragmentController.php @@ -90,6 +90,12 @@ abstract class PhragmentController extends PhabricatorController { ->setHref($file_uri) ->setDisabled($file === null) ->setIcon('download')); + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Download Contents as ZIP')) + ->setHref($this->getApplicationURI("zip/".$fragment->getPath())) + ->setDisabled(false) // TODO: Policy + ->setIcon('zip')); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Update Fragment')) diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php new file mode 100644 index 0000000000..13893cdee2 --- /dev/null +++ b/src/applications/phragment/controller/PhragmentZIPController.php @@ -0,0 +1,111 @@ +dblob = idx($data, "dblob", ""); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $parents = $this->loadParentFragments($this->dblob); + if ($parents === null) { + return new Aphront404Response(); + } + $fragment = idx($parents, count($parents) - 1, null); + + $temp = new TempFile(); + + $zip = null; + try { + $zip = new ZipArchive(); + } catch (Exception $e) { + $dialog = new AphrontDialogView(); + $dialog->setUser($viewer); + + $inst = pht( + 'This system does not have the ZIP PHP extension installed. This '. + 'is required to download ZIPs from Phragment.'); + + $dialog->setTitle(pht('ZIP Extension Not Installed')); + $dialog->appendParagraph($inst); + + $dialog->addCancelButton('/phragment/browse/'.$this->dblob); + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + if (!$zip->open((string)$temp, ZipArchive::CREATE)) { + throw new Exception("Unable to create ZIP archive!"); + } + + $mappings = $this->getFragmentMappings($fragment, $fragment->getPath()); + + $phids = array(); + foreach ($mappings as $path => $file_phid) { + $phids[] = $file_phid; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + foreach ($mappings as $path => $file_phid) { + if (!isset($files[$file_phid])) { + unset($mappings[$path]); + } + $mappings[$path] = $files[$file_phid]; + } + + foreach ($mappings as $path => $file) { + $zip->addFromString($path, $file->loadFileData()); + } + $zip->close(); + + $zip_name = $fragment->getName(); + if (substr($zip_name, -4) !== '.zip') { + $zip_name .= '.zip'; + } + + $data = Filesystem::readFile((string)$temp); + $file = PhabricatorFile::buildFromFileDataOrHash( + $data, + array( + 'name' => $zip_name, + 'ttl' => time() + 60 * 60 * 24, + )); + return id(new AphrontRedirectResponse()) + ->setURI($file->getBestURI()); + } + + /** + * Returns a list of mappings like array('some/path.txt' => 'file PHID'); + */ + private function getFragmentMappings(PhragmentFragment $current, $base_path) { + $children = id(new PhragmentFragmentQuery()) + ->setViewer($this->getRequest()->getUser()) + ->needLatestVersion(true) + ->withLeadingPath($current->getPath().'/') + ->withDepths(array($current->getDepth() + 1)) + ->execute(); + + if (count($children) === 0) { + $path = substr($current->getPath(), strlen($base_path) + 1); + return array($path => $current->getLatestVersion()->getFilePHID()); + } else { + $mappings = array(); + foreach ($children as $child) { + $child_mappings = $this->getFragmentMappings($child, $base_path); + foreach ($child_mappings as $key => $value) { + $mappings[$key] = $value; + } + } + return $mappings; + } + } + +}