Zip.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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. * @param string $source Path for the existing zip
  56. * @param string $destination Path to extract the zip files
  57. * @param array $options
  58. * @return bool
  59. */
  60. public static function extract($source, $destination, $options = [])
  61. {
  62. extract(array_merge([
  63. 'mask' => 0777,
  64. ], $options));
  65. if (file_exists($destination) || mkdir($destination, $mask, true)) {
  66. $zip = new ZipArchive;
  67. if ($zip->open($source) === true) {
  68. $zip->extractTo($destination);
  69. $zip->close();
  70. return true;
  71. }
  72. }
  73. return false;
  74. }
  75. /**
  76. * Creates a new empty zip file.
  77. * @param string $destination Path for the new zip
  78. * @param mixed $source
  79. * @param array $options
  80. * @return self
  81. */
  82. public static function make($destination, $source, $options = [])
  83. {
  84. $zip = new self;
  85. $zip->open($destination, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
  86. if (is_string($source)) {
  87. $zip->add($source, $options);
  88. } elseif (is_callable($source)) {
  89. $source($zip);
  90. } elseif (is_array($source)) {
  91. foreach ($source as $_source) {
  92. $zip->add($_source, $options);
  93. }
  94. }
  95. $zip->close();
  96. return $zip;
  97. }
  98. /**
  99. * Includes a source to the Zip.
  100. * @param mixed $source
  101. * @param array $options
  102. * @return self
  103. */
  104. public function add($source, $options = [])
  105. {
  106. /*
  107. * A directory has been supplied, convert it to a useful glob
  108. *
  109. * The wildcard for including hidden files:
  110. * - isn't hidden with an '.'
  111. * - is hidden with a '.' but is followed by a non '.' character
  112. * - starts with '..' but has at least one character after it
  113. */
  114. if (is_dir($source)) {
  115. $includeHidden = isset($options['includeHidden']) && $options['includeHidden'];
  116. $wildcard = $includeHidden ? '{*,.[!.]*,..?*}' : '*';
  117. $source = implode('/', [dirname($source), basename($source), $wildcard]);
  118. }
  119. extract(array_merge([
  120. 'recursive' => true,
  121. 'includeHidden' => false,
  122. 'basedir' => dirname($source),
  123. 'baseglob' => basename($source),
  124. ], $options));
  125. if (is_file($source)) {
  126. $files = [$source];
  127. $recursive = false;
  128. } else {
  129. $files = glob($source, GLOB_BRACE);
  130. $folders = glob(dirname($source).'/*', GLOB_ONLYDIR);
  131. }
  132. foreach ($files as $file) {
  133. if (! is_file($file)) {
  134. continue;
  135. }
  136. $localpath = $this->removePathPrefix($basedir.'/', dirname($file).'/');
  137. $localfile = $this->folderPrefix.$localpath.basename($file);
  138. $this->addFile($file, $localfile);
  139. }
  140. if (! $recursive) {
  141. return $this;
  142. }
  143. foreach ($folders as $folder) {
  144. if (! is_dir($folder)) {
  145. continue;
  146. }
  147. $localpath = $this->folderPrefix.$this->removePathPrefix($basedir.'/', $folder.'/');
  148. $this->addEmptyDir($localpath);
  149. $this->add($folder.'/'.$baseglob, array_merge($options, ['basedir' => $basedir]));
  150. }
  151. return $this;
  152. }
  153. /**
  154. * Creates a new folder inside the Zip and adds source files (optional).
  155. * @param string $name Folder name
  156. * @param mixed $source
  157. * @return self
  158. */
  159. public function folder($name, $source = null)
  160. {
  161. $prefix = $this->folderPrefix;
  162. $this->addEmptyDir($prefix.$name);
  163. if ($source === null) {
  164. return $this;
  165. }
  166. $this->folderPrefix = $prefix.$name.'/';
  167. if (is_string($source)) {
  168. $this->add($source);
  169. } elseif (is_callable($source)) {
  170. $source($this);
  171. } elseif (is_array($source)) {
  172. foreach ($source as $_source) {
  173. $this->add($_source);
  174. }
  175. }
  176. $this->folderPrefix = $prefix;
  177. return $this;
  178. }
  179. /**
  180. * Removes a file or folder from the zip collection.
  181. * Does not support wildcards.
  182. * @param string $source
  183. * @return self
  184. */
  185. public function remove($source)
  186. {
  187. if (is_array($source)) {
  188. foreach ($source as $_source) {
  189. $this->remove($_source);
  190. }
  191. }
  192. if (! is_string($source)) {
  193. return $this;
  194. }
  195. if (substr($source, 0, 1) == '/') {
  196. $source = substr($source, 1);
  197. }
  198. for ($i = 0; $i < $this->numFiles; $i++) {
  199. $stats = $this->statIndex($i);
  200. if (substr($stats['name'], 0, strlen($source)) == $source) {
  201. $this->deleteIndex($i);
  202. }
  203. }
  204. return $this;
  205. }
  206. /**
  207. * Removes a prefix from a path.
  208. * @param string $prefix /var/sites/
  209. * @param string $path /var/sites/moo/cow/
  210. * @return string moo/cow/
  211. */
  212. protected function removePathPrefix($prefix, $path)
  213. {
  214. return (strpos($path, $prefix) === 0)
  215. ? substr($path, strlen($prefix))
  216. : $path;
  217. }
  218. }