Skip to content

Commit

Permalink
Merge pull request #77 from tott/feature/45-auto-retry
Browse files Browse the repository at this point in the history
Feature/45 auto retry
  • Loading branch information
joshbetz committed Jun 23, 2015
2 parents c645880 + 05ad92d commit 7149bc9
Show file tree
Hide file tree
Showing 9 changed files with 689 additions and 398 deletions.
156 changes: 156 additions & 0 deletions includes/class-syndication-admin-notices.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

/**
* Class to handle Admin notices for dismissable user notifications
*/
class Syndication_Logger_Admin_Notice {

private static $notice_option = 'syn_notices';
private static $notice_bundles_option = 'syn_notice_bundles';

private static $dismiss_parameter = 'syn_dismiss';

public function __construct() {
add_action( 'admin_init', array( __CLASS__, 'handle_dismiss_syndication_notice' ) );
add_action( 'admin_notices', array( __CLASS__, 'display_valid_notices' ) );
}

/**
* Create a admin notice
* @param text $message_text The message you would like to show
* @param string $message_type A message type, shown alongside with your message and used to categorize and bundle messages of the same type
* @param string $class css class applied to the notice eg. 'updated', 'error', 'update-nag'
* @param boolean $summarize_multiple setting this to true allows summarizing messages of the same message_type into one message. The message text is then passed through the syn_message_text_multiple filter and all messages of this type can be dismissed at once
*/
public static function add_notice( $message_text, $message_type = 'Syndication', $class = 'updated', $summarize_multiple = false ) {
$notices = get_option( self::$notice_option );

$changed = false;
$message_key = md5( $message_type . $message_text );
if ( ! is_array( $notices ) || ! isset( $notices[$message_type] ) || ! isset( $notices[$message_type][$message_key] ) ) {
$notices[$message_type][$message_key] = array(
'message_text' => sanitize_text_field( $message_text ),
'summarize_multiple' => (boolean) $summarize_multiple,
'message_type' => sanitize_text_field( $message_type ),
'class' => sanitize_text_field( $class ),
);
$changed = true;
}

if ( true === $changed ) {
update_option( self::$notice_option, $notices );
}

return true;
}

/**
* Evaluate and display valid notices
*/
public static function display_valid_notices() {
$capability = apply_filters( 'syn_syndicate_cap', 'manage_options' );

$messages = get_option( self::$notice_option );
$notice_bundles = get_option( self::$notice_bundles_option );
$messages_to_display = array();
$notice_bundles_changed = false;

if ( !is_array( $messages ) || empty( $messages ) ) {
return;
}

foreach( $messages as $message_type => $message_values ) {
foreach( $message_values as $message_key => $message_data ) {
if ( isset( $message_data['summarize_multiple'] ) && true === $message_data['summarize_multiple'] ) {
$message_text = apply_filters( 'syn_message_text_multiple', $message_data['message_text'], $message_data );
} else {
$message_text = apply_filters( 'syn_message_text', $message_data['message_text'], $message_data );
}

$new_message_key = md5( $message_type . $message_text );
$new_message_data = array(
'message_text' => sanitize_text_field( $message_text ),
'summarize_multiple' => (boolean) $message_data['summarize_multiple'],
'message_type' => sanitize_text_field( $message_data['message_type'] ),
'class' => sanitize_text_field( $message_data['class'] )
);

if ( $new_message_key != $message_key ) {
if ( ! isset( $notice_bundles[$new_message_key] ) || ! in_array( $message_key, $notice_bundles[$new_message_key] ) ) {
$notice_bundles[$new_message_key][] = $message_key;
$notice_bundles_changed = true;
}
}

if ( current_user_can( $capability ) ) {
$messages_to_display[$message_type][$new_message_key] = $new_message_data;
}
}
}

if ( true === $notice_bundles_changed ) {
update_option( self::$notice_bundles_option, $notice_bundles );
}

foreach( $messages_to_display as $message_type => $message_values ) {
foreach( $message_values as $message_key => $message_data ) {
$dismiss_nonce = wp_create_nonce( esc_attr( $message_key ) );
printf( '<div class="%s"><p>', esc_attr( $message_data['class'] ) );
printf( __('%1$s : %2$s <a href="%3$s">Hide Notice</a>'), esc_html( $message_type ), wp_kses_post( $message_data['message_text'] ), add_query_arg( array( self::$dismiss_parameter => esc_attr( $message_key ), 'syn_dismiss_nonce' => esc_attr( $dismiss_nonce ) ) ) );
printf( '</p></div>' );
}
}
}

/**
* Handle dismissing of notices
*/
public static function handle_dismiss_syndication_notice() {
$capability = apply_filters( 'syn_syndicate_cap', 'manage_options' );

// add nonce
if ( isset( $_GET[self::$dismiss_parameter] ) && current_user_can( $capability ) ) {

$dismiss_key = esc_attr( $_GET[self::$dismiss_parameter] );
$dismiss_nonce = esc_attr( $_GET['syn_dismiss_nonce'] );
if ( ! wp_verify_nonce( $dismiss_nonce, $dismiss_key ) ) {
wp_die( __( "Invalid security check" ) );
}
$messages = get_option( self::$notice_option );
$notice_bundles = get_option( self::$notice_bundles_option );

$dismiss_items = array();
if ( isset( $notice_bundles[$dismiss_key] ) ) {
$dismiss_items = $notice_bundles[$dismiss_key];
} else {
$dismiss_items = array( $dismiss_key );
}

foreach( $messages as $message_type => $message_values ) {
$message_keys = array_keys( $message_values );
$dismiss_it = array_intersect( $message_keys, $dismiss_items );
foreach( $dismiss_it as $dismiss_it_key ) {
unset( $messages[$message_type][$dismiss_it_key] );
}
}

if ( isset( $notice_bundles[$dismiss_key] ) ) {
unset( $notice_bundles[$dismiss_key] );
}

update_option( self::$notice_option, $messages );
update_option( self::$notice_bundles_option, $notice_bundles );

}
}
}

add_filter( 'syn_message_text_multiple', 'syn_handle_multiple_error_notices', 10, 2 );
function syn_handle_multiple_error_notices( $message, $message_data ) {
return __( 'There have been multiple errors. Please validate your syndication logs' );
}

add_action( 'push_syndication_site_disabled', 'syn_add_site_disabled_notice', 10, 2 );
function syn_add_site_disabled_notice( $site_id, $count ) {
Syndication_Logger_Admin_Notice::add_notice( $message_text = sprintf( __( 'Site %d disabled after %d pull failure(s).', 'push-syndication' ), (int) $site_id, (int) $count ), $message_type = 'Syndication site disabled', $class = 'error', $summarize_multiple = false );
}
2 changes: 1 addition & 1 deletion includes/class-syndication-logger-viewer.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ private function create_log_id_dropdown() {
}
}

echo implode( "\n", $log_ids );
echo implode( "\n", $log_ids ); // sanitization happens right above
?>
</select>

Expand Down
6 changes: 6 additions & 0 deletions includes/class-syndication-logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/**
* Syndication_Logger implements a unified logging mechanism for the syndication plugin.
*
* @todo implement removal of old log messages, cron to remove messages older than X?
*/
class Syndication_Logger {

Expand Down Expand Up @@ -76,6 +78,10 @@ public function __construct() {
*/
public static function init() {
self::instance()->log_id = md5( uniqid() . microtime() );

require_once( dirname( __FILE__ ) . '/class-syndication-admin-notices.php' );
new Syndication_Logger_Admin_Notice;

if ( is_admin() ) {
require_once( dirname( __FILE__ ) . '/class-syndication-logger-viewer.php' );
$viewer = new Syndication_Logger_Viewer;
Expand Down
145 changes: 145 additions & 0 deletions includes/class-syndication-site-auto-retry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

/**
* Failed Syndication Auto Retry
*
* Watches syndication events and handles site-related failures.
*
* There is a cron setup in class-wp-push-syndication-server.php:1266 which will
* retry a failed pull X times (set in admin settings) and when that limit is met
* the site becomes 'disabled'. The retry interval could be anything from 5min to 1hr
* or longer.
*
* After each failed retry we don't want to wait the retry interval to try again
* if the remote server was simply unreachable for a brief spell. This auto retry
* bypasses the retry interval and retries the pull once a minute up to 3 tries.
*
* @uses Syndication_Logger
*/

class Failed_Syndication_Auto_Retry {

/**
* Hook into WordPress
*/
public function __construct() {

// Watch the push_syndication_event action for site pull failures
add_action( 'push_syndication_after_event_pull_failure', array( $this, 'handle_pull_failure_event' ), 10, 2 );

// Watch the push_syndication_event action for site pull successes
add_action( 'push_syndication_after_event_pull_success', array( $this, 'handle_pull_success_event' ), 10, 2 );
}

/**
* Handle a site pull failure event
*
* @param $site_id int The post id of the site we need to retry
* @param $failed_attempts int The number of pull failures this site has experienced
*
* @return null
*/
public function handle_pull_failure_event( $site_id = 0, $failed_attempts = 0 ) {

$site_auto_retry_count = 0;
$site_id = (int) $site_id;
$failed_attempts = (int) $failed_attempts;
$cleanup = false;

// Fetch the allowable number of max pull attempts before the site is marked as 'disabled'
$max_pull_attempts = (int) get_option( 'push_syndication_max_pull_attempts', 0 );

// Bail if we've already met the max pull attempt count
if ( ! $max_pull_attempts ) {
return;
}

// Only proceed if we have a valid site id
if ( 0 !== $site_id ) {

// Fetch the site post
$site = get_post( $site_id );

// Fetch the site url
$site_url = get_post_meta( $site->ID, 'syn_feed_url', true );

// Fetch the number of times we've tried to auto-retry
$site_auto_retry_count = (int) get_post_meta( $site_id, 'syn_failed_auto_retry_attempts', true );

// Only proceed if we haven't hit the pull attempt ceiling
if ( $failed_attempts < $max_pull_attempts ) {

// Allow the default auto retry to be filtered
// By default, only auto retry 3 times
$auto_retry_limit = apply_filters( 'pull_syndication_failure_auto_retry_limit', 3 );

// Store the current time for repeated use below
$time_now = time();

// Create a string time to be sent to the logger
// Add 1 so our log items appear to occur a second later
// and hence order better in the log viewer
// without this, sometimes when the pull occurs quickly
// these log items appear to occur at the same time as the failure
$log_time = date( 'Y-m-d H:i:s', $time_now + 1 );

// Are we still below the auto retry limit?
if ( $site_auto_retry_count < $auto_retry_limit ) {

// Yes we are..

// Run in one minute by default
$auto_retry_interval = apply_filters( 'syndication_failure_auto_retry_interval', $time_now + MINUTE_IN_SECONDS );

Syndication_Logger::log_post_info( $site->ID, $status = 'start_auto_retry', $message = sprintf( __( 'Connection retry %d of %d to %s in %s..', 'push-syndication' ), $site_auto_retry_count + 1, $auto_retry_limit, $site_url, human_time_diff( $time_now, $auto_retry_interval ) ), $log_time, $extra = array() );

// Schedule a pull retry for one minute in the future
wp_schedule_single_event(
$auto_retry_interval, // retry in X time
'syn_pull_content', // fire the syndication_auto_retry hook
array( array( $site ) ) // the site which failed to pull
);

// Increment our auto retry counter
$site_auto_retry_count++;

// And update the post meta auto retry count
update_post_meta( $site->ID, 'syn_failed_auto_retry_attempts', $site_auto_retry_count );

} else {

// Auto Retry limit met
// Let's cleanup after ourselves
$cleanup = true ;
}
} else {

// Retry attempt limit met
// The site has been disabled, let's cleanup after ourselves
$cleanup = true;
}

// Should we cleanup after ourselves?
if ( $cleanup ) {

// Remove the auto retry if there was one
delete_post_meta( $site->ID, 'syn_failed_auto_retry_attempts' );

Syndication_Logger::log_post_error( $site->ID, $status = 'end_auto_retry', $message = sprintf( __( 'Failed %d times to reconnect to %s', 'push-syndication' ), $site_auto_retry_count, $site_url ), $log_time, $extra = array() );
}
}
}

/**
* Handle a site pull success event
*
* @param $site_id int The post id of the site which just successfully pulled
* @param $failed_attempts int The number of pull failures this site has experienced
* @return null
*/
public function handle_pull_success_event( $site_id = 0, $failed_attempts = 0 ) {

// Remove the auto retry if there was one
delete_post_meta( $site_id, 'syn_failed_auto_retry_attempts' );
}
}
4 changes: 3 additions & 1 deletion includes/class-syndication-site-failure-monitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ public function handle_pull_failure_event( $site_id, $count ) {
do_action( 'push_syndication_reset_event', 'pull_failure', $site_id );

// Log what happened.
Syndication_Logger::log_post_error( $site_id, 'error', sprintf( __( 'Site disabled after %d pull failure(s).', 'push-syndication' ), $count ) );
Syndication_Logger::log_post_error( $site_id, 'error', sprintf( __( 'Site %d disabled after %d pull failure(s).', 'push-syndication' ), (int) $site_id, (int) $count ) );

do_action( 'push_syndication_site_disabled', $site_id, $count );
}
}
}
Loading

0 comments on commit 7149bc9

Please sign in to comment.