<?php
/**
 * File: class-boldgrid-backup-admin-cron-test.php
 *
 * @link https://www.boldgrid.com
 * @since 1.6.5
 *
 * @package    Boldgrid_Backup
 * @subpackage Boldgrid_Backup/admin
 * @copyright  BoldGrid
 * @version    $Id$
 * @author     BoldGrid <support@boldgrid.com>
 */

/**
 * Class: Boldgrid_Backup_Admin_Cron_Test
 *
 * @since 1.6.5
 */
class Boldgrid_Backup_Admin_Cron_Test {

	/**
	 * The core class object.
	 *
	 * @since 1.6.5
	 * @access private
	 * @var Boldgrid_Backup_Admin_Core
	 */
	private $core;

	/**
	 * Path to config file.
	 *
	 * @since 1.6.5
	 * @access private
	 * @var string
	 */
	private $cron_config_path;

	/**
	 * Path to php file, which is ran via cron.
	 *
	 * @since 1.6.5
	 * @access private
	 * @var string
	 */
	private $cron_path;

	/**
	 * Path to results.
	 *
	 * @since 1.6.5
	 * @access private
	 * @var string
	 */
	private $cron_result_path;

	/**
	 * Constructor.
	 *
	 * @since 1.6.5
	 *
	 * @param Boldgrid_Backup_Admin_Core $core Core class object.
	 */
	public function __construct( $core ) {
		$this->core = $core;

		$this->cron_path        = BOLDGRID_BACKUP_PATH . '/cron/cron-test.php';
		$this->cron_config_path = BOLDGRID_BACKUP_PATH . '/cron/cron-test.config';
		$this->cron_result_path = BOLDGRID_BACKUP_PATH . '/cron/cron-test.result';
	}

	/**
	 * Clean up the test.
	 *
	 * Delete the config file and remove the cron entries.
	 *
	 * @since 1.6.5
	 */
	public function clean_up() {
		wp_delete_file( $this->cron_config_path );
		$this->core->cron->entry_delete_contains( $this->cron_path );
	}

	/**
	 * Generate the markup used on the prefligh check page.
	 *
	 * Ensure markup generated by this method are compatible with $allowed_tags configured in
	 * admin/partials/boldgrid-backup-admin-test.php.
	 *
	 * @since 1.6.5
	 *
	 * @return string
	 */
	public function get_preflight_markup() {
		$server_offset = $this->core->time->get_server_offset();
		$cron_offset   = $this->get_offset();
		$offset_match  = (int) $server_offset === (int) $cron_offset;
		$markup        = '';

		$run_test = '<p>
			<form method="post" action="admin.php?page=boldgrid-backup-test">
				<input type="hidden" name="cron_timezone_test" value="1" />
				<input type="submit" value="%1$s" class="button" style="vertical-align:baseline;" /> (' . __( 'Test may take up to 25 minutes to complete', 'boldgrid-backup' ) . ')
				' . wp_nonce_field( 'cron_timezone_test', '_wpnonce', true, false ) . '
			</form>
		</p>';

		$no = sprintf(
			'<span class="warning">%1$s</span><br />%2$s',
			__( 'No', 'boldgrid-backup' ),
			__( 'Please contact your server administrator for assistance with troubleshooting.' )
		);

		if ( $this->is_running() ) {
			$markup .= '<p><span class="spinner inline"></span>' . __( 'This test is in progress, and may take up to 25 minutes to complete. Refresh this page for an update.', 'boldgrid-backup' ) . '</p>';
		} elseif ( false === $cron_offset ) {
			$markup .= sprintf( $run_test, esc_attr( __( 'Run test', 'boldgrid-backup' ) ) );
		} else {
			$match_markup = ( $offset_match ? __( 'Yes', 'boldgrid-backup' ) : $no );
			$markup       = $match_markup . $markup;

			$markup .= '<p>' . __( 'Server offset', 'boldgrid-backup' ) . ': ' . $server_offset . '<br />';
			$markup .= __( 'Cron offset' ) . ': ' . $cron_offset . '</p>';

			$markup .= sprintf( $run_test, esc_attr( __( 'Run test again', 'boldgrid-backup' ) ) );
		}

		// Help info, displayed by clicking the help icon.
		$markup .= '<p class="help" data-id="cron-time-zone">' . __( 'Cron should run in the same time zone as your server. For example, if your server timezone is EST, setting a cron to run at 5am should run it at 5am EST. This test runs a series of cron jobs to determine which timezone is used by Cron.', 'boldgrid-backup' ) . '</p>';

		return $markup;
	}

	/**
	 * Get the cron offset.
	 *
	 * This looks in the cron-test.result file for the data.
	 *
	 * @since 1.6.5
	 *
	 * @return mixed
	 */
	public function get_offset() {
		$result = $this->core->wp_filesystem->get_contents( $this->cron_result_path );
		if ( ! $result ) {
			return false;
		}

		$result = json_decode( $result, true );

		return isset( $result['offset'] ) ? $result['offset'] : false;
	}

	/**
	 * Whether or not the test is currently running.
	 *
	 * @since 1.6.5
	 *
	 * @return bool
	 */
	public function is_running() {
		$config_file_exists = $this->core->wp_filesystem->exists( $this->cron_config_path );

		$matching_crons = $this->core->cron->entry_search( $this->cron_path );

		return false === $this->get_offset() && $config_file_exists && ! empty( $matching_crons );
	}

	/**
	 * Setup the tests.
	 *
	 * HERE'S THE PROBLEM:
	 * Cron is supposed to run using the server's timezone. In one scenario, the crons were not running
	 * when we expected them to. Further troubleshooting found that the server was set to PDT and the
	 * Crons were running at EDT times. So, our 5am PDT cron was actually running at 5am EDT. It took
	 * a while to figure this out, so this test was built.
	 *
	 * HERE'S HOW THIS TEST WORKS:
	 * We get the current UTC time. Let's say it's 3:15 pm. We then setup the following crons:
	 *
	 * # 3:18 pm (UTC)
	 * # 4:19 pm (UTC+1)
	 * # 5:20 pm (UTC+2)
	 *
	 * We don't know when each of these crons will actually run, but they're basically 1 hour and
	 * 1 minute apart. When the first of these crons actually does run, the script it triggers (cron-test.php)
	 * will look at the current MINUTE. Using the example above, if the minute is 19, then we know cron
	 * is running on UTC+1 time. If the minute is 20, then we know we're running at UTC+2.
	 *
	 * The array of times to offsets (like 4:19 pm = UTC+1) is stored in the cron-test.config file.
	 *
	 * @since 1.6.5
	 */
	public function setup() {

		// Delete the log, the config file, and all existing crons.
		$this->clean_up();
		wp_delete_file( $this->cron_result_path );

		$time_data     = array();
		$unix_time     = time();
		$minutes_ahead = 3;
		// When looping through offsets, begin at the user's offset so test completes faster.
		$server_offset = (int) $this->core->time->get_server_offset();

		/*
		 * Add our cron jobs.
		 *
		 * Currently, we are only doing hour increments. A few 30 & 45 minute offsets exist, but this
		 * test is really for edge cases, and those 30/45 offsets are edge cases too. We'll get to those
		 * later.
		 *
		 * @see https://www.timeanddate.com/time/time-zones-interesting.html
		 */
		for ( $x = 1; $x <= 25; $x++ ) {
			$cron_time = $unix_time + ( $server_offset * 60 * 60 ) + ( $minutes_ahead * 60 );

			$time_data[] = array(
				'time'   => $cron_time,
				'offset' => $server_offset,
			);

			// Add our cron. Use (int) to make sure our minutes do not have leading zero's.
			$cron_command    = (int) date( 'i', $cron_time ) . ' ' . date( 'G * * *', $cron_time ) . ' php -d register_argc_argv=1 -qf ' . $this->cron_path . ' > /dev/null 2>&1';
			$all_crons_added = $this->core->cron->update_cron( $cron_command );

			if ( ! $all_crons_added ) {
				break;
			}

			$minutes_ahead++;
			// Server offset must be between -12 and 12.
			$server_offset++;
			$server_offset = $server_offset > 12 ? -12 : $server_offset;
		}

		if ( $all_crons_added ) {
			$this->core->wp_filesystem->put_contents( $this->cron_config_path, wp_json_encode( $time_data ) );
		} else {
			$error = __( 'Failed to setup Cron time zone test.', 'boldgrid-backup' );
			$this->core->notice->boldgrid_backup_notice( $error );
			$this->clean_up();
		}
	}
}
