xpdozip.class.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <?php
  2. /*
  3. * Copyright 2010-2015 by MODX, LLC.
  4. *
  5. * This file is part of xPDO.
  6. *
  7. * xPDO is free software; you can redistribute it and/or modify it under the
  8. * terms of the GNU General Public License as published by the Free Software
  9. * Foundation; either version 2 of the License, or (at your option) any later
  10. * version.
  11. *
  12. * xPDO is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * xPDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  18. * Suite 330, Boston, MA 02111-1307 USA
  19. */
  20. /**
  21. * A class to abstract compression support for ZIP format.
  22. *
  23. * This file contains the xPDOZip class to represent ZIP archives.
  24. *
  25. * @package xpdo
  26. * @subpackage compression
  27. */
  28. /**
  29. * Represents a compressed archive in ZIP format.
  30. *
  31. * @package xpdo
  32. * @subpackage compression
  33. */
  34. class xPDOZip {
  35. const CREATE = 'create';
  36. const OVERWRITE = 'overwrite';
  37. const ZIP_TARGET = 'zip_target';
  38. public $xpdo = null;
  39. protected $_filename = '';
  40. protected $_options = array();
  41. protected $_archive = null;
  42. protected $_errors = array();
  43. /**
  44. * Construct an instance representing a specific archive.
  45. *
  46. * @param xPDO &$xpdo A reference to an xPDO instance.
  47. * @param string $filename The name of the archive the instance will represent.
  48. * @param array $options An array of options for this instance.
  49. */
  50. public function __construct(xPDO &$xpdo, $filename, array $options = array()) {
  51. $this->xpdo =& $xpdo;
  52. $this->_filename = is_string($filename) ? $filename : '';
  53. $this->_options = is_array($options) ? $options : array();
  54. $this->_archive = new ZipArchive();
  55. if (!empty($this->_filename) && file_exists(dirname($this->_filename))) {
  56. if (file_exists($this->_filename)) {
  57. if ($this->getOption(xPDOZip::OVERWRITE, null, false) && is_writable($this->_filename)) {
  58. if ($this->_archive->open($this->_filename, ZIPARCHIVE::OVERWRITE) !== true) {
  59. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOZip: Error opening archive at {$this->_filename} for OVERWRITE");
  60. }
  61. } else {
  62. if ($this->_archive->open($this->_filename) !== true) {
  63. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOZip: Error opening archive at {$this->_filename}");
  64. }
  65. }
  66. } elseif ($this->getOption(xPDOZip::CREATE, null, false) && is_writable(dirname($this->_filename))) {
  67. if ($this->_archive->open($this->_filename, ZIPARCHIVE::CREATE) !== true) {
  68. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOZip: Could not create archive at {$this->_filename}");
  69. }
  70. } else {
  71. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOZip: The location specified is not writable: {$this->_filename}");
  72. }
  73. } else {
  74. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOZip: The location specified does not exist: {$this->_filename}");
  75. }
  76. }
  77. /**
  78. * Pack the contents from the source into the archive.
  79. *
  80. * @todo Implement custom file filtering options
  81. *
  82. * @param string $source The path to the source file(s) to pack.
  83. * @param array $options An array of options for the operation.
  84. * @return array An array of results for the operation.
  85. */
  86. public function pack($source, array $options = array()) {
  87. $results = array();
  88. if ($this->_archive) {
  89. $target = $this->getOption(xPDOZip::ZIP_TARGET, $options, '');
  90. if (is_dir($source)) {
  91. if ($dh = opendir($source)) {
  92. if ($source[strlen($source) - 1] !== '/') $source .= '/';
  93. $targetDir = rtrim($target, '/');
  94. if (!empty($targetDir)) {
  95. if ($this->_archive->addEmptyDir($targetDir)) {
  96. $results[$target] = "Successfully added directory {$target} from {$source}";
  97. } else {
  98. $results[$target] = "Error adding directory {$target} from {$source}";
  99. $this->_errors[] = $results[$target];
  100. }
  101. }
  102. while (($file = readdir($dh)) !== false) {
  103. if (is_dir($source . $file)) {
  104. if (($file !== '.') && ($file !== '..')) {
  105. $results = $results + $this->pack($source . $file . '/', array_merge($options, array(xPDOZip::ZIP_TARGET => $target . $file . '/')));
  106. }
  107. } elseif (is_file($source . $file)) {
  108. if ($this->_archive->addFile($source . $file, $target . $file)) {
  109. $results[$target . $file] = "Successfully packed {$target}{$file} from {$source}{$file}";
  110. } else {
  111. $results[$target . $file] = "Error packing {$target}{$file} from {$source}{$file}";
  112. $this->_errors[] = $results[$target . $file];
  113. }
  114. } else {
  115. $results[$target . $file] = "Error packing {$target}{$file} from {$source}{$file}";
  116. $this->_errors[] = $results[$target . $file];
  117. }
  118. }
  119. }
  120. } elseif (is_file($source)) {
  121. $file = basename($source);
  122. if ($this->_archive->addFile($source, $target . $file)) {
  123. $results[$target . $file] = "Successfully packed {$target}{$file} from {$source}";
  124. } else {
  125. $results[$target . $file] = "Error packing {$target}{$file} from {$source}";
  126. $this->_errors[] = $results[$target . $file];
  127. }
  128. } else {
  129. $this->_errors[]= "Invalid source specified: {$source}";
  130. }
  131. }
  132. return $results;
  133. }
  134. /**
  135. * Unpack the compressed contents from the archive to the target.
  136. *
  137. * @param string $target The path of the target location to unpack the files.
  138. * @param array $options An array of options for the operation.
  139. * @return bool|array An array of results for the operation.
  140. */
  141. public function unpack($target, $options = array()) {
  142. $results = false;
  143. if ($this->_archive) {
  144. if (is_dir($target) && is_writable($target)) {
  145. if ($this->getOption('check_filetype', $options, false)) {
  146. $fileIndex = array();
  147. for ($i = 0; $i < $this->_archive->numFiles; $i++) {
  148. $filename = $this->_archive->getNameIndex($i);
  149. if ($this->checkFiletype($filename)) {
  150. $fileIndex[] = $filename;
  151. }
  152. }
  153. $results = $this->_archive->extractTo($target, $fileIndex);
  154. } else {
  155. $results = $this->_archive->extractTo($target);
  156. }
  157. }
  158. }
  159. return $results;
  160. }
  161. /**
  162. * Close the archive.
  163. */
  164. public function close() {
  165. if ($this->_archive) {
  166. $this->_archive->close();
  167. }
  168. }
  169. /**
  170. * Get an option from supplied options, the xPDOZip instance options, or xpdo itself.
  171. *
  172. * @param string $key Unique identifier for the option.
  173. * @param array $options A set of explicit options to override those from xPDO or the
  174. * xPDOZip instance.
  175. * @param mixed $default An optional default value to return if no value is found.
  176. * @return mixed The value of the option.
  177. */
  178. public function getOption($key, $options = null, $default = null) {
  179. $option = $default;
  180. if (is_array($key)) {
  181. if (!is_array($option)) {
  182. $default= $option;
  183. $option= array();
  184. }
  185. foreach ($key as $k) {
  186. $option[$k]= $this->getOption($k, $options, $default);
  187. }
  188. } elseif (is_string($key) && !empty($key)) {
  189. if (is_array($options) && !empty($options) && array_key_exists($key, $options)) {
  190. $option = $options[$key];
  191. } elseif (is_array($this->_options) && !empty($this->_options) && array_key_exists($key, $this->_options)) {
  192. $option = $this->_options[$key];
  193. } else {
  194. $option = $this->xpdo->getOption($key, null, $default);
  195. }
  196. }
  197. return $option;
  198. }
  199. /**
  200. * Detect if errors occurred during any operations.
  201. *
  202. * @return boolean True if errors exist, otherwise false.
  203. */
  204. public function hasError() {
  205. return !empty($this->_errors);
  206. }
  207. /**
  208. * Return any errors from operations on the archive.
  209. */
  210. public function getErrors() {
  211. return $this->_errors;
  212. }
  213. /**
  214. * Check that the filename has a file type extension that is allowed
  215. *
  216. * @param $filename
  217. * @return bool
  218. */
  219. private function checkFiletype($filename)
  220. {
  221. if ($this->xpdo->getOption('allowedFileTypes')) {
  222. $allowedFileTypes = $this->getOption('allowedFileTypes');
  223. $allowedFileTypes = (!is_array($allowedFileTypes)) ? explode(',', $allowedFileTypes) : $allowedFileTypes;
  224. } else {
  225. $allowedFiles = $this->xpdo->getOption('upload_files') ? explode(',', $this->xpdo->getOption('upload_files')) : array();
  226. $allowedImages = $this->xpdo->getOption('upload_images') ? explode(',', $this->xpdo->getOption('upload_images')) : array();
  227. $allowedMedia = $this->xpdo->getOption('upload_media') ? explode(',', $this->xpdo->getOption('upload_media')) : array();
  228. $allowedFlash = $this->xpdo->getOption('upload_flash') ? explode(',', $this->xpdo->getOption('upload_flash')) : array();
  229. $allowedFileTypes = array_unique(array_merge($allowedFiles, $allowedImages, $allowedMedia, $allowedFlash));
  230. $this->xpdo->setOption('allowedFileTypes', $allowedFileTypes);
  231. }
  232. $ext = pathinfo($filename, PATHINFO_EXTENSION);
  233. $ext = strtolower($ext);
  234. if (!empty($allowedFileTypes) && !in_array($ext, $allowedFileTypes)) {
  235. $this->xpdo->log(XPDO::LOG_LEVEL_WARN, $filename .' can\'t be extracted, because the file type is not allowed');
  236. return false;
  237. }
  238. return true;
  239. }
  240. }