|
@@ -0,0 +1,254 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace Dcat\Admin\Support;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Zip helper
|
|
|
+ *
|
|
|
+ * @package october\filesystem
|
|
|
+ * @author Alexey Bobkov, Samuel Georges
|
|
|
+ *
|
|
|
+ * Usage:
|
|
|
+ *
|
|
|
+ * Zip::make('file.zip', '/some/path/*.php');
|
|
|
+ *
|
|
|
+ * Zip::make('file.zip', function($zip) {
|
|
|
+ *
|
|
|
+ * // Add all PHP files and directories
|
|
|
+ * $zip->add('/some/path/*.php');
|
|
|
+ *
|
|
|
+ * // Do not include subdirectories, one level only
|
|
|
+ * $zip->add('/non/recursive/*', ['recursive' => false]);
|
|
|
+ *
|
|
|
+ * // Add multiple paths
|
|
|
+ * $zip->add([
|
|
|
+ * '/collection/of/paths/*',
|
|
|
+ * '/a/single/file.php'
|
|
|
+ * ]);
|
|
|
+ *
|
|
|
+ * // Add all INI files to a zip folder "config"
|
|
|
+ * $zip->folder('/config', '/path/to/config/*.ini');
|
|
|
+ *
|
|
|
+ * // Add multiple paths to a zip folder "images"
|
|
|
+ * $zip->folder('/images', function($zip) {
|
|
|
+ * $zip->add('/my/gifs/*.gif', );
|
|
|
+ * $zip->add('/photo/reel/*.{png,jpg}', );
|
|
|
+ * });
|
|
|
+ *
|
|
|
+ * // Remove these files/folders from the zip
|
|
|
+ * $zip->remove([
|
|
|
+ * '.htaccess',
|
|
|
+ * 'config.php',
|
|
|
+ * 'some/folder'
|
|
|
+ * ]);
|
|
|
+ *
|
|
|
+ * });
|
|
|
+ *
|
|
|
+ * Zip::extract('file.zip', '/destination/path');
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+use ZipArchive;
|
|
|
+
|
|
|
+class Zip extends ZipArchive
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * @var string Folder prefix
|
|
|
+ */
|
|
|
+ protected $folderPrefix = '';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Extract an existing zip file.
|
|
|
+ * @param string $source Path for the existing zip
|
|
|
+ * @param string $destination Path to extract the zip files
|
|
|
+ * @param array $options
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ public static function extract($source, $destination, $options = [])
|
|
|
+ {
|
|
|
+ extract(array_merge([
|
|
|
+ 'mask' => 0777
|
|
|
+ ], $options));
|
|
|
+
|
|
|
+ if (file_exists($destination) || mkdir($destination, $mask, true)) {
|
|
|
+ $zip = new ZipArchive;
|
|
|
+ if ($zip->open($source) === true) {
|
|
|
+ $zip->extractTo($destination);
|
|
|
+ $zip->close();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates a new empty zip file.
|
|
|
+ * @param string $destination Path for the new zip
|
|
|
+ * @param mixed $source
|
|
|
+ * @param array $options
|
|
|
+ * @return self
|
|
|
+ */
|
|
|
+ public static function make($destination, $source, $options = [])
|
|
|
+ {
|
|
|
+ $zip = new self;
|
|
|
+ $zip->open($destination, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
|
|
|
+
|
|
|
+ if (is_string($source)) {
|
|
|
+ $zip->add($source, $options);
|
|
|
+ }
|
|
|
+ elseif (is_callable($source)) {
|
|
|
+ $source($zip);
|
|
|
+ }
|
|
|
+ elseif (is_array($source)) {
|
|
|
+ foreach ($source as $_source) {
|
|
|
+ $zip->add($_source, $options);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $zip->close();
|
|
|
+ return $zip;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Includes a source to the Zip
|
|
|
+ * @param mixed $source
|
|
|
+ * @param array $options
|
|
|
+ * @return self
|
|
|
+ */
|
|
|
+ public function add($source, $options = [])
|
|
|
+ {
|
|
|
+ /*
|
|
|
+ * A directory has been supplied, convert it to a useful glob
|
|
|
+ *
|
|
|
+ * The wildcard for including hidden files:
|
|
|
+ * - isn't hidden with an '.'
|
|
|
+ * - is hidden with a '.' but is followed by a non '.' character
|
|
|
+ * - starts with '..' but has at least one character after it
|
|
|
+ */
|
|
|
+ if (is_dir($source)) {
|
|
|
+ $includeHidden = isset($options['includeHidden']) && $options['includeHidden'];
|
|
|
+ $wildcard = $includeHidden ? '{*,.[!.]*,..?*}' : '*';
|
|
|
+ $source = implode('/', [dirname($source), basename($source), $wildcard]);
|
|
|
+ }
|
|
|
+
|
|
|
+ extract(array_merge([
|
|
|
+ 'recursive' => true,
|
|
|
+ 'includeHidden' => false,
|
|
|
+ 'basedir' => dirname($source),
|
|
|
+ 'baseglob' => basename($source)
|
|
|
+ ], $options));
|
|
|
+
|
|
|
+ if (is_file($source)) {
|
|
|
+ $files = [$source];
|
|
|
+ $recursive = false;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ $files = glob($source, GLOB_BRACE);
|
|
|
+ $folders = glob(dirname($source) . '/*', GLOB_ONLYDIR);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($files as $file) {
|
|
|
+ if (!is_file($file)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $localpath = $this->removePathPrefix($basedir.'/', dirname($file).'/');
|
|
|
+ $localfile = $this->folderPrefix . $localpath . basename($file);
|
|
|
+ $this->addFile($file, $localfile);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$recursive) {
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($folders as $folder) {
|
|
|
+ if (!is_dir($folder)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $localpath = $this->folderPrefix . $this->removePathPrefix($basedir.'/', $folder.'/');
|
|
|
+ $this->addEmptyDir($localpath);
|
|
|
+ $this->add($folder.'/'.$baseglob, array_merge($options, ['basedir' => $basedir]));
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates a new folder inside the Zip and adds source files (optional)
|
|
|
+ * @param string $name Folder name
|
|
|
+ * @param mixed $source
|
|
|
+ * @return self
|
|
|
+ */
|
|
|
+ public function folder($name, $source = null)
|
|
|
+ {
|
|
|
+ $prefix = $this->folderPrefix;
|
|
|
+ $this->addEmptyDir($prefix . $name);
|
|
|
+ if ($source === null) {
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->folderPrefix = $prefix . $name . '/';
|
|
|
+
|
|
|
+ if (is_string($source)) {
|
|
|
+ $this->add($source);
|
|
|
+ }
|
|
|
+ elseif (is_callable($source)) {
|
|
|
+ $source($this);
|
|
|
+ }
|
|
|
+ elseif (is_array($source)) {
|
|
|
+ foreach ($source as $_source) {
|
|
|
+ $this->add($_source);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->folderPrefix = $prefix;
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes a file or folder from the zip collection.
|
|
|
+ * Does not support wildcards.
|
|
|
+ * @param string $source
|
|
|
+ * @return self
|
|
|
+ */
|
|
|
+ public function remove($source)
|
|
|
+ {
|
|
|
+ if (is_array($source)) {
|
|
|
+ foreach ($source as $_source) {
|
|
|
+ $this->remove($_source);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!is_string($source)) {
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (substr($source, 0, 1) == '/') {
|
|
|
+ $source = substr($source, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ for ($i = 0; $i < $this->numFiles; $i++) {
|
|
|
+ $stats = $this->statIndex($i);
|
|
|
+ if (substr($stats['name'], 0, strlen($source)) == $source) {
|
|
|
+ $this->deleteIndex($i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes a prefix from a path.
|
|
|
+ * @param string $prefix /var/sites/
|
|
|
+ * @param string $path /var/sites/moo/cow/
|
|
|
+ * @return string moo/cow/
|
|
|
+ */
|
|
|
+ protected function removePathPrefix($prefix, $path)
|
|
|
+ {
|
|
|
+ return (strpos($path, $prefix) === 0)
|
|
|
+ ? substr($path, strlen($prefix))
|
|
|
+ : $path;
|
|
|
+ }
|
|
|
+}
|