PageRenderTime 59ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/backupbuddy/lib/zipbuddy/zipbuddy.php

https://bitbucket.org/betaimages/chakalos
PHP | 876 lines | 562 code | 148 blank | 166 comment | 158 complexity | e6e129cb75fc9f52b595209d3265ba1a MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * pluginbuddy_zipbuddy Class
  4. *
  5. * Handles zipping and unzipping, using the best methods available and falling back to worse methods
  6. * as needed for compatibility. Allows for forcing compatibility modes.
  7. *
  8. * Version: 3.0.0
  9. * Author: Dustin Bolton
  10. * Author URI: http://dustinbolton.com/
  11. *
  12. * $temp_dir string Temporary directory absolute path for temporary file storage. Must be writable!
  13. * $zip_methods array Optional. Array of available zip methods to use. Useful for not having to re-test every time.
  14. * If omitted then a test will be performed to find the methods that work on this host.
  15. * $mode string Future use to allow for other compression methods other than zip. Currently not in use.
  16. *
  17. */
  18. // Try and load the experimental version - if successful then class will exist and remaining code will be ignored
  19. if (
  20. ( defined( 'USE_EXPERIMENTAL_ZIPBUDDY' ) && ( true === USE_EXPERIMENTAL_ZIPBUDDY ) )
  21. ||
  22. ( isset( pb_backupbuddy::$options['alternative_zip'] ) && ( '1' == pb_backupbuddy::$options['alternative_zip'] ) )
  23. ) {
  24. require_once( dirname( __FILE__ ) . '/x-zipbuddy.php' );
  25. }
  26. if ( !class_exists( "pluginbuddy_zipbuddy" ) ) {
  27. class pluginbuddy_zipbuddy {
  28. /********** Properties **********/
  29. const ZIP_METHODS_TRANSIENT = 'pb_backupbuddy_avail_zip_methods_classic';
  30. const ZIP_EXECPATH_TRANSIENT = 'pb_backupbuddy_exec_path_classic';
  31. const ZIP_TRANSIENT_LIFE = 60;
  32. private $_commandbuddy;
  33. public $_zip_methods; // Array of available zip methods.
  34. /********** Methods **********/
  35. function __construct( $temp_dir, $zip_methods = array(), $mode = 'zip' ) {
  36. //$this->_status = array();
  37. $this->_tempdir = $temp_dir;
  38. $this->_execpath = '';
  39. // Handles command line execution.
  40. require_once( pb_backupbuddy::plugin_path() . '/lib/commandbuddy/commandbuddy.php' );
  41. $this->_commandbuddy = new pb_backupbuddy_commandbuddy();
  42. if ( !empty( $zip_methods ) && ( count( $zip_methods ) > 0 ) ) {
  43. $this->_zip_methods = $zip_methods;
  44. } else {
  45. if ( function_exists( 'get_transient' ) ) { // Inside WordPress
  46. if ( pb_backupbuddy::$options['disable_zipmethod_caching'] == '1' ) {
  47. pb_backupbuddy::status( 'details', 'Zip method caching disabled based on settings.' );
  48. $available_methods = false;
  49. $exec_path = false;
  50. } else { // Use caching.
  51. $available_methods = get_transient( self::ZIP_METHODS_TRANSIENT );
  52. $exec_path = get_transient( self::ZIP_EXECPATH_TRANSIENT );
  53. }
  54. if ( ( $available_methods === false ) || ( $exec_path === false ) ) {
  55. pb_backupbuddy::status( 'details', 'Zip methods or exec path were not cached; detecting...' );
  56. $this->_zip_methods = $this->available_zip_methods( false, $mode );
  57. set_transient( self::ZIP_METHODS_TRANSIENT, $this->_zip_methods, self::ZIP_TRANSIENT_LIFE );
  58. set_transient( self::ZIP_EXECPATH_TRANSIENT, $this->_execpath, self::ZIP_TRANSIENT_LIFE ); // Calculated and set in available_zip_methods().
  59. pb_backupbuddy::status( 'details', 'Caching zipbuddy classic methods & exec path for `' . self::ZIP_TRANSIENT_LIFE . '` seconds.' );
  60. } else {
  61. pb_backupbuddy::status( 'details', 'Using cached zipbuddy classic methods: `' . implode( ',', $available_methods ) . '`.' );
  62. pb_backupbuddy::status( 'details', 'Using cached zipbuddy classic exec path: `' . $exec_path . '`.' );
  63. $this->_zip_methods = $available_methods;
  64. }
  65. } else { // Outside WordPress
  66. $this->_zip_methods = $this->available_zip_methods( false, $mode );
  67. pb_backupbuddy::status( 'details', 'Zipbuddy classic methods not cached due to being outside WordPress.' );
  68. }
  69. }
  70. }
  71. // Returns true if the file (with path) exists in the ZIP.
  72. // If leave_open is true then the zip object will be left open for faster checking for subsequent files within this zip
  73. function file_exists( $zip_file, $locate_file, $leave_open = false ) {
  74. if ( in_array( 'ziparchive', $this->_zip_methods ) ) {
  75. $this->_zip = new ZipArchive;
  76. if ( $this->_zip->open( $zip_file ) === true ) {
  77. if ( $this->_zip->locateName( $locate_file ) === false ) { // File not found in zip.
  78. $this->_zip->close();
  79. pb_backupbuddy::status( 'details', __('File not found (ziparchive)','it-l10n-backupbuddy' ) . ': ' . $locate_file );
  80. return false;
  81. } else {
  82. pb_backupbuddy::status( 'details', __('File found (ziparchive)','it-l10n-backupbuddy' ) . ': ' . $locate_file );
  83. }
  84. $this->_zip->close();
  85. return true; // Never ran into a file missing so must have found them all.
  86. } else {
  87. pb_backupbuddy::status( 'details', sprintf( __('ZipArchive failed to open file to check if file exists (looking for %1$s in %2$s).','it-l10n-backupbuddy' ), $locate_file , $zip_file ) );
  88. return false;
  89. }
  90. }
  91. // If we made it this far then ziparchive not available/failed.
  92. if ( in_array( 'pclzip', $this->_zip_methods ) ) {
  93. require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' );
  94. $this->_zip = new PclZip( $zip_file );
  95. if ( ( $file_list = $this->_zip->listContent() ) == 0 ) { // If zero, zip is corrupt or empty.
  96. pb_backupbuddy::status( 'details', $this->_zip->errorInfo( true ) );
  97. } else {
  98. foreach( $file_list as $file ) {
  99. if ( $file['filename'] == $locate_file ) { // Found file.
  100. return true;
  101. }
  102. }
  103. pb_backupbuddy::status( 'details', __('File not found (pclzip)','it-l10n-backupbuddy' ) . ': ' . $locate_file );
  104. return false;
  105. }
  106. } else {
  107. pb_backupbuddy::status( 'details', __('Unable to check if file exists: No compatible zip method found.','it-l10n-backupbuddy' ) );
  108. return false;
  109. }
  110. }
  111. /* set_comment()
  112. *
  113. * Retrieve archive comment.
  114. *
  115. * @param string $zip_file Filename of archive to set comment on.
  116. * @param string $comment Comment to apply to archive.
  117. * @return boolean/string true on success, error message otherwise.
  118. */
  119. function set_comment( $zip_file, $comment ) {
  120. if ( in_array( 'ziparchive', $this->_zip_methods ) ) {
  121. $this->_zip = new ZipArchive;
  122. if ( $this->_zip->open( $zip_file ) === true ) {
  123. $result = $this->_zip->setArchiveComment( $comment );
  124. $this->_zip->close();
  125. return $result;
  126. return true; // Never ran into a file missing so must have found them all.
  127. } else {
  128. $message = 'ZipArchive failed to open file to set comment in file: `' . $zip_file . '`.';
  129. pb_backupbuddy::status( 'details', $message );
  130. return $message;
  131. }
  132. }
  133. $message = "\n\nYour host does not support ZipArchive.\nThe note will only be stored internally in your settings and not in the zip file itself.";
  134. pb_backupbuddy::status( 'details', $message );
  135. return $message;
  136. } // End set_comment().
  137. /* get_comment()
  138. *
  139. * Retrieve archive comment.
  140. *
  141. * @param string $zip_file Filename of archive to retrieve comment from.
  142. * @return string Zip comment.
  143. */
  144. function get_comment( $zip_file ) {
  145. if ( in_array( 'ziparchive', $this->_zip_methods ) ) {
  146. $this->_zip = new ZipArchive;
  147. if ( $this->_zip->open( $zip_file ) === true ) {
  148. $comment = $this->_zip->getArchiveComment();
  149. $this->_zip->close();
  150. return $comment;
  151. return true; // Never ran into a file missing so must have found them all.
  152. } else {
  153. pb_backupbuddy::status( 'details', sprintf( __('ZipArchive failed to open file to retrieve comment in file %1$s','it-l10n-backupbuddy' ), $zip_file ) );
  154. return false;
  155. }
  156. }
  157. // If we made it this far then ziparchive not available/failed.
  158. if ( in_array( 'pclzip', $this->_zip_methods ) ) {
  159. if ( !class_exists( 'PclZip' ) ) {
  160. return false;
  161. }
  162. $this->_zip = new PclZip( $zip_file );
  163. if ( ( $comment = $this->_zip->properties() ) == 0 ) { // If zero, zip is corrupt or no comment.
  164. return false;
  165. } else {
  166. return $comment['comment'];
  167. }
  168. }
  169. pb_backupbuddy::status( 'details', __('Unable to get comment: No compatible zip method found.','it-l10n-backupbuddy' ) );
  170. return false;
  171. } // End get_comment().
  172. // FOR FUTURE USE; NOT YET IMPLEMENTED. Use to check .sql file is non-empty.
  173. function file_stats( $zip_file, $locate_file, $leave_open = false ) {
  174. if ( in_array( 'ziparchive', $this->_zip_methods ) ) {
  175. $this->_zip = new ZipArchive;
  176. if ( $this->_zip->open( $zip_file ) === true ) {
  177. if ( ( $stats = $this->_zip->statName( $locate_file ) ) === false ) { // File not found in zip.
  178. $this->_zip->close();
  179. pb_backupbuddy::status( 'details', __('File not found (ziparchive) for stats','it-l10n-backupbuddy' ) . ': ' . $locate_file );
  180. return false;
  181. }
  182. $this->_zip->close();
  183. return $stats;
  184. } else {
  185. pb_backupbuddy::status( 'details', sprintf( __('ZipArchive failed to open file to check stats (looking in %1$s).','it-l10n-backupbuddy' ), $zip_file ) );
  186. return false;
  187. }
  188. }
  189. // If we made it this far then ziparchive not available/failed.
  190. if ( in_array( 'pclzip', $this->_zip_methods ) ) {
  191. require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' );
  192. $this->_zip = new PclZip( $zip_file );
  193. if ( ( $file_list = $this->_zip->listContent() ) == 0 ) { // If zero, zip is corrupt or empty.
  194. pb_backupbuddy::status( 'details', $this->_zip->errorInfo( true ) );
  195. } else {
  196. foreach( $file_list as $file ) {
  197. if ( $file['filename'] == $locate_file ) { // Found file.
  198. return true;
  199. }
  200. }
  201. pb_backupbuddy::status( 'details', __('File not found (pclzip)','it-l10n-backupbuddy' ) . ': ' . $locate_file );
  202. return false;
  203. }
  204. } else {
  205. pb_backupbuddy::status( 'details', __('Unable to check if file exists: No compatible zip method found.','it-l10n-backupbuddy' ) );
  206. return false;
  207. }
  208. }
  209. /* get_zip_methods()
  210. *
  211. * Get an array of the zip methods. Useful for transient caching for constructor.
  212. *
  213. * @return array Array of methods.
  214. */
  215. public function get_zip_methods() {
  216. $this->_zip_methods;
  217. } // End get_zip_methods();
  218. /**
  219. * add_directory_to_zip()
  220. *
  221. * Adds a directory to a new or existing (TODO: not yet available) ZIP file.
  222. *
  223. * $zip_file string Full path & filename of ZIP file to create.
  224. * $add_directory string Full directory to add to zip file.
  225. * $compression boolean True to enable ZIP compression,
  226. * (if possible with available zip methods)
  227. * $excludes array(strings) Array of strings of paths/files to exclude from zipping,
  228. * (if possible with available zip methods).
  229. * $temporary_zip_directory string Optional. Full directory path to directory to temporarily place ZIP
  230. * file while creating. Uses same directory if omitted.
  231. * $force_compatibility_mode boolean True: only use PCLZip. False: try exec first if available,
  232. * and fallback to lesser methods as required.
  233. *
  234. * @return true on success, false otherwise
  235. *
  236. */
  237. function add_directory_to_zip( $zip_file, $add_directory, $compression, $excludes = array(), $temporary_zip_directory = '', $force_compatibility_mode = false ) {
  238. if ( $force_compatibility_mode === true ) {
  239. $zip_methods = array( 'pclzip' );
  240. pb_backupbuddy::status( 'message', __('Forced compatibility mode (PCLZip) based on settings. This is slower and less reliable.','it-l10n-backupbuddy' ) );
  241. } else {
  242. $zip_methods = $this->_zip_methods;
  243. pb_backupbuddy::status( 'details', __('Using all available zip methods in preferred order.','it-l10n-backupbuddy' ) );
  244. }
  245. $append = false; // Possible future option to allow appending if file exists.
  246. // Normalize $temporary_zip_directory to format: /xxx/yyy/zzz/.
  247. $temporary_zip_directory = rtrim( $temporary_zip_directory, '/\\' ) . '/';
  248. if ( !empty( $temporary_zip_directory ) ) {
  249. if ( !file_exists( $temporary_zip_directory ) ) { // Create temp dir if it does not exist.
  250. mkdir( $temporary_zip_directory );
  251. }
  252. }
  253. if ( is_array( $excludes ) ) {
  254. $excludes_text = implode( ',', $excludes );
  255. } else {
  256. $excludes_text = '(in file: `' . $excludes . '`)';
  257. }
  258. pb_backupbuddy::status( 'details', __('Creating ZIP file','it-l10n-backupbuddy' ) . ' `' . $zip_file . '`. ' . __('Adding directory','it-l10n-backupbuddy' ) . ' `' . $add_directory . '`. ' . __('Compression','it-l10n-backupbuddy' ) . ': ' . $compression . '; ' . __('Excludes','it-l10n-backupbuddy' ) . ': ' . $excludes_text );
  259. unset( $excludes_text );
  260. if ( in_array( 'exec', $zip_methods ) ) {
  261. pb_backupbuddy::status( 'details', __('Using exec() method for ZIP.','it-l10n-backupbuddy' ) );
  262. $command = 'zip -q -r';
  263. if ( $compression !== true ) {
  264. $command .= ' -0';
  265. pb_backupbuddy::status( 'details', __('Exec compression disabled based on settings.','it-l10n-backupbuddy' ) );
  266. }
  267. if ( file_exists( $zip_file ) ) {
  268. if ( $append === true ) {
  269. pb_backupbuddy::status( 'details', __('ZIP file exists. Appending based on options.','it-l10n-backupbuddy' ) );
  270. $command .= ' -g';
  271. } else {
  272. pb_backupbuddy::status( 'details', __('ZIP file exists. Deleting & writing based on options.','it-l10n-backupbuddy' ) );
  273. unlink( $zip_file );
  274. }
  275. }
  276. //$command .= " -r";
  277. // Set temporary directory to store ZIP while it's being generated.
  278. if ( !empty( $temporary_zip_directory ) ) {
  279. $command .= " -b '{$temporary_zip_directory}'";
  280. }
  281. $command .= " '{$zip_file}' .";
  282. // -i '*'"; // Not needed. Zip defaults to doing this. Removed July 10, 2012 for v3.0.41.
  283. // Handle exclusions by placing them in an exclusion text file.
  284. $exclusion_file = $temporary_zip_directory . 'exclusions.txt';
  285. $this->_render_exclusions_file( $exclusion_file, $excludes );
  286. pb_backupbuddy::status( 'details', 'Using exclusion file `' . $exclusion_file . '`.' );
  287. $command .= ' -x@' . $exclusion_file;
  288. $command .= ' 2>&1'; // 2>&1 redirects STDERR to STDOUT
  289. $working_dir = getcwd();
  290. chdir( $add_directory ); // Change directory to the path we are adding.
  291. if ( $this->_execpath != '' ) {
  292. pb_backupbuddy::status( 'details', __( 'Using custom exec() path: ', 'it-l10n-backupbuddy' ) . $this->_execpath );
  293. }
  294. // Run ZIP command.
  295. if ( stristr( PHP_OS, 'WIN' ) && !stristr( PHP_OS, 'DARWIN' ) ) { // Running Windows. (not darwin)
  296. if ( file_exists( ABSPATH . 'zip.exe' ) ) {
  297. pb_backupbuddy::status( 'message', __('Attempting to use provided Windows zip.exe.','it-l10n-backupbuddy' ) );
  298. $command = str_replace( '\'', '"', $command ); // Windows wants double quotes
  299. $command = ABSPATH . $command;
  300. }
  301. pb_backupbuddy::status( 'details', __('Exec command (Windows)','it-l10n-backupbuddy' ) . ': ' . $command );
  302. list( $exec_output, $exec_exit_code ) = $this->_commandbuddy->execute( $this->_execpath . $command );
  303. } else { // Allow exec warnings not in Windows
  304. pb_backupbuddy::status( 'details', __('Exec command (Linux)','it-l10n-backupbuddy' ) . ': ' . $command );
  305. list( $exec_output, $exec_exit_code ) = $this->_commandbuddy->execute( $this->_execpath . $command );
  306. }
  307. // Cleanup exclusions file if it exists.
  308. if ( file_exists( $temporary_zip_directory . 'exclusions.txt' ) ) {
  309. @unlink( $temporary_zip_directory . 'exclusions.txt' );
  310. }
  311. sleep( 1 );
  312. // Verify zip command was created and exec reports no errors. If fails then falls back to other methods.
  313. if ( ( ! file_exists( $zip_file ) ) || ( $exec_exit_code == '-1' ) ) { // File not made or error returned.
  314. if ( ! file_exists( $zip_file ) ) {
  315. pb_backupbuddy::status( 'details', __( 'Exec command ran but ZIP file did not exist.','it-l10n-backupbuddy' ) );
  316. }
  317. pb_backupbuddy::status( 'message', __( 'Full speed mode did not complete. Trying compatibility mode next.','it-l10n-backupbuddy' ) );
  318. if ( file_exists( $zip_file ) ) { // If file was somehow created, its likely damaged since an error was thrown. Delete it.
  319. pb_backupbuddy::status( 'details', __( 'Cleaning up damaged ZIP file. Issue #3489328998.','it-l10n-backupbuddy' ) );
  320. unlink( $zip_file );
  321. }
  322. // If exec completed but left behind a temporary file/directory (often happens if a third party process killed off exec) then clean it up.
  323. if ( file_exists( $temporary_zip_directory ) ) {
  324. pb_backupbuddy::status( 'details', __( 'Cleaning up incomplete temporary ZIP file. Issue #343894.','it-l10n-backupbuddy' ) );
  325. $this->delete_directory_recursive( $temporary_zip_directory );
  326. }
  327. } else {
  328. pb_backupbuddy::status( 'message', __( 'Full speed mode completed & generated ZIP file.','it-l10n-backupbuddy' ) );
  329. return true;
  330. }
  331. chdir( $working_dir );
  332. unset( $command );
  333. unset( $exclude );
  334. unset( $excluding_additional );
  335. pb_backupbuddy::status( 'details', __('Exec command did not succeed. Falling back.','it-l10n-backupbuddy' ) );
  336. }
  337. if ( in_array( 'pclzip', $zip_methods ) ) {
  338. pb_backupbuddy::status( 'message', __('Using Compatibility Mode for ZIP. This is slower and less reliable.','it-l10n-backupbuddy' ) );
  339. pb_backupbuddy::status( 'message', __('If your backup times out in compatibility mode try disabled zip compression.','it-l10n-backupbuddy' ) );
  340. pb_backupbuddy::status( 'message', __('WARNING: Directory/file exclusion unavailable in Compatibility Mode. Even existing old backups will be backed up.','it-l10n-backupbuddy' ) );
  341. require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' );
  342. // Determine zip file name / path.
  343. if ( !empty( $temporary_zip_directory ) ) {
  344. $pclzip_file = $temporary_zip_directory . basename( $zip_file );
  345. } else {
  346. $pclzip_file = $zip_file;
  347. }
  348. if ( !file_exists( dirname( $pclzip_file ) ) ) {
  349. pb_backupbuddy::status( 'details', 'Creating PCLZip file directory `' . dirname( $pclzip_file ) . '`.' );
  350. mkdir( dirname( $pclzip_file ) );
  351. }
  352. // Instantiate PclZip Object.
  353. pb_backupbuddy::status( 'details', 'PclZip zip filename: `' . $pclzip_file . '`.' );
  354. $pclzip = new PclZip( $pclzip_file );
  355. if ( $compression !== true ) {
  356. pb_backupbuddy::status( 'details', __('PCLZip compression disabled based on settings.','it-l10n-backupbuddy' ) );
  357. $arguments = array( $add_directory, PCLZIP_OPT_NO_COMPRESSION, PCLZIP_OPT_REMOVE_PATH, $add_directory );
  358. } else {
  359. pb_backupbuddy::status( 'details', __('PCLZip compression enabled based on settings.','it-l10n-backupbuddy' ) );
  360. $arguments = array( $add_directory, PCLZIP_OPT_REMOVE_PATH, $add_directory );
  361. }
  362. $mode = 'create';
  363. if ( file_exists( $zip_file ) && ( $append === true ) ) {
  364. pb_backupbuddy::status( 'details', __('ZIP file exists. Appending based on options.','it-l10n-backupbuddy' ) );
  365. $mode = 'append';
  366. }
  367. if ( $mode == 'append' ) {
  368. pb_backupbuddy::status( 'details', __('Appending to ZIP file via PCLZip.','it-l10n-backupbuddy' ) );
  369. $result = call_user_func_array( array( &$pclzip, 'add' ), $arguments );
  370. } else { // create
  371. pb_backupbuddy::status( 'details', __( 'Creating ZIP file via PCLZip','it-l10n-backupbuddy' ) . ':' . implode( ';', $arguments ) );
  372. //error_log( 'pclzip args: ' . print_r( $arguments, true ) . "\n" );
  373. $result = call_user_func_array( array( &$pclzip, 'create' ), $arguments );
  374. }
  375. if ( !empty( $temporary_zip_directory ) ) {
  376. if ( file_exists( $temporary_zip_directory . basename( $zip_file ) ) ) {
  377. pb_backupbuddy::status( 'details', __('Renaming PCLZip File...','it-l10n-backupbuddy' ) );
  378. rename( $temporary_zip_directory . basename( $zip_file ), $zip_file );
  379. if ( file_exists( $zip_file ) ) {
  380. pb_backupbuddy::status( 'details', __('Renaming PCLZip success.','it-l10n-backupbuddy' ) );
  381. } else {
  382. pb_backupbuddy::status( 'details', __('Renaming PCLZip failure.','it-l10n-backupbuddy' ) );
  383. }
  384. } else {
  385. pb_backupbuddy::status( 'details', __('Temporary PCLZip archive file expected but not found. Please verify permissions on the ZIP archive directory.','it-l10n-backupbuddy' ) );
  386. }
  387. }
  388. pb_backupbuddy::status( 'details', __( 'PCLZip error message (if any):' ) . ' ' . $pclzip->errorInfo( true ) );
  389. if ( false !== strpos( $pclzip->errorInfo( true ), 'PCLZIP_ERR_READ_OPEN_FAIL' ) ) {
  390. pb_backupbuddy::status( 'details', 'PCLZIP_ERR_READ_OPEN_FAIL details: This error indicates that fopen failed (returned false) when trying to open the file in the mode specified. This is almost always due to permissions.' );
  391. }
  392. // If not a result of 0 and the file exists then it looks like the backup was a success.
  393. if ( ( $result != 0 ) && file_exists( $zip_file ) ) {
  394. pb_backupbuddy::status( 'details', __('Backup file created in compatibility mode (PCLZip).','it-l10n-backupbuddy' ) );
  395. return true;
  396. } else {
  397. if ( $result == 0 ) {
  398. pb_backupbuddy::status( 'details', __('PCLZip returned status 0.','it-l10n-backupbuddy' ) );
  399. }
  400. if ( !file_exists( $zip_file ) ) {
  401. pb_backupbuddy::status( 'details', __('PCLZip archive ZIP file was not found.','it-l10n-backupbuddy' ) );
  402. }
  403. }
  404. unset( $result );
  405. unset( $mode );
  406. unset( $arguments );
  407. unset( $pclzip );
  408. }
  409. // If we made it this far then something didnt result in a success.
  410. return false;
  411. }
  412. /**
  413. * unzip()
  414. *
  415. * Extracts the contents of a zip file to the specified directory using the best unzip methods possible.
  416. *
  417. * $zip_file string Full path & filename of ZIP file to create.
  418. * $destination_directory string Full directory path to extract into.
  419. * $force_compatibility_mode mixed false (default): use best methods available (zip exec first), falling back as needed.
  420. * ziparchive: first fallback method. (Medium performance)
  421. * pclzip: second fallback method. (Worst performance; buggy)
  422. *
  423. * @return`` true on success, false otherwise
  424. */
  425. function unzip( $zip_file, $destination_directory, $force_compatibility_mode = false ) {
  426. $destination_directory = rtrim( $destination_directory, '\\/' ) . '/'; // Make sure trailing slash exists to normalize.
  427. if ( $force_compatibility_mode == 'ziparchive' ) {
  428. $zip_methods = array( 'ziparchive' );
  429. pb_backupbuddy::status( 'message', __('Forced compatibility mode (ZipArchive; medium speed) based on settings. This is slower and less reliable.','it-l10n-backupbuddy' ) );
  430. } elseif ( $force_compatibility_mode == 'pclzip' ) {
  431. $zip_methods = array( 'pclzip' );
  432. pb_backupbuddy::status( 'message', __('Forced compatibility mode (PCLZip; slow speed) based on settings. This is slower and less reliable.','it-l10n-backupbuddy' ) );
  433. } else {
  434. $zip_methods = $this->_zip_methods;
  435. pb_backupbuddy::status( 'details', __('Using all available zip methods in preferred order.','it-l10n-backupbuddy' ) );
  436. }
  437. if ( in_array( 'exec', $zip_methods ) ) {
  438. pb_backupbuddy::status( 'details', 'Starting highspeed extraction (exec)... This may take a moment...' );
  439. $command = 'unzip -qo'; // q = quiet, o = overwrite without prompt.
  440. $command .= " '$zip_file' -d '$destination_directory' -x 'importbuddy.php'"; // x excludes importbuddy script to prevent overwriting newer importbuddy on extract step.
  441. // Handle windows.
  442. if ( stristr( PHP_OS, 'WIN' ) && !stristr( PHP_OS, 'DARWIN' ) ) { // Running Windows. (not darwin)
  443. if ( file_exists( ABSPATH . 'unzip.exe' ) ) {
  444. pb_backupbuddy::status( 'details', 'Attempting to use Windows unzip.exe.' );
  445. $command = str_replace( '\'', '"', $command ); // Windows wants double quotes
  446. $command = ABSPATH . $command;
  447. }
  448. }
  449. $command .= ' 2>&1'; // Redirect STDERR to STDOUT.
  450. if ( $this->_execpath != '' ) {
  451. pb_backupbuddy::status( 'details', __( 'Using custom exec() path: ', 'it-l10n-backupbuddy' ) . $this->_execpath );
  452. }
  453. pb_backupbuddy::status( 'details', 'Running ZIP command. This may take a moment.' );
  454. list( $exec_output, $exec_exit_code ) = $this->_commandbuddy->execute( $this->_execpath . $command );
  455. $failed = false; // Default.
  456. if ( !file_exists( $destination_directory . 'wp-login.php' ) && !file_exists( $destination_directory . 'db_1.sql' ) && !file_exists( $destination_directory . 'wordpress/wp-login.php' ) ) { // wp-login.php for WordPress, db_1.sql for DB backup, wordpress/wp-login.php for fresh WordPress downloaded from wp.org for MS export
  457. pb_backupbuddy::status( 'error', 'Both wp-login.php (full backups) and db_1.sql (database only backups) are missing after extraction. Unzip process appears to have failed.' );
  458. $failed = true;
  459. }
  460. if ( $exec_exit_code != '0' ) {
  461. pb_backupbuddy::status( 'error', 'Exit code `' . $exec_exit_code . '` indicates a problem was encountered.' );
  462. $failed = true;
  463. }
  464. // Sometimes exec returns success codes but never extracted actual files. Do a check to make sure known files were extracted to verify against that.
  465. if ( $failed === false ) {
  466. pb_backupbuddy::status( 'message', 'File extraction complete.' );
  467. return true;
  468. } else {
  469. pb_backupbuddy::status( 'message', 'Falling back to next compatibility mode.' );
  470. }
  471. }
  472. if ( in_array( 'ziparchive', $zip_methods ) ) {
  473. pb_backupbuddy::status( 'details', 'Starting medium speed extraction (ziparchive)... This may take a moment...' );
  474. $zip = new ZipArchive;
  475. if ( $zip->open( $zip_file ) === true ) {
  476. if ( true === $zip->extractTo( $destination_directory ) ) {
  477. pb_backupbuddy::status( 'details', 'ZipArchive extraction success.' );
  478. $zip->close();
  479. return true;
  480. } else {
  481. $zip->close();
  482. pb_backupbuddy::status( 'message', 'Error: ZipArchive was available but failed extracting files. Falling back to next compatibility mode.' );
  483. }
  484. } else {
  485. pb_backupbuddy::status( 'message', 'Error: Unable to open zip file via ZipArchive. Falling back to next compatibility mode.' );
  486. }
  487. }
  488. if ( in_array( 'pclzip', $zip_methods ) ) {
  489. pb_backupbuddy::status( 'details', 'Starting low speed extraction (pclzip)... This may take a moment...' );
  490. if ( !class_exists( 'PclZip' ) ) {
  491. $pclzip_file = pb_backupbuddy::plugin_path() . '/lib/pclzip/pclzip.php';
  492. pb_backupbuddy::status( 'details', 'PCLZip class not found. Attempting to load from `' . $pclzip_file . '`.' );
  493. if ( file_exists( $pclzip_file ) ) {
  494. pb_backupbuddy::status( 'details', 'Loading `' . $pclzip_file . '`.' );
  495. require_once( $pclzip_file );
  496. } else {
  497. pb_backupbuddy::status( 'details', 'PCLZip file not found: `' . $pclzip_file . '`.' );
  498. }
  499. }
  500. $archive = new PclZip( $zip_file );
  501. $result = $archive->extract(); // Extract to current directory. Explicity using PCLZIP_OPT_PATH results in extraction to a PCLZIP_OPT_PATH subfolder.
  502. if ( 0 == $result ) {
  503. pb_backupbuddy::status( 'details', 'PCLZip Failure: ' . $archive->errorInfo( true ) );
  504. pb_backupbuddy::status( 'message', 'Low speed (PCLZip) extraction failed.', $archive->errorInfo( true ) );
  505. } else {
  506. return true;
  507. }
  508. }
  509. // Nothing succeeded if we made it this far...
  510. return false;
  511. }
  512. // Test availability of ZipArchive and that it actually works.
  513. function test_ziparchive() {
  514. if ( class_exists( 'ZipArchive' ) ) {
  515. $test_file = $this->_tempdir . 'temp_test_' . uniqid() . '.zip';
  516. $zip = new ZipArchive;
  517. if ( $zip->open( $test_file, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE ) === true ) {
  518. $zip->addFile( __FILE__, 'this_is_a_test.txt');
  519. $zip->close();
  520. if ( file_exists( $test_file ) ) {
  521. unlink( $test_file );
  522. pb_backupbuddy::status( 'details', __('ZipArchive test passed.','it-l10n-backupbuddy' ) );
  523. return true;
  524. } else {
  525. pb_backupbuddy::status( 'details', __('ZipArchive test failed: Zip file not found.','it-l10n-backupbuddy' ) );
  526. return false;
  527. }
  528. } else {
  529. pb_backupbuddy::status( 'details', __('ZipArchive test FAILED: Unable to create/open zip file.','it-l10n-backupbuddy' ) );
  530. return false;
  531. }
  532. }
  533. }
  534. /* get_file_list()
  535. *
  536. * Get an array of all files in a zip file.
  537. *
  538. * @param
  539. * @return array
  540. */
  541. public function get_file_list( $file ) {
  542. if ( !in_array( 'ziparchive', $this->_zip_methods ) ) { // Currently only available if ziparchive is available.
  543. return false;
  544. }
  545. $za = new ZipArchive();
  546. $za->open( $file );
  547. $result = array();
  548. for( $i = 0; $i < $za->numFiles; $i++ ){
  549. $stat = $za->statIndex( $i );
  550. $result[] = array(
  551. $stat['name'],
  552. $stat['size'],
  553. $stat['comp_size'],
  554. $stat['mtime'],
  555. );
  556. } // end for.
  557. return $result;
  558. } // End get_file_list().
  559. /* available_zip_methods()
  560. *
  561. * Test availability of zip methods to determine which exist and actually work.
  562. * Detects the available zipping methods on this server. Tests command line zip via exec(), PHP's ZipArchive, or emulated zip via the PHP PCLZip library.
  563. * TODO: Actually test unzipping in unzip mode not just zipping and assuming the other will work
  564. *
  565. * @param boolean $return_best
  566. * @param string $mode Possible values: zip, unzip
  567. * @return array Possible return values: exec, ziparchive, pclzip
  568. */
  569. function available_zip_methods( $return_best = true, $mode = 'zip' ) {
  570. $return = array();
  571. $test_file = $this->_tempdir . 'temp_' . uniqid() . '.zip';
  572. // Test command-line ZIP.
  573. if ( function_exists( 'exec' ) ) {
  574. $command = 'zip';
  575. $run_exec_zip_test = true;
  576. // Handle windows.
  577. if ( stristr( PHP_OS, 'WIN' ) && !stristr( PHP_OS, 'DARWIN' ) ) { // Running Windows. (not darwin)
  578. if ( file_exists( ABSPATH . 'zip.exe' ) ) {
  579. $command = ABSPATH . $command;
  580. }
  581. // If unzip mode and unzip.exe is found then assume we have that option for unzipping since we arent actually testing unzip.
  582. if ( $mode == 'unzip' ) {
  583. $run_exec_zip_test = false;
  584. if ( file_exists( ABSPATH . 'unzip.exe' ) ) {
  585. array_push( $return, 'exec' );
  586. }
  587. }
  588. $exec_paths = array( '' );
  589. } else { // *NIX system.
  590. $exec_paths = array( '', '/usr/bin/', '/usr/local/bin/', '/usr/local/sbin/', '/usr/sbin/', '/sbin/', '/bin/' ); // Include preceeding & trailing slash.
  591. }
  592. if ( $run_exec_zip_test === true ) {
  593. // Possible locations to find the ZIP executable. Start with a blank string to attempt to run in current directory.
  594. pb_backupbuddy::status( 'details', 'Trying exec() in the following paths: `' . implode( ',', $exec_paths ) . '`' );
  595. $exec_completion = false; // default state.
  596. while( $exec_completion === false ) { // Check all possible zip path locations starting with current dir. Usually the path is set to make this work without hunting.
  597. if ( empty( $exec_paths ) ) {
  598. $exec_completion = true;
  599. pb_backupbuddy::status( 'error', __( 'Exhausted all known exec() path possibilities with no success.', 'it-l10n-backupbuddy' ) );
  600. break;
  601. }
  602. $path = array_shift( $exec_paths );
  603. pb_backupbuddy::status( 'details', __( 'Trying exec() ZIP path:', 'it-l10n-backupbuddy' ) . ' `' . $path . '`.' );
  604. $exec_command = $path . $command . ' "' . $test_file . '" "' . __FILE__ . '" 2>&1'; // 2>&1 to redirect STRERR to STDOUT.
  605. pb_backupbuddy::status( 'details', 'Zip test exec() command: `' . $exec_command . '`' );
  606. list( $exec_output, $exec_exit_code ) = $this->_commandbuddy->execute( $exec_command );
  607. if ( ( !file_exists( $test_file ) ) || ( $exec_exit_code == '-1' ) ) { // File not made or error returned.
  608. $exec_completion = false;
  609. if ( $exec_exit_code == '-1' ) {
  610. pb_backupbuddy::status( 'details', __( 'Exec command returned -1.', 'it-l10n-backupbuddy' ) );
  611. }
  612. if ( !file_exists( $test_file ) ) {
  613. pb_backupbuddy::status( 'details', __( 'Exec command ran but ZIP file did not exist.', 'it-l10n-backupbuddy' ) );
  614. }
  615. if ( file_exists( $test_file ) ) { // If file was somehow created, do cleanup on it.
  616. pb_backupbuddy::status( 'details', __( 'Cleaning up damaged ZIP file. Issue #3489328998.', 'it-l10n-backupbuddy' ) );
  617. unlink( $test_file );
  618. }
  619. } else { // Success.
  620. $exec_completion = true;
  621. if ( !unlink( $test_file ) ) {
  622. echo sprintf( __( 'Error #564634. Unable to delete test file (%s)!', 'it-l10n-backupbuddy' ), $test_file );
  623. }
  624. array_push( $return, 'exec' );
  625. $this->_execpath = $path;
  626. break;
  627. }
  628. } // end while
  629. } // End $run_exec_test === true.
  630. } // End function_exists( 'exec' ).
  631. // Test ZipArchive
  632. if ( class_exists( 'ZipArchive' ) ) {
  633. if ( $this->test_ziparchive() === true ) {
  634. array_push( $return, 'ziparchive' );
  635. }
  636. }
  637. // Test PCLZip
  638. if ( class_exists( 'PclZip' ) ) { // Class already loaded.
  639. array_push( $return, 'pclzip' );
  640. } else { // Class not loaded. Seek it out.
  641. if ( file_exists( ABSPATH . 'wp-admin/includes/class-pclzip.php' ) ) { // Inside WP.
  642. require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' );
  643. array_push( $return, 'pclzip' );
  644. } elseif ( file_exists( pb_backupbuddy::plugin_path() . '/lib/pclzip/pclzip.php' ) ) { // ImportBuddy.
  645. require_once( pb_backupbuddy::plugin_path() . '/lib/pclzip/pclzip.php' );
  646. array_push( $return, 'pclzip' );
  647. }
  648. }
  649. return $return;
  650. } // End available_zip_methods().
  651. // Recursively delete a directory and all content within.
  652. function delete_directory_recursive( $directory ) {
  653. $directory = preg_replace( '|[/\\\\]+$|', '', $directory );
  654. $files = glob( $directory . '/*', GLOB_MARK );
  655. if ( is_array( $files ) && !empty( $files ) ) {
  656. foreach( $files as $file ) {
  657. if( '/' === substr( $file, -1 ) )
  658. $this->rmdir_recursive( $file );
  659. else
  660. unlink( $file );
  661. }
  662. }
  663. if ( is_dir( $directory ) ) rmdir( $directory );
  664. if ( is_dir( $directory ) )
  665. return false;
  666. return true;
  667. } // End delete_directory_recursive().
  668. function set_zip_methods( $methods ) {
  669. $this->_zip_methods = $methods;
  670. } // End set_zip_methods().
  671. /* _render_exclusions_file()
  672. *
  673. * function description
  674. *
  675. * @param string $file File to write exclusions into.
  676. * @param array $exclusions Array of directories/paths to exclude. One per line.
  677. * @return null
  678. */
  679. public function _render_exclusions_file( $file, $exclusions ) {
  680. pb_backupbuddy::status( 'details', 'Creating backup exclusions file `' . $file . '`.' );
  681. //$exclusions = pb_backupbuddy::$classes['core']->get_directory_exclusions();
  682. // Format.
  683. foreach( $exclusions as &$exclusion ) {
  684. // DIRECTORY.
  685. if ( is_dir( ABSPATH . ltrim( $exclusion, '/' ) ) ) {
  686. $exclusion = rtrim( $exclusion, '/\\' ) . '/*';
  687. pb_backupbuddy::status( 'details', 'Excluding directory `' . $exclusion . '`.' );
  688. // FILE.
  689. } elseif ( is_file( ABSPATH . ltrim( $exclusion, '/' ) ) ) {
  690. pb_backupbuddy::status( 'details', 'Excluding file `' . $exclusion . '`.' );
  691. // SYMBOLIC LINK.
  692. } elseif ( is_link( ABSPATH . ltrim( $exclusion, '/' ) ) ) {
  693. pb_backupbuddy::status( 'details', 'Excluding symbolic link `' . $exclusion . '`.' );
  694. // DOES NOT EXIST.
  695. } else { // File does not exist. Skip using it in exclusion list.
  696. pb_backupbuddy::status( 'details', 'Omitting exclusion as file/direcory does not currently exist: `' . $exclusion . '`.' );
  697. unset( $exclusion ); // Remove.
  698. }
  699. }
  700. $exclusions = implode( "\n", $exclusions );
  701. file_put_contents( $file, $exclusions );
  702. pb_backupbuddy::status( 'details', 'Backup exclusions file created.' );
  703. } // End render_exclusions_file().
  704. } // End class
  705. //$pluginbuddy_zipbuddy = new pluginbuddy_zipbuddy( pb_backupbuddy::$options['backup_directory'] );
  706. }
  707. ?>