as3cf = $as3cf;
$this->cron_hook = 'as3cf_cron_update_' . $this->upgrade_name;
$this->cron_schedule_key = 'as3cf_update_' . $this->upgrade_name . '_interval';
$this->cron_interval_in_minutes = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_interval', 5 );
$this->error_threshold = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_error_threshold', 20 );
add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) );
add_action( $this->cron_hook, array( $this, 'do_upgrade' ) );
add_action( 'as3cf_pre_settings_render', array( $this, 'maybe_display_notices' ) );
add_action( 'admin_init', array( $this, 'maybe_handle_action' ) );
// Do default checks if the upgrade can be started
if ( $this->maybe_init() ) {
$this->init();
}
}
/**
* Can we start the upgrade using default checks
*
* @return bool
*/
protected function maybe_init() {
if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
return false;
}
// make sure this only fires inside the network admin for multisites
if ( is_multisite() && ! is_network_admin() ) {
return false;
}
// If the upgrade status is already set, then we've already initialized the upgrade
if ( $this->get_upgrade_status() ) {
return false;
}
// Have we completed the upgrade?
if ( $this->as3cf->get_setting( $this->settings_key, 0 ) >= $this->upgrade_id ) {
return false;
}
// Has the previous upgrade completed yet?
$previous_id = $this->upgrade_id - 1;
if ( 0 !== $previous_id && (int) $this->as3cf->get_setting( $this->settings_key, 0 ) < $previous_id ) {
// Previous still running, abort
return false;
}
// Do we actually attachments to process?
if ( 0 === $this->count_attachments_to_process() ) {
$this->upgrade_finished();
return false;
}
return true;
}
/**
* @return int
*/
abstract protected function count_attachments_to_process();
/**
* @param $prefix
* @param $limit
*
* @return array
*/
abstract protected function get_attachments_to_process( $prefix, $limit );
/**
* @param $attachment
*
* @return bool
*/
abstract protected function upgrade_attachment( $attachment );
/**
* Fire up the upgrade
*/
protected function init() {
// Initialize the upgrade
$this->save_session( array( 'status' => self::STATUS_RUNNING ) );
$this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key );
}
/**
* Cron jon to update the region of the bucket in s3 metadata
*/
function do_upgrade() {
// Check if the cron should even be running
if ( $this->as3cf->get_setting( $this->settings_key, 0 ) >= $this->upgrade_id || $this->get_upgrade_status() !== self::STATUS_RUNNING ) {
$this->as3cf->clear_scheduled_event( $this->cron_hook );
return;
}
// set the batch size limit for the query
$limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', 500 );
$all_limit = $limit;
// only process the loop for a certain amount of time
$minutes = $this->cron_interval_in_minutes * 60;
// smaller time limit so won't run into another instance of cron
$minutes = $minutes * 0.8;
$finish = time() + $minutes;
$session = $this->get_session();
// find the blog IDs that have been processed so we can skip them
$processed_blog_ids = isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array();
$this->error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0;
// get the table prefixes for all the blogs
$table_prefixes = $this->as3cf->get_all_blog_table_prefixes( $processed_blog_ids );
$all_attachments = array();
$all_count = 0;
foreach ( $table_prefixes as $blog_id => $table_prefix ) {
$attachments = $this->get_attachments_to_process( $table_prefix, $limit );
$count = count( $attachments );
if ( 0 === $count ) {
// no more attachments, record the blog ID to skip next time
$processed_blog_ids[] = $blog_id;
} else {
$all_count += $count;
$all_attachments[ $blog_id ] = $attachments;
}
if ( $all_count >= $all_limit ) {
break;
}
$limit = $limit - $count;
}
if ( 0 === $all_count ) {
$this->upgrade_finished();
return;
}
// loop through and update s3 meta with region
foreach ( $all_attachments as $blog_id => $attachments ) {
$this->as3cf->switch_to_blog( $blog_id );
foreach ( $attachments as $attachment ) {
if ( $this->error_count >= $this->error_threshold ) {
$this->upgrade_error( $session );
return;
}
// Do the actual upgrade to the attachment
$this->upgrade_attachment( $attachment );
if ( time() >= $finish || $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) {
// Batch limits reached
break 2;
}
}
}
$this->as3cf->restore_current_blog( $blog_id );
$session['processed_blog_ids'] = $processed_blog_ids;
$session['error_count'] = $this->error_count;
$this->save_session( $session );
}
/**
* Adds notices about issues with upgrades allowing user to restart them
*/
function maybe_display_notices() {
$action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'restart_update', 'update' => $this->upgrade_name ), 'self' );
$msg_type = 'notice-info';
switch ( $this->get_upgrade_status() ) {
case self::STATUS_RUNNING:
$msg = sprintf( __( 'Running %s Update — We’re going through all the Media Library items uploaded to S3 %s This will be done quietly in the background, processing a small batch of Media Library items every %d minutes. There should be no noticeable impact on your server’s performance.', 'amazon-s3-and-cloudfront' ), ucfirst( $this->upgrade_type ), $this->running_update_text, $this->cron_interval_in_minutes );
$action_text = __( 'Pause Update', 'amazon-s3-and-cloudfront' );
$action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'pause_update', 'update' => $this->upgrade_name ), 'self' );
break;
case self::STATUS_PAUSED:
$msg = sprintf( __( '%s Update Paused — Updating Media Library %s has been paused.', 'amazon-s3-and-cloudfront' ), ucfirst( $this->upgrade_type ), $this->upgrade_type );
$action_text = __( 'Restart Update', 'amazon-s3-and-cloudfront' );
break;
case self::STATUS_ERROR:
$msg = sprintf( __( 'Error Updating %s — We ran into some errors attempting to update the %s for all your Media Library items that have been uploaded to S3. Please check your error log for details. (#%d)', 'amazon-s3-and-cloudfront' ), ucfirst( $this->upgrade_type ), $this->upgrade_type, $this->upgrade_id );
$action_text = __( 'Try Run It Again', 'amazon-s3-and-cloudfront' );
$msg_type = 'error';
break;
default:
return;
}
$msg .= ' ' . $action_text . '';
$args = array(
'message' => $msg,
'type' => $msg_type,
);
$this->as3cf->render_view( 'notice', $args );
}
/**
* Handler for the running upgrade actions
*/
function maybe_handle_action() {
if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== $this->as3cf->get_plugin_slug() || ! isset( $_GET['action'] ) ) { // input var okay
return;
}
$method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay
if ( method_exists( $this, $method_name ) ) {
call_user_func( array( $this, $method_name ) );
}
}
/**
* Exit upgrade with an error
*
* @param array $session
*/
function upgrade_error( $session ) {
$session['status'] = self::STATUS_ERROR;
$this->save_session( $session );
$this->as3cf->clear_scheduled_event( $this->cron_hook );
}
/**
* Complete the upgrade
*/
function upgrade_finished() {
$this->clear_session();
$this->as3cf->set_setting( $this->settings_key, $this->upgrade_id );
$this->as3cf->save_settings();
$this->as3cf->clear_scheduled_event( $this->cron_hook );
}
/**
* Restart upgrade
*/
function action_restart_update() {
if ( ! isset( $_GET['update'] ) || $this->upgrade_name !== sanitize_key( $_GET['update'] ) ) {
return;
}
$this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key );
$this->change_status_request( self::STATUS_RUNNING );
}
/**
* Pause upgrade
*/
function action_pause_update() {
if ( ! isset( $_GET['update'] ) || $this->upgrade_name !== sanitize_key( $_GET['update'] ) ) {
return;
}
$this->as3cf->clear_scheduled_event( $this->cron_hook );
$this->change_status_request( self::STATUS_PAUSED );
}
/**
* Helper for the above action requests
*
* @param int $status
*/
function change_status_request( $status ) {
$session = $this->get_session();
$session['status'] = $status;
$this->save_session( $session );
$url = $this->as3cf->get_plugin_page_url( array(), 'self' );
wp_redirect( $url );
exit;
}
/**
* Add custom cron interval schedules
*
* @param array $schedules
*
* @return array
*/
function cron_schedules( $schedules ) {
// Add the upgrade interval to the existing schedules.
$schedules[ $this->cron_schedule_key ] = array(
'interval' => $this->cron_interval_in_minutes * 60,
'display' => sprintf( __( 'Every %d Minutes', 'amazon-s3-and-cloudfront' ), $this->cron_interval_in_minutes ),
);
return $schedules;
}
/**
* Get the current status of the upgrade
* See STATUS_* constants in the class declaration above.
*/
function get_upgrade_status() {
$session = $this->get_session();
if ( ! isset( $session['status'] ) ) {
return '';
}
return $session['status'];
}
/**
* Retrieve session data from plugin settings
*
* @return array
*/
function get_session() {
return get_site_option( 'update_' . $this->upgrade_name . '_session', array() );
}
/**
* Store data to be used between requests in plugin settings
*
* @param array $session session data to store
*/
function save_session( $session ) {
update_site_option( 'update_' . $this->upgrade_name . '_session', $session );
}
/**
* Remove the session data to be used between requests
*
*/
function clear_session() {
delete_site_option( 'update_' . $this->upgrade_name . '_session' );
}
}