2016-11-28 21:52:15 -08:00
< ? php
2018-01-26 15:50:15 +01:00
if ( ! defined ( 'ABSPATH' )) die ( 'No direct access allowed' );
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
if ( class_exists ( 'ZipArchive' )) :
/**
* We just add a last_error variable for comaptibility with our UpdraftPlus_PclZip object
*/
2016-11-28 21:52:15 -08:00
class UpdraftPlus_ZipArchive extends ZipArchive {
2018-01-26 15:50:15 +01:00
public $last_error = 'Unknown: ZipArchive does not return error messages' ;
2016-11-28 21:52:15 -08:00
}
endif ;
2018-01-26 15:50:15 +01:00
/**
* A ZipArchive compatibility layer , with behaviour sufficient for our usage of ZipArchive
*/
2016-11-28 21:52:15 -08:00
class UpdraftPlus_PclZip {
protected $pclzip ;
2018-01-26 15:50:15 +01:00
2016-11-28 21:52:15 -08:00
protected $path ;
2018-01-26 15:50:15 +01:00
2016-11-28 21:52:15 -08:00
protected $addfiles ;
2018-01-26 15:50:15 +01:00
2016-11-28 21:52:15 -08:00
protected $adddirs ;
2018-01-26 15:50:15 +01:00
2016-11-28 21:52:15 -08:00
private $statindex ;
2018-01-26 15:50:15 +01:00
2016-11-28 21:52:15 -08:00
private $include_mtime = false ;
2018-01-26 15:50:15 +01:00
2016-11-28 21:52:15 -08:00
public $last_error ;
public function __construct () {
$this -> addfiles = array ();
$this -> adddirs = array ();
// Put this in a non-backed-up, writeable location, to make sure that huge temporary files aren't created and then added to the backup - and that we have somewhere writable
global $updraftplus ;
if ( ! defined ( 'PCLZIP_TEMPORARY_DIR' )) define ( 'PCLZIP_TEMPORARY_DIR' , trailingslashit ( $updraftplus -> backups_dir_location ()));
}
2018-01-26 15:50:15 +01:00
/**
* Used to include mtime in statindex ( by default , not done - to save memory ; probably a bit paranoid )
*
* @ return null
*/
2016-11-28 21:52:15 -08:00
public function ud_include_mtime () {
$this -> include_mtime = true ;
}
public function __get ( $name ) {
2018-01-26 15:50:15 +01:00
if ( 'numFiles' == $name || 'numAll' == $name ) {
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
if ( empty ( $this -> pclzip )) return false ;
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$statindex = $this -> pclzip -> listContent ();
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
if ( empty ( $statindex )) {
$this -> statindex = array ();
// We return a value that is == 0, but allowing a PclZip error to be detected (PclZip returns 0 in the case of an error).
if ( 0 === $statindex ) $this -> last_error = $this -> pclzip -> errorInfo ( true );
return ( 0 === $statindex ) ? false : 0 ;
}
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
if ( 'numFiles' == $name ) {
$result = array ();
foreach ( $statindex as $i => $file ) {
if ( ! isset ( $statindex [ $i ][ 'folder' ]) || 0 == $statindex [ $i ][ 'folder' ]) {
$result [] = $file ;
}
unset ( $statindex [ $i ]);
}
$this -> statindex = $result ;
} else {
$this -> statindex = $statindex ;
2016-11-28 21:52:15 -08:00
}
2018-01-26 15:50:15 +01:00
return count ( $this -> statindex );
}
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
return null ;
2016-11-28 21:52:15 -08:00
}
public function statIndex ( $i ) {
if ( empty ( $this -> statindex [ $i ])) return array ( 'name' => null , 'size' => 0 );
$v = array ( 'name' => $this -> statindex [ $i ][ 'filename' ], 'size' => $this -> statindex [ $i ][ 'size' ]);
if ( $this -> include_mtime ) $v [ 'mtime' ] = $this -> statindex [ $i ][ 'mtime' ];
return $v ;
}
public function open ( $path , $flags = 0 ) {
2018-01-26 15:50:15 +01:00
if ( ! class_exists ( 'PclZip' )) include_once ( ABSPATH . '/wp-admin/includes/class-pclzip.php' );
if ( ! class_exists ( 'PclZip' )) {
2016-11-28 21:52:15 -08:00
$this -> last_error = " No PclZip class was found " ;
return false ;
}
2018-01-26 15:50:15 +01:00
// Route around PHP bug (exact version with the problem not known)
2016-11-28 21:52:15 -08:00
$ziparchive_create_match = ( version_compare ( PHP_VERSION , '5.2.12' , '>' ) && defined ( 'ZIPARCHIVE::CREATE' )) ? ZIPARCHIVE :: CREATE : 1 ;
if ( $flags == $ziparchive_create_match && file_exists ( $path )) @ unlink ( $path );
$this -> pclzip = new PclZip ( $path );
2018-01-26 15:50:15 +01:00
2016-11-28 21:52:15 -08:00
if ( empty ( $this -> pclzip )) {
$this -> last_error = 'Could not get a PclZip object' ;
return false ;
}
2018-01-26 15:50:15 +01:00
// Make the empty directory we need to implement add_empty_dir()
2016-11-28 21:52:15 -08:00
global $updraftplus ;
$updraft_dir = $updraftplus -> backups_dir_location ();
if ( ! is_dir ( $updraft_dir . '/emptydir' ) && ! mkdir ( $updraft_dir . '/emptydir' )) {
$this -> last_error = " Could not create empty directory ( $updraft_dir /emptydir) " ;
return false ;
}
$this -> path = $path ;
return true ;
}
2018-01-26 15:50:15 +01:00
/**
* Do the actual write - out - it is assumed that close () is where this is done . Needs to return true / false
*
* @ return boolean
*/
2016-11-28 21:52:15 -08:00
public function close () {
if ( empty ( $this -> pclzip )) {
$this -> last_error = 'Zip file was not opened' ;
return false ;
}
global $updraftplus ;
$updraft_dir = $updraftplus -> backups_dir_location ();
$activity = false ;
2018-01-26 15:50:15 +01:00
// Add the empty directories
2016-11-28 21:52:15 -08:00
foreach ( $this -> adddirs as $dir ) {
if ( false == $this -> pclzip -> add ( $updraft_dir . '/emptydir' , PCLZIP_OPT_REMOVE_PATH , $updraft_dir . '/emptydir' , PCLZIP_OPT_ADD_PATH , $dir )) {
$this -> last_error = $this -> pclzip -> errorInfo ( true );
return false ;
}
$activity = true ;
}
foreach ( $this -> addfiles as $rdirname => $adirnames ) {
foreach ( $adirnames as $adirname => $files ) {
if ( false == $this -> pclzip -> add ( $files , PCLZIP_OPT_REMOVE_PATH , $rdirname , PCLZIP_OPT_ADD_PATH , $adirname )) {
$this -> last_error = $this -> pclzip -> errorInfo ( true );
return false ;
}
$activity = true ;
}
unset ( $this -> addfiles [ $rdirname ]);
}
$this -> pclzip = false ;
$this -> addfiles = array ();
$this -> adddirs = array ();
clearstatcache ();
if ( $activity && filesize ( $this -> path ) < 50 ) {
$this -> last_error = " Write failed - unknown cause (check your file permissions) " ;
return false ;
}
return true ;
}
2018-01-26 15:50:15 +01:00
/**
* Note : basename ( $add_as ) is irrelevant ; that is , it is actually basename ( $file ) that will be used . But these are always identical in our usage .
*
* @ param string $file Specific file to add
* @ param string $add_as This is the name of the file that it is added as but it is usually the same as $file
*/
2016-11-28 21:52:15 -08:00
public function addFile ( $file , $add_as ) {
2018-01-26 15:50:15 +01:00
// Add the files. PclZip appears to do the whole (copy zip to temporary file, add file, move file) cycle for each file - so batch them as much as possible. We have to batch by dirname(). On a test with 1000 files of 25KB each in the same directory, this reduced the time needed on that directory from 120s to 15s (or 5s with primed caches).
2016-11-28 21:52:15 -08:00
$rdirname = dirname ( $file );
$adirname = dirname ( $add_as );
$this -> addfiles [ $rdirname ][ $adirname ][] = $file ;
}
2018-01-26 15:50:15 +01:00
/**
* PclZip doesn ' t have a direct way to do this
*
* @ param string $dir Specific Directory to empty
*/
2016-11-28 21:52:15 -08:00
public function addEmptyDir ( $dir ) {
$this -> adddirs [] = $dir ;
}
2018-01-26 15:50:15 +01:00
public function extract ( $path_to_extract , $path ) {
return $this -> pclzip -> extract ( PCLZIP_OPT_PATH , $path_to_extract , PCLZIP_OPT_BY_NAME , $path );
}
2016-11-28 21:52:15 -08:00
}
class UpdraftPlus_BinZip extends UpdraftPlus_PclZip {
private $binzip ;
public function __construct () {
global $updraftplus_backup ;
$this -> binzip = $updraftplus_backup -> binzip ;
if ( ! is_string ( $this -> binzip )) {
$this -> last_error = " No binary zip was found " ;
return false ;
}
return parent :: __construct ();
}
public function addFile ( $file , $add_as ) {
global $updraftplus ;
2018-01-26 15:50:15 +01:00
// Get the directory that $add_as is relative to
2016-11-28 21:52:15 -08:00
$base = $updraftplus -> str_lreplace ( $add_as , '' , $file );
if ( $file == $base ) {
// Shouldn't happen; but see: https://bugs.php.net/bug.php?id=62119
$updraftplus -> log ( " File skipped due to unexpected name mismatch (locale: " . setlocale ( LC_CTYPE , " 0 " ) . " ): file= $file add_as= $add_as " , 'notice' , false , true );
} else {
$rdirname = untrailingslashit ( $base );
2018-01-26 15:50:15 +01:00
// Note: $file equals $rdirname/$add_as
2016-11-28 21:52:15 -08:00
$this -> addfiles [ $rdirname ][] = $add_as ;
}
}
2018-01-26 15:50:15 +01:00
/**
* The standard zip binary cannot list ; so we use PclZip for that
* Do the actual write - out - it is assumed that close () is where this is done . Needs to return true / false
*
* @ return boolean - success or failure state
*/
2016-11-28 21:52:15 -08:00
public function close () {
if ( empty ( $this -> pclzip )) {
$this -> last_error = 'Zip file was not opened' ;
return false ;
}
global $updraftplus , $updraftplus_backup ;
$updraft_dir = $updraftplus -> backups_dir_location ();
$activity = false ;
2018-01-26 15:50:15 +01:00
// BinZip does not like zero-sized zip files
2016-11-28 21:52:15 -08:00
if ( file_exists ( $this -> path ) && 0 == filesize ( $this -> path )) @ unlink ( $this -> path );
$descriptorspec = array (
0 => array ( 'pipe' , 'r' ),
1 => array ( 'pipe' , 'w' ),
2 => array ( 'pipe' , 'w' )
);
$exec = $this -> binzip ;
if ( defined ( 'UPDRAFTPLUS_BINZIP_OPTS' ) && UPDRAFTPLUS_BINZIP_OPTS ) $exec .= ' ' . UPDRAFTPLUS_BINZIP_OPTS ;
$exec .= " -v -@ " . escapeshellarg ( $this -> path );
$last_recorded_alive = time ();
$something_useful_happened = $updraftplus -> something_useful_happened ;
$orig_size = file_exists ( $this -> path ) ? filesize ( $this -> path ) : 0 ;
$last_size = $orig_size ;
clearstatcache ();
$added_dirs_yet = false ;
2018-01-26 15:50:15 +01:00
// If there are no files to add, but there are empty directories, then we need to make sure the directories actually get added
2016-11-28 21:52:15 -08:00
if ( 0 == count ( $this -> addfiles ) && 0 < count ( $this -> adddirs )) {
$dir = realpath ( $updraftplus_backup -> make_zipfile_source );
$this -> addfiles [ $dir ] = '././.' ;
}
// Loop over each destination directory name
foreach ( $this -> addfiles as $rdirname => $files ) {
$process = proc_open ( $exec , $descriptorspec , $pipes , $rdirname );
if ( ! is_resource ( $process )) {
$updraftplus -> log ( 'BinZip error: proc_open failed' );
$this -> last_error = 'BinZip error: proc_open failed' ;
return false ;
}
if ( ! $added_dirs_yet ) {
2018-01-26 15:50:15 +01:00
// Add the directories - (in fact, with binzip, non-empty directories automatically have their entries added; but it doesn't hurt to add them explicitly)
2016-11-28 21:52:15 -08:00
foreach ( $this -> adddirs as $dir ) {
fwrite ( $pipes [ 0 ], $dir . " / \n " );
}
2018-01-26 15:50:15 +01:00
$added_dirs_yet = true ;
2016-11-28 21:52:15 -08:00
}
$read = array ( $pipes [ 1 ], $pipes [ 2 ]);
$except = null ;
if ( ! is_array ( $files ) || 0 == count ( $files )) {
fclose ( $pipes [ 0 ]);
$write = array ();
} else {
$write = array ( $pipes [ 0 ]);
}
while (( ! feof ( $pipes [ 1 ]) || ! feof ( $pipes [ 2 ]) || ( is_array ( $files ) && count ( $files ) > 0 )) && false !== ( $changes = @ stream_select ( $read , $write , $except , 0 , 200000 ))) {
if ( is_array ( $write ) && in_array ( $pipes [ 0 ], $write ) && is_array ( $files ) && count ( $files ) > 0 ) {
$file = array_pop ( $files );
// Send the list of files on stdin
fwrite ( $pipes [ 0 ], $file . " \n " );
if ( 0 == count ( $files )) fclose ( $pipes [ 0 ]);
}
if ( is_array ( $read ) && in_array ( $pipes [ 1 ], $read )) {
$w = fgets ( $pipes [ 1 ]);
// Logging all this really slows things down; use debug to mitigate
if ( $w && $updraftplus_backup -> debug ) $updraftplus -> log ( " Output from zip: " . trim ( $w ), 'debug' );
if ( time () > $last_recorded_alive + 5 ) {
$updraftplus -> record_still_alive ();
$last_recorded_alive = time ();
}
if ( file_exists ( $this -> path )) {
$new_size = @ filesize ( $this -> path );
if ( ! $something_useful_happened && $new_size > $orig_size + 20 ) {
$updraftplus -> something_useful_happened ();
$something_useful_happened = true ;
}
clearstatcache ();
2018-01-26 15:50:15 +01:00
// Log when 20% bigger or at least every 50MB
2016-11-28 21:52:15 -08:00
if ( $new_size > $last_size * 1.2 || $new_size > $last_size + 52428800 ) {
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( basename ( $this -> path ) . sprintf ( " : size is now: %.2f MB " , round ( $new_size / 1048576 , 1 )));
2016-11-28 21:52:15 -08:00
$last_size = $new_size ;
}
}
}
if ( is_array ( $read ) && in_array ( $pipes [ 2 ], $read )) {
$last_error = fgets ( $pipes [ 2 ]);
if ( ! empty ( $last_error )) $this -> last_error = rtrim ( $last_error );
}
// Re-set
$read = array ( $pipes [ 1 ], $pipes [ 2 ]);
$write = ( is_array ( $files ) && count ( $files ) > 0 ) ? array ( $pipes [ 0 ]) : array ();
$except = null ;
}
fclose ( $pipes [ 1 ]);
fclose ( $pipes [ 2 ]);
$ret = proc_close ( $process );
2018-01-26 15:50:15 +01:00
if ( 0 != $ret && 12 != $ret ) {
2016-11-28 21:52:15 -08:00
if ( $ret < 128 ) {
$updraftplus -> log ( " Binary zip: error (code: $ret - look it up in the Diagnostics section of the zip manual at http://www.info-zip.org/mans/zip.html for interpretation... and also check that your hosting account quota is not full) " );
} else {
$updraftplus -> log ( " Binary zip: error (code: $ret - a code above 127 normally means that the zip process was deliberately killed ... and also check that your hosting account quota is not full) " );
}
if ( ! empty ( $w ) && ! $updraftplus_backup -> debug ) $updraftplus -> log ( " Last output from zip: " . trim ( $w ), 'debug' );
return false ;
}
unset ( $this -> addfiles [ $rdirname ]);
}
return true ;
}
}