Zip.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. namespace Dcat\Admin\Support;
  3. /**
  4. * Zip helper.
  5. *
  6. * @author Alexey Bobkov, Samuel Georges
  7. *
  8. * Usage:
  9. *
  10. * Zip::make('file.zip', '/some/path/*.php');
  11. *
  12. * Zip::make('file.zip', function($zip) {
  13. *
  14. * // Add all PHP files and directories
  15. * $zip->add('/some/path/*.php');
  16. *
  17. * // Do not include subdirectories, one level only
  18. * $zip->add('/non/recursive/*', ['recursive' => false]);
  19. *
  20. * // Add multiple paths
  21. * $zip->add([
  22. * '/collection/of/paths/*',
  23. * '/a/single/file.php'
  24. * ]);
  25. *
  26. * // Add all INI files to a zip folder "config"
  27. * $zip->folder('/config', '/path/to/config/*.ini');
  28. *
  29. * // Add multiple paths to a zip folder "images"
  30. * $zip->folder('/images', function($zip) {
  31. * $zip->add('/my/gifs/*.gif', );
  32. * $zip->add('/photo/reel/*.{png,jpg}', );
  33. * });
  34. *
  35. * // Remove these files/folders from the zip
  36. * $zip->remove([
  37. * '.htaccess',
  38. * 'config.php',
  39. * 'some/folder'
  40. * ]);
  41. *
  42. * });
  43. *
  44. * Zip::extract('file.zip', '/destination/path');
  45. */
  46. use ZipArchive;
  47. class Zip extends ZipArchive
  48. {
  49. /**
  50. * @var string Folder prefix
  51. */
  52. protected $folderPrefix = '';
  53. /**
  54. * Extract an existing zip file.
  55. *
  56. * @param string $source Path for the existing zip
  57. * @param string $destination Path to extract the zip files
  58. * @param array $options
  59. * @return bool
  60. */
  61. public static function extract($source, $destination, $options = [])
  62. {
  63. extract(array_merge([
  64. 'mask' => 0777,
  65. ], $options));
  66. if (file_exists($destination) || mkdir($destination, $mask, true)) {
  67. $zip = new ZipArchive;
  68. if ($zip->open($source) === true) {
  69. $zip->extractTo($destination);
  70. $zip->close();
  71. return true;
  72. }
  73. }
  74. return false;
  75. }
  76. /**
  77. * Creates a new empty zip file.
  78. *
  79. * @param string $destination Path for the new zip
  80. * @param mixed $source
  81. * @param array $options
  82. * @return self
  83. */
  84. public static function make($destination, $source, $options = [])
  85. {
  86. $zip = new self;
  87. $zip->open($destination, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
  88. if (is_string($source)) {
  89. $zip->add($source, $options);
  90. } elseif (is_callable($source)) {
  91. $source($zip);
  92. } elseif (is_array($source)) {
  93. foreach ($source as $_source) {
  94. $zip->add($_source, $options);
  95. }
  96. }
  97. $zip->close();
  98. return $zip;
  99. }
  100. /**
  101. * Includes a source to the Zip.
  102. *
  103. * @param mixed $source
  104. * @param array $options
  105. * @return self
  106. */
  107. public function add($source, $options = [])
  108. {
  109. /*
  110. * A directory has been supplied, convert it to a useful glob
  111. *
  112. * The wildcard for including hidden files:
  113. * - isn't hidden with an '.'
  114. * - is hidden with a '.' but is followed by a non '.' character
  115. * - starts with '..' but has at least one character after it
  116. */
  117. if (is_dir($source)) {
  118. $includeHidden = isset($options['includeHidden']) && $options['includeHidden'];
  119. $wildcard = $includeHidden ? '{*,.[!.]*,..?*}' : '*';
  120. $source = implode('/', [dirname($source), Helper::basename($source), $wildcard]);
  121. }
  122. extract(array_merge([
  123. 'recursive' => true,
  124. 'includeHidden' => false,
  125. 'basedir' => dirname($source),
  126. 'baseglob' => Helper::basename($source),
  127. ], $options));
  128. if (is_file($source)) {
  129. $files = [$source];
  130. $recursive = false;
  131. } else {
  132. $files = glob($source, GLOB_BRACE);
  133. $folders = glob(dirname($source).'/*', GLOB_ONLYDIR);
  134. }
  135. foreach ($files as $file) {
  136. if (! is_file($file)) {
  137. continue;
  138. }
  139. $localpath = $this->removePathPrefix($basedir.'/', dirname($file).'/');
  140. $localfile = $this->folderPrefix.$localpath.Helper::basename($file);
  141. $this->addFile($file, $localfile);
  142. }
  143. if (! $recursive) {
  144. return $this;
  145. }
  146. foreach ($folders as $folder) {
  147. if (! is_dir($folder)) {
  148. continue;
  149. }
  150. $localpath = $this->folderPrefix.$this->removePathPrefix($basedir.'/', $folder.'/');
  151. $this->addEmptyDir($localpath);
  152. $this->add($folder.'/'.$baseglob, array_merge($options, ['basedir' => $basedir]));
  153. }
  154. return $this;
  155. }
  156. /**
  157. * Creates a new folder inside the Zip and adds source files (optional).
  158. *
  159. * @param string $name Folder name
  160. * @param mixed $source
  161. * @return self
  162. */
  163. public function folder($name, $source = null)
  164. {
  165. $prefix = $this->folderPrefix;
  166. $this->addEmptyDir($prefix.$name);
  167. if ($source === null) {
  168. return $this;
  169. }
  170. $this->folderPrefix = $prefix.$name.'/';
  171. if (is_string($source)) {
  172. $this->add($source);
  173. } elseif (is_callable($source)) {
  174. $source($this);
  175. } elseif (is_array($source)) {
  176. foreach ($source as $_source) {
  177. $this->add($_source);
  178. }
  179. }
  180. $this->folderPrefix = $prefix;
  181. return $this;
  182. }
  183. /**
  184. * Removes a file or folder from the zip collection.
  185. * Does not support wildcards.
  186. *
  187. * @param string $source
  188. * @return self
  189. */
  190. public function remove($source)
  191. {
  192. if (is_array($source)) {
  193. foreach ($source as $_source) {
  194. $this->remove($_source);
  195. }
  196. }
  197. if (! is_string($source)) {
  198. return $this;
  199. }
  200. if (substr($source, 0, 1) == '/') {
  201. $source = substr($source, 1);
  202. }
  203. for ($i = 0; $i < $this->numFiles; $i++) {
  204. $stats = $this->statIndex($i);
  205. if (substr($stats['name'], 0, strlen($source)) == $source) {
  206. $this->deleteIndex($i);
  207. }
  208. }
  209. return $this;
  210. }
  211. /**
  212. * Removes a prefix from a path.
  213. *
  214. * @param string $prefix /var/sites/
  215. * @param string $path /var/sites/moo/cow/
  216. * @return string moo/cow/
  217. */
  218. protected function removePathPrefix($prefix, $path)
  219. {
  220. return (strpos($path, $prefix) === 0)
  221. ? substr($path, strlen($prefix))
  222. : $path;
  223. }
  224. }