Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -2187,6 +2187,7 @@ '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 @@ 'PhragmentPHIDTypeFragmentVersion' => 'PhabricatorPHIDType', 'PhragmentPatchUtil' => 'Phobject', 'PhragmentUpdateController' => 'PhragmentController', + 'PhragmentZIPController' => 'PhragmentController', 'PhrequentController' => 'PhabricatorController', 'PhrequentDAO' => 'PhabricatorLiskDAO', 'PhrequentListController' => Index: src/applications/phragment/application/PhabricatorApplicationPhragment.php =================================================================== --- src/applications/phragment/application/PhabricatorApplicationPhragment.php +++ src/applications/phragment/application/PhabricatorApplicationPhragment.php @@ -38,6 +38,7 @@ 'create/(?P.*)' => 'PhragmentCreateController', 'update/(?P.*)' => 'PhragmentUpdateController', 'history/(?P.*)' => 'PhragmentHistoryController', + 'zip/(?P.*)' => 'PhragmentZIPController', ), ); } Index: src/applications/phragment/controller/PhragmentController.php =================================================================== --- src/applications/phragment/controller/PhragmentController.php +++ src/applications/phragment/controller/PhragmentController.php @@ -92,6 +92,12 @@ ->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')) ->setHref($this->getApplicationURI("update/".$fragment->getPath())) ->setDisabled(false) // TODO: Policy Index: src/applications/phragment/controller/PhragmentZIPController.php =================================================================== --- /dev/null +++ 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; + } + } + +}