* @copyright Copyright 2007 Edward Dale * @license http://www.gnu.org/licenses/gpl.txt GPL 2.0 * @version $Id$ * @link http://www.scompt.com/projects/wp-crontrol * @since 0.2 */ class Crontrol { var $json; /** * Hook onto all of the actions and filters needed by the plugin. */ function Crontrol() { define( 'CRONTROL_CRON_JOB', 'crontrol_cron_job'); $this->json = new Crontrol_JSON(); if( function_exists('add_action') ) { add_action('init', array(&$this, 'init')); add_action('init', array(&$this, 'handle_posts')); add_action('admin_menu', array(&$this, 'admin_menu')); // Make sure the activation works from subdirectories as well as // directly in the plugin directory. $activate_action = str_replace(ABSPATH.PLUGINDIR.'/', 'activate_', __FILE__); add_action($activate_action, array(&$this, 'activate')); add_filter('cron_schedules', array(&$this, 'cron_schedules')); add_action(CRONTROL_CRON_JOB, array(&$this, 'php_cron_entry')); } } /** * Evaluates the provided code using eval. */ function php_cron_entry($code) { eval($code); } /** * Run using the 'init' action. */ function init() { load_plugin_textdomain('crontrol', str_replace(ABSPATH, '', dirname(__FILE__).'/gettext')); } /** * Handles any POSTs made by the plugin. Run using the 'init' action. */ function handle_posts() { if( isset($_POST['new_cron']) ) { if( !current_user_can('manage_options') ) die(__('You are not allowed to add new cron events.', 'crontrol')); check_admin_referer("new-cron"); extract($_POST, EXTR_PREFIX_ALL, 'in'); $in_args = $this->json->decode(stripslashes($in_args)); $this->add_cron($in_next_run, $in_schedule, $in_hookname, $in_args); wp_redirect("edit.php?page=crontrol_admin_manage_page&crontrol_message=5&crontrol_name={$in_hookname}"); } else if( isset($_POST['new_php_cron']) ) { if( !current_user_can('manage_options') ) die(__('You are not allowed to add new cron events.', 'crontrol')); check_admin_referer("new-cron"); extract($_POST, EXTR_PREFIX_ALL, 'in'); $args = array('code'=>stripslashes($in_hookcode)); $hookname = CRONTROL_CRON_JOB; $this->add_cron($in_next_run, $in_schedule, $hookname, $args); wp_redirect("edit.php?page=crontrol_admin_manage_page&crontrol_message=5&crontrol_name={$in_hookname}"); } else if( isset($_POST['edit_cron']) ) { if( !current_user_can('manage_options') ) die(__('You are not allowed to edit cron events.', 'crontrol')); extract($_POST, EXTR_PREFIX_ALL, 'in'); check_admin_referer("edit-cron_{$in_original_hookname}_{$in_original_sig}_{$in_original_next_run}"); $in_args = $this->json->decode(stripslashes($in_args)); $i=$this->delete_cron($in_original_hookname, $in_original_sig, $in_original_next_run); $i=$this->add_cron($in_next_run, $in_schedule, $in_hookname, $in_args); wp_redirect("edit.php?page=crontrol_admin_manage_page&crontrol_message=4&crontrol_name={$in_hookname}"); } else if( isset($_POST['edit_php_cron']) ) { if( !current_user_can('manage_options') ) die(__('You are not allowed to edit cron events.', 'crontrol')); extract($_POST, EXTR_PREFIX_ALL, 'in'); check_admin_referer("edit-cron_{$in_original_hookname}_{$in_original_sig}_{$in_original_next_run}"); $args['code'] = stripslashes($in_hookcode); $hookname = CRONTROL_CRON_JOB; $args = array('code'=>stripslashes($in_hookcode)); $i=$this->delete_cron($in_original_hookname, $in_original_sig, $in_original_next_run); $i=$this->add_cron($in_next_run, $in_schedule, $hookname, $args); wp_redirect("edit.php?page=crontrol_admin_manage_page&crontrol_message=4&crontrol_name={$in_hookname}"); } else if( isset($_POST['new_schedule']) ) { if( !current_user_can('manage_options') ) die(__('You are not allowed to add new cron schedules.', 'crontrol')); check_admin_referer("new-sched"); $name = $_POST['internal_name']; $interval = $_POST['interval']; $display = $_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( $future===FALSE || $future == -1 || $now>$future) { wp_redirect("options-general.php?page=crontrol_admin_options_page&crontrol_message=7&crontrol_name=".urlencode($interval)); return; } $interval = $future-$now; } else if( $interval<=0 ) { wp_redirect("options-general.php?page=crontrol_admin_options_page&crontrol_message=7&crontrol_name=".urlencode($interval)); return; } $this->add_schedule($name, $interval, $display); wp_redirect("options-general.php?page=crontrol_admin_options_page&crontrol_message=3&crontrol_name=$name"); } else if( isset($_GET['action']) && $_GET['action']=='delete-sched') { if( !current_user_can('manage_options') ) die(__('You are not allowed to delete cron schedules.', 'crontrol')); $id = $_GET['id']; check_admin_referer("delete-sched_$id"); $this->delete_schedule($id); wp_redirect("options-general.php?page=crontrol_admin_options_page&crontrol_message=2&crontrol_name=$id"); } else if( isset($_GET['action']) && $_GET['action']=='delete-cron') { if( !current_user_can('manage_options') ) die(__('You are not allowed to delete cron events.', 'crontrol')); $id = $_GET['id']; $sig = $_GET['sig']; $next_run = $_GET['next_run']; check_admin_referer("delete-cron_$id_$sig_{$next_run}"); if( $this->delete_cron($id, $sig, $next_run) ) { wp_redirect("edit.php?page=crontrol_admin_manage_page&crontrol_message=6&crontrol_name=$id"); } else { wp_redirect("edit.php?page=crontrol_admin_manage_page&crontrol_message=7&crontrol_name=$id"); }; } else if( isset($_GET['action']) && $_GET['action']=='run-cron') { if( !current_user_can('manage_options') ) die(__('You are not allowed to run cron events.', 'crontrol')); $id = $_GET['id']; $sig = $_GET['sig']; check_admin_referer("run-cron_$id_$sig"); if( $this->run_cron($id, $sig) ) { wp_redirect("edit.php?page=crontrol_admin_manage_page&crontrol_message=1&crontrol_name=$id"); } else { wp_redirect("edit.php?page=crontrol_admin_manage_page&crontrol_message=8&crontrol_name=$id"); } } } /** * Executes a cron entry immediately. * * Executes an entry by scheduling a new single event with the same arguments. * * @param string $hookname The hookname of the cron entry to run */ function run_cron($hookname, $sig) { $crons = _get_cron_array(); foreach( $crons as $time=>$cron ) { if( isset( $cron[$hookname][$sig] ) ) { $args = $cron[$hookname][$sig]['args']; wp_schedule_single_event(time()-1, $hookname, $args); spawn_cron(); return true; } } return false; } /** * Adds a new cron entry. * * @param string $next_run A human-readable (strtotime) time that the entry should be run at * @param string $schedule The recurrence of the cron entry * @param string $hookname The name of the hook to execute * @param array $args Arguments to add to the cron entry */ function add_cron($next_run, $schedule, $hookname, $args) { $next_run = strtotime($next_run); if( $next_run===FALSE || $next_run==-1 ) $next_run=time(); if( !is_array($args) ) $args=array(); if( $schedule == '_oneoff' ) { return wp_schedule_single_event($next_run, $hookname, $args) === NULL; } else { return wp_schedule_event( $next_run, $schedule, $hookname, $args ) === NULL; } } /** * Deletes a cron entry. * * @param string $name The hookname of the entry to delete. */ function delete_cron($to_delete, $sig, $next_run) { $crons = _get_cron_array(); if( isset( $crons[$next_run][$to_delete][$sig] ) ) { $args = $crons[$next_run][$to_delete][$sig]['args']; wp_unschedule_event($next_run, $to_delete, $args); return true; } return false; } /** * Adds a new custom cron schedule. * * @param string $name The internal name of the schedule * @param int $interval The interval between executions of the new schedule * @param string $display The display name of the schedule */ function add_schedule($name, $interval, $display) { $old_scheds = get_option('crontrol_schedules'); $old_scheds[$name] = array('interval'=>$interval, 'display'=>$display); update_option('crontrol_schedules', $old_scheds); } /** * Deletes a custom cron schedule. * * @param string $name The internal_name of the schedule to delete. */ function delete_schedule($name) { $scheds = get_option('crontrol_schedules'); unset($scheds[$name]); update_option('crontrol_schedules', $scheds); } /** * Sets up the plugin environment upon first activation. * * Run using the 'activate_' action. */ function activate() { $extra_scheds = array('twicedaily'=>array('interval'=>43200, 'display'=>__('Twice Daily', 'crontrol'))); add_option('crontrol_schedules', $extra_scheds); // if there's never been a cron entry, _get_cron_array will return FALSE if( _get_cron_array() === FALSE ) { _set_cron_array(array()); } } /** * Adds options & management pages to the admin menu. * * Run using the 'admin_menu' action. */ function admin_menu() { $page = add_options_page('Crontrol', 'Crontrol', 'manage_options', 'crontrol_admin_options_page', array(&$this, 'admin_options_page') ); $page = add_management_page('Crontrol', "Crontrol", 'manage_options', 'crontrol_admin_manage_page', array(&$this, 'admin_manage_page') ); } /** * Gives WordPress the plugin's set of cron schedules. * * Called by the 'cron_schedules' filter. * * @param array $scheds The current cron schedules. Usually an empty array. * @return array The existing cron schedules along with the plugin's schedules. */ function cron_schedules($scheds) { $new_scheds = get_option('crontrol_schedules'); return array_merge($new_scheds, $scheds); } /** * Displays the options page for the plugin. */ function admin_options_page() { $schedules = $this->get_schedules(); $custom_schedules = get_option('crontrol_schedules'); $custom_keys = array_keys($custom_schedules); if( isset($_GET['crontrol_message']) ) { $messages = array( '2' => __("Successfully deleted the cron schedule %s", 'crontrol'), '3' => __("Successfully added the cron schedule %s", 'crontrol'), '7' => __("Cron schedule not added because there was a problem parsing %s", 'crontrol')); $hook = $_GET['crontrol_name']; $msg = sprintf($messages[$_GET['crontrol_message']], $hook); echo "

$msg

"; } ?>

$data ) { echo ""; echo ""; echo ""; echo ""; if( in_array($name, $custom_keys) ) { echo ""; } else { echo "\n"; } echo ""; $class = empty($class)?"alternate":""; } } ?>
$name{$data['interval']} (".$this->interval($data['interval'])."){$data['display']}".__( 'Delete' )." 

get_schedules(); echo 'this form.', 'crontrol'); $link = ' ('. __('Add new entry', 'crontrol') .')'; } else { $helper_text = __('Cron entries trigger actions in your code. A cron entry added using the form below needs a corresponding action hook somewhere in code, perhaps the functions.php file in your theme. It is also possible to create your action hook from within WordPress using this form.', 'crontrol'); $link = ' ('. __('Add new PHP entry', 'crontrol') .')'; } if( is_array($existing) ) { $other_fields = wp_nonce_field( "edit-cron_{$existing['hookname']}_{$existing['sig']}_{$existing['next_run']}", "_wpnonce", true, false ); $other_fields .= ''; $other_fields .= ''; $other_fields .= ''; $existing['args'] = $is_php ? htmlentities($existing['args']['code']) : htmlentities($this->json->encode($existing['args'])); $existing['next_run'] = strftime("%D %T", $existing['next_run']); $action = $is_php ? 'edit_php_cron' : 'edit_cron'; $button = $is_php ? __('Modify PHP Cron Entry', 'crontrol') : __('Modify Cron Entry', 'crontrol'); $link = false; } else { $other_fields = wp_nonce_field( "new-cron", "_wpnonce", true, false ); $existing = array('hookname'=>'','hookcode'=>'','args'=>'','next_run'=>'now','schedule'=>false); $action = $is_php ? 'new_php_cron' : 'new_cron'; $button = $is_php ? __('Add PHP Cron Entry', 'crontrol') : __('Add Cron Entry', 'crontrol'); } ?>

'.$link.''; ?>



schedules_dropdown($existing['schedule']) ?>

__('Successfully executed the cron entry %s', 'crontrol'), '4' => __('Successfully edited the cron entry %s', 'crontrol'), '5' => __('Successfully created the cron entry %s', 'crontrol'), '6' => __('Successfully deleted the cron entry %s', 'crontrol'), '7' => __('Failed to the delete the cron entry %s', 'crontrol'), '8' => __('Failed to the execute the cron entry %s', 'crontrol')); $hook = $_GET['crontrol_name']; $msg = sprintf($messages[$_GET['crontrol_message']], $hook); echo "

$msg

"; } $crons = _get_cron_array(); $schedules = $this->get_schedules(); $doing_edit = (isset( $_GET['action']) && $_GET['action']=='edit-cron') ? $_GET['id'] : false ; ?>

$cron ) { foreach( $cron as $hook=>$dings) { foreach( $dings as $sig=>$data ) { if( $doing_edit && $doing_edit==$hook && $time == $_GET['next_run'] && $sig==$_GET['sig'] ) { $doing_edit = array('hookname'=>$hook, 'next_run'=>$time, 'schedule'=>($data['schedule'] ? $data['schedule'] : '_oneoff'), 'sig'=>$sig, 'args'=>$data['args']); } echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; $class = empty($class)?"alternate":""; } } } } ?>
".($hook==CRONTROL_CRON_JOB ? __('PHP Cron', 'crontrol') : $hook)."".($hook==CRONTROL_CRON_JOB ? __('PHP Code', 'crontrol') : $this->json->encode($data['args']))."".strftime("%D %T", $time)." (".$this->time_since(time(), $time).")".($data['schedule'] ? $data['interval'].' ('.$this->interval($data['interval']).')' : __('Non-repeating', 'crontrol'))."EditDo NowDelete
show_cron_form($doing_edit['hookname']==CRONTROL_CRON_JOB, $doing_edit); } else { $this->show_cron_form($_GET['action']=='new-php-cron', false); } } /** * Pretty-prints the difference in two times. * * @param time $older_date * @param time $newer_date * @return string The pretty time_since value * @link http://binarybonsai.com/code/timesince.txt */ function time_since($older_date, $newer_date) { return $this->interval( $newer_date - $older_date ); } function interval( $since ) { // array of time period chunks $chunks = array( array(60 * 60 * 24 * 365 , __ngettext_noop('%s year', '%s years', 'crontrol')), array(60 * 60 * 24 * 30 , __ngettext_noop('%s month', '%s months', 'crontrol')), array(60 * 60 * 24 * 7, __ngettext_noop('%s week', '%s weeks', 'crontrol')), array(60 * 60 * 24 , __ngettext_noop('%s day', '%s days', 'crontrol')), array(60 * 60 , __ngettext_noop('%s hour', '%s hours', 'crontrol')), array(60 , __ngettext_noop('%s minute', '%s minutes', 'crontrol')), array( 1 , __ngettext_noop('%s second', '%s seconds', 'crontrol')), ); if( $since <= 0 ) { return __('now', '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: // step one: the first chunk for ($i = 0, $j = count($chunks); $i < $j; $i++) { $seconds = $chunks[$i][0]; $name = $chunks[$i][1]; // finding the biggest chunk (if the chunk fits, break) if (($count = floor($since / $seconds)) != 0) { break; } } // set output var $output = sprintf(__ngettext($name[0], $name[1], $count, 'crontrol'), $count); // step two: the second chunk if ($i + 1 < $j) { $seconds2 = $chunks[$i + 1][0]; $name2 = $chunks[$i + 1][1]; if (($count2 = floor(($since - ($seconds * $count)) / $seconds2)) != 0) { // add to output var $output .= ' '.sprintf(__ngettext($name2[0], $name2[1], $count2, 'crontrol'), $count2); } } return $output; } } if( !function_exists('json_encode' ) ) { if( !class_exists('Services_JSON') ) require_once('JSON.php'); class Crontrol_JSON { var $json; function Crontrol_JSON() { $this->json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); } function encode($in) { return $this->json->encode($in); } function decode($in) { return $this->json->decode($in); } } } else { class Crontrol_JSON { function encode($in) { return json_encode($in); } function decode($in) { return json_decode($in, true); } } } // Get this show on the road new Crontrol(); ?>