Helix_Website/wp-content/plugins/wp-crontrol/wp-crontrol.php

1589 lines
50 KiB
PHP

<?php
/**
* Plugin Name: WP Crontrol
* Plugin URI: https://wordpress.org/plugins/wp-crontrol/
* Description: WP Crontrol lets you view and control what's happening in the WP-Cron system.
* Author: John Blackbourn & crontributors
* Author URI: https://github.com/johnbillion/wp-crontrol/graphs/contributors
* Version: 1.8.5
* Text Domain: wp-crontrol
* Domain Path: /languages/
* Requires PHP: 5.3.6
* License: GPL v2 or later
*
* LICENSE
* This file is part of WP Crontrol.
*
* WP Crontrol is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* @package wp-crontrol
* @author John Blackbourn <john@johnblackbourn.com> & Edward Dale <scompt@scompt.com>
* @copyright Copyright 2008 Edward Dale, 2012-2020 John Blackbourn
* @license http://www.gnu.org/licenses/gpl.txt GPL 2.0
* @link https://wordpress.org/plugins/wp-crontrol/
* @since 0.2
*/
namespace Crontrol;
use WP_Error;
defined( 'ABSPATH' ) || die();
require_once __DIR__ . '/src/event.php';
require_once __DIR__ . '/src/schedule.php';
/**
* Hook onto all of the actions and filters needed by the plugin.
*/
function init_hooks() {
$plugin_file = plugin_basename( __FILE__ );
add_action( 'init', __NAMESPACE__ . '\action_init' );
add_action( 'init', __NAMESPACE__ . '\action_handle_posts' );
add_action( 'admin_menu', __NAMESPACE__ . '\action_admin_menu' );
add_action( 'wp_ajax_crontrol_checkhash', __NAMESPACE__ . '\ajax_check_events_hash' );
add_filter( "plugin_action_links_{$plugin_file}", __NAMESPACE__ . '\plugin_action_links', 10, 4 );
add_filter( 'removable_query_args', __NAMESPACE__ . '\filter_removable_query_args' );
add_filter( 'in_admin_header', __NAMESPACE__ . '\do_tabs' );
add_filter( 'pre_unschedule_event', __NAMESPACE__ . '\maybe_clear_doing_cron' );
add_action( 'load-tools_page_crontrol_admin_manage_page', __NAMESPACE__ . '\setup_manage_page' );
add_filter( 'cron_schedules', __NAMESPACE__ . '\filter_cron_schedules' );
add_action( 'crontrol_cron_job', __NAMESPACE__ . '\action_php_cron_event' );
add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
add_action( 'crontrol/tab-header', __NAMESPACE__ . '\show_cron_status', 20 );
}
/**
* Run using the 'init' action.
*/
function action_init() {
load_plugin_textdomain( 'wp-crontrol', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
/**
* Handles any POSTs made by the plugin. Run using the 'init' action.
*/
function action_handle_posts() {
if ( isset( $_POST['new_cron'] ) ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to add new cron events.', 'wp-crontrol' ), 401 );
}
check_admin_referer( 'new-cron' );
extract( wp_unslash( $_POST ), EXTR_PREFIX_ALL, 'in' );
if ( 'crontrol_cron_job' === $in_hookname && ! current_user_can( 'edit_files' ) ) {
wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 );
}
$in_args = json_decode( $in_args, true );
if ( empty( $in_args ) ) {
$in_args = array();
}
$next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom : $in_next_run_date_local;
$added = Event\add( $next_run_local, $in_schedule, $in_hookname, $in_args );
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '5',
'crontrol_name' => rawurlencode( $in_hookname ),
);
if ( false === $added ) {
$redirect['crontrol_message'] = '10';
}
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} elseif ( isset( $_POST['new_php_cron'] ) ) {
if ( ! current_user_can( 'edit_files' ) ) {
wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 );
}
check_admin_referer( 'new-cron' );
extract( wp_unslash( $_POST ), EXTR_PREFIX_ALL, 'in' );
$next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom : $in_next_run_date_local;
$args = array(
'code' => $in_hookcode,
'name' => $in_eventname,
);
$added = Event\add( $next_run_local, $in_schedule, 'crontrol_cron_job', $args );
$hookname = ( ! empty( $in_eventname ) ) ? $in_eventname : __( 'PHP Cron', 'wp-crontrol' );
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '5',
'crontrol_name' => rawurlencode( $hookname ),
);
if ( false === $added ) {
$redirect['crontrol_message'] = '10';
}
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} elseif ( isset( $_POST['edit_cron'] ) ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ), 401 );
}
extract( wp_unslash( $_POST ), EXTR_PREFIX_ALL, 'in' );
check_admin_referer( "edit-cron_{$in_original_hookname}_{$in_original_sig}_{$in_original_next_run_utc}" );
if ( 'crontrol_cron_job' === $in_hookname && ! current_user_can( 'edit_files' ) ) {
wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 );
}
$in_args = json_decode( $in_args, true );
if ( empty( $in_args ) ) {
$in_args = array();
}
Event\delete( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
$next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom : $in_next_run_date_local;
$added = Event\add( $next_run_local, $in_schedule, $in_hookname, $in_args );
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '4',
'crontrol_name' => rawurlencode( $in_hookname ),
);
if ( false === $added ) {
$redirect['crontrol_message'] = '10';
}
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} elseif ( isset( $_POST['edit_php_cron'] ) ) {
if ( ! current_user_can( 'edit_files' ) ) {
wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 );
}
extract( wp_unslash( $_POST ), EXTR_PREFIX_ALL, 'in' );
check_admin_referer( "edit-cron_{$in_original_hookname}_{$in_original_sig}_{$in_original_next_run_utc}" );
$args = array(
'code' => $in_hookcode,
'name' => $in_eventname,
);
Event\delete( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
$next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom : $in_next_run_date_local;
$added = Event\add( $next_run_local, $in_schedule, 'crontrol_cron_job', $args );
$hookname = ( ! empty( $in_eventname ) ) ? $in_eventname : __( 'PHP Cron', 'wp-crontrol' );
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '4',
'crontrol_name' => rawurlencode( $hookname ),
);
if ( false === $added ) {
$redirect['crontrol_message'] = '10';
}
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} elseif ( isset( $_POST['new_schedule'] ) ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to add new cron schedules.', 'wp-crontrol' ), 401 );
}
check_admin_referer( 'new-sched' );
$name = wp_unslash( $_POST['internal_name'] );
$interval = wp_unslash( $_POST['interval'] );
$display = wp_unslash( $_POST['display_name'] );
// The user entered something that wasn't a number.
// Try to convert it with strtotime.
if ( ! is_numeric( $interval ) ) {
$now = time();
$future = strtotime( $interval, $now );
if ( false === $future || $now > $future ) {
$redirect = array(
'page' => 'crontrol_admin_options_page',
'crontrol_message' => '7',
'crontrol_name' => rawurlencode( $interval ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
exit;
}
$interval = $future - $now;
} elseif ( $interval <= 0 ) {
$redirect = array(
'page' => 'crontrol_admin_options_page',
'crontrol_message' => '7',
'crontrol_name' => rawurlencode( $interval ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
exit;
}
Schedule\add( $name, $interval, $display );
$redirect = array(
'page' => 'crontrol_admin_options_page',
'crontrol_message' => '3',
'crontrol_name' => rawurlencode( $name ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
exit;
} elseif ( isset( $_GET['action'] ) && 'delete-sched' === $_GET['action'] ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to delete cron schedules.', 'wp-crontrol' ), 401 );
}
$id = wp_unslash( $_GET['id'] );
check_admin_referer( "delete-sched_{$id}" );
Schedule\delete( $id );
$redirect = array(
'page' => 'crontrol_admin_options_page',
'crontrol_message' => '2',
'crontrol_name' => rawurlencode( $id ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
exit;
} elseif ( ( isset( $_POST['action'] ) && 'delete_crons' === $_POST['action'] ) || ( isset( $_POST['action2'] ) && 'delete_crons' === $_POST['action2'] ) ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
}
check_admin_referer( 'bulk-crontrol-events' );
if ( empty( $_POST['delete'] ) ) {
return;
}
$delete = wp_unslash( $_POST['delete'] );
$deleted = 0;
foreach ( $delete as $next_run_utc => $events ) {
foreach ( $events as $id => $sig ) {
if ( 'crontrol_cron_job' === $id && ! current_user_can( 'edit_files' ) ) {
continue;
}
if ( Event\delete( urldecode( $id ), $sig, $next_run_utc ) ) {
$deleted++;
}
}
}
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_name' => $deleted,
'crontrol_message' => '9',
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} elseif ( isset( $_GET['action'] ) && 'delete-cron' === $_GET['action'] ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
}
$id = wp_unslash( $_GET['id'] );
$sig = wp_unslash( $_GET['sig'] );
$next_run_utc = intval( $_GET['next_run_utc'] );
check_admin_referer( "delete-cron_{$id}_{$sig}_{$next_run_utc}" );
if ( 'crontrol_cron_job' === $id && ! current_user_can( 'edit_files' ) ) {
wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
}
if ( Event\delete( $id, $sig, $next_run_utc ) ) {
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '6',
'crontrol_name' => rawurlencode( $id ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} else {
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '7',
'crontrol_name' => rawurlencode( $id ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
};
} elseif ( isset( $_GET['action'] ) && 'delete-hook' === $_GET['action'] ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
}
$id = wp_unslash( $_GET['id'] );
$deleted = false;
check_admin_referer( "delete-hook_{$id}" );
if ( 'crontrol_cron_job' === $id ) {
wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
}
if ( function_exists( 'wp_unschedule_hook' ) ) {
$deleted = wp_unschedule_hook( $id );
}
if ( 0 === $deleted ) {
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '3',
'crontrol_name' => rawurlencode( $id ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} elseif ( $deleted ) {
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '2',
'crontrol_name' => rawurlencode( $id ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} else {
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '7',
'crontrol_name' => rawurlencode( $id ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
}
} elseif ( isset( $_GET['action'] ) && 'run-cron' === $_GET['action'] ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ), 401 );
}
$id = wp_unslash( $_GET['id'] );
$sig = wp_unslash( $_GET['sig'] );
check_admin_referer( "run-cron_{$id}_{$sig}" );
if ( Event\run( $id, $sig ) ) {
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '1',
'crontrol_name' => rawurlencode( $id ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
} else {
$redirect = array(
'page' => 'crontrol_admin_manage_page',
'crontrol_message' => '8',
'crontrol_name' => rawurlencode( $id ),
);
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
exit;
}
}
}
/**
* Adds options & management pages to the admin menu.
*
* Run using the 'admin_menu' action.
*/
function action_admin_menu() {
add_options_page( esc_html__( 'Cron Schedules', 'wp-crontrol' ), esc_html__( 'Cron Schedules', 'wp-crontrol' ), 'manage_options', 'crontrol_admin_options_page', __NAMESPACE__ . '\admin_options_page' );
add_management_page( esc_html__( 'Cron Events', 'wp-crontrol' ), esc_html__( 'Cron Events', 'wp-crontrol' ), 'manage_options', 'crontrol_admin_manage_page', __NAMESPACE__ . '\admin_manage_page' );
}
/**
* Adds items to the plugin's action links on the Plugins listing screen.
*
* @param string[] $actions Array of action links.
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
* @param array $plugin_data An array of plugin data.
* @param string $context The plugin context.
* @return string[] Array of action links.
*/
function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
$new = array(
'crontrol-events' => sprintf(
'<a href="%s">%s</a>',
esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page' ) ),
esc_html__( 'Events', 'wp-crontrol' )
),
'crontrol-schedules' => sprintf(
'<a href="%s">%s</a>',
esc_url( admin_url( 'options-general.php?page=crontrol_admin_options_page' ) ),
esc_html__( 'Schedules', 'wp-crontrol' )
),
);
return array_merge( $new, $actions );
}
/**
* Gives WordPress the plugin's set of cron schedules.
*
* Called by the `cron_schedules` filter.
*
* @param array[] $scheds Array of cron schedule arrays. Usually empty.
* @return array[] Array of modified cron schedule arrays.
*/
function filter_cron_schedules( array $scheds ) {
$new_scheds = get_option( 'crontrol_schedules', array() );
return array_merge( $new_scheds, $scheds );
}
/**
* Displays the options page for the plugin.
*/
function admin_options_page() {
$messages = array(
'2' => array(
/* translators: 1: The name of the cron schedule. */
__( 'Deleted the cron schedule %s.', 'wp-crontrol' ),
'success',
),
'3' => array(
/* translators: 1: The name of the cron schedule. */
__( 'Added the cron schedule %s.', 'wp-crontrol' ),
'success',
),
'7' => array(
/* translators: 1: The name of the cron schedule. */
__( 'Cron schedule not added because there was a problem parsing %s.', 'wp-crontrol' ),
'error',
),
);
if ( isset( $_GET['crontrol_message'] ) && isset( $_GET['crontrol_name'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
$hook = wp_unslash( $_GET['crontrol_name'] );
$message = wp_unslash( $_GET['crontrol_message'] );
printf(
'<div id="crontrol-message" class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
esc_attr( $messages[ $message ][1] ),
sprintf(
esc_html( $messages[ $message ][0] ),
'<strong>' . esc_html( $hook ) . '</strong>'
)
);
}
require_once __DIR__ . '/src/schedule-list-table.php';
$table = new Schedule_List_Table();
$table->prepare_items();
?>
<div class="wrap">
<h1><?php esc_html_e( 'Cron Schedules', 'wp-crontrol' ); ?></h1>
<?php $table->views(); ?>
<div class="table-responsive">
<?php $table->display(); ?>
</div>
<div class="wrap narrow">
<h2 class="title"><?php esc_html_e( 'Add Cron Schedule', 'wp-crontrol' ); ?></h2>
<p><?php esc_html_e( 'Adding a new cron schedule will allow you to schedule events that re-occur at the given interval.', 'wp-crontrol' ); ?></p>
<form method="post" action="options-general.php?page=crontrol_admin_options_page">
<table class="form-table">
<tbody>
<tr>
<th valign="top" scope="row"><label for="cron_internal_name"><?php esc_html_e( 'Internal name', 'wp-crontrol' ); ?></label></th>
<td><input type="text" class="regular-text" value="" id="cron_internal_name" name="internal_name" required/></td>
</tr>
<tr>
<th valign="top" scope="row"><label for="cron_interval"><?php esc_html_e( 'Interval (seconds)', 'wp-crontrol' ); ?></label></th>
<td><input type="number" class="regular-text" value="" id="cron_interval" name="interval" min="1" step="1" required/></td>
</tr>
<tr>
<th valign="top" scope="row"><label for="cron_display_name"><?php esc_html_e( 'Display name', 'wp-crontrol' ); ?></label></th>
<td><input type="text" class="regular-text" value="" id="cron_display_name" name="display_name" required/></td>
</tr>
</tbody></table>
<p class="submit"><input id="schedadd-submit" type="submit" class="button button-primary" value="<?php esc_attr_e( 'Add Cron Schedule', 'wp-crontrol' ); ?>" name="new_schedule"/></p>
<?php wp_nonce_field( 'new-sched' ); ?>
</form>
</div>
<?php
}
/**
* Clears the doing cron status when an event is unscheduled.
*
* What on earth does this function do, and why?
*
* Good question. The purpose of this function is to prevent other overdue cron events from firing when an event is run
* manually with the "Run Now" action. WP Crontrol works very hard to ensure that when cron event runs manually that it
* runs in the exact same way it would run as part of its schedule - via a properly spawned cron with a queued event in
* place. It does this by queueing an event at time `1` (1 second into 1st January 1970) and then immediately spawning
* cron (see the `Event\run()` function).
*
* The problem this causes is if other events are due then they will all run too, and this isn't desirable because if a
* site has a large number of stuck events due to a problem with the cron runner then it's not desirable for all those
* events to run when another is manually run. This happens because WordPress core will attempt to run all due events
* whenever cron is spawned.
*
* The code in this function prevents multiple events from running by changing the value of the `doing_cron` transient
* when an event gets unscheduled during a manual run, which prevents wp-cron.php from iterating more than one event.
*
* The `pre_unschedule_event` filter is used for this because it's just about the only hook available within this loop.
*
* Refs:
* - https://core.trac.wordpress.org/browser/trunk/src/wp-cron.php?rev=47198&marks=127,141#L122
*
* @param mixed $pre The pre-flight value of the event unschedule short-circuit. Not used.
* @return mixed Thee unaltered pre-flight value.
*/
function maybe_clear_doing_cron( $pre ) {
if ( defined( 'DOING_CRON' ) && DOING_CRON && isset( $_GET['crontrol-single-event'] ) ) {
delete_transient( 'doing_cron' );
}
return $pre;
}
/**
* Ajax handler which outputs a hash of the current list of scheduled events.
*/
function ajax_check_events_hash() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( null, 403 );
}
wp_send_json_success( md5( json_encode( Event\get_list_table()->items ) ) );
}
/**
* Gets the status of WP-Cron functionality on the site by performing a test spawn if necessary. Cached for one hour when all is well.
*
* @param bool $cache Whether to use the cached result from previous calls.
* @return true|WP_Error Boolean true if the cron spawner is working as expected, or a `WP_Error` object if not.
*/
function test_cron_spawn( $cache = true ) {
global $wp_version;
$cron_runner_plugins = array(
'\HM\Cavalcade\Plugin\Job' => 'Cavalcade',
'\Automattic\WP\Cron_Control' => 'Cron Control',
);
foreach ( $cron_runner_plugins as $class => $plugin ) {
if ( class_exists( $class ) ) {
return new WP_Error( 'crontrol_info', sprintf(
/* translators: 1: The name of the plugin that controls the running of cron events. */
__( 'WP-Cron spawning is being managed by the %s plugin.', 'wp-crontrol' ),
$plugin
) );
}
}
if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
return new WP_Error( 'crontrol_info', sprintf(
/* translators: 1: The name of the PHP constant that is set. */
__( 'The %s constant is set to true. WP-Cron spawning is disabled.', 'wp-crontrol' ),
'DISABLE_WP_CRON'
) );
}
if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
return new WP_Error( 'crontrol_info', sprintf(
/* translators: 1: The name of the PHP constant that is set. */
__( 'The %s constant is set to true.', 'wp-crontrol' ),
'ALTERNATE_WP_CRON'
) );
}
$cached_status = get_transient( 'crontrol-cron-test-ok' );
if ( $cache && $cached_status ) {
return true;
}
$sslverify = version_compare( $wp_version, 4.0, '<' );
$doing_wp_cron = sprintf( '%.22F', microtime( true ) );
$cron_request = apply_filters( 'cron_request', array(
'url' => add_query_arg( 'doing_wp_cron', $doing_wp_cron, site_url( 'wp-cron.php' ) ),
'key' => $doing_wp_cron,
'args' => array(
'timeout' => 3,
'blocking' => true,
'sslverify' => apply_filters( 'https_local_ssl_verify', $sslverify ),
),
) );
$cron_request['args']['blocking'] = true;
$result = wp_remote_post( $cron_request['url'], $cron_request['args'] );
if ( is_wp_error( $result ) ) {
return $result;
} elseif ( wp_remote_retrieve_response_code( $result ) >= 300 ) {
return new WP_Error( 'unexpected_http_response_code', sprintf(
/* translators: 1: The HTTP response code. */
__( 'Unexpected HTTP response code: %s', 'wp-crontrol' ),
intval( wp_remote_retrieve_response_code( $result ) )
) );
} else {
set_transient( 'crontrol-cron-test-ok', 1, 3600 );
return true;
}
}
/**
* Determines whether the given feature is enabled.
*
* The feature directly corresponds to one of WP Crontrol's tabs. Currently the only feature
* that's not enabled by default is "logs" which are provided by WP Crontrol Pro.
*
* @param string $feature The feature name.
* @return bool Whether the specified tab is active.
*/
function is_feature_enabled( $feature ) {
$enabled = ( 'logs' !== $feature );
return apply_filters( "crontrol/enabled/{$feature}", $enabled );
}
/**
* Shows the status of WP-Cron functionality on the site. Only displays a message when there's a problem.
*
* @param string $tab The tab name.
*/
function show_cron_status( $tab ) {
if ( ! is_feature_enabled( $tab ) ) {
return;
}
if ( 'UTC' !== date_default_timezone_get() ) {
$string = sprintf(
/* translators: %s: Help page URL. */
__( 'PHP default timezone is not set to UTC. This may cause issues with cron event timings. <a href="%s">More information</a>.', 'wp-crontrol' ),
'https://github.com/johnbillion/wp-crontrol/wiki/PHP-default-timezone-is-not-set-to-UTC'
);
?>
<div id="crontrol-timezone-warning" class="notice notice-warning">
<p>
<?php
echo wp_kses(
$string,
array(
'a' => array(
'href' => true,
),
)
);
?>
</p>
</div>
<?php
}
$status = test_cron_spawn();
if ( is_wp_error( $status ) ) {
if ( 'crontrol_info' === $status->get_error_code() ) {
?>
<div id="cron-status-notice" class="notice notice-info">
<p><?php echo esc_html( $status->get_error_message() ); ?></p>
</div>
<?php
} else {
?>
<div id="cron-status-error" class="error">
<p>
<?php
printf(
/* translators: 1: Error message text. */
esc_html__( 'There was a problem spawning a call to the WP-Cron system on your site. This means WP-Cron events on your site may not work. The problem was: %s', 'wp-crontrol' ),
'<br><strong>' . esc_html( $status->get_error_message() ) . '</strong>'
);
?>
</p>
</div>
<?php
}
}
}
/**
* Get the display name for the site's timezone.
*
* @return string The name and UTC offset for the site's timezone.
*/
function get_timezone_name() {
$timezone_string = get_option( 'timezone_string', '' );
$gmt_offset = get_option( 'gmt_offset', 0 );
if ( 'UTC' === $timezone_string || ( empty( $gmt_offset ) && empty( $timezone_string ) ) ) {
return 'UTC';
}
if ( '' === $timezone_string ) {
return get_utc_offset();
}
return sprintf(
'%s, %s',
str_replace( '_', ' ', $timezone_string ),
get_utc_offset()
);
}
/**
* Returns a display value for a UTC offset.
*
* Examples:
* - UTC
* - UTC+4
* - UTC-6
*
* @return string The UTC offset display value.
*/
function get_utc_offset() {
$offset = get_option( 'gmt_offset', 0 );
if ( empty( $offset ) ) {
return 'UTC';
}
if ( 0 <= $offset ) {
$formatted_offset = '+' . (string) $offset;
} else {
$formatted_offset = (string) $offset;
}
$formatted_offset = str_replace(
array( '.25', '.5', '.75' ),
array( ':15', ':30', ':45' ),
$formatted_offset
);
return 'UTC' . $formatted_offset;
}
/**
* Shows the form used to add/edit cron events.
*
* @param bool $editing Whether the form is for the event editor.
* @param bool $is_php Whether the form is for a PHP event.
* @return void
*/
function show_cron_form( $editing, $is_php = null ) {
$display_args = '';
$edit_id = null;
$existing = false;
if ( ! empty( $_GET['id'] ) ) {
$edit_id = wp_unslash( $_GET['id'] );
}
foreach ( Event\get() as $event ) {
if ( $edit_id === $event->hook && intval( $_GET['next_run_utc'] ) === $event->time && $event->sig === $_GET['sig'] ) {
$existing = array(
'hookname' => $event->hook,
'next_run' => $event->time, // UTC
'schedule' => ( $event->schedule ? $event->schedule : '_oneoff' ),
'sig' => $event->sig,
'args' => $event->args,
);
break;
}
}
if ( null === $is_php ) {
$is_php = ( $existing && 'crontrol_cron_job' === $existing['hookname'] );
}
if ( $is_php ) {
$helper_text = esc_html__( 'Cron events trigger actions in your code. Enter the schedule of the event, as well as the PHP code to execute when the action is triggered.', 'wp-crontrol' );
} else {
$helper_text = sprintf(
/* translators: %s: A file name */
esc_html__( 'Cron events trigger actions in your code. A cron event needs a corresponding action hook somewhere in code, e.g. the %1$s file in your theme.', 'wp-crontrol' ),
'<code>functions.php</code>'
);
}
if ( is_array( $existing ) ) {
$other_fields = wp_nonce_field( "edit-cron_{$existing['hookname']}_{$existing['sig']}_{$existing['next_run']}", '_wpnonce', true, false );
$other_fields .= sprintf( '<input name="original_hookname" type="hidden" value="%s" />',
esc_attr( $existing['hookname'] )
);
$other_fields .= sprintf( '<input name="original_sig" type="hidden" value="%s" />',
esc_attr( $existing['sig'] )
);
$other_fields .= sprintf( '<input name="original_next_run_utc" type="hidden" value="%s" />',
esc_attr( $existing['next_run'] )
);
if ( ! empty( $existing['args'] ) ) {
$display_args = wp_json_encode( $existing['args'] );
}
$action = $is_php ? 'edit_php_cron' : 'edit_cron';
$button = __( 'Update Event', 'wp-crontrol' );
$next_run_date_local = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $existing['next_run'] ), 'Y-m-d H:i:s' );
} else {
$other_fields = wp_nonce_field( 'new-cron', '_wpnonce', true, false );
$existing = array(
'hookname' => '',
'args' => array(),
'next_run' => 'now', // UTC
'schedule' => false,
);
$action = $is_php ? 'new_php_cron' : 'new_cron';
$button = __( 'Add Event', 'wp-crontrol' );
$next_run_date_local = '';
}
if ( $is_php ) {
if ( ! isset( $existing['args']['code'] ) ) {
$existing['args']['code'] = '';
}
if ( ! isset( $existing['args']['name'] ) ) {
$existing['args']['name'] = '';
}
}
$allowed = ( ! $is_php || current_user_can( 'edit_files' ) );
?>
<div id="crontrol_form" class="wrap narrow">
<?php
if ( $allowed ) {
if ( $editing ) {
if ( $is_php ) {
$heading = __( 'Edit PHP Cron Event', 'wp-crontrol' );
} else {
$heading = __( 'Edit Cron Event', 'wp-crontrol' );
}
} else {
if ( $is_php ) {
$heading = __( 'Add PHP Cron Event', 'wp-crontrol' );
} else {
$heading = __( 'Add Cron Event', 'wp-crontrol' );
}
}
printf(
'<h1>%s</h1>',
esc_html( $heading )
);
printf(
'<p>%s</p>',
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$helper_text
);
?>
<form method="post" action="<?php echo esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page' ) ); ?>">
<?php
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $other_fields;
?>
<table class="form-table"><tbody>
<?php if ( $is_php ) : ?>
<tr>
<th valign="top" scope="row"><label for="hookcode"><?php esc_html_e( 'PHP Code', 'wp-crontrol' ); ?></label></th>
<td>
<p class="description">
<?php
printf(
/* translators: The PHP tag name */
esc_html__( 'The opening %s tag must not be included.', 'wp-crontrol' ),
'<code>&lt;?php</code>'
);
?>
</p>
<p><textarea class="large-text code" rows="10" cols="50" id="hookcode" name="hookcode"><?php echo esc_textarea( $existing['args']['code'] ); ?></textarea></p>
</td>
</tr>
<tr>
<th valign="top" scope="row"><label for="eventname"><?php esc_html_e( 'Event Name (optional)', 'wp-crontrol' ); ?></label></th>
<td><input type="text" class="regular-text" id="eventname" name="eventname" value="<?php echo esc_attr( $existing['args']['name'] ); ?>"/></td>
</tr>
<?php else : ?>
<tr>
<th valign="top" scope="row"><label for="hookname"><?php esc_html_e( 'Hook Name', 'wp-crontrol' ); ?></label></th>
<td>
<input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text" id="hookname" name="hookname" value="<?php echo esc_attr( $existing['hookname'] ); ?>" required />
</td>
</tr>
<tr>
<th valign="top" scope="row"><label for="args"><?php esc_html_e( 'Arguments (optional)', 'wp-crontrol' ); ?></label></th>
<td>
<input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text" id="args" name="args" value="<?php echo esc_attr( $display_args ); ?>"/>
<p class="description">
<?php
printf(
/* translators: 1, 2, and 3: Example values for an input field. */
esc_html__( 'Use a JSON encoded array, e.g. %1$s, %2$s, or %3$s', 'wp-crontrol' ),
'<code>[25]</code>',
'<code>["asdf"]</code>',
'<code>["i","want",25,"cakes"]</code>'
);
?>
</p>
</td>
</tr>
<?php endif; ?>
<tr>
<th valign="top" scope="row"><label for="next_run_date_local"><?php esc_html_e( 'Next Run', 'wp-crontrol' ); ?></label></th>
<td>
<ul>
<li>
<label>
<input type="radio" name="next_run_date_local" value="now" checked>
<?php esc_html_e( 'Now', 'wp-crontrol' ); ?>
</label>
</li>
<li>
<label>
<input type="radio" name="next_run_date_local" value="+1 day">
<?php esc_html_e( 'Tomorrow', 'wp-crontrol' ); ?>
</label>
</li>
<li>
<label>
<input type="radio" name="next_run_date_local" value="custom" id="next_run_date_local_custom" <?php checked( $editing ); ?>>
<?php
printf(
/* translators: %s: An input field for specifying a date and time */
esc_html__( 'At: %s', 'wp-crontrol' ),
sprintf(
'<br><input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" name="next_run_date_local_custom" value="%s" class="regular-text" onfocus="jQuery(\'#next_run_date_local_custom\').prop(\'checked\',true);" />',
esc_attr( $next_run_date_local )
)
);
?>
</label>
</li>
</ul>
<p class="description">
<?php
printf(
/* translators: %s Timezone name. */
esc_html__( 'Timezone: %s', 'wp-crontrol' ),
esc_html( get_timezone_name() )
);
?>
</p>
<p class="description">
<?php
printf(
/* translators: 1: Date/time format for an input field, 2: PHP function name. */
esc_html__( 'Format: %1$s or anything accepted by %2$s', 'wp-crontrol' ),
'<code>YYYY-MM-DD HH:MM:SS</code>',
'<a href="https://www.php.net/manual/function.strtotime.php"><code>strtotime()</code></a>'
);
?>
</p>
</td>
</tr><tr>
<th valign="top" scope="row"><label for="schedule"><?php esc_html_e( 'Recurrence', 'wp-crontrol' ); ?></label></th>
<td>
<?php Schedule\dropdown( $existing['schedule'] ); ?>
</td>
</tr>
</tbody></table>
<p class="submit"><input type="submit" class="button button-primary" value="<?php echo esc_attr( $button ); ?>" name="<?php echo esc_attr( $action ); ?>"/></p>
</form>
<?php } else { ?>
<div class="error inline">
<p><?php esc_html_e( 'You cannot add, edit, or delete PHP cron events because your user account does not have the ability to edit files.', 'wp-crontrol' ); ?></p>
</div>
<?php } ?>
</div>
<?php
}
/**
* Displays the manage page for the plugin.
*/
function admin_manage_page() {
$messages = array(
'1' => array(
/* translators: 1: The name of the cron event. */
__( 'Scheduled the cron event %s to run now.', 'wp-crontrol' ),
'success',
true,
),
'2' => array(
/* translators: 1: The name of the cron event. */
__( 'Deleted all %s cron events.', 'wp-crontrol' ),
'success',
false,
),
'3' => array(
/* translators: 1: The name of the cron event. */
__( 'There are no %s cron events to delete.', 'wp-crontrol' ),
'info',
false,
),
'4' => array(
/* translators: 1: The name of the cron event. */
__( 'Saved the cron event %s.', 'wp-crontrol' ),
'success',
false,
),
'5' => array(
/* translators: 1: The name of the cron event. */
__( 'Created the cron event %s.', 'wp-crontrol' ),
'success',
false,
),
'6' => array(
/* translators: 1: The name of the cron event. */
__( 'Deleted the cron event %s.', 'wp-crontrol' ),
'success',
false,
),
'7' => array(
/* translators: 1: The name of the cron event. */
__( 'Failed to the delete the cron event %s.', 'wp-crontrol' ),
'error',
false,
),
'8' => array(
/* translators: 1: The name of the cron event. */
__( 'Failed to the execute the cron event %s.', 'wp-crontrol' ),
'error',
false,
),
'9' => array(
__( 'Deleted the selected cron events.', 'wp-crontrol' ),
'success',
false,
),
'10' => array(
/* translators: 1: The name of the cron event. */
__( 'Failed to save the cron event %s.', 'wp-crontrol' ),
'error',
false,
),
);
if ( isset( $_GET['crontrol_name'] ) && isset( $_GET['crontrol_message'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
$hook = wp_unslash( $_GET['crontrol_name'] );
$message = wp_unslash( $_GET['crontrol_message'] );
$link = '';
printf(
'<div id="crontrol-message" class="notice notice-%1$s is-dismissible"><p>%2$s%3$s</p></div>',
esc_attr( $messages[ $message ][1] ),
sprintf(
esc_html( $messages[ $message ][0] ),
'<strong>' . esc_html( $hook ) . '</strong>'
),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$link
);
}
$tabs = get_tab_states();
$table = Event\get_list_table();
switch ( true ) {
case $tabs['events']:
?>
<div class="wrap">
<h1><?php esc_html_e( 'Cron Events', 'wp-crontrol' ); ?></h1>
<?php $table->views(); ?>
<form id="posts-filter" method="get" action="tools.php">
<input type="hidden" name="page" value="crontrol_admin_manage_page" />
<?php $table->search_box( __( 'Search Hook Names', 'wp-crontrol' ), 'cron-event' ); ?>
</form>
<form method="post" action="tools.php?page=crontrol_admin_manage_page">
<div class="table-responsive">
<?php $table->display(); ?>
</div>
</form>
<p>
<?php
echo esc_html( sprintf(
/* translators: 1: Date and time, 2: Timezone */
__( 'Site time: %1$s (%2$s)', 'wp-crontrol' ),
date_i18n( 'Y-m-d H:i:s' ),
get_timezone_name()
) );
?>
</p>
</div>
<?php
break;
case $tabs['add-event']:
show_cron_form( false );
break;
case $tabs['add-php-event']:
show_cron_form( false, true );
break;
case $tabs['edit-event']:
show_cron_form( true );
break;
}
}
/**
* Get the states of the various cron-related tabs.
*
* @return bool[] Array of states keyed by tab name.
*/
function get_tab_states() {
return array(
'events' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_manage_page' === $_GET['page'] && empty( $_GET['action'] ) ),
'schedules' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_options_page' === $_GET['page'] ),
'add-event' => ( ! empty( $_GET['action'] ) && 'new-cron' === $_GET['action'] ),
'add-php-event' => ( ! empty( $_GET['action'] ) && 'new-php-cron' === $_GET['action'] ),
'edit-event' => ( ! empty( $_GET['action'] ) && 'edit-cron' === $_GET['action'] ),
);
}
/**
* Output the cron-related tabs if we're on a cron-related admin screen.
*/
function do_tabs() {
$tabs = get_tab_states();
$tab = array_filter( $tabs );
if ( ! $tab ) {
return;
}
$tab = array_keys( $tab );
$tab = reset( $tab );
$links = array(
'events' => array(
'tools.php?page=crontrol_admin_manage_page',
__( 'Cron Events', 'wp-crontrol' ),
),
'schedules' => array(
'options-general.php?page=crontrol_admin_options_page',
__( 'Cron Schedules', 'wp-crontrol' ),
),
'add-event' => array(
'tools.php?page=crontrol_admin_manage_page&action=new-cron',
__( 'Add Cron Event', 'wp-crontrol' ),
),
'add-php-event' => array(
'tools.php?page=crontrol_admin_manage_page&action=new-php-cron',
__( 'Add PHP Cron Event', 'wp-crontrol' ),
),
);
?>
<div id="crontrol-header">
<nav class="nav-tab-wrapper">
<?php
foreach ( $links as $id => $link ) {
if ( $tabs[ $id ] ) {
printf(
'<a href="%s" class="nav-tab nav-tab-active">%s</a>',
esc_url( $link[0] ),
esc_html( $link[1] )
);
} else {
printf(
'<a href="%s" class="nav-tab">%s</a>',
esc_url( $link[0] ),
esc_html( $link[1] )
);
}
}
if ( $tabs['edit-event'] ) {
printf(
'<span class="nav-tab nav-tab-active">%s</span>',
esc_html__( 'Edit Cron Event', 'wp-crontrol' )
);
}
?>
</nav>
<?php
do_action( 'crontrol/tab-header', $tab, $tabs );
?>
</div>
<?php
}
/**
* Returns an array of the callback functions that are attached to the given hook name.
*
* @param string $name The hook name.
* @return array[] Array of callbacks attached to the hook.
*/
function get_hook_callbacks( $name ) {
global $wp_filter;
$actions = array();
if ( isset( $wp_filter[ $name ] ) ) {
// See http://core.trac.wordpress.org/ticket/17817.
$action = $wp_filter[ $name ];
foreach ( $action as $priority => $callbacks ) {
foreach ( $callbacks as $callback ) {
$callback = populate_callback( $callback );
$actions[] = array(
'priority' => $priority,
'callback' => $callback,
);
}
}
}
return $actions;
}
/**
* Populates the details of the given callback function.
*
* @param array $callback A callback entry.
* @return array The updated callback entry.
*/
function populate_callback( array $callback ) {
// If Query Monitor is installed, use its rich callback analysis.
if ( method_exists( '\QM_Util', 'populate_callback' ) ) {
return \QM_Util::populate_callback( $callback );
}
if ( is_string( $callback['function'] ) && ( false !== strpos( $callback['function'], '::' ) ) ) {
$callback['function'] = explode( '::', $callback['function'] );
}
if ( is_array( $callback['function'] ) ) {
if ( is_object( $callback['function'][0] ) ) {
$class = get_class( $callback['function'][0] );
$access = '->';
} else {
$class = $callback['function'][0];
$access = '::';
}
$callback['name'] = $class . $access . $callback['function'][1] . '()';
} elseif ( is_object( $callback['function'] ) ) {
if ( is_a( $callback['function'], 'Closure' ) ) {
$callback['name'] = 'Closure';
} else {
$class = get_class( $callback['function'] );
$callback['name'] = $class . '->__invoke()';
}
} else {
$callback['name'] = $callback['function'] . '()';
}
return $callback;
}
/**
* Returns a user-friendly representation of the callback function.
*
* @param array $callback The callback entry.
* @return string The displayable version of the callback name.
*/
function output_callback( array $callback ) {
$qm = WP_PLUGIN_DIR . '/query-monitor/query-monitor.php';
$html = plugin_dir_path( $qm ) . 'output/Html.php';
// If Query Monitor is installed, use its rich callback output.
if ( class_exists( '\QueryMonitor' ) && file_exists( $html ) ) {
require_once $html;
if ( class_exists( '\QM_Output_Html' ) ) {
if ( ! empty( $callback['callback']['error'] ) ) {
$return = '<code>' . $callback['callback']['name'] . '</code>';
$return .= '<br><span style="color:#c00"><span class="dashicons dashicons-warning" aria-hidden="true"></span> ';
$return .= esc_html( $callback['callback']['error']->get_error_message() );
$return .= '</span>';
return $return;
}
return \QM_Output_Html::output_filename(
$callback['callback']['name'],
$callback['callback']['file'],
$callback['callback']['line']
);
}
}
return '<code>' . $callback['callback']['name'] . '</code>';
}
/**
* Pretty-prints the difference in two times.
*
* @param int $older_date Unix timestamp.
* @param int $newer_date Unix timestamp.
* @return string The pretty time_since value
* @link http://binarybonsai.com/code/timesince.txt
*/
function time_since( $older_date, $newer_date ) {
return interval( $newer_date - $older_date );
}
/**
* Converts a period of time in seconds into a human-readable format representing the interval.
*
* Example:
*
* echo \Crontrol\interval( 90 );
* // 1 minute 30 seconds
*
* @param int $since A period of time in seconds.
* @return string An interval represented as a string.
*/
function interval( $since ) {
// Array of time period chunks.
$chunks = array(
/* translators: 1: The number of years in an interval of time. */
array( 60 * 60 * 24 * 365, _n_noop( '%s year', '%s years', 'wp-crontrol' ) ),
/* translators: 1: The number of months in an interval of time. */
array( 60 * 60 * 24 * 30, _n_noop( '%s month', '%s months', 'wp-crontrol' ) ),
/* translators: 1: The number of weeks in an interval of time. */
array( 60 * 60 * 24 * 7, _n_noop( '%s week', '%s weeks', 'wp-crontrol' ) ),
/* translators: 1: The number of days in an interval of time. */
array( 60 * 60 * 24, _n_noop( '%s day', '%s days', 'wp-crontrol' ) ),
/* translators: 1: The number of hours in an interval of time. */
array( 60 * 60, _n_noop( '%s hour', '%s hours', 'wp-crontrol' ) ),
/* translators: 1: The number of minutes in an interval of time. */
array( 60, _n_noop( '%s minute', '%s minutes', 'wp-crontrol' ) ),
/* translators: 1: The number of seconds in an interval of time. */
array( 1, _n_noop( '%s second', '%s seconds', 'wp-crontrol' ) ),
);
if ( $since <= 0 ) {
return __( 'now', 'wp-crontrol' );
}
/**
* We only want to output two chunks of time here, eg:
* x years, xx months
* x days, xx hours
* so there's only two bits of calculation below:
*/
$j = count( $chunks );
// Step one: the first chunk.
for ( $i = 0; $i < $j; $i++ ) {
$seconds = $chunks[ $i ][0];
$name = $chunks[ $i ][1];
// Finding the biggest chunk (if the chunk fits, break).
$count = floor( $since / $seconds );
if ( $count ) {
break;
}
}
// Set output var.
$output = sprintf( translate_nooped_plural( $name, $count, 'wp-crontrol' ), $count );
// Step two: the second chunk.
if ( $i + 1 < $j ) {
$seconds2 = $chunks[ $i + 1 ][0];
$name2 = $chunks[ $i + 1 ][1];
$count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 );
if ( $count2 ) {
// Add to output var.
$output .= ' ' . sprintf( translate_nooped_plural( $name2, $count2, 'wp-crontrol' ), $count2 );
}
}
return $output;
}
/**
* Sets up the Events listing screen.
*/
function setup_manage_page() {
// Initialise the list table
Event\get_list_table();
// Add the initially hidden admin notice about the out of date events list
add_action( 'admin_notices', function() {
printf(
'<div id="crontrol-hash-message" class="notice notice-warning"><p>%s</p></div>',
esc_html__( 'The scheduled cron events have changed since you first opened this page. Reload the page to see the up to date list.', 'wp-crontrol' )
);
} );
if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
return;
}
if ( ! current_user_can( 'edit_files' ) ) {
return;
}
$settings = wp_enqueue_code_editor( array(
'type' => 'text/x-php',
) );
if ( false === $settings ) {
return;
}
wp_add_inline_script( 'code-editor', sprintf(
'jQuery( function( $ ) {
if ( $( "#hookcode" ).length ) {
wp.codeEditor.initialize( "hookcode", %s );
}
} );',
wp_json_encode( $settings )
) );
}
/**
* Registers the stylesheet and JavaScript for the admin areas.
*
* @param string $hook_suffix The admin screen ID.
*/
function enqueue_assets( $hook_suffix ) {
$tab = get_tab_states();
if ( ! array_filter( $tab ) ) {
return;
}
$ver = filemtime( plugin_dir_path( __FILE__ ) . 'css/wp-crontrol.css' );
wp_enqueue_style( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'css/wp-crontrol.css', array(), $ver );
$ver = filemtime( plugin_dir_path( __FILE__ ) . 'js/wp-crontrol.js' );
wp_enqueue_script( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'js/wp-crontrol.js', array( 'jquery' ), $ver, true );
if ( ! empty( $tab['events'] ) ) {
wp_localize_script( 'wp-crontrol', 'wpCrontrol', array(
'eventsHash' => md5( json_encode( Event\get_list_table()->items ) ),
'eventsHashInterval' => 20,
) );
}
}
/**
* Filters the list of query arguments which get removed from admin area URLs in WordPress.
*
* @param string[] $args List of removable query arguments.
* @return string[] Updated list of removable query arguments.
*/
function filter_removable_query_args( array $args ) {
return array_merge( $args, array(
'crontrol_message',
'crontrol_name',
) );
}
/**
* Returns an array of cron event hooks that are persistently added by WordPress core.
*
* @return string[] Array of hook names.
*/
function get_persistent_core_hooks() {
return array(
'delete_expired_transients',
'recovery_mode_clean_expired_keys',
'update_network_counts',
'wp_privacy_delete_old_export_files',
'wp_scheduled_auto_draft_delete',
'wp_scheduled_delete',
'wp_site_health_scheduled_check',
'wp_update_plugins',
'wp_update_themes',
'wp_version_check',
);
}
/**
* Returns an array of all cron event hooks that are added by WordPress core.
*
* @return string[] Array of hook names.
*/
function get_all_core_hooks() {
return array_merge(
get_persistent_core_hooks(),
array(
'do_pings',
'importer_scheduled_cleanup',
'publish_future_post',
'upgrader_scheduled_cleanup',
'wp_maybe_auto_update',
'wp_split_shared_term_batch',
)
);
}
/**
* Returns an array of cron schedules that are added by WordPress core.
*
* @return string[] Array of schedule names.
*/
function get_core_schedules() {
return array(
'hourly',
'twicedaily',
'daily',
);
}
/**
* Encodes some input as JSON for output.
*
* @param mixed $input The input.
* @return string The JSON-encoded output.
*/
function json_output( $input ) {
$json_options = 0;
if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
// phpcs:ignore PHPCompatibility.Constants.NewConstants.json_unescaped_slashesFound
$json_options |= JSON_UNESCAPED_SLASHES;
}
if ( defined( 'JSON_PRETTY_PRINT' ) ) {
$json_options |= JSON_PRETTY_PRINT;
}
return wp_json_encode( $input, $json_options );
}
/**
* Evaluates the code in a PHP cron event using eval.
*
* Security: Only users with the `edit_files` capability can manage PHP cron events. This means if a user cannot edit
* files on the site (eg. through the Plugin Editor or Theme Editor) then they cannot edit or add a PHP cron event. By
* default, only Administrators have this capability, and with Multisite enabled only Super Admins have this capability.
*
* If file editing has been disabled via the `DISALLOW_FILE_MODS` or `DISALLOW_FILE_EDIT` configuration constants then
* no user will have the `edit_files` capability, which means editing or adding a PHP cron event will not be permitted.
*
* Therefore, the user access level required to execute arbitrary PHP code does not change with WP Crontrol activated.
*
* @param string $code The PHP code to evaluate.
*/
function action_php_cron_event( $code ) {
// phpcs:ignore Squiz.PHP.Eval.Discouraged
eval( $code );
}
// Get this show on the road.
init_hooks();