From e72d3bfb60b95372b0e07d5b66cc51b5c0011a3d Mon Sep 17 00:00:00 2001 From: Benjamin Perche Date: Thu, 3 Jul 2014 13:10:53 +0200 Subject: [PATCH] =?UTF-8?q?Finish=20implementing=20and=20testing=20zip=20?= =?UTF-8?q?=09modifi=C3=A9:=20=20=20=20=20=20=20=20=20core/lib/Thelia/Core?= =?UTF-8?q?/FileFormat/Archive/AbstractArchiveBuilder.php=20=09nouveau=20f?= =?UTF-8?q?ichier:=20core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilde?= =?UTF-8?q?r/Exception/TarArchiveException.php=20=09renomm=C3=A9:=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20core/lib/Thelia/Core/FileFormat/Archive/Archi?= =?UTF-8?q?veBuilder/ZipArchiveException.php=20->=20core/lib/Thelia/Core/F?= =?UTF-8?q?ileFormat/Archive/ArchiveBuilder/Exception/ZipArchiveException.?= =?UTF-8?q?php=20=09nouveau=20fichier:=20core/lib/Thelia/Core/FileFormat/A?= =?UTF-8?q?rchive/ArchiveBuilder/TarArchiveBuilder.php=20=09modifi=C3=A9:?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20core/lib/Thelia/Core/FileFormat/Arch?= =?UTF-8?q?ive/ArchiveBuilder/ZipArchiveBuilder.php=20=09modifi=C3=A9:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20core/lib/Thelia/Core/FileFormat/Archive?= =?UTF-8?q?/ArchiveBuilderInterface.php=20=09nouveau=20fichier:=20core/lib?= =?UTF-8?q?/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/TarArchiveBuild?= =?UTF-8?q?erTest.php=20=09modifi=C3=A9:=20=20=20=20=20=20=20=20=20core/li?= =?UTF-8?q?b/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuil?= =?UTF-8?q?derTest.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Archive/AbstractArchiveBuilder.php | 103 +++- .../Exception/TarArchiveException.php | 23 + .../{ => Exception}/ZipArchiveException.php | 2 +- .../ArchiveBuilder/TarArchiveBuilder.php | 479 ++++++++++++++++++ .../ArchiveBuilder/ZipArchiveBuilder.php | 300 ++++++----- .../Archive/ArchiveBuilderInterface.php | 36 +- .../ArchiveBuilder/TarArchiveBuilderTest.php | 86 ++++ .../ArchiveBuilder/ZipArchiveBuilderTest.php | 89 +++- 8 files changed, 964 insertions(+), 154 deletions(-) create mode 100644 core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/Exception/TarArchiveException.php rename core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/{ => Exception}/ZipArchiveException.php (94%) create mode 100644 core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/TarArchiveBuilder.php create mode 100644 core/lib/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/TarArchiveBuilderTest.php diff --git a/core/lib/Thelia/Core/FileFormat/Archive/AbstractArchiveBuilder.php b/core/lib/Thelia/Core/FileFormat/Archive/AbstractArchiveBuilder.php index 447bf1265..b444860b6 100644 --- a/core/lib/Thelia/Core/FileFormat/Archive/AbstractArchiveBuilder.php +++ b/core/lib/Thelia/Core/FileFormat/Archive/AbstractArchiveBuilder.php @@ -13,6 +13,8 @@ namespace Thelia\Core\FileFormat\Archive; use Thelia\Core\FileFormat\FormatInterface; use Thelia\Core\Translation\Translator; +use Thelia\Exception\FileNotFoundException; +use Thelia\Exception\FileNotReadableException; use Thelia\Log\Tlog; use Thelia\Tools\FileDownload\FileDownloaderAwareTrait; @@ -27,6 +29,9 @@ abstract class AbstractArchiveBuilder implements FormatInterface, ArchiveBuilder const TEMP_DIRECTORY_NAME = "archive_builder"; + /** @var string */ + protected $cacheFile; + /** @var \Thelia\Core\Translation\Translator */ protected $translator; @@ -58,7 +63,7 @@ abstract class AbstractArchiveBuilder implements FormatInterface, ArchiveBuilder ); } - $archiveBuilderCacheDir = $this->cache_dir = $theliaCacheDir . static::TEMP_DIRECTORY_NAME; + $archiveBuilderCacheDir = $this->cacheDir = $theliaCacheDir . static::TEMP_DIRECTORY_NAME; if (!is_dir($archiveBuilderCacheDir) && !mkdir($archiveBuilderCacheDir, 0755)) { throw new \ErrorException( @@ -74,6 +79,97 @@ abstract class AbstractArchiveBuilder implements FormatInterface, ArchiveBuilder return $archiveBuilderCacheDir; } + /** + * @param $pathToFile + * @param $destination + * @param $isOnline + * @return $this + * @throws \ErrorException + */ + public function copyFile($pathToFile, $destination, $isOnline) + { + if ($isOnline) { + /** + * It's an online file + */ + $this->getFileDownloader() + ->download($pathToFile, $destination) + ; + } else { + /** + * It's a local file + */ + if (!is_file($pathToFile)) { + $this->throwFileNotFound($pathToFile); + } elseif (!is_readable($pathToFile)) { + throw new FileNotReadableException( + $this->translator + ->trans( + "The file %file is not readable", + [ + "%file" => $pathToFile, + ] + ) + ); + } + + if (!copy($pathToFile, $destination)) { + $translatedErrorMessage = $this->translator->trans( + "An error happend while copying %prev to %dest", + [ + "%prev" => $pathToFile, + "%dest" => $destination, + ] + ); + + $this->logger + ->error($translatedErrorMessage) + ; + + throw new \ErrorException($translatedErrorMessage); + } + } + + return $this; + } + + /** + * @return string + */ + public function generateCacheFile($environment) + { + $cacheFileName = md5(uniqid()); + + $cacheFile = $this->getArchiveBuilderCacheDirectory($environment) . DS; + $cacheFile .= $cacheFileName . "." . $this->getExtension(); + + return $cacheFile; + } + + public function throwFileNotFound($file) + { + + throw new FileNotFoundException( + $this->translator + ->trans( + "The file %file is missing or is not readable", + [ + "%file" => $file, + ] + ) + ); + } + + /** + * @param $path + * @return $this + */ + public function setCacheFile($path) + { + $this->cacheFile = $path; + + return $this; + } public function getCacheDir() { @@ -95,4 +191,9 @@ abstract class AbstractArchiveBuilder implements FormatInterface, ArchiveBuilder { return $this->translator; } + + public function getCacheFile() + { + return $this->cacheFile; + } } \ No newline at end of file diff --git a/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/Exception/TarArchiveException.php b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/Exception/TarArchiveException.php new file mode 100644 index 000000000..9945a65be --- /dev/null +++ b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/Exception/TarArchiveException.php @@ -0,0 +1,23 @@ + + */ +class TarArchiveException extends \Exception +{ + +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/ZipArchiveException.php b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/Exception/ZipArchiveException.php similarity index 94% rename from core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/ZipArchiveException.php rename to core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/Exception/ZipArchiveException.php index c85fce339..24f9ddb37 100644 --- a/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/ZipArchiveException.php +++ b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/Exception/ZipArchiveException.php @@ -10,7 +10,7 @@ /* file that was distributed with this source code. */ /*************************************************************************************/ -namespace Thelia\Core\FileFormat\Archive\ArchiveBuilder; +namespace Thelia\Core\FileFormat\Archive\ArchiveBuilder\Exception; /** * Class ZipArchiveException diff --git a/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/TarArchiveBuilder.php b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/TarArchiveBuilder.php new file mode 100644 index 000000000..c5b019f46 --- /dev/null +++ b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/TarArchiveBuilder.php @@ -0,0 +1,479 @@ + + */ +class TarArchiveBuilder extends AbstractArchiveBuilder +{ + const PHAR_FORMAT = \Phar::TAR; + + /** @var string */ + protected $environment; + + /** @var null|string */ + protected $compression; + + /** @var string */ + protected $tarCacheFile; + + /** @var \PharData */ + protected $tar; + + /** @var \Thelia\Core\Translation\Translator */ + protected $translator; + + /** @var \Thelia\Log\Tlog */ + protected $logger; + + function __construct($compressionType = null) + { + $this->translator = Translator::getInstance(); + $this->logger = Tlog::getNewInstance(); + + $supportedCompression = [ + "gz", + "bz2", + null + ]; + + if (!in_array($compressionType, $supportedCompression)) { + throw new TarArchiveException( + $this->translator->trans( + "The compression %type is not supported" + ) + ); + } + + $this->compression = $compressionType; + } + + /** + public function __destruct() + { + if ($this->tar instanceof \PharData) { + if (file_exists($this->cacheFile)) { + unlink($this->cacheFile); + } + } + }*/ + + /** + * @param string $filePath It is the path to access the file. + * @param string $directoryInArchive This is the directory where it will be stored in the archive + * @param null|string $name The name of the file in the archive. if it null or empty, it keeps the same name + * @param bool $isOnline + * @return $this + * @throws \Thelia\Exception\FileNotFoundException + * @throws \Thelia\Exception\FileNotReadableException + * @throws \ErrorException + * + * This methods adds a file in the archive. + * If the file is local, $isOnline must be false, + * If the file online, $filePath must be an URL. + */ + public function addFile($filePath, $directoryInArchive = "/", $name = null, $isOnline = false) + { + if (empty($name) || !is_scalar($name)) { + $name = basename($filePath); + } + + /** + * Download the file if it is online + * If it's local check if the file exists and if it is redable + */ + $fileDownloadCache = $this->cacheDir . DS . "download.tmp"; + $this->copyFile($filePath, $fileDownloadCache, $isOnline); + + /** + * Then write the file in the archive + */ + $directoryInArchive = $this->formatDirectoryPath($directoryInArchive); + + if (!empty($directoryInArchive)) { + $name = $this->formatFilePath( + $directoryInArchive . $name + ); + } + + $this->tar->addFile($filePath, $name); + + return $this; + } + + /** + * @param $content + * @param $name + * @param string $directoryInArchive + * @return mixed + * @throws \ErrorException + * + * This method creates a file in the archive with its content + */ + public function addFileFromString($content, $name, $directoryInArchive = "/") + { + if (empty($name) || !is_scalar($name)) { + throw new \ErrorException( + $this->translator->trans( + "The file name must be valid" + ) + ); + } + + $directoryInArchive = $this->formatDirectoryPath($directoryInArchive); + + if (!empty($directoryInArchive)) { + $name = $this->formatFilePath( + $directoryInArchive . $name + ); + } + try { + $this->tar->addFromString($name, $content); + } catch(\Exception $e) { + throw new \ErrorException( + $this->translator->trans( + "Error while writing the file into the archive, error message: %errmes", + [ + "%errmes" => $e->getMessage() + ] + ) + ); + } + } + + + /** + * @param $directoryPath + * @return $this + * @throws \ErrorException + * + * This method creates an empty directory + */ + public function addDirectory($directoryPath) + { + $directoryInArchive = $this->formatDirectoryPath($directoryPath); + + if (!empty($directoryInArchive)) { + + try { + $this->tar->addEmptyDir($directoryInArchive); + } catch(\Exception $e) { + throw new \ErrorException( + $this->translator->trans( + "The directory %dir has not been created in the archive", + [ + "%dir" => $directoryInArchive + ] + ) + ); + } + } + + return $this; + } + + /** + * @param string $pathToFile + * @return null|string + * @throws \Thelia\Exception\FileNotFoundException + * @throws \Thelia\Exception\FileNotReadableException + * @throws \ErrorException + * + * This method returns a file content + */ + public function getFileContent($pathToFile) + { + + } + + + /** + * @param $pathInArchive + * @return $this + * @throws \Thelia\Exception\FileNotFoundException + * @throws \ErrorException + * + * This method deletes a file in the archive + */ + public function deleteFile($pathInArchive) + { + if (!$this->hasFile($pathInArchive)) { + $this->throwFileNotFound($pathInArchive); + } + + if (false === $this->tar->delete($pathInArchive)) { + throw new \ErrorException( + $this->translator->trans( + "Unknown error while deleting the file %file", + [ + "%file" => $pathInArchive + ] + ) + ); + } + } + + /** + * @return \Thelia\Core\HttpFoundation\Response + * + * This method return an instance of a Response with the archive as content. + */ + public function buildArchiveResponse() + { + + } + + /** + * @param string $pathToArchive + * @param string $environment + * @param bool $isOnline + * @param FileDownloaderInterface $fileDownloader + * @return $this + * @throws \Thelia\Exception\FileNotFoundException + * @throws \Thelia\Exception\HttpUrlException + * @throws TarArchiveException + * + * Loads an archive + */ + public static function loadArchive( + $pathToArchive, + $environment, + $isOnline = false, + FileDownloaderInterface $fileDownloader = null + ) { + /** @var TarArchiveBuilder $instance */ + $instance = new static(); + + if ($fileDownloader !== null) { + $instance->setFileDownloader($fileDownloader); + } + + $instance->setCacheFile($instance->getCacheFile()) + ->copyFile($pathToArchive, $isOnline); + + /** + * This throws TarArchiveBuilderException if + * the archive is not valid. + */ + $instance->setEnvironment($environment); + + return $instance; + } + + /** + * @param $pathToFile + * @return bool + * + * Checks if the archive has a file. + * In \PharData, if you call it as a array, + * the keys are the files in the archive. + */ + public function hasFile($pathToFile) + { + $isFile = false; + + $pathToFile = $this->formatFilePath($pathToFile); + try { + /** @var \PharFileInfo $fileInfo */ + $fileInfo = $this->tar[$pathToFile]; + + if($fileInfo->isFile()) { + $isFile = true; + } + /** + * Catch the exception to avoid its displaying. + */ + } catch(\BadMethodCallException $e) {} + + return $isFile; + } + + /** + * @param string $directory + * @return bool + * + * Check if the archive has a directory + */ + public function hasDirectory($directory) + { + $isDir = false; + + $pathToDir = $this->formatDirectoryPath($directory); + try { + /** @var \PharFileInfo $fileInfo */ + $fileInfo = $this->tar[$pathToDir]; + + if($fileInfo->isDir()) { + $isDir = true; + } + /** + * Catch the exception to avoid its displaying. + */ + } catch(\BadMethodCallException $e) {} + + return $isDir; + } + + /** + * @param string $environment + * @return $this + * + * Sets the execution environment of the Kernel, + * used to know which cache is used. + */ + public function setEnvironment($environment) + { + if ($this->cacheFile === null) { + $cacheFile = $this->generateCacheFile($environment); + + if (file_exists($cacheFile)) { + unlink($cacheFile); + } + } else { + $cacheFile = $this->cacheFile; + } + + $errorMessage = null; + + try { + $this->tar = new \PharData($cacheFile, null, null, static::PHAR_FORMAT); + + switch ($this->compression) { + case "gz": + $this->tar = $this->tar->compress(\Phar::GZ); + $cacheFile .= ".gz"; + break; + case "bz2": + $this->tar = $this->tar->compress(\Phar::BZ2); + $cacheFile .= ".bz2"; + break; + } + + } catch(\BadMethodCallException $e) { + /** + * This should not happen + */ + $errorMessage = "You have badly called the method setEnvironment twice for %file"; + } catch(\UnexpectedValueException $e) { + $errorMessage = "The file %file is corrupted"; + } + + if ($errorMessage !== null) { + throw new TarArchiveException( + $this->translator->trans( + $errorMessage, + [ + "%file" => $cacheFile + ] + ) + ); + } + + $this->cacheFile = $cacheFile; + + return $this; + } + + /** + * @param string $initialString + * @return string + * + * Gives a valid file path for \ZipArchive + */ + public function formatFilePath($initialString) + { + /** + * Remove the / at the beginning and the end. + */ + $initialString = trim($initialString, "/"); + + /** + * Remove the double, triple, ... slashes + */ + $initialString = preg_replace("#\/{2,}#", "/", $initialString); + + return $initialString; + } + + /** + * @param string $initialString + * @return string + * + * Gives a valid directory path for \ZipArchive + */ + public function formatDirectoryPath($initialString) + { + $initialString = $this->formatFilePath($initialString); + + return $initialString . "/"; + } + + /** + * @return string + * + * This method must return a string, the name of the format. + * + * example: + * return "XML"; + */ + public function getName() + { + $name = "tar"; + + if ($this->compression !== null) { + $name .= "." . $this->compression; + } + + return $name; + } + + /** + * @return string + * + * This method must return a string, the extension of the file format, without the ".". + * The string should be lowercase. + * + * example: + * return "xml"; + */ + public function getExtension() + { + return $this->getName(); + } + + /** + * @return string + * + * This method must return a string, the mime type of the file format. + * + * example: + * return "application/json"; + */ + public function getMimeType() + { + return $this->compression === null ? + "application/x-tar" : + "application/x-gtar" + ; + } + +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuilder.php b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuilder.php index 41611330a..fd750bc0c 100644 --- a/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuilder.php +++ b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuilder.php @@ -12,12 +12,10 @@ namespace Thelia\Core\FileFormat\Archive\ArchiveBuilder; use Thelia\Core\FileFormat\Archive\AbstractArchiveBuilder; +use Thelia\Core\FileFormat\Archive\ArchiveBuilder\Exception\ZipArchiveException; use Thelia\Core\HttpFoundation\Response; use Thelia\Core\Thelia; -use Thelia\Core\Translation\Translator; -use Thelia\Exception\FileNotFoundException; use Thelia\Exception\FileNotReadableException; -use Thelia\Log\Tlog; use Thelia\Tools\FileDownload\FileDownloaderInterface; /** @@ -38,16 +36,6 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder */ protected $zip; - /** - * @var string This is the absolute path to the zip file in cache - */ - protected $zipCacheFile; - - /** - * @var string This is the path of the cache - */ - protected $cacheDir; - public function __construct() { parent::__construct(); @@ -64,8 +52,8 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder if ($this->zip instanceof \ZipArchive) { @$this->zip->close(); - if (file_exists($this->zipCacheFile)) { - unlink($this->zipCacheFile); + if (file_exists($this->cacheFile)) { + unlink($this->cacheFile); } } } @@ -78,6 +66,7 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder * @return $this * @throws \Thelia\Exception\FileNotFoundException * @throws \Thelia\Exception\FileNotReadableException + * @throws \ErrorException * * This methods adds a file in the archive. * If the file is local, $isOnline must be false, @@ -85,71 +74,37 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder */ public function addFile($filePath, $directoryInArchive = null, $name = null, $isOnline = false) { + $directoryInArchive = $this->formatDirectoryPath($directoryInArchive); + /** * Add empty directory if it doesn't exist */ - if (empty($directoryInArchive) || preg_match("#^\/+$#", $directoryInArchive)) { - $directoryInArchive = ""; - } if(!empty($directoryInArchive)) { - $directoryInArchive = $this->getDirectoryPath($directoryInArchive); + $this->addDirectory($directoryInArchive); + } - if (!$this->zip->addEmptyDir($directoryInArchive)) { - throw new \ErrorException( - $this->translator->trans( - "The directory %dir has not been created in the archive", - [ - "%dir" => $directoryInArchive - ] - ) - ); - } + if (empty($name) || !is_scalar($name)) { + $name = basename($filePath); } /** * Download the file if it is online * If it's local check if the file exists and if it is redable */ - if ($isOnline) { - $fileDownloadCache = $this->cacheDir . DS . "download"; - - $this->getFileDownloader() - ->download($filePath, $fileDownloadCache) - ; - - $filePath = $fileDownloadCache; - } else { - if (!file_exists($filePath)) { - $this->throwFileNotFound($filePath); - } else if (!is_readable($filePath)) { - throw new FileNotReadableException( - $this->translator - ->trans( - "The file %file is not readable", - [ - "%file" => $filePath, - ] - ) - ); - } - } - - if (empty($name)) { - $name = basename($filePath); - } + $fileDownloadCache = $this->cacheDir . DS . "download.tmp"; + $this->copyFile($filePath, $fileDownloadCache, $isOnline); /** * Then write the file in the archive and commit the changes */ - $destination = $directoryInArchive . $name; - if (!$this->zip->addFile($filePath,$destination)) { + if (!$this->zip->addFile($fileDownloadCache, $destination)) { $translatedErrorMessage = $this->translator->trans( "An error occurred while adding this file to the archive: %file", [ - "%file" => $filePath + "%file" => $fileDownloadCache ] ); @@ -163,6 +118,146 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder return $this; } + /** + * @param $content + * @param $name + * @param string $directoryInArchive + * @return mixed + * @throws \ErrorException + * + * This method creates a file in the archive with its content + */ + public function addFileFromString($content, $name, $directoryInArchive = "/") + { + $directoryInArchive = $this->formatDirectoryPath($directoryInArchive); + + if (!empty($directoryInArchive) && $directoryInArchive !== "/") { + $this->addDirectory($directoryInArchive); + } + + if (empty($name) || !is_scalar($name)) { + throw new \ErrorException( + $this->translator->trans( + "The filename is not correct" + ) + ); + } + + $filePath = $this->getFilePath($directoryInArchive . DS . $name); + + if (!$this->zip->addFromString($filePath, $content)) { + throw new \ErrorException( + $this->translator->trans( + "Unable to write the file %file into the archive", + [ + "%file" => $filePath, + ] + ) + ); + } + + $this->commit(); + } + + + /** + * @param $directoryPath + * @return $this + * @throws \ErrorException + * + * This method creates an empty directory + */ + public function addDirectory($directoryPath) + { + $directoryInArchive = $this->formatDirectoryPath($directoryPath); + + if (!empty($directoryInArchive)) { + if (!$this->zip->addEmptyDir($directoryInArchive)) { + throw new \ErrorException( + $this->translator->trans( + "The directory %dir has not been created in the archive", + [ + "%dir" => $directoryInArchive + ] + ) + ); + } + } + + return $this; + } + + /** + * @param string $pathToFile + * @return null|string + * @throws \Thelia\Exception\FileNotFoundException + * @throws \Thelia\Exception\FileNotReadableException + * @throws \ErrorException + * + * This method returns a file content + */ + public function getFileContent($pathToFile) + { + $pathToFile = $this->formatFilePath($pathToFile); + + if (!$this->hasFile($pathToFile)) { + $this->throwFileNotFound($pathToFile); + } + + $stream = $this->zip->getStream($pathToFile); + $content = ""; + + while (!feof($stream)) { + $content .= fread($stream, 2); + } + + fclose($stream); + + return $content; + } + + + /** + * @param string $initialString + * @return string + * + * Gives a valid file path for \ZipArchive + */ + public function getFilePath($initialString) + { + /** + * Remove the / at the beginning and the end. + */ + $initialString = trim($initialString, "/"); + + /** + * Remove the double, triple, ... slashes + */ + $initialString = preg_replace("#\/{2,}#", "/", $initialString); + + if (preg_match("#\/?[^\/]+\/[^/]+\/?#", $initialString)) { + $initialString = "/" . $initialString; + } + return $initialString; + } + + /** + * @param string $initialString + * @return string + * + * Gives a valid directory path for \ZipArchive + */ + public function getDirectoryPath($initialString) + { + $initialString = $this->getFilePath($initialString); + + if ($initialString[0] !== "/") { + $initialString = "/" . $initialString; + } + + return $initialString . "/"; + } + /** * @param $pathInArchive * @return $this @@ -173,7 +268,7 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder */ public function deleteFile($pathInArchive) { - $pathInArchive = $this->getFilePath($pathInArchive); + $pathInArchive = $this->formatFilePath($pathInArchive); if (!$this->hasFile($pathInArchive)) { $this->throwFileNotFound($pathInArchive); @@ -206,22 +301,22 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder $this->commit(); - if (!file_exists($this->zipCacheFile)) { - $this->throwFileNotFound($this->zipCacheFile); + if (!file_exists($this->cacheFile)) { + $this->throwFileNotFound($this->cacheFile); } - if (!is_readable($this->zipCacheFile)) { + if (!is_readable($this->cacheFile)) { throw new FileNotReadableException( $this->translator->trans( "The cache file %file is not readable", [ - "%file" => $this->zipCacheFile + "%file" => $this->cacheFile ] ) ); } - $content = file_get_contents($this->zipCacheFile); + $content = file_get_contents($this->cacheFile); $this->zip->close(); @@ -262,39 +357,9 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder $instance->setFileDownloader($fileDownloader); } - if ($isOnline) { - /** - * It's an online file - */ - $instance->getFileDownloader() - ->download($pathToArchive, $instance->getZipCacheFile()) - ; - } else { - /** - * It's a local file - */ - if (!is_file($pathToArchive) || !is_readable($pathToArchive)) { - $instance->throwFileNotFound($pathToArchive); - } + $instance->copyFile($pathToArchive, $instance->getCacheFile(), $isOnline); - if (!copy($pathToArchive, $instance->getZipCacheFile())) { - $translatedErrorMessage = $instance->getTranslator()->trans( - "An unknown error happend while copying %prev to %dest", - [ - "%prev" => $pathToArchive, - "%dest" => $instance->getZipCacheFile(), - ] - ); - - $instance->getLogger() - ->error($translatedErrorMessage) - ; - - throw new \ErrorException($translatedErrorMessage); - } - } - - if (true !== $return = $zip->open($instance->getZipCacheFile())) { + if (true !== $return = $zip->open($instance->getCacheFile())) { throw new ZipArchiveException( $instance->getZipErrorMessage($return) ); @@ -312,7 +377,7 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder public function hasFile($pathToFile) { return $this->zip - ->locateName($this->getFilePath($pathToFile)) !== false + ->locateName($this->formatFilePath($pathToFile)) !== false ; } @@ -324,7 +389,7 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder */ public function hasDirectory($directory) { - $link = $this->zip->locateName($this->getDirectoryPath($directory)); + $link = $this->zip->locateName($this->formatDirectoryPath($directory)); return $link !== false; } @@ -339,10 +404,7 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder public function setEnvironment($environment) { - $cacheFileName = md5 (uniqid()); - - $cacheFile = $this->getArchiveBuilderCacheDirectory($environment) . DS; - $cacheFile .= $cacheFileName . "." . $this->getExtension(); + $cacheFile = $this->generateCacheFile($environment); if (file_exists($cacheFile)) { unlink($cacheFile); @@ -361,7 +423,7 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder ); } - $this->zipCacheFile = $cacheFile; + $this->cacheFile = $cacheFile; return $this; } @@ -433,7 +495,7 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder public function commit() { $this->zip->close(); - $result = $this->zip->open($this->getZipCacheFile()); + $result = $this->zip->open($this->getCacheFile()); if ($result !== true) { throw new \ErrorException( @@ -452,7 +514,7 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder * * Gives a valid file path for \ZipArchive */ - public function getFilePath($initialString) + public function formatFilePath($initialString) { /** * Remove the / at the beginning and the end. @@ -476,31 +538,17 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder * * Gives a valid directory path for \ZipArchive */ - public function getDirectoryPath($initialString) + public function formatDirectoryPath($initialString) { - $initialString = $this->getFilePath($initialString); + $initialString = $this->formatFilePath($initialString); - if ($initialString[0] !== "/") { + if ($initialString !== "" && $initialString[0] !== "/") { $initialString = "/" . $initialString; } return $initialString . "/"; } - public function throwFileNotFound($file) - { - - throw new FileNotFoundException( - $this->getTranslator() - ->trans( - "The file %file is missing or is not readable", - [ - "%file" => $file, - ] - ) - ); - } - /** * @return string * @@ -548,10 +596,4 @@ class ZipArchiveBuilder extends AbstractArchiveBuilder { return $this->zip; } - - public function getZipCacheFile() - { - return $this->zipCacheFile; - } - } \ No newline at end of file diff --git a/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilderInterface.php b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilderInterface.php index c779722a6..2365ee624 100644 --- a/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilderInterface.php +++ b/core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilderInterface.php @@ -11,6 +11,7 @@ /*************************************************************************************/ namespace Thelia\Core\FileFormat\Archive; +use Thelia\Tools\FileDownload\FileDownloaderInterface; /** * Interface ArchiveBuilderInterface @@ -29,6 +30,7 @@ interface ArchiveBuilderInterface * @return $this * @throws \Thelia\Exception\FileNotFoundException * @throws \Thelia\Exception\FileNotReadableException + * @throws \ErrorException * * This methods adds a file in the archive. * If the file is local, $isOnline must be false, @@ -36,15 +38,45 @@ interface ArchiveBuilderInterface */ public function addFile($filePath, $directoryInArchive = "/", $name = null, $isOnline = false); + /** + * @param $content + * @param $name + * @param string $directoryInArchive + * @return mixed + * @throws \ErrorException + * + * This method creates a file in the archive with its content + */ + public function addFileFromString($content, $name, $directoryInArchive = "/"); + + /** + * @param string $pathToFile + * @return null|string + * @throws \Thelia\Exception\FileNotFoundException + * @throws \Thelia\Exception\FileNotReadableException + * @throws \ErrorException + * + * This method returns a file content + */ + public function getFileContent($pathToFile); /** * @param $pathInArchive * @return $this * @throws \Thelia\Exception\FileNotFoundException + * @throws \ErrorException * * This method deletes a file in the archive */ public function deleteFile($pathInArchive); + /** + * @param $directoryPath + * @return $this + * @throws \ErrorException + * + * This method creates an empty directory + */ + public function addDirectory($directoryPath); /** * @return \Thelia\Core\HttpFoundation\Response * @@ -54,14 +86,16 @@ interface ArchiveBuilderInterface /** * @param string $pathToArchive + * @param string $environment * @param bool $isOnline + * @param FileDownloaderInterface $fileDownloader * @return $this * @throws \Thelia\Exception\FileNotFoundException * @throws \Thelia\Exception\HttpUrlException * * Loads an archive */ - public static function loadArchive($pathToArchive, $environment, $isOnline = false); + public static function loadArchive($pathToArchive, $environment, $isOnline = false, FileDownloaderInterface $fileDownloader = null); /** * @param $pathToFile diff --git a/core/lib/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/TarArchiveBuilderTest.php b/core/lib/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/TarArchiveBuilderTest.php new file mode 100644 index 000000000..df48f0629 --- /dev/null +++ b/core/lib/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/TarArchiveBuilderTest.php @@ -0,0 +1,86 @@ + + */ +class TarArchiveBuilderTest extends \PHPUnit_Framework_TestCase +{ + /** @var TarArchiveBuilder */ + protected $tar; + + public function setUp() + { + new Translator(new Container()); + + Tlog::getNewInstance(); + + $this->tar = new TarArchiveBuilder(); + } + + public function testAddFileAndDirectory() + { + $this->tar->setEnvironment("dev"); + + /** + * File + */ + $tar = $this->tar->addFile( + __DIR__ . DS . "TestResources/test_file" + ); + + $this->assertTrue($tar->hasFile("test_file")); + + $this->assertFalse($tar->hasDirectory("test_file")); + + $tar = $this->tar->addFile( + __DIR__ . DS . "TestResources/test_file", + null, + "TEST.txt" + ); + + $this->assertTrue($tar->hasFile("TEST.txt")); + + $this->assertFalse($tar->hasDirectory("TEST.txt")); + + /** + * Directory + */ + $this->tar->addDirectory("foo"); + + $this->assertTrue($tar->hasDirectory("foo")); + + $this->assertFalse($tar->hasFile("foo")); + + /**s + * File in a directory + */ + $this->tar->addFile( + __DIR__ . DS . "TestResources/test_file", + "bar", + "baz" + ); + + $this->assertTrue($this->tar->hasFile("bar/baz")); + + $this->assertTrue($this->tar->hasDirectory("bar")); + + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuilderTest.php b/core/lib/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuilderTest.php index aae3402fc..0ad6ca76c 100644 --- a/core/lib/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuilderTest.php +++ b/core/lib/Thelia/Tests/FileFormat/Archive/ArchiveBuilder/ZipArchiveBuilderTest.php @@ -53,52 +53,52 @@ class ZipArchiveBuilderTest extends \PHPUnit_Framework_TestCase { $this->assertEquals( "foo", - $this->zip->getFilePath("foo") + $this->zip->formatFilePath("foo") ); $this->assertEquals( "foo", - $this->zip->getFilePath("/foo") + $this->zip->formatFilePath("/foo") ); $this->assertEquals( "foo", - $this->zip->getFilePath("foo/") + $this->zip->formatFilePath("foo/") ); $this->assertEquals( "foo", - $this->zip->getFilePath("/foo/") + $this->zip->formatFilePath("/foo/") ); $this->assertEquals( "/foo/bar", - $this->zip->getFilePath("foo/bar") + $this->zip->formatFilePath("foo/bar") ); $this->assertEquals( "/foo/bar", - $this->zip->getFilePath("/foo/bar") + $this->zip->formatFilePath("/foo/bar") ); $this->assertEquals( "/foo/bar", - $this->zip->getFilePath("/foo//bar/") + $this->zip->formatFilePath("/foo//bar/") ); $this->assertEquals( "/foo/bar", - $this->zip->getFilePath("/foo/bar/") + $this->zip->formatFilePath("/foo/bar/") ); $this->assertEquals( "/foo/bar/baz", - $this->zip->getFilePath("foo/bar/baz") + $this->zip->formatFilePath("foo/bar/baz") ); $this->assertEquals( "/foo/bar/baz", - $this->zip->getFilePath("//foo/bar///baz/") + $this->zip->formatFilePath("//foo/bar///baz/") ); } @@ -106,52 +106,52 @@ class ZipArchiveBuilderTest extends \PHPUnit_Framework_TestCase { $this->assertEquals( "/foo/", - $this->zip->getDirectoryPath("foo") + $this->zip->formatDirectoryPath("foo") ); $this->assertEquals( "/foo/", - $this->zip->getDirectoryPath("/foo") + $this->zip->formatDirectoryPath("/foo") ); $this->assertEquals( "/foo/", - $this->zip->getDirectoryPath("foo/") + $this->zip->formatDirectoryPath("foo/") ); $this->assertEquals( "/foo/", - $this->zip->getDirectoryPath("/foo/") + $this->zip->formatDirectoryPath("/foo/") ); $this->assertEquals( "/foo/bar/", - $this->zip->getDirectoryPath("foo/bar") + $this->zip->formatDirectoryPath("foo/bar") ); $this->assertEquals( "/foo/bar/", - $this->zip->getDirectoryPath("/foo/bar") + $this->zip->formatDirectoryPath("/foo/bar") ); $this->assertEquals( "/foo/bar/", - $this->zip->getDirectoryPath("/foo//bar/") + $this->zip->formatDirectoryPath("/foo//bar/") ); $this->assertEquals( "/foo/bar/", - $this->zip->getDirectoryPath("/foo/bar/") + $this->zip->formatDirectoryPath("/foo/bar/") ); $this->assertEquals( "/foo/bar/baz/", - $this->zip->getDirectoryPath("foo/bar/baz") + $this->zip->formatDirectoryPath("foo/bar/baz") ); $this->assertEquals( "/foo/bar/baz/", - $this->zip->getDirectoryPath("//foo/bar///baz/") + $this->zip->formatDirectoryPath("//foo/bar///baz/") ); } @@ -169,7 +169,7 @@ class ZipArchiveBuilderTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException \Thelia\Core\FileFormat\Archive\ArchiveBuilder\ZipArchiveException + * @expectedException \Thelia\Core\FileFormat\Archive\ArchiveBuilder\Exception\ZipArchiveException * @expectedExceptionMessage [Zip Error] The file is not a zip archive */ public function testLoadNotValidZip() @@ -202,7 +202,7 @@ class ZipArchiveBuilderTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException \Thelia\Core\FileFormat\Archive\ArchiveBuilder\ZipArchiveException + * @expectedException \Thelia\Core\FileFormat\Archive\ArchiveBuilder\Exception\ZipArchiveException * @expectedExceptionMessage [Zip Error] The file is not a zip archive */ public function testLoadOnlineAvailableAndNotValidFile() @@ -339,4 +339,49 @@ class ZipArchiveBuilderTest extends \PHPUnit_Framework_TestCase $loadedArchiveResponseContent ); } + + public function testAddValidFileFromString() + { + $this->loadedZip->addFileFromString( + "foo", "bar" + ); + + $this->assertTrue( + $this->loadedZip->hasFile("bar") + ); + + $this->assertEquals( + "foo", + $this->loadedZip->getFileContent("bar") + ); + } + + /** + * @expectedException \Thelia\Exception\FileNotFoundException + */ + public function testGetFileContentFileNotFound() + { + $this->loadedZip->getFileContent("bar"); + } + + /** + * @expectedException \ErrorException + */ + public function testAddNotValidFileFromString() + { + $this->loadedZip->addFileFromString( + "foo", $this + ); + } + + /** + * @expectedException \ErrorException + */ + public function testAddNotValidFileValueFromString() + { + $this->loadedZip->addFileFromString( + $this, "bar" + ); + } + } \ No newline at end of file