2020-05-06 17:00:25 +02:00
< ? 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
2020-07-21 16:23:51 +02:00
* Version : 1.8 . 5
2020-05-06 17:00:25 +02:00
* 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' );
2020-07-21 16:23:51 +02:00
add_action ( 'crontrol/tab-header' , __NAMESPACE__ . '\show_cron_status' , 20 );
2020-05-06 17:00:25 +02:00
}
/**
* 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 1 st 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 ;
}
}
2020-07-21 16:23:51 +02:00
/**
* 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 );
}
2020-05-06 17:00:25 +02:00
/**
* Shows the status of WP - Cron functionality on the site . Only displays a message when there ' s a problem .
2020-07-21 16:23:51 +02:00
*
* @ param string $tab The tab name .
2020-05-06 17:00:25 +02:00
*/
2020-07-21 16:23:51 +02:00
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
}
2020-05-06 17:00:25 +02:00
$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><?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 >
2020-07-21 16:23:51 +02:00
< input type = " radio " name = " next_run_date_local " value = " now " checked >
2020-05-06 17:00:25 +02:00
< ? 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 () {
2020-07-21 16:23:51 +02:00
$tabs = get_tab_states ();
$tab = array_filter ( $tabs );
2020-05-06 17:00:25 +02:00
2020-07-21 16:23:51 +02:00
if ( ! $tab ) {
2020-05-06 17:00:25 +02:00
return ;
}
2020-07-21 16:23:51 +02:00
$tab = array_keys ( $tab );
$tab = reset ( $tab );
2020-05-06 17:00:25 +02:00
$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 ) {
2020-07-21 16:23:51 +02:00
if ( $tabs [ $id ] ) {
2020-05-06 17:00:25 +02:00
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 ] )
);
}
}
2020-07-21 16:23:51 +02:00
if ( $tabs [ 'edit-event' ] ) {
2020-05-06 17:00:25 +02:00
printf (
'<span class="nav-tab nav-tab-active">%s</span>' ,
esc_html__ ( 'Edit Cron Event' , 'wp-crontrol' )
);
}
?>
</ nav >
< ? php
2020-07-21 16:23:51 +02:00
do_action ( 'crontrol/tab-header' , $tab , $tabs );
2020-05-06 17:00:25 +02:00
?>
</ 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 .
*
2020-07-21 16:23:51 +02:00
* 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 .
2020-05-06 17:00:25 +02:00
*
* 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 ();