<?php
/**
 *	pluginbuddy_zbzippclzip Class
 *
 *  Extends the zip capability core class with pclzip specific capability
 *	
 *	Version: 1.0.0
 *	Author:
 *	Author URI:
 *
 *	@param		$parent		object		Optional parent object which can provide functions for reporting, etc.
 *	@return		null
 *
 */
if ( !class_exists( "pluginbuddy_zbzippclzip" ) ) {

	/**
	 *	pluginbuddy_PclZip Class
	 *
	 *	Wrapper for PclZip to encapsulate the process of loading the PclZip library (if not
	 *	already loaded, which it shouldn't be generally) and also surrounding method calls
	 *	with the unpleasant workaround for the mbstring issue where things may fail because
	 *	PclZip is using string functions to process binary data and if the string functions
	 *	are overloaded with the multi-byte versions the processing can (probably will) fail.
	 *
	 *	@param	string	$zip_filename	The name of the zip file that will be managed
	 *	@return	null
	 *
	 */
	class pluginbuddy_PclZip {

        /**
         * The created PclZip object if it can be created
         * 
         * @var $_za 	object
         */
		private $_za = NULL;
		
		/**
		 *	__construct()
		 *	
		 *	Default constructor.
		 *	This is used to try and load the PclZip library and then create an instance of
		 *	an archive with that. If the library cannot be made available then an exception
		 *	is thrown and that is handled by the caller.
		 *	TODO: Consider having a "suppress warnings" parameter to determine whether methods
		 *	should be invoked with warnings suppressed or not. For is_available() usage we would
		 *	want to so as not to potentially flood the PHP error log. For other functions that
		 *	are not called frequently we might not want to suppress the warnings.
		 *	
		 *	@param		string	$zip_filename	The name of the zip file that will be managed
		 *	@return		null
		 *
		 */
		public function __construct( $zip_filename ) {
		
			// The PclZip class has to be available for us so let's have a go
			// Note: it is not required because nothing will break without it but the method will 
			// simply not be available
			// This may seem laborious but it's robust against include_once not playing nice if the
			// class is already included and trying to include it again
			if ( !@class_exists( 'PclZip', false ) ) {
			
				$possibles = array( ABSPATH . 'wp-admin/includes/class-pclzip.php', pb_backupbuddy::plugin_path() . '/lib/pclzip/pclzip.php' );
				
				foreach ( $possibles as $possible) {
				
					if ( @is_readable( $possible ) ) {
					
						// Found one that should be loadable so try it and then break out
						pb_backupbuddy::status( 'details', 'PCLZip class not found. Attempting to load from `' . $possible . '`.' );
						@include_once( $possible );
						break;
										
					} 
				
				}

			}
			
			// By now PclZip _should_ be available so let's see...
			if ( @class_exists( 'PclZip', false ) ) {
			
				// It's available so create the private instance
				$this->_za = new PclZip( $zip_filename );
				
			} else {
			
				// Not available so throw the exception for the caller to handle
				throw new Exception( 'PclZip class does not exist.' );
			
			}
			
			return;
		
		}
		
		/**
		 *	__destruct()
		 *	
		 *	Default destructor.
		 *	
		 *	@return		null
		 *
		 */
		public function __destruct() {
		
			if ( NULL != $this->_za ) { unset ( $this->_za ); }
			
			return;
		
		}
		
		/**
		 *	__call()
		 *	
		 *	Magic method intercepting calls to unknown methods. This allows us to intercept
		 *	all method calls and add additional processing
		 *	
		 *	@param		string	$method		The name of the intercepted method
		 *	@param		array	$arguments	Array of the arguments associated with the method call
		 *	@return		mixed	$result		Whatever the invoked wrapper method call returns
		 *
		 */
		public function __call( $method, $arguments ) {
		
			$result = false;
		
			// See #15789 - PclZip uses string functions on binary data
			// If it's overloaded with Multibyte safe functions the results are incorrect.
			if ( @ini_get( 'mbstring.func_overload' ) && @function_exists( 'mb_internal_encoding' ) ) {
			
				$previous_encoding = @mb_internal_encoding();
				@mb_internal_encoding( 'ISO-8859-1' );
				
			}
		
			$result = @call_user_func_array( array( $this->_za, $method ), $arguments );
			
			// Now undo any change we may have made to the encoding
			if ( isset( $previous_encoding ) ) {
			
				@mb_internal_encoding( $previous_encoding );
				unset( $previous_encoding );

			}
		
			return $result;
		
		}
	
	}
	
	/**
	 *	pb_backupbuddy_pclzip_helper Class
	 *
	 *	Extends the parent pb_backupbuddy_zip_helper class for the pclzip method.
	 *	We need a single instance of this class with a static method to get the
	 *	instance that can be called in the pclzip callbacks and then from within
	 *	the callbacks the non-static class methods can be called on the instance.
	 *	So conventional usage would be to do a new to get an instance of the
	 *	class and then the callback can call the static function to get that instance
	 *	and the object can also be called as usual. Equally the static function could
	 *	be called and have it actually create the instance and if we need it later
	 *	outside of pclzip we can call the static function to get the instance.
	 *
	 */
	class pb_backupbuddy_pclzip_helper extends pb_backupbuddy_zip_helper {
	
        /**
         * Our object instance
         * 
         * @var $_instance 	object
         */
		protected static $_instance = NULL;
	
		// Create an instance - normally would call this
		/**
		 *	__construct()
		 *	
		 *	Default constructor.
		 *	Record our own instance and then use the parent constructor.
		 *	
		 *	@return		none
		 *
		 */
		public function __construct() {
		
			self::$_instance = $this;
			parent::__construct();
			
		}
		
		/**
		 *	__destruct()
		 *	
		 *	Default destructor.
		 *	Nullify our own instance and then use the parent destructor.
		 *	
		 *	@return		none
		 *
		 */
		public function __destruct() {
		
			self::$_instance = NULL;
			parent::__destruct();
		
		}
	
		/**
		 * 
		 *	get_instance()
		 *
		 *	If the object is already created then simply return the instance else
		 *	create an object and return the instance.
		 *	Currently only one instance is allowed at a time but currently there is
		 *	no scenario that would require more than one at any time.
		 *
		 *	@return		object					This object instance	
		 *
		 */
		public static function get_instance() {
		
			if ( NULL === self::$_instance ) {
			
				self::$_instance = new self;
				
			}
		
			return self::$_instance;
			
		}
	
		/**
		 * 
		 *	event_handler()
		 *
		 *	Callback function from pclzip to call appropriate event handler function.
		 *
		 *	@param		int			$event		The callback event identifier
		 *	@param		reference	&$header	The element header array
		 *	@return		bool					True|False based on handler function result	
		 *
		 */
		public function event_handler( $event, &$header ) {
		
			$result = true;
		
			switch ( $event ) {
			
				case PCLZIP_CB_POST_ADD:
				
					$result = $this->element_added( $header );
					break;
				
				default:
				
					pb_backupbuddy::status( 'details', sprintf( __("Unknown PclZip Callback Event: %1$s",'it-l10n-backupbuddy' ), $event ) );
			
			}
			
			return $result;
			
		}
		
		/**
		 * 
		 *	element_added()
		 *
		 *	Callback function handler for after an element has been added.
		 *	Only process the element if it has been added (header status is 'ok')
		 *	Keep track of and log progress.
		 *	Keep track of and log usage data - in particular the user space time used
		 *	which relates to what is counted against execution time.
		 *	Optionally handle multi-burst processing by an execution time reset.
		 *	Optionally handle server tickling by dummy flush to server.
		 *
		 *	@param		reference	&$header	The element header array
		 *	@return		bool					True always	
		 *
		 */
		private function element_added( &$header ) {
		
			$usage_data = array();
			$current_log_period = 0;
			$current_burst_period = 0;
			$current_tickle_period = 0;
			$reported = false;
			$result = true;
		
			// Only if the dir/file was added ok
			if ( 'ok' === $header[ 'status' ] ) {
			
				// Always logging progress
				// Increment the appropriate count and decide if we need to log progress
				( true === $header[ 'folder' ] ) ? $this->incr_added_dir_count() : $this->incr_added_file_count() ;
				if ( 0 === ( ( $this->get_added_file_count() + $this->get_added_dir_count() ) % 100 ) ) {
				
					pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s/%2$s (directories/files) added to backup zip archive','it-l10n-backupbuddy' ), $this->get_added_dir_count(), $this->get_added_file_count() ) );
				
				}
				
				// Would expect to log except on Windows which doesn't support getrusage()
				if ( true === $this->is_logging_usage() ) {
				
					// Decide if we need to log usage data
					$current_log_period = ( time() - $this->get_log_start_time() );
					if ( $this->get_log_threshold_period() < $current_log_period ) {
				
						if ( false === $reported ) {
					
							// Log where we are at and connection status for information
							pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s/%2$s (directories/files) added to backup zip archive','it-l10n-backupbuddy' ), $this->get_added_dir_count(), $this->get_added_file_count() ) );
							pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: Connection status: %1$s (%2$s)','it-l10n-backupbuddy' ), $this->connection_status_tostring( connection_status() ), connection_status() ) );
							$reported = true;
						
						}
					
						// Get some usage data from the server (check that function available) and log it
						$usage_data = getrusage();
					
						// Determine the total user space time since we initialized the logging (relative)
						$this->_elapsed_user_time = ( $usage_data[ 'ru_utime.tv_sec' ] - $this->_start_user_time );
						pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: Usage data (raw/relative): ( %1$s, %2$s, %3$s, %4$s, %5$s )/( -, %6$s, -, -, - )','it-l10n-backupbuddy' ), $usage_data[ 'ru_stime.tv_sec' ], $usage_data[ 'ru_utime.tv_sec' ], $usage_data[ 'ru_majflt' ], $usage_data[ 'ru_nvcsw' ], $usage_data[ 'ru_nivcsw' ], $this->_elapsed_user_time ) );

						// Reset the logging period start time
						$this->set_log_start_time( time() );

					}
				
				}
				
				// Only bother with burst handling if multi-burst mode selected
				if ( true === $this->is_multi_burst() ) {
				
					// Decide if we have been running long enough to need to reset time limit
					$current_burst_period = ( time() - $this->get_burst_start_time() );
					if ( $this->get_burst_threshold_period() < $current_burst_period ) {
				
						if ( false === $reported ) {
						
							// Log where we are at and connection status for information
							pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s/%2$s (directories/files) added to backup zip archive','it-l10n-backupbuddy' ), $this->get_added_dir_count(), $this->get_added_file_count() ) );
							pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: Connection status: %1$s (%2$s)','it-l10n-backupbuddy' ), $this->connection_status_tostring( connection_status() ), connection_status() ) );
							$reported = true;
							
						}
					
						// Log how long the burst ran for and then reset the burst start time and max period
						pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s seconds elapsed - resetting timebase to %2$s seconds','it-l10n-backupbuddy' ), $current_burst_period, $this->get_burst_max_period() ) );

						// Reset the burst period start time
						$this->set_burst_start_time( time() );
					
						// Reset the execution time timer (if the server honours this)
						@set_time_limit( $this->get_burst_max_period() );
						// Maybe use ini_set() if set_time_limit() were to be disabled (test that on object creation)
						//@ini_set( 'max_execution_time', 30 );
					
					}
				
				}
			
				// Only bother with server tickling if it is selected
				if ( true === $this->is_server_tickling() ) {
				
					// Decide if we have been running long enough to need to tickle the server
					$current_tickle_period = ( time() - $this->get_tickle_start_time() );
					if ( $this->get_tickle_threshold_period() < $current_tickle_period ) {
				
						if ( false === $reported ) {
						
							// Log where we are at and connection status for information
							pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s/%2$s (directories/files) added to backup zip archive','it-l10n-backupbuddy' ), $this->get_added_dir_count(), $this->get_added_file_count() ) );
							pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: Connection status: %1$s (%2$s)','it-l10n-backupbuddy' ), $this->connection_status_tostring( connection_status() ), connection_status() ) );
							$reported = true;
							
						}
					
						// Log how long since we last tickled and indicate tickling
						pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s seconds elapsed - tickling server','it-l10n-backupbuddy' ), $current_tickle_period ) );
						$this->set_tickle_start_time( time() );
					
						// Output the tickler to give something to flush
						echo $this->get_server_tickler();
					
						// Force flushing and end of buffering
						// Possibly should need to do this because nothing should have started buffering
						// should it? It's possible that PHP config could have enabled one level of buffering
						// so at least handle that with the method as exemplified in the PHP manual. Also do
						// a staright flush as that should cause a flush at least to the server which is
						// actually all we want in this particular case.
						while ( @ob_end_flush() );
						flush();

					}
				
				}
			
			}
			
			// Just return based on whether what we tried to do worked or not
			return $result;
		
		}
	
	}

	class pluginbuddy_zbzippclzip extends pluginbuddy_zbzipcore {
	
		// Constants for file handling
		const ZIP_ERRORS_FILE_NAME   = 'last_pclzip_errors.txt';
		const ZIP_WARNINGS_FILE_NAME = 'last_pclzip_warnings.txt';
		const ZIP_OTHERS_FILE_NAME   = 'last_pclzip_others.txt';
		const ZIP_CONTENT_FILE_NAME  = 'last_pclzip_list.txt';
		
        /**
         * method tag used to refer to the method and entities associated with it such as class name
         * 
         * @var $_method_tag 	string
         */
		public static $_method_tag = 'pclzip';
			
        /**
         * This tells us whether this method is regarded as a "compatibility" method
         * 
         * @var bool
         */
		public static $_is_compatibility_method = true;
			
        /**
         * This tells us the dependencies of this method so they can be check to see if the method can be supported
         * Note: PclZip constructor checks for gzopen function and dies on failure so we may as well pre-empt that
         * 
         * @var array
         */
		public static $_method_dependencies = array( 'classes' => array(),
											  		 'functions' => array( 'gzopen' ),
											  		 'extensions' => array( ),
											  		 'files' => array(),
											  		 'check_func' => 'check_method_dependencies_static'
													);
			
		/**
		 * 
		 * get_method_tag_static()
		 *
		 * Get the static method tag in a static context
		 *
		 * @return		string	The method tag
		 *
		 */
		public static function get_method_tag_static() {
		
			return self::$_method_tag;
			
		}

		/**
		 * 
		 * get_is_compatibility_method_static()
		 *
		 * Get the compatibility method indicator in a static context
		 *
		 * @return		bool	True if is a compatibility method
		 *
		 */
		public static function get_is_compatibility_method_static() {
		
			return self::$_is_compatibility_method;
		}

		/**
		 * 
		 * get_method_dependencies_static()
		 *
		 * Get the method dependencies array in a static context
		 *
		 * @return		array	The dependencies of the method that is requires to be a supported method
		 *
		 */
		public static function get_method_dependencies_static() {
		
			return self::$_method_dependencies;
		}

		/**
		 * 
		 * check_method_dependencies_static()
		 *
		 * Allows additional method dependency checks beyond the standard in a static context
		 *
		 * @return		bool	True if additional dependency checks passed
		 *
		 */
		public static function check_method_dependencies_static() {
		
			$result = false;
			
			// Need to verify that at least PclZip should be available to be loaded (but we
			// don't actually want to load it here)
			$possibles = array( ABSPATH . 'wp-admin/includes/class-pclzip.php', pb_backupbuddy::plugin_path() . '/lib/pclzip/pclzip.php' );
			
			foreach ( $possibles as $possible) {
			
				if ( @is_readable( $possible ) ) {
				
					// Found one that should be loadable so break out
					$result = true;
					break;
									
				} 
			
			}
			
			return $result;
		
		}

		/**
		 *	__construct()
		 *	
		 *	Default constructor.
		 *	
		 *	@param		reference	&$parent		[optional] Reference to the object containing the status() function for status updates.
		 *	@return		null
		 *
		 */
		public function __construct( &$parent = NULL ) {

			parent::__construct( $parent );
			
			// Override some of parent defaults
			$this->_method_details[ 'attr' ] = array_merge( $this->_method_details[ 'attr' ],
															array( 'name' => 'PclZip Method',
													  			   'compatibility' => pluginbuddy_zbzippclzip::$_is_compatibility_method )
													  	   );

			// No relevant parameters for this method
			$this->_method_details[ 'param' ] = array();
			
		}
		
		/**
		 *	__destruct()
		 *	
		 *	Default destructor.
		 *	
		 *	@return		null
		 *
		 */
		public function __destruct( ) {
		
			parent::__destruct();

		}
		
		/**
		 *	get_method_tag()
		 *	
		 *	Returns the (static) method tag
		 *	
		 *	@return		string The method tag
		 *
		 */
		public function get_method_tag() {
		
			return pluginbuddy_zbzippclzip::$_method_tag;
			
		}
		
			/**
		 *	get_is_compatibility_method()
		 *	
		 *	Returns the (static) is_compatibility_method boolean
		 *	
		 *	@return		bool
		 *
		 */
		public function get_is_compatibility_method() {
		
			return pluginbuddy_zbzippclzip::$_is_compatibility_method;
			
		}
		
		/**
		 *	is_available()
		 *	
		 *	A function that tests for the availability of the specific method and its available modes. Will test for
		 *  multiple modes (zip & unzip) and only return false if neither is available. Actual available modes will
		 *  be indicated in the method attributes.
		 *
		 *  Note: in this case as the zip and unzip capabilities are all wrapped up in the same class then if we
		 *  can zip then we'll assume (for now) that we can unzip as well so attributes are set accordingly.
		 *	
		 *	@param		string	$tempdir	Temporary directory to use for any test files (must be writeable)
		 *	@return		bool				True if the method is available for at least one mode, false otherwise
		 *
		 */
		public function is_available( $tempdir ) {
		
			$result = false;
			$za = NULL;
			
			$test_file = $tempdir . 'temp_test_' . uniqid() . '.zip';
			
			// This should give us a new archive object, of not catch it and bail out
			try {
			
				$za = new pluginbuddy_PclZip( $test_file );
				$result = true;
				
			} catch ( Exception $e ) {
			
				$error_string = $e->getMessage();
				pb_backupbuddy::status( 'details', sprintf( __('PclZip test FAILED: %1$s','it-l10n-backupbuddy' ), $error_string ) );
				$result = false;

			}
			
			// Only continue if we have a valid archive object
			if ( true === $result ) {
				
				if ( $za->create( __FILE__ , PCLZIP_OPT_REMOVE_PATH, dirname( __FILE__)  ) !== 0 ) {
						
					if ( @file_exists( $test_file ) ) {
					
						if ( !@unlink( $test_file ) ) {
					
							pb_backupbuddy::status( 'details', sprintf( __('Error #564634. Unable to delete test file (%s)!','it-l10n-backupbuddy' ), $test_file ) );
						
						}
						
						// The zip operation was successful - implies can zip and unzip and hence archive, check and list
						$this->_method_details[ 'attr' ][ 'is_zipper' ] = true;
						$this->_method_details[ 'attr' ][ 'is_unzipper' ] = true;
						$this->_method_details[ 'attr' ][ 'is_archiver' ] = true;
						$this->_method_details[ 'attr' ][ 'is_checker' ] = true;
						$this->_method_details[ 'attr' ][ 'is_lister' ] = true;
						$this->_method_details[ 'attr' ][ 'is_commenter' ] = true;
						$this->_method_details[ 'attr' ][ 'is_unarchiver' ] = true;
						$this->_method_details[ 'attr' ][ 'is_extractor' ] = true;
						
						pb_backupbuddy::status( 'details', __('PclZip test PASSED.','it-l10n-backupbuddy' ) );
						$result = true;
						
					} else {
					
						pb_backupbuddy::status( 'details', __('PclZip test FAILED: Zip file not found.','it-l10n-backupbuddy' ) );
						$result = false;
						
					}
					
				} else {
				
					$error_string = $za->errorInfo( true );
					pb_backupbuddy::status( 'details', __('PclZip test FAILED: Unable to create/open zip file.','it-l10n-backupbuddy' ) );
					pb_backupbuddy::status( 'details', __('PclZip Error: ','it-l10n-backupbuddy' ) . $error_string );
					$result = false;
					
				}
				
			}
		  	
		  	if ( NULL != $za ) { unset( $za ); }
		  	
		  	return $result;
		  	
		}
		
		/**
		 *	create()
		 *	
		 *	A function that creates an archive file
		 *	
		 *	The $excludes will be a list or relative path excludes if the $listmaker object is NULL otehrwise
		 *	will be absolute path excludes and relative path excludes can be had from the $listmaker object
		 *	
		 *	@param		string	$zip			Full path & filename of ZIP Archive file to create
		 *	@param		string	$dir			Full path of directory to add to ZIP Archive file
		 *	@parame		array	$excludes		List of either absolute path exclusions or relative exclusions
		 *	@param		string	$tempdir		Full path of directory for temporary usage
		 *	@param		object	$listmaker		The object from which we can get an inclusions list
		 *	@return		bool					True if the creation was successful, false otherwise
		 *
		 */
		public function create( $zip, $dir, $excludes, $tempdir, $listmaker = NULL ) {
		
			$za = NULL;
			$result = false;
			$exitcode = 0;
			$zip_output = array();
			$temp_zip = '';
			$excluding_additional = false;
			$exclude_count = 0;
			$exclusions = array();
			$temp_file_compression_threshold = 5;
			$pre_add_func = '';
			$have_zip_errors = false;
			$zip_errors_count = 0;
			$zip_errors = array();
			$have_zip_warnings = false;
			$zip_warnings_count = 0;
			$zip_warnings = array();
			$have_zip_additions = false;
			$zip_additions_count = 0;
			$zip_additions = array();
			$have_zip_debug = false;
			$zip_debug_count = 0;
			$zip_debug = array();
			$have_zip_other = false;
			$zip_other_count = 0;
			$zip_other = array();
			$zip_ignoring_symlinks = false;
			$symlinks_found = array();
			$zip_helper = NULL;
			
			// The basedir must have a trailing normalized directory separator
			$basedir = ( rtrim( trim( $dir ), self::DIRECTORY_SEPARATORS ) ) . self::NORM_DIRECTORY_SEPARATOR;
		
			// Normalize platform specific directory separators in path
			$basedir = str_replace( DIRECTORY_SEPARATOR, self::NORM_DIRECTORY_SEPARATOR, $basedir );
			
			// Ensure no stale file information
			clearstatcache();
			
			// Note: could enforce trailing directory separator for robustness
			if ( empty( $tempdir ) || !file_exists( $tempdir ) ) {
			
				// This breaks the rule of single point of exit (at end) but it's early enough to not be a problem
				pb_backupbuddy::status( 'details', __('Temporary working directory must be available.','it-l10n-backupbuddy' ) );				
				return false;
				
			}
			
			pb_backupbuddy::status( 'message', __('Using Compatibility Mode.','it-l10n-backupbuddy' ) );
			pb_backupbuddy::status( 'message', __('If your backup times out in Compatibility Mode try disabling zip compression in Settings.','it-l10n-backupbuddy' ) );
			
			// Update the definition before it is used by loading the library
			// This will not wok if perchance the file has already been loaded :-(
			define( 'PCLZIP_TEMPORARY_DIR', $tempdir );
			
			// Decide whether we are offering exclusions or not
			// Note that unlike proc and zip we always use inclusion if available to offer exclusion capability for pclzip
			if ( is_object( $listmaker ) ) {
				
				// Need to get the relative exclusions so we can log what is being excluded...
				$exclusions = $listmaker->get_relative_excludes( $basedir );
				
				// Build the exclusion list - first the relative directories
				if ( count( $exclusions ) > 0 ) {
				
					pb_backupbuddy::status( 'details', __('Calculating directories/files to exclude from backup (relative to site root).','it-l10n-backupbuddy' ) );
					
					foreach ( $exclusions as $exclude ) {
					
						if ( !strstr( $exclude, 'backupbuddy_backups' ) ) {
	
							// Set variable to show we are excluding additional directories besides backup dir.
							$excluding_additional = true;
								
						}
							
						pb_backupbuddy::status( 'details', __('Excluding','it-l10n-backupbuddy' ) . ': ' . $exclude );
						
						$exclude_count++;
							
					}
					
				}
				
				
				if ( true === $excluding_additional ) {
				
					pb_backupbuddy::status( 'message', __( 'Excluding archives directory and additional directories defined in settings.','it-l10n-backupbuddy' ) . ' ' . $exclude_count . ' ' . __( 'total','it-l10n-backupbuddy' ) . '.' );
					
				} else {
				
					pb_backupbuddy::status( 'message', __( 'Only excluding archives directory based on settings.','it-l10n-backupbuddy' ) . ' ' . $exclude_count . ' ' . __( 'total','it-l10n-backupbuddy' ) . '.' );
					
				}
				
				// Now get the list from the top node
				$the_list = $listmaker->get_terminals();
				
				// Retain this for reference for now
				//file_put_contents( ( dirname( $tempdir ) . DIRECTORY_SEPARATOR . self::ZIP_CONTENT_FILE_NAME ), print_r( $the_list, true ) );
			
			} else {
		
				// We don't have the inclusion list so we are not offering exclusions
				pb_backupbuddy::status( 'message', __('WARNING: Directory/file exclusion unavailable in Compatibility Mode. Even existing old backups will be backed up.','it-l10n-backupbuddy' ) );
				$the_list = array( $dir );
			
			}
		
			// Get started with out zip object
			// Put our final zip file in the temporary directory - it will be moved later
			$temp_zip = $tempdir . basename( $zip );
			
			// This should give us a new archive object, of not catch it and bail out
			try {
					
				$za = new pluginbuddy_PclZip( $temp_zip );
				$result = true;
				
			} catch ( Exception $e ) {
			
				// Something fishy - the methods indicated pclzip but we couldn't find the class
				$error_string = $e->getMessage();
				pb_backupbuddy::status( 'details', sprintf( __('pclzip indicated as available method but error reported: %1$s','it-l10n-backupbuddy' ), $error_string ) );
				$result = false;
				
			}
			
			// Only continue if we have a valid archive object
			if ( true === $result ) {
			
				// Basic argument list
				$arguments = array();
				array_push( $arguments, $the_list );
				array_push( $arguments, PCLZIP_OPT_REMOVE_PATH, $dir );				

				if ( true !== $this->get_compression() ) {
				
					// Note: don't need to force use of temporary files for compression
					pb_backupbuddy::status( 'details', __('Zip archive creation compression disabled based on settings.','it-l10n-backupbuddy' ) );
					array_push( $arguments, PCLZIP_OPT_NO_COMPRESSION );
					
				} else {
	
					// Note: force the use of temporary files for compression when file size exceeds given value.
					// This over-rides the "auto-sense" which is based on memory_limit and this _may_ indicate a
					// memory availability that is higher than reality leading to memory allocation failure if
					// trying to compress large files. Set the threshold low enough (specify in MB) so that except in
					// The tightest memory situations we should be ok. Could have option to force use of temporary
					// files regardless.
					pb_backupbuddy::status( 'details', __('Zip archive creation compression enabled based on settings.','it-l10n-backupbuddy' ) );
					array_push( $arguments, PCLZIP_OPT_TEMP_FILE_THRESHOLD, $temp_file_compression_threshold );
						
				}
				
				// Check if ignoring (not following) symlinks
				if ( true === $this->get_ignore_symlinks() ) {
			
					// Want to not follow symlinks so set flag for later use
					$zip_ignoring_symlinks = true;
				
					pb_backupbuddy::status( 'details', __('Zip archive creation symbolic links will be ignored based on settings.','it-l10n-backupbuddy' ) );

				} else {
			
					pb_backupbuddy::status( 'details', __('Zip archive creation symbolic links will not be ignored based on settings.','it-l10n-backupbuddy' ) );

				}
			
				// Check if we are ignoring warnings - meaning can still get a backup even
				// if, e.g., some files cannot be read
				if ( true === $this->get_ignore_warnings() ) {
				
					// Note: warnings are being ignored but will still be gathered and logged
					pb_backupbuddy::status( 'details', __('Zip archive creation actionable warnings will be ignored based on settings.','it-l10n-backupbuddy' ) );
					
				} else {
				
					pb_backupbuddy::status( 'details', __('Zip archive creation actionable warnings will not be ignored based on settings.','it-l10n-backupbuddy' ) );

				}
				
				// Use anonymous function to weed out the unreadable and non-existent files (common reason for failure)
				// and possibly symlinks based on user settings.
				// PclZip will record these files as 'skipped' in the file status and we can post-process to determine
				// if we had any of these and hence either stop the backup or continue dependent on whether the user
				// has chosen to ignore warnings or not and/or ignore symlinks or not.
				// Unfortunately we cannot directly tag the file with the reason why it has been skipped so when we
				// have to process the skipped items we have to try and work out why it was skipped - but shouldn't
				// be too hard.
				// TODO: Consider moving this into the PclZip wrapper and have a method to set the various pre/post
				// functions or select predefined functions (such as this).
				if ( true ) {
				
					// Note: This could be simplified - it's written to be extensible but may not need to be
					$args = '$event, &$header';
					$code = '';
					$code .= 'static $symlinks = array(); ';
					$code .= '$result = true; ';
					
					// Handle symlinks - keep the two cases of ignoring/not-ignoring separate for now to make logic more
					// apparent - but could be merged with different conditional handling
					// For a valid symlink: is_link() -> true; is_file()/is_dir() -> true; file_exists() -> true
					// For a broken symlink: is_link() -> true; is_file()/is_dir() -> false; file_exists() -> false
					// Note: pclzip first tests every file using file_exists() before ever trying to add the file so
					// for a broken symlink it will _always_ error out immediately it discovers a broken symlink so
					// we never have a chance to filter these out at this stage.
					if ( true === $zip_ignoring_symlinks ) {
					
						// If it's a symlink or it's neither a file nor a directory then ignore it. A broken symlink
						// will never get this far because pclzip will have choked on it
						$code .= 'if ( ( true === $result ) && !( @is_link( $header[\'filename\'] ) ) ) { ';
						$code .= '    if ( @is_file( $header[\'filename\'] ) || @is_dir( $header[\'filename\'] ) ) { ';
						$code .= '        $result = true; ';
						$code .= '        foreach ( $symlinks as $prefix ) { ';
						$code .= '            if ( !( false === strpos( $header[\'filename\'], $prefix ) ) ) { ';
						$code .= '                $result = false; ';
						$code .= '                break; ';
						$code .= '             } ';
						$code .= '        } ';
						$code .= '    } else { ';
//						$code .= '        error_log( "Neither a file nor a directory (ignoring): \'" . $header[\'filename\'] . "\'" ); ';
						$code .= '        $result = false; ';
						$code .= '    } ';
						$code .= '} else { ';
//						$code .= '    error_log( "File is a symlink (ignoring): \'" . $header[\'filename\'] . "\'" ); ';
						$code .= '    $symlinks[] = $header[\'filename\']; ';
//						$code .= '    error_log( "Symlinks Array: \'" . print_r( $symlinks, true ) . "\'" ); ';
						$code .= '    $result = false; ';
						$code .= '} ';
						
					} else {
					
						// If it's neither a file nor directory then ignore it - a valid symlink will register as a file
						// or directory dependent on what it is pointing at. A broken symlink will never get this far.
						$code .= 'if ( ( true === $result ) && ( @is_file( $header[\'filename\'] ) || @is_dir( $header[\'filename\'] ) ) ) { ';
						$code .= '    $result = true; ';
						$code .= '} else { ';
//						$code .= '    error_log( "Neither a file nor a directory (ignoring): \'" . $header[\'filename\'] . "\'" ); ';
						$code .= '    $result = false; ';
						$code .= '} ';
						
					}
					
					// Add the code block for ignoring unreadable files
					if ( true ) {
					
						$code .= 'if ( ( true === $result ) && ( @is_readable( $header[\'filename\'] ) ) ) { ';
						$code .= '    $result = true; ';
						$code .= '} else { ';
//						$code .= '    error_log( "File not readable: \'" . $header[\'filename\'] . "\'" ); ';
						$code .= '    $result = false; ';
						$code .= '} ';
					
					}
					
					// Return true (to include file) if file passes conditions otherwise false (to skip file) if not
					$code .= 'return ( ( true === $result ) ? 1 : 0 ); ';

					$pre_add_func = create_function( $args, $code );
				
				}
				
				// If we had cause to create a pre add function then add it to the argument list here
				if ( !empty( $pre_add_func ) ) {
				
					array_push( $arguments, PCLZIP_CB_PRE_ADD, $pre_add_func );
		
				}
				
				// Add a post-add function for progress logging, usage data logging,
				// burst handling and server tickling
				$post_add_func = '';
				
				// Create the helper function here so we can use it outside of the post-add
				// function. Using all defaults so includes multi-burst and server tickling
				// for now but with options we can modify this.				
				$zip_helper = new pb_backupbuddy_pclzip_helper();
				
				if (true) {
				
					$args = '$event, &$header';
					$code = '';
					$code .= '$result = true; ';
					$code .= '$zip_helper = pb_backupbuddy_pclzip_helper::get_instance();';
					$code .= '$result = $zip_helper->event_handler( $event, $header );';
					$code .= 'return $result;';
				
					$post_add_func = create_function( $args, $code );
				
				}

				// If we had cause to create a pre add function then add it to the argument list here
				if ( !empty( $post_add_func ) ) {
				
					array_push( $arguments, PCLZIP_CB_POST_ADD, $post_add_func );
		
				}

				if ( @file_exists( $zip ) ) {
	
					pb_backupbuddy::status( 'details', __('Existing ZIP Archive file will be replaced.','it-l10n-backupbuddy' ) );
					@unlink( $zip );
	
				}
				
				// Now actually create the zip archive file
				// First implode any embedded array in the argument list and truncate the result if too long
				// Assume no arrays embedded in arrays - currently no reason for that
				// TODO: Make the summary length configurable so that can see more if required
				// TODO: Consider mapping pclzip argument identifiers to string representations for clarity
				$args = '$item';
				$code = 'if ( is_array( $item ) ) { $string_item = implode( ",", $item); return ( ( strlen( $string_item ) <= 50 ) ? $string_item : "List: " . substr( $string_item, 0, 50 ) . "..." ); } else { return $item; }; ';
				$imploder_func = create_function( $args, $code );
				$imploded_arguments = array_map( $imploder_func, $arguments );
				
				pb_backupbuddy::status( 'details', $this->get_method_tag() . __( ' command arguments','it-l10n-backupbuddy' ) . ': ' . implode( ';', $imploded_arguments ) );
				
				// Do this as close to when we actually want to start monitoring usage
				$zip_helper->initialize_logging_usage();
				
				$output = call_user_func_array( array( &$za, 'create' ), $arguments );
				
				// Work out whether we have a problem or not
				if ( is_array( $output ) ) {
				
					// It's an array so at least we produced a zip archive 
					$exitcode = 0;

					// We can report how many dirs/files added according to pclzip					
					pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s/%2$s (directories/files) added to backup zip archive (final)','it-l10n-backupbuddy' ), $zip_helper->get_added_dir_count(), $zip_helper->get_added_file_count() ) );

					// Process the array for any "warnings" or other reportable conditions
					$id = 0; // Create a unique key (like a line number) for later sorting
					foreach( $output as $file ) {
					
						switch ( $file[ 'status' ] ) {
							case "skipped":
							
								// First need to filter out any files skipped because under a symlink dir
								foreach ( $symlinks_found as $prefix ) {
								
									if ( !( false === strpos( $file[ 'filename' ], $prefix ) ) ) {
									
										$id++;
										// break out of the foreach and the switch
										break 2;
										
									}
									
								}
								
								// For skipped files need to determine why it was skipped
								if ( ( true === $zip_ignoring_symlinks ) && @is_link( $file[ 'filename' ] ) ) {
								
									// Remember this for filtering other files skipped because in symlink directory
									$symlinks_found[] = $file[ 'filename' ];
									
									// Skipped because we are ignoring symlinks and this is a symlink
									$zip_other[ self::ZIP_OTHER_IGNORED_SYMLINK ][ $id++ ] = $file[ 'filename' ];
									$zip_other_count++;
									
								} else {
								
									//Skipped because probably unreadable or non-existent (catch-all for now)
									$zip_warnings[ self::ZIP_WARNING_SKIPPED ][ $id++ ] = $file[ 'filename' ];
									$zip_warnings_count++;
									
								}
								break;
							case "filtered":
								$zip_warnings[ self::ZIP_OTHER_FILTERED ][ $id++ ] = $file[ 'filename' ];
								$zip_warnings_count++;
								break;
							case "filename_too_long":
								$zip_warnings[ self::ZIP_OTHER_LONGPATH ][ $id++ ] = $file[ 'filename' ];
								$zip_warnings_count++;
								break;
							default:
								// Currently not processing "ok" entries
								$id++;
						}
					
					}
					
					// Now free up the memory...
					unset( $output );
					
					// Set convenience flags			
					$have_zip_warnings = ( 0 < $zip_warnings_count );
					$have_zip_other = ( 0 < $zip_other_count );
				
				} else {
				
					// Not an array so a bad error code, something we didn't or couldn't catch
					$exitcode = $za->errorCode();
					
					// Put the error information into an array for consistency
					$zip_errors[] = $za->errorInfo( true );
					$zip_errors_count = sizeof( $zip_errors );
					$have_zip_errors = ( 0 < $zip_errors_count );
				
				}
				
				// Convenience for handling different scanarios
				$result = false;
				
				// Always report the exit code regardless of whether we might ignore it or not
				pb_backupbuddy::status( 'details', __('Zip process exit code: ','it-l10n-backupbuddy' ) . $exitcode );
	
				// Always report the number of warnings - even just to confirm that we didn't have any
				pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s warning%2$s','it-l10n-backupbuddy' ), $zip_warnings_count, ( ( 1 == $zip_warnings_count ) ? '' : 's' ) ) );
				
				// Always report warnings regardless of whether user has selected to ignore them
				if ( true === $have_zip_warnings ) {
			
					$this->log_zip_reports( $zip_warnings, self::$_warning_desc, "WARNING", self::MAX_WARNING_LINES_TO_SHOW, dirname( dirname( $tempdir ) ) . DIRECTORY_SEPARATOR . 'pb_backupbuddy' . DIRECTORY_SEPARATOR . self::ZIP_WARNINGS_FILE_NAME );

				}
			
				// Always report other reports regardless
				if ( true === $have_zip_other ) {
			
					// Only report number of informationals if we have any as they are not that important
					pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s information%2$s','it-l10n-backupbuddy' ), $zip_other_count, ( ( 1 == $zip_other_count ) ? 'al' : 'als' ) ) );

					$this->log_zip_reports( $zip_other, self::$_other_desc, "INFORMATION", self::MAX_OTHER_LINES_TO_SHOW, dirname( dirname( $tempdir ) ) . DIRECTORY_SEPARATOR . 'pb_backupbuddy' . DIRECTORY_SEPARATOR . self::ZIP_OTHERS_FILE_NAME );

				}
			
				// See if we can figure out what happened
				// Note: only expect exitcode to be non-zero for an error we couldn't pre-empt
				// Note: warnings will cause the operation to be stopped if user hasn't chosen to ignore regardless
				// of whether we got a zip file (which we most likely did).
				// Note: a non-zero exitcode and presence of warnings are mutually exclusive
				if ( ( ! @file_exists( $temp_zip ) ) ||
					 ( 0 != $exitcode ) ||
					 ( ( true == $have_zip_warnings ) && !$this->get_ignore_warnings() ) ) {
				
					// If we have any zip errors reported show them regardless
					if ( true == $have_zip_errors ) {
					
						pb_backupbuddy::status( 'details', sprintf( __('Zip process reported: %1$s error%2$s','it-l10n-backupbuddy' ), $zip_errors_count, ( ( 1 == $zip_errors_count ) ? '' : 's' )  ) );
					
						foreach ( $zip_errors as $line ) {
						
							pb_backupbuddy::status( 'details', __( 'Zip process reported: ','it-l10n-backupbuddy' ) . $line );
						
						}
					
					}
					
					// Report whether or not the zip file was created (this will always be in the temporary location)			
					if ( ! @file_exists( $temp_zip ) ) {
					
						pb_backupbuddy::status( 'details', __( 'Zip Archive file not created - check process exit code.','it-l10n-backupbuddy' ) );
						
					} else {
						
						pb_backupbuddy::status( 'details', __( 'Zip Archive file created but with errors/actionable-warnings so will be deleted - check process exit code and warnings.','it-l10n-backupbuddy' ) );
	
					}
					
					// The operation has failed one way or another. Note that for pclzip the zip file is always created in the temporary
					// location regardless of whether the user selected to ignore errors or not (we can never guarantee to create a valid
					// zip file because the script might be terminated by the server so we must wait to produce a valid file and then
					// move it to the final location if it is valid).
					// Therefore if there is a zip file (produced but with warnings) it will not be visible and will be deleted when the
					// temporary directory is deleted below.
					
					$result = false;
					
				} else {
				
					// Got file with no error or warnings _or_ with warnings that the user has chosen to ignore
					// File always built in temporary location so always need to move it
					pb_backupbuddy::status( 'details', __('Moving Zip Archive file to local archive directory.','it-l10n-backupbuddy' ) );
					
					// Make sure no stale file information
					clearstatcache();
					
					// Relocate the temporary zip file to final location
					@rename( $temp_zip, $zip );
					
					// Check that we moved the file ok
					if ( @file_exists( $zip ) ) {
					
						pb_backupbuddy::status( 'details', __('Zip Archive file moved to local archive directory.','it-l10n-backupbuddy' ) );
						pb_backupbuddy::status( 'message', __( 'Zip Archive file successfully created with no errors (any actionable warnings ignored by user settings).','it-l10n-backupbuddy' ) );
						
						$this->log_archive_file_stats( $zip );
						
						// Temporary for now - try and incorporate into stats logging (makes the stats logging function part of the zip helper class?)
						pb_backupbuddy::status( 'details', sprintf( __('Zip Archive file size: %1$s/%2$s (directories/files) added','it-l10n-backupbuddy' ), $zip_helper->get_added_dir_count(), $zip_helper->get_added_file_count() ) );
					
						$result = true;
						
					} else {
					
						pb_backupbuddy::status( 'details', __('Zip Archive file could not be moved to local archive directory.','it-l10n-backupbuddy' ) );
						$result = false;
						
					}
									
				}

			}
			
			// Cleanup the temporary directory that will have all detritus and maybe incomplete zip file		
			pb_backupbuddy::status( 'details', __('Removing temporary directory.','it-l10n-backupbuddy' ) );
			
			if ( !( $this->delete_directory_recursive( $tempdir ) ) ) {
			
					pb_backupbuddy::status( 'details', __('Temporary directory could not be deleted: ','it-l10n-backupbuddy' ) . $tempdir );
			
			}
			
		  	if ( NULL != $za ) { unset( $za ); }
		  	
			return $result;
															
		}
		
		/**
		 *	extract()
		 *
		 *	Extracts the contents of a zip file to the specified directory using the best unzip methods possible.
		 *	If no specific items given to extract then it's a complete unzip
		 *
		 *	@param	string		$zip_file					Full path & filename of ZIP file to extract from.
		 *	@param	string		$destination_directory		Full directory path to extract into.
		 *	@param	array		$items						Mapping of what to extract and to what
		 *	@return	bool									true on success (all extractions successful), false otherwise
		 */
		public function extract( $zip_file, $destination_directory = '', $items = array() ) {
		
			$result = false;
		
			switch ( $this->get_os_type() ) {
				case self::OS_TYPE_NIX:
					if ( empty( $items ) ) {
						$result = $this->extract_generic_full( $zip_file, $destination_directory );
					} else {
						$result = $this->extract_generic_selected( $zip_file, $destination_directory, $items );					
					}
					break;
				case self::OS_TYPE_WIN:
					if ( empty( $items ) ) {
						$result = $this->extract_generic_full( $zip_file, $destination_directory );
					} else {
						$result = $this->extract_generic_selected( $zip_file, $destination_directory, $items );					
					}
					break;
				default:
					$result = false;
			}
			
			return $result;
			
		}

		/**
		 *	extract_generic_full()
		 *
		 *	Extracts the contents of a zip file to the specified directory using the best unzip methods possible.
		 *
		 *	@param	string		$zip_file					Full path & filename of ZIP file to extract from.
		 *	@param	string		$destination_directory		Full directory path to extract into.
		 *	@return	bool									true on success, false otherwise
		 */
		protected function extract_generic_full( $zip_file, $destination_directory = '' ) {
		
			$result = false;
			$za = NULL;
								
			// Update the definition before it is used by loading the library
			// This will not wok if perchance the file has already been loaded :-(
			// TODO: Need a temporary directory that we can use for this
			//define( 'PCLZIP_TEMPORARY_DIR', $tempdir );
			
			// This should give us a new archive object, if not catch it and bail out
			try {
					
				$za = new pluginbuddy_PclZip( $zip_file );
				$result = true;
				
			} catch ( Exception $e ) {
			
				// Something fishy - the methods indicated pclzip but we couldn't find the class
				$error_string = $e->getMessage();
				pb_backupbuddy::status( 'details', sprintf( __('pclzip indicated as available method but error reported: %1$s','it-l10n-backupbuddy' ), $error_string ) );
				$result = false;
				
			}
			
			// Only continue if we have a valid archive object
			if ( true === $result ) {
			 	
				// Make sure we opened the zip ok and it has content
				if ( ( $content_list = $za->extract( PCLZIP_OPT_PATH, $destination_directory ) ) !== 0 ) {
				
					// How many files - must be >0 to have got here
					$file_count = sizeof( $content_list );
					
					pb_backupbuddy::status( 'details', sprintf( __('pclzip extracted file contents (%1$s to %2$s)','it-l10n-backupbuddy' ), $zip_file, $destination_directory ) );

					$this->log_archive_file_stats( $zip_file );	
					
					$result = true;
					
				} else {
				
					// Couldn't open archive - will return for maybe another method to try
					$error_string = $za->errorInfo( true );
					pb_backupbuddy::status( 'details', sprintf( __('pclzip failed to open file to extract contents (%1$s to %2$s) - Error Info: %3$s.','it-l10n-backupbuddy' ), $zip_file, $destination_directory, $error_string ) );

					// Return an error code and a description - this needs to be handled more generically
					//$result = array( 1, "Unable to get archive contents" );
					// Currently as we are returning an array as a valid result we just return false on failure
					$result = false;

				}
							
			}
			
		  	if ( NULL != $za ) { unset( $za ); }		
			
			return $result;
			
		}

		/**
		 *	extract_generic_selected()
		 *
		 *	Extracts the contents of a zip file to the specified directory using the best unzip methods possible.
		 *
		 *	@param	string		$zip_file					Full path & filename of ZIP file to extract from.
		 *	@param	string		$destination_directory		Full directory path to extract into.
		 *	@param	array		$items						Mapping of what to extract and to what
		 *	@return	bool									true on success (all extractions successful), false otherwise
		 */
		protected function extract_generic_selected( $zip_file, $destination_directory = '', $items ) {
		
			$result = false;
			$za = NULL;
			$stat = array();
			
			// This should give us a new archive object, if not catch it and bail out
			try {
			
				$za = new pluginbuddy_PclZip( $zip_file );
				$result = true;
				
			} catch ( Exception $e ) {
			
				// Something fishy - the methods indicated ziparchive but we couldn't find the class
				$error_string = $e->getMessage();
				pb_backupbuddy::status( 'details', sprintf( __('pclzip indicated as available method but error reported: %1$s','it-l10n-backupbuddy' ), $error_string ) );
				$result = false;
				
			}
			
			// Only continue if we have a valid archive object
			if ( true === $result ) {
				
				// Make sure we opened the zip ok and it has content
				if ( ( $content_list = $za->listContent() ) !== 0 ) {
				
					// Now we need to take each item and run an unzip for it - unfortunately there is no easy way of combining
					// arbitrary extractions into a single command if some might be to a 
					foreach ( $items as $what => $where ) {
			
						$rename_required = false;
						$result = false;
				
						// Decide how to extract based on where
						if ( empty( $where) ) {
					
							// First we'll extract and junk the path
							// Note: For some odd reason when we have a $what file that is a hidden (dot) file
							// the file_exists() test in pclzip for the filepath to extract to returns true even
							// though only the parent directory exists and not the file itself. No idea why at
							// present. Because of that we have to use the PCL_ZIP_OPT_REPLACE_NEWER option
							// so the fact the test returns true is ignored.
							$extract_list = $za->extract( PCLZIP_OPT_PATH, $destination_directory, PCLZIP_OPT_BY_NAME, $what, PCLZIP_OPT_REMOVE_ALL_PATH, PCLZIP_OPT_REPLACE_NEWER );
								
							// Check whether we succeeded or not (would only be no list array for a zip file problem)
							// but extraction of the file itself may still have failed
							$result = ( $extract_list !== 0  && ( $extract_list[ 0 ][ 'status' ] == 'ok' ) );
															
						} elseif ( !empty( $where ) ) {
					
							if ( $what === $where ) {
							
								// Check for wildcard directory extraction like dir/* => dir/*
								if ( "*" == substr( trim( $what ), -1 ) ) {

									// Turn this into a preg_match pattern
									$whatmatch = "|^" . $what . "|";									

									// First we'll extract but we're not junking the paths
									// Note: For some odd reason when we have a $what file that is a hidden (dot) file
									// the file_exists() test in pclzip for the filepath to extract to returns true even
									// though only the parent directory exists and not the file itself. No idea why at
									// present. Because of that we have to use the PCL_ZIP_OPT_REPLACE_NEWER option
									// so the fact the test returns true is ignored.
									$extract_list = $za->extract( PCLZIP_OPT_PATH, $destination_directory, PCLZIP_OPT_BY_PREG, $whatmatch, PCLZIP_OPT_REPLACE_NEWER );

									// Check whether we succeeded or not (would only be no list array for a zip file problem)
									// but extraction of individual files themselves may still have failed
									if ( 0 !== $extract_list ) {
									
										// So far so good - assume everything will be ok
										$result = true;
	
										// At least we got no major failure so check the extracted files
										foreach ( $extract_list as $file ) {
										
											if ( 'ok' !== $file[ 'status' ] ) {
											
												// Oops - we found a file that didn't extract ok so bail out with false
												$result = false;
												break;
											
											}
										
										}
									
									}
								
								} else {
								
									// It's just a single file extraction - breath a sign of relief
									// Extract to same directory structure - don't junk path, no need to add where to destnation as automatic
									// Note: For some odd reason when we have a $what file that is a hidden (dot) file
									// the file_exists() test in pclzip for the filepath to extract to returns true even
									// though only the parent directory exists and not the file itself. No idea why at
									// present. Because of that we have to use the PCL_ZIP_OPT_REPLACE_NEWER option
									// so the fact the test returns true is ignored.
									$extract_list = $za->extract( PCLZIP_OPT_PATH, $destination_directory, PCLZIP_OPT_BY_NAME, $what, PCLZIP_OPT_REPLACE_NEWER );
									
									// Check whether we succeeded or not (would only be no list array for a zip file problem)
									// but extraction of the file itself may still have failed
									$result = ( $extract_list !== 0  && ( $extract_list[ 0 ][ 'status' ] == 'ok' ) );

								}
						
							} else {

								// First we'll extract and junk the path
								// Note: For some odd reason when we have a $what file that is a hidden (dot) file
								// the file_exists() test in pclzip for the filepath to extract to returns true even
								// though only the parent directory exists and not the file itself. No idea why at
								// present. Because of that we have to use the PCL_ZIP_OPT_REPLACE_NEWER option
								// so the fact the test returns true is ignored.
								$extract_list = $za->extract( PCLZIP_OPT_PATH, $destination_directory, PCLZIP_OPT_BY_NAME, $what, PCLZIP_OPT_REMOVE_ALL_PATH, PCLZIP_OPT_REPLACE_NEWER );
																							
								// Check whether we succeeded or not (would only be no list array for a zip file problem)
								// but extraction of the file itself may still have failed
								$result = ( $extract_list !== 0  && ( $extract_list[ 0 ][ 'status' ] == 'ok' ) );

								// Will need to rename if the extract is ok
								$rename_required = true;
						
							}
					
						}
				
						// Note: we don't open the file and then do stuff but it's all done in one action
						// so we need to interpret the return code to dedide what to do
						// Currently we can only distinguish between success and failure but no finer grain
						if ( true === $result ) {
					
							pb_backupbuddy::status( 'details', sprintf( __('pclzip extracted file contents (%1$s from %2$s to %3$s%4$s)','it-l10n-backupbuddy' ), $what, $zip_file, $destination_directory, $where ) );

							// Rename if we have to
							if ( true === $rename_required) {
							
								// Note: we junked the path on the extraction so just the filename of $what is the source but
								// $where could be a simple file name or a file path 
								$result = $result && rename( $destination_directory . DIRECTORY_SEPARATOR . basename( $what ),
															 $destination_directory . DIRECTORY_SEPARATOR . $where );
							
							}

						} else {
					
							// For now let's just print the error code and drop through
							$error_string = $za->errorInfo();
							pb_backupbuddy::status( 'details', sprintf( __('pclzip failed to open/process file to extract file contents (%1$s from %2$s to %3$s%4$s) - Error Info: %5$s.','it-l10n-backupbuddy' ), $what, $zip_file, $destination_directory, $where, $error_string ) );
					
							// May seem redundant but belt'n'braces
							$result = false;
							
						}
					
						// If the extraction failed (or rename after extraction) then break out of the foreach and simply return false
						if ( false === $result ) {
					
							break;
						
						}
					
					}
				
				} else {
				
					// Couldn't open archive - will return for maybe another method to try
					$error_string = $za->errorInfo( $result );
					pb_backupbuddy::status( 'details', sprintf( __('pclzip failed to open file to extract contents (%1$s to %2$s) - Error Info: %3$s.','it-l10n-backupbuddy' ), $zip_file, $destination_directory, $error_string ) );

					// Return an error code and a description - this needs to be handled more generically
					//$result = array( 1, "Unable to get archive contents" );
					// Currently as we are returning an array as a valid result we just return false on failure
					$result = false;

				}
				
				$za->close();
			
			}
			
		  	if ( NULL != $za ) { unset( $za ); }		
			
			return $result;
			
		}
		
		/**
		 *	file_exists()
		 *	
		 *	Tests whether a file (with path) exists in the given zip file
		 *	If leave_open is true then the zip object will be left open for faster checking for subsequent files within this zip
		 *	
		 *	@param		string	$zip_file		The zip file to check
		 *	@param		string	$locate_file	The file to test for
		 *	@param		bool	$leave_open		Optional: True if the zip file should be left open
		 *	@return		bool/array				True if the file is found in the zip and false if not, array for other problem
		 *
		 */
		public function file_exists( $zip_file, $locate_file, $leave_open = false ) {
		
			$result = array( 1, "Generic failure indication" );
			$za = NULL;
			$stat = array();
								
			
			// This should give us a new archive object, of not catch it and bail out
			try {
			
				$za = new pluginbuddy_PclZip( $zip_file );
				$result = true;
				
			} catch ( Exception $e ) {
			
				// Something fishy - the methods indicated pclzip but we couldn't find the class
				$error_string = $e->getMessage();
				pb_backupbuddy::status( 'details', sprintf( __('pclzip indicated as available method but error reported: %1$s','it-l10n-backupbuddy' ), $error_string ) );

				// Return an error code and a description - this needs to be handled more generically
				$result = array( 1, "Class not available to match method" );
				
			}
			
			// Only continue if we have a valid archive object
			if ( true === $result ) {
				
				// Make sure we opened the zip ok and it has content
				if ( ( $content_list = $za->listContent() ) !== 0 ) {
				
					// Assume failure
					$result = false;
					
					// Get each file in sequence by index and get the properties
					for ( $i = 0; $i < sizeof( $content_list ); $i++ ) {
					
						$stat = $content_list[ $i ];
						
						// Assume the key exists (consider testing)
						if ( $stat[ 'filename' ] == $locate_file ) {
						
							// File found so we can note that
							pb_backupbuddy::status( 'details', __('File found (pclzip)','it-l10n-backupbuddy' ) . ': ' . $locate_file );
							$result = true;
							
							// Need to exit the for loop
							break;
							
						}
						
					}
					
					if ( false === $result ) {
					
						// Only get here if the file wasn't found
						pb_backupbuddy::status( 'details', __('File not found (pclzip)','it-l10n-backupbuddy' ) . ': ' . $locate_file );
						
					}

				} else {
				
					// Couldn't open archive - will return for maybe another method to try
					$error_string = $za->errorInfo( true );
					pb_backupbuddy::status( 'details', sprintf( __('pclzip failed to open file to check if file exists (looking for %1$s in %2$s) - Error Info: %3$s.','it-l10n-backupbuddy' ), $locate_file , $zip_file, $error_string ) );

					// Return an error code and a description - this needs to be handled more generically
					$result = array( 1, "Failed to open/process file" );

				}
							
			}
			
		  	if ( NULL != $za ) { unset( $za ); }		
			
			return $result;
				
		}
				
		/*	get_file_list()
		 *	
		 *	Get an array of all files in a zip file with some file properties.
		 *	
		 *	@param		string		$zip_file	The file to list the content of
		 *	@return		bool|array				false on failure, otherwise array of file properties (may be empty)
		 */
		public function get_file_list( $zip_file ) {
		
			$file_list = array();
			$result = false;
			$za = NULL;
			$stat = array();
								
			// This should give us a new archive object, of not catch it and bail out
			try {
					
				$za = new pluginbuddy_PclZip( $zip_file );
				$result = true;
				
			} catch ( Exception $e ) {
			
				// Something fishy - the methods indicated pclzip but we couldn't find the class
				$error_string = $e->getMessage();
				pb_backupbuddy::status( 'details', sprintf( __('pclzip indicated as available method but error reported: %1$s','it-l10n-backupbuddy' ), $error_string ) );
				$result = false;
				
			}
			
			// Only continue if we have a valid archive object
			if ( true === $result ) {
				
				// Make sure we opened the zip ok and it has content
				if ( 0 !== ( $content_list = $za->listContent() ) ) {
				
					// How many files - must be >0 to have got here
					$file_count = sizeof( $content_list );
					
					// Get each file in sequence by index and get the properties
					for ( $i = 0; $i < $file_count; $i++ ) {
					
						$stat = $content_list[ $i ];
						
						// Assume all these keys do exist (consider testing)
						$file_list[] = array(
							$stat[ 'filename' ],
							$stat[ 'size' ],
							$stat[ 'compressed_size' ],
							$stat[ 'mtime' ]
						);
												
					}
					
					pb_backupbuddy::status( 'details', sprintf( __('pclzip listed file contents (%1$s)','it-l10n-backupbuddy' ), $zip_file ) );

					$this->log_archive_file_stats( $zip_file );
					
					$result = &$file_list;
					
				} else {
				
					// Couldn't open archive - will return for maybe another method to try
					$error_string = $za->errorInfo( true );
					pb_backupbuddy::status( 'details', sprintf( __('pclzip failed to open file to list contents (%1$s) - Error Info: %2$s.','it-l10n-backupbuddy' ), $zip_file, $error_string ) );

					// Return an error code and a description - this needs to be handled more generically
					//$result = array( 1, "Unable to get archive contents" );
					// Currently as we are returning an array as a valid result we just return false on failure
					$result = false;

				}
							
			} 
			
		  	if ( NULL != $za ) { unset( $za ); }		
			
			return $result;
				
		}
		
		/*	set_comment()
		 *	
		 *	Retrieve archive comment.
		 *	
		 *	@param		string			$zip_file		Filename of archive to set comment on.
		 *	@param		string			$comment		Comment to apply to archive.
		 *	@return		bool							true on success, otherwise false.
		 */
		public function set_comment( $zip_file, $comment ) {
		
			$result = false;
			$za = NULL;
			
			// This should give us a new archive object, of not catch it and bail out
			try {
					
				$za = new pluginbuddy_PclZip( $zip_file );
				$result = true;
				
			} catch ( Exception $e ) {
			
				// Something fishy - the methods indicated pclzip but we couldn't find the class
				$error_string = $e->getMessage();
				pb_backupbuddy::status( 'details', sprintf( __('pclzip indicated as available method but error reported: %1$s','it-l10n-backupbuddy' ), $error_string ) );
				$result = false;
				
			}
			
			// Only continue if we have a valid archive object
			if ( true === $result ) {
				
				// Make sure we opened the zip ok and we added the comment ok
				// Note: using empty array as we don't actually want to add any files
				if ( 0 !== ( $list = $za->add( array(), PCLZIP_OPT_COMMENT, $comment ) ) ) {
				
					// We got a list back so adding comment should have been successful
					pb_backupbuddy::status( 'details', sprintf( __('PclZip set comment in file %1$s','it-l10n-backupbuddy' ), $zip_file ) );
					$result = true;
					
				} else {
				
					// If we failed to set the commnent then log it (?) and drop through
					$error_string = $za->errorInfo( true );
					pb_backupbuddy::status( 'details', sprintf( __('PclZip failed to set comment in file %1$s - Error Info: %2$s','it-l10n-backupbuddy' ), $zip_file, $error_string ) );
					$result = false;
										
				}
			
			}
			
		  	if ( NULL != $za ) { unset( $za ); }		
			
			return $result;
				
		}

		/*	get_comment()
		 *	
		 *	Retrieve archive comment.
		 *	
		 *	@param		string		$zip_file		Filename of archive to retrieve comment from.
		 *	@return		bool|string					false on failure, Zip comment otherwise.
		 */
		public function get_comment( $zip_file ) {
		
			$result = false;
			$za = NULL;
			
			// This should give us a new archive object, of not catch it and bail out
			try {
					
				$za = new pluginbuddy_PclZip( $zip_file );
				$result = true;
				
			} catch ( Exception $e ) {
			
				// Something fishy - the methods indicated pclzip but we couldn't find the class
				$error_string = $e->getMessage();
				pb_backupbuddy::status( 'details', sprintf( __('pclzip indicated as available method but error reported: %1$s','it-l10n-backupbuddy' ), $error_string ) );
				$result = false;
				
			}
			
			// Only continue if we have a valid archive object
			if ( true === $result ) {
				
				// Make sure we opened the zip ok and it has properties
				if ( 0 !== ( $properties = $za->properties() ) ) {
				
					// We got properties so should have a comment to return, even if empty
					pb_backupbuddy::status( 'details', sprintf( __('PclZip retrieved comment in file %1$s','it-l10n-backupbuddy' ), $zip_file ) );
					$result = $properties[ 'comment' ];
					
				} else {
				
					// If we failed to get the commnent then log it (?) and drop through
					$error_string = $za->errorInfo( true );
					pb_backupbuddy::status( 'details', sprintf( __('PclZip failed to retrieve comment in file %1$s - Error Info: %2$s','it-l10n-backupbuddy' ), $zip_file, $error_string ) );
					$result = false;
					
				}
				
			
			}
			
		  	if ( NULL != $za ) { unset( $za ); }		
			
			return $result;
				
		}
		
	} // end pluginbuddy_zbzippclzip class.	
	
}
?>
