Commit bc8ab37d authored by Erick Hitter's avatar Erick Hitter
Browse files

Initial commit of version 0.6 from http://wordpress.org/extend/plugins/wp-cron-control/.

parents
=== WP-Cron Control ===
Contributors: tott, automattic
Tags: wp-cron, cron, cron jobs, post missed schedule, scheduled posts
Donate link: http://hitchhackerguide.com
Tested up to: 3.3
Stable tag: trunk
This plugin allows you to take control over the execution of cron jobs.
== Description ==
This plugin allows you to take control over the execution of cron jobs. It's mainly useful for sites that either don't get enough comments to ensure a frequent execution of wp-cron or for sites where the execution of cron via regular methods can cause race conditions resulting in multiple execution of wp-cron at the same time. It can also help when you run into posts that missed their schedule.
This plugin implements a secret parameter and ensures that cron jobs are only executed when this parameter is existing.
== Installation ==
* Install either via the WordPress.org plugin directory, or by uploading the files to your server.
* Activate the Plugin and ensure that you enable the feature in the plugins' settings screen
* Follow the instructions on the plugins' settings screen in order to set up a cron job that either calls `php wp-cron-control.php http://blog.address secret_string` or `wget -q "http://blog.address/wp-cron.php?doing_wp_cron&secret_string"`
* If you like to have a global secret string you can define it in your wp-config.php by adding `define( 'WP_CRON_CONTROL_SECRET', my_secret_string' );`
== Limitations ==
This plugin performs a `remove_action( 'sanitize_comment_cookies', 'wp_cron' );` call in order to disable the spawning of new cron processes via the regular WordPress method. If `wp_cron` is hooked in an other action or called directly this might cause trouble.
== Screenshots ==
1. Settings screen to enable/disable various features.
== ChangeLog ==
= Version 0.6 =
* Make sure that validated wp-cron-control requests also are valid in wp-cron.php by setting the global $doing_wp_cron value
= Version 0.5 =
* Adjustments for improved cron locking introduced in WordPress 3.3 http://core.trac.wordpress.org/changeset/18659
= Version 0.4 =
* Implementing feedback from Yoast http://yoast.com/wp-plugin-review/wp-cron-control/, fixing button classes, more inline comments
= Version 0.3 =
* Added option to enable extra check that would search for missing jobs for scheduled posts and add them if necessary.
= Version 0.2 =
* Added capability check in settings page
= Version 0.1 =
* Initial version of this plugin.
<?php
/*
Plugin Name: WP-Cron Control
Plugin URI: http://wordpress.org/extend/plugins/wp-cron-control/
Description: get control over wp-cron execution.
Author: Thorsten Ott, Automattic
Version: 0.6
Author URI: http://hitchhackerguide.com
*/
class WP_Cron_Control {
private static $__instance = NULL;
private $settings = array();
private $default_settings = array();
private $settings_texts = array();
private $plugin_prefix = 'wpcroncontrol_';
private $plugin_name = 'WP-Cron Control';
private $settings_page_name ='WP-Cron Control Settings';
private $dashed_name = 'wp-cron-control';
private $js_version = '20110801';
private $css_version = '20110801';
private $define_global_secret = NULL; // if this is set, it's value will be used as secret instead of the option
public function __construct() {
global $blog_id;
// this allows overwriting of the default secret with a value set in the code. Useful If you don't want to give control to users.
if ( NULL <> $this->define_global_secret && !defined( 'WP_CRON_CONTROL_SECRET' ) )
define( 'WP_CRON_CONTROL_SECRET', $this->define_global_secret );
add_action( 'admin_init', array( &$this, 'register_setting' ) );
add_action( 'admin_menu', array( &$this, 'register_settings_page' ) );
/**
* Default settings that will be used for the setup. You can alter these value with a simple filter such as this
* add_filter( 'wpcroncontrol_default_settings', 'mywpcroncontrol_settings' );
* function mywpcroncontrol_settings( $settings ) {
* $settings['secret_string'] = 'i am more secret than the default';
* return $settings;
* }
*/
$this->default_settings = (array) apply_filters( $this->plugin_prefix . 'default_settings', array(
'enable' => 1,
'enable_scheduled_post_validation' => 0,
'secret_string' => md5( __FILE__ . $blog_id ),
) );
/**
* Define fields that will be used on the options page
* the array key is the field_name the array then describes the label, description and type of the field. possible values for field types are 'text' and 'yesno' for a text field or input fields or 'echo' for a simple output
* a filter similar to the default settings (ie wpcroncontrol_settings_texts) can be used to alter this values
*/
$this->settings_texts = (array) apply_filters( $this->plugin_prefix . 'settings_texts', array(
'enable' => array( 'label' => 'Enable ' . $this->plugin_name, 'desc' => 'Enable this plugin and allow requests to wp-cron.php only with the appended secret parameter.', 'type' => 'yesno' ),
'secret_string' => array( 'label' => 'Secret string', 'desc' => 'The secret parameter that needs to be appended to wp-cron.php requests.', 'type' => 'text' ),
'enable_scheduled_post_validation' => array( 'label' => 'Enable scheduled post validation', 'desc' => 'In some rare cases it can happen that even when running wp-cron via a scheduled system cron job posts miss their schedule. This feature makes sure that there is a scheduled event for each scheduled post.', 'type' => 'yesno' ),
) );
$user_settings = get_option( $this->plugin_prefix . 'settings' );
if ( false === $user_settings )
$user_settings = array();
// after getting default settings make sure to parse the arguments together with the user settings
$this->settings = wp_parse_args( $user_settings, $this->default_settings );
/**
* If you define( 'WP_CRON_CONTROL_SECRET', 'my_super_secret_string' ); in your wp-config.php or your theme then
* users are not allowed to change the secret, so we output the existing secret string rather than allowing to add a new one
*/
if ( defined( 'WP_CRON_CONTROL_SECRET' ) ) {
$this->settings_texts['secret_string']['type'] = 'echo';
$this->settings_texts['secret_string']['desc'] = $this->settings_texts['secret_string']['desc'] . " Cannot be changed as it is defined via WP_CRON_CONTROL_SECRET";
$this->settings['secret_string'] = WP_CRON_CONTROL_SECRET;
}
}
public static function init() {
if ( 1 == self::instance()->settings['enable'] ) {
}
self::instance()->prepare();
}
/*
* Use this singleton to address methods
*/
public static function instance() {
if ( self::$__instance == NULL )
self::$__instance = new WP_Cron_Control;
return self::$__instance;
}
public function prepare() {
/**
* If a css file for this plugin exists in ./css/wp-cron-control.css make sure it's included
*/
if ( file_exists( dirname( __FILE__ ) . "/css/" . $this->dashed_name . ".css" ) )
wp_enqueue_style( $this->dashed_name, plugins_url( "css/" . $this->dashed_name . ".css", __FILE__ ), $deps = array(), $this->css_version );
/**
* If a js file for this plugin exists in ./js/wp-cron-control.css make sure it's included
*/
if ( file_exists( dirname( __FILE__ ) . "/js/" . $this->dashed_name . ".js" ) )
wp_enqueue_script( $this->dashed_name, plugins_url( "js/" . $this->dashed_name . ".js", __FILE__ ), array(), $this->js_version, true );
/**
* When the plugin is enabled make sure remove the default behavior for issueing wp-cron requests and add our own method
* see: http://core.trac.wordpress.org/browser/trunk/wp-includes/default-filters.php#L236
* and http://core.trac.wordpress.org/browser/trunk/wp-includes/cron.php#L258
*/
if ( 1 == $this->settings['enable'] ) {
remove_action( 'sanitize_comment_cookies', 'wp_cron' );
add_action( 'init', array( &$this, 'validate_cron_request' ) );
}
}
public function register_settings_page() {
add_options_page( $this->settings_page_name, $this->plugin_name, 'manage_options', $this->dashed_name, array( &$this, 'settings_page' ) );
}
public function register_setting() {
register_setting( $this->plugin_prefix . 'settings', $this->plugin_prefix . 'settings', array( &$this, 'validate_settings') );
}
public function validate_settings( $settings ) {
// reset to defaults
if ( !empty( $_POST[ $this->dashed_name . '-defaults'] ) ) {
$settings = $this->default_settings;
$_REQUEST['_wp_http_referer'] = add_query_arg( 'defaults', 'true', $_REQUEST['_wp_http_referer'] );
// or do some custom validations
} else {
}
return $settings;
}
public function settings_page() {
if ( !current_user_can( 'manage_options' ) ) {
wp_die( __( 'You do not permission to access this page' ) );
}
?>
<div class="wrap">
<?php if ( function_exists('screen_icon') ) screen_icon(); ?>
<h2><?php echo $this->settings_page_name; ?></h2>
<form method="post" action="options.php">
<?php settings_fields( $this->plugin_prefix . 'settings' ); ?>
<table class="form-table">
<?php foreach( $this->settings as $setting => $value): ?>
<tr valign="top">
<th scope="row"><label for="<?php echo $this->dashed_name . '-' . $setting; ?>"><?php if ( isset( $this->settings_texts[$setting]['label'] ) ) { echo $this->settings_texts[$setting]['label']; } else { echo $setting; } ?></label></th>
<td>
<?php
/**
* Implement various handlers for the different types of fields. This could be easily extended to allow for drop-down boxes, textareas and more
*/
?>
<?php switch( $this->settings_texts[$setting]['type'] ):
case 'yesno': ?>
<select name="<?php echo $this->plugin_prefix; ?>settings[<?php echo $setting; ?>]" id="<?php echo $this->dashed_name . '-' . $setting; ?>" class="postform">
<?php
$yesno = array( 0 => 'No', 1 => 'Yes' );
foreach ( $yesno as $val => $txt ) {
echo '<option value="' . esc_attr( $val ) . '"' . selected( $value, $val, false ) . '>' . esc_html( $txt ) . "&nbsp;</option>\n";
}
?>
</select><br />
<?php break;
case 'text': ?>
<div><input type="text" name="<?php echo $this->plugin_prefix; ?>settings[<?php echo $setting; ?>]" id="<?php echo $this->dashed_name . '-' . $setting; ?>" class="postform" value="<?php echo esc_attr( $value ); ?>" /></div>
<?php break;
case 'echo': ?>
<div><span id="<?php echo $this->dashed_name . '-' . $setting; ?>" class="postform"><?php echo esc_attr( $value ); ?></span></div>
<?php break;
default: ?>
<?php echo $this->settings_texts[$setting]['type']; ?>
<?php break;
endswitch; ?>
<?php if ( !empty( $this->settings_texts[$setting]['desc'] ) ) { echo $this->settings_texts[$setting]['desc']; } ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( 1 == $this->settings['enable'] ): ?>
<tr>
<td colspan="3">
<p>You enabled wp-cron-control. To make sure that scheduled tasks are still executed correctly you will need to setup a system cron job that will call wp-cron.php with the secret parameter defined in the settings.</p>
<p>
You can either use the function defined in this script and setup a cron job that calls either
</p>
<p><code>php <?php echo __FILE__; ?> <?php echo get_site_url(); ?> <?php echo $this->settings['secret_string']; ?></code></p>
<p>or</p>
<p><code>wget -q "<?php echo get_site_url(); ?>/wp-cron.php?doing_wp_cron&<?php echo $this->settings['secret_string']; ?>"</code></p>
<p>You can setup an interval as low as one minute, but should consider a reasonable value of 5-15 minutes as well.</p>
<p>If you need help setting up a cron job please refer to the documentation that your provider offers.</p>
<p>Anyway, chances are high that either <a href="http://docs.cpanel.net/twiki/bin/view/AllDocumentation/CpanelDocs/CronJobs#Adding a cron job" target="_blank">the CPanel</a>, <a href="http://download1.parallels.com/Plesk/PP10/10.3.1/Doc/en-US/online/plesk-administrator-guide/plesk-control-panel-user-guide/index.htm?fileName=65208.htm" target="_blank">Plesk</a> or <a href="http://www.thegeekstuff.com/2011/07/php-cron-job/" target="_blank">the crontab</a> documentation will help you.</p>
</td>
</tr>
<?php endif; ?>
</table>
<p class="submit">
<?php
if ( function_exists( 'submit_button' ) ) {
submit_button( null, 'primary', $this->dashed_name . '-submit', false );
echo ' ';
submit_button( 'Reset to Defaults', '', $this->dashed_name . '-defaults', false );
} else {
echo '<input type="submit" name="' . $this->dashed_name . '-submit" class="button-primary" value="Save Changes" />' . "\n";
echo '<input type="submit" name="' . $this->dashed_name . '-defaults" id="' . $this->dashed_name . '-defaults" class="button-primary" value="Reset to Defaults" />' . "\n";
}
?>
</p>
</form>
</div>
<?php
}
/**
* Alternative function to the current wp_cron function that would usually executed on sanitize_comment_cookies
*/
public function validate_cron_request() {
// make sure we're in wp-cron.php
if ( false !== strpos( $_SERVER['REQUEST_URI'], '/wp-cron.php' ) ) {
// grab the necessary secret string
if ( defined( 'WP_CRON_CONTROL_SECRET' ) )
$secret = WP_CRON_CONTROL_SECRET;
else
$secret = $this->settings['secret_string'];
// make sure a secret string is provided in the ur
if ( isset( $_GET[$secret] ) ) {
// check if there is already a cron request running
$local_time = time();
if ( function_exists( '_get_cron_lock' ) )
$flag = _get_cron_lock();
else
$flag = get_transient('doing_cron');
if ( defined( 'WP_CRON_LOCK_TIMEOUT' ) )
$timeout = WP_CRON_LOCK_TIMEOUT;
else
$timeout = 60;
if ( $flag > $local_time + 10 * $timeout )
$flag = 0;
// don't run if another process is currently running it or more than once every 60 sec.
if ( $flag + $timeout > $local_time )
die( 'another cron process running or previous not older than 60 secs' );
// set a transient to allow locking down parallel requests
set_transient( 'doing_cron', $local_time );
// make sure the request also validates in wp-cron.php
global $doing_wp_cron;
$doing_wp_cron = $local_time;
// if settings allow it validate if there are any scheduled posts without a cron event
if ( 1 == self::instance()->settings['enable_scheduled_post_validation'] ) {
$this->validate_scheduled_posts();
}
return true;
}
// something went wrong
die( 'invalid secret string' );
}
// for all other cases disable wp-cron.php and spawn_cron() by telling the system it's already running
if ( !defined( 'DOING_CRON' ) )
define( 'DOING_CRON', true );
// and also disable the wp_cron() call execution
if ( !defined( 'DISABLE_WP_CRON' ) )
define( 'DISABLE_WP_CRON', true );
return false;
}
public function validate_scheduled_posts() {
global $wpdb;
// grab all scheduled posts from posts table
$sql = $wpdb->prepare( "SELECT ID, post_date_gmt FROM $wpdb->posts WHERE post_status = 'future' " );
$results = $wpdb->get_results( $sql );
$return = true;
// if none exists just return
if ( empty( $results ) )
return true;
// otherwise check each of them
foreach ( $results as $r ) {
$gmt_time = strtotime( $r->post_date_gmt . ' GMT' );
// grab the scheduled job for this post
$timestamp = wp_next_scheduled( 'publish_future_post', array( (int) $r->ID ) );
if ( $timestamp === false ) {
// if none exists issue one
wp_schedule_single_event( $gmt_time, 'publish_future_post', array( (int) $r->ID ) );
$return = false;
} else {
// if one exists update timestamp to adjust for daylights savings change, when necessary
if ( $timestamp != $gmt_time ) {
wp_clear_scheduled_hook( 'publish_future_post', array( (int) $r->ID ) );
wp_schedule_single_event( $gmt_time, 'publish_future_post', array( (int) $r->ID ) );
$new_date = date( 'Y-m-d H:i:s', $gmt_time );
$sql_u = $wpdb->prepare( "UPDATE $wpdb->posts SET post_date_gmt=%s WHERE ID=%d", $new_date, $r->ID );
$wpdb->query( $sql_u );
$return = false;
}
}
}
return $return;
}
}
/**
* This method can be used to initiate a cron call via cli
*/
function wp_cron_control_call_cron( $blog_address, $secret ) {
$cron_url = $blog_address . '/wp-cron.php?doing_wp_cron&' . $secret;
$ch = curl_init( $cron_url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 0 );
curl_setopt( $ch, CURLOPT_TIMEOUT, '3' );
$result = curl_exec( $ch );
curl_close( $ch );
return $result;
}
// if we loaded wp-config then ABSPATH is defined and we know the script was not called directly to issue a cli call
if ( defined('ABSPATH') ) {
WP_Cron_Control::init();
} else {
// otherwise parse the arguments and call the cron.
if ( !empty( $argv ) && $argv[0] == basename( __FILE__ ) || $argv[0] == __FILE__ ) {
if ( isset( $argv[1] ) && isset( $argv[2] ) ) {
wp_cron_control_call_cron( $argv[1], $argv[2] );
} else {
echo "Usage: php " . __FILE__ . " <blog_address> <secret_string>\n";
echo "Example: php " . __FILE__ . " http://my.blog.com efe18b0e53498e737da9b91cf4ca3d25\n";
exit;
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment