File "event-20250613002658.php"

Full Path: /home/digimqhe/flashdigi.uk/comment-content/plugins/wp-crontrol/src/event-20250613002658.php
File size: 14.42 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * Functions related to cron events.
 */

namespace Crontrol\Event;

use stdClass;
use Crontrol\Schedule;
use WP_Error;

use const Crontrol\PAUSED_OPTION;

/**
 * Executes a cron event immediately.
 *
 * Executes an event by scheduling a new single event with the same arguments.
 *
 * @param string $hookname The hook name of the cron event to run.
 * @param string $sig      The cron event signature.
 * @return true|WP_Error True if the execution was successful, WP_Error if not.
 */
function run( $hookname, $sig ) {
	$crons = get_core_cron_array();

	foreach ( $crons as $time => $cron ) {
		if ( isset( $cron[ $hookname ][ $sig ] ) ) {
			$event = $cron[ $hookname ][ $sig ];

			$event['hook'] = $hookname;
			$event['timestamp'] = $time;

			$event = (object) $event;

			delete_transient( 'doing_cron' );
			$scheduled = force_schedule_single_event( $hookname, $event->args ); // UTC

			if ( is_wp_error( $scheduled ) ) {
				return $scheduled;
			}

			add_filter( 'cron_request', function ( array $cron_request_array ) {
				$cron_request_array['url'] = add_query_arg( 'crontrol-single-event', 1, $cron_request_array['url'] );
				return $cron_request_array;
			} );

			spawn_cron();

			sleep( 1 );

			/**
			 * Fires after a cron event is scheduled to run manually.
			 *
			 * @param stdClass $event {
			 *     An object containing the event's data.
			 *
			 *     @type string       $hook      Action hook to execute when the event is run.
			 *     @type int          $timestamp Unix timestamp (UTC) for when to next run the event.
			 *     @type string|false $schedule  How often the event should subsequently recur.
			 *     @type array        $args      Array containing each separate argument to pass to the hook's callback function.
			 *     @type int          $interval  The interval time in seconds for the schedule. Only present for recurring events.
			 * }
			 */
			do_action( 'crontrol/ran_event', $event );

			return true;
		}
	}

	return new WP_Error(
		'not_found',
		sprintf(
			/* translators: %s: The name of the cron event. */
			__( 'The cron event %s could not be found.', 'wp-crontrol' ),
			$hookname
		)
	);
}

/**
 * Forcibly schedules a single event for the purpose of manually running it.
 *
 * This is used instead of `wp_schedule_single_event()` to avoid the duplicate check that's otherwise performed.
 *
 * @param string  $hook Action hook to execute when the event is run.
 * @param mixed[] $args Optional. Array containing each separate argument to pass to the hook's callback function.
 * @return true|WP_Error True if event successfully scheduled. WP_Error on failure.
 */
function force_schedule_single_event( $hook, $args = array() ) {
	$event = (object) array(
		'hook'      => $hook,
		'timestamp' => 1,
		'schedule'  => false,
		'args'      => $args,
	);
	$crons = get_core_cron_array();
	$key   = md5( serialize( $event->args ) );

	$crons[ $event->timestamp ][ $event->hook ][ $key ] = array(
		'schedule' => $event->schedule,
		'args'     => $event->args,
	);
	ksort( $crons );

	$result = _set_cron_array( $crons );

	// Not using the WP_Error from `_set_cron_array()` here so we can provide a more specific error message.
	if ( false === $result ) {
		return new WP_Error(
			'could_not_add',
			sprintf(
				/* translators: %s: The name of the cron event. */
				__( 'Failed to schedule the cron event %s.', 'wp-crontrol' ),
				$hook
			)
		);
	}

	return true;
}

/**
 * Adds a new cron event.
 *
 * @param string  $next_run_local The time that the event should be run at, in the site's timezone.
 * @param string  $schedule       The schedule of the cron event.
 * @param string  $hook           The name of the hook to execute.
 * @param mixed[] $args           Arguments to add to the cron event.
 * @phpstan-param list<mixed> $args
 * @return true|WP_error True if the addition was successful, WP_Error otherwise.
 */
function add( $next_run_local, $schedule, $hook, array $args ) {
	/**
	 * @var int
	 */
	$current_time = current_time( 'timestamp' );
	$next_run_local = strtotime( $next_run_local, $current_time );

	if ( false === $next_run_local ) {
		return new WP_Error(
			'invalid_timestamp',
			__( 'Invalid timestamp provided.', 'wp-crontrol' )
		);
	}

	$next_run_utc = (int) get_gmt_from_date( gmdate( 'Y-m-d H:i:s', $next_run_local ), 'U' );

	if ( 'crontrol_cron_job' === $hook && ! empty( $args[0]['code'] ) ) {
		try {
			/**
			 * The call to `eval()` below checks the syntax of the PHP code provided in the cron event. This is done to
			 * add a flag to a cron event that contains invalid PHP code, so that the user can be informed of the syntax
			 * error when viewing the event in the list table.
			 *
			 * Security: The code is not executed due to the early return statement that precedes it. The code is only
			 * checked for syntax correctness.
			 */
			// phpcs:ignore Squiz.PHP.Eval.Discouraged
			eval( sprintf(
				'return true; %s',
				$args[0]['code']
			) );
		} catch ( \ParseError $e ) {
			$args[0]['syntax_error_message'] = $e->getMessage();
			$args[0]['syntax_error_line'] = $e->getLine();
		}
	}

	if ( '_oneoff' === $schedule || '' === $schedule ) {
		$result = wp_schedule_single_event( $next_run_utc, $hook, $args, true );
	} else {
		$result = wp_schedule_event( $next_run_utc, $schedule, $hook, $args, true );
	}

	if ( is_wp_error( $result ) ) {
		return $result;
	}

	return true;
}

/**
 * Deletes a cron event.
 *
 * @param string $hook         The hook name of the event to delete.
 * @param string $sig          The cron event signature.
 * @param string $next_run_utc The UTC time that the event would be run at.
 * @return true|WP_Error True if the deletion was successful, WP_Error otherwise.
 */
function delete( $hook, $sig, $next_run_utc ) {
	$event = get_single( $hook, $sig, $next_run_utc );

	if ( is_wp_error( $event ) ) {
		return $event;
	}

	$unscheduled = wp_unschedule_event( $event->timestamp, $event->hook, $event->args, true );

	if ( is_wp_error( $unscheduled ) ) {
		return $unscheduled;
	}

	return true;
}

/**
 * Pauses a cron event.
 *
 * @param string $hook The hook name of the event to pause.
 * @return true|WP_Error True if the pause was successful, WP_Error otherwise.
 */
function pause( $hook ) {
	$paused = get_option( PAUSED_OPTION, array() );

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

	$paused[ $hook ] = true;

	$result = update_option( PAUSED_OPTION, $paused, true );

	if ( false === $result ) {
		return new WP_Error(
			'could_not_pause',
			sprintf(
				/* translators: %s: The name of the cron event. */
				__( 'Failed to pause the cron event %s.', 'wp-crontrol' ),
				$hook
			)
		);
	}

	return true;
}

/**
 * Resumes a paused cron event.
 *
 * @param string $hook The hook name of the event to resume.
 * @return true|WP_Error True if the resumption was successful, WP_Error otherwise.
 */
function resume( $hook ) {
	$paused = get_option( PAUSED_OPTION );

	if ( ! is_array( $paused ) || ( count( $paused ) === 0 ) ) {
		return true;
	}

	unset( $paused[ $hook ] );

	$result = update_option( PAUSED_OPTION, $paused, true );

	if ( false === $result ) {
		return new WP_Error(
			'could_not_resume',
			sprintf(
				/* translators: %s: The name of the cron event. */
				__( 'Failed to resume the cron event %s.', 'wp-crontrol' ),
				$hook
			)
		);
	}

	return true;
}

/**
 * Returns a flattened array of cron events.
 *
 * @return array<string,stdClass> An array of cron event objects keyed by unique signature.
 */
function get() {
	$crons  = get_core_cron_array();
	$events = array();

	if ( empty( $crons ) ) {
		return array();
	}

	foreach ( $crons as $time => $cron ) {
		foreach ( $cron as $hook => $dings ) {
			foreach ( $dings as $sig => $data ) {

				// This is a prime candidate for a Crontrol_Event class but I'm not bothering currently.
				$events[ "$hook-$sig-$time" ] = (object) array(
					'hook'     => $hook,
					'timestamp' => $time, // UTC
					'sig'      => $sig,
					'args'     => $data['args'],
					'schedule' => $data['schedule'],
					'interval' => isset( $data['interval'] ) ? $data['interval'] : null,
				);

			}
		}
	}

	// Ensure events are always returned in date descending order.
	// External cron runners such as Cavalcade don't guarantee events are returned in order of time.
	uasort( $events, 'Crontrol\Event\uasort_order_events' );

	return $events;
}

/**
 * Gets a single cron event.
 *
 * @param string     $hook         The hook name of the event.
 * @param string     $sig          The event signature.
 * @param string|int $next_run_utc The UTC time that the event would be run at.
 * @return stdClass|WP_Error A cron event object, or a WP_Error if it's not found.
 */
function get_single( $hook, $sig, $next_run_utc ) {
	$crons = get_core_cron_array();
	$next_run_utc = (int) $next_run_utc;

	if ( isset( $crons[ $next_run_utc ][ $hook ][ $sig ] ) ) {
		$event = $crons[ $next_run_utc ][ $hook ][ $sig ];

		$event['hook'] = $hook;
		$event['timestamp'] = $next_run_utc;

		$event = (object) $event;

		return $event;
	}

	return new WP_Error(
		'not_found',
		sprintf(
			/* translators: %s: The name of the cron event. */
			__( 'The cron event %s could not be found.', 'wp-crontrol' ),
			$hook
		)
	);
}

/**
 * Returns an array of the number of events for each hook.
 *
 * @return array<string,int> Array of number of events for each hook, keyed by the hook name.
 */
function count_by_hook() {
	$crons  = get_core_cron_array();
	$events = array();

	if ( empty( $crons ) ) {
		return array();
	}

	foreach ( $crons as $time => $cron ) {
		foreach ( $cron as $hook => $dings ) {
			if ( ! isset( $events[ $hook ] ) ) {
				$events[ $hook ] = 0;
			}

			$events[ $hook ] += count( $dings );
		}
	}

	return $events;
}

/**
 * Returns the schedule display name for a given event.
 *
 * @param stdClass $event A WP-Cron event.
 * @return string|WP_Error The interval display name, or a WP_Error object if no such schedule exists.
 */
function get_schedule_name( stdClass $event ) {
	$schedules = Schedule\get();

	if ( isset( $schedules[ $event->schedule ] ) ) {
		return isset( $schedules[ $event->schedule ]['display'] ) ? $schedules[ $event->schedule ]['display'] : $schedules[ $event->schedule ]['name'];
	}

	return new WP_Error( 'unknown_schedule', sprintf(
		/* translators: %s: Schedule name */
		__( 'Unknown (%s)', 'wp-crontrol' ),
		$event->schedule
	) );
}

/**
 * Determines whether the schedule for an event means it runs too frequently to be reliable.
 *
 * @param stdClass $event A WP-Cron event.
 * @return bool Whether the event scheduled is too frequent.
 */
function is_too_frequent( stdClass $event ) {
	$schedules = Schedule\get();

	if ( ! isset( $schedules[ $event->schedule ] ) ) {
		return false;
	}

	return $schedules[ $event->schedule ]['is_too_frequent'];
}

/**
 * Determines whether an event is late.
 *
 * An event which has missed its schedule by more than 10 minutes is considered late.
 *
 * @param stdClass $event The event.
 * @return bool Whether the event is late.
 */
function is_late( stdClass $event ) {
	$until = $event->timestamp - time();

	return ( $until < ( 0 - ( 10 * MINUTE_IN_SECONDS ) ) );
}

/**
 * Determines whether an event is paused.
 *
 * @param stdClass $event The event.
 * @return bool Whether the event is paused.
 */
function is_paused( stdClass $event ) {
	$paused = get_option( PAUSED_OPTION );

	if ( ! is_array( $paused ) ) {
		return false;
	}

	return array_key_exists( $event->hook, $paused );
}

/**
 * Determines whether the integrity check of a URL or PHP cron event has failed.
 *
 * @param stdClass $event The event.
 * @return bool Whether the event integrity check has failed.
 */
function integrity_failed( stdClass $event ): bool {
	$args = $event->args[0] ?? array();
	$failed = false;

	switch ( $event->hook ) {

		// PHP cron events:
		case 'crontrol_cron_job':
			// This is a PHP cron event saved prior to WP Crontrol 1.16.2.
			if ( isset( $event->args['code'] ) ) {
				$failed = true;
			} else {
				$failed = ! check_integrity( $args['code'] ?? null, $args['hash'] ?? null );
			}
			break;

		// URL cron events:
		case 'crontrol_url_cron_job':
			$failed = ! check_integrity( $args['url'] ?? null, $args['hash'] ?? null );
			break;

	}

	return $failed;
}

/**
 * Checks the integrity of a code string compared to its stored hash.
 *
 * @param string|null $code        The code string.
 * @param string|null $stored_hash The stored HMAC of the code.
 * @return bool
 */
function check_integrity( $code, $stored_hash ): bool {
	// If there's no code or hash then the integrity check is not ok.
	if ( empty( $code ) || empty( $stored_hash ) ) {
		return false;
	}

	$code_hash = wp_hash( $code );

	// If the hashes match then the integrity check is ok.
	return hash_equals( $stored_hash, $code_hash );
}

/**
 * Initialises and returns the list table for events.
 *
 * @return Table The list table.
 */
function get_list_table() {
	static $table = null;

	if ( ! $table ) {
		$table = new Table();
		$table->prepare_items();

	}

	return $table;
}

/**
 * Order events function.
 *
 * The comparison function returns an integer less than, equal to, or greater than zero if the first argument is
 * considered to be respectively less than, equal to, or greater than the second.
 *
 * @param stdClass $a The first event to compare.
 * @param stdClass $b The second event to compare.
 * @return int
 */
function uasort_order_events( $a, $b ) {
	$orderby = ( ! empty( $_GET['orderby'] ) && is_string( $_GET['orderby'] ) ) ? sanitize_text_field( $_GET['orderby'] ) : 'crontrol_next';
	$order   = ( ! empty( $_GET['order'] ) && is_string( $_GET['order'] ) ) ? sanitize_text_field( $_GET['order'] ) : 'asc';
	$compare = 0;

	switch ( $orderby ) {
		case 'crontrol_hook':
			if ( 'asc' === $order ) {
				$compare = strcmp( $a->hook, $b->hook );
			} else {
				$compare = strcmp( $b->hook, $a->hook );
			}
			break;
		case 'crontrol_schedule':
			if ( 'asc' === $order ) {
				$compare = ( $a->interval ?? 0 ) <=> ( $b->interval ?? 0 );
			} else {
				$compare = ( $b->interval ?? 0 ) <=> ( $a->interval ?? 0 );
			}
			break;
		default:
			if ( 'asc' === $order ) {
				$compare = $a->timestamp <=> $b->timestamp;
			} else {
				$compare = $b->timestamp <=> $a->timestamp;
			}
			break;
	}

	return $compare;
}

/**
 * Fetches the list of cron events from WordPress core.
 *
 * @return array<int,array<string,array<string,mixed[]>>>
 * @phpstan-return array<int,array<string,array<string,array{
 *     args: mixed[],
 *     schedule: string|false,
 *     interval?: int,
 * }>>>
 */
function get_core_cron_array() {
	$crons = _get_cron_array();

	if ( empty( $crons ) ) {
		$crons = array();
	}

	return $crons;
}