<?php

// Exit if accessed directly
if (! defined('ABSPATH')) exit;

/**
 * Modify WordPress core plugin installer
 *
 * @package catch-updater
 *
 * @since catch-updater 0.1
 */

if (! class_exists('CatchUpdaterModifyPluginInstaller')) {
	class CatchUpdaterModifyPluginInstaller
	{
		public $_errors 		= array();

		public $_backup_status = 0;

		/**
		 * CatchUpdaterModifyPluginInstaller _constructor to enqueue scripts, and modify output of the plugin-install page
		 * @uses add_action
		 * @hooks  admin_init, load-plugin-install.php, admin_enqueue_scripts
		 */
		public function __construct()
		{
			// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended -- REQUEST_URI is used only for routing; no user data is extracted.
			if (isset($_SERVER['REQUEST_URI']) && preg_match('/update\.php/', wp_unslash($_SERVER['REQUEST_URI'])) && isset($_REQUEST['action'])) {
				add_action('admin_init', array($this, 'handle_updates'), 100);
			}
			// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended

			add_action('load-plugin-install.php', array($this, 'start_output_buffering'));
		}

		/**
		 * filter_output filters the output of plugin-install page and modifies the content
		 * @param  $output the contents of current output buffer
		 * @return modified output
		 * @uses preg_replace
		 */
		public function filter_output($output)
		{
			$text = '<div id="catch-web-tools-main">';

			$text .= '<h3>' . esc_html__('CWT: Catch Updater Plugin', 'catch-web-tools') . '</h3>';

			$output = preg_replace('/(<input [^>]*name="(?:plugin)zip".+?\n)/', "$text\$1", $output);

			$text = '<p><i>' . esc_html__('By default, the Catch Updater will overwrite an existing plugin and create a backup in media library.', 'catch-web-tools') . '</i></p>';

			$text .= '<a class="button button-primary" id="more_options_show_button" />' . esc_html__('More Options', 'catch-web-tools') . '</a>';

			$text .= '<a class="button button-primary" id="more_options_hide_button"  style="display: none;" />' . esc_html__('Less Options', 'catch-web-tools') . '</a>';

			$text .= '<div id="more_options">';
			$text .= '<p><label>' . esc_html__('Update existing Plugin? ', 'catch-web-tools') . '</label>
								<select name="catch_updater_plugin_update_existing">
									<option value="yes">' . esc_html__('Yes', 'catch-web-tools') . '</option>
									<option value="no">' . esc_html__('No', 'catch-web-tools') . '</option>
								</select>
							</p>';

			$text .= '<p><label>' . esc_html__('Create Backup? ', 'catch-web-tools') . '</label>
								<select name="catch_updater_plugin_create_backup">
									<option value="yes">' . esc_html__('Yes', 'catch-web-tools') . '</option>
									<option value="no">' . esc_html__('No', 'catch-web-tools') . '</option>
								</select>
							</p>';

			$text .= '<p>' . esc_html__('Message to display in front-end until update has finished', 'catch-web-tools') . '
								<textarea name="catch_updater_plugin_update_message">' . esc_html__('The site is being updated and will be back in a few minutes.', 'catch-web-tools') . '</textarea>
							</p>';
			$text .= '</div>';
			$text .= '</div>';

			$output = preg_replace('/(<input [^>]*name="pluginzip".+?\n)/', "\$1$text", $output);

			return $output;
		}

		/**
		 * start_output_buffering Just start outpur buffering
		 * @uses  $this->filter_output()
		 */
		public function start_output_buffering()
		{
			ob_start(array($this, 'filter_output'));
		}

		/**
		 * _get_plugin_data gets the data for the plugin passed as parameter
		 * @param  $directory plugin of which, data in required
		 * @return plugin data
		 */
		public function _get_plugin_data($directory)
		{
			$data           = array();

			$plugins        = get_plugins();

			$current_plugin = array();

			foreach ((array) $plugins as $plugin_file => $plugin_data) {
				preg_match('^(.*?)\/^', $plugin_file, $matches);
				if ($matches[1] === $directory) {
					$current_plugin = $plugin_data;
					$data['directory'] 	= WP_PLUGIN_DIR . '/' . $directory;
					break;
				}
			}

			if (empty($current_plugin))
				return $data;

			$data['version'] 	= $current_plugin['Version'];

			$data['name'] 		= $current_plugin['Name'];

			return $data;
		}

		/**
		 * handle_updates main function that handles upgrades
		 */
		public function handle_updates()
		{
			if (empty($_POST['catch_updater_plugin_update_existing'])) {
				return;
			}

			if ('no' === sanitize_text_field(wp_unslash($_POST['catch_updater_plugin_update_existing']))) {
				if (version_compare($GLOBALS['wp_version'], '3.8.9', '>')) {
					$link = admin_url('plugin-install.php?upload');
				} else {
					$link = admin_url('plugin-install.php?tab=upload');
				}

				$this->_errors[] = wp_kses_post(
					sprintf(
						/* translators: %1$s: opening anchor tag, %2$s: closing anchor tag */
						__('You must select "Yes" from the "update existing plugin?" dropdown option in order to update an existing plugin. %1$sTry again%2$s.', 'catch-web-tools'),
						'<a href="' . esc_url($link) . '">',
						'</a>'
					)
				);
				add_action('admin_notices', array($this, 'show_update_option_error_message'));

				return;
			}

			remove_action('admin_print_styles', 'builder_add_global_admin_styles');

			require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';

			require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';

			require_once ABSPATH . 'wp-admin/includes/file.php';

			check_admin_referer('plugin-upload');

			@set_time_limit(300); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,Squiz.PHP.DiscouragedFunctions.Discouraged -- set_time_limit() is safe to silence and necessary for long-running zip operations.

			$archive 	= new PclZip($_FILES['pluginzip']['tmp_name']); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- tmp_name is a server-generated path, not user input.

			$directory 	= '';

			$contents 	= $archive->listContent();

			foreach ((array) $contents as $content) {
				if (preg_match('^(.*?)\/^', $content['filename'], $matches)) {
					$directory = $matches[1];
					break;
				}
			}

			$data = $this->_get_plugin_data($directory);

			if (empty($data))
				return;

			if ('yes' === sanitize_text_field(wp_unslash($_POST['catch_updater_plugin_create_backup']))) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
				$time_string 	= time();

				$zip_file 		= "$directory-{$data['version']}-$time_string.zip";

				$wp_upload_dir 	= wp_upload_dir();

				$zip_path 		= $wp_upload_dir['path'] . '/' . $zip_file;

				$zip_url 		= $wp_upload_dir['url'] . '/' . $zip_file;

				$archive 		= new PclZip($zip_path);

				$zip_result 	= $archive->create($data['directory'], PCLZIP_OPT_REMOVE_PATH, dirname($data['directory']));

				if (0 === $zip_result) {
					$this->_errors[] = esc_html__('Unable to make a backup of the existing plugin. Will not proceed with the update.', 'catch-web-tools');
					add_action('admin_notices', array($this, 'show_update_option_error_message'));

					return;
				}

				$attachment = array(
					'post_mime_type'	=> 'application/zip',
					'guid'				=> $zip_url,
					'post_title'		=> "Catch Updater Backup - {$data['name']} - {$data['version']}",
					'post_content'		=> '',
				);

				$id = wp_insert_attachment($attachment, $zip_path);
				if (!is_wp_error($id))
					wp_update_attachment_metadata($id, wp_generate_attachment_metadata($id, $zip_path));

				$this->_zip_url 		= $zip_url;

				$this->backup_status 	= 1;
			}

			// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- $wp_filesystem must be global to use WP_Filesystem().
			global $wp_filesystem;

			if (! WP_Filesystem()) {
				$this->_errors[] = esc_html__('Unable to initialize WP_Filesystem. Will not proceed with the update.', 'catch-web-tools');
				add_action('admin_notices', array($this, 'show_update_option_error_message'));

				return;
			}

			if (! $wp_filesystem->delete($data['directory'], true)) {
				$this->_errors[] = esc_html__('Unable to remove the existing plugin directory. Will not proceed with the update.', 'catch-web-tools');
				add_action('admin_notices', array($this, 'show_update_option_error_message'));

				return;
			}

			add_action('all_admin_notices', array($this, 'show_message'));
		}

		/**
		 * show_message display message
		 */
		public function show_message()
		{
			if (isset($this->_zip_url)) {
				echo '<div id="message" class="updated fade">
						<p>
							<strong>' . wp_kses_post(
					sprintf(
						/* translators: %1$s: opening anchor tag, %2$s: closing anchor tag */
						__('A backup zip file of the old plugin version can be downloaded %1$shere%2$s.', 'catch-web-tools'),
						'<a href="' . esc_url($this->_zip_url) . '">',
						'</a>'
					)
				) . '</strong>
						</p>
					</div>';
			} else {
				echo '<div id="message" class="updated fade">
						<p>
							<strong>' . esc_html__('Update Successful. No backup created.', 'catch-web-tools') . '</strong>
						</p>
					</div>';
			}
		}

		/**
		 * show_update_option_error_message show error message if error occurs
		 */
		public function show_update_option_error_message()
		{
			if (! isset($this->_errors)) {
				return;
			}

			if (! is_array($this->_errors))
				$this->_errors = array($this->_errors);

			foreach ((array) $this->_errors as $error)
				echo '<div id="message" class="error"><p><strong>' . wp_kses_post($error) . '</strong></p></div>' . "\n";
		}
	}

	new CatchUpdaterModifyPluginInstaller();
}
