<?php
// @codingStandardsIgnoreStart
/*
UpdraftPlus Addon: morefiles:Back up more files, including WordPress core
Description: Creates a backup of WordPress core (including everything in that directory WordPress is in), and any other file/directory you specify too.
Version: 2.6
Shop: /shop/more-files/
Latest Change: 1.14.3
*/
// @codingStandardsIgnoreEnd

if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');

new UpdraftPlus_Addons_MoreFiles;

class UpdraftPlus_Addons_MoreFiles {

	private $wpcore_foundyet = 0;

	private $more_paths = array();

	public function __construct() {
		add_filter('updraft_backupable_file_entities', array($this, 'backupable_file_entities'), 10, 2);
		add_filter('updraft_backupable_file_entities_final', array($this, 'backupable_file_entities_final'), 10, 2);

		add_filter('updraftplus_restore_movein_wpcore', array($this, 'restore_movein_wpcore'), 10, 2);
		add_filter('updraftplus_backup_makezip_wpcore', array($this, 'backup_makezip_wpcore'), 10, 3);
		add_filter('updraftplus_restore_movein_more', array($this, 'restore_movein_more'), 10, 3);
		add_filter('updraftplus_backup_makezip_more', array($this, 'backup_makezip_more'), 10, 3);

		add_filter('updraftplus_defaultoption_include_more', '__return_false');
		add_filter('updraftplus_defaultoption_include_wpcore', '__return_false');

		add_filter('updraftplus_admin_directories_description', array($this, 'admin_directories_description'));
		
		add_filter('updraftplus_fileinfo_more', array($this, 'fileinfo_more'), 10, 2);

		add_filter('updraftplus_config_option_include_more', array($this, 'config_option_include_more'), 10, 2);
		add_filter('updraftplus_config_option_include_wpcore', array($this, 'config_option_include_wpcore'), 10, 2);

		add_action('updraftplus_restore_form_wpcore', array($this, 'restore_form_wpcore'));
		add_filter('updraftplus_checkzip_wpcore', array($this, 'checkzip_wpcore'), 10, 4);
		add_filter('updraftplus_checkzip_end_wpcore', array($this, 'checkzip_end_wpcore'), 10, 3);
		
		add_filter('updraftplus_browse_download_link', array($this, 'updraftplus_browse_download_link'));
		add_filter('updraftplus_command_get_zipfile_download', array($this, 'updraftplus_command_get_zipfile_download'), 10, 2);

		add_filter('updraftplus_dirlist_more', array($this, 'backup_more_dirlist'));
		add_filter('updraftplus_dirlist_wpcore', array($this, 'backup_wpcore_dirlist'));
		add_filter('updraftplus_get_disk_space_used_none', array($this, 'get_disk_space_used_none'), 10, 3);
		
		add_filter('updraftplus_include_wpcore_exclude', array($this, 'include_wpcore_exclude'));

		add_filter('updraftplus_include_manifest', array($this, 'more_include_manifest'), 10, 2);
		add_filter('updraftplus_more_rebuild', array($this, 'more_rebuild'), 10, 1);

		add_filter('updraftplus_restore_all_downloaded_postscan', array($this, 'restore_all_downloaded_postscan_more'), 10, 7);
		add_filter('updraftplus_restore_all_downloaded_postscan', array($this, 'restore_all_downloaded_postscan_selective_restore'), 10, 7);
		add_filter('updraft_backupable_file_entities_on_restore', array($this, 'backupable_file_entities_on_restore'), 10, 3);
		add_filter('updraftplus_restore_path', array($this, 'restore_path_more'), 10, 4);

		add_action('updraftplus_admin_enqueue_scripts', array($this, 'updraftplus_admin_enqueue_scripts'));
	}

	/**
	 * Runs upon the WP action updraftplus_admin_enqueue_scripts
	 */
	public function updraftplus_admin_enqueue_scripts() {
		add_action('admin_footer', array($this, 'admin_footer_more_files_js'));
	}

	/**
	 * WP filter updraftplus_get_disk_space_used_none
	 *
	 * @param String       $result              - the unfiltered value to return
	 * @param String       $entity              - the entity type
	 * @param Array|String $backupable_entities - a path or list of paths
	 *
	 * @return String                           - filtered result
	 */
	public function get_disk_space_used_none($result, $entity, $backupable_entities) {
		return ('more' == $entity && empty($backupable_entities['more'])) ? __('(None configured)', 'updraftplus') : $result;
	}
	
	public function updraftplus_browse_download_link() {
		return '<a href="'.esc_url(UpdraftPlus::get_current_clean_url()).'" id="updraft_zip_download_item">'._x('Download', '(verb)', 'updraftplus').'</a>';
	}
	
	public function updraftplus_command_get_zipfile_download($result, $params) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the method is used as a WP filter.
		global $updraftplus;

		$zip_object = $updraftplus->get_zip_object_name();

		// Retrieve the information from our backup history
		$backup_history = UpdraftPlus_Backup_History::get_history();

		// Base name
		$file = $backup_history[$params['timestamp']][$params['type']];

		// Deal with multi-archive sets
		if (is_array($file)) $file = $file[$params['findex']];

		// Where it should end up being downloaded to
		$fullpath = $updraftplus->backups_dir_location().'/'.$file;

		$path = substr($params['path'], strpos($params['path'], DIRECTORY_SEPARATOR) + 1);

		if (file_exists($fullpath) && is_readable($fullpath) && filesize($fullpath)>0) {

			$zip = new $zip_object;

			if (!$zip->open($fullpath)) {
				return array('error' => 'UpdraftPlus: opening zip (' . $fullpath . '): failed to open this zip file.');
			} else {

				if ('UpdraftPlus_PclZip' == $zip_object) {
					$extracted = $zip->extract($updraftplus->backups_dir_location() . DIRECTORY_SEPARATOR . 'ziptemp' . DIRECTORY_SEPARATOR, $path);
				} else {
					$replaced_dir_sep_path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
					$extracted = $zip->extractTo($updraftplus->backups_dir_location() . DIRECTORY_SEPARATOR . 'ziptemp' . DIRECTORY_SEPARATOR, $replaced_dir_sep_path);
				}
				
				@$zip->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.

				if ($extracted) {
					return array('path' => 'ziptemp'.DIRECTORY_SEPARATOR.$path);
				} else {
					return array('error' => 'UpdraftPlus: failed to extract (' . $path . ')');
				}
			}
		}

		return array('error' => 'UpdraftPlus: no such file or directory (' . $fullpath . '): if the file does exist please make sure it is readable by the server.');
	}
	
	public function fileinfo_more($data, $ind) {
		if (!is_array($data) || !is_numeric($ind) || empty($this->more_paths) || !is_array($this->more_paths) || empty($this->more_paths[$ind])) return $data;

		global $updraftplus;
		$file_entities = $updraftplus->jobdata_get('job_file_entities');
		if (!isset($file_entities['more'])) return $data;

		return array(
			'html'=> '<br>'.__('Contains:', 'updraftplus').' '.htmlspecialchars($this->more_paths[$ind]),
			'text'=> '\r\n'.__('Contains:', 'updraftplus').' '.$this->more_paths[$ind]
		);
	}
	
	public function restore_form_wpcore() {

		?>
		<div id="updraft_restorer_wpcoreoptions" style="display:none; padding:12px; margin: 8px 0 4px; border: dashed 1px;"><h4 style="margin: 0px 0px 6px; padding:0px;"><?php echo esc_html(sprintf(__('%s restoration options:', 'updraftplus'), __('WordPress Core', 'updraftplus'))); ?></h4>

			<input name="updraft_restorer_wpcore_includewpconfig" id="updraft_restorer_wpcore_includewpconfig" type="checkbox" value="1"><label for="updraft_restorer_wpcore_includewpconfig"> <?php esc_html_e('Over-write wp-config.php', 'updraftplus'); ?></label> <a href="https://teamupdraft.com/documentation/updraftplus/topics/restoration/faqs/should-i-include-wp-config-php-when-i-restore-wordpress-core/" target="_blank"><?php esc_html_e('(learn more about this significant option)', 'updraftplus'); ?></a>

			<script>
				jQuery('#updraft_restore_wpcore').on('change', function(){
					if (jQuery('#updraft_restore_wpcore').is(':checked')) {
						jQuery('#updraft_restorer_wpcoreoptions').slideDown();
					} else {
						jQuery('#updraft_restorer_wpcoreoptions').slideUp();
					}
				});
			</script>

			</div>
		<?php
	}

	public function admin_directories_description() {
		return '<div>'.__('The above files comprise everything in a WordPress installation.', 'updraftplus').'</div>';
	}

	public function backupable_file_entities($arr, $full_info) {
		if ($full_info) {
			$arr['wpcore'] = array(
				'path' => untrailingslashit(ABSPATH),
				'description' => apply_filters('updraft_wpcore_description', __('WordPress core (including any additions to your WordPress root directory)', 'updraftplus')),
				'htmltitle' => sprintf(__('WordPress root directory server path: %s', 'updraftplus'), ABSPATH)
			);
		} else {
			$arr['wpcore'] = untrailingslashit(ABSPATH);
		}
		return $arr;
	}

	/**
	 * N.B. &$err is also available as a fourth parameter if needed
	 *
	 * @param string $zipfile
	 * @param array  $mess
	 * @param array  $warn
	 * @return void
	 */
	public function checkzip_wpcore($zipfile, &$mess, &$warn) {
		if (!empty($this->wpcore_foundyet) && 3 == $this->wpcore_foundyet) return;

		if (!is_readable($zipfile)) {
			$warn[] = sprintf(__('Unable to read zip file (%s) - could not pre-scan it to check its integrity.', 'updraftplus'), basename($zipfile));
			return;
		}

		if ('.zip' == strtolower(substr($zipfile, -4, 4))) {

			if (!class_exists('UpdraftPlus_PclZip')) updraft_try_include_file('includes/class-zip.php', 'include');
			$zip = new UpdraftPlus_PclZip;

			if (!$zip->open($zipfile)) {
				$warn[] = sprintf(__('Unable to open zip file (%s) - could not pre-scan it to check its integrity.', 'updraftplus'), basename($zipfile));
				return;
			}

			// Don't put this in the for loop, or the magic __get() method gets called every time the loop goes round
			$numfiles = $zip->numFiles;

			if (false === $numfiles) {
				$warn[] = sprintf(__('Unable to read any files from the zip (%s) - could not pre-scan it to check its integrity.', 'updraftplus'), basename($zipfile)).' '.sprintf(__('Zip error: (%s)', 'updraftplus'), $zip->last_error);
				return;
			}

			for ($i=0; $i < $numfiles; $i++) {
				$si = $zip->statIndex($i);
				if ('wp-admin/index.php' == $si['name']) {
					$this->wpcore_foundyet = $this->wpcore_foundyet | 1;
					if (3 == $this->wpcore_foundyet) return;
				}
				if ('xmlrpc.php' == $si['name'] || 'xmlrpc.php/xmlrpc.php' == $si['name']) {
					$this->wpcore_foundyet = $this->wpcore_foundyet | 2;
					if (3 == $this->wpcore_foundyet) return;
				}
			}

			@$zip->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
		} elseif (preg_match('/\.tar(\.(gz|bz2))$/i', $zipfile)) {

			if (!class_exists('UpdraftPlus_Archive_Tar')) {
				if (false === strpos(get_include_path(), UPDRAFTPLUS_DIR.'/includes/PEAR')) set_include_path(UPDRAFTPLUS_DIR.'/includes/PEAR'.PATH_SEPARATOR.get_include_path());
				updraft_try_include_file('includes/PEAR/Archive/Tar.php', 'include_once');
			}

			$p_compress = null;
			if ('.tar.gz' == strtolower(substr($zipfile, -7, 7))) {
				$p_compress = 'gz';
			} elseif ('.tar.bz2' == strtolower(substr($zipfile, -8, 8))) {
				$p_compress = 'bz2';
			}

			$tar = new UpdraftPlus_Archive_Tar($zipfile, $p_compress);
			$list = $tar->listContent();

			foreach ($list as $file) {
				if (is_array($file) && isset($file['filename'])) {
					if ('wp-admin/index.php' == $file['filename']) {
						$this->wpcore_foundyet = $this->wpcore_foundyet | 1;
						if (3 == $this->wpcore_foundyet) return;
					} elseif ('xmlrpc.php' == $file['filename']) {
						$this->wpcore_foundyet = $this->wpcore_foundyet | 2;
						if (3 == $this->wpcore_foundyet) return;
					}
				}
			}
		}
	}

	public function checkzip_end_wpcore(&$mess, &$warn, &$err) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the method is used as a WP filter.
		if (!empty($this->wpcore_foundyet) && 3 == $this->wpcore_foundyet) return;
		if (0 == ($this->wpcore_foundyet & 1)) $warn[] = sprintf(__('This does not look like a valid WordPress core backup - the file %s was missing.', 'updraftplus'), 'wp-admin/index.php').' '.__('If you are not sure then you should stop; otherwise you may destroy this WordPress installation.', 'updraftplus');
		if (0 == ($this->wpcore_foundyet & 2)) $warn[] = sprintf(__('This does not look like a valid WordPress core backup - the file %s was missing.', 'updraftplus'), 'xmlrpc.php').' '.__('If you are not sure then you should stop; otherwise you may destroy this WordPress installation.', 'updraftplus');
	}

	/**
	 * This function returns a list of file entities that can be backed up other than potential entities  (subject to user's settings), and optionally further meta-data about them
	 *
	 * @param  array   $arr       -  List of entities
	 * @param  boolean $full_info -  boolean to indicate if we need full details regarding the entity
	 * @return array              -  array of final entity list
	 */
	public function backupable_file_entities_final($arr, $full_info) {
		$path = UpdraftPlus_Options::get_updraft_option('updraft_include_more_path', '');
		if (is_array($path)) {
			$path = array_map('untrailingslashit', $path);
			if (1 == count($path)) $path = array_shift($path);
		} else {
			$path = untrailingslashit($path);
		}
		if ($full_info) {
			$arr['more'] = array(
				'path' => $path,
				'description' => __('Any other file/directory on your server that you wish to backup', 'updraftplus'),
				'shortdescription' => __('More Files', 'updraftplus'),
				'restorable' => true
			);
		} else {
			$arr['more'] = $path;
		}
		return $arr;
	}

	/**
	 * Returns HTML for adding more directories other than wordpress to backup.
	 * Callback function for "updraftplus_config_option_include_{$key}" where key='more'.
	 *
	 * @param string $ret    Default value.
	 * @param string $prefix Prefix for widget.
	 * @return string Returns HTML for adding more directories to backup in
	 *                'Include in files backup' in 'Settings' Tab.
	 */
	public function config_option_include_more($ret, $prefix) {

		if ($prefix) return $ret;

		$display = UpdraftPlus_Options::get_updraft_option('updraft_include_more') ? '' : 'style="display:none;"';
		$class = $display ? 'updraft-hidden' : '';
		
		$paths = UpdraftPlus_Options::get_updraft_option('updraft_include_more_path', array());

		// Remove empty elements
		$paths = is_array($paths) ? array_filter($paths) : array($paths);

		$ret .= '<div id="updraft_include_more_options" '.wp_kses($display, array()).' class="updraft_include_container '.esc_attr($class).'"><p class="updraft-field-description">';

		$ret .= esc_html(__('If you are not sure what this option is for, then you will not want it, and should turn it off.', 'updraftplus').' '.__('If using it, select a path from the directory tree below and then press confirm selection.', 'updraftplus'));
		
		$ret .= ' '.esc_html__('Be careful what you select - if you select / then it really will try to create a zip containing your entire webserver.', 'updraftplus');

		$ret .= '</p>';

		$ret .= '<p id="updraft_include_more_paths_error"></p>';

		$ret .= '<div id="updraft_include_more_paths">';

		// Stops default empty path input being output to screen
		
		if (empty($paths)) {
			$paths = array('');
		} else {
			foreach ($paths as $ind => $path) {
				$ret .= '<div class="updraftplus-morefiles-row"><label for="updraft_include_more_path_'.esc_attr($ind).'"></label>';
				$ret .= '<input type="text" data-mp_index="'.esc_attr($ind).'" id="updraft_include_more_path_'.esc_attr($ind).'" class="updraft_include_more_path" name="updraft_include_more_path[]" size="54" value="'.esc_attr($path).'" title="'.esc_attr($path).'"/> <a href="#" title="'.esc_attr__('Edit', 'updraftplus').'" class="updraftplus-morefiles-row-edit dashicons dashicons-edit hidden-in-updraftcentral"></a> <a href="#" title="'.esc_attr__('Remove', 'updraftplus').'" class="updraftplus-morefiles-row-delete dashicons dashicons-no"></a>';
				$ret .= '</div>';
			}
		}
		
		$ret .= '</div>';

		$ret .= $this->get_jstree_ui('options');

		$ret .= '</div>';
		
		return $ret;
	}

	/**
	 * Gives html for the wp core exclude settings. Called by the updraftplus_config_option_include_wpcore filter
	 *
	 * @param String $ret    the value passed by filter. by default, it is empty string
	 * @param String $prefix Prefix for the ID
	 * @return String html for exclude wp core
	 */
	public function config_option_include_wpcore($ret, $prefix) {
		global $updraftplus, $updraftplus_admin;
		
		$for_updraftcentral = defined('UPDRAFTCENTRAL_COMMAND') && UPDRAFTCENTRAL_COMMAND;

		if ($prefix) return $ret;

		$display = UpdraftPlus_Options::get_updraft_option('updraft_include_wpcore') ? '' : 'style="display:none;"';
		$exclude_container_class = 'updraft_include_wpcore_exclude';
		if (!$for_updraftcentral)  $exclude_container_class .= '_container';

		$ret .= '<div id="'.esc_attr($exclude_container_class).'" class="updraft_exclude_container" '.wp_kses($display, array()).'>';

		$ret .= '<label class="updraft-exclude-label" for="updraft_include_wpcore_exclude">'.esc_html__('Exclude these:', 'updraftplus').'</label>';

		$exclude_input_type = $for_updraftcentral ? "text" : "hidden";
		$exclude_input_extra_attr = $for_updraftcentral ? 'title="'.__('If entering multiple files/directories, then separate them with commas.', 'updraftplus').' '.__('For entities at the top level, you can use a * at the start or end of the entry as a wildcard.', 'updraftplus').'" size="54"' : '';
		$ret .= '<input type="'.esc_attr($exclude_input_type).'" id="updraft_include_wpcore_exclude" name="updraft_include_wpcore_exclude" value="'.esc_attr(UpdraftPlus_Options::get_updraft_option('updraft_include_wpcore_exclude')).'" '.wp_kses($exclude_input_extra_attr, array()).' />';
		if (!$for_updraftcentral) {
			$backupable_file_entities = $updraftplus->get_backupable_file_entities();
			$path = UpdraftPlus_Manipulation_Functions::wp_normalize_path($backupable_file_entities['wpcore']);
			$ret .= $updraftplus_admin->include_template('wp-admin/settings/file-backup-exclude.php', true, array(
				'prefix' => $prefix,
				'key' => 'wpcore',
				'include_exclude' => UpdraftPlus_Options::get_updraft_option('updraft_include_wpcore_exclude'),
				'path' => $path,
				'show_exclusion_options' => true
			));
		}
		$ret .= '</div>';

		return $ret;
	}

	/**
	 * Called via the WP filter updraftplus_dirlist_more
	 *
	 * @param String|Array $whichdirs - a path, or list of paths. Ultimately comes from the option updraft_include_more_path
	 *
	 * @return String|Array - filtered value
	 */
	public function backup_more_dirlist($whichdirs) {
		// Need to properly analyse the plugins, themes, uploads, content paths in order to strip them out (they may have various non-default manual values)

		global $updraftplus;

		$possible_backups = $updraftplus->get_backupable_file_entities(false);
		// We don't want to exclude the very thing we are backing up
		unset($possible_backups['more']);
		// We do want to exclude everything in WordPress and in wp-content
		$possible_backups['wp-content'] = WP_CONTENT_DIR;
		$possible_backups['wordpress'] = untrailingslashit(ABSPATH);

		$possible_backups_dirs = array();
		foreach ($possible_backups as $possback) {
			if (is_array($possback)) {
				foreach ($possback as $pb) $possible_backups_dirs[] = $pb;
			} else {
				$possible_backups_dirs[] = $possback;
			}
		}

		$possible_backups_dirs = array_unique($possible_backups_dirs);
		// $possible_backups_dirs = array_flip($possible_backups); // old

		$orig_was_array = is_array($whichdirs);
		if (!$orig_was_array) $whichdirs = array($whichdirs);
		$dirlist = array();

		foreach ($whichdirs as $whichdir) {

			if (!empty($whichdir) && (is_dir($whichdir) || is_file($whichdir))) {
				// Removing the slash is important (though ought to be redundant by here); otherwise path matching does not work
				$dirlist[] = $updraftplus->compile_folder_list_for_backup(untrailingslashit($whichdir), $possible_backups_dirs, array());
			} else {
				$dirlist[] = array();
				if (!empty($whichdir)) {
					$updraftplus->log("We expected to find something to back up at: ".$whichdir);
					$updraftplus->log($whichdir.': '.__("No backup of location: there was nothing found to back up", 'updraftplus'), 'warning');
				}
			}

		}

		return $orig_was_array ? $dirlist : array_shift($dirlist);

	}

	/**
	 * This function will build and keep track of a list of more files that will be backed up
	 *
	 * @param array   $whichdirs            - an array of directories that need to be backed up
	 * @param string  $backup_file_basename - the backup file basename
	 * @param integer $index                - the backup index
	 *
	 * @return array|boolean               - returns an array of created more file zips or false if none are created
	 */
	public function backup_makezip_more($whichdirs, $backup_file_basename, $index) {

		global $updraftplus, $updraftplus_backup;

		if (!is_array($whichdirs)) $whichdirs = array($whichdirs);

		$this->more_paths = array();

		$final_created = $updraftplus->jobdata_get('morefiles_temporary_final_created');
		if (!is_array($final_created)) $final_created = array();

		$first_linked_index = 0;
		
		// Oct 2018: changed the way more files are tracked, there are now two arrays:
		// more_locations: a numerical array of unique more file locations
		// more_map: a numerical array where array keys match the backup file and array values match an array key in the more_locations array
		// For tracking which "more files" configuration entry goes into which zip, to avoid useless activity (or worse, duplicate backups)
		$more_map = $updraftplus->jobdata_get('morefiles_linked_indexes');
		$more_locations = $updraftplus->jobdata_get('morefiles_more_locations');
		if (!is_array($more_map)) $more_map = array();
		if (!is_array($more_locations)) $more_locations = array();

		foreach ($whichdirs as $whichdir) {
			if (in_array($whichdir, $more_locations)) continue;

			// Actually create the thing
			$dirlist = $this->backup_more_dirlist($whichdir);

			if (count($dirlist)>0) {
				$this->more_paths[] = $whichdir;
				
				if (!in_array($whichdir, $more_locations)) {
					$more_locations[] = $whichdir;
					$updraftplus->jobdata_set('morefiles_more_locations', $more_locations);
				}
				
				if (!empty($more_map) && isset($more_map[$first_linked_index])) {
					$first_linked_index = $index = count($more_map);
				}

				$created = $updraftplus_backup->create_zip($dirlist, 'more', $backup_file_basename, $index, $first_linked_index);
				
				if (!empty($created)) {

					foreach ($created as $key => $name) {
						$more_map[$key] = array_search($whichdir, $more_locations);
					}
					$updraftplus->jobdata_set('morefiles_linked_indexes', $more_map);

					$keys = array_keys($created);
					$index = end($keys);
					$index++;
					$first_linked_index = $index;
				}
				
				if (is_string($created)) {
					$final_created[] = $created;
				} elseif (is_array($created)) {
					$final_created = array_merge($final_created, $created);
				} else {
					$updraftplus->log("$whichdir: More files backup: create_zip returned an error", 'warning', 'morefiles-'.md5($whichdir));
					// return false;
				}
			} else {
				$updraftplus->log("$whichdir: No backup of 'more' directory: there was nothing found to back up", 'warning', 'morefiles-empty-'.md5($whichdir));
				// return false;
			}

			$final_created = array_unique($final_created);
			$updraftplus->jobdata_set('morefiles_temporary_final_created', $final_created);
		}

		return (empty($final_created)) ? false : $final_created;
	}

	public function include_wpcore_exclude() {
		return explode(',', UpdraftPlus_Options::get_updraft_option('updraft_include_wpcore_exclude', ''));
	}

	public function backup_wpcore_dirlist($whichdir, $logit = false) {

		// Need to properly analyse the plugins, themes, uploads, content paths in order to strip them out (they may have various non-default manual values)

		global $updraftplus;

		if (false !== ($wpcore_dirlist = apply_filters('updraftplus_dirlist_wpcore_override', false, $whichdir))) return $wpcore_dirlist;

		$possible_backups = $updraftplus->get_backupable_file_entities(false);
		// We don't want to exclude the very thing we are backing up
		unset($possible_backups['wpcore']);
		// We do want to exclude everything in wp-content
		$possible_backups['wp-content'] = WP_CONTENT_DIR;
		
		$possible_backups_dirs = array();

		foreach ($possible_backups as $key => $dir) {
			if (is_array($dir)) {
				foreach ($dir as $ind => $rdir) {
					if (!empty($rdir)) $possible_backups_dirs[$rdir] = $key.$ind;
				}
			} else {
				if (!empty($dir)) $possible_backups_dirs[$dir] = $key;
			}
		}

		// Create an array of directories to be skipped
		$exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_wpcore_exclude', '');
		if ($logit) $updraftplus->log("Exclusion option setting (wpcore): ".$exclude);
		// Make the values into the keys
		$wpcore_skip = array_flip(preg_split("/,/", $exclude));
		$wpcore_skip['wp_content'] = 0;

		// Removing the slash is important (though ought to be redundant by here); otherwise path matching does not work
		$wpcore_dirlist = $updraftplus->compile_folder_list_for_backup(untrailingslashit($whichdir), $possible_backups_dirs, $wpcore_skip);

		// This is not required to be a perfect test. The point is to make sure we do get WP core.
		// Not using this approach for now.
// if (true == apply_filters('updraftplus_backup_wpcore_dirlist_strict', false)) {
// $wpcore_valid = array('wp-admin', 'wp-includes', 'index.php', 'xmlrpc.php');
// foreach ($wpcore_dirlist as $dir) {
//
// }
// }

		return $wpcore_dirlist;

	}

	/**
	 * $whichdir will equal untrailingslashit(ABSPATH) (is ultimately sourced from our backupable_file_entities filter callback)
	 *
	 * @param  string $whichdir
	 * @param  string $backup_file_basename
	 * @param  string $index
	 * @return array
	 */
	public function backup_makezip_wpcore($whichdir, $backup_file_basename, $index) {

		global $updraftplus, $updraftplus_backup;

		// Actually create the thing

		$wpcore_dirlist = $this->backup_wpcore_dirlist($whichdir, true);

		if (count($wpcore_dirlist)>0) {
			$created = $updraftplus_backup->create_zip($wpcore_dirlist, 'wpcore', $backup_file_basename, $index);
			if (is_string($created) || is_array($created)) {
				return $created;
			} else {
				$updraftplus->log("WP Core backup: create_zip returned an error");
				return false;
			}
		} else {
			$updraftplus->log("No backup of WP core directories: there was nothing found to back up");
			$updraftplus->log(sprintf(__("No backup of %s directories: there was nothing found to back up", 'updraftplus'), __('WordPress Core', 'updraftplus')), 'error');
			return false;
		}

	}


	/**
	 * $wp_dir is trailingslashit($wp_filesystem->abspath())
	 * Must only use $wp_filesystem methods
	 * $working_dir is the directory which contains the backup entity/ies. It is a child of wp-content/upgrade
	 * We need to make sure we do not over-write any entities that are restored elsewhere. i.e. Don't touch plugins/themes etc. - but use backupable_file_entities in order to be fully compatible, but with an additional over-ride of touching nothing inside WP_CONTENT_DIR. Can recycle code from the 'others' handling to assist with this.
	 *
	 * @param  string $working_dir The directory where the WP-Core backup file(s) have been extracted
	 * @param  string $wp_dir      The root directory of the WordPress
	 * @return boolean|WP_Error    True if all extraced WP-Core files are successfully copy and/or moved to the WordPress directory, otherwise false or WP_Error object if there's a failure
	 */
	public function restore_movein_wpcore($working_dir, $wp_dir) {

		global $updraftplus_restorer;

		// On subsequent archives of a multi-archive set, don't move anything; but do on the first
		$preserve_existing = isset($updraftplus_restorer->been_restored['wpcore']) ? Updraft_Restorer::MOVEIN_COPY_IN_CONTENTS : Updraft_Restorer::MOVEIN_OVERWRITE_NO_BACKUP;

		if (null !== ($result = apply_filters('updraftplus_use_builtin_wpcore_restoration', null, $working_dir, $wp_dir, 1))) {
			return $result;
		} else {
			return $updraftplus_restorer->move_backup_in($working_dir, $wp_dir, $preserve_existing, array(basename(WP_CONTENT_DIR)), 'wpcore');
		}
	}

	/**
	 * This function is called via a filter and will restore the more files backups
	 * Must only use $wp_filesystem methods
	 * We need to make sure we do not over-write any entities that are restored elsewhere. i.e. Don't touch plugins/themes etc. - but use backupable_file_entities in order to be fully compatible, but with an additional over-ride of touching nothing inside WP_CONTENT_DIR. Can recycle code from the 'others' handling to assist with this.
	 *
	 * @param string $working_dir       - the directory which contains the backup entity/ies. it is a child of wp-content/upgrade
	 * @param string $wp_dir            - is trailingslashit($wp_filesystem->abspath())
	 * @param string $wp_filesystem_dir - the location we want to restore the more file backup
	 *
	 * @return boolean|WP_Error - boolean for success or a wordpress error
	 */
	public function restore_movein_more($working_dir, $wp_dir, $wp_filesystem_dir) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the method is used as a WP filter.

		global $updraftplus_restorer;

		// On subsequent archives of a multi-archive set, don't move anything; but do on the first
		$preserve_existing = isset($updraftplus_restorer->been_restored['more']) ? Updraft_Restorer::MOVEIN_COPY_IN_CONTENTS : Updraft_Restorer::MOVEIN_OVERWRITE_NO_BACKUP;

		return $updraftplus_restorer->move_backup_in($working_dir, trailingslashit($wp_filesystem_dir), $preserve_existing, array(basename(WP_CONTENT_DIR)), 'more');

	}

	/**
	 * This function is called via a filter and will search the backup set for the correct more files path for that backup file
	 *
	 * @param array  $path        - the path to be filtered
	 * @param string $backup_file - the backup file we are restoring
	 * @param array  $backup_set  - the backup set being restored
	 * @param string $type        - the type of backup file
	 *
	 * @return string - the filtered path
	 */
	public function restore_path_more($path, $backup_file, $backup_set, $type) {
		
		if ('more' != $type) return $path;

		if (!isset($backup_set['morefiles_linked_indexes']) || !isset($backup_set['morefiles_more_locations'])) return $path;

		if (false !== ($file_key = array_search($backup_file, $backup_set['more']))) {

			$location_key = $backup_set['morefiles_linked_indexes'][$file_key];

			return $backup_set['morefiles_more_locations'][$location_key];
		}

		return $path;
	}

	/**
	 * This function will filter and return a boolean to indicate if the backup should include a manifest or not
	 *
	 * @param boolean $include  - a boolean to indicate if we should include a manifest in the backup
	 * @param string  $whichone - the entity that this backup is
	 *
	 * @return boolean          - returns a boolean to indicate if we should include a manifest in the backup
	 */
	public function more_include_manifest($include, $whichone) {
		return ('more' == $whichone) ? true : $include;
	}

	/**
	 * This function will rebuild the more files linked indexes and more locations array if the backup history is missing this information and return the backup history otherwise returns false
	 *
	 * @param array $backup_history - the backup history
	 *
	 * @return array|boolean        - the modified backup history or false if theres no changes
	 */
	public function more_rebuild($backup_history) {

		$changes = false;
		
		foreach ($backup_history as $btime => $bdata) {
			if (!isset($bdata['more'])) continue;
			foreach ($bdata['more'] as $key => $filename) {
				if (!isset($bdata['morefiles_linked_indexes'])) $bdata['morefiles_linked_indexes'] = array();
				if (!isset($bdata['morefiles_more_locations'])) $bdata['morefiles_more_locations'] = array();
				if (isset($bdata['morefiles_linked_indexes'][$key])) continue;

				$morefile_path = $this->more_manifest_file_directory('', $filename);

				if ('' == $morefile_path) continue;

				$changes = true;

				$morefile_path_key = array_search($morefile_path, $bdata['morefiles_more_locations']);

				if (false !== $morefile_path_key) {
					$bdata['morefiles_linked_indexes'][$key] = $morefile_path_key;
				} else {
					if (!in_array($morefile_path, $bdata['morefiles_more_locations'])) $bdata['morefiles_more_locations'][] = $morefile_path;
					$bdata['morefiles_linked_indexes'][$key] = count($bdata['morefiles_more_locations']) - 1;
				}
			}

			// We sort these here so that they appear in order in the backup history which makes for easier debugging
			ksort($bdata['morefiles_more_locations']);
			ksort($bdata['morefiles_linked_indexes']);
			
			$backup_history[$btime] = $bdata;
		}

		if ($changes) return $backup_history;
		
		return false;
	}

	/**
	 * This function will check the passed in more files zip to see if it includes a manifest file and if so it will extract the directory that them files belong to and return it, otherwise it will return the default value passed in.
	 *
	 * @param string $path     - the default passed in path
	 * @param string $filename - the more file zip name
	 *
	 * @return string          - the default path if no manifest is found or the location of the more files if it is found
	 */
	private function more_manifest_file_directory($path, $filename) {
		global $updraftplus;

		$zip_object = $updraftplus->get_zip_object_name();
		$fullpath = $updraftplus->backups_dir_location() . DIRECTORY_SEPARATOR . $filename;

		if (file_exists($fullpath) && is_readable($fullpath) && filesize($fullpath) > 0) {
			$zip = new $zip_object;
			
			$zip_opened = $zip->open($fullpath);

			if (true !== $zip_opened) {
				return array('error' => 'UpdraftPlus: opening zip (' . $fullpath . '): failed to open this zip file (object='.$zip_object.', code: '.$zip_opened.')');
			} else {

				if ('UpdraftPlus_PclZip' == $zip_object) {
					$zip->extract($updraftplus->backups_dir_location() . DIRECTORY_SEPARATOR . 'ziptemp' . DIRECTORY_SEPARATOR, 'updraftplus-manifest.json');
				} else {
					$zip->extractTo($updraftplus->backups_dir_location() . DIRECTORY_SEPARATOR . 'ziptemp' . DIRECTORY_SEPARATOR, 'updraftplus-manifest.json');
				}
				
				$manifest_path = $updraftplus->backups_dir_location() . DIRECTORY_SEPARATOR . 'ziptemp' . DIRECTORY_SEPARATOR . 'updraftplus-manifest.json';
				$manifest = json_decode(file_get_contents($manifest_path), true);

				$path = $manifest['directory'];

				@$zip->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
			}
		}

		return $path;
	}

	/**
	 * WordPress action updraftplus_restore_all_downloaded_postscan called during the restore process.
	 *
	 * The last four parameters can be edited in-place.
	 *
	 * @param Array   $backups   - list of backups
	 * @param Integer $timestamp - the timestamp (epoch time) of the backup being restored
	 * @param Array   $entities  - elements being restored (as the keys of the array)
	 * @param Array   $info      - information about the backup being restored
	 * @param Array   $mess      - array of informational-level messages
	 * @param Array   $warn      - array of warning-level messages
	 * N.B. An extra parameter $err is also available after $warn
	 */
	public function restore_all_downloaded_postscan_more($backups, $timestamp, $entities, &$info, &$mess, &$warn) {
		if (!isset($entities['more']) || !isset($backups[$timestamp]['more'])) return;

		$not_found = false;
		$more_ui = '<div class="updraft_include_more_paths">';

		if (empty($backups[$timestamp]['morefiles_linked_indexes']) || empty($backups[$timestamp]['morefiles_more_locations'])) {
			$not_found = true;
			foreach ($backups[$timestamp]['more'] as $key => $value) {
				$more_ui .= $this->get_jstree_row_ui($key, $value, '', true, false, $key);
			}
		} else {
			
			$last_index = -1;
			$split_set = false;

			foreach ($backups[$timestamp]['more'] as $key => $value) {
				if (!isset($backups[$timestamp]['morefiles_linked_indexes'][$key])) {
					$not_found = true;
					$more_ui .= $this->get_jstree_row_ui($key, $value, '', true, false, $key);
				}
				
				$index = $backups[$timestamp]['morefiles_linked_indexes'][$key];

				$split_set = $last_index === $index ? true : false;
				
				if (!file_exists(dirname($backups[$timestamp]['morefiles_more_locations'][$index]))) {
					$not_found = true;
					$more_ui .= $this->get_jstree_row_ui($key, $value, $backups[$timestamp]['morefiles_more_locations'][$index], true, $split_set, $index);
				} else {
					$more_ui .= $this->get_jstree_row_ui($key, $value, $backups[$timestamp]['morefiles_more_locations'][$index], false, $split_set, $index);
				}
				
				$last_index = $index;
			}
		}
		$more_ui .= $this->get_jstree_ui('restore');
		$more_ui .= '</div>';

		if ($not_found) {
			$warn[] = __('The original filesystem location for some of the following items was not found.', 'updraftplus').' '.__('Please select where you want these backups to be restored to.', 'updraftplus');
		}
		$mess[] = __('Please select the more files backups that you wish to restore:', 'updraftplus');
		$info['addui'] = empty($info['addui']) ? $more_ui : $info['addui'] . '<br>' . $more_ui;
	}

	/**
	 * WordPress action updraftplus_restore_all_downloaded_postscan called during the restore process.
	 *
	 * The last three parameters can be edited in-place.
	 *
	 * @param Array   $backups   - list of backups
	 * @param Integer $timestamp - the timestamp (epoch time) of the backup being restored
	 * @param Array   $entities  - elements being restored (as the keys of the array)
	 * @param Array   $info      - information about the backup being restored
	 * @param Array   $mess      - array of informational-level messages
	 * @param Array   $warn      - array of warning-level messages
	 * N.B. An extra parameter $err is also available after $warn
	 */
	public function restore_all_downloaded_postscan_selective_restore($backups, $timestamp, $entities, &$info, &$mess, &$warn) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the method is used as a WP filter.

		$selective_restore_types = array(
			'plugins',
			'themes',
		);

		foreach ($selective_restore_types as $type) {
			if (!isset($entities[$type]) || !isset($backups[$timestamp][$type])) continue;

			$backup_entities = $this->get_backup_contents_list($backups[$timestamp][$type]);
	
			if (empty($backup_entities)) continue;
			$selective_restore_ui = $this->get_entity_selective_restore_ui($backup_entities, $type);
	
			$info['addui'] = empty($info['addui']) ? $selective_restore_ui : $info['addui'] . '<br>' . $selective_restore_ui;
		}
	}

	/**
	 * This function will build the selective entity restore UI and return it
	 *
	 * @param Array  $entities - an array of entities to restore (plugins, themes)
	 * @param String $type     - the type of entity (plugins, themes)
	 *
	 * @return String - returns the selective restore UI for this entity
	 */
	private function get_entity_selective_restore_ui($entities, $type) {

		global $updraftplus;

		$backupable_entities = $updraftplus->get_backupable_file_entities(false, true);
		$description = isset($backupable_entities[$type]['singular_description']) ? $backupable_entities[$type]['singular_description'] : $type;
		
		$php_max_input_vars = ini_get("max_input_vars"); // phpcs:ignore PHPCompatibility.IniDirectives.NewIniDirectives.max_input_varsFound -- does not exist in PHP 5.2

		$php_max_input_vars_exceeded = false;
		if (!empty($php_max_input_vars) && count($entities) >= 0.90 * $php_max_input_vars) {
			$php_max_input_vars_exceeded = true;
			// If the amount of tables exceed 90% of the php max input vars then truncate the list to 50% of the php max input vars value
			$entities = array_splice($entities, 0, $php_max_input_vars / 2);
		}

		$selective_restore_ui = '<div class="udp-notice below-h2 updraft-restore-option">';
		$selective_restore_ui .= '<p>'.sprintf(__('If you do not want to restore all your %s files, then de-select the unwanted ones here.', 'updraftplus'), strtolower($description)).' '.__('Files not chosen will not be replaced.', 'updraftplus').'(<a href="#" id="updraftplus_restore_'.$type.'_showmoreoptions">...</a>)</p>';

		$selective_restore_ui .= '<div class="updraftplus_restore_'.$type.'_options_container" style="display:none;">';

		$selective_restore_ui .= '<a class="updraft_restore_select_all_'.$type.'" href="#">'.__('Select all', 'updraftplus').'</a>';
		$selective_restore_ui .= ' | <a class="updraft_restore_deselect_all_'.$type.'" href="#">'.__('Deselect all', 'updraftplus').'</a><br><br>';

		if ($php_max_input_vars_exceeded) {
			$all_other_entity_title = sprintf(__('The amount of %1$s files scanned is near or over the php_max_input_vars value so some %1$s files maybe truncated.', 'updraftplus'), strtolower($description)).' '.sprintf(__('This option will ensure all %s files not found will be restored.', 'updraftplus'), strtolower($description));
			$selective_restore_ui .= '<input class="updraft_restore_'.$type.'_options" id="updraft_restore_'.$type.'_udp_all_other_'.$type.'" checked="checked" type="checkbox" name="updraft_restore_'.$type.'_options[]" value="udp_all_other_'.$type.'"> ';
			$selective_restore_ui .= '<label for="updraft_restore_'.$type.'_udp_all_other_'.$type.'"  title="'.$all_other_entity_title.'">'.sprintf(__('Restore all %s not listed below', 'updraftplus'), strtolower($description)).'</label><br>';
		}

		foreach ($entities as $entity) {
			$selective_restore_ui .= '<input class="updraft_restore_'.$type.'_options" id="updraft_restore_'.$type.'_'.$entity.'" checked="checked" type="checkbox" name="updraft_restore_'.$type.'_options[]" value="'.$entity.'"> ';
			$selective_restore_ui .= '<label for="updraft_restore_'.$type.'_'.$entity.'">'.$entity.'</label><br>';
		}
		$selective_restore_ui .= '</div></div>';

		return $selective_restore_ui;
	}

	/**
	 * This function will get the top level folders (plugins, themes) from the passed in backup archives
	 *
	 * @param Array $backups - an array of backup archives
	 *
	 * @return Array - an array of top level entities inside the backups (plugin, themes)
	 */
	private function get_backup_contents_list($backups) {

		global $updraftplus;
		
		if (!class_exists('UpdraftPlus_PclZip')) updraft_try_include_file('includes/class-zip.php', 'include');

		$updraft_dir = $updraftplus->backups_dir_location();

		$entities = array();

		foreach ($backups as $file) {
			$zip = new UpdraftPlus_PclZip;

			$zipfile = $updraft_dir . '/' . $file;

			if (!$zip->open($zipfile)) return $entities;
	
			// Don't put this in the for loop, or the magic __get() method gets called every time the loop goes round
			$numfiles = $zip->numFiles;

			if (false === $numfiles) $updraftplus->log("get_backup_contents_list(): could not read any files from the zip: (".basename($zipfile).") Zip error: (".$zip->last_error.")");
	
			for ($i=0; $i < $numfiles; $i++) {
				$si = $zip->statIndex($i);
				$folders = explode('/', $si['name']);
				$entity = isset($folders[1]) ? $folders[1] : false;

				// We don't want hidden file system files showing up in the list of entities to restore
				if (!$entity || "." === substr($entity, 0, 1) || "index.php" == $entity) continue;

				if (!in_array($entity, $entities)) {
					$entities[] = $entity;
				}
			}
	
			@$zip->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
		}

		sort($entities);

		return $entities;
	}

	/**
	 * This function is called via the filter updraft_backupable_file_entities_on_restore and is used to filter the restore paths found in $backupable_entities and replace them with the paths saved in the backup set.
	 *
	 * @param  array $backupable_entities - an array of backupable entities and their restore paths
	 * @param  array $restore_options     - the restore options
	 * @param  array $backup_set          - the backup set being restored
	 *
	 * @return array - the filtered backupable_entities array
	 */
	public function backupable_file_entities_on_restore($backupable_entities, $restore_options, $backup_set) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the method is used as a WP filter.

		if (isset($backupable_entities['more']) && isset($backup_set['morefiles_more_locations'])) $backupable_entities['more']['path'] = $backup_set['morefiles_more_locations'];

		return $backupable_entities;
	}

	/**
	 * This function will output the more files jstree row ui for each entity thats not found
	 *
	 * @param integer $key       - the backup file index
	 * @param string  $file      - the backup file name
	 * @param string  $path      - the path to the backup location
	 * @param boolean $not_found - a bool to indicate if the backup location was found or not
	 * @param boolean $split_set - a bool to indicate if this is part of a split set
	 * @param integer $index     - the more files linked indexes index
	 *
	 * @return string - returns the ui
	 */
	public function get_jstree_row_ui($key, $file, $path, $not_found, $split_set, $index) {

		$html = '';
		$hidden = $split_set ? 'style="display:none;"' : '';
		$location = empty($path) ? $file : $path;
		
		$index_checkbox = '<input type="checkbox"  ' . $hidden . ' data-mp_index="' . esc_attr($key) . '" data-set_index="' . esc_attr($index) . '" id="updraft_include_more_index_' . esc_attr($key) . '" class="updraft_include_more_index" name="updraft_include_more_index[]" value="' . esc_attr($key) . '"/>';
		
		if ($split_set) return $index_checkbox;

		if ($not_found) {
			$html .= '<div class="updraftplus-morefiles-row"><label for="updraft_include_more_path_restore_' . esc_attr($key) . '">' . __('Restore location does not exist on the filesystem for:', 'updraftplus') . '<br>' . esc_attr($location) . '</label>';
		} else {
			$html .= '<div class="updraftplus-morefiles-row"><label for="updraft_include_more_path_restore_' . esc_attr($key) . '">' . __('Restore location found for:', 'updraftplus') . '<br>' . esc_attr($location) . '</label>';
		}
		$html .= '<br>'.$index_checkbox;
		$html .= '<input type="text" data-mp_index="' . esc_attr($key) . '" data-set_index="' . esc_attr($index) . '" id="updraft_include_more_path_restore_' . esc_attr($key) . '" class="updraft_include_more_path" name="updraft_include_more_path[]" size="54" value="' . esc_attr($path) . '" title="' . esc_attr($path) . '" readonly="readonly"/> <a href="#" title="' . __('Edit', 'updraftplus') . '" class="updraftplus-morefiles-row-edit dashicons dashicons-edit hidden-in-updraftcentral"></a></div>';
		
		return $html;
	}

	/**
	 * This function will output the more file jstree ui and assign unique id's using the passed in page
	 *
	 * @param string $page - the page we are displaying the ui on
	 *
	 * @return string - the more files jstree ui
	 */
	public function get_jstree_ui($page) {
		$ret = '';
		if ('options' == $page) $ret .= '<div><a href="#" id="updraft_include_more_paths_another" class="updraft_icon_link"><span class="dashicons dashicons-plus"></span>' . esc_html__('Add directory...', 'updraftplus') . '</a></div>';
		$ret .= '<div id="updraft_more_files_container_'. esc_attr($page) .'" class="hidden-in-updraftcentral" style="clear:left;">
					<div id="updraft_jstree_container_'. esc_attr($page) .'">
						<button class="button" id="updraft_parent_directory_'. esc_attr($page) .'" title="' . esc_attr__('Go up a directory', 'updraftplus') . '"><span class="dashicons dashicons-arrow-up-alt"></span>' . esc_html__('Go up a directory', 'updraftplus') . '</button>
						<div id="updraft_more_files_jstree_'. esc_attr($page).'"></div>
					</div>
					<div id="updraft_jstree_buttons_'. esc_attr($page).'">
						<button class="button" id="updraft_jstree_cancel_'. esc_attr($page).'">' . esc_html__('Cancel', 'updraftplus') . '</button> 
						<button class="button button-primary" id="updraft_jstree_confirm_'. esc_attr($page) .'">' . esc_html__('Confirm', 'updraftplus') . '</button>
					</div>
				</div>';
		return $ret;
	}

	/**
	 * This function will output any needed js for the more files addon.
	 *
	 * @return void
	 */
	public function admin_footer_more_files_js() {
		?>
		<script>
		jQuery(function() {
			<?php
				$paths = UpdraftPlus_Options::get_updraft_option('updraft_include_more_path');
				if (!is_array($paths)) $paths = array($paths);
				$maxind = max(count($paths) - 1, 1);
				$maxind++;
				$edit = __('Edit', 'updraftplus');
				$remove = __('Remove', 'updraftplus');
				$placeholder = __('Please choose a file or directory', 'updraftplus');
			?>
			var updraftplus_morefiles_lastind = <?php echo (int) $maxind; ?>;
			var edit = "<?php echo esc_js($edit); ?>";
			var remove = "<?php echo esc_js($remove); ?>";
			var placeholder = "<?php echo esc_js($placeholder); ?>";
			jQuery('#updraft_include_more').on('click', function() {
				if (jQuery('#updraft_include_more').is(':checked')) {
					jQuery('#updraft_include_more_options').slideDown();
				} else {
					jQuery('#updraft_include_more_options').slideUp();
				}
			});

			jQuery('#updraft_include_more_paths_another').on('click', function(e) {
				e.preventDefault();
				updraftplus_morefiles_lastind++;
				jQuery('#updraft_include_more_paths').append('<div class="updraftplus-morefiles-row"><label for="updraft_include_more_path_' + updraftplus_morefiles_lastind + '"></label><input type="text" class="updraft_more_path_editing" id="updraft_include_more_path_' + updraftplus_morefiles_lastind + '" name="updraft_include_more_path[]" size="54" placeholder="' + placeholder + '" value="" title="' + placeholder + '" readonly/> <a href="#" title="' + edit + '" class="updraftplus-morefiles-row-edit dashicons dashicons-edit hidden-in-updraftcentral"></a> <a href="#" title="' + remove + '" class="updraftplus-morefiles-row-delete dashicons dashicons-no"></a></div>');
				more_files_jstree('filebrowser', '', false, 'options');
			});

			/**
			 * Creates the jstree and makes a call to the backend to dynamically get the tree nodes
			 * 
			 * @param {string} entity - the type of jstree this is
			 * @param {string} path - Optional path parameter if not passed in then ABSPATH will be used
			 * @param {bool} drop_directory - Optional parameter that if passed will remove the last directory level from the path, used for if you want to move up the directory tree from the root node
			 * @param {string} page - the page this jstree is being created on
			 */
			function more_files_jstree(entity, path, drop_directory, page) {
				if ('options' == page) jQuery('#updraft_include_more_paths_another').hide();
				jQuery('#updraft_more_files_container_'+page).insertAfter(jQuery('.updraft_more_path_editing').closest('.updraftplus-morefiles-row')).show();
				jQuery('#updraft_jstree_cancel_'+page).show();
				jQuery('#updraft_jstree_confirm_'+page).show();
				jQuery('.updraft_more_path_editing').data('previous-value', jQuery('.updraft_more_path_editing').val());
				jQuery('#updraft_more_files_jstree_'+page).jstree({
					"core": {
						"multiple": false,
						"data": function (nodeid, callback) {
							updraft_send_command('get_jstree_directory_nodes', {entity:entity, node:nodeid, path:path, drop_directory:drop_directory, page:page}, function(response) {
								if (response.hasOwnProperty('error')) {
									jQuery('#updraft_include_more_paths_error').text(response.error);
								} else {
									jQuery('#updraft_include_more_paths_error').text('');
									callback.call(this, response.nodes);
								}
							});
						}
					},
					'plugins' : ['sort','types'],
					'sort' : function(a, b) {
						a1 = this.get_node(a);
						b1 = this.get_node(b);
						if (a1.icon == b1.icon){
							return (a1.text > b1.text) ? 1 : -1;
						} else {
							return (a1.icon < b1.icon) ? 1 : -1;
						}
					},
				});

				// Detect change on the tree and update the input that has been marked as editing
				jQuery('#updraft_more_files_jstree_'+page).on("changed.jstree", function (e, data) {
					if ('restore' == page) {
						var group = jQuery('.updraft_more_path_editing').data('set_index');
						var textboxes = jQuery('input[type="text"][data-set_index="' + group + '"]');
						textboxes.val(data.selected[0]);
						textboxes.attr('title', data.selected[0]);
					} else {
						jQuery('.updraft_more_path_editing').val(data.selected[0]);
						jQuery('.updraft_more_path_editing').attr('title', data.selected[0]);
					}
				});
			}

			// Cancel the selection and clean up the UI
			jQuery('#updraft-restore-modal-stage2a').on('click', '#updraft_jstree_cancel_restore', function(e) {
				e.preventDefault();
				// reset value on cancel
				jQuery('.updraft_more_path_editing').val(jQuery('.updraft_more_path_editing').data('previous-value'));
				cleanup_jstree_ui('restore');
			});

			// Cancel the selection and clean up the UI
			jQuery('#updraft_jstree_cancel_options').on('click', function(e) {
				e.preventDefault();
				// reset value on cancel
				jQuery('.updraft_more_path_editing').val(jQuery('.updraft_more_path_editing').data('previous-value'));
				cleanup_jstree_ui('options');
			});

			// Grabs all selected paths and outputs them to the page ready to be saved then updates the UI and removes the tree object
			jQuery('#updraft-restore-modal-stage2a').on('click', '#updraft_jstree_confirm_restore', function(e) {
				e.preventDefault();
				cleanup_jstree_ui('restore');
			});

			// Grabs all selected paths and outputs them to the page ready to be saved then updates the UI and removes the tree object
			jQuery('#updraft_jstree_confirm_options').on('click', function(e) {
				e.preventDefault();
				cleanup_jstree_ui('options');
			});

			/**
			 * Cleans the UI and removes the jstree
			 */
			function cleanup_jstree_ui(page) {
				jQuery('#updraft_more_files_container_'+page).hide();
				jQuery('#updraft_jstree_cancel_'+page).hide();
				jQuery('#updraft_jstree_confirm_'+page).hide();
				if ('options' == page) jQuery('#updraft_include_more_paths_another').show();
				
				// if the new item is cancelled, remove the row
				if ('' == jQuery('.updraft_more_path_editing').val() && 'restore' != page) {
					jQuery('.updraft_more_path_editing').closest('.updraftplus-morefiles-row').remove();
				}

				jQuery('#updraft-restore-modal-stage2a .updraftplus-morefiles-row > .updraft_more_path_editing').removeClass('updraft_more_path_editing');
				jQuery('#updraft_include_more_paths > .updraftplus-morefiles-row > .updraft_more_path_editing').removeClass('updraft_more_path_editing');
				jQuery('#updraft_more_files_jstree_'+page).jstree("destroy").empty();
			}

			// Removes the current tree object and creates a new tree one directory above
			jQuery('#updraft-restore-modal-stage2a').on('click', '#updraft_parent_directory_restore', function(e) {
				e.preventDefault();
				jstree_parent_directory('restore');
			});

			// Removes the current tree object and creates a new tree one directory above
			jQuery('#updraft_parent_directory_options').on('click', function(e) {
				e.preventDefault();
				jstree_parent_directory('options');
			});

			/**
			 * Moves the jstree up a directory
			 *
			 * @param {string} page - the page this jstree is being created on
			 */
			function jstree_parent_directory(page) {
				var parent_node_id = jQuery('#updraft_more_files_jstree_'+page+' ul > li').first().attr('id');
				jQuery('#updraft_more_files_jstree_'+page).jstree("destroy").empty();
				more_files_jstree('filebrowser', parent_node_id, true, page);
			}

			jQuery('#updraft-restore-modal-stage2a').on('click', '.updraftplus-morefiles-row-edit', function(e) {
				e.preventDefault();
				// Clean up the UI just in case
				cleanup_jstree_ui('restore');

				var prow = jQuery(this).parent('.updraftplus-morefiles-row').children('input[type="text"]');
				jQuery(prow).addClass('updraft_more_path_editing');

				var drop_directory = true
				if (jQuery(prow).val() == '') drop_directory = '';
				more_files_jstree('filebrowser', jQuery(prow).val(), drop_directory, 'restore');
			});
			
			jQuery('#updraft_include_more_options').on('click', '.updraftplus-morefiles-row-edit', function(e) {
				e.preventDefault();
				
				// Clean up the UI just in case
				cleanup_jstree_ui('options');

				var prow = jQuery(this).parent('.updraftplus-morefiles-row').children('input');
				jQuery(prow).addClass('updraft_more_path_editing');

				var drop_directory = true
				if (jQuery(prow).val() == '') drop_directory = '';
				more_files_jstree('filebrowser', jQuery(prow).val(), drop_directory, 'options');
			});

			jQuery('#updraft_include_more_options').on('click', '.updraftplus-morefiles-row-delete', function(e) {
				e.preventDefault();

				var prow = jQuery(this).parent('.updraftplus-morefiles-row');

				// Check if the one being deleted is being edited if so cleanup the UI
				if (jQuery(prow).children('input').hasClass('updraft_more_path_editing')) {
					cleanup_jstree_ui('options');
				}
				jQuery(prow).slideUp().delay(400).remove();
			});

			add_readonly();

			function add_readonly() {
				jQuery('#updraft_include_more_paths').find('input').each(function() {
					jQuery(this).attr('readonly','readonly');
				});
			}

			jQuery('#updraft-restore-modal-stage2a').on('click', '.updraft_include_more_paths input[type="checkbox"][data-set_index]', function() {
				var checked = jQuery(this).prop('checked');
				var group = jQuery(this).data('set_index');
				var checkboxes = jQuery('input[type="checkbox"][data-set_index="' + group + '"]');
				var othercheckboxes = checkboxes.not(this);
				othercheckboxes.prop('checked', checked);
			});
		});
		</script>
		<?php
	}
}
