Your IP : 216.73.216.171


Current Path : /home/ereika83/public_html/wp-content/plugins/snapshot/lib/Snapshot/Helper/
Upload File :
Current File : /home/ereika83/public_html/wp-content/plugins/snapshot/lib/Snapshot/Helper/Backup.php

<?php // phpcs:ignore

/**
 * Full backup creation helper
 */
class Snapshot_Helper_Backup {

	const FINAL_PREFIX = 'full_backup';

	/**
	 * Holds current step numeric reference
	 *
	 * @var int
	 */
	private $_current_step = 0;

	/**
	 * Estimated runtime, in total steps
	 *
	 * @var int
	 */
	private $_estimate = 0;

	/**
	 * Internal errors reference
	 *
	 * @var array
	 */
	private $_errors;

	/**
	 * Internal blog ID reference
	 *
	 * @var number
	 */
	private $_blog_id;

	/**
	 * Internal queues reference
	 *
	 * @var array
	 */
	private $_queues = array();

	/**
	 * Internal backup index reference
	 *
	 * Used for backup resolution
	 *
	 * @var string
	 */
	private $_idx;

	/**
	 * Internal backup timestamp reference
	 *
	 * @var int
	 */
	private $_timestamp;


	/**
	 * Filesize of zip before validation for error
	 *
	 * @var int
	 */
	private $_existing_zip_size = 0;


	/**
	 * Number of processed files before validation
	 *
	 * @var int
	 *
	 */
	private $_num_processed_files = 0;

	/**
	 * Creates the backup helper instance
	 */
	public function __construct() {}

	/**
	 * Gets the current blog ID
	 *
	 * @return int
	 */
	public function get_blog_id() {
		return (int) $this->_blog_id;
	}

	/**
	 * Sets the current blog ID
	 *
	 * @param int $blog_id Blog ID.
	 */
	public function set_blog_id( $blog_id = null ) {
		$this->_blog_id = $blog_id;
		return $this->_blog_id;
	}

	/**
	 * Create a backup location
	 * Can be called multiple times, won't re-create the location
	 *
	 * @param string $idx End location.
	 *
	 * @return bool
	 */
	public function create( $idx ) {
		$status = false; // Start with assumed failure.

		if ( ! preg_match( '/^[-_a-z0-9]+$/', $idx ) ) {
			return $this->_set_error( sprintf( __( 'Invalid destination: %s', SNAPSHOT_I18N_DOMAIN ), $idx ) );
		}

		$path = $this->resolve_backup( $idx );
		if ( empty( $path ) ) {
			return $status;
		}

		$this->_idx = $idx;
		$this->_timestamp = time();

		$status = true;
		return $status;
	}

	/**
	 * Add a queue to current backup
	 *
	 * @param Snapshot_Model_Queue $queue Queue instance.
	 *
	 * @return bool
	 */
	public function add_queue( $queue ) {
		if ( ! ($queue instanceof Snapshot_Model_Queue) ) {
			return false;
		}
		$this->_queues[] = $queue;
		return true;
	}

	/**
	 * Loads the session instance
	 *
	 * @param string $idx Index of the session to load.
	 *
	 * @return Snapshot_Helper_Session session instance
	 */
	public static function get_session( $idx ) {
		$loc = trailingslashit( WPMUDEVSnapshot::instance()->get_setting( 'backupSessionFolderFull' ) );
		$session = new Snapshot_Helper_Session( $loc, Snapshot_Helper_String::conceal( "backup_{$idx}" ) );
		return $session;
	}

	/**
	 * Check if a given archive name is a full backup
	 *
	 * @param string $filename Filename to check.
	 * @param int    $timestamp Optional timestamp.
	 *
	 * @return bool
	 */
	public static function is_full_backup( $filename, $timestamp = false ) {
		$timestamp = (int) $timestamp;
		if ( empty( $timestamp ) ) {
			$timestamp = '[0-9]+';
		}
		return (bool) preg_match(
			'/' .
				preg_quote( self::FINAL_PREFIX, '/' ) . '-' . $timestamp . '-(full|automated)-[A-Za-z0-9]+\.zip$' .
			'/',
			$filename
		);
	}

	/**
	 * Checks if a given archive name is a full *and* automated backup
	 *
	 * @param string $filename Filename to check.
	 * @param int    $timestamp Optional timestamp.
	 *
	 * @return bool
	 */
	public static function is_automated_backup( $filename, $timestamp = false ) {
		if ( ! self::is_full_backup( $filename, $timestamp ) ) {
			return false;
		}

		return (bool) preg_match( '/-automated-/', $filename );
	}

	/**
	 * Saves backup progress indicators
	 *
	 * This is achieved by saving current state in the session
	 *
	 * @return bool
	 */
	public function save() {
		$session = self::get_session( $this->_idx );
		$session->data = array(
			'queues' => $this->get_queues(),
			'timestamp' => $this->_timestamp,
			'current_step' => $this->_current_step,
		);
		if ( ! empty( $this->_estimate ) ) {
			$session->data['total_estimate'] = $this->_estimate;
		}

		//To track the zip size error
		$session->data['num_processed_files'] = $this->_num_processed_files;
		$session->data['existing_zip_size'] = $this->_existing_zip_size;

		return $session->save_session();
	}

	/**
	 * Loads backup state from session
	 *
	 * @param string $idx Backup index to load.
	 *
	 * @return Snapshot_Helper_Backup|bool instance on success, (bool)false on failure
	 */
	public static function load( $idx ) {
		$session = self::get_session( $idx );
		$session->load_session();
		if ( empty( $session->data ) || ! is_array( $session->data ) ) {
			return false;
		}

		$me = new self();
		$me->create( $idx );

		$queues = ! empty( $session->data['queues'] ) && is_array( $session->data['queues'] )
			? $session->data['queues']
			: array()
		;
		if ( empty( $queues ) ) {
			return false;
		}

		foreach ( $queues as $item ) {
			if ( empty( $item['type'] ) || empty( $item['sources'] ) ) {
				continue;
			}
			$class = 'Snapshot_Model_Queue_' . ucfirst( $item['type'] );
			$queue = new $class($idx);
			foreach ( $item['sources'] as $source ) {
				$queue->add_source( $source );
			}
			$me->add_queue( $queue );
		}

		$me->_timestamp = ! empty( $session->data['timestamp'] )
			? $session->data['timestamp']
			: time();

		$me->_current_step = ! empty( $session->data['current_step'] ) && is_numeric( $session->data['current_step'] )
			? (int) $session->data['current_step']
			: 0
		;

		$me->_estimate = ! empty( $session->data['total_estimate'] ) && is_numeric( $session->data['total_estimate'] )
			? (int) $session->data['total_estimate']
			: 0
		;

		$me->_num_processed_files =  ! empty( $session->data['num_processed_files'] ) && is_numeric( $session->data['num_processed_files'] )
			? (int) $session->data['num_processed_files']
			: 0;

		$me->_existing_zip_size =  ! empty( $session->data['existing_zip_size'] ) && is_numeric( $session->data['existing_zip_size'] )
			? (int) $session->data['existing_zip_size']
			: 0;

		return $me;
	}

	/**
	 * Get queues list
	 *
	 * @return array
	 */
	public function get_queues() {
		if ( empty( $this->_queues ) ) {
			return array();
		}

		$result = array();
		foreach ( $this->_queues as $queue ) {
			if ( ! ($queue instanceof Snapshot_Model_Queue) ) {
				continue;
			}
			$result[] = array(
				'type' => strtolower( preg_replace( '/Snapshot_Model_Queue_/', '', get_class( $queue ) ) ),
				'sources' => $queue->get_sources(),
			);
		}
		return $result;
	}

	/**
	 * Estimates the total steps the backup will take to run
	 *
	 * @return int
	 */
	public function get_total_steps_estimate() {
		$size = $this->_estimate;
		if ( ! empty( $size ) ) {
			return $size;
		}

		if ( empty( $this->_queues ) ) {
			return $size;
		}
		if ( $this->will_do_system_backup() ) {
			return 2; // Two steps: FS and DB.
		}
		foreach ( $this->_queues as $queue ) {
			$size += $queue->get_total_steps();
		}

		$this->_estimate = $size;

		return $size;
	}

	/**
	 * Returns current backup status estimate, in percentages
	 *
	 * @return float Percentage, or -1 on missing estimate
	 */
	public function get_current_status_estimate() {
		$total = $this->_estimate;
		if ( empty( $total ) ) {
			$total = $this->get_total_steps_estimate();
			if ( empty( $total ) ) {
				return -1;
			}
		}

		$current = $this->_current_step;
		if ( empty( $current ) ) {
			return 0;
		}

		$status = ($current / $total) * 100;

		return $status;
	}

	/**
	 * Check if the queues backup is done
	 *
	 * @return bool
	 */
	public function is_done() {
		$queues = is_array( $this->_queues ) ? $this->_queues : array();
		foreach ( $queues as $queue ) {
			if ( ! $queue->is_done() ) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Call clear on all queues
	 *
	 * @return bool
	 */
	public function clear() {
		$this->_create_manifest();
		$queues = is_array( $this->_queues ) ? $this->_queues : array();
		foreach ( $queues as $queue ) {
			$queue->clear();
		}
		// And now! clean up ourselves.
		$this->_queues = array();
		$this->save();
		return true;
	}


	/**
	 * Stops and removes currently processing backup
	 *
	 * @return bool
	 */
	public function stop_and_remove() {
		$this->clear(); // Clear all queues.

		// Now drop all intermediate backup files.
		$intermediate_path = $this->get_archive_path( $this->_idx );
		if ( ! is_writable( $intermediate_path ) ) {
			return false;
		}

		return unlink( $intermediate_path );
	}

	/**
	 * Last step in a backup - postprocess the created archive
	 *
	 * @return bool
	 */
	public function postprocess() {
		// Lastly, move archive - first, get the intermediate archive path.
		$intermediate_path = $this->get_archive_path( $this->_idx );
		if ( ! file_exists( $intermediate_path ) ) {
			return false;
		}

		// Next up, create the new filename.
		$destination = $this->get_destination_path();
		if ( empty( $destination ) || file_exists( $destination ) ) {
			return false;
		}

		// Lastly, move it.
		return rename( $intermediate_path, $destination );
	}

	/**
	 * Fetch backup timestamp
	 *
	 * @return int UNIX timestamp
	 */
	public function get_timestamp() {
		return ! empty( $this->_timestamp )
			? (int) $this->_timestamp
			: 0
		;
	}

	/**
	 * Gets the final destination filename.
	 *
	 * @return mixed (string)Destination filename if sanity check successful, (bool)false on if sanity check unsuccessful
	 */
	public function get_destination_filename() {
		static $filename;

		if ( empty( $filename ) ) {
			$intermediate_path = $this->get_archive_path( $this->_idx );
			$type = Snapshot_Controller_Full_Hub::get()->is_doing_automated_backup()
				? 'automated'
				: $this->_idx
			;

			if ( preg_match( '/[^A-Za-z-\w]/', $type ) ) {
				return false;
			}

			$filename = self::FINAL_PREFIX . '-' . $this->get_timestamp() . '-' . $type . '-' . Snapshot_Helper_Utility::get_file_checksum( $intermediate_path ) . '.zip';
		}

		return $filename;
	}

	/**
	 * Get full destination path.
	 *
	 * @return string
	 */
	public function get_destination_path() {
		$filename = $this->get_destination_filename();
		if ( empty( $filename ) ) {
			return false;
		}
		$destination = trailingslashit( WPMUDEVSnapshot::instance()->get_setting( 'backupBaseFolderFull' ) );
		return "{$destination}{$filename}";
	}

	/**
	 * Get full path for a certain backup
	 *
	 * @param string $idx End location.
	 *
	 * @return string Full backup path
	 */
	public function get_path( $idx ) {
		$destination = preg_replace( '/[^-_a-z0-9]/', '', Snapshot_Helper_String::conceal( basename( $idx ) ) );
		if ( empty( $destination ) ) {
			return $this->_set_error( sprintf( __( 'Invalid destination: %s', SNAPSHOT_I18N_DOMAIN ), $idx ) );
		}

		return trailingslashit( WPMUDEVSnapshot::instance()->get_setting( 'backupBackupFolderFull' ) ) . $destination;
	}

	/**
	 * Get archive file name (basename)
	 *
	 * @return string Archive file basename
	 */
	public function get_archive_name() {
		return Snapshot_Helper_String::conceal( 'archive.zip' );
	}

	/**
	 * Archive path getter
	 *
	 * @param string $idx End location.
	 *
	 * @return string Full archive path
	 */
	public function get_archive_path( $idx ) {
		return trailingslashit( $this->get_path( $idx ) ) . $this->get_archive_name();
	}

	/**
	 * Resolve the backup full path
	 * If destination doesn't exist, it will be created
	 *
	 * @param string $idx End location.
	 *
	 * @return mixed (bool)false on failure, (string)full path on success
	 */
	public function resolve_backup( $idx ) {
		$path = $this->get_path( $idx );
		if ( empty( $path ) ) {
			return false;
		}

		if ( file_exists( $path ) ) {
			return true; // Already done, moving on.
		}
		wp_mkdir_p( $path );

		if ( ! file_exists( $path ) ) {
			return $this->_set_error( sprintf( __( 'Backup path creation failed: %s', SNAPSHOT_I18N_DOMAIN ), $path ) );
		}
		if ( ! is_writable( $path ) ) {
			return $this->_set_error( sprintf( __( 'Backup path not writable: %s', SNAPSHOT_I18N_DOMAIN ), $path ) );
		}

		return $path;
	}

	/**
	 * Checks to determine if we're to do backup using syscalls
	 *
	 * @uses SNAPSHOT_ATTEMPT_SYSTEM_BACKUP define state to allow/disallow
	 *
	 * @return bool
	 */
	public function will_do_system_backup() {
		// First up, are we expected to try system backup?
		if ( ! (defined( 'SNAPSHOT_ATTEMPT_SYSTEM_BACKUP' ) && SNAPSHOT_ATTEMPT_SYSTEM_BACKUP) ) {
			return false;
		}
		return $this->supports_system_backup();
	}

	/**
	 * Checks whether we're able to use system tools to perform backup
	 *
	 * @uses SNAPSHOT_NO_SYSTEM_BACKUP define to shortcircuit detection
	 *
	 * @return bool
	 */
	public function supports_system_backup() {
		if ( defined( 'SNAPSHOT_NO_SYSTEM_BACKUP' ) && SNAPSHOT_NO_SYSTEM_BACKUP ) {
			return false;
		}

		if (
			! Snapshot_Helper_System::is_available( 'escapeshellarg' )
			||
			! Snapshot_Helper_System::is_available( 'escapeshellcmd' )
			||
			! Snapshot_Helper_System::is_available( 'exec' )
		) {
			return false;
		}

		return Snapshot_Helper_System::has_command( 'zip' )
			&& Snapshot_Helper_System::has_command( 'ln' )
			&& Snapshot_Helper_System::has_command( 'rm' )
			&& Snapshot_Helper_System::has_command( 'mysqldump' );
	}

	/**
	 * Process the entire files queue, working directly with archive
	 *
	 * @return bool
	 */
	public function process_files() {
		if ( $this->will_do_system_backup() ) {
			return $this->process_archive_system();
		} else {
			Snapshot_Helper_Log::warn( 'Unable to perform requested system backup, proceeding with builtin' );
			// Carry on as normal...
		}

		$path = $this->get_archive_path( $this->_idx );

		$zip = Snapshot_Helper_Zip::get( $path );
		$queues = is_array( $this->_queues ) ? $this->_queues : array();
		$files = array();
		$status = false;

		foreach ( $queues as $queue ) {
			if ( $queue->is_done() ) {
				continue;
			}

			/**
			 * Validates Zip file size
			 */
			if( $queue instanceof Snapshot_Model_Queue_Fileset ) {
				$backup_validation = $this->validate_backup_zip_size( $queue, $path );
				if (empty($backup_validation)) {
					return $this->_set_error( __( 'New backup zip size is smaller than the existing zip size', SNAPSHOT_I18N_DOMAIN ) );

				}
			}

			$current_source = $queue->get_current_source();

			$status = true; // We still have a queue to process.
			$files = $queue->get_files();

			$zip->set_root( $queue->get_root() );
			$prefix = $queue->get_prefix();

			$next_source = $queue->get_current_source();

			if ( ! empty( $files ) ) {
				$status = $zip->add( $files, $prefix );
			} elseif ( ! empty( $current_source['chunk'] ) && ! empty( $next_source['chunk'] ) ) {
				$status = $current_source['chunk'] !== $next_source['chunk'];
			} else {
				$status = false;
			}

			break;
		}

		if ( $status ) {
			$this->_current_step++;
		}

		return $status;
	}

	/**
	 * Iterates through queues and processes them through syscalls
	 *
	 * @return bool
	 */
	public function process_archive_system() {
		Snapshot_Helper_Log::info( 'Attempting system backup' );

		// This will potentially take a while, so let's extend the time we're allowed to run.
		if ( ! function_exists( 'ini_get' ) ) {
			Snapshot_Helper_Log::warn( 'Could not probe for safe mode' );
		} else {
			if ( ! ! ini_get( 'safe_mode' ) ) {
				Snapshot_Helper_Log::warn( 'Safe mode is on, will not attempt extending exec time' );
			} else {
				if ( ! function_exists( 'set_time_limit' ) ) {
					Snapshot_Helper_Log::warn( 'Unable to extend exec time limit' );
				} elseif ( set_time_limit( 0 ) ) {
					Snapshot_Helper_Log::info( 'Removed exec time limit constraint' );
				} else {
					Snapshot_Helper_Log::warn( 'Unable to remove exec time constraint' );
				}
			}
		}

		$queues = is_array( $this->_queues ) ? $this->_queues : array();
		$status = false;

		foreach ( $queues as $queue ) {
			if ( $queue->is_done() ) {
				continue;
			}

			if ( $queue instanceof Snapshot_Model_Queue_Fileset ) {
				$status = $this->system_process_files( $queue );
				if ( $status ) {
					$queue->set_done();
					$this->_current_step++;
				}
				break;
			}

			if ( $queue instanceof Snapshot_Model_Queue_Tableset ) {
				$status = $this->system_process_tables( $queue );
				if ( $status ) {
					$queue->set_done();
					$this->_current_step++;
				}
				break;
			}
		}

		return $status;
	}

	/**
	 * Renders and packs up tableset from queue using syscalls
	 *
	 * Requires zip and mysqldump system utilities
	 *
	 * @param Snapshot_Model_Queue_Tableset $queue Queue to process.
	 *
	 * @return bool
	 */
	public function system_process_tables( $queue ) {
		if ( ! ($queue instanceof Snapshot_Model_Queue_Tableset) ) {
			return false;
		}

		$tables = $queue->get_sources();
		if ( empty( $tables ) ) {
			return true; // Nothing to do here.
		}
		$dump_path = Snapshot_Helper_System::get_command( 'mysqldump' );
		if ( empty( $dump_path ) ) {
			return false;
		}

		$zip_path = Snapshot_Helper_System::get_command( 'zip' );
		if ( empty( $zip_path ) ) {
			return false;
		}

		$backup_path = escapeshellarg( $this->get_path( $this->_idx ) );
		$db_name = escapeshellcmd( DB_NAME );
		$db_user = escapeshellcmd( DB_USER );
		$db_pass = escapeshellcmd( DB_PASSWORD );

		$connection = '';
		if ( Snapshot_Helper_System::is_socket_connection( DB_HOST ) ) {
			$db_socket = escapeshellcmd( Snapshot_Helper_System::get_raw_db_mode( DB_HOST ) );

			$connection = "--socket={$db_socket}";
		} else {
			$db_host = escapeshellcmd( Snapshot_Helper_System::get_db_host( DB_HOST ) );
			$db_port = escapeshellcmd( Snapshot_Helper_System::get_db_port( DB_HOST ) );

			$connection = "-h{$db_host} -P{$db_port}";
		}

		$include_sqls = array();
		foreach ( $tables as $table ) {
			$table = escapeshellcmd( $table );
			$tblfile = "{$table}.sql";

			// Go to where we need to be first.
			$command = "cd {$backup_path}";
			// Actual mysqldump command string.
			$command .= " && {$dump_path} {$connection} -u{$db_user}";
			if ( ! empty( $db_pass ) ) {
				$command .= " -p{$db_pass}";
			}
			$command .= " {$db_name} {$table} > {$tblfile}";

			$status = Snapshot_Helper_System::run(
				$command,
				sprintf( 'dump table %s to file %s', $table, $tblfile )
			);
			if ( is_wp_error( $status ) ) {
				Snapshot_Helper_System::run(
					"cd {$backup_path} && rm {$tblfile}",
					'clean up'
				); // Attempt cleanup.
			} else {
				$include_sqls[] = $tblfile;
			}
		}

		if ( empty( $include_sqls ) ) {
			Snapshot_Helper_Log::error( 'No table files rendered, nothing to back up' );
			return false;
		}

		// We're here, let's add to the archive.
		$archive = $this->get_archive_name();
		$all_sql_files = join( ' ', $include_sqls );

		$command = "cd {$backup_path}";
		$command .= " && {$zip_path} {$archive} {$all_sql_files}";
		$command .= " && rm {$all_sql_files}";

		$status = Snapshot_Helper_System::run( $command, 'back up rendered SQL files' );
		if ( is_wp_error( $status ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Packs up fileset from queue using syscalls
	 *
	 * Requires zip and ln system utilities
	 *
	 * @uses SNAPSHOT_SYSTEM_ZIP_ONLY define to force alternative zip-only based workflow
	 *
	 * @param Snapshot_Model_Queue_Fileset $queue Queue to process.
	 *
	 * @return bool
	 */
	public function system_process_files( $queue ) {
		if ( ! ($queue instanceof Snapshot_Model_Queue_Fileset) ) {
			return false;
		}

		$src = $queue->get_current_source_type();
		$source = Snapshot_Model_Fileset::get_source( $src );

		$zip_path = Snapshot_Helper_System::get_command( 'zip' );
		if ( empty( $zip_path ) ) {
			return false;
		}

		$ln_path = Snapshot_Helper_System::get_command( 'ln' );
		if ( empty( $ln_path ) ) {
			return false;
		}

		$find_path = Snapshot_Helper_System::get_command( 'find' );
		$has_find = ! empty( $find_path ) && ! (defined( 'SNAPSHOT_SYSTEM_ZIP_ONLY' ) && SNAPSHOT_SYSTEM_ZIP_ONLY);

		$backup_path = escapeshellarg( $this->get_path( $this->_idx ) );
		$source_root = escapeshellarg( $source->get_root() );
		$target_prefix = escapeshellarg( $queue->get_prefix() );
		$archive = $this->get_archive_name();
		$exclusions = $this->get_system_exclusion_paths( $source, $has_find );

		// Start by cleaning up everything.
		$command = "cd {$backup_path} && rm -f ./*";
		// Add symbolic link to target prefix directory.
		// This is to force processed files into proper zipped structure.
		$command .= " && ln -s {$source_root} {$target_prefix}";
		// Actual zip command.
		if ( $has_find ) {
			Snapshot_Helper_Log::info( 'Attempt backing up everything using find (quicker)' );
			$max_size = Snapshot_Model_Queue_Fileset::get_size_threshold();
			$command .= " && {$find_path} -L www";
			if ( ! empty( $exclusions ) ) {
				$command .= ' ' . join( ' ', $exclusions );
			}
			if ( ! empty( $max_size ) ) {
				$command .= ' -not \( -type f -size +' . $max_size . 'c -prune \)';
			}
			$command .= ' -print';
			$command .= " | {$zip_path} {$archive} -@";
		} else {
			// No find - process files via zip utility.
			// This can take _a while_ to work through.
			$command .= " && {$zip_path} -r {$archive} .";
			if ( ! empty( $exclusions ) ) {
				$command .= ' ' . join( ' ', $exclusions );
			}
		}
		// Clean up - remove symlink.
		$command .= " && rm {$target_prefix}";

		$status = Snapshot_Helper_System::run( $command, 'clean up initially and back up files' );
		if ( is_wp_error( $status ) ) {
			Snapshot_Helper_System::run(
				"cd {$backup_path} && rm -f ./*",
				'clean up'
			); // Attempt cleanup.
			return false;
		}

		return true;
	}

	/**
	 * Gets exclusion paths converted to be source-relative
	 *
	 * Used for system files processing (i.e. zip utility call)
	 *
	 * @param Snapshot_Model_Fileset $source Instance object.
	 * @param bool                   $format Optional format parameter - (bool)true for find, (bool)false for zip.
	 *
	 * @return array
	 */
	public function get_system_exclusion_paths( $source, $format = false ) {
		$raw_exclusions = Snapshot_Model_Fileset::get_excluded_paths( $format );
		$exclusions = array();

		foreach ( $raw_exclusions as $excl ) {
			$excl = preg_replace(
				'/' . preg_quote( trailingslashit( $source->get_root() ), '/' ) . '/',
				'',
				$excl
			);
			if ( empty( $excl ) ) {
				continue;
			}
			$excl = is_file( path_join( $source->get_root(), $excl ) ) ? $excl : trailingslashit( $excl );
			$exclusions[] = $format
				? '-not \( -path \'*' . escapeshellcmd( $excl ) . '*\' -prune \)'
				: '-x ' . escapeshellarg( '*' . trailingslashit( $excl ) . '*' );
		}
		return $exclusions;
	}

	/**
	 * Gets the first undone queue
	 *
	 * @return object|false Snapshot_Model_Queue instance, or false
	 */
	public function get_current_queue() {
		$queues = is_array( $this->_queues ) ? $this->_queues : array();
		foreach ( $queues as $queue ) {
			if ( $queue->is_done() ) {
				continue;
			}

			return $queue;
		}
		return false;
	}

	/**
	 * Get all recorded errors.
	 *
	 * @return array All errors encountered this far
	 */
	public function errors() {
		return $this->_errors;
	}

	/**
	 * Get manifest object for this backup
	 *
	 * @return object Snapshot_Model_Manifest instance
	 */
	public function get_manifest() {
		static $manifest;
		if ( empty( $manifest ) ) {
			$manifest = Snapshot_Model_Manifest::create( $this );
		}

		return $manifest;
	}

	/**
	 * Create a manifest file for this backup
	 *
	 * @param string $path Path to create the file in.
	 *
	 * @return string File path
	 */
	public function create_manifest_file( $path = '' ) {
		$manifest = $this->get_manifest();
		$path = ! empty( $path ) ? $path : trailingslashit( $this->get_path( $this->_idx ) );
		$file = $path . Snapshot_Model_Manifest::get_file_name();

		file_put_contents( $file, $manifest->get_flat() ); //phpcs:ignore

		return $file;
	}

	/**
	 * Creates and packs manifest file
	 *
	 * @return bool
	 */
	private function _create_manifest() {
		$path = trailingslashit( $this->get_path( $this->_idx ) );
		$archive = $this->get_archive_path( $this->_idx );
		$zip = Snapshot_Helper_Zip::get( $archive );

		$file = $this->create_manifest_file( $path );
		if ( ! file_exists( $file ) ) {
			return false;
		}

		$zip->set_root( $path );
		$status = $zip->add( array( $file ) );

		if ( is_writable( $file ) ) {
			unlink( $file );
		}

		return $status;
	}

	/**
	 * Add error to the queue
	 *
	 * @param string $string Error message.
	 */
	private function _set_error( $string ) {
		$this->_errors[] = $string;
		return false;
	}

	/**
	 * Validates the Backup Zip file size for any corruption
	 *
	 * @param $queue
	 * @param $path
	 *
	 * @return bool|null
	 */
	public function validate_backup_zip_size( $queue, $path ) {

		if ( ! $queue || ! $path ) {
			return true;
		}

		/**
		 * Constant: SNAPSHOT_DISABLE_ZIP_VALIDATION
		 * Allows to disable zip size validation while creating Backup zip
		 *
		 */
		if( defined('SNAPSHOT_DISABLE_ZIP_VALIDATION') && SNAPSHOT_DISABLE_ZIP_VALIDATION ) {
			return true;
		}

		$chunk_size = $queue->get_chunk_size();

		//Number of files processed since last step
		$count = ( $this->_current_step * $chunk_size ) - $this->_num_processed_files ;

		$file_count = 1000;

		/**
		 * Constant: SNAPSHOT_ZIP_VALIDATION_FILE_COUNT
		 * Allows to change the file count interval, after which zip file is validated for size corruption.
		 *  Greater the better.
		 *
		 */

		if( defined('SNAPSHOT_ZIP_VALIDATION_FILE_COUNT') && SNAPSHOT_ZIP_VALIDATION_FILE_COUNT  ) {
			$file_count = SNAPSHOT_ZIP_VALIDATION_FILE_COUNT;
		}

		//If we have at least n files processed ->
		// Compare zip size with previous size and increase count of total files processed
		if ( $count >= $file_count && file_exists( $path ) ) {

			$current_zip_size = filesize( $path );

			$zip_corrupt = $current_zip_size < $this->_existing_zip_size;

			/**
			 * Filter: snapshot_zip_file_corrupt
			 * Allows to imitate zip file size getting corrupted after new files are added to it
			 *
			 */
			$zip_corrupt = apply_filters( 'snapshot_zip_file_corrupt', $zip_corrupt );

			//Error Out if new zip size is smaller than existing zip
			if ( $zip_corrupt ) {
				Snapshot_Helper_Log::warn( "The backup zip got corrupted between steps." );

				return false;
			}

			$this->_existing_zip_size = $current_zip_size;
			//Total number of files processed, increase only after every >1000 files processed
			$this->_num_processed_files = $this->_current_step * $chunk_size;

		}

		return true;
	}

}