<?php

/**
 * Class in that the BackWPup job runs
 */
class BackWPup_Job {

	const ENCRYPTION_SYMMETRIC = 'symmetric';
	const ENCRYPTION_ASYMMETRIC = 'asymmetric';

	/**
	 * @var array of the job settings
	 */
	public $job = array();

	/**
	 * @var int The timestamp when the job starts
	 */
	public $start_time = 0;

	/**
	 * @var string the logfile
	 */
	public $logfile = '';
	/**
	 * @var array for temp values
	 */
	public $temp = array();
	/**
	 * @var string Folder where is Backup files in
	 */
	public $backup_folder = '';
	/**
	 * @var string the name of the Backup archive file
	 */
	public $backup_file = '';
	/**
	 * @var int The size of the Backup archive file
	 */
	public $backup_filesize = 0;
	/**
	 * @var int PID of script
	 */
	public $pid = 0;
	/**
	 * @var float Timestamp of last update off .running file
	 */
	public $timestamp_last_update = 0;
	/**
	 * @var int Number of warnings
	 */
	public $warnings = 0;
	/**
	 * @var int Number of errors
	 */
	public $errors = 0;
	/**
	 * @var string the last log notice message
	 */
	public $lastmsg = '';
	/**
	 * @var string the last log error/waring message
	 */
	public $lasterrormsg = '';
	/**
	 * @var array of steps to do
	 */
	public $steps_todo = array( 'CREATE' );
	/**
	 * @var array of done steps
	 */
	public $steps_done = array();
	/**
	 * @var array  of steps data
	 */
	public $steps_data = array();
	/**
	 * @var string working on step
	 */
	public $step_working = 'CREATE';
	/**
	 * @var int Number of sub steps must do in step
	 */
	public $substeps_todo = 0;
	/**
	 * @var int Number of sub steps done in step
	 */
	public $substeps_done = 0;
	/**
	 * @var int Percent of steps done
	 */
	public $step_percent = 1;
	/**
	 * @var int Percent of sub steps done
	 */
	public $substep_percent = 1;
	/**
	 * @var array of files to additional to backup
	 */
	public $additional_files_to_backup = array();
	/**
	 * @var array of files/folder to exclude from backup
	 */
	public $exclude_from_backup = array();
	/**
	 * @var int count of affected files
	 */
	public $count_files = 0;
	/**
	 * @var int count of affected file sizes
	 */
	public $count_files_size = 0;
	/**
	 * @var int count of affected folders
	 */
	public $count_folder = 0;
	/**
	 * If job aborted from user
	 *
	 * @var bool
	 */
	public $user_abort = false;
	/**
	 * A uniqid ID uniqid('', true); to identify process
	 *
	 * @var string
	 */
	public $uniqid = '';
	/**
	 * @var float Timestamp of script start
	 */
	private $timestamp_script_start = 0;
	/**
	 * Stores data that will only used in a single run
	 *
	 * @var array
	 */
	private $run = array();
	/**
	 * @var string logging level (normal|normal_untranslated|debug|debug_untranslated)
	 */
	private $log_level = 'normal';

	/**
	 * @var int Signal of signal handler
	 */
	private $signal = 0;

	public static function start_http( $starttype, $jobid = 0 ) {

		//load text domain
		$log_level = get_site_option( 'backwpup_cfg_loglevel', 'normal_translated' );
		if ( strstr( $log_level, 'translated' ) ) {
			BackWPup::load_text_domain();
		} else {
			add_filter( 'override_load_textdomain', '__return_true' );
			$GLOBALS['l10n'] = array();
		}
		if ( $starttype !== 'restart' ) {

			//check job id exists
			if ( (int) $jobid !== (int) BackWPup_Option::get( $jobid, 'jobid' ) ) {
				return false;
			}

			//check folders
			$log_folder = get_site_option( 'backwpup_cfg_logfolder' );
			$folder_message_log = BackWPup_File::check_folder( BackWPup_File::get_absolute_path( $log_folder ) );
			$folder_message_temp = BackWPup_File::check_folder( BackWPup::get_plugin_data( 'TEMP' ), true );
			if ( ! empty( $folder_message_log ) || ! empty( $folder_message_temp ) ) {
				BackWPup_Admin::message( $folder_message_log, true );
				BackWPup_Admin::message( $folder_message_temp, true );

				return false;
			}
		}

		// redirect
		if ( $starttype === 'runnowalt' ) {
			ob_start();
			wp_redirect( add_query_arg( array( 'page' => 'backwpupjobs' ), network_admin_url( 'admin.php' ) ) );
			echo ' ';
			flush();
			if ( $level = ob_get_level() ) {
				for ( $i = 0; $i < $level; $i ++ ) {
					ob_end_clean();
				}
			}
		}

		// Should be preventing doubled running job's on http requests
		$random = mt_rand( 10, 90 ) * 10000;
		usleep( $random );

		// Check running job.
		$backwpup_job_object = self::get_working_data();
		// Start class.
		$starttype_exists = in_array( $starttype, array( 'runnow', 'runnowalt', 'runext', 'cronrun' ), true );
		if ( ! $backwpup_job_object && $starttype_exists && $jobid ) {
			// Schedule restart event.
			wp_schedule_single_event( time() + 60, 'backwpup_cron', array( 'arg' => 'restart' ) );
			// Sstart job.
			$backwpup_job_object = new self();
			$backwpup_job_object->create( $starttype, $jobid );
		}
		if ( $backwpup_job_object ) {
			$backwpup_job_object->run();
		}
	}

	/**
	 *
	 * Get data off a working job
	 *
	 * @return bool|object BackWPup_Job Object or Bool if file not exits
	 */
	public static function get_working_data() {

	    clearstatcache( true, BackWPup::get_plugin_data( 'running_file' ) );

		if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) {
			return false;
		}

		$file_data = file_get_contents( BackWPup::get_plugin_data( 'running_file' ), false, null, 8 );
		if ( empty( $file_data ) ) {
			return false;
		}

		if ( $job_object = unserialize( $file_data ) ) {
			if ( $job_object instanceof BackWPup_Job ) {
				return $job_object;
			}
		}

		return false;

	}

	/**
	 *
	 * This starts or restarts the job working
	 *
	 * @param string $start_type Start types are 'runnow', 'runnowalt', 'cronrun', 'runext', 'runcli'
	 * @param array|int $job_id The id of job of a job to start
	 */
	private function create( $start_type, $job_id = 0 ) {

		global $wpdb;
		/* @var wpdb $wpdb */

		//check startype
		if ( ! in_array( $start_type, array( 'runnow', 'runnowalt', 'cronrun', 'runext', 'runcli' ), true ) ) {
			return;
		}

		if ( $job_id ) {
			$this->job = BackWPup_Option::get_job( $job_id );
		} else {
			return;
		}

		$this->start_time = current_time( 'timestamp' );
		$this->lastmsg = __( 'Starting job', 'backwpup' );
		//set Logfile
		$log_folder = get_site_option( 'backwpup_cfg_logfolder' );
		$log_folder = BackWPup_File::get_absolute_path( $log_folder );
		$this->logfile = $log_folder . 'backwpup_log_' . BackWPup::get_generated_hash( 6 ) . '_' . date( 'Y-m-d_H-i-s',
				current_time( 'timestamp' ) ) . '.html';
		//write settings to job
		BackWPup_Option::update( $this->job['jobid'], 'lastrun', $this->start_time );
		BackWPup_Option::update( $this->job['jobid'], 'logfile', $this->logfile ); //Set current logfile
		BackWPup_Option::update( $this->job['jobid'], 'lastbackupdownloadurl', '' );
		//Set needed job values
		$this->timestamp_last_update = microtime( true );
		$this->exclude_from_backup = explode( ',', trim( $this->job['fileexclude'] ) );
		$this->exclude_from_backup = array_unique( $this->exclude_from_backup );
		//setup job steps
		$this->steps_data['CREATE']['CALLBACK'] = '';
		$this->steps_data['CREATE']['NAME'] = __( 'Job Start', 'backwpup' );
		$this->steps_data['CREATE']['STEP_TRY'] = 0;
		//ADD Job types file
		/* @var $job_type_class BackWPup_JobTypes */
		$job_need_dest = false;
		if ( $job_types = BackWPup::get_job_types() ) {
			foreach ( $job_types as $id => $job_type_class ) {
				if ( in_array( $id, $this->job['type'], true ) && $job_type_class->creates_file() ) {
					$this->steps_todo[] = 'JOB_' . $id;
					$this->steps_data[ 'JOB_' . $id ]['NAME'] = $job_type_class->info['description'];
					$this->steps_data[ 'JOB_' . $id ]['STEP_TRY'] = 0;
					$this->steps_data[ 'JOB_' . $id ]['SAVE_STEP_TRY'] = 0;
					$job_need_dest = true;
				}
			}
		}
		//add destinations and create archive if a job where files to backup
		if ( $job_need_dest ) {
			//Create manifest file
			$this->steps_todo[] = 'CREATE_MANIFEST';
			$this->steps_data['CREATE_MANIFEST']['NAME'] = __( 'Creates manifest file', 'backwpup' );
			$this->steps_data['CREATE_MANIFEST']['STEP_TRY'] = 0;
			$this->steps_data['CREATE_MANIFEST']['SAVE_STEP_TRY'] = 0;
			//Add archive creation and backup filename on backup type archive
			if ( $this->job['backuptype'] == 'archive' ) {
				//get Backup folder if destination folder set
				if ( in_array( 'FOLDER', $this->job['destinations'], true ) ) {
					$this->backup_folder = $this->job['backupdir'];
					//check backup folder
					if ( ! empty( $this->backup_folder ) ) {
						$this->backup_folder = BackWPup_File::get_absolute_path( $this->backup_folder );
						$this->job['backupdir'] = $this->backup_folder;
					}
				}
				//set temp folder to backup folder if not set because we need one
				if ( ! $this->backup_folder || $this->backup_folder == '/' ) {
					$this->backup_folder = BackWPup::get_plugin_data( 'TEMP' );
				}
				//Create backup archive full file name
				$this->backup_file = $this->generate_filename( $this->job['archivename'], $this->job['archiveformat'] );
				//add archive create
				$this->steps_todo[] = 'CREATE_ARCHIVE';
				$this->steps_data['CREATE_ARCHIVE']['NAME'] = __( 'Creates archive', 'backwpup' );
				$this->steps_data['CREATE_ARCHIVE']['STEP_TRY'] = 0;
				$this->steps_data['CREATE_ARCHIVE']['SAVE_STEP_TRY'] = 0;
				// Encrypt archive
				if ( BackWPup_Option::get( $this->job['jobid'], 'archiveencryption' ) ) {
					$this->steps_todo[] = 'ENCRYPT_ARCHIVE';
					$this->steps_data['ENCRYPT_ARCHIVE']['NAME'] = __( 'Encrypts the archive', 'backwpup' );
					$this->steps_data['ENCRYPT_ARCHIVE']['STEP_TRY'] = 0;
					$this->steps_data['ENCRYPT_ARCHIVE']['SAVE_STEP_TRY'] = 0;
				}
			}
			//ADD Destinations
			/* @var BackWPup_Destinations $dest_class */
			foreach ( BackWPup::get_registered_destinations() as $id => $dest ) {
				if ( ! in_array( $id, $this->job['destinations'], true ) || empty( $dest['class'] ) ) {
					continue;
				}
				$dest_class = BackWPup::get_destination( $id );
				if ( $dest_class->can_run( $this->job ) ) {
					if ( $this->job['backuptype'] == 'sync' ) {
						if ( $dest['can_sync'] ) {
							$this->steps_todo[] = 'DEST_SYNC_' . $id;
							$this->steps_data[ 'DEST_SYNC_' . $id ]['NAME'] = $dest['info']['description'];
							$this->steps_data[ 'DEST_SYNC_' . $id ]['STEP_TRY'] = 0;
							$this->steps_data[ 'DEST_SYNC_' . $id ]['SAVE_STEP_TRY'] = 0;
						}
					} else {
						$this->steps_todo[] = 'DEST_' . $id;
						$this->steps_data[ 'DEST_' . $id ]['NAME'] = $dest['info']['description'];
						$this->steps_data[ 'DEST_' . $id ]['STEP_TRY'] = 0;
						$this->steps_data[ 'DEST_' . $id ]['SAVE_STEP_TRY'] = 0;
					}
				}
			}
		}
		//ADD Job type no file
		if ( $job_types = BackWPup::get_job_types() ) {
			foreach ( $job_types as $id => $job_type_class ) {
				if ( in_array( $id, $this->job['type'], true ) && ! $job_type_class->creates_file() ) {
					$this->steps_todo[] = 'JOB_' . $id;
					$this->steps_data[ 'JOB_' . $id ]['NAME'] = $job_type_class->info['description'];
					$this->steps_data[ 'JOB_' . $id ]['STEP_TRY'] = 0;
					$this->steps_data[ 'JOB_' . $id ]['SAVE_STEP_TRY'] = 0;
				}
			}
		}
		$this->steps_todo[] = 'END';
		$this->steps_data['END']['NAME'] = __( 'End of Job', 'backwpup' );
		$this->steps_data['END']['STEP_TRY'] = 1;
		//must write working data
		$this->write_running_file();

		//set log level
		$this->log_level = get_site_option( 'backwpup_cfg_loglevel', 'normal_translated' );
		if ( ! in_array( $this->log_level,
			array(
				'normal_translated',
				'normal',
				'debug_translated',
				'debug',
			),
			true ) ) {
			$this->log_level = 'normal_translated';
		}
		//create log file
		$head = '';
		$info = '';
		$head .= "<!DOCTYPE html>" . PHP_EOL;
		$head .= "<html lang=\"" . str_replace( '_', '-', get_locale() ) . "\">" . PHP_EOL;
		$head .= "<head>" . PHP_EOL;
		$head .= "<meta charset=\"" . get_bloginfo( 'charset' ) . "\" />" . PHP_EOL;
		$head .= "<title>" . sprintf( __( 'BackWPup log for %1$s from %2$s at %3$s', 'backwpup' ),
				$this->job['name'],
				date_i18n( get_option( 'date_format' ) ),
				date_i18n( get_option( 'time_format' ) ) ) . "</title>" . PHP_EOL;
		$head .= "<meta name=\"robots\" content=\"noindex, nofollow\" />" . PHP_EOL;
		$head .= "<meta name=\"copyright\" content=\"Copyright &copy; 2012 - " . date( 'Y' ) . " Inpsyde GmbH\" />" . PHP_EOL;
		$head .= "<meta name=\"author\" content=\"Inpsyde GmbH\" />" . PHP_EOL;
		$head .= "<meta name=\"generator\" content=\"BackWPup " . BackWPup::get_plugin_data( 'Version' ) . "\" />" . PHP_EOL;
		$head .= "<meta http-equiv=\"cache-control\" content=\"no-cache\" />" . PHP_EOL;
		$head .= "<meta http-equiv=\"pragma\" content=\"no-cache\" />" . PHP_EOL;
		$head .= "<meta name=\"date\" content=\"" . date( 'c' ) . "\" />" . PHP_EOL;
		$head .= str_pad( '<meta name="backwpup_errors" content="0" />', 100 ) . PHP_EOL;
		$head .= str_pad( '<meta name="backwpup_warnings" content="0" />', 100 ) . PHP_EOL;
		$head .= "<meta name=\"backwpup_jobid\" content=\"" . $this->job['jobid'] . "\" />" . PHP_EOL;
		$head .= "<meta name=\"backwpup_jobname\" content=\"" . esc_attr( $this->job['name'] ) . "\" />" . PHP_EOL;
		$head .= "<meta name=\"backwpup_jobtype\" content=\"" . implode( '+', $this->job['type'] ) . "\" />" . PHP_EOL;
		$head .= str_pad( '<meta name="backwpup_backupfilesize" content="0" />', 100 ) . PHP_EOL;
		$head .= str_pad( '<meta name="backwpup_jobruntime" content="0" />', 100 ) . PHP_EOL;
		$head .= '</head>' . PHP_EOL;
		$head .= '<body style="margin:0;padding:3px;font-family:monospace;font-size:12px;line-height:15px;background-color:black;color:#c0c0c0;white-space:nowrap;">' . PHP_EOL;
		$info .= sprintf( _x( '[INFO] %1$s %2$s; A project of Inpsyde GmbH',
				'Plugin name; Plugin Version; plugin url',
				'backwpup' ),
				BackWPup::get_plugin_data( 'name' ),
				BackWPup::get_plugin_data( 'Version' ),
				__( 'http://backwpup.com', 'backwpup' ) ) . '<br />' . PHP_EOL;
		$info .= sprintf( _x( '[INFO] WordPress %1$s on %2$s', 'WordPress Version; Blog url', 'backwpup' ),
				BackWPup::get_plugin_data( 'wp_version' ),
				esc_attr( site_url( '/' ) ) ) . '<br />' . PHP_EOL;
		$level = __( 'Normal', 'backwpup' );
		$translated = '';
		if ( $this->is_debug() ) {
			$level = __( 'Debug', 'backwpup' );
		}
		if ( is_textdomain_loaded( 'backwpup' ) ) {
			$translated = __( '(translated)', 'backwpup' );
		}
		$info .= sprintf( __( '[INFO] Log Level: %1$s %2$s', 'backwpup' ), $level, $translated ) . '<br />' . PHP_EOL;
		$job_name = esc_attr( $this->job['name'] );
		if ( $this->is_debug() ) {
			$job_name .= '; ' . implode( '+', $this->job['type'] );
		}
		$info .= sprintf( __( '[INFO] BackWPup job: %1$s', 'backwpup' ), $job_name ) . '<br />' . PHP_EOL;
		if ( $this->is_debug() ) {
			$current_user = wp_get_current_user();
			$info .= sprintf( __( '[INFO] Runs with user: %1$s (%2$d) ', 'backwpup' ),
					$current_user->user_login,
					$current_user->ID ) . '<br />' . PHP_EOL;
		}
		if ( $this->job['activetype'] === 'wpcron' ) {
			//check next run
			$cron_next = wp_next_scheduled( 'backwpup_cron', array( 'arg' => $this->job['jobid'] ) );
			if ( ! $cron_next || $cron_next < time() ) {
				wp_unschedule_event( $cron_next, 'backwpup_cron', array( 'arg' => $this->job['jobid'] ) );
				$cron_next = BackWPup_Cron::cron_next( $this->job['cron'] );
				wp_schedule_single_event( $cron_next, 'backwpup_cron', array( 'arg' => $this->job['jobid'] ) );
				$cron_next = wp_next_scheduled( 'backwpup_cron', array( 'arg' => $this->job['jobid'] ) );
			}
			//output scheduling
			if ( $this->is_debug() ) {
				if ( ! $cron_next ) {
					$cron_next = __( 'Not scheduled!', 'backwpup' );
				} else {
					$cron_next = date_i18n( 'D, j M Y @ H:i',
						$cron_next + ( get_option( 'gmt_offset' ) * 3600 ),
						true );
				}
				$info .= sprintf( __( '[INFO] Cron: %s; Next: %s ', 'backwpup' ),
						$this->job['cron'],
						$cron_next ) . '<br />' . PHP_EOL;
			}
		} elseif ( $this->job['activetype'] == 'link' && $this->is_debug() ) {
			$info .= __( '[INFO] BackWPup job start with link is active', 'backwpup' ) . '<br />' . PHP_EOL;
		} elseif ( $this->job['activetype'] == 'easycron' && $this->is_debug() ) {
			$info .= __( '[INFO] BackWPup job start with EasyCron.com', 'backwpup' ) . '<br />' . PHP_EOL;
			//output scheduling
			if ( $this->is_debug() ) {
				$cron_next = BackWPup_Cron::cron_next( $this->job['cron'] );
				$cron_next = date_i18n( 'D, j M Y @ H:i', $cron_next + ( get_option( 'gmt_offset' ) * 3600 ), true );
				$info .= sprintf( __( '[INFO] Cron: %s; Next: %s ', 'backwpup' ),
						$this->job['cron'],
						$cron_next ) . '<br />' . PHP_EOL;
			}
		} elseif ( $this->is_debug() ) {
			$info .= __( '[INFO] BackWPup no automatic job start configured', 'backwpup' ) . '<br />' . PHP_EOL;
		}
		if ( $this->is_debug() ) {
			if ( $start_type == 'cronrun' ) {
				$info .= __( '[INFO] BackWPup job started from wp-cron', 'backwpup' ) . '<br />' . PHP_EOL;
			} elseif ( $start_type == 'runnow' || $start_type == 'runnowalt' ) {
				$info .= __( '[INFO] BackWPup job started manually', 'backwpup' ) . '<br />' . PHP_EOL;
			} elseif ( $start_type == 'runext' ) {
				$info .= __( '[INFO] BackWPup job started from external url', 'backwpup' ) . '<br />' . PHP_EOL;
			} elseif ( $start_type == 'runcli' ) {
				$info .= __( '[INFO] BackWPup job started form commandline interface',
						'backwpup' ) . '<br />' . PHP_EOL;
			}
			$bit = '';
			if ( PHP_INT_SIZE === 4 ) {
				$bit = ' (32bit)';
			}
			if ( PHP_INT_SIZE === 8 ) {
				$bit = ' (64bit)';
			}
			$info .= __( '[INFO] PHP ver.:',
					'backwpup' ) . ' ' . PHP_VERSION . $bit . '; ' . PHP_SAPI . '; ' . PHP_OS . '<br />' . PHP_EOL;
			$info .= sprintf( __( '[INFO] Maximum PHP script execution time is %1$d seconds', 'backwpup' ),
					ini_get( 'max_execution_time' ) ) . '<br />' . PHP_EOL;
			if ( php_sapi_name() != 'cli' ) {
				$job_max_execution_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' );
				if ( ! empty( $job_max_execution_time ) ) {
					$info .= sprintf( __( '[INFO] Script restart time is configured to %1$d seconds', 'backwpup' ),
							$job_max_execution_time ) . '<br />' . PHP_EOL;
				}
			}
			$info .= sprintf( __( '[INFO] MySQL ver.: %s', 'backwpup' ),
					$wpdb->get_var( "SELECT VERSION() AS version" ) ) . '<br />' . PHP_EOL;
			if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
				$info .= sprintf( __( '[INFO] Web Server: %s', 'backwpup' ),
						$_SERVER['SERVER_SOFTWARE'] ) . '<br />' . PHP_EOL;
			}
			if ( function_exists( 'curl_init' ) ) {
				$curlversion = curl_version();
				$info .= sprintf( __( '[INFO] curl ver.: %1$s; %2$s', 'backwpup' ),
						$curlversion['version'],
						$curlversion['ssl_version'] ) . '<br />' . PHP_EOL;
			}
			$info .= sprintf( __( '[INFO] Temp folder is: %s', 'backwpup' ),
					BackWPup::get_plugin_data( 'TEMP' ) ) . '<br />' . PHP_EOL;
		}
		if ( $this->is_debug() ) {
			$logfile = $this->logfile;
		} else {
			$logfile = basename( $this->logfile );
		}
		$info .= sprintf( __( '[INFO] Logfile is: %s', 'backwpup' ), $logfile ) . '<br />' . PHP_EOL;
		if ( ! empty( $this->backup_file ) && $this->job['backuptype'] === 'archive' ) {
			if ( $this->is_debug() ) {
				$backupfile = $this->backup_folder . $this->backup_file;
			} else {
				$backupfile = $this->backup_file;
			}
			$info .= sprintf( __( '[INFO] Backup file is: %s', 'backwpup' ), $backupfile ) . '<br />' . PHP_EOL;
		} else {
			$info .= sprintf( __( '[INFO] Backup type is: %s', 'backwpup' ),
					$this->job['backuptype'] ) . '<br />' . PHP_EOL;
		}
		//output info on cli
		if ( php_sapi_name() == 'cli' && defined( 'STDOUT' ) ) {
			fwrite( STDOUT, strip_tags( $info ) );
		}
		if ( ! file_put_contents( $this->logfile, $head . $info, FILE_APPEND ) ) {
			$this->logfile = '';
			$this->log( __( 'Could not write log file', 'backwpup' ), E_USER_ERROR );
		}
		//test for destinations
		if ( $job_need_dest ) {
			$desttest = false;
			foreach ( $this->steps_todo as $deststeptest ) {
				if ( substr( $deststeptest, 0, 5 ) == 'DEST_' ) {
					$desttest = true;
					break;
				}
			}
			if ( ! $desttest ) {
				$this->log( __( 'No destination correctly defined for backup! Please correct job settings.',
					'backwpup' ),
					E_USER_ERROR );
				$this->steps_todo = array( 'END' );
			}
		}
		//test backup folder
		if ( ! empty( $this->backup_folder ) ) {
			$folder_message = BackWPup_File::check_folder( $this->backup_folder, true );
			if ( ! empty( $folder_message ) ) {
				$this->log( $folder_message, E_USER_ERROR );
				$this->steps_todo = array( 'END' );
			}
		}

		//Set start as done
		$this->steps_done[] = 'CREATE';
	}

	/**
	 * @param        $name
	 * @param string $suffix
	 * @param bool $delete_temp_file
	 *
	 * @return string
	 */
	public function generate_filename( $name, $suffix = '', $delete_temp_file = true ) {

		if ( $suffix ) {
			$suffix = '.' . trim( $suffix, '. ' );
		}

		$name = BackWPup_Option::substitute_date_vars( $name );
		$name .= $suffix;
		if ( $delete_temp_file && is_writeable( BackWPup::get_plugin_data( 'TEMP' ) . $name ) && ! is_dir( BackWPup::get_plugin_data( 'TEMP' ) . $name ) && ! is_link( BackWPup::get_plugin_data( 'TEMP' ) . $name ) ) {
			unlink( BackWPup::get_plugin_data( 'TEMP' ) . $name );
		}

		return $name;
	}

	/**
	 * Sanitizes a filename, replacing whitespace with underscores.
	 *
	 * @param $filename
	 *
	 * @return mixed
	 */
	public static function sanitize_file_name( $filename ) {

		$filename = trim( $filename );

		$special_chars = array(
			"?",
			"[",
			"]",
			"/",
			"\\",
			"=",
			"<",
			">",
			":",
			";",
			",",
			"'",
			"\"",
			"&",
			"$",
			"#",
			"*",
			"(",
			")",
			"|",
			"~",
			"`",
			"!",
			"{",
			"}",
			chr( 0 ),
		);

		$filename = str_replace( $special_chars, '', $filename );

		$filename = str_replace( array( ' ', '%20', '+' ), '_', $filename );
		$filename = str_replace( array( "\n", "\t", "\r" ), '-', $filename );
		$filename = trim( $filename, '.-_' );

		return $filename;
	}

	private function write_running_file() {

		$clone = clone $this;
		$data = '<?php //' . serialize( $clone );

		$write = file_put_contents( BackWPup::get_plugin_data( 'running_file' ), $data );
		if ( ! $write || $write < strlen( $data ) ) {
			unlink( BackWPup::get_plugin_data( 'running_file' ) );
			$this->log( __( 'Cannot write progress to working file. Job will be aborted.', 'backwpup' ), E_USER_ERROR );
		}
	}

	/**
	 * Write messages to log file
	 *
	 * @param string $message the error message
	 * @param int $type the error number (E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE, ...)
	 * @param string $file the full path of file with error (__FILE__)
	 * @param int $line the line in that is the error (__LINE__)
	 *
	 * @return bool true
	 */
	public function log( $message, $type = E_USER_NOTICE, $file = '', $line = 0 ) {

		// if error has been suppressed with an @
		if ( error_reporting() == 0 ) {
			return true;
		}

		//if first the type an second the message switch it on user errors
		if ( ! is_int( $type ) && is_int( $message ) && in_array( $message,
				array(
					1,
					2,
					4,
					8,
					16,
					32,
					64,
					128,
					256,
					512,
					1024,
					2048,
					4096,
					8192,
					16384,
				),
				true )
		) {
			$temp = $message;
			$message = $type;
			$type = $temp;
		}

		//json message if array or object
		if ( is_array( $message ) || is_object( $message ) ) {
			$message = json_encode( $message );
		}

		//if not set line and file get it
		if ( $this->is_debug() ) {
			if ( empty( $file ) || empty( $line ) ) {
				$debug_info = debug_backtrace();
				$file = $debug_info[0]['file'];
				$line = $debug_info[0]['line'];
			}
		}

		$error = false;
		$warning = false;

		switch ( $type ) {
			case E_NOTICE:
			case E_USER_NOTICE:
				break;
			case E_WARNING:
			case E_CORE_WARNING:
			case E_COMPILE_WARNING:
			case E_USER_WARNING:
				$this->warnings ++;
				$warning = true;
				$message = __( 'WARNING:', 'backwpup' ) . ' ' . $message;
				break;
			case E_ERROR:
			case E_PARSE:
			case E_CORE_ERROR:
			case E_COMPILE_ERROR:
			case E_USER_ERROR:
				$this->errors ++;
				$error = true;
				$message = __( 'ERROR:', 'backwpup' ) . ' ' . $message;
				break;
			case E_DEPRECATED:
			case E_USER_DEPRECATED:
				$message = __( 'DEPRECATED:', 'backwpup' ) . ' ' . $message;
				break;
			case E_STRICT:
				$message = __( 'STRICT NOTICE:', 'backwpup' ) . ' ' . $message;
				break;
			case E_RECOVERABLE_ERROR:
				$this->errors ++;
				$error = true;
				$message = __( 'RECOVERABLE ERROR:', 'backwpup' ) . ' ' . $message;
				break;
			default:
				$message = $type . ': ' . $message;
				break;
		}

		//print message to cli
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			$output_message = str_replace( array( '&hellip;', '&#160;' ), array( '...', ' ' ), esc_html( $message ) );
			if ( ! call_user_func( array( '\cli\Shell', 'isPiped' ) ) ) {
				if ( $error ) {
					$output_message = '%r' . $output_message . '%n';
				}
				if ( $warning ) {
					$output_message = '%y' . $output_message . '%n';
				}
				$output_message = call_user_func( array( '\cli\Colors', 'colorize' ), $output_message, true );
			}
			WP_CLI::line( $output_message );
		} elseif ( php_sapi_name() == 'cli' && defined( 'STDOUT' ) ) {
			$output_message = str_replace( array( '&hellip;', '&#160;' ),
					array(
						'...',
						' ',
					),
					esc_html( $message ) ) . PHP_EOL;
			fwrite( STDOUT, $output_message );
		}

		//timestamp for log file
		$debug_info = '';
		if ( $this->is_debug() ) {
			$debug_info = ' title="[Type: ' . $type . '|Line: ' . $line . '|File: ' . $this->get_destination_path_replacement( $file ) . '|Mem: ' . size_format( @memory_get_usage( true ),
					2 ) . '|Mem Max: ' . size_format( @memory_get_peak_usage( true ),
					2 ) . '|Mem Limit: ' . ini_get( 'memory_limit' ) . '|PID: ' . self::get_pid() . ' | UniqID: ' . $this->uniqid . '|Queries: ' . get_num_queries() . ']"';
		}
		$timestamp = '<span datetime="' . date( 'c' ) . '" ' . $debug_info . '>[' . date( 'd-M-Y H:i:s',
				current_time( 'timestamp' ) ) . ']</span> ';

		//set last Message
		if ( $error ) {
			$output_message = '<span style="background-color:#ff6766;color:black;padding:0 2px;">' . esc_html( $message ) . '</span>';
			$this->lasterrormsg = $output_message;
		} elseif ( $warning ) {
			$output_message = '<span style="background-color:#ffc766;color:black;padding:0 2px;">' . esc_html( $message ) . '</span>';
			$this->lasterrormsg = $output_message;
		} else {
			$output_message = esc_html( $message );
			$this->lastmsg = $output_message;
		}
		//write log file
		if ( $this->logfile ) {
			if ( ! file_put_contents( $this->logfile,
				$timestamp . $output_message . '<br />' . PHP_EOL,
				FILE_APPEND ) ) {
				$this->logfile = '';
				restore_error_handler();
				trigger_error( esc_html( $message ), $type );
			}

			//write new log header
			if ( ( $error || $warning ) && $this->logfile ) {
				if ( $fd = fopen( $this->logfile, 'r+' ) ) {
					$file_pos = ftell( $fd );
					while ( ! feof( $fd ) ) {
						$line = fgets( $fd );
						if ( $error && stripos( $line, '<meta name="backwpup_errors" content="' ) !== false ) {
							fseek( $fd, $file_pos );
							fwrite( $fd,
								str_pad( '<meta name="backwpup_errors" content="' . $this->errors . '" />',
									100 ) . PHP_EOL );
							break;
						}
						if ( $warning && stripos( $line, '<meta name="backwpup_warnings" content="' ) !== false ) {
							fseek( $fd, $file_pos );
							fwrite( $fd,
								str_pad( '<meta name="backwpup_warnings" content="' . $this->warnings . '" />',
									100 ) . PHP_EOL );
							break;
						}
						$file_pos = ftell( $fd );
					}
					fclose( $fd );
				}
			}
		}

		//write working data
		$this->update_working_data( $error || $warning );

		//true for no more php error handling.
		return true;
	}

	/**
	 * Is debug log active
	 *
	 * @return bool
	 */
	public function is_debug() {

		return strstr( $this->log_level, 'debug' ) ? true : false;
	}

	/**
	 * Change path of a given path
	 * for better storing in archives or on sync destinations
	 *
	 * @param $path string path to change to wp default path
	 *
	 * @return string
	 */
	public function get_destination_path_replacement( $path ) {

		$abs_path = realpath( BackWPup_Path_Fixer::fix_path( ABSPATH ) );
		if ( $this->job['backupabsfolderup'] ) {
			$abs_path = dirname( $abs_path );
		}
		$abs_path = trailingslashit( str_replace( '\\', '/', $abs_path ) );

		$path = str_replace( array( '\\', $abs_path ), '/', $path );

		//replace the colon from windows drive letters with so they will not be problems with them in archives or on copying to directory
		if ( 0 === stripos( PHP_OS, 'WIN' ) && 1 === strpos( $path, ':/' ) ) {
		    $path = '/' . substr_replace( $path, '', 1,1 );
        }

		return $path;
	}

	/**
	 * Get the Process id of working script
	 *
	 * @return int
	 */
	private static function get_pid() {

		if ( function_exists( 'posix_getpid' ) ) {

			return posix_getpid();
		} elseif ( function_exists( 'getmypid' ) ) {

			return getmypid();
		}

		return - 1;
	}

	/**
	 *
	 * Write the Working data to display the process or that i can executes again
	 * The write will only done every second
	 *
	 * @param bool $must
	 */
	public function update_working_data( $must = false ) {

		global $wpdb;

		//to reduce server load
		if ( get_site_option( 'backwpup_cfg_jobwaittimems' ) > 0 && get_site_option( 'backwpup_cfg_jobwaittimems' ) <= 500000 ) {
			usleep( get_site_option( 'backwpup_cfg_jobwaittimems' ) );
		}

		//check free memory
		$this->need_free_memory( '10M' );

		//only run every 1 sec.
		$time_to_update = microtime( true ) - $this->timestamp_last_update;
		if ( $time_to_update < 1 && ! $must ) {
			return;
		}

		//FCGI must have a permanent output so that it not broke
		if ( get_site_option( 'backwpup_cfg_jobdooutput' ) && ! defined( 'STDOUT' ) ) {
			echo str_repeat( ' ', 12 );
			flush();
		}

		// check WPDB connection. WP will do it after a query that will cause messages.
		$wpdb->check_connection( false );

		//set execution time again for 5 min
		@set_time_limit( 300 );

		//calc sub step percent
		if ( $this->substeps_todo > 0 && $this->substeps_done > 0 ) {
			$this->substep_percent = min( round( $this->substeps_done / $this->substeps_todo * 100 ), 100 );
		} else {
			$this->substep_percent = 1;
		}

		//check if job aborted
		if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) {
			if ( $this->step_working !== 'END' ) {
				$this->end();
			}
		} else {
			$this->timestamp_last_update = microtime( true ); //last update of working file
			$this->write_running_file();
		}

		if ( $this->signal !== 0 ) {
			$this->do_restart();
		}
	}

	/**
	 *
	 * Increase automatically the memory that is needed
	 *
	 * @param int|string $memneed of the needed memory
	 */
	public function need_free_memory( $memneed ) {

		//need memory
		$needmemory = @memory_get_usage( true ) + self::convert_hr_to_bytes( $memneed );
		// increase Memory
		if ( $needmemory > self::convert_hr_to_bytes( ini_get( 'memory_limit' ) ) ) {
			$newmemory = round( $needmemory / 1024 / 1024 ) + 1 . 'M';
			if ( $needmemory >= 1073741824 ) {
				$newmemory = round( $needmemory / 1024 / 1024 / 1024 ) . 'G';
			}
			@ini_set( 'memory_limit', $newmemory );
		}
	}

	/**
	 *
	 * Converts hr to bytes
	 *
	 * @param $size
	 *
	 * @return int
	 */
	public static function convert_hr_to_bytes( $size ) {

		$size = strtolower( $size );
		$bytes = (int) $size;
		if ( strpos( $size, 'k' ) !== false ) {
			$bytes = intval( $size ) * 1024;
		} elseif ( strpos( $size, 'm' ) !== false ) {
			$bytes = intval( $size ) * 1024 * 1024;
		} elseif ( strpos( $size, 'g' ) !== false ) {
			$bytes = intval( $size ) * 1024 * 1024 * 1024;
		}

		return $bytes;
	}

	/**
	 *
	 * Called on job stop makes cleanup and terminates the script
	 *
	 */
	private function end() {

		$this->step_working = 'END';
		$this->substeps_todo = 1;

		if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) {
			$this->log( __( 'Aborted by user!', 'backwpup' ), E_USER_ERROR );
		}

		//delete old logs
		if ( get_site_option( 'backwpup_cfg_maxlogs' ) ) {
			$log_file_list = array();
			$log_folder = trailingslashit( dirname( $this->logfile ) );
			if ( is_readable( $log_folder ) ) { //make file list
				try {
					$dir = new BackWPup_Directory( $log_folder );

					foreach ( $dir as $file ) {
						if ( ! $file->isDot() && strpos( $file->getFilename(),
								'backwpup_log_' ) === 0 && strpos( $file->getFilename(), '.html' ) !== false ) {
							$log_file_list[ $file->getMTime() ] = clone $file;
						}
					}
				} catch ( UnexpectedValueException $e ) {
					$this->log( sprintf( __( "Could not open path: %s" ), $e->getMessage() ), E_USER_WARNING );
				}
			}
			if ( count( $log_file_list ) > 0 ) {
				krsort( $log_file_list, SORT_NUMERIC );
				$num_delete_files = 0;
				$i = - 1;
				foreach ( $log_file_list as $log_file ) {
					$i ++;
					if ( $i < get_site_option( 'backwpup_cfg_maxlogs' ) ) {
						continue;
					}
					unlink( $log_file->getPathname() );
					$num_delete_files ++;
				}
				if ( $num_delete_files > 0 ) {
					$this->log( sprintf( _n( 'One old log deleted',
						'%d old logs deleted',
						$num_delete_files,
						'backwpup' ),
						$num_delete_files ) );
				}
			}
		}

		//Display job working time
		if ( $this->errors > 0 ) {
			$this->log( sprintf( __( 'Job has ended with errors in %s seconds. You must resolve the errors for correct execution.',
				'backwpup' ),
				current_time( 'timestamp' ) - $this->start_time ),
				E_USER_ERROR );
		} elseif ( $this->warnings > 0 ) {
			$this->log( sprintf( __( 'Job finished with warnings in %s seconds. Please resolve them for correct execution.',
				'backwpup' ),
				current_time( 'timestamp' ) - $this->start_time ),
				E_USER_WARNING );
		} else {
			$this->log( sprintf( __( 'Job done in %s seconds.', 'backwpup' ),
				current_time( 'timestamp' ) - $this->start_time ) );
		}

		//Update job options
		$this->job['lastruntime'] = current_time( 'timestamp' ) - $this->start_time;
		BackWPup_Option::update( $this->job['jobid'], 'lastruntime', $this->job['lastruntime'] );


		//write header info
		if ( ! empty( $this->logfile ) ) {

			if ( $fd = fopen( $this->logfile, 'r+' ) ) {
				$filepos = ftell( $fd );
				$found = 0;
				while ( ! feof( $fd ) ) {
					$line = fgets( $fd );
					if ( stripos( $line, '<meta name="backwpup_jobruntime"' ) !== false ) {
						fseek( $fd, $filepos );
						fwrite( $fd,
							str_pad( '<meta name="backwpup_jobruntime" content="' . $this->job['lastruntime'] . '" />',
								100 ) . PHP_EOL );
						$found ++;
					}
					if ( stripos( $line, '<meta name="backwpup_backupfilesize"' ) !== false ) {
						fseek( $fd, $filepos );
						fwrite( $fd,
							str_pad( '<meta name="backwpup_backupfilesize" content="' . $this->backup_filesize . '" />',
								100 ) . PHP_EOL );
						$found ++;
					}
					if ( $found >= 2 ) {
						break;
					}
					$filepos = ftell( $fd );
				}
				fclose( $fd );
			}

			//Send mail with log
			$sendmail = false;
			if ( $this->job['mailaddresslog'] ) {
				$sendmail = true;
			}
			if ( $this->errors === 0 && $this->job['mailerroronly'] ) {
				$sendmail = false;
			}
			if ( $sendmail ) {
				//special subject
				$status = __( 'SUCCESSFUL', 'backwpup' );
				if ( $this->warnings > 0 ) {
					$status = __( 'WARNING', 'backwpup' );
				}
				if ( $this->errors > 0 ) {
					$status = __( 'ERROR', 'backwpup' );
				}

				$subject = sprintf( __( '[%3$s] BackWPup log %1$s: %2$s', 'backwpup' ),
					date_i18n( 'd-M-Y H:i', $this->start_time, true ),
					esc_attr( $this->job['name'] ),
					$status );
				$headers = array();
				$headers[] = 'Content-Type: text/html; charset=' . get_bloginfo( 'charset' );
				if ( $this->job['mailaddresssenderlog'] ) {
					$this->job['mailaddresssenderlog'] = str_replace( array( '&lt;', '&gt;' ),
						array(
							'<',
							'>',
						),
						$this->job['mailaddresssenderlog'] );

					$bracket_pos = strpos( $this->job['mailaddresssenderlog'], '<' );
					$at_pos = strpos( $this->job['mailaddresssenderlog'], '@' );
					if ( $bracket_pos === false || $at_pos === false ) {
						$this->job['mailaddresssenderlog'] = str_replace( array(
								'<',
								'>',
							),
								'',
								$this->job['mailaddresssenderlog'] ) . ' <' . get_bloginfo( 'admin_email' ) . '>';
					}

					$headers[] = 'From: ' . $this->job['mailaddresssenderlog'];
				}
				wp_mail( $this->job['mailaddresslog'], $subject, file_get_contents( $this->logfile ), $headers );
			}
		}

		//set done
		$this->substeps_done = 1;
		$this->steps_done[] = 'END';

		//clean up temp
		self::clean_temp_folder();

		//remove shutdown action
		remove_action( 'shutdown', array( $this, 'shutdown' ) );
		restore_exception_handler();
		restore_error_handler();

		//logfile end
		file_put_contents( $this->logfile, "</body>" . PHP_EOL . "</html>", FILE_APPEND );

		BackWPup_Cron::check_cleanup();

		exit();
	}

	/**
	 * Cleanup Temp Folder
	 */
	public static function clean_temp_folder() {

		$instance = new self();
		$temp_dir = BackWPup::get_plugin_data( 'TEMP' );
		$do_not_delete_files = array( '.htaccess', 'nginx.conf', 'index.php', '.', '..', '.donotbackup' );

		if ( is_writable( $temp_dir ) ) {
			try {
				$dir = new BackWPup_Directory( $temp_dir );
				foreach ( $dir as $file ) {
					if ( in_array( $file->getFilename(),
							$do_not_delete_files,
							true ) || $file->isDir() || $file->isLink() ) {
						continue;
					}
					if ( $file->isWritable() ) {
						unlink( $file->getPathname() );
					}
				}
			} catch ( UnexpectedValueException $e ) {
				$instance->log( sprintf( __( "Could not open path: %s" ), $e->getMessage() ), E_USER_WARNING );
			}
		}
	}

	/**
	 * Do a job restart
	 *
	 * @param bool $must Restart must done
	 */
	public function do_restart( $must = false ) {

		//restart must done if signal
		if ( $this->signal !== 0 ) {
			$must = true;
		}

		//no restart if in end step
		if ( $this->step_working === 'END' || ( count( $this->steps_done ) + 1 ) >= count( $this->steps_todo ) ) {
			return;
		}

		//no restart on cli usage
		if ( php_sapi_name() == 'cli' ) {
			return;
		}

		//no restart if no restart time configured
		$job_max_execution_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' );
		if ( ! $must && empty( $job_max_execution_time ) ) {
			return;
		}

		//no restart when restart was 3 Seconds before
		$execution_time = microtime( true ) - $this->timestamp_script_start;
		if ( ! $must && $execution_time < 3 ) {
			return;
		}

		//no restart if no working job
		if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) {
			return;
		}

		//print message
		if ( $this->is_debug() ) {
			if ( $execution_time !== 0 ) {
				$this->log( sprintf( __( 'Restart after %1$d seconds.', 'backwpup' ), ceil( $execution_time ) ) );
			} elseif ( $this->signal !== 0 ) {
				$this->log( __( 'Restart after getting signal.', 'backwpup' ) );
			}
		}

		//do things for a clean restart
		$this->pid = 0;
		$this->uniqid = '';
		$this->write_running_file();
		remove_action( 'shutdown', array( $this, 'shutdown' ) );
		//do restart
		wp_clear_scheduled_hook( 'backwpup_cron', array( 'arg' => 'restart' ) );
		wp_schedule_single_event( time() + 5, 'backwpup_cron', array( 'arg' => 'restart' ) );
		self::get_jobrun_url( 'restart' );

		exit();
	}

	/**
	 *
	 * Get a url to run a job of BackWPup
	 *
	 * @param string $starttype Start types are 'runnow', 'runnowlink', 'cronrun', 'runext', 'restart', 'restartalt',
	 *                          'test'
	 * @param int $jobid The id of job to start else 0
	 *
	 * @return array|object [url] is the job url [header] for auth header or object form wp_remote_get()
	 */
	public static function get_jobrun_url( $starttype, $jobid = 0 ) {

		$authentication = get_site_option( 'backwpup_cfg_authentication',
			array(
				'method' => '',
				'basic_user' => '',
				'basic_password' => '',
				'user_id' => 0,
				'query_arg' => '',
			) );
		$url = site_url( 'wp-cron.php' );
		$header = array( 'Cache-Control' => 'no-cache' );
		$authurl = '';
		$query_args = array(
			'_nonce' => substr( wp_hash( wp_nonce_tick() . 'backwpup_job_run-' . $starttype, 'nonce' ), - 12, 10 ),
			'doing_wp_cron' => sprintf( '%.22F', microtime( true ) ),
		);

		if ( in_array( $starttype, array( 'restart', 'runnow', 'cronrun', 'runext', 'test' ), true ) ) {
			$query_args['backwpup_run'] = $starttype;
		}

		if ( in_array( $starttype, array( 'runnowlink', 'runnow', 'cronrun', 'runext' ), true ) && ! empty( $jobid ) ) {
			$query_args['jobid'] = $jobid;
		}

		if ( ! empty( $authentication['basic_user'] ) && ! empty( $authentication['basic_password'] ) && $authentication['method'] == 'basic' ) {
			$header['Authorization'] = 'Basic ' . base64_encode( $authentication['basic_user'] . ':' . BackWPup_Encryption::decrypt( $authentication['basic_password'] ) );
			$authurl = urlencode( $authentication['basic_user'] ) . ':' . urlencode( BackWPup_Encryption::decrypt( $authentication['basic_password'] ) ) . '@';
		}

		if ( ! empty( $authentication['query_arg'] ) && $authentication['method'] == 'query_arg' ) {
			$url .= '?' . $authentication['query_arg'];
		}

		if ( $starttype === 'runext' ) {
			$query_args['_nonce'] = get_site_option( 'backwpup_cfg_jobrunauthkey' );
			$query_args['doing_wp_cron'] = null;
			if ( ! empty( $authurl ) ) {
				$url = str_replace( 'https://', 'https://' . $authurl, $url );
				$url = str_replace( 'http://', 'http://' . $authurl, $url );
			}
		}

		if ( $starttype === 'runnowlink' && ( ! defined( 'ALTERNATE_WP_CRON' ) || ! ALTERNATE_WP_CRON ) ) {
			$url = wp_nonce_url( network_admin_url( 'admin.php' ), 'backwpup_job_run-' . $starttype );
			$query_args['page'] = 'backwpupjobs';
			$query_args['action'] = 'runnow';
			$query_args['doing_wp_cron'] = null;
			unset( $query_args['_nonce'] );
		}

		if ( $starttype === 'runnowlink' && defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
			$query_args['backwpup_run'] = 'runnowalt';
			$query_args['_nonce'] = substr( wp_hash( wp_nonce_tick() . 'backwpup_job_run-runnowalt', 'nonce' ),
				- 12,
				10 );
			$query_args['doing_wp_cron'] = null;
		}

		if ( $starttype === 'restartalt' && defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
			$query_args['backwpup_run'] = 'restart';
			$query_args['_nonce'] = null;
		}

		if ( $starttype === 'restart' || $starttype === 'test' ) {
			$query_args['_nonce'] = null;
		}

		if ( ! empty( $authentication['user_id'] ) && $authentication['method'] === 'user' ) {
			//cache cookies for auth some
			$cookies = get_site_transient( 'backwpup_cookies' );
			if ( empty( $cookies ) ) {
				$wp_admin_user = get_users( array( 'role' => 'administrator', 'number' => 1 ) );
				if ( empty( $wp_admin_user ) ) {
					$wp_admin_user = get_users( array( 'role' => 'backwpup_admin', 'number' => 1 ) );
				}
				if ( ! empty( $wp_admin_user[0]->ID ) ) {
					$expiration = time() + ( 2 * DAY_IN_SECONDS );
					$manager = WP_Session_Tokens::get_instance( $wp_admin_user[0]->ID );
					$token = $manager->create( $expiration );
					$cookies[ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( $wp_admin_user[0]->ID,
						$expiration,
						'logged_in',
						$token );
				}
				set_site_transient( 'backwpup_cookies', $cookies, 2 * DAY_IN_SECONDS );
			}
		} else {
			$cookies = '';
		}

		$cron_request = array(
			'url' => add_query_arg( $query_args, $url ),
			'key' => $query_args['doing_wp_cron'],
			'args' => array(
				'blocking' => false,
				'sslverify' => false,
				'timeout' => 0.01,
				'headers' => $header,
				'user-agent' => BackWPup::get_plugin_data( 'User-Agent' ),
			),
		);

		if ( ! empty( $cookies ) ) {
			foreach ( $cookies as $name => $value ) {
				$cron_request['args']['cookies'][] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value ) );
			}
		}

		$cron_request = apply_filters( 'cron_request', $cron_request );

		if ( $starttype === 'test' ) {
			$cron_request['args']['timeout'] = 15;
			$cron_request['args']['blocking'] = true;
		}

		if ( ! in_array( $starttype, array( 'runnowlink', 'runext', 'restartalt' ), true ) ) {
			delete_transient( 'doing_cron' );

			return wp_remote_post( $cron_request['url'], $cron_request['args'] );
		}

		return $cron_request;
	}

	/**
	 * Run baby run
	 */
	public function run() {

		global $wpdb;
		/* @var wpdb $wpdb */

		//disable output buffering
		if ( $level = ob_get_level() ) {
			for ( $i = 0; $i < $level; $i ++ ) {
				ob_end_clean();
			}
		}

		// Job can't run it is not created
		if ( empty( $this->steps_todo ) || empty( $this->logfile ) ) {
			$running_file = BackWPup::get_plugin_data( 'running_file' );
			if ( file_exists( $running_file ) ) {
				unlink( $running_file );
			}

			return;
		}

		//Check double running and inactivity
		$last_update = microtime( true ) - $this->timestamp_last_update;
		if ( ! empty( $this->pid ) && $last_update > 300 ) {
			$this->log( __( 'Job restarts due to inactivity for more than 5 minutes.', 'backwpup' ), E_USER_WARNING );
		} elseif ( ! empty( $this->pid ) ) {
			return;
		}
		// set timestamp of script start
		$this->timestamp_script_start = microtime( true );
		//set Pid
		$this->pid = self::get_pid();
		$this->uniqid = uniqid( '', true );
		//Early write new working file
		$this->write_running_file();
		if ( $this->is_debug() ) {
			@ini_set( 'error_log', $this->logfile );
			error_reporting( - 1 );
		}
		@ini_set( 'display_errors', '0' );
		@ini_set( 'log_errors', '1' );
		@ini_set( 'html_errors', '0' );
		@ini_set( 'report_memleaks', '1' );
		@ini_set( 'zlib.output_compression', '0' );
		@ini_set( 'implicit_flush', '0' );
		@putenv( 'TMPDIR=' . BackWPup::get_plugin_data( 'TEMP' ) );
		//Write Wordpress DB errors to log
		$wpdb->suppress_errors( false );
		$wpdb->hide_errors();
		//set wp max memory limit
		@ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
		//set error handler
		if ( ! empty( $this->logfile ) ) {
			if ( $this->is_debug() ) {
				set_error_handler( array( $this, 'log' ) );
			} else {
				set_error_handler( array( $this, 'log' ), E_ALL ^ E_NOTICE );
			}
		}
		set_exception_handler( array( $this, 'exception_handler' ) );
		// execute function on job shutdown  register_shutdown_function( array( $this, 'shutdown' ) );
		add_action( 'shutdown', array( $this, 'shutdown' ) );

		if ( function_exists( 'pcntl_signal' ) ) {
			$signals = array(
				'SIGHUP', //Term
				'SIGINT', //Term
				'SIGQUIT', //Core
				'SIGILL', //Core
				//'SIGTRAP', //Core
				'SIGABRT', //Core
				'SIGBUS', //Core
				'SIGFPE', //Core
				//'SIGKILL', //Term
				'SIGSEGV', //Core
				//'SIGPIPE', Term
				//'SIGALRM', Term
				'SIGTERM', //Term
				'SIGSTKFLT', //Term
				'SIGUSR1',//Term
				'SIGUSR2', //Term
				//'SIGCHLD', //Ign
				//'SIGCONT', //Cont
				//'SIGSTOP', //Stop
				//'SIGTSTP', //Stop
				//'SIGTTIN', //Stop
				//'SIGTTOU', //Stop
				//'SIGURG', //Ign
				'SIGXCPU', //Core
				'SIGXFSZ', //Core
				//'SIGVTALRM', //Term
				//'SIGPROF', //Term
				//'SIGWINCH', //Ign
				//'SIGIO', //Term
				'SIGPWR', //Term
				'SIGSYS' //Core
			);
			$signals = apply_filters( 'backwpup_job_signals_to_handel', $signals );
			declare( ticks=1 );
			$this->signal = 0;
			foreach ( $signals as $signal ) {
				if ( defined( $signal ) ) {
					pcntl_signal( constant( $signal ), array( $this, 'signal_handler' ), false );
				}
			}
		}
		$job_types = BackWPup::get_job_types();
		//go step by step
		foreach ( $this->steps_todo as $this->step_working ) {
			//Check if step already done
			if ( in_array( $this->step_working, $this->steps_done, true ) ) {
				continue;
			}
			//calc step percent
			if ( count( $this->steps_done ) > 0 ) {
				$this->step_percent = min( round( count( $this->steps_done ) / count( $this->steps_todo ) * 100 ),
					100 );
			} else {
				$this->step_percent = 1;
			}
			// do step tries
			while ( true ) {
				if ( $this->steps_data[ $this->step_working ]['STEP_TRY'] >= get_site_option( 'backwpup_cfg_jobstepretry' ) ) {
					$this->log( __( 'Step aborted: too many attempts!', 'backwpup' ), E_USER_ERROR );
					$this->temp = array();
					$this->steps_done[] = $this->step_working;
					$this->substeps_done = 0;
					$this->substeps_todo = 0;
					$this->do_restart();
					break;
				}

				$this->steps_data[ $this->step_working ]['STEP_TRY'] ++;
				$done = false;

				//executes the methods of job process
				if ( $this->step_working == 'CREATE_ARCHIVE' ) {
					$done = $this->create_archive();
				} elseif ( $this->step_working == 'ENCRYPT_ARCHIVE' ) {
					$done = $this->encrypt_archive();
				} elseif ( $this->step_working == 'CREATE_MANIFEST' ) {
					$done = $this->create_manifest();
				} elseif ( $this->step_working == 'END' ) {
					$this->end();
					break 2;
				} elseif ( strstr( $this->step_working, 'JOB_' ) ) {
					$done = $job_types[ str_replace( 'JOB_', '', $this->step_working ) ]->job_run( $this );
				} elseif ( strstr( $this->step_working, 'DEST_SYNC_' ) ) {
					$done = BackWPup::get_destination( str_replace( 'DEST_SYNC_', '', $this->step_working ) )
					                ->job_run_sync( $this );
				} elseif ( strstr( $this->step_working, 'DEST_' ) ) {
					$done = BackWPup::get_destination( str_replace( 'DEST_', '', $this->step_working ) )
					                ->job_run_archive( $this );
				} elseif ( ! empty( $this->steps_data[ $this->step_working ]['CALLBACK'] ) ) {
					$done = $this->steps_data[ $this->step_working ]['CALLBACK']( $this );
				}

				// set step as done
				if ( $done === true ) {
					$this->temp = array();
					$this->steps_done[] = $this->step_working;
					$this->substeps_done = 0;
					$this->substeps_todo = 0;
					$this->update_working_data( true );
				}
				if ( count( $this->steps_done ) < count( $this->steps_todo ) - 1 ) {
					$this->do_restart();
				}
				if ( $done === true ) {
					break;
				}
			}
		}
	}

	/**
	 * Creates the backup archive
	 */
	private function create_archive() {

		//load folders to backup
		$folders_to_backup = $this->get_folders_to_backup();

		$this->substeps_todo = $this->count_folder + 1;

		//initial settings for restarts in archiving
		if ( ! isset( $this->steps_data[ $this->step_working ]['on_file'] ) ) {
			$this->steps_data[ $this->step_working ]['on_file'] = '';
		}
		if ( ! isset( $this->steps_data[ $this->step_working ]['on_folder'] ) ) {
			$this->steps_data[ $this->step_working ]['on_folder'] = '';
		}

		if ( $this->steps_data[ $this->step_working ]['on_folder'] == '' && $this->steps_data[ $this->step_working ]['on_file'] == '' && is_file( $this->backup_folder . $this->backup_file ) ) {
			unlink( $this->backup_folder . $this->backup_file );
		}

		if ( $this->steps_data[ $this->step_working ]['SAVE_STEP_TRY'] != $this->steps_data[ $this->step_working ]['STEP_TRY'] ) {
			$this->log( sprintf( __( '%d. Trying to create backup archive &hellip;', 'backwpup' ),
				$this->steps_data[ $this->step_working ]['STEP_TRY'] ),
				E_USER_NOTICE );
		}

		try {
			$backup_archive = new BackWPup_Create_Archive( $this->backup_folder . $this->backup_file );

			//show method for creation
			if ( $this->substeps_done == 0 ) {
				$this->log( sprintf( _x( 'Compressing files as %s. Please be patient, this may take a moment.',
					'Archive compression method',
					'backwpup' ),
					$backup_archive->get_method() ) );
			}

			//add extra files
			if ( $this->substeps_done == 0 ) {
				if ( ! empty( $this->additional_files_to_backup ) && $this->substeps_done == 0 ) {
					if ( $this->is_debug() ) {
						$this->log( __( 'Adding Extra files to Archive', 'backwpup' ) );
					}
					foreach ( $this->additional_files_to_backup as $file ) {
						if ( $backup_archive->add_file( $file, basename( $file ) ) ) {
							$this->count_files ++;
							$this->count_files_size = $this->count_files_size + filesize( $file );
							$this->update_working_data();
						} else {
							$backup_archive->close();
							$this->steps_data[ $this->step_working ]['on_file'] = '';
							$this->steps_data[ $this->step_working ]['on_folder'] = '';
							$this->log( __( 'Cannot create backup archive correctly. Aborting creation.', 'backwpup' ),
								E_USER_ERROR );

							return false;
						}
					}
				}
				$this->substeps_done ++;
			}

			//add normal files
			while ( $folder = array_shift( $folders_to_backup ) ) {
				//jump over already done folders
				if ( in_array( $this->steps_data[ $this->step_working ]['on_folder'], $folders_to_backup, true ) ) {
					continue;
				}
				if ( $this->is_debug() ) {
					$this->log( sprintf( __( 'Archiving Folder: %s', 'backwpup' ), $folder ) );
				}
				$this->steps_data[ $this->step_working ]['on_folder'] = $folder;
				$files_in_folder = $this->get_files_in_folder( $folder );
				//add empty folders
				if ( empty( $files_in_folder ) ) {
					$folder_name_in_archive = trim( ltrim( $this->get_destination_path_replacement( $folder ), '/' ) );
					if ( ! empty ( $folder_name_in_archive ) ) {
						$backup_archive->add_empty_folder( $folder, $folder_name_in_archive );
					}
					continue;
				}
				//add files
				while ( $file = array_shift( $files_in_folder ) ) {
					//jump over already done files
					if ( in_array( $this->steps_data[ $this->step_working ]['on_file'], $files_in_folder, true ) ) {
						continue;
					}
					if ( $this->maybe_sql_dump( $file ) ) {
						continue;
					}

					$this->steps_data[ $this->step_working ]['on_file'] = $file;
					//restart if needed
					$restart_time = $this->get_restart_time();
					if ( $restart_time <= 0 ) {
						unset( $backup_archive );
						$this->do_restart_time( true );

						return false;
					}
					//generate filename in archive
					$in_archive_filename = ltrim( $this->get_destination_path_replacement( $file ), '/' );
					//add file to archive
					if ( $backup_archive->add_file( $file, $in_archive_filename ) ) {
						$this->count_files ++;
						$this->count_files_size = $this->count_files_size + filesize( $file );
						$this->update_working_data();
					} else {
						$backup_archive->close();
						unset( $backup_archive );
						$this->steps_data[ $this->step_working ]['on_file'] = '';
						$this->steps_data[ $this->step_working ]['on_folder'] = '';
						$this->substeps_done = 0;
						$this->backup_filesize = filesize( $this->backup_folder . $this->backup_file );
						if ( $this->backup_filesize === false ) {
							$this->backup_filesize = PHP_INT_MAX;
						}
						$this->log( __( 'Cannot create backup archive correctly. Aborting creation.', 'backwpup' ),
							E_USER_ERROR );

						return false;
					}
				}
				$this->steps_data[ $this->step_working ]['on_file'] = '';
				$this->substeps_done ++;
			}
			$backup_archive->close();
			unset( $backup_archive );
			$this->log( __( 'Backup archive created.', 'backwpup' ), E_USER_NOTICE );
		} catch ( Exception $e ) {
			$this->log( $e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine() );
			unset( $backup_archive );

			return false;
		}

		$this->backup_filesize = filesize( $this->backup_folder . $this->backup_file );
		if ( $this->backup_filesize === false ) {
			$this->backup_filesize = PHP_INT_MAX;
		}

		if ( $this->backup_filesize >= PHP_INT_MAX ) {
			$this->log( __( 'The Backup archive will be too large for file operations with this PHP Version. You might want to consider splitting the backup job in multiple jobs with less files each.',
				'backwpup' ),
				E_USER_ERROR );
			$this->end();
		} else {
			$this->log( sprintf( __( 'Archive size is %s.', 'backwpup' ), size_format( $this->backup_filesize, 2 ) ),
				E_USER_NOTICE );
		}

		$this->log( sprintf( __( '%1$d Files with %2$s in Archive.', 'backwpup' ),
			$this->count_files,
			size_format( $this->count_files_size, 2 ) ),
			E_USER_NOTICE );

		return true;
	}

	/**
	 * Encrypt Archive
	 *
	 * Encrypt the backup archive.
	 *
	 * @return bool true when done, false otherwise
	 */
	private function encrypt_archive() {

		$encryptionType = get_site_option( 'backwpup_cfg_encryption' );
		// Substeps is number of 128 KB chunks
		$block_size = 128 * 1024;
		$this->substeps_todo = ceil( $this->backup_filesize / $block_size );

		if ( ! isset( $this->steps_data[ $this->step_working ]['encrypted_filename'] ) ) {
			$this->steps_data[ $this->step_working ]['encrypted_filename'] = $this->backup_folder . $this->backup_file . '.encrypted';
			if ( is_file( $this->steps_data[ $this->step_working ]['encrypted_filename'] ) ) {
				unlink( $this->steps_data[ $this->step_working ]['encrypted_filename'] );
			}
		}

		if ( ! isset( $this->steps_data[ $this->step_working ]['key'] ) ) {
			switch ( $encryptionType ) {
				case self::ENCRYPTION_SYMMETRIC:
					$key = get_site_option( 'backwpup_cfg_encryptionkey' );
					$this->steps_data[ $this->step_working ]['key'] = pack( 'H*', $key );
					break;
				case self::ENCRYPTION_ASYMMETRIC:
					$this->steps_data[ $this->step_working ]['key'] = \phpseclib\Crypt\Random::string( 32 );
					break;
			}

			if ( empty( $this->steps_data[ $this->step_working ]['key'] ) ) {
				$this->log( __( 'No encryption key was provided. Aborting encryption.', 'backwpup' ), E_USER_WARNING );

				return false;
			}
		}

		if ( $this->steps_data[ $this->step_working ]['SAVE_STEP_TRY'] != $this->steps_data[ $this->step_working ]['STEP_TRY'] ) {
			// Show initial log message
			$this->log( sprintf( __( '%d. Trying to encrypt archive &hellip;', 'backwpup' ),
				$this->steps_data[ $this->step_working ]['STEP_TRY'] ),
				E_USER_NOTICE );
		}

		$aes = new \phpseclib\Crypt\AES( \phpseclib\Crypt\AES::MODE_CBC );
		$aes->setKey( $this->steps_data[ $this->step_working ]['key'] );
		$aes->enableContinuousBuffer();
		$aes->disablePadding();

		$file_in = fopen( $this->backup_folder . $this->backup_file, 'rb' );
		if ( ! $file_in ) {
			$this->log( __( 'Cannot open the archive for reading. Aborting encryption.', 'backwpup' ), E_USER_ERROR );

			return false;
		}

		$file_out = fopen( $this->steps_data[ $this->step_working ]['encrypted_filename'], 'a+b' );
		if ( ! $file_out ) {
			$this->log( __( 'Cannot write the encrypted archive. Aborting encryption.', 'backwpup' ), E_USER_ERROR );

			return false;
		}

		if ( $this->substeps_done === 0 ) {
			switch ( $encryptionType ) {
				case self::ENCRYPTION_SYMMETRIC:
					fwrite( $file_out, "\x00" );
					break;
				case self::ENCRYPTION_ASYMMETRIC:
					fwrite( $file_out, "\x01" );

					$rsa = new \phpseclib\Crypt\RSA();
					$rsa->loadKey( get_site_option( 'backwpup_cfg_publickey' ) );

					$key = $rsa->encrypt( $this->steps_data[ $this->step_working ]['key'] );
					$length = strlen( $key );

					fwrite( $file_out, pack( 'H*', dechex( $length ) ) );
					fwrite( $file_out, $key );
					break;
			}
		}

		$bytes_read = $this->substeps_done * $block_size;
		fseek( $file_in, $bytes_read );

		while ( $bytes_read <= $this->backup_filesize ) {
			$data = fread( $file_in, $block_size );
			// Check if final block
			$length = strlen( $data );
			if ( $this->substeps_done == $this->substeps_todo - 1 ) {
				// Pad as necessary
				$pad = 16 - ( $length % 16 );
				$data = str_pad( $data, $length + $pad, chr( $pad ) );
			}
			fwrite( $file_out, $aes->encrypt( $data ) );

			$this->substeps_done ++;
			$bytes_read += strlen( $data );
			$this->update_working_data();

			// Should we restart?
			$restart_time = $this->get_restart_time();
			if ( $restart_time <= 0 ) {
				$this->do_restart_time( true );
				fclose( $file_in );
				fclose( $file_out );

				return false;
			}
		}

		fclose( $file_in );
		fclose( $file_out );

		$this->log( sprintf( __( 'Encrypted %s of data.', 'backwpup' ), size_format( $bytes_read, 2 ) ),
			E_USER_NOTICE );

		// Remove the original file then rename the encrypted file
		if ( ! unlink( $this->backup_folder . $this->backup_file ) ) {
			$this->log( __( 'Unable to delete unencrypted archive.', 'backwpup' ) );

			return false;
		}
		if ( ! rename( $this->steps_data[ $this->step_working ]['encrypted_filename'],
			$this->backup_folder . $this->backup_file ) ) {
			$this->log( __( 'Unable to rename encrypted archive.', 'backwpup' ) );

			return false;
		}

		$this->backup_filesize = filesize( $this->backup_folder . $this->backup_file );
		$this->log( __( 'Archive has been successfully encrypted.', 'backwpup' ) );

		return true;
	}

	/**
	 * Get list of Folder for backup
	 *
	 * @return array folder list
	 */
	public function get_folders_to_backup() {

		$file = BackWPup::get_plugin_data( 'temp' ) . 'backwpup-' . BackWPup::get_plugin_data( 'hash' ) . '-folder.php';

		if ( ! file_exists( $file ) ) {
			return array();
		}

		$folders = array();

		$file_data = file( $file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );

		foreach ( $file_data as $folder ) {
			$folder = trim( str_replace( array( '<?php', '//' ), '', $folder ) );
			if ( ! empty( $folder ) && is_dir( $folder ) ) {
				$folders[] = $folder;
			}
		}
		$folders = array_unique( $folders );
		sort( $folders );
		$this->count_folder = count( $folders );

		return $folders;
	}

	/**
	 *
	 * Get back a array of files to backup in the selected folder
	 *
	 * @param string $folder the folder to get the files from
	 *
	 * @return array files to backup
	 */
	public function get_files_in_folder( $folder ) {

		$files = array();
		$folder = trailingslashit( $folder );

		if ( ! is_dir( $folder ) ) {
			$this->log( sprintf( _x( 'Folder %s does not exist', 'Folder name', 'backwpup' ), $folder ),
				E_USER_WARNING );

			return $files;
		}

		if ( ! is_readable( $folder ) ) {
			$this->log( sprintf( _x( 'Folder %s is not readable', 'Folder name', 'backwpup' ), $folder ),
				E_USER_WARNING );

			return $files;
		}

		try {
			$dir = new BackWPup_Directory( $folder );

			foreach ( $dir as $file ) {
				if ( $file->isDot() || $file->isDir() ) {
					continue;
				}
				$path = BackWPup_Path_Fixer::slashify( $file->getPathname() );
				foreach ( $this->exclude_from_backup as $exclusion ) { //exclude files
					$exclusion = trim( $exclusion );
					if ( stripos( $path, $exclusion ) !== false && ! empty( $exclusion ) ) {
						continue 2;
					}
				}
				if ( $this->job['backupexcludethumbs'] && strpos( $folder,
						BackWPup_File::get_upload_dir() ) !== false && preg_match( "/\-[0-9]{1,4}x[0-9]{1,4}.+\.(jpg|png|gif)$/i",
						$file->getFilename() ) ) {
					continue;
				}
				if ( $file->isLink() ) {
					$this->log( sprintf( __( 'Link "%s" not following.', 'backwpup' ), $file->getPathname() ),
						E_USER_WARNING );
				} elseif ( ! $file->isReadable() ) {
					$this->log( sprintf( __( 'File "%s" is not readable!', 'backwpup' ), $file->getPathname() ),
						E_USER_WARNING );
				} else {
					$file_size = $file->getSize();
					if ( ! is_int( $file_size ) || $file_size < 0 || $file_size > 2147483647 ) {
						$this->log( sprintf( __( 'File size of “%s” cannot be retrieved. File might be too large and will not be added to queue.',
							'backwpup' ),
							$file->getPathname() . ' ' . $file_size ),
							E_USER_WARNING );
						continue;
					}
					$files[] = BackWPup_Path_Fixer::slashify( realpath( $path ) );
				}
			}

		} catch ( UnexpectedValueException $e ) {
			$this->log( sprintf( __( "Could not open path: %s" ), $e->getMessage() ), E_USER_WARNING );
		}

		return $files;
	}

	/**
	 * Get job restart time
	 *
	 * @return int remaining time
	 */
	public function get_restart_time() {

		if ( php_sapi_name() == 'cli' ) {
			return 300;
		}

		$job_max_execution_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' );

		if ( empty( $job_max_execution_time ) ) {
			return 300;
		}

		$execution_time = microtime( true ) - $this->timestamp_script_start;

		return $job_max_execution_time - $execution_time - 3;
	}

	/**
	 * Do a job restart
	 *
	 * @param bool $do_restart_now should time restart now be done
	 *
	 * @return int remaining time
	 */
	public function do_restart_time( $do_restart_now = false ) {

		if ( php_sapi_name() == 'cli' ) {
			return 300;
		}

		//do restart after signal is send
		if ( $this->signal !== 0 ) {
			$this->steps_data[ $this->step_working ]['SAVE_STEP_TRY'] = $this->steps_data[ $this->step_working ]['STEP_TRY'];
			$this->steps_data[ $this->step_working ]['STEP_TRY'] -= 1;
			$this->do_restart( true );
		}

		$job_max_execution_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' );

		if ( empty( $job_max_execution_time ) ) {
			return 300;
		}

		$execution_time = microtime( true ) - $this->timestamp_script_start;

		// do restart 3 sec. before max. execution time
		if ( $do_restart_now || $execution_time >= ( $job_max_execution_time - 3 ) ) {
			$this->steps_data[ $this->step_working ]['SAVE_STEP_TRY'] = $this->steps_data[ $this->step_working ]['STEP_TRY'];
			$this->steps_data[ $this->step_working ]['STEP_TRY'] -= 1;
			$this->do_restart( true );
		}

		return $job_max_execution_time - $execution_time;
	}

	/**
	 * create manifest file
	 *
	 * @return bool
	 */
	public function create_manifest() {

		$this->substeps_todo = 3;

		$this->log( sprintf( __( '%d. Trying to generate a manifest file&#160;&hellip;', 'backwpup' ),
			$this->steps_data[ $this->step_working ]['STEP_TRY'] ) );

		//build manifest
		$manifest = array();
		// add blog information
		$manifest['blog_info']['url'] = home_url();
		$manifest['blog_info']['wpurl'] = site_url();
		$manifest['blog_info']['prefix'] = $GLOBALS['wpdb']->prefix;
		$manifest['blog_info']['description'] = get_option( 'blogdescription' );
		$manifest['blog_info']['stylesheet_directory'] = get_template_directory_uri();
		$manifest['blog_info']['activate_plugins'] = wp_get_active_and_valid_plugins();
		$manifest['blog_info']['activate_theme'] = wp_get_theme()->get( 'Name' );
		$manifest['blog_info']['admin_email'] = get_option( 'admin_email' );
		$manifest['blog_info']['charset'] = get_bloginfo( 'charset' );
		$manifest['blog_info']['version'] = BackWPup::get_plugin_data( 'wp_version' );
		$manifest['blog_info']['backwpup_version'] = BackWPup::get_plugin_data( 'version' );
		$manifest['blog_info']['language'] = get_bloginfo( 'language' );
		$manifest['blog_info']['name'] = get_bloginfo( 'name' );
		$manifest['blog_info']['abspath'] = ABSPATH;
		$manifest['blog_info']['uploads'] = wp_upload_dir( null, false, true );
		$manifest['blog_info']['contents']['basedir'] = WP_CONTENT_DIR;
		$manifest['blog_info']['contents']['baseurl'] = WP_CONTENT_URL;
		$manifest['blog_info']['plugins']['basedir'] = WP_PLUGIN_DIR;
		$manifest['blog_info']['plugins']['baseurl'] = WP_PLUGIN_URL;
		$manifest['blog_info']['themes']['basedir'] = get_theme_root();
		$manifest['blog_info']['themes']['baseurl'] = get_theme_root_uri();

        // Add job settings
        $manifest['job_settings'] = array(
            'dbdumptype' => $this->job['dbdumptype'],
            'dbdumpfile' => $this->job['dbdumpfile'],
            'dbdumpfilecompression' => $this->job['dbdumpfilecompression'],
            'dbdumpdbcharset' => !empty($this->job['dbdumpdbcharset']) ? $this->job['dbdumpdbcharset'] : '',
            'type' => $this->job['type'],
            'destinations' => $this->job['destinations'],
            'backuptype' => $this->job['backuptype'],
            'archiveformat' => $this->job['archiveformat'],
            'dbdumpexclude' => $this->job['dbdumpexclude'],

        );

		// add archive info
		foreach ( $this->additional_files_to_backup as $file ) {
			$manifest['archive']['extra_files'][] = basename( $file );
		}
		if ( isset( $this->steps_data['JOB_FILE'] ) ) {
			if ( $this->job['backuproot'] ) {
				$manifest['archive']['abspath'] = trailingslashit( $this->get_destination_path_replacement( ABSPATH ) );
			}
			if ( $this->job['backupuploads'] ) {
				$manifest['archive']['uploads'] = trailingslashit( $this->get_destination_path_replacement( BackWPup_File::get_upload_dir() ) );
			}
			if ( $this->job['backupcontent'] ) {
				$manifest['archive']['contents'] = trailingslashit( $this->get_destination_path_replacement( WP_CONTENT_DIR ) );
			}
			if ( $this->job['backupplugins'] ) {
				$manifest['archive']['plugins'] = trailingslashit( $this->get_destination_path_replacement( WP_PLUGIN_DIR ) );
			}
			if ( $this->job['backupthemes'] ) {
				$manifest['archive']['themes'] = trailingslashit( $this->get_destination_path_replacement( get_theme_root() ) );
			}
		}

		if ( ! file_put_contents( BackWPup::get_plugin_data( 'TEMP' ) . 'manifest.json', json_encode( $manifest ) ) ) {
			return false;
		}
		$this->substeps_done = 1;

		//Create backwpup_readme.txt
		$readme_text = __( 'You may have noticed the manifest.json file in this archive.', 'backwpup' ) . PHP_EOL;
		$readme_text .= __( 'manifest.json might be needed for later restoring a backup from this archive.',
				'backwpup' ) . PHP_EOL;
		$readme_text .= __( 'Please leave manifest.json untouched and in place. Otherwise it is safe to be ignored.',
				'backwpup' ) . PHP_EOL;
		if ( ! file_put_contents( BackWPup::get_plugin_data( 'TEMP' ) . 'backwpup_readme.txt', $readme_text ) ) {
			return false;
		}
		$this->substeps_done = 2;

		//add file to backup files
		if ( is_readable( BackWPup::get_plugin_data( 'TEMP' ) . 'manifest.json' ) ) {
			$this->additional_files_to_backup[] = BackWPup::get_plugin_data( 'TEMP' ) . 'manifest.json';
			$this->additional_files_to_backup[] = BackWPup::get_plugin_data( 'TEMP' ) . 'backwpup_readme.txt';
			$this->log( sprintf( __( 'Added manifest.json file with %1$s to backup file list.', 'backwpup' ),
				size_format( filesize( BackWPup::get_plugin_data( 'TEMP' ) . 'manifest.json' ), 2 ) ) );
		}
		$this->substeps_done = 3;

		return true;
	}

	/**
	 * @param $jobid
	 */
	public static function start_cli( $jobid ) {

		if ( php_sapi_name() != 'cli' ) {
			return;
		}

		//define DOING_CRON to prevent caching
		if ( ! defined( 'DOING_CRON' ) ) {
			define( 'DOING_CRON', true );
		}

		//load text domain
		$log_level = get_site_option( 'backwpup_cfg_loglevel', 'normal_translated' );
		if ( strstr( $log_level, 'translated' ) ) {
			BackWPup::load_text_domain();
		} else {
			add_filter( 'override_load_textdomain', '__return_true' );
			$GLOBALS['l10n'] = array();
		}

		$jobid = absint( $jobid );

		//Logs Folder
		$log_folder = get_site_option( 'backwpup_cfg_logfolder' );
		$log_folder = BackWPup_File::get_absolute_path( $log_folder );

		//check job id exists
		$jobids = BackWPup_Option::get_job_ids();
		if ( ! in_array( $jobid, $jobids, true ) ) {
			die( __( 'Wrong BackWPup JobID', 'backwpup' ) );
		}
		//check folders
		$log_folder_message = BackWPup_File::check_folder( $log_folder );
		if ( ! empty( $log_folder_message ) ) {
			die( $log_folder_message );
		}
		$log_folder_message = BackWPup_File::check_folder( BackWPup::get_plugin_data( 'TEMP' ), true );
		if ( ! empty( $log_folder_message ) ) {
			die( $log_folder_message );
		}
		//check running job
		if ( file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) {
			die( __( 'A BackWPup job is already running', 'backwpup' ) );
		}

		//start class
		$backwpup_job_object = new self();
		$backwpup_job_object->create( 'runcli', (int) $jobid );
		$backwpup_job_object->run();
	}

	/**
	 * disable caches
	 */
	public static function disable_caches() {

		//Special settings
		@putenv( 'nokeepalive=1' );
		@ini_set( 'zlib.output_compression', 'Off' );

		// deactivate caches
		if ( ! defined( 'DONOTCACHEDB' ) ) {
			define( 'DONOTCACHEDB', true );
		}
		if ( ! defined( 'DONOTCACHEPAGE' ) ) {
			define( 'DONOTCACHEPAGE', true );
		}
	}

	/**
	 *
	 * Reads a BackWPup logfile header and gives back a array of information
	 *
	 * @param string $logfile full logfile path
	 *
	 * @return array|bool
	 */
	public static function read_logheader( $logfile ) {

		$usedmetas = array(
			"date" => "logtime",
			"backwpup_logtime" => "logtime", //old value of date
			"backwpup_errors" => "errors",
			"backwpup_warnings" => "warnings",
			"backwpup_jobid" => "jobid",
			"backwpup_jobname" => "name",
			"backwpup_jobtype" => "type",
			"backwpup_jobruntime" => "runtime",
			"backwpup_backupfilesize" => "backupfilesize",
		);

		//get metadata of logfile
		$metas = array();
		if ( is_readable( $logfile ) ) {
			if ( '.gz' == substr( $logfile, - 3 ) ) {
				$metas = (array) get_meta_tags( 'compress.zlib://' . $logfile );
			} else {
				$metas = (array) get_meta_tags( $logfile );
			}
		}

		//only output needed data
		foreach ( $usedmetas as $keyword => $field ) {
			if ( isset( $metas[ $keyword ] ) ) {
				$joddata[ $field ] = $metas[ $keyword ];
			} else {
				$joddata[ $field ] = '';
			}
		}

		//convert date
		if ( isset( $metas['date'] ) ) {
			$joddata['logtime'] = strtotime( $metas['date'] ) + ( get_option( 'gmt_offset' ) * 3600 );
		}

		//use file create date if none
		if ( empty( $joddata['logtime'] ) ) {
			$joddata['logtime'] = filectime( $logfile );
		}

		return $joddata;
	}

	public static function user_abort() {

		/* @var $job_object BackWPup_Job */
		$job_object = BackWPup_Job::get_working_data();

		unlink( BackWPup::get_plugin_data( 'running_file' ) );

		//if job not working currently abort it this way for message
		$not_worked_time = microtime( true ) - $job_object->timestamp_last_update;
		$restart_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' );
		if ( empty( $restart_time ) ) {
			$restart_time = 60;
		}
		if ( empty( $job_object->pid ) || $not_worked_time > $restart_time ) {
			$job_object->user_abort = true;
			$job_object->update_working_data();
		}

	}

	/**
	 * Check whether exec has been disabled.
	 *
	 * @access public
	 * @static
	 * @return bool
	 */
	public static function is_exec() {

		// Is function avail
		if ( ! function_exists( 'exec' ) ) {
			return false;
		}

		// Is shell_exec disabled?
		if ( in_array( 'exec', array_map( 'trim', explode( ',', @ini_get( 'disable_functions' ) ) ), true ) ) {
			return false;
		}

		// Can we issue a simple echo command?
		$output = exec( 'echo backwpupechotest' );
		if ( $output != 'backwpupechotest' ) {
			return false;
		}

		return true;

	}

	/**
	 * Check if mysqldump is installed in the server.
	 *
	 * @param string $dbdumpmysqlfolder Path to mysqldump binary
	 *
	 * @return bool
	 */
	public static function mysqldump_installed( $dbdumpmysqlfolder ) {

		if ( false === self::is_exec() ) {
			return false;
		}

		if ( ! file_exists( $dbdumpmysqlfolder ) || ! is_executable( $dbdumpmysqlfolder ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Returns mysqldump error message.
	 *
	 * @param string $dbdumpmysqlfolder Path to mysqldump binary
	 *
	 * @return string
	 */
	public static function mysqldump_error_message( $dbdumpmysqlfolder ) {

		$output = '';

		if ( false === self::is_exec() ) {
			$output = '<span style="color:#999;">' . __( 'exec command is not active.', 'backwpup' ) . '</span>';

			return $output;
		}

		if ( ! file_exists( $dbdumpmysqlfolder ) || ! is_executable( $dbdumpmysqlfolder ) ) {
			$output = '<span style="color:#999;">' . __( 'mysqldump binary not found.', 'backwpup' ) . '</span>';

			return $output;
		}

		return $output;
	}

	/**
	 * Delete some data on cloned objects
	 */
	public function __clone() {

		$this->temp = array();
		$this->run = array();
	}

	/**
	 * Signal handler
	 *
	 * @param $signal_send
	 */
	public function signal_handler( $signal_send ) {

		//known signals
		$signals = array(
			'SIGHUP' => array(
				'description' => _x( 'Hangup detected on controlling terminal or death of controlling process',
					'SIGHUP: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGINT' => array(
				'description' => _x( 'Interrupt from keyboard',
					'SIGINT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGQUIT' => array(
				'description' => _x( 'Quit from keyboard',
					'SIGQUIT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGILL' => array(
				'description' => _x( 'Illegal Instruction',
					'SIGILL: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGABRT' => array(
				'description' => _x( 'Abort signal from abort(3)',
					'SIGABRT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_NOTICE,
			),
			'SIGBUS' => array(
				'description' => _x( 'Bus error (bad memory access)',
					'SIGBUS: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGFPE' => array(
				'description' => _x( 'Floating point exception',
					'SIGFPE: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGSEGV' => array(
				'description' => _x( 'Invalid memory reference',
					'SIGSEGV: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGTERM' => array(
				'description' => _x( 'Termination signal',
					'SIGTERM: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_WARNING,
			),
			'SIGSTKFLT' => array(
				'description' => _x( 'Stack fault on coprocessor',
					'SIGSTKFLT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGUSR1' => array(
				'description' => _x( 'User-defined signal 1',
					'SIGUSR1: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_NOTICE,
			),
			'SIGUSR2' => array(
				'description' => _x( 'User-defined signal 2',
					'SIGUSR2: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_NOTICE,
			),
			'SIGURG' => array(
				'description' => _x( 'Urgent condition on socket',
					'SIGURG: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_NOTICE,
			),
			'SIGXCPU' => array(
				'description' => _x( 'CPU time limit exceeded',
					'SIGXCPU: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGXFSZ' => array(
				'description' => _x( 'File size limit exceeded',
					'SIGXFSZ: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGPWR' => array(
				'description' => _x( 'Power failure',
					'SIGPWR: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
			'SIGSYS' => array(
				'description' => _x( 'Bad argument to routine',
					'SIGSYS: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details',
					'backwpup' ),
				'error' => E_USER_ERROR,
			),
		);

		foreach ( $signals as $signal => $config ) {
			if ( defined( $signal ) && $signal_send === constant( $signal ) ) {
				$this->log( sprintf( __( 'Signal "%1$s" (%2$s) is sent to script!', 'backwpup' ),
					$signal,
					$config['description'] ),
					$config['error'] );
				$this->signal = $signal_send;
				break;
			}
		}

	}

	/**
	 *
	 * Shutdown function is call if script terminates try to make a restart if needed
	 *
	 * Prepare the job for start
	 *
	 * @internal param int the signal that terminates the job
	 */
	public function shutdown() {

		//Put last error to log if one
		$lasterror = error_get_last();
		if ( $lasterror['type'] === E_ERROR || $lasterror['type'] === E_PARSE || $lasterror['type'] === E_CORE_ERROR || $lasterror['type'] === E_CORE_WARNING || $lasterror['type'] === E_COMPILE_ERROR || $lasterror['type'] === E_COMPILE_WARNING ) {
			$this->log( $lasterror['type'], $lasterror['message'], $lasterror['file'], $lasterror['line'] );
		}

		$error = false;
		if ( function_exists( 'pcntl_get_last_error' ) ) {
			$error = pcntl_get_last_error();
			if ( ! empty( $error ) ) {
				$error_msg = pcntl_strerror( $error );
				if ( ! empty( $error_msg ) ) {
					$error = '(' . $error . ') ' . $error_msg;
				}
			}
			if ( ! empty( $error ) ) {
				$this->log( sprintf( __( 'System: %s', 'backwpup' ), $error ), E_USER_ERROR );
			}
		}

		if ( function_exists( 'posix_get_last_error' ) && ! $error ) {
			$error = posix_get_last_error();
			if ( ! empty( $error ) ) {
				$error_msg = posix_strerror( $error );
				if ( ! empty( $error_msg ) ) {
					$error = '(' . $error . ') ' . $error_msg;
				}
			}
			if ( ! empty( $error ) ) {
				$this->log( sprintf( __( 'System: %s', 'backwpup' ), $error ), E_USER_ERROR );
			}
		}

		$this->do_restart( true );
	}

	/**
	 *
	 * The uncouth exception handler
	 *
	 * @param object $exception
	 */
	public function exception_handler( $exception ) {

		$this->log( sprintf( __( 'Exception caught in %1$s: %2$s', 'backwpup' ),
			get_class( $exception ),
			$exception->getMessage() ),
			E_USER_ERROR,
			$exception->getFile(),
			$exception->getLine() );
	}

	/**
	 *
	 * Callback for the CURLOPT_READFUNCTION that submit the transferred bytes
	 * to build the process bar
	 *
	 * @param $curl_handle
	 * @param $file_handle
	 * @param $read_count
	 *
	 * @return string
	 * @internal param $out
	 */
	public function curl_read_callback( $curl_handle, $file_handle, $read_count ) {

		$data = null;
		if ( ! empty( $file_handle ) && is_numeric( $read_count ) ) {
			$data = fread( $file_handle, $read_count );
		}

		if ( $this->job['backuptype'] == 'sync' ) {
			return $data;
		}

		$length = ( is_numeric( $read_count ) ) ? $read_count : strlen( $read_count );
		$this->substeps_done = $this->substeps_done + $length;
		$this->update_working_data();

		return $data;
	}

	/**
	 * For storing and getting data in/from a extra temp file
	 *
	 * @param    string $storage The name of the storage
	 * @param    array $data data to save in storage
	 *
	 * @return    array|mixed|null data from storage
	 */
	public function data_storage( $storage = null, $data = null ) {

		if ( empty( $storage ) ) {
			return $data;
		}

		$storage = strtolower( $storage );

		$file = BackWPup::get_plugin_data( 'temp' ) . 'backwpup-' . BackWPup::get_plugin_data( 'hash' ) . '-' . $storage . '.json';

		if ( ! empty( $data ) ) {
			file_put_contents( $file, json_encode( $data ) );
		} elseif ( is_readable( $file ) ) {
			$json = file_get_contents( $file );
			$data = json_decode( $json, true );
		}

		return $data;
	}

	/**
	 * Add a Folders to Folder list that should be backup
	 *
	 * @param array $folders folder to add
	 * @param bool $new overwrite existing file
	 */
	public function add_folders_to_backup( $folders = array(), $new = false ) {

		if ( ! is_array( $folders ) ) {
			$folders = (array) $folders;
		}

		$file = BackWPup::get_plugin_data( 'temp' ) . 'backwpup-' . BackWPup::get_plugin_data( 'hash' ) . '-folder.php';

		if ( ! file_exists( $file ) || $new ) {
			file_put_contents( $file, '<?php' . PHP_EOL );
		}

		$content = '';
		foreach ( $folders AS $folder ) {
			$content .= '//' . $folder . PHP_EOL;
		}

		if ( $content ) {
			file_put_contents( $file, $content, FILE_APPEND );
		}
	}

	/**
	 * Check if file is a dbdump.
	 *
	 * @param $file
	 *
	 * @return bool
	 */
	private function maybe_sql_dump( $file ) {

		$file_name = BackWPup_Option::get( $this->job['jobid'], 'dbdumpfile' );

		$dump_files = array(
			$file_name . '.sql',
			$file_name . '.sql.gz',
			$file_name . '.xml',
		);

		return in_array( basename( $file ), $dump_files, true );
	}
}
