Passed
Pull Request — master (#284)
by Brian
04:40
created
libraries/action-scheduler/classes/ActionScheduler_DataController.php 1 patch
Indentation   +170 added lines, -170 removed lines patch added patch discarded remove patch
@@ -14,174 +14,174 @@
 block discarded – undo
14 14
  * @since 3.0.0
15 15
  */
16 16
 class ActionScheduler_DataController {
17
-	/** Action data store class name. */
18
-	const DATASTORE_CLASS = 'ActionScheduler_DBStore';
19
-
20
-	/** Logger data store class name. */
21
-	const LOGGER_CLASS    = 'ActionScheduler_DBLogger';
22
-
23
-	/** Migration status option name. */
24
-	const STATUS_FLAG     = 'action_scheduler_migration_status';
25
-
26
-	/** Migration status option value. */
27
-	const STATUS_COMPLETE = 'complete';
28
-
29
-	/** Migration minimum required PHP version. */
30
-	const MIN_PHP_VERSION = '5.5';
31
-
32
-	/** @var ActionScheduler_DataController */
33
-	private static $instance;
34
-
35
-	/** @var int */
36
-	private static $sleep_time = 0;
37
-
38
-	/** @var int */
39
-	private static $free_ticks = 50;
40
-
41
-	/**
42
-	 * Get a flag indicating whether the migration environment dependencies are met.
43
-	 *
44
-	 * @return bool
45
-	 */
46
-	public static function dependencies_met() {
47
-		$php_support = version_compare( PHP_VERSION, self::MIN_PHP_VERSION, '>=' );
48
-		return $php_support && apply_filters( 'action_scheduler_migration_dependencies_met', true );
49
-	}
50
-
51
-	/**
52
-	 * Get a flag indicating whether the migration is complete.
53
-	 *
54
-	 * @return bool Whether the flag has been set marking the migration as complete
55
-	 */
56
-	public static function is_migration_complete() {
57
-		return get_option( self::STATUS_FLAG ) === self::STATUS_COMPLETE;
58
-	}
59
-
60
-	/**
61
-	 * Mark the migration as complete.
62
-	 */
63
-	public static function mark_migration_complete() {
64
-		update_option( self::STATUS_FLAG, self::STATUS_COMPLETE );
65
-	}
66
-
67
-	/**
68
-	 * Unmark migration when a plugin is de-activated. Will not work in case of silent activation, for example in an update.
69
-	 * We do this to mitigate the bug of lost actions which happens if there was an AS 2.x to AS 3.x migration in the past, but that plugin is now
70
-	 * deactivated and the site was running on AS 2.x again.
71
-	 */
72
-	public static function mark_migration_incomplete() {
73
-		delete_option( self::STATUS_FLAG );
74
-	}
75
-
76
-	/**
77
-	 * Set the action store class name.
78
-	 *
79
-	 * @param string $class Classname of the store class.
80
-	 *
81
-	 * @return string
82
-	 */
83
-	public static function set_store_class( $class ) {
84
-		return self::DATASTORE_CLASS;
85
-	}
86
-
87
-	/**
88
-	 * Set the action logger class name.
89
-	 *
90
-	 * @param string $class Classname of the logger class.
91
-	 *
92
-	 * @return string
93
-	 */
94
-	public static function set_logger_class( $class ) {
95
-		return self::LOGGER_CLASS;
96
-	}
97
-
98
-	/**
99
-	 * Set the sleep time in seconds.
100
-	 *
101
-	 * @param integer $sleep_time The number of seconds to pause before resuming operation.
102
-	 */
103
-	public static function set_sleep_time( $sleep_time ) {
104
-		self::$sleep_time = $sleep_time;
105
-	}
106
-
107
-	/**
108
-	 * Set the tick count required for freeing memory.
109
-	 *
110
-	 * @param integer $free_ticks The number of ticks to free memory on.
111
-	 */
112
-	public static function set_free_ticks( $free_ticks ) {
113
-		self::$free_ticks = $free_ticks;
114
-	}
115
-
116
-	/**
117
-	 * Free memory if conditions are met.
118
-	 *
119
-	 * @param int $ticks Current tick count.
120
-	 */
121
-	public static function maybe_free_memory( $ticks ) {
122
-		if ( self::$free_ticks && 0 === $ticks % self::$free_ticks ) {
123
-			self::free_memory();
124
-		}
125
-	}
126
-
127
-	/**
128
-	 * Reduce memory footprint by clearing the database query and object caches.
129
-	 */
130
-	public static function free_memory() {
131
-		if ( 0 < self::$sleep_time ) {
132
-			/* translators: %d: amount of time */
133
-			\WP_CLI::warning( sprintf( _n( 'Stopped the insanity for %d second', 'Stopped the insanity for %d seconds', self::$sleep_time, 'action-scheduler' ), self::$sleep_time ) );
134
-			sleep( self::$sleep_time );
135
-		}
136
-
137
-		\WP_CLI::warning( __( 'Attempting to reduce used memory...', 'action-scheduler' ) );
138
-
139
-		/**
140
-		 * @var $wpdb            \wpdb
141
-		 * @var $wp_object_cache \WP_Object_Cache
142
-		 */
143
-		global $wpdb, $wp_object_cache;
144
-
145
-		$wpdb->queries = array();
146
-
147
-		if ( ! is_a( $wp_object_cache, 'WP_Object_Cache' ) ) {
148
-			return;
149
-		}
150
-
151
-		$wp_object_cache->group_ops      = array();
152
-		$wp_object_cache->stats          = array();
153
-		$wp_object_cache->memcache_debug = array();
154
-		$wp_object_cache->cache          = array();
155
-
156
-		if ( is_callable( array( $wp_object_cache, '__remoteset' ) ) ) {
157
-			call_user_func( array( $wp_object_cache, '__remoteset' ) ); // important
158
-		}
159
-	}
160
-
161
-	/**
162
-	 * Connect to table datastores if migration is complete.
163
-	 * Otherwise, proceed with the migration if the dependencies have been met.
164
-	 */
165
-	public static function init() {
166
-		if ( self::is_migration_complete() ) {
167
-			add_filter( 'action_scheduler_store_class', array( 'ActionScheduler_DataController', 'set_store_class' ), 100 );
168
-			add_filter( 'action_scheduler_logger_class', array( 'ActionScheduler_DataController', 'set_logger_class' ), 100 );
169
-			add_action( 'deactivate_plugin', array( 'ActionScheduler_DataController', 'mark_migration_incomplete' ) );
170
-		} elseif ( self::dependencies_met() ) {
171
-			Controller::init();
172
-		}
173
-
174
-		add_action( 'action_scheduler/progress_tick', array( 'ActionScheduler_DataController', 'maybe_free_memory' ) );
175
-	}
176
-
177
-	/**
178
-	 * Singleton factory.
179
-	 */
180
-	public static function instance() {
181
-		if ( ! isset( self::$instance ) ) {
182
-			self::$instance = new static();
183
-		}
184
-
185
-		return self::$instance;
186
-	}
17
+    /** Action data store class name. */
18
+    const DATASTORE_CLASS = 'ActionScheduler_DBStore';
19
+
20
+    /** Logger data store class name. */
21
+    const LOGGER_CLASS    = 'ActionScheduler_DBLogger';
22
+
23
+    /** Migration status option name. */
24
+    const STATUS_FLAG     = 'action_scheduler_migration_status';
25
+
26
+    /** Migration status option value. */
27
+    const STATUS_COMPLETE = 'complete';
28
+
29
+    /** Migration minimum required PHP version. */
30
+    const MIN_PHP_VERSION = '5.5';
31
+
32
+    /** @var ActionScheduler_DataController */
33
+    private static $instance;
34
+
35
+    /** @var int */
36
+    private static $sleep_time = 0;
37
+
38
+    /** @var int */
39
+    private static $free_ticks = 50;
40
+
41
+    /**
42
+     * Get a flag indicating whether the migration environment dependencies are met.
43
+     *
44
+     * @return bool
45
+     */
46
+    public static function dependencies_met() {
47
+        $php_support = version_compare( PHP_VERSION, self::MIN_PHP_VERSION, '>=' );
48
+        return $php_support && apply_filters( 'action_scheduler_migration_dependencies_met', true );
49
+    }
50
+
51
+    /**
52
+     * Get a flag indicating whether the migration is complete.
53
+     *
54
+     * @return bool Whether the flag has been set marking the migration as complete
55
+     */
56
+    public static function is_migration_complete() {
57
+        return get_option( self::STATUS_FLAG ) === self::STATUS_COMPLETE;
58
+    }
59
+
60
+    /**
61
+     * Mark the migration as complete.
62
+     */
63
+    public static function mark_migration_complete() {
64
+        update_option( self::STATUS_FLAG, self::STATUS_COMPLETE );
65
+    }
66
+
67
+    /**
68
+     * Unmark migration when a plugin is de-activated. Will not work in case of silent activation, for example in an update.
69
+     * We do this to mitigate the bug of lost actions which happens if there was an AS 2.x to AS 3.x migration in the past, but that plugin is now
70
+     * deactivated and the site was running on AS 2.x again.
71
+     */
72
+    public static function mark_migration_incomplete() {
73
+        delete_option( self::STATUS_FLAG );
74
+    }
75
+
76
+    /**
77
+     * Set the action store class name.
78
+     *
79
+     * @param string $class Classname of the store class.
80
+     *
81
+     * @return string
82
+     */
83
+    public static function set_store_class( $class ) {
84
+        return self::DATASTORE_CLASS;
85
+    }
86
+
87
+    /**
88
+     * Set the action logger class name.
89
+     *
90
+     * @param string $class Classname of the logger class.
91
+     *
92
+     * @return string
93
+     */
94
+    public static function set_logger_class( $class ) {
95
+        return self::LOGGER_CLASS;
96
+    }
97
+
98
+    /**
99
+     * Set the sleep time in seconds.
100
+     *
101
+     * @param integer $sleep_time The number of seconds to pause before resuming operation.
102
+     */
103
+    public static function set_sleep_time( $sleep_time ) {
104
+        self::$sleep_time = $sleep_time;
105
+    }
106
+
107
+    /**
108
+     * Set the tick count required for freeing memory.
109
+     *
110
+     * @param integer $free_ticks The number of ticks to free memory on.
111
+     */
112
+    public static function set_free_ticks( $free_ticks ) {
113
+        self::$free_ticks = $free_ticks;
114
+    }
115
+
116
+    /**
117
+     * Free memory if conditions are met.
118
+     *
119
+     * @param int $ticks Current tick count.
120
+     */
121
+    public static function maybe_free_memory( $ticks ) {
122
+        if ( self::$free_ticks && 0 === $ticks % self::$free_ticks ) {
123
+            self::free_memory();
124
+        }
125
+    }
126
+
127
+    /**
128
+     * Reduce memory footprint by clearing the database query and object caches.
129
+     */
130
+    public static function free_memory() {
131
+        if ( 0 < self::$sleep_time ) {
132
+            /* translators: %d: amount of time */
133
+            \WP_CLI::warning( sprintf( _n( 'Stopped the insanity for %d second', 'Stopped the insanity for %d seconds', self::$sleep_time, 'action-scheduler' ), self::$sleep_time ) );
134
+            sleep( self::$sleep_time );
135
+        }
136
+
137
+        \WP_CLI::warning( __( 'Attempting to reduce used memory...', 'action-scheduler' ) );
138
+
139
+        /**
140
+         * @var $wpdb            \wpdb
141
+         * @var $wp_object_cache \WP_Object_Cache
142
+         */
143
+        global $wpdb, $wp_object_cache;
144
+
145
+        $wpdb->queries = array();
146
+
147
+        if ( ! is_a( $wp_object_cache, 'WP_Object_Cache' ) ) {
148
+            return;
149
+        }
150
+
151
+        $wp_object_cache->group_ops      = array();
152
+        $wp_object_cache->stats          = array();
153
+        $wp_object_cache->memcache_debug = array();
154
+        $wp_object_cache->cache          = array();
155
+
156
+        if ( is_callable( array( $wp_object_cache, '__remoteset' ) ) ) {
157
+            call_user_func( array( $wp_object_cache, '__remoteset' ) ); // important
158
+        }
159
+    }
160
+
161
+    /**
162
+     * Connect to table datastores if migration is complete.
163
+     * Otherwise, proceed with the migration if the dependencies have been met.
164
+     */
165
+    public static function init() {
166
+        if ( self::is_migration_complete() ) {
167
+            add_filter( 'action_scheduler_store_class', array( 'ActionScheduler_DataController', 'set_store_class' ), 100 );
168
+            add_filter( 'action_scheduler_logger_class', array( 'ActionScheduler_DataController', 'set_logger_class' ), 100 );
169
+            add_action( 'deactivate_plugin', array( 'ActionScheduler_DataController', 'mark_migration_incomplete' ) );
170
+        } elseif ( self::dependencies_met() ) {
171
+            Controller::init();
172
+        }
173
+
174
+        add_action( 'action_scheduler/progress_tick', array( 'ActionScheduler_DataController', 'maybe_free_memory' ) );
175
+    }
176
+
177
+    /**
178
+     * Singleton factory.
179
+     */
180
+    public static function instance() {
181
+        if ( ! isset( self::$instance ) ) {
182
+            self::$instance = new static();
183
+        }
184
+
185
+        return self::$instance;
186
+    }
187 187
 }
Please login to merge, or discard this patch.
action-scheduler/classes/ActionScheduler_InvalidActionException.php 1 patch
Indentation   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -9,39 +9,39 @@
 block discarded – undo
9 9
  */
10 10
 class ActionScheduler_InvalidActionException extends \InvalidArgumentException implements ActionScheduler_Exception {
11 11
 
12
-	/**
13
-	 * Create a new exception when the action's schedule cannot be fetched.
14
-	 *
15
-	 * @param string $action_id The action ID with bad args.
16
-	 * @return static
17
-	 */
18
-	public static function from_schedule( $action_id, $schedule ) {
19
-		$message = sprintf(
20
-			/* translators: 1: action ID 2: schedule */
21
-			__( 'Action [%1$s] has an invalid schedule: %2$s', 'action-scheduler' ),
22
-			$action_id,
23
-			var_export( $schedule, true )
24
-		);
12
+    /**
13
+     * Create a new exception when the action's schedule cannot be fetched.
14
+     *
15
+     * @param string $action_id The action ID with bad args.
16
+     * @return static
17
+     */
18
+    public static function from_schedule( $action_id, $schedule ) {
19
+        $message = sprintf(
20
+            /* translators: 1: action ID 2: schedule */
21
+            __( 'Action [%1$s] has an invalid schedule: %2$s', 'action-scheduler' ),
22
+            $action_id,
23
+            var_export( $schedule, true )
24
+        );
25 25
 
26
-		return new static( $message );
27
-	}
26
+        return new static( $message );
27
+    }
28 28
 
29
-	/**
30
-	 * Create a new exception when the action's args cannot be decoded to an array.
31
-	 *
32
-	 * @author Jeremy Pry
33
-	 *
34
-	 * @param string $action_id The action ID with bad args.
35
-	 * @return static
36
-	 */
37
-	public static function from_decoding_args( $action_id, $args = array() ) {
38
-		$message = sprintf(
39
-			/* translators: 1: action ID 2: arguments */
40
-			__( 'Action [%1$s] has invalid arguments. It cannot be JSON decoded to an array. $args = %2$s', 'action-scheduler' ),
41
-			$action_id,
42
-			var_export( $args, true )
43
-		);
29
+    /**
30
+     * Create a new exception when the action's args cannot be decoded to an array.
31
+     *
32
+     * @author Jeremy Pry
33
+     *
34
+     * @param string $action_id The action ID with bad args.
35
+     * @return static
36
+     */
37
+    public static function from_decoding_args( $action_id, $args = array() ) {
38
+        $message = sprintf(
39
+            /* translators: 1: action ID 2: arguments */
40
+            __( 'Action [%1$s] has invalid arguments. It cannot be JSON decoded to an array. $args = %2$s', 'action-scheduler' ),
41
+            $action_id,
42
+            var_export( $args, true )
43
+        );
44 44
 
45
-		return new static( $message );
46
-	}
45
+        return new static( $message );
46
+    }
47 47
 }
Please login to merge, or discard this patch.
includes/libraries/action-scheduler/classes/ActionScheduler_Versions.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -4,59 +4,59 @@
 block discarded – undo
4 4
  * Class ActionScheduler_Versions
5 5
  */
6 6
 class ActionScheduler_Versions {
7
-	/**
8
-	 * @var ActionScheduler_Versions
9
-	 */
10
-	private static $instance = NULL;
11
-
12
-	private $versions = array();
13
-
14
-	public function register( $version_string, $initialization_callback ) {
15
-		if ( isset($this->versions[$version_string]) ) {
16
-			return FALSE;
17
-		}
18
-		$this->versions[$version_string] = $initialization_callback;
19
-		return TRUE;
20
-	}
21
-
22
-	public function get_versions() {
23
-		return $this->versions;
24
-	}
25
-
26
-	public function latest_version() {
27
-		$keys = array_keys($this->versions);
28
-		if ( empty($keys) ) {
29
-			return false;
30
-		}
31
-		uasort( $keys, 'version_compare' );
32
-		return end($keys);
33
-	}
34
-
35
-	public function latest_version_callback() {
36
-		$latest = $this->latest_version();
37
-		if ( empty($latest) || !isset($this->versions[$latest]) ) {
38
-			return '__return_null';
39
-		}
40
-		return $this->versions[$latest];
41
-	}
42
-
43
-	/**
44
-	 * @return ActionScheduler_Versions
45
-	 * @codeCoverageIgnore
46
-	 */
47
-	public static function instance() {
48
-		if ( empty(self::$instance) ) {
49
-			self::$instance = new self();
50
-		}
51
-		return self::$instance;
52
-	}
53
-
54
-	/**
55
-	 * @codeCoverageIgnore
56
-	 */
57
-	public static function initialize_latest_version() {
58
-		$self = self::instance();
59
-		call_user_func($self->latest_version_callback());
60
-	}
7
+    /**
8
+     * @var ActionScheduler_Versions
9
+     */
10
+    private static $instance = NULL;
11
+
12
+    private $versions = array();
13
+
14
+    public function register( $version_string, $initialization_callback ) {
15
+        if ( isset($this->versions[$version_string]) ) {
16
+            return FALSE;
17
+        }
18
+        $this->versions[$version_string] = $initialization_callback;
19
+        return TRUE;
20
+    }
21
+
22
+    public function get_versions() {
23
+        return $this->versions;
24
+    }
25
+
26
+    public function latest_version() {
27
+        $keys = array_keys($this->versions);
28
+        if ( empty($keys) ) {
29
+            return false;
30
+        }
31
+        uasort( $keys, 'version_compare' );
32
+        return end($keys);
33
+    }
34
+
35
+    public function latest_version_callback() {
36
+        $latest = $this->latest_version();
37
+        if ( empty($latest) || !isset($this->versions[$latest]) ) {
38
+            return '__return_null';
39
+        }
40
+        return $this->versions[$latest];
41
+    }
42
+
43
+    /**
44
+     * @return ActionScheduler_Versions
45
+     * @codeCoverageIgnore
46
+     */
47
+    public static function instance() {
48
+        if ( empty(self::$instance) ) {
49
+            self::$instance = new self();
50
+        }
51
+        return self::$instance;
52
+    }
53
+
54
+    /**
55
+     * @codeCoverageIgnore
56
+     */
57
+    public static function initialize_latest_version() {
58
+        $self = self::instance();
59
+        call_user_func($self->latest_version_callback());
60
+    }
61 61
 }
62
- 
63 62
\ No newline at end of file
63
+    
64 64
\ No newline at end of file
Please login to merge, or discard this patch.
action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php 1 patch
Indentation   +232 added lines, -232 removed lines patch added patch discarded remove patch
@@ -5,236 +5,236 @@
 block discarded – undo
5 5
  */
6 6
 abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abstract_QueueRunner_Deprecated {
7 7
 
8
-	/** @var ActionScheduler_QueueCleaner */
9
-	protected $cleaner;
10
-
11
-	/** @var ActionScheduler_FatalErrorMonitor */
12
-	protected $monitor;
13
-
14
-	/** @var ActionScheduler_Store */
15
-	protected $store;
16
-
17
-	/**
18
-	 * The created time.
19
-	 *
20
-	 * Represents when the queue runner was constructed and used when calculating how long a PHP request has been running.
21
-	 * For this reason it should be as close as possible to the PHP request start time.
22
-	 *
23
-	 * @var int
24
-	 */
25
-	private $created_time;
26
-
27
-	/**
28
-	 * ActionScheduler_Abstract_QueueRunner constructor.
29
-	 *
30
-	 * @param ActionScheduler_Store             $store
31
-	 * @param ActionScheduler_FatalErrorMonitor $monitor
32
-	 * @param ActionScheduler_QueueCleaner      $cleaner
33
-	 */
34
-	public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null ) {
35
-
36
-		$this->created_time = microtime( true );
37
-
38
-		$this->store   = $store ? $store : ActionScheduler_Store::instance();
39
-		$this->monitor = $monitor ? $monitor : new ActionScheduler_FatalErrorMonitor( $this->store );
40
-		$this->cleaner = $cleaner ? $cleaner : new ActionScheduler_QueueCleaner( $this->store );
41
-	}
42
-
43
-	/**
44
-	 * Process an individual action.
45
-	 *
46
-	 * @param int $action_id The action ID to process.
47
-	 * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
48
-	 *        Generally, this should be capitalised and not localised as it's a proper noun.
49
-	 */
50
-	public function process_action( $action_id, $context = '' ) {
51
-		try {
52
-			$valid_action = false;
53
-			do_action( 'action_scheduler_before_execute', $action_id, $context );
54
-
55
-			if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
56
-				do_action( 'action_scheduler_execution_ignored', $action_id, $context );
57
-				return;
58
-			}
59
-
60
-			$valid_action = true;
61
-			do_action( 'action_scheduler_begin_execute', $action_id, $context );
62
-
63
-			$action = $this->store->fetch_action( $action_id );
64
-			$this->store->log_execution( $action_id );
65
-			$action->execute();
66
-			do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
67
-			$this->store->mark_complete( $action_id );
68
-		} catch ( Exception $e ) {
69
-			if ( $valid_action ) {
70
-				$this->store->mark_failure( $action_id );
71
-				do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
72
-			} else {
73
-				do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
74
-			}
75
-		}
76
-
77
-		if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) {
78
-			$this->schedule_next_instance( $action, $action_id );
79
-		}
80
-	}
81
-
82
-	/**
83
-	 * Schedule the next instance of the action if necessary.
84
-	 *
85
-	 * @param ActionScheduler_Action $action
86
-	 * @param int $action_id
87
-	 */
88
-	protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) {
89
-		try {
90
-			ActionScheduler::factory()->repeat( $action );
91
-		} catch ( Exception $e ) {
92
-			do_action( 'action_scheduler_failed_to_schedule_next_instance', $action_id, $e, $action );
93
-		}
94
-	}
95
-
96
-	/**
97
-	 * Run the queue cleaner.
98
-	 *
99
-	 * @author Jeremy Pry
100
-	 */
101
-	protected function run_cleanup() {
102
-		$this->cleaner->clean( 10 * $this->get_time_limit() );
103
-	}
104
-
105
-	/**
106
-	 * Get the number of concurrent batches a runner allows.
107
-	 *
108
-	 * @return int
109
-	 */
110
-	public function get_allowed_concurrent_batches() {
111
-		return apply_filters( 'action_scheduler_queue_runner_concurrent_batches', 1 );
112
-	}
113
-
114
-	/**
115
-	 * Check if the number of allowed concurrent batches is met or exceeded.
116
-	 *
117
-	 * @return bool
118
-	 */
119
-	public function has_maximum_concurrent_batches() {
120
-		return $this->store->get_claim_count() >= $this->get_allowed_concurrent_batches();
121
-	}
122
-
123
-	/**
124
-	 * Get the maximum number of seconds a batch can run for.
125
-	 *
126
-	 * @return int The number of seconds.
127
-	 */
128
-	protected function get_time_limit() {
129
-
130
-		$time_limit = 30;
131
-
132
-		// Apply deprecated filter from deprecated get_maximum_execution_time() method
133
-		if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) {
134
-			_deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' );
135
-			$time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit );
136
-		}
137
-
138
-		return absint( apply_filters( 'action_scheduler_queue_runner_time_limit', $time_limit ) );
139
-	}
140
-
141
-	/**
142
-	 * Get the number of seconds the process has been running.
143
-	 *
144
-	 * @return int The number of seconds.
145
-	 */
146
-	protected function get_execution_time() {
147
-		$execution_time = microtime( true ) - $this->created_time;
148
-
149
-		// Get the CPU time if the hosting environment uses it rather than wall-clock time to calculate a process's execution time.
150
-		if ( function_exists( 'getrusage' ) && apply_filters( 'action_scheduler_use_cpu_execution_time', defined( 'PANTHEON_ENVIRONMENT' ) ) ) {
151
-			$resource_usages = getrusage();
152
-
153
-			if ( isset( $resource_usages['ru_stime.tv_usec'], $resource_usages['ru_stime.tv_usec'] ) ) {
154
-				$execution_time = $resource_usages['ru_stime.tv_sec'] + ( $resource_usages['ru_stime.tv_usec'] / 1000000 );
155
-			}
156
-		}
157
-
158
-		return $execution_time;
159
-	}
160
-
161
-	/**
162
-	 * Check if the host's max execution time is (likely) to be exceeded if processing more actions.
163
-	 *
164
-	 * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action
165
-	 * @return bool
166
-	 */
167
-	protected function time_likely_to_be_exceeded( $processed_actions ) {
168
-
169
-		$execution_time        = $this->get_execution_time();
170
-		$max_execution_time    = $this->get_time_limit();
171
-		$time_per_action       = $execution_time / $processed_actions;
172
-		$estimated_time        = $execution_time + ( $time_per_action * 3 );
173
-		$likely_to_be_exceeded = $estimated_time > $max_execution_time;
174
-
175
-		return apply_filters( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', $likely_to_be_exceeded, $this, $processed_actions, $execution_time, $max_execution_time );
176
-	}
177
-
178
-	/**
179
-	 * Get memory limit
180
-	 *
181
-	 * Based on WP_Background_Process::get_memory_limit()
182
-	 *
183
-	 * @return int
184
-	 */
185
-	protected function get_memory_limit() {
186
-		if ( function_exists( 'ini_get' ) ) {
187
-			$memory_limit = ini_get( 'memory_limit' );
188
-		} else {
189
-			$memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce
190
-		}
191
-
192
-		if ( ! $memory_limit || -1 === $memory_limit || '-1' === $memory_limit ) {
193
-			// Unlimited, set to 32GB.
194
-			$memory_limit = '32G';
195
-		}
196
-
197
-		return ActionScheduler_Compatibility::convert_hr_to_bytes( $memory_limit );
198
-	}
199
-
200
-	/**
201
-	 * Memory exceeded
202
-	 *
203
-	 * Ensures the batch process never exceeds 90% of the maximum WordPress memory.
204
-	 *
205
-	 * Based on WP_Background_Process::memory_exceeded()
206
-	 *
207
-	 * @return bool
208
-	 */
209
-	protected function memory_exceeded() {
210
-
211
-		$memory_limit    = $this->get_memory_limit() * 0.90;
212
-		$current_memory  = memory_get_usage( true );
213
-		$memory_exceeded = $current_memory >= $memory_limit;
214
-
215
-		return apply_filters( 'action_scheduler_memory_exceeded', $memory_exceeded, $this );
216
-	}
217
-
218
-	/**
219
-	 * See if the batch limits have been exceeded, which is when memory usage is almost at
220
-	 * the maximum limit, or the time to process more actions will exceed the max time limit.
221
-	 *
222
-	 * Based on WC_Background_Process::batch_limits_exceeded()
223
-	 *
224
-	 * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action
225
-	 * @return bool
226
-	 */
227
-	protected function batch_limits_exceeded( $processed_actions ) {
228
-		return $this->memory_exceeded() || $this->time_likely_to_be_exceeded( $processed_actions );
229
-	}
230
-
231
-	/**
232
-	 * Process actions in the queue.
233
-	 *
234
-	 * @author Jeremy Pry
235
-	 * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
236
-	 *        Generally, this should be capitalised and not localised as it's a proper noun.
237
-	 * @return int The number of actions processed.
238
-	 */
239
-	abstract public function run( $context = '' );
8
+    /** @var ActionScheduler_QueueCleaner */
9
+    protected $cleaner;
10
+
11
+    /** @var ActionScheduler_FatalErrorMonitor */
12
+    protected $monitor;
13
+
14
+    /** @var ActionScheduler_Store */
15
+    protected $store;
16
+
17
+    /**
18
+     * The created time.
19
+     *
20
+     * Represents when the queue runner was constructed and used when calculating how long a PHP request has been running.
21
+     * For this reason it should be as close as possible to the PHP request start time.
22
+     *
23
+     * @var int
24
+     */
25
+    private $created_time;
26
+
27
+    /**
28
+     * ActionScheduler_Abstract_QueueRunner constructor.
29
+     *
30
+     * @param ActionScheduler_Store             $store
31
+     * @param ActionScheduler_FatalErrorMonitor $monitor
32
+     * @param ActionScheduler_QueueCleaner      $cleaner
33
+     */
34
+    public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null ) {
35
+
36
+        $this->created_time = microtime( true );
37
+
38
+        $this->store   = $store ? $store : ActionScheduler_Store::instance();
39
+        $this->monitor = $monitor ? $monitor : new ActionScheduler_FatalErrorMonitor( $this->store );
40
+        $this->cleaner = $cleaner ? $cleaner : new ActionScheduler_QueueCleaner( $this->store );
41
+    }
42
+
43
+    /**
44
+     * Process an individual action.
45
+     *
46
+     * @param int $action_id The action ID to process.
47
+     * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
48
+     *        Generally, this should be capitalised and not localised as it's a proper noun.
49
+     */
50
+    public function process_action( $action_id, $context = '' ) {
51
+        try {
52
+            $valid_action = false;
53
+            do_action( 'action_scheduler_before_execute', $action_id, $context );
54
+
55
+            if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
56
+                do_action( 'action_scheduler_execution_ignored', $action_id, $context );
57
+                return;
58
+            }
59
+
60
+            $valid_action = true;
61
+            do_action( 'action_scheduler_begin_execute', $action_id, $context );
62
+
63
+            $action = $this->store->fetch_action( $action_id );
64
+            $this->store->log_execution( $action_id );
65
+            $action->execute();
66
+            do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
67
+            $this->store->mark_complete( $action_id );
68
+        } catch ( Exception $e ) {
69
+            if ( $valid_action ) {
70
+                $this->store->mark_failure( $action_id );
71
+                do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
72
+            } else {
73
+                do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
74
+            }
75
+        }
76
+
77
+        if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) {
78
+            $this->schedule_next_instance( $action, $action_id );
79
+        }
80
+    }
81
+
82
+    /**
83
+     * Schedule the next instance of the action if necessary.
84
+     *
85
+     * @param ActionScheduler_Action $action
86
+     * @param int $action_id
87
+     */
88
+    protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) {
89
+        try {
90
+            ActionScheduler::factory()->repeat( $action );
91
+        } catch ( Exception $e ) {
92
+            do_action( 'action_scheduler_failed_to_schedule_next_instance', $action_id, $e, $action );
93
+        }
94
+    }
95
+
96
+    /**
97
+     * Run the queue cleaner.
98
+     *
99
+     * @author Jeremy Pry
100
+     */
101
+    protected function run_cleanup() {
102
+        $this->cleaner->clean( 10 * $this->get_time_limit() );
103
+    }
104
+
105
+    /**
106
+     * Get the number of concurrent batches a runner allows.
107
+     *
108
+     * @return int
109
+     */
110
+    public function get_allowed_concurrent_batches() {
111
+        return apply_filters( 'action_scheduler_queue_runner_concurrent_batches', 1 );
112
+    }
113
+
114
+    /**
115
+     * Check if the number of allowed concurrent batches is met or exceeded.
116
+     *
117
+     * @return bool
118
+     */
119
+    public function has_maximum_concurrent_batches() {
120
+        return $this->store->get_claim_count() >= $this->get_allowed_concurrent_batches();
121
+    }
122
+
123
+    /**
124
+     * Get the maximum number of seconds a batch can run for.
125
+     *
126
+     * @return int The number of seconds.
127
+     */
128
+    protected function get_time_limit() {
129
+
130
+        $time_limit = 30;
131
+
132
+        // Apply deprecated filter from deprecated get_maximum_execution_time() method
133
+        if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) {
134
+            _deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' );
135
+            $time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit );
136
+        }
137
+
138
+        return absint( apply_filters( 'action_scheduler_queue_runner_time_limit', $time_limit ) );
139
+    }
140
+
141
+    /**
142
+     * Get the number of seconds the process has been running.
143
+     *
144
+     * @return int The number of seconds.
145
+     */
146
+    protected function get_execution_time() {
147
+        $execution_time = microtime( true ) - $this->created_time;
148
+
149
+        // Get the CPU time if the hosting environment uses it rather than wall-clock time to calculate a process's execution time.
150
+        if ( function_exists( 'getrusage' ) && apply_filters( 'action_scheduler_use_cpu_execution_time', defined( 'PANTHEON_ENVIRONMENT' ) ) ) {
151
+            $resource_usages = getrusage();
152
+
153
+            if ( isset( $resource_usages['ru_stime.tv_usec'], $resource_usages['ru_stime.tv_usec'] ) ) {
154
+                $execution_time = $resource_usages['ru_stime.tv_sec'] + ( $resource_usages['ru_stime.tv_usec'] / 1000000 );
155
+            }
156
+        }
157
+
158
+        return $execution_time;
159
+    }
160
+
161
+    /**
162
+     * Check if the host's max execution time is (likely) to be exceeded if processing more actions.
163
+     *
164
+     * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action
165
+     * @return bool
166
+     */
167
+    protected function time_likely_to_be_exceeded( $processed_actions ) {
168
+
169
+        $execution_time        = $this->get_execution_time();
170
+        $max_execution_time    = $this->get_time_limit();
171
+        $time_per_action       = $execution_time / $processed_actions;
172
+        $estimated_time        = $execution_time + ( $time_per_action * 3 );
173
+        $likely_to_be_exceeded = $estimated_time > $max_execution_time;
174
+
175
+        return apply_filters( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', $likely_to_be_exceeded, $this, $processed_actions, $execution_time, $max_execution_time );
176
+    }
177
+
178
+    /**
179
+     * Get memory limit
180
+     *
181
+     * Based on WP_Background_Process::get_memory_limit()
182
+     *
183
+     * @return int
184
+     */
185
+    protected function get_memory_limit() {
186
+        if ( function_exists( 'ini_get' ) ) {
187
+            $memory_limit = ini_get( 'memory_limit' );
188
+        } else {
189
+            $memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce
190
+        }
191
+
192
+        if ( ! $memory_limit || -1 === $memory_limit || '-1' === $memory_limit ) {
193
+            // Unlimited, set to 32GB.
194
+            $memory_limit = '32G';
195
+        }
196
+
197
+        return ActionScheduler_Compatibility::convert_hr_to_bytes( $memory_limit );
198
+    }
199
+
200
+    /**
201
+     * Memory exceeded
202
+     *
203
+     * Ensures the batch process never exceeds 90% of the maximum WordPress memory.
204
+     *
205
+     * Based on WP_Background_Process::memory_exceeded()
206
+     *
207
+     * @return bool
208
+     */
209
+    protected function memory_exceeded() {
210
+
211
+        $memory_limit    = $this->get_memory_limit() * 0.90;
212
+        $current_memory  = memory_get_usage( true );
213
+        $memory_exceeded = $current_memory >= $memory_limit;
214
+
215
+        return apply_filters( 'action_scheduler_memory_exceeded', $memory_exceeded, $this );
216
+    }
217
+
218
+    /**
219
+     * See if the batch limits have been exceeded, which is when memory usage is almost at
220
+     * the maximum limit, or the time to process more actions will exceed the max time limit.
221
+     *
222
+     * Based on WC_Background_Process::batch_limits_exceeded()
223
+     *
224
+     * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action
225
+     * @return bool
226
+     */
227
+    protected function batch_limits_exceeded( $processed_actions ) {
228
+        return $this->memory_exceeded() || $this->time_likely_to_be_exceeded( $processed_actions );
229
+    }
230
+
231
+    /**
232
+     * Process actions in the queue.
233
+     *
234
+     * @author Jeremy Pry
235
+     * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
236
+     *        Generally, this should be capitalised and not localised as it's a proper noun.
237
+     * @return int The number of actions processed.
238
+     */
239
+    abstract public function run( $context = '' );
240 240
 }
Please login to merge, or discard this patch.
action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php 1 patch
Indentation   +649 added lines, -649 removed lines patch added patch discarded remove patch
@@ -1,7 +1,7 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 
3 3
 if ( ! class_exists( 'WP_List_Table' ) ) {
4
-	require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
4
+    require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
5 5
 }
6 6
 
7 7
 /**
@@ -23,652 +23,652 @@  discard block
 block discarded – undo
23 23
  */
24 24
 abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
25 25
 
26
-	/**
27
-	 * The table name
28
-	 */
29
-	protected $table_name;
30
-
31
-	/**
32
-	 * Package name, used to get options from WP_List_Table::get_items_per_page.
33
-	 */
34
-	protected $package;
35
-
36
-	/**
37
-	 * How many items do we render per page?
38
-	 */
39
-	protected $items_per_page = 10;
40
-
41
-	/**
42
-	 * Enables search in this table listing. If this array
43
-	 * is empty it means the listing is not searchable.
44
-	 */
45
-	protected $search_by = array();
46
-
47
-	/**
48
-	 * Columns to show in the table listing. It is a key => value pair. The
49
-	 * key must much the table column name and the value is the label, which is
50
-	 * automatically translated.
51
-	 */
52
-	protected $columns = array();
53
-
54
-	/**
55
-	 * Defines the row-actions. It expects an array where the key
56
-	 * is the column name and the value is an array of actions.
57
-	 *
58
-	 * The array of actions are key => value, where key is the method name
59
-	 * (with the prefix row_action_<key>) and the value is the label
60
-	 * and title.
61
-	 */
62
-	protected $row_actions = array();
63
-
64
-	/**
65
-	 * The Primary key of our table
66
-	 */
67
-	protected $ID = 'ID';
68
-
69
-	/**
70
-	 * Enables sorting, it expects an array
71
-	 * of columns (the column names are the values)
72
-	 */
73
-	protected $sort_by = array();
74
-
75
-	protected $filter_by = array();
76
-
77
-	/**
78
-	 * @var array The status name => count combinations for this table's items. Used to display status filters.
79
-	 */
80
-	protected $status_counts = array();
81
-
82
-	/**
83
-	 * @var array Notices to display when loading the table. Array of arrays of form array( 'class' => {updated|error}, 'message' => 'This is the notice text display.' ).
84
-	 */
85
-	protected $admin_notices = array();
86
-
87
-	/**
88
-	 * @var string Localised string displayed in the <h1> element above the able.
89
-	 */
90
-	protected $table_header;
91
-
92
-	/**
93
-	 * Enables bulk actions. It must be an array where the key is the action name
94
-	 * and the value is the label (which is translated automatically). It is important
95
-	 * to notice that it will check that the method exists (`bulk_$name`) and will throw
96
-	 * an exception if it does not exists.
97
-	 *
98
-	 * This class will automatically check if the current request has a bulk action, will do the
99
-	 * validations and afterwards will execute the bulk method, with two arguments. The first argument
100
-	 * is the array with primary keys, the second argument is a string with a list of the primary keys,
101
-	 * escaped and ready to use (with `IN`).
102
-	 */
103
-	protected $bulk_actions = array();
104
-
105
-	/**
106
-	 * Makes translation easier, it basically just wraps
107
-	 * `_x` with some default (the package name).
108
-	 * 
109
-	 * @deprecated 3.0.0
110
-	 */
111
-	protected function translate( $text, $context = '' ) {
112
-		return $text;
113
-	}
114
-
115
-	/**
116
-	 * Reads `$this->bulk_actions` and returns an array that WP_List_Table understands. It
117
-	 * also validates that the bulk method handler exists. It throws an exception because
118
-	 * this is a library meant for developers and missing a bulk method is a development-time error.
119
-	 */
120
-	protected function get_bulk_actions() {
121
-		$actions = array();
122
-
123
-		foreach ( $this->bulk_actions as $action => $label ) {
124
-			if ( ! is_callable( array( $this, 'bulk_' . $action ) ) ) {
125
-				throw new RuntimeException( "The bulk action $action does not have a callback method" );
126
-			}
127
-
128
-			$actions[ $action ] = $label;
129
-		}
130
-
131
-		return $actions;
132
-	}
133
-
134
-	/**
135
-	 * Checks if the current request has a bulk action. If that is the case it will validate and will
136
-	 * execute the bulk method handler. Regardless if the action is valid or not it will redirect to
137
-	 * the previous page removing the current arguments that makes this request a bulk action.
138
-	 */
139
-	protected function process_bulk_action() {
140
-		global $wpdb;
141
-		// Detect when a bulk action is being triggered.
142
-		$action = $this->current_action();
143
-		if ( ! $action ) {
144
-			return;
145
-		}
146
-
147
-		check_admin_referer( 'bulk-' . $this->_args['plural'] );
148
-
149
-		$method   = 'bulk_' . $action;
150
-		if ( array_key_exists( $action, $this->bulk_actions ) && is_callable( array( $this, $method ) ) && ! empty( $_GET['ID'] ) && is_array( $_GET['ID'] ) ) {
151
-			$ids_sql = '(' . implode( ',', array_fill( 0, count( $_GET['ID'] ), '%s' ) ) . ')';
152
-			$this->$method( $_GET['ID'], $wpdb->prepare( $ids_sql, $_GET['ID'] ) );
153
-		}
154
-
155
-		wp_redirect( remove_query_arg(
156
-			array( '_wp_http_referer', '_wpnonce', 'ID', 'action', 'action2' ),
157
-			wp_unslash( $_SERVER['REQUEST_URI'] )
158
-		) );
159
-		exit;
160
-	}
161
-
162
-	/**
163
-	 * Default code for deleting entries.
164
-	 * validated already by process_bulk_action()
165
-	 */
166
-	protected function bulk_delete( array $ids, $ids_sql ) {
167
-		$store = ActionScheduler::store();
168
-		foreach ( $ids as $action_id ) {
169
-			$store->delete( $action_id );
170
-		}
171
-	}
172
-
173
-	/**
174
-	 * Prepares the _column_headers property which is used by WP_Table_List at rendering.
175
-	 * It merges the columns and the sortable columns.
176
-	 */
177
-	protected function prepare_column_headers() {
178
-		$this->_column_headers = array(
179
-			$this->get_columns(),
180
-			array(),
181
-			$this->get_sortable_columns(),
182
-		);
183
-	}
184
-
185
-	/**
186
-	 * Reads $this->sort_by and returns the columns name in a format that WP_Table_List
187
-	 * expects
188
-	 */
189
-	public function get_sortable_columns() {
190
-		$sort_by = array();
191
-		foreach ( $this->sort_by as $column ) {
192
-			$sort_by[ $column ] = array( $column, true );
193
-		}
194
-		return $sort_by;
195
-	}
196
-
197
-	/**
198
-	 * Returns the columns names for rendering. It adds a checkbox for selecting everything
199
-	 * as the first column
200
-	 */
201
-	public function get_columns() {
202
-		$columns = array_merge(
203
-			array( 'cb' => '<input type="checkbox" />' ),
204
-			$this->columns
205
-		);
206
-
207
-		return $columns;
208
-	}
209
-
210
-	/**
211
-	 * Get prepared LIMIT clause for items query
212
-	 *
213
-	 * @global wpdb $wpdb
214
-	 *
215
-	 * @return string Prepared LIMIT clause for items query.
216
-	 */
217
-	protected function get_items_query_limit() {
218
-		global $wpdb;
219
-
220
-		$per_page = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
221
-		return $wpdb->prepare( 'LIMIT %d', $per_page );
222
-	}
223
-
224
-	/**
225
-	 * Returns the number of items to offset/skip for this current view.
226
-	 *
227
-	 * @return int
228
-	 */
229
-	protected function get_items_offset() {
230
-		$per_page = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
231
-		$current_page = $this->get_pagenum();
232
-		if ( 1 < $current_page ) {
233
-			$offset = $per_page * ( $current_page - 1 );
234
-		} else {
235
-			$offset = 0;
236
-		}
237
-
238
-		return $offset;
239
-	}
240
-
241
-	/**
242
-	 * Get prepared OFFSET clause for items query
243
-	 *
244
-	 * @global wpdb $wpdb
245
-	 *
246
-	 * @return string Prepared OFFSET clause for items query.
247
-	 */
248
-	protected function get_items_query_offset() {
249
-		global $wpdb;
250
-
251
-		return $wpdb->prepare( 'OFFSET %d', $this->get_items_offset() );
252
-	}
253
-
254
-	/**
255
-	 * Prepares the ORDER BY sql statement. It uses `$this->sort_by` to know which
256
-	 * columns are sortable. This requests validates the orderby $_GET parameter is a valid
257
-	 * column and sortable. It will also use order (ASC|DESC) using DESC by default.
258
-	 */
259
-	protected function get_items_query_order() {
260
-		if ( empty( $this->sort_by ) ) {
261
-			return '';
262
-		}
263
-
264
-		$orderby = esc_sql( $this->get_request_orderby() );
265
-		$order   = esc_sql( $this->get_request_order() );
266
-
267
-		return "ORDER BY {$orderby} {$order}";
268
-	}
269
-
270
-	/**
271
-	 * Return the sortable column specified for this request to order the results by, if any.
272
-	 *
273
-	 * @return string
274
-	 */
275
-	protected function get_request_orderby() {
276
-
277
-		$valid_sortable_columns = array_values( $this->sort_by );
278
-
279
-		if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns ) ) {
280
-			$orderby = sanitize_text_field( $_GET['orderby'] );
281
-		} else {
282
-			$orderby = $valid_sortable_columns[0];
283
-		}
284
-
285
-		return $orderby;
286
-	}
287
-
288
-	/**
289
-	 * Return the sortable column order specified for this request.
290
-	 *
291
-	 * @return string
292
-	 */
293
-	protected function get_request_order() {
294
-
295
-		if ( ! empty( $_GET['order'] ) && 'desc' === strtolower( $_GET['order'] ) ) {
296
-			$order = 'DESC';
297
-		} else {
298
-			$order = 'ASC';
299
-		}
300
-
301
-		return $order;
302
-	}
303
-
304
-	/**
305
-	 * Return the status filter for this request, if any.
306
-	 *
307
-	 * @return string
308
-	 */
309
-	protected function get_request_status() {
310
-		$status = ( ! empty( $_GET['status'] ) ) ? $_GET['status'] : '';
311
-		return $status;
312
-	}
313
-
314
-	/**
315
-	 * Return the search filter for this request, if any.
316
-	 *
317
-	 * @return string
318
-	 */
319
-	protected function get_request_search_query() {
320
-		$search_query = ( ! empty( $_GET['s'] ) ) ? $_GET['s'] : '';
321
-		return $search_query;
322
-	}
323
-
324
-	/**
325
-	 * Process and return the columns name. This is meant for using with SQL, this means it
326
-	 * always includes the primary key.
327
-	 *
328
-	 * @return array
329
-	 */
330
-	protected function get_table_columns() {
331
-		$columns = array_keys( $this->columns );
332
-		if ( ! in_array( $this->ID, $columns ) ) {
333
-			$columns[] = $this->ID;
334
-		}
335
-
336
-		return $columns;
337
-	}
338
-
339
-	/**
340
-	 * Check if the current request is doing a "full text" search. If that is the case
341
-	 * prepares the SQL to search texts using LIKE.
342
-	 *
343
-	 * If the current request does not have any search or if this list table does not support
344
-	 * that feature it will return an empty string.
345
-	 *
346
-	 * TODO:
347
-	 *   - Improve search doing LIKE by word rather than by phrases.
348
-	 *
349
-	 * @return string
350
-	 */
351
-	protected function get_items_query_search() {
352
-		global $wpdb;
353
-
354
-		if ( empty( $_GET['s'] ) || empty( $this->search_by ) ) {
355
-			return '';
356
-		}
357
-
358
-		$filter  = array();
359
-		foreach ( $this->search_by as $column ) {
360
-			$filter[] = $wpdb->prepare('`' . $column . '` like "%%s%"', $wpdb->esc_like( $_GET['s'] ));
361
-		}
362
-		return implode( ' OR ', $filter );
363
-	}
364
-
365
-	/**
366
-	 * Prepares the SQL to filter rows by the options defined at `$this->filter_by`. Before trusting
367
-	 * any data sent by the user it validates that it is a valid option.
368
-	 */
369
-	protected function get_items_query_filters() {
370
-		global $wpdb;
371
-
372
-		if ( ! $this->filter_by || empty( $_GET['filter_by'] ) || ! is_array( $_GET['filter_by'] ) ) {
373
-			return '';
374
-		}
375
-
376
-		$filter = array();
377
-
378
-		foreach ( $this->filter_by as $column => $options ) {
379
-			if ( empty( $_GET['filter_by'][ $column ] ) || empty( $options[ $_GET['filter_by'][ $column ] ] ) ) {
380
-				continue;
381
-			}
382
-
383
-			$filter[] = $wpdb->prepare( "`$column` = %s", $_GET['filter_by'][ $column ] );
384
-		}
385
-
386
-		return implode( ' AND ', $filter );
387
-
388
-	}
389
-
390
-	/**
391
-	 * Prepares the data to feed WP_Table_List.
392
-	 *
393
-	 * This has the core for selecting, sorting and filting data. To keep the code simple
394
-	 * its logic is split among many methods (get_items_query_*).
395
-	 *
396
-	 * Beside populating the items this function will also count all the records that matches
397
-	 * the filtering criteria and will do fill the pagination variables.
398
-	 */
399
-	public function prepare_items() {
400
-		global $wpdb;
401
-
402
-		$this->process_bulk_action();
403
-
404
-		$this->process_row_actions();
405
-
406
-		if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
407
-			// _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
408
-			wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
409
-			exit;
410
-		}
411
-
412
-		$this->prepare_column_headers();
413
-
414
-		$limit   = $this->get_items_query_limit();
415
-		$offset  = $this->get_items_query_offset();
416
-		$order   = $this->get_items_query_order();
417
-		$where   = array_filter(array(
418
-			$this->get_items_query_search(),
419
-			$this->get_items_query_filters(),
420
-		));
421
-		$columns = '`' . implode( '`, `', $this->get_table_columns() ) . '`';
422
-
423
-		if ( ! empty( $where ) ) {
424
-			$where = 'WHERE ('. implode( ') AND (', $where ) . ')';
425
-		} else {
426
-			$where = '';
427
-		}
428
-
429
-		$sql = "SELECT $columns FROM {$this->table_name} {$where} {$order} {$limit} {$offset}";
430
-
431
-		$this->set_items( $wpdb->get_results( $sql, ARRAY_A ) );
432
-
433
-		$query_count = "SELECT COUNT({$this->ID}) FROM {$this->table_name} {$where}";
434
-		$total_items = $wpdb->get_var( $query_count );
435
-		$per_page    = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
436
-		$this->set_pagination_args( array(
437
-			'total_items' => $total_items,
438
-			'per_page'    => $per_page,
439
-			'total_pages' => ceil( $total_items / $per_page ),
440
-		) );
441
-	}
442
-
443
-	public function extra_tablenav( $which ) {
444
-		if ( ! $this->filter_by || 'top' !== $which ) {
445
-			return;
446
-		}
447
-
448
-		echo '<div class="alignleft actions">';
449
-
450
-		foreach ( $this->filter_by as $id => $options ) {
451
-			$default = ! empty( $_GET['filter_by'][ $id ] ) ? $_GET['filter_by'][ $id ] : '';
452
-			if ( empty( $options[ $default ] ) ) {
453
-				$default = '';
454
-			}
455
-
456
-			echo '<select name="filter_by[' . esc_attr( $id ) . ']" class="first" id="filter-by-' . esc_attr( $id ) . '">';
457
-
458
-			foreach ( $options as $value => $label ) {
459
-				echo '<option value="' . esc_attr( $value ) . '" ' . esc_html( $value == $default ? 'selected' : '' )  .'>'
460
-					. esc_html( $label )
461
-				. '</option>';
462
-			}
463
-
464
-			echo '</select>';
465
-		}
466
-
467
-		submit_button( esc_html__( 'Filter', 'action-scheduler' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
468
-		echo '</div>';
469
-	}
470
-
471
-	/**
472
-	 * Set the data for displaying. It will attempt to unserialize (There is a chance that some columns
473
-	 * are serialized). This can be override in child classes for futher data transformation.
474
-	 */
475
-	protected function set_items( array $items ) {
476
-		$this->items = array();
477
-		foreach ( $items as $item ) {
478
-			$this->items[ $item[ $this->ID ] ] = array_map( 'maybe_unserialize', $item );
479
-		}
480
-	}
481
-
482
-	/**
483
-	 * Renders the checkbox for each row, this is the first column and it is named ID regardless
484
-	 * of how the primary key is named (to keep the code simpler). The bulk actions will do the proper
485
-	 * name transformation though using `$this->ID`.
486
-	 */
487
-	public function column_cb( $row ) {
488
-		return '<input name="ID[]" type="checkbox" value="' . esc_attr( $row[ $this->ID ] ) .'" />';
489
-	}
490
-
491
-	/**
492
-	 * Renders the row-actions.
493
-	 *
494
-	 * This method renders the action menu, it reads the definition from the $row_actions property,
495
-	 * and it checks that the row action method exists before rendering it.
496
-	 *
497
-	 * @param array $row     Row to render
498
-	 * @param $column_name   Current row
499
-	 * @return
500
-	 */
501
-	protected function maybe_render_actions( $row, $column_name ) {
502
-		if ( empty( $this->row_actions[ $column_name ] ) ) {
503
-			return;
504
-		}
505
-
506
-		$row_id = $row[ $this->ID ];
507
-
508
-		$actions = '<div class="row-actions">';
509
-		$action_count = 0;
510
-		foreach ( $this->row_actions[ $column_name ] as $action_key => $action ) {
511
-
512
-			$action_count++;
513
-
514
-			if ( ! method_exists( $this, 'row_action_' . $action_key ) ) {
515
-				continue;
516
-			}
517
-
518
-			$action_link = ! empty( $action['link'] ) ? $action['link'] : add_query_arg( array( 'row_action' => $action_key, 'row_id' => $row_id, 'nonce'  => wp_create_nonce( $action_key . '::' . $row_id ) ) );
519
-			$span_class  = ! empty( $action['class'] ) ? $action['class'] : $action_key;
520
-			$separator   = ( $action_count < count( $this->row_actions[ $column_name ] ) ) ? ' | ' : '';
521
-
522
-			$actions .= sprintf( '<span class="%s">', esc_attr( $span_class ) );
523
-			$actions .= sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', esc_url( $action_link ), esc_attr( $action['desc'] ), esc_html( $action['name'] ) );
524
-			$actions .= sprintf( '%s</span>', $separator );
525
-		}
526
-		$actions .= '</div>';
527
-		return $actions;
528
-	}
529
-
530
-	protected function process_row_actions() {
531
-		$parameters = array( 'row_action', 'row_id', 'nonce' );
532
-		foreach ( $parameters as $parameter ) {
533
-			if ( empty( $_REQUEST[ $parameter ] ) ) {
534
-				return;
535
-			}
536
-		}
537
-
538
-		$method = 'row_action_' . $_REQUEST['row_action'];
539
-
540
-		if ( $_REQUEST['nonce'] === wp_create_nonce( $_REQUEST[ 'row_action' ] . '::' . $_REQUEST[ 'row_id' ] ) && method_exists( $this, $method ) ) {
541
-			$this->$method( $_REQUEST['row_id'] );
542
-		}
543
-
544
-		wp_redirect( remove_query_arg(
545
-			array( 'row_id', 'row_action', 'nonce' ),
546
-			wp_unslash( $_SERVER['REQUEST_URI'] )
547
-		) );
548
-		exit;
549
-	}
550
-
551
-	/**
552
-	 * Default column formatting, it will escape everythig for security.
553
-	 */
554
-	public function column_default( $item, $column_name ) {
555
-		$column_html = esc_html( $item[ $column_name ] );
556
-		$column_html .= $this->maybe_render_actions( $item, $column_name );
557
-		return $column_html;
558
-	}
559
-
560
-	/**
561
-	 * Display the table heading and search query, if any
562
-	 */
563
-	protected function display_header() {
564
-		echo '<h1 class="wp-heading-inline">' . esc_attr( $this->table_header ) . '</h1>';
565
-		if ( $this->get_request_search_query() ) {
566
-			/* translators: %s: search query */
567
-			echo '<span class="subtitle">' . esc_attr( sprintf( __( 'Search results for "%s"', 'action-scheduler' ), $this->get_request_search_query() ) ) . '</span>';
568
-		}
569
-		echo '<hr class="wp-header-end">';
570
-	}
571
-
572
-	/**
573
-	 * Display the table heading and search query, if any
574
-	 */
575
-	protected function display_admin_notices() {
576
-		foreach ( $this->admin_notices as $notice ) {
577
-			echo '<div id="message" class="' . $notice['class'] . '">';
578
-			echo '	<p>' . wp_kses_post( $notice['message'] ) . '</p>';
579
-			echo '</div>';
580
-		}
581
-	}
582
-
583
-	/**
584
-	 * Prints the available statuses so the user can click to filter.
585
-	 */
586
-	protected function display_filter_by_status() {
587
-
588
-		$status_list_items = array();
589
-		$request_status    = $this->get_request_status();
590
-
591
-		// Helper to set 'all' filter when not set on status counts passed in
592
-		if ( ! isset( $this->status_counts['all'] ) ) {
593
-			$this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts;
594
-		}
595
-
596
-		foreach ( $this->status_counts as $status_name => $count ) {
597
-
598
-			if ( 0 === $count ) {
599
-				continue;
600
-			}
601
-
602
-			if ( $status_name === $request_status || ( empty( $request_status ) && 'all' === $status_name ) ) {
603
-				$status_list_item = '<li class="%1$s"><strong>%3$s</strong> (%4$d)</li>';
604
-			} else {
605
-				$status_list_item = '<li class="%1$s"><a href="%2$s">%3$s</a> (%4$d)</li>';
606
-			}
607
-
608
-			$status_filter_url   = ( 'all' === $status_name ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_name );
609
-			$status_filter_url   = remove_query_arg( array( 'paged', 's' ), $status_filter_url );
610
-			$status_list_items[] = sprintf( $status_list_item, esc_attr( $status_name ), esc_url( $status_filter_url ), esc_html( ucfirst( $status_name ) ), absint( $count ) );
611
-		}
612
-
613
-		if ( $status_list_items ) {
614
-			echo '<ul class="subsubsub">';
615
-			echo implode( " | \n", $status_list_items );
616
-			echo '</ul>';
617
-		}
618
-	}
619
-
620
-	/**
621
-	 * Renders the table list, we override the original class to render the table inside a form
622
-	 * and to render any needed HTML (like the search box). By doing so the callee of a function can simple
623
-	 * forget about any extra HTML.
624
-	 */
625
-	protected function display_table() {
626
-		echo '<form id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="get">';
627
-		foreach ( $_GET as $key => $value ) {
628
-			if ( '_' === $key[0] || 'paged' === $key ) {
629
-				continue;
630
-			}
631
-			echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $value ) . '" />';
632
-		}
633
-		if ( ! empty( $this->search_by ) ) {
634
-			echo $this->search_box( $this->get_search_box_button_text(), 'plugin' ); // WPCS: XSS OK
635
-		}
636
-		parent::display();
637
-		echo '</form>';
638
-	}
639
-
640
-	/**
641
-	 * Process any pending actions.
642
-	 */
643
-	public function process_actions() {
644
-		$this->process_bulk_action();
645
-		$this->process_row_actions();
646
-
647
-		if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
648
-			// _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
649
-			wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
650
-			exit;
651
-		}
652
-	}
653
-
654
-	/**
655
-	 * Render the list table page, including header, notices, status filters and table.
656
-	 */
657
-	public function display_page() {
658
-		$this->prepare_items();
659
-
660
-		echo '<div class="wrap">';
661
-		$this->display_header();
662
-		$this->display_admin_notices();
663
-		$this->display_filter_by_status();
664
-		$this->display_table();
665
-		echo '</div>';
666
-	}
667
-
668
-	/**
669
-	 * Get the text to display in the search box on the list table.
670
-	 */
671
-	protected function get_search_box_placeholder() {
672
-		return esc_html__( 'Search', 'action-scheduler' );
673
-	}
26
+    /**
27
+     * The table name
28
+     */
29
+    protected $table_name;
30
+
31
+    /**
32
+     * Package name, used to get options from WP_List_Table::get_items_per_page.
33
+     */
34
+    protected $package;
35
+
36
+    /**
37
+     * How many items do we render per page?
38
+     */
39
+    protected $items_per_page = 10;
40
+
41
+    /**
42
+     * Enables search in this table listing. If this array
43
+     * is empty it means the listing is not searchable.
44
+     */
45
+    protected $search_by = array();
46
+
47
+    /**
48
+     * Columns to show in the table listing. It is a key => value pair. The
49
+     * key must much the table column name and the value is the label, which is
50
+     * automatically translated.
51
+     */
52
+    protected $columns = array();
53
+
54
+    /**
55
+     * Defines the row-actions. It expects an array where the key
56
+     * is the column name and the value is an array of actions.
57
+     *
58
+     * The array of actions are key => value, where key is the method name
59
+     * (with the prefix row_action_<key>) and the value is the label
60
+     * and title.
61
+     */
62
+    protected $row_actions = array();
63
+
64
+    /**
65
+     * The Primary key of our table
66
+     */
67
+    protected $ID = 'ID';
68
+
69
+    /**
70
+     * Enables sorting, it expects an array
71
+     * of columns (the column names are the values)
72
+     */
73
+    protected $sort_by = array();
74
+
75
+    protected $filter_by = array();
76
+
77
+    /**
78
+     * @var array The status name => count combinations for this table's items. Used to display status filters.
79
+     */
80
+    protected $status_counts = array();
81
+
82
+    /**
83
+     * @var array Notices to display when loading the table. Array of arrays of form array( 'class' => {updated|error}, 'message' => 'This is the notice text display.' ).
84
+     */
85
+    protected $admin_notices = array();
86
+
87
+    /**
88
+     * @var string Localised string displayed in the <h1> element above the able.
89
+     */
90
+    protected $table_header;
91
+
92
+    /**
93
+     * Enables bulk actions. It must be an array where the key is the action name
94
+     * and the value is the label (which is translated automatically). It is important
95
+     * to notice that it will check that the method exists (`bulk_$name`) and will throw
96
+     * an exception if it does not exists.
97
+     *
98
+     * This class will automatically check if the current request has a bulk action, will do the
99
+     * validations and afterwards will execute the bulk method, with two arguments. The first argument
100
+     * is the array with primary keys, the second argument is a string with a list of the primary keys,
101
+     * escaped and ready to use (with `IN`).
102
+     */
103
+    protected $bulk_actions = array();
104
+
105
+    /**
106
+     * Makes translation easier, it basically just wraps
107
+     * `_x` with some default (the package name).
108
+     * 
109
+     * @deprecated 3.0.0
110
+     */
111
+    protected function translate( $text, $context = '' ) {
112
+        return $text;
113
+    }
114
+
115
+    /**
116
+     * Reads `$this->bulk_actions` and returns an array that WP_List_Table understands. It
117
+     * also validates that the bulk method handler exists. It throws an exception because
118
+     * this is a library meant for developers and missing a bulk method is a development-time error.
119
+     */
120
+    protected function get_bulk_actions() {
121
+        $actions = array();
122
+
123
+        foreach ( $this->bulk_actions as $action => $label ) {
124
+            if ( ! is_callable( array( $this, 'bulk_' . $action ) ) ) {
125
+                throw new RuntimeException( "The bulk action $action does not have a callback method" );
126
+            }
127
+
128
+            $actions[ $action ] = $label;
129
+        }
130
+
131
+        return $actions;
132
+    }
133
+
134
+    /**
135
+     * Checks if the current request has a bulk action. If that is the case it will validate and will
136
+     * execute the bulk method handler. Regardless if the action is valid or not it will redirect to
137
+     * the previous page removing the current arguments that makes this request a bulk action.
138
+     */
139
+    protected function process_bulk_action() {
140
+        global $wpdb;
141
+        // Detect when a bulk action is being triggered.
142
+        $action = $this->current_action();
143
+        if ( ! $action ) {
144
+            return;
145
+        }
146
+
147
+        check_admin_referer( 'bulk-' . $this->_args['plural'] );
148
+
149
+        $method   = 'bulk_' . $action;
150
+        if ( array_key_exists( $action, $this->bulk_actions ) && is_callable( array( $this, $method ) ) && ! empty( $_GET['ID'] ) && is_array( $_GET['ID'] ) ) {
151
+            $ids_sql = '(' . implode( ',', array_fill( 0, count( $_GET['ID'] ), '%s' ) ) . ')';
152
+            $this->$method( $_GET['ID'], $wpdb->prepare( $ids_sql, $_GET['ID'] ) );
153
+        }
154
+
155
+        wp_redirect( remove_query_arg(
156
+            array( '_wp_http_referer', '_wpnonce', 'ID', 'action', 'action2' ),
157
+            wp_unslash( $_SERVER['REQUEST_URI'] )
158
+        ) );
159
+        exit;
160
+    }
161
+
162
+    /**
163
+     * Default code for deleting entries.
164
+     * validated already by process_bulk_action()
165
+     */
166
+    protected function bulk_delete( array $ids, $ids_sql ) {
167
+        $store = ActionScheduler::store();
168
+        foreach ( $ids as $action_id ) {
169
+            $store->delete( $action_id );
170
+        }
171
+    }
172
+
173
+    /**
174
+     * Prepares the _column_headers property which is used by WP_Table_List at rendering.
175
+     * It merges the columns and the sortable columns.
176
+     */
177
+    protected function prepare_column_headers() {
178
+        $this->_column_headers = array(
179
+            $this->get_columns(),
180
+            array(),
181
+            $this->get_sortable_columns(),
182
+        );
183
+    }
184
+
185
+    /**
186
+     * Reads $this->sort_by and returns the columns name in a format that WP_Table_List
187
+     * expects
188
+     */
189
+    public function get_sortable_columns() {
190
+        $sort_by = array();
191
+        foreach ( $this->sort_by as $column ) {
192
+            $sort_by[ $column ] = array( $column, true );
193
+        }
194
+        return $sort_by;
195
+    }
196
+
197
+    /**
198
+     * Returns the columns names for rendering. It adds a checkbox for selecting everything
199
+     * as the first column
200
+     */
201
+    public function get_columns() {
202
+        $columns = array_merge(
203
+            array( 'cb' => '<input type="checkbox" />' ),
204
+            $this->columns
205
+        );
206
+
207
+        return $columns;
208
+    }
209
+
210
+    /**
211
+     * Get prepared LIMIT clause for items query
212
+     *
213
+     * @global wpdb $wpdb
214
+     *
215
+     * @return string Prepared LIMIT clause for items query.
216
+     */
217
+    protected function get_items_query_limit() {
218
+        global $wpdb;
219
+
220
+        $per_page = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
221
+        return $wpdb->prepare( 'LIMIT %d', $per_page );
222
+    }
223
+
224
+    /**
225
+     * Returns the number of items to offset/skip for this current view.
226
+     *
227
+     * @return int
228
+     */
229
+    protected function get_items_offset() {
230
+        $per_page = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
231
+        $current_page = $this->get_pagenum();
232
+        if ( 1 < $current_page ) {
233
+            $offset = $per_page * ( $current_page - 1 );
234
+        } else {
235
+            $offset = 0;
236
+        }
237
+
238
+        return $offset;
239
+    }
240
+
241
+    /**
242
+     * Get prepared OFFSET clause for items query
243
+     *
244
+     * @global wpdb $wpdb
245
+     *
246
+     * @return string Prepared OFFSET clause for items query.
247
+     */
248
+    protected function get_items_query_offset() {
249
+        global $wpdb;
250
+
251
+        return $wpdb->prepare( 'OFFSET %d', $this->get_items_offset() );
252
+    }
253
+
254
+    /**
255
+     * Prepares the ORDER BY sql statement. It uses `$this->sort_by` to know which
256
+     * columns are sortable. This requests validates the orderby $_GET parameter is a valid
257
+     * column and sortable. It will also use order (ASC|DESC) using DESC by default.
258
+     */
259
+    protected function get_items_query_order() {
260
+        if ( empty( $this->sort_by ) ) {
261
+            return '';
262
+        }
263
+
264
+        $orderby = esc_sql( $this->get_request_orderby() );
265
+        $order   = esc_sql( $this->get_request_order() );
266
+
267
+        return "ORDER BY {$orderby} {$order}";
268
+    }
269
+
270
+    /**
271
+     * Return the sortable column specified for this request to order the results by, if any.
272
+     *
273
+     * @return string
274
+     */
275
+    protected function get_request_orderby() {
276
+
277
+        $valid_sortable_columns = array_values( $this->sort_by );
278
+
279
+        if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns ) ) {
280
+            $orderby = sanitize_text_field( $_GET['orderby'] );
281
+        } else {
282
+            $orderby = $valid_sortable_columns[0];
283
+        }
284
+
285
+        return $orderby;
286
+    }
287
+
288
+    /**
289
+     * Return the sortable column order specified for this request.
290
+     *
291
+     * @return string
292
+     */
293
+    protected function get_request_order() {
294
+
295
+        if ( ! empty( $_GET['order'] ) && 'desc' === strtolower( $_GET['order'] ) ) {
296
+            $order = 'DESC';
297
+        } else {
298
+            $order = 'ASC';
299
+        }
300
+
301
+        return $order;
302
+    }
303
+
304
+    /**
305
+     * Return the status filter for this request, if any.
306
+     *
307
+     * @return string
308
+     */
309
+    protected function get_request_status() {
310
+        $status = ( ! empty( $_GET['status'] ) ) ? $_GET['status'] : '';
311
+        return $status;
312
+    }
313
+
314
+    /**
315
+     * Return the search filter for this request, if any.
316
+     *
317
+     * @return string
318
+     */
319
+    protected function get_request_search_query() {
320
+        $search_query = ( ! empty( $_GET['s'] ) ) ? $_GET['s'] : '';
321
+        return $search_query;
322
+    }
323
+
324
+    /**
325
+     * Process and return the columns name. This is meant for using with SQL, this means it
326
+     * always includes the primary key.
327
+     *
328
+     * @return array
329
+     */
330
+    protected function get_table_columns() {
331
+        $columns = array_keys( $this->columns );
332
+        if ( ! in_array( $this->ID, $columns ) ) {
333
+            $columns[] = $this->ID;
334
+        }
335
+
336
+        return $columns;
337
+    }
338
+
339
+    /**
340
+     * Check if the current request is doing a "full text" search. If that is the case
341
+     * prepares the SQL to search texts using LIKE.
342
+     *
343
+     * If the current request does not have any search or if this list table does not support
344
+     * that feature it will return an empty string.
345
+     *
346
+     * TODO:
347
+     *   - Improve search doing LIKE by word rather than by phrases.
348
+     *
349
+     * @return string
350
+     */
351
+    protected function get_items_query_search() {
352
+        global $wpdb;
353
+
354
+        if ( empty( $_GET['s'] ) || empty( $this->search_by ) ) {
355
+            return '';
356
+        }
357
+
358
+        $filter  = array();
359
+        foreach ( $this->search_by as $column ) {
360
+            $filter[] = $wpdb->prepare('`' . $column . '` like "%%s%"', $wpdb->esc_like( $_GET['s'] ));
361
+        }
362
+        return implode( ' OR ', $filter );
363
+    }
364
+
365
+    /**
366
+     * Prepares the SQL to filter rows by the options defined at `$this->filter_by`. Before trusting
367
+     * any data sent by the user it validates that it is a valid option.
368
+     */
369
+    protected function get_items_query_filters() {
370
+        global $wpdb;
371
+
372
+        if ( ! $this->filter_by || empty( $_GET['filter_by'] ) || ! is_array( $_GET['filter_by'] ) ) {
373
+            return '';
374
+        }
375
+
376
+        $filter = array();
377
+
378
+        foreach ( $this->filter_by as $column => $options ) {
379
+            if ( empty( $_GET['filter_by'][ $column ] ) || empty( $options[ $_GET['filter_by'][ $column ] ] ) ) {
380
+                continue;
381
+            }
382
+
383
+            $filter[] = $wpdb->prepare( "`$column` = %s", $_GET['filter_by'][ $column ] );
384
+        }
385
+
386
+        return implode( ' AND ', $filter );
387
+
388
+    }
389
+
390
+    /**
391
+     * Prepares the data to feed WP_Table_List.
392
+     *
393
+     * This has the core for selecting, sorting and filting data. To keep the code simple
394
+     * its logic is split among many methods (get_items_query_*).
395
+     *
396
+     * Beside populating the items this function will also count all the records that matches
397
+     * the filtering criteria and will do fill the pagination variables.
398
+     */
399
+    public function prepare_items() {
400
+        global $wpdb;
401
+
402
+        $this->process_bulk_action();
403
+
404
+        $this->process_row_actions();
405
+
406
+        if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
407
+            // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
408
+            wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
409
+            exit;
410
+        }
411
+
412
+        $this->prepare_column_headers();
413
+
414
+        $limit   = $this->get_items_query_limit();
415
+        $offset  = $this->get_items_query_offset();
416
+        $order   = $this->get_items_query_order();
417
+        $where   = array_filter(array(
418
+            $this->get_items_query_search(),
419
+            $this->get_items_query_filters(),
420
+        ));
421
+        $columns = '`' . implode( '`, `', $this->get_table_columns() ) . '`';
422
+
423
+        if ( ! empty( $where ) ) {
424
+            $where = 'WHERE ('. implode( ') AND (', $where ) . ')';
425
+        } else {
426
+            $where = '';
427
+        }
428
+
429
+        $sql = "SELECT $columns FROM {$this->table_name} {$where} {$order} {$limit} {$offset}";
430
+
431
+        $this->set_items( $wpdb->get_results( $sql, ARRAY_A ) );
432
+
433
+        $query_count = "SELECT COUNT({$this->ID}) FROM {$this->table_name} {$where}";
434
+        $total_items = $wpdb->get_var( $query_count );
435
+        $per_page    = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
436
+        $this->set_pagination_args( array(
437
+            'total_items' => $total_items,
438
+            'per_page'    => $per_page,
439
+            'total_pages' => ceil( $total_items / $per_page ),
440
+        ) );
441
+    }
442
+
443
+    public function extra_tablenav( $which ) {
444
+        if ( ! $this->filter_by || 'top' !== $which ) {
445
+            return;
446
+        }
447
+
448
+        echo '<div class="alignleft actions">';
449
+
450
+        foreach ( $this->filter_by as $id => $options ) {
451
+            $default = ! empty( $_GET['filter_by'][ $id ] ) ? $_GET['filter_by'][ $id ] : '';
452
+            if ( empty( $options[ $default ] ) ) {
453
+                $default = '';
454
+            }
455
+
456
+            echo '<select name="filter_by[' . esc_attr( $id ) . ']" class="first" id="filter-by-' . esc_attr( $id ) . '">';
457
+
458
+            foreach ( $options as $value => $label ) {
459
+                echo '<option value="' . esc_attr( $value ) . '" ' . esc_html( $value == $default ? 'selected' : '' )  .'>'
460
+                    . esc_html( $label )
461
+                . '</option>';
462
+            }
463
+
464
+            echo '</select>';
465
+        }
466
+
467
+        submit_button( esc_html__( 'Filter', 'action-scheduler' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
468
+        echo '</div>';
469
+    }
470
+
471
+    /**
472
+     * Set the data for displaying. It will attempt to unserialize (There is a chance that some columns
473
+     * are serialized). This can be override in child classes for futher data transformation.
474
+     */
475
+    protected function set_items( array $items ) {
476
+        $this->items = array();
477
+        foreach ( $items as $item ) {
478
+            $this->items[ $item[ $this->ID ] ] = array_map( 'maybe_unserialize', $item );
479
+        }
480
+    }
481
+
482
+    /**
483
+     * Renders the checkbox for each row, this is the first column and it is named ID regardless
484
+     * of how the primary key is named (to keep the code simpler). The bulk actions will do the proper
485
+     * name transformation though using `$this->ID`.
486
+     */
487
+    public function column_cb( $row ) {
488
+        return '<input name="ID[]" type="checkbox" value="' . esc_attr( $row[ $this->ID ] ) .'" />';
489
+    }
490
+
491
+    /**
492
+     * Renders the row-actions.
493
+     *
494
+     * This method renders the action menu, it reads the definition from the $row_actions property,
495
+     * and it checks that the row action method exists before rendering it.
496
+     *
497
+     * @param array $row     Row to render
498
+     * @param $column_name   Current row
499
+     * @return
500
+     */
501
+    protected function maybe_render_actions( $row, $column_name ) {
502
+        if ( empty( $this->row_actions[ $column_name ] ) ) {
503
+            return;
504
+        }
505
+
506
+        $row_id = $row[ $this->ID ];
507
+
508
+        $actions = '<div class="row-actions">';
509
+        $action_count = 0;
510
+        foreach ( $this->row_actions[ $column_name ] as $action_key => $action ) {
511
+
512
+            $action_count++;
513
+
514
+            if ( ! method_exists( $this, 'row_action_' . $action_key ) ) {
515
+                continue;
516
+            }
517
+
518
+            $action_link = ! empty( $action['link'] ) ? $action['link'] : add_query_arg( array( 'row_action' => $action_key, 'row_id' => $row_id, 'nonce'  => wp_create_nonce( $action_key . '::' . $row_id ) ) );
519
+            $span_class  = ! empty( $action['class'] ) ? $action['class'] : $action_key;
520
+            $separator   = ( $action_count < count( $this->row_actions[ $column_name ] ) ) ? ' | ' : '';
521
+
522
+            $actions .= sprintf( '<span class="%s">', esc_attr( $span_class ) );
523
+            $actions .= sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', esc_url( $action_link ), esc_attr( $action['desc'] ), esc_html( $action['name'] ) );
524
+            $actions .= sprintf( '%s</span>', $separator );
525
+        }
526
+        $actions .= '</div>';
527
+        return $actions;
528
+    }
529
+
530
+    protected function process_row_actions() {
531
+        $parameters = array( 'row_action', 'row_id', 'nonce' );
532
+        foreach ( $parameters as $parameter ) {
533
+            if ( empty( $_REQUEST[ $parameter ] ) ) {
534
+                return;
535
+            }
536
+        }
537
+
538
+        $method = 'row_action_' . $_REQUEST['row_action'];
539
+
540
+        if ( $_REQUEST['nonce'] === wp_create_nonce( $_REQUEST[ 'row_action' ] . '::' . $_REQUEST[ 'row_id' ] ) && method_exists( $this, $method ) ) {
541
+            $this->$method( $_REQUEST['row_id'] );
542
+        }
543
+
544
+        wp_redirect( remove_query_arg(
545
+            array( 'row_id', 'row_action', 'nonce' ),
546
+            wp_unslash( $_SERVER['REQUEST_URI'] )
547
+        ) );
548
+        exit;
549
+    }
550
+
551
+    /**
552
+     * Default column formatting, it will escape everythig for security.
553
+     */
554
+    public function column_default( $item, $column_name ) {
555
+        $column_html = esc_html( $item[ $column_name ] );
556
+        $column_html .= $this->maybe_render_actions( $item, $column_name );
557
+        return $column_html;
558
+    }
559
+
560
+    /**
561
+     * Display the table heading and search query, if any
562
+     */
563
+    protected function display_header() {
564
+        echo '<h1 class="wp-heading-inline">' . esc_attr( $this->table_header ) . '</h1>';
565
+        if ( $this->get_request_search_query() ) {
566
+            /* translators: %s: search query */
567
+            echo '<span class="subtitle">' . esc_attr( sprintf( __( 'Search results for "%s"', 'action-scheduler' ), $this->get_request_search_query() ) ) . '</span>';
568
+        }
569
+        echo '<hr class="wp-header-end">';
570
+    }
571
+
572
+    /**
573
+     * Display the table heading and search query, if any
574
+     */
575
+    protected function display_admin_notices() {
576
+        foreach ( $this->admin_notices as $notice ) {
577
+            echo '<div id="message" class="' . $notice['class'] . '">';
578
+            echo '	<p>' . wp_kses_post( $notice['message'] ) . '</p>';
579
+            echo '</div>';
580
+        }
581
+    }
582
+
583
+    /**
584
+     * Prints the available statuses so the user can click to filter.
585
+     */
586
+    protected function display_filter_by_status() {
587
+
588
+        $status_list_items = array();
589
+        $request_status    = $this->get_request_status();
590
+
591
+        // Helper to set 'all' filter when not set on status counts passed in
592
+        if ( ! isset( $this->status_counts['all'] ) ) {
593
+            $this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts;
594
+        }
595
+
596
+        foreach ( $this->status_counts as $status_name => $count ) {
597
+
598
+            if ( 0 === $count ) {
599
+                continue;
600
+            }
601
+
602
+            if ( $status_name === $request_status || ( empty( $request_status ) && 'all' === $status_name ) ) {
603
+                $status_list_item = '<li class="%1$s"><strong>%3$s</strong> (%4$d)</li>';
604
+            } else {
605
+                $status_list_item = '<li class="%1$s"><a href="%2$s">%3$s</a> (%4$d)</li>';
606
+            }
607
+
608
+            $status_filter_url   = ( 'all' === $status_name ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_name );
609
+            $status_filter_url   = remove_query_arg( array( 'paged', 's' ), $status_filter_url );
610
+            $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_name ), esc_url( $status_filter_url ), esc_html( ucfirst( $status_name ) ), absint( $count ) );
611
+        }
612
+
613
+        if ( $status_list_items ) {
614
+            echo '<ul class="subsubsub">';
615
+            echo implode( " | \n", $status_list_items );
616
+            echo '</ul>';
617
+        }
618
+    }
619
+
620
+    /**
621
+     * Renders the table list, we override the original class to render the table inside a form
622
+     * and to render any needed HTML (like the search box). By doing so the callee of a function can simple
623
+     * forget about any extra HTML.
624
+     */
625
+    protected function display_table() {
626
+        echo '<form id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="get">';
627
+        foreach ( $_GET as $key => $value ) {
628
+            if ( '_' === $key[0] || 'paged' === $key ) {
629
+                continue;
630
+            }
631
+            echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $value ) . '" />';
632
+        }
633
+        if ( ! empty( $this->search_by ) ) {
634
+            echo $this->search_box( $this->get_search_box_button_text(), 'plugin' ); // WPCS: XSS OK
635
+        }
636
+        parent::display();
637
+        echo '</form>';
638
+    }
639
+
640
+    /**
641
+     * Process any pending actions.
642
+     */
643
+    public function process_actions() {
644
+        $this->process_bulk_action();
645
+        $this->process_row_actions();
646
+
647
+        if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
648
+            // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
649
+            wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
650
+            exit;
651
+        }
652
+    }
653
+
654
+    /**
655
+     * Render the list table page, including header, notices, status filters and table.
656
+     */
657
+    public function display_page() {
658
+        $this->prepare_items();
659
+
660
+        echo '<div class="wrap">';
661
+        $this->display_header();
662
+        $this->display_admin_notices();
663
+        $this->display_filter_by_status();
664
+        $this->display_table();
665
+        echo '</div>';
666
+    }
667
+
668
+    /**
669
+     * Get the text to display in the search box on the list table.
670
+     */
671
+    protected function get_search_box_placeholder() {
672
+        return esc_html__( 'Search', 'action-scheduler' );
673
+    }
674 674
 }
Please login to merge, or discard this patch.
classes/abstracts/ActionScheduler_Abstract_RecurringSchedule.php 1 patch
Indentation   +86 added lines, -86 removed lines patch added patch discarded remove patch
@@ -5,98 +5,98 @@
 block discarded – undo
5 5
  */
6 6
 abstract class ActionScheduler_Abstract_RecurringSchedule extends ActionScheduler_Abstract_Schedule {
7 7
 
8
-	/**
9
-	 * The date & time the first instance of this schedule was setup to run (which may not be this instance).
10
-	 *
11
-	 * Schedule objects are attached to an action object. Each schedule stores the run date for that
12
-	 * object as the start date - @see $this->start - and logic to calculate the next run date after
13
-	 * that - @see $this->calculate_next(). The $first_date property also keeps a record of when the very
14
-	 * first instance of this chain of schedules ran.
15
-	 *
16
-	 * @var DateTime
17
-	 */
18
-	private $first_date = NULL;
8
+    /**
9
+     * The date & time the first instance of this schedule was setup to run (which may not be this instance).
10
+     *
11
+     * Schedule objects are attached to an action object. Each schedule stores the run date for that
12
+     * object as the start date - @see $this->start - and logic to calculate the next run date after
13
+     * that - @see $this->calculate_next(). The $first_date property also keeps a record of when the very
14
+     * first instance of this chain of schedules ran.
15
+     *
16
+     * @var DateTime
17
+     */
18
+    private $first_date = NULL;
19 19
 
20
-	/**
21
-	 * Timestamp equivalent of @see $this->first_date
22
-	 *
23
-	 * @var int
24
-	 */
25
-	protected $first_timestamp = NULL;
20
+    /**
21
+     * Timestamp equivalent of @see $this->first_date
22
+     *
23
+     * @var int
24
+     */
25
+    protected $first_timestamp = NULL;
26 26
 
27
-	/**
28
-	 * The recurrance between each time an action is run using this schedule.
29
-	 * Used to calculate the start date & time. Can be a number of seconds, in the
30
-	 * case of ActionScheduler_IntervalSchedule, or a cron expression, as in the
31
-	 * case of ActionScheduler_CronSchedule. Or something else.
32
-	 *
33
-	 * @var mixed
34
-	 */
35
-	protected $recurrence;
27
+    /**
28
+     * The recurrance between each time an action is run using this schedule.
29
+     * Used to calculate the start date & time. Can be a number of seconds, in the
30
+     * case of ActionScheduler_IntervalSchedule, or a cron expression, as in the
31
+     * case of ActionScheduler_CronSchedule. Or something else.
32
+     *
33
+     * @var mixed
34
+     */
35
+    protected $recurrence;
36 36
 
37
-	/**
38
-	 * @param DateTime $date The date & time to run the action.
39
-	 * @param mixed $recurrence The data used to determine the schedule's recurrance.
40
-	 * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance.
41
-	 */
42
-	public function __construct( DateTime $date, $recurrence, DateTime $first = null ) {
43
-		parent::__construct( $date );
44
-		$this->first_date = empty( $first ) ? $date : $first;
45
-		$this->recurrence = $recurrence;
46
-	}
37
+    /**
38
+     * @param DateTime $date The date & time to run the action.
39
+     * @param mixed $recurrence The data used to determine the schedule's recurrance.
40
+     * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance.
41
+     */
42
+    public function __construct( DateTime $date, $recurrence, DateTime $first = null ) {
43
+        parent::__construct( $date );
44
+        $this->first_date = empty( $first ) ? $date : $first;
45
+        $this->recurrence = $recurrence;
46
+    }
47 47
 
48
-	/**
49
-	 * @return bool
50
-	 */
51
-	public function is_recurring() {
52
-		return true;
53
-	}
48
+    /**
49
+     * @return bool
50
+     */
51
+    public function is_recurring() {
52
+        return true;
53
+    }
54 54
 
55
-	/**
56
-	 * Get the date & time of the first schedule in this recurring series.
57
-	 *
58
-	 * @return DateTime|null
59
-	 */
60
-	public function get_first_date() {
61
-		return clone $this->first_date;
62
-	}
55
+    /**
56
+     * Get the date & time of the first schedule in this recurring series.
57
+     *
58
+     * @return DateTime|null
59
+     */
60
+    public function get_first_date() {
61
+        return clone $this->first_date;
62
+    }
63 63
 
64
-	/**
65
-	 * @return string
66
-	 */
67
-	public function get_recurrence() {
68
-		return $this->recurrence;
69
-	}
64
+    /**
65
+     * @return string
66
+     */
67
+    public function get_recurrence() {
68
+        return $this->recurrence;
69
+    }
70 70
 
71
-	/**
72
-	 * For PHP 5.2 compat, since DateTime objects can't be serialized
73
-	 * @return array
74
-	 */
75
-	public function __sleep() {
76
-		$sleep_params = parent::__sleep();
77
-		$this->first_timestamp = $this->first_date->getTimestamp();
78
-		return array_merge( $sleep_params, array(
79
-			'first_timestamp',
80
-			'recurrence'
81
-		) );
82
-	}
71
+    /**
72
+     * For PHP 5.2 compat, since DateTime objects can't be serialized
73
+     * @return array
74
+     */
75
+    public function __sleep() {
76
+        $sleep_params = parent::__sleep();
77
+        $this->first_timestamp = $this->first_date->getTimestamp();
78
+        return array_merge( $sleep_params, array(
79
+            'first_timestamp',
80
+            'recurrence'
81
+        ) );
82
+    }
83 83
 
84
-	/**
85
-	 * Unserialize recurring schedules serialized/stored prior to AS 3.0.0
86
-	 *
87
-	 * Prior to Action Scheduler 3.0.0, schedules used different property names to refer
88
-	 * to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp
89
-	 * was the same as ActionScheduler_SimpleSchedule::timestamp. This was addressed in
90
-	 * Action Scheduler 3.0.0, where properties and property names were aligned for better
91
-	 * inheritance. To maintain backward compatibility with scheduled serialized and stored
92
-	 * prior to 3.0, we need to correctly map the old property names.
93
-	 */
94
-	public function __wakeup() {
95
-		parent::__wakeup();
96
-		if ( $this->first_timestamp > 0 ) {
97
-			$this->first_date = as_get_datetime_object( $this->first_timestamp );
98
-		} else {
99
-			$this->first_date = $this->get_date();
100
-		}
101
-	}
84
+    /**
85
+     * Unserialize recurring schedules serialized/stored prior to AS 3.0.0
86
+     *
87
+     * Prior to Action Scheduler 3.0.0, schedules used different property names to refer
88
+     * to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp
89
+     * was the same as ActionScheduler_SimpleSchedule::timestamp. This was addressed in
90
+     * Action Scheduler 3.0.0, where properties and property names were aligned for better
91
+     * inheritance. To maintain backward compatibility with scheduled serialized and stored
92
+     * prior to 3.0, we need to correctly map the old property names.
93
+     */
94
+    public function __wakeup() {
95
+        parent::__wakeup();
96
+        if ( $this->first_timestamp > 0 ) {
97
+            $this->first_date = as_get_datetime_object( $this->first_timestamp );
98
+        } else {
99
+            $this->first_date = $this->get_date();
100
+        }
101
+    }
102 102
 }
Please login to merge, or discard this patch.
action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php 1 patch
Indentation   +145 added lines, -145 removed lines patch added patch discarded remove patch
@@ -4,149 +4,149 @@
 block discarded – undo
4 4
  * Class ActionScheduler_TimezoneHelper
5 5
  */
6 6
 abstract class ActionScheduler_TimezoneHelper {
7
-	private static $local_timezone = NULL;
8
-
9
-	/**
10
-	 * Set a DateTime's timezone to the WordPress site's timezone, or a UTC offset
11
-	 * if no timezone string is available.
12
-	 *
13
-	 * @since  2.1.0
14
-	 *
15
-	 * @param DateTime $date
16
-	 * @return ActionScheduler_DateTime
17
-	 */
18
-	public static function set_local_timezone( DateTime $date ) {
19
-
20
-		// Accept a DateTime for easier backward compatibility, even though we require methods on ActionScheduler_DateTime
21
-		if ( ! is_a( $date, 'ActionScheduler_DateTime' ) ) {
22
-			$date = as_get_datetime_object( $date->format( 'U' ) );
23
-		}
24
-
25
-		if ( get_option( 'timezone_string' ) ) {
26
-			$date->setTimezone( new DateTimeZone( self::get_local_timezone_string() ) );
27
-		} else {
28
-			$date->setUtcOffset( self::get_local_timezone_offset() );
29
-		}
30
-
31
-		return $date;
32
-	}
33
-
34
-	/**
35
-	 * Helper to retrieve the timezone string for a site until a WP core method exists
36
-	 * (see https://core.trac.wordpress.org/ticket/24730).
37
-	 *
38
-	 * Adapted from wc_timezone_string() and https://secure.php.net/manual/en/function.timezone-name-from-abbr.php#89155.
39
-	 *
40
-	 * If no timezone string is set, and its not possible to match the UTC offset set for the site to a timezone
41
-	 * string, then an empty string will be returned, and the UTC offset should be used to set a DateTime's
42
-	 * timezone.
43
-	 *
44
-	 * @since 2.1.0
45
-	 * @return string PHP timezone string for the site or empty if no timezone string is available.
46
-	 */
47
-	protected static function get_local_timezone_string( $reset = false ) {
48
-		// If site timezone string exists, return it.
49
-		$timezone = get_option( 'timezone_string' );
50
-		if ( $timezone ) {
51
-			return $timezone;
52
-		}
53
-
54
-		// Get UTC offset, if it isn't set then return UTC.
55
-		$utc_offset = intval( get_option( 'gmt_offset', 0 ) );
56
-		if ( 0 === $utc_offset ) {
57
-			return 'UTC';
58
-		}
59
-
60
-		// Adjust UTC offset from hours to seconds.
61
-		$utc_offset *= 3600;
62
-
63
-		// Attempt to guess the timezone string from the UTC offset.
64
-		$timezone = timezone_name_from_abbr( '', $utc_offset );
65
-		if ( $timezone ) {
66
-			return $timezone;
67
-		}
68
-
69
-		// Last try, guess timezone string manually.
70
-		foreach ( timezone_abbreviations_list() as $abbr ) {
71
-			foreach ( $abbr as $city ) {
72
-				if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) {
73
-					return $city['timezone_id'];
74
-				}
75
-			}
76
-		}
77
-
78
-		// No timezone string
79
-		return '';
80
-	}
81
-
82
-	/**
83
-	 * Get timezone offset in seconds.
84
-	 *
85
-	 * @since  2.1.0
86
-	 * @return float
87
-	 */
88
-	protected static function get_local_timezone_offset() {
89
-		$timezone = get_option( 'timezone_string' );
90
-
91
-		if ( $timezone ) {
92
-			$timezone_object = new DateTimeZone( $timezone );
93
-			return $timezone_object->getOffset( new DateTime( 'now' ) );
94
-		} else {
95
-			return floatval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS;
96
-		}
97
-	}
98
-
99
-	/**
100
-	 * @deprecated 2.1.0
101
-	 */
102
-	public static function get_local_timezone( $reset = FALSE ) {
103
-		_deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' );
104
-		if ( $reset ) {
105
-			self::$local_timezone = NULL;
106
-		}
107
-		if ( !isset(self::$local_timezone) ) {
108
-			$tzstring = get_option('timezone_string');
109
-
110
-			if ( empty($tzstring) ) {
111
-				$gmt_offset = get_option('gmt_offset');
112
-				if ( $gmt_offset == 0 ) {
113
-					$tzstring = 'UTC';
114
-				} else {
115
-					$gmt_offset *= HOUR_IN_SECONDS;
116
-					$tzstring   = timezone_name_from_abbr( '', $gmt_offset, 1 );
117
-
118
-					// If there's no timezone string, try again with no DST.
119
-					if ( false === $tzstring ) {
120
-						$tzstring = timezone_name_from_abbr( '', $gmt_offset, 0 );
121
-					}
122
-
123
-					// Try mapping to the first abbreviation we can find.
124
-					if ( false === $tzstring ) {
125
-						$is_dst = date( 'I' );
126
-						foreach ( timezone_abbreviations_list() as $abbr ) {
127
-							foreach ( $abbr as $city ) {
128
-								if ( $city['dst'] == $is_dst && $city['offset'] == $gmt_offset ) {
129
-									// If there's no valid timezone ID, keep looking.
130
-									if ( null === $city['timezone_id'] ) {
131
-										continue;
132
-									}
133
-
134
-									$tzstring = $city['timezone_id'];
135
-									break 2;
136
-								}
137
-							}
138
-						}
139
-					}
140
-
141
-					// If we still have no valid string, then fall back to UTC.
142
-					if ( false === $tzstring ) {
143
-						$tzstring = 'UTC';
144
-					}
145
-				}
146
-			}
147
-
148
-			self::$local_timezone = new DateTimeZone($tzstring);
149
-		}
150
-		return self::$local_timezone;
151
-	}
7
+    private static $local_timezone = NULL;
8
+
9
+    /**
10
+     * Set a DateTime's timezone to the WordPress site's timezone, or a UTC offset
11
+     * if no timezone string is available.
12
+     *
13
+     * @since  2.1.0
14
+     *
15
+     * @param DateTime $date
16
+     * @return ActionScheduler_DateTime
17
+     */
18
+    public static function set_local_timezone( DateTime $date ) {
19
+
20
+        // Accept a DateTime for easier backward compatibility, even though we require methods on ActionScheduler_DateTime
21
+        if ( ! is_a( $date, 'ActionScheduler_DateTime' ) ) {
22
+            $date = as_get_datetime_object( $date->format( 'U' ) );
23
+        }
24
+
25
+        if ( get_option( 'timezone_string' ) ) {
26
+            $date->setTimezone( new DateTimeZone( self::get_local_timezone_string() ) );
27
+        } else {
28
+            $date->setUtcOffset( self::get_local_timezone_offset() );
29
+        }
30
+
31
+        return $date;
32
+    }
33
+
34
+    /**
35
+     * Helper to retrieve the timezone string for a site until a WP core method exists
36
+     * (see https://core.trac.wordpress.org/ticket/24730).
37
+     *
38
+     * Adapted from wc_timezone_string() and https://secure.php.net/manual/en/function.timezone-name-from-abbr.php#89155.
39
+     *
40
+     * If no timezone string is set, and its not possible to match the UTC offset set for the site to a timezone
41
+     * string, then an empty string will be returned, and the UTC offset should be used to set a DateTime's
42
+     * timezone.
43
+     *
44
+     * @since 2.1.0
45
+     * @return string PHP timezone string for the site or empty if no timezone string is available.
46
+     */
47
+    protected static function get_local_timezone_string( $reset = false ) {
48
+        // If site timezone string exists, return it.
49
+        $timezone = get_option( 'timezone_string' );
50
+        if ( $timezone ) {
51
+            return $timezone;
52
+        }
53
+
54
+        // Get UTC offset, if it isn't set then return UTC.
55
+        $utc_offset = intval( get_option( 'gmt_offset', 0 ) );
56
+        if ( 0 === $utc_offset ) {
57
+            return 'UTC';
58
+        }
59
+
60
+        // Adjust UTC offset from hours to seconds.
61
+        $utc_offset *= 3600;
62
+
63
+        // Attempt to guess the timezone string from the UTC offset.
64
+        $timezone = timezone_name_from_abbr( '', $utc_offset );
65
+        if ( $timezone ) {
66
+            return $timezone;
67
+        }
68
+
69
+        // Last try, guess timezone string manually.
70
+        foreach ( timezone_abbreviations_list() as $abbr ) {
71
+            foreach ( $abbr as $city ) {
72
+                if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) {
73
+                    return $city['timezone_id'];
74
+                }
75
+            }
76
+        }
77
+
78
+        // No timezone string
79
+        return '';
80
+    }
81
+
82
+    /**
83
+     * Get timezone offset in seconds.
84
+     *
85
+     * @since  2.1.0
86
+     * @return float
87
+     */
88
+    protected static function get_local_timezone_offset() {
89
+        $timezone = get_option( 'timezone_string' );
90
+
91
+        if ( $timezone ) {
92
+            $timezone_object = new DateTimeZone( $timezone );
93
+            return $timezone_object->getOffset( new DateTime( 'now' ) );
94
+        } else {
95
+            return floatval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS;
96
+        }
97
+    }
98
+
99
+    /**
100
+     * @deprecated 2.1.0
101
+     */
102
+    public static function get_local_timezone( $reset = FALSE ) {
103
+        _deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' );
104
+        if ( $reset ) {
105
+            self::$local_timezone = NULL;
106
+        }
107
+        if ( !isset(self::$local_timezone) ) {
108
+            $tzstring = get_option('timezone_string');
109
+
110
+            if ( empty($tzstring) ) {
111
+                $gmt_offset = get_option('gmt_offset');
112
+                if ( $gmt_offset == 0 ) {
113
+                    $tzstring = 'UTC';
114
+                } else {
115
+                    $gmt_offset *= HOUR_IN_SECONDS;
116
+                    $tzstring   = timezone_name_from_abbr( '', $gmt_offset, 1 );
117
+
118
+                    // If there's no timezone string, try again with no DST.
119
+                    if ( false === $tzstring ) {
120
+                        $tzstring = timezone_name_from_abbr( '', $gmt_offset, 0 );
121
+                    }
122
+
123
+                    // Try mapping to the first abbreviation we can find.
124
+                    if ( false === $tzstring ) {
125
+                        $is_dst = date( 'I' );
126
+                        foreach ( timezone_abbreviations_list() as $abbr ) {
127
+                            foreach ( $abbr as $city ) {
128
+                                if ( $city['dst'] == $is_dst && $city['offset'] == $gmt_offset ) {
129
+                                    // If there's no valid timezone ID, keep looking.
130
+                                    if ( null === $city['timezone_id'] ) {
131
+                                        continue;
132
+                                    }
133
+
134
+                                    $tzstring = $city['timezone_id'];
135
+                                    break 2;
136
+                                }
137
+                            }
138
+                        }
139
+                    }
140
+
141
+                    // If we still have no valid string, then fall back to UTC.
142
+                    if ( false === $tzstring ) {
143
+                        $tzstring = 'UTC';
144
+                    }
145
+                }
146
+            }
147
+
148
+            self::$local_timezone = new DateTimeZone($tzstring);
149
+        }
150
+        return self::$local_timezone;
151
+    }
152 152
 }
Please login to merge, or discard this patch.
libraries/action-scheduler/classes/abstracts/ActionScheduler_Store.php 1 patch
Indentation   +337 added lines, -337 removed lines patch added patch discarded remove patch
@@ -5,341 +5,341 @@
 block discarded – undo
5 5
  * @codeCoverageIgnore
6 6
  */
7 7
 abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
8
-	const STATUS_COMPLETE = 'complete';
9
-	const STATUS_PENDING  = 'pending';
10
-	const STATUS_RUNNING  = 'in-progress';
11
-	const STATUS_FAILED   = 'failed';
12
-	const STATUS_CANCELED = 'canceled';
13
-	const DEFAULT_CLASS   = 'ActionScheduler_wpPostStore';
14
-
15
-	/** @var ActionScheduler_Store */
16
-	private static $store = NULL;
17
-
18
-	/** @var int */
19
-	protected static $max_args_length = 191;
20
-
21
-	/**
22
-	 * @param ActionScheduler_Action $action
23
-	 * @param DateTime $scheduled_date Optional Date of the first instance
24
-	 *        to store. Otherwise uses the first date of the action's
25
-	 *        schedule.
26
-	 *
27
-	 * @return string The action ID
28
-	 */
29
-	abstract public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL );
30
-
31
-	/**
32
-	 * @param string $action_id
33
-	 *
34
-	 * @return ActionScheduler_Action
35
-	 */
36
-	abstract public function fetch_action( $action_id );
37
-
38
-	/**
39
-	 * @param string $hook Hook name/slug.
40
-	 * @param array  $params Hook arguments.
41
-	 * @return string ID of the next action matching the criteria.
42
-	 */
43
-	abstract public function find_action( $hook, $params = array() );
44
-
45
-	/**
46
-	 * @param array  $query Query parameters.
47
-	 * @param string $query_type Whether to select or count the results. Default, select.
48
-	 *
49
-	 * @return array|int The IDs of or count of actions matching the query.
50
-	 */
51
-	abstract public function query_actions( $query = array(), $query_type = 'select' );
52
-
53
-	/**
54
-	 * Get a count of all actions in the store, grouped by status
55
-	 *
56
-	 * @return array
57
-	 */
58
-	abstract public function action_counts();
59
-
60
-	/**
61
-	 * @param string $action_id
62
-	 */
63
-	abstract public function cancel_action( $action_id );
64
-
65
-	/**
66
-	 * @param string $action_id
67
-	 */
68
-	abstract public function delete_action( $action_id );
69
-
70
-	/**
71
-	 * @param string $action_id
72
-	 *
73
-	 * @return DateTime The date the action is schedule to run, or the date that it ran.
74
-	 */
75
-	abstract public function get_date( $action_id );
76
-
77
-
78
-	/**
79
-	 * @param int      $max_actions
80
-	 * @param DateTime $before_date Claim only actions schedule before the given date. Defaults to now.
81
-	 * @param array    $hooks       Claim only actions with a hook or hooks.
82
-	 * @param string   $group       Claim only actions in the given group.
83
-	 *
84
-	 * @return ActionScheduler_ActionClaim
85
-	 */
86
-	abstract public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' );
87
-
88
-	/**
89
-	 * @return int
90
-	 */
91
-	abstract public function get_claim_count();
92
-
93
-	/**
94
-	 * @param ActionScheduler_ActionClaim $claim
95
-	 */
96
-	abstract public function release_claim( ActionScheduler_ActionClaim $claim );
97
-
98
-	/**
99
-	 * @param string $action_id
100
-	 */
101
-	abstract public function unclaim_action( $action_id );
102
-
103
-	/**
104
-	 * @param string $action_id
105
-	 */
106
-	abstract public function mark_failure( $action_id );
107
-
108
-	/**
109
-	 * @param string $action_id
110
-	 */
111
-	abstract public function log_execution( $action_id );
112
-
113
-	/**
114
-	 * @param string $action_id
115
-	 */
116
-	abstract public function mark_complete( $action_id );
117
-
118
-	/**
119
-	 * @param string $action_id
120
-	 *
121
-	 * @return string
122
-	 */
123
-	abstract public function get_status( $action_id );
124
-
125
-	/**
126
-	 * @param string $action_id
127
-	 * @return mixed
128
-	 */
129
-	abstract public function get_claim_id( $action_id );
130
-
131
-	/**
132
-	 * @param string $claim_id
133
-	 * @return array
134
-	 */
135
-	abstract public function find_actions_by_claim_id( $claim_id );
136
-
137
-	/**
138
-	 * @param string $comparison_operator
139
-	 * @return string
140
-	 */
141
-	protected function validate_sql_comparator( $comparison_operator ) {
142
-		if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) {
143
-			return $comparison_operator;
144
-		}
145
-		return '=';
146
-	}
147
-
148
-	/**
149
-	 * Get the time MySQL formated date/time string for an action's (next) scheduled date.
150
-	 *
151
-	 * @param ActionScheduler_Action $action
152
-	 * @param DateTime $scheduled_date (optional)
153
-	 * @return string
154
-	 */
155
-	protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
156
-		$next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
157
-		if ( ! $next ) {
158
-			return '0000-00-00 00:00:00';
159
-		}
160
-		$next->setTimezone( new DateTimeZone( 'UTC' ) );
161
-
162
-		return $next->format( 'Y-m-d H:i:s' );
163
-	}
164
-
165
-	/**
166
-	 * Get the time MySQL formated date/time string for an action's (next) scheduled date.
167
-	 *
168
-	 * @param ActionScheduler_Action $action
169
-	 * @param DateTime $scheduled_date (optional)
170
-	 * @return string
171
-	 */
172
-	protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
173
-		$next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
174
-		if ( ! $next ) {
175
-			return '0000-00-00 00:00:00';
176
-		}
177
-
178
-		ActionScheduler_TimezoneHelper::set_local_timezone( $next );
179
-		return $next->format( 'Y-m-d H:i:s' );
180
-	}
181
-
182
-	/**
183
-	 * Validate that we could decode action arguments.
184
-	 *
185
-	 * @param mixed $args      The decoded arguments.
186
-	 * @param int   $action_id The action ID.
187
-	 *
188
-	 * @throws ActionScheduler_InvalidActionException When the decoded arguments are invalid.
189
-	 */
190
-	protected function validate_args( $args, $action_id ) {
191
-		// Ensure we have an array of args.
192
-		if ( ! is_array( $args ) ) {
193
-			throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id );
194
-		}
195
-
196
-		// Validate JSON decoding if possible.
197
-		if ( function_exists( 'json_last_error' ) && JSON_ERROR_NONE !== json_last_error() ) {
198
-			throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id, $args );
199
-		}
200
-	}
201
-
202
-	/**
203
-	 * Validate a ActionScheduler_Schedule object.
204
-	 *
205
-	 * @param mixed $schedule  The unserialized ActionScheduler_Schedule object.
206
-	 * @param int   $action_id The action ID.
207
-	 *
208
-	 * @throws ActionScheduler_InvalidActionException When the schedule is invalid.
209
-	 */
210
-	protected function validate_schedule( $schedule, $action_id ) {
211
-		if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) {
212
-			throw ActionScheduler_InvalidActionException::from_schedule( $action_id, $schedule );
213
-		}
214
-	}
215
-
216
-	/**
217
-	 * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
218
-	 *
219
-	 * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
220
-	 * with custom tables, we use an indexed VARCHAR column instead.
221
-	 *
222
-	 * @param  ActionScheduler_Action $action Action to be validated.
223
-	 * @throws InvalidArgumentException When json encoded args is too long.
224
-	 */
225
-	protected function validate_action( ActionScheduler_Action $action ) {
226
-		if ( strlen( json_encode( $action->get_args() ) ) > static::$max_args_length ) {
227
-			throw new InvalidArgumentException( sprintf( __( 'ActionScheduler_Action::$args too long. To ensure the args column can be indexed, action args should not be more than %d characters when encoded as JSON.', 'action-scheduler' ), static::$max_args_length ) );
228
-		}
229
-	}
230
-
231
-	/**
232
-	 * Cancel pending actions by hook.
233
-	 *
234
-	 * @since 3.0.0
235
-	 *
236
-	 * @param string $hook Hook name.
237
-	 *
238
-	 * @return void
239
-	 */
240
-	public function cancel_actions_by_hook( $hook ) {
241
-		$action_ids = true;
242
-		while ( ! empty( $action_ids ) ) {
243
-			$action_ids = $this->query_actions(
244
-				array(
245
-					'hook' => $hook,
246
-					'status' => self::STATUS_PENDING,
247
-					'per_page' => 1000,
248
-				)
249
-			);
250
-
251
-			$this->bulk_cancel_actions( $action_ids );
252
-		}
253
-	}
254
-
255
-	/**
256
-	 * Cancel pending actions by group.
257
-	 *
258
-	 * @since 3.0.0
259
-	 *
260
-	 * @param string $group Group slug.
261
-	 *
262
-	 * @return void
263
-	 */
264
-	public function cancel_actions_by_group( $group ) {
265
-		$action_ids = true;
266
-		while ( ! empty( $action_ids ) ) {
267
-			$action_ids = $this->query_actions(
268
-				array(
269
-					'group' => $group,
270
-					'status' => self::STATUS_PENDING,
271
-					'per_page' => 1000,
272
-				)
273
-			);
274
-
275
-			$this->bulk_cancel_actions( $action_ids );
276
-		}
277
-	}
278
-
279
-	/**
280
-	 * Cancel a set of action IDs.
281
-	 *
282
-	 * @since 3.0.0
283
-	 *
284
-	 * @param array $action_ids List of action IDs.
285
-	 *
286
-	 * @return void
287
-	 */
288
-	private function bulk_cancel_actions( $action_ids ) {
289
-		foreach ( $action_ids as $action_id ) {
290
-			$this->cancel_action( $action_id );
291
-		}
292
-
293
-		do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
294
-	}
295
-
296
-	/**
297
-	 * @return array
298
-	 */
299
-	public function get_status_labels() {
300
-		return array(
301
-			self::STATUS_COMPLETE => __( 'Complete', 'action-scheduler' ),
302
-			self::STATUS_PENDING  => __( 'Pending', 'action-scheduler' ),
303
-			self::STATUS_RUNNING  => __( 'In-progress', 'action-scheduler' ),
304
-			self::STATUS_FAILED   => __( 'Failed', 'action-scheduler' ),
305
-			self::STATUS_CANCELED => __( 'Canceled', 'action-scheduler' ),
306
-		);
307
-	}
308
-
309
-	/**
310
-	 * Check if there are any pending scheduled actions due to run.
311
-	 *
312
-	 * @param ActionScheduler_Action $action
313
-	 * @param DateTime $scheduled_date (optional)
314
-	 * @return string
315
-	 */
316
-	public function has_pending_actions_due() {
317
-		$pending_actions = $this->query_actions( array(
318
-			'date'   => as_get_datetime_object(),
319
-			'status' => ActionScheduler_Store::STATUS_PENDING,
320
-		) );
321
-
322
-		return ! empty( $pending_actions );
323
-	}
324
-
325
-	/**
326
-	 * Callable initialization function optionally overridden in derived classes.
327
-	 */
328
-	public function init() {}
329
-
330
-	/**
331
-	 * Callable function to mark an action as migrated optionally overridden in derived classes.
332
-	 */
333
-	public function mark_migrated( $action_id ) {}
334
-
335
-	/**
336
-	 * @return ActionScheduler_Store
337
-	 */
338
-	public static function instance() {
339
-		if ( empty( self::$store ) ) {
340
-			$class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS );
341
-			self::$store = new $class();
342
-		}
343
-		return self::$store;
344
-	}
8
+    const STATUS_COMPLETE = 'complete';
9
+    const STATUS_PENDING  = 'pending';
10
+    const STATUS_RUNNING  = 'in-progress';
11
+    const STATUS_FAILED   = 'failed';
12
+    const STATUS_CANCELED = 'canceled';
13
+    const DEFAULT_CLASS   = 'ActionScheduler_wpPostStore';
14
+
15
+    /** @var ActionScheduler_Store */
16
+    private static $store = NULL;
17
+
18
+    /** @var int */
19
+    protected static $max_args_length = 191;
20
+
21
+    /**
22
+     * @param ActionScheduler_Action $action
23
+     * @param DateTime $scheduled_date Optional Date of the first instance
24
+     *        to store. Otherwise uses the first date of the action's
25
+     *        schedule.
26
+     *
27
+     * @return string The action ID
28
+     */
29
+    abstract public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL );
30
+
31
+    /**
32
+     * @param string $action_id
33
+     *
34
+     * @return ActionScheduler_Action
35
+     */
36
+    abstract public function fetch_action( $action_id );
37
+
38
+    /**
39
+     * @param string $hook Hook name/slug.
40
+     * @param array  $params Hook arguments.
41
+     * @return string ID of the next action matching the criteria.
42
+     */
43
+    abstract public function find_action( $hook, $params = array() );
44
+
45
+    /**
46
+     * @param array  $query Query parameters.
47
+     * @param string $query_type Whether to select or count the results. Default, select.
48
+     *
49
+     * @return array|int The IDs of or count of actions matching the query.
50
+     */
51
+    abstract public function query_actions( $query = array(), $query_type = 'select' );
52
+
53
+    /**
54
+     * Get a count of all actions in the store, grouped by status
55
+     *
56
+     * @return array
57
+     */
58
+    abstract public function action_counts();
59
+
60
+    /**
61
+     * @param string $action_id
62
+     */
63
+    abstract public function cancel_action( $action_id );
64
+
65
+    /**
66
+     * @param string $action_id
67
+     */
68
+    abstract public function delete_action( $action_id );
69
+
70
+    /**
71
+     * @param string $action_id
72
+     *
73
+     * @return DateTime The date the action is schedule to run, or the date that it ran.
74
+     */
75
+    abstract public function get_date( $action_id );
76
+
77
+
78
+    /**
79
+     * @param int      $max_actions
80
+     * @param DateTime $before_date Claim only actions schedule before the given date. Defaults to now.
81
+     * @param array    $hooks       Claim only actions with a hook or hooks.
82
+     * @param string   $group       Claim only actions in the given group.
83
+     *
84
+     * @return ActionScheduler_ActionClaim
85
+     */
86
+    abstract public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' );
87
+
88
+    /**
89
+     * @return int
90
+     */
91
+    abstract public function get_claim_count();
92
+
93
+    /**
94
+     * @param ActionScheduler_ActionClaim $claim
95
+     */
96
+    abstract public function release_claim( ActionScheduler_ActionClaim $claim );
97
+
98
+    /**
99
+     * @param string $action_id
100
+     */
101
+    abstract public function unclaim_action( $action_id );
102
+
103
+    /**
104
+     * @param string $action_id
105
+     */
106
+    abstract public function mark_failure( $action_id );
107
+
108
+    /**
109
+     * @param string $action_id
110
+     */
111
+    abstract public function log_execution( $action_id );
112
+
113
+    /**
114
+     * @param string $action_id
115
+     */
116
+    abstract public function mark_complete( $action_id );
117
+
118
+    /**
119
+     * @param string $action_id
120
+     *
121
+     * @return string
122
+     */
123
+    abstract public function get_status( $action_id );
124
+
125
+    /**
126
+     * @param string $action_id
127
+     * @return mixed
128
+     */
129
+    abstract public function get_claim_id( $action_id );
130
+
131
+    /**
132
+     * @param string $claim_id
133
+     * @return array
134
+     */
135
+    abstract public function find_actions_by_claim_id( $claim_id );
136
+
137
+    /**
138
+     * @param string $comparison_operator
139
+     * @return string
140
+     */
141
+    protected function validate_sql_comparator( $comparison_operator ) {
142
+        if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) {
143
+            return $comparison_operator;
144
+        }
145
+        return '=';
146
+    }
147
+
148
+    /**
149
+     * Get the time MySQL formated date/time string for an action's (next) scheduled date.
150
+     *
151
+     * @param ActionScheduler_Action $action
152
+     * @param DateTime $scheduled_date (optional)
153
+     * @return string
154
+     */
155
+    protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
156
+        $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
157
+        if ( ! $next ) {
158
+            return '0000-00-00 00:00:00';
159
+        }
160
+        $next->setTimezone( new DateTimeZone( 'UTC' ) );
161
+
162
+        return $next->format( 'Y-m-d H:i:s' );
163
+    }
164
+
165
+    /**
166
+     * Get the time MySQL formated date/time string for an action's (next) scheduled date.
167
+     *
168
+     * @param ActionScheduler_Action $action
169
+     * @param DateTime $scheduled_date (optional)
170
+     * @return string
171
+     */
172
+    protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
173
+        $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
174
+        if ( ! $next ) {
175
+            return '0000-00-00 00:00:00';
176
+        }
177
+
178
+        ActionScheduler_TimezoneHelper::set_local_timezone( $next );
179
+        return $next->format( 'Y-m-d H:i:s' );
180
+    }
181
+
182
+    /**
183
+     * Validate that we could decode action arguments.
184
+     *
185
+     * @param mixed $args      The decoded arguments.
186
+     * @param int   $action_id The action ID.
187
+     *
188
+     * @throws ActionScheduler_InvalidActionException When the decoded arguments are invalid.
189
+     */
190
+    protected function validate_args( $args, $action_id ) {
191
+        // Ensure we have an array of args.
192
+        if ( ! is_array( $args ) ) {
193
+            throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id );
194
+        }
195
+
196
+        // Validate JSON decoding if possible.
197
+        if ( function_exists( 'json_last_error' ) && JSON_ERROR_NONE !== json_last_error() ) {
198
+            throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id, $args );
199
+        }
200
+    }
201
+
202
+    /**
203
+     * Validate a ActionScheduler_Schedule object.
204
+     *
205
+     * @param mixed $schedule  The unserialized ActionScheduler_Schedule object.
206
+     * @param int   $action_id The action ID.
207
+     *
208
+     * @throws ActionScheduler_InvalidActionException When the schedule is invalid.
209
+     */
210
+    protected function validate_schedule( $schedule, $action_id ) {
211
+        if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) {
212
+            throw ActionScheduler_InvalidActionException::from_schedule( $action_id, $schedule );
213
+        }
214
+    }
215
+
216
+    /**
217
+     * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
218
+     *
219
+     * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
220
+     * with custom tables, we use an indexed VARCHAR column instead.
221
+     *
222
+     * @param  ActionScheduler_Action $action Action to be validated.
223
+     * @throws InvalidArgumentException When json encoded args is too long.
224
+     */
225
+    protected function validate_action( ActionScheduler_Action $action ) {
226
+        if ( strlen( json_encode( $action->get_args() ) ) > static::$max_args_length ) {
227
+            throw new InvalidArgumentException( sprintf( __( 'ActionScheduler_Action::$args too long. To ensure the args column can be indexed, action args should not be more than %d characters when encoded as JSON.', 'action-scheduler' ), static::$max_args_length ) );
228
+        }
229
+    }
230
+
231
+    /**
232
+     * Cancel pending actions by hook.
233
+     *
234
+     * @since 3.0.0
235
+     *
236
+     * @param string $hook Hook name.
237
+     *
238
+     * @return void
239
+     */
240
+    public function cancel_actions_by_hook( $hook ) {
241
+        $action_ids = true;
242
+        while ( ! empty( $action_ids ) ) {
243
+            $action_ids = $this->query_actions(
244
+                array(
245
+                    'hook' => $hook,
246
+                    'status' => self::STATUS_PENDING,
247
+                    'per_page' => 1000,
248
+                )
249
+            );
250
+
251
+            $this->bulk_cancel_actions( $action_ids );
252
+        }
253
+    }
254
+
255
+    /**
256
+     * Cancel pending actions by group.
257
+     *
258
+     * @since 3.0.0
259
+     *
260
+     * @param string $group Group slug.
261
+     *
262
+     * @return void
263
+     */
264
+    public function cancel_actions_by_group( $group ) {
265
+        $action_ids = true;
266
+        while ( ! empty( $action_ids ) ) {
267
+            $action_ids = $this->query_actions(
268
+                array(
269
+                    'group' => $group,
270
+                    'status' => self::STATUS_PENDING,
271
+                    'per_page' => 1000,
272
+                )
273
+            );
274
+
275
+            $this->bulk_cancel_actions( $action_ids );
276
+        }
277
+    }
278
+
279
+    /**
280
+     * Cancel a set of action IDs.
281
+     *
282
+     * @since 3.0.0
283
+     *
284
+     * @param array $action_ids List of action IDs.
285
+     *
286
+     * @return void
287
+     */
288
+    private function bulk_cancel_actions( $action_ids ) {
289
+        foreach ( $action_ids as $action_id ) {
290
+            $this->cancel_action( $action_id );
291
+        }
292
+
293
+        do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
294
+    }
295
+
296
+    /**
297
+     * @return array
298
+     */
299
+    public function get_status_labels() {
300
+        return array(
301
+            self::STATUS_COMPLETE => __( 'Complete', 'action-scheduler' ),
302
+            self::STATUS_PENDING  => __( 'Pending', 'action-scheduler' ),
303
+            self::STATUS_RUNNING  => __( 'In-progress', 'action-scheduler' ),
304
+            self::STATUS_FAILED   => __( 'Failed', 'action-scheduler' ),
305
+            self::STATUS_CANCELED => __( 'Canceled', 'action-scheduler' ),
306
+        );
307
+    }
308
+
309
+    /**
310
+     * Check if there are any pending scheduled actions due to run.
311
+     *
312
+     * @param ActionScheduler_Action $action
313
+     * @param DateTime $scheduled_date (optional)
314
+     * @return string
315
+     */
316
+    public function has_pending_actions_due() {
317
+        $pending_actions = $this->query_actions( array(
318
+            'date'   => as_get_datetime_object(),
319
+            'status' => ActionScheduler_Store::STATUS_PENDING,
320
+        ) );
321
+
322
+        return ! empty( $pending_actions );
323
+    }
324
+
325
+    /**
326
+     * Callable initialization function optionally overridden in derived classes.
327
+     */
328
+    public function init() {}
329
+
330
+    /**
331
+     * Callable function to mark an action as migrated optionally overridden in derived classes.
332
+     */
333
+    public function mark_migrated( $action_id ) {}
334
+
335
+    /**
336
+     * @return ActionScheduler_Store
337
+     */
338
+    public static function instance() {
339
+        if ( empty( self::$store ) ) {
340
+            $class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS );
341
+            self::$store = new $class();
342
+        }
343
+        return self::$store;
344
+    }
345 345
 }
Please login to merge, or discard this patch.
action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schedule.php 1 patch
Indentation   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -5,79 +5,79 @@
 block discarded – undo
5 5
  */
6 6
 abstract class ActionScheduler_Abstract_Schedule extends ActionScheduler_Schedule_Deprecated {
7 7
 
8
-	/**
9
-	 * The date & time the schedule is set to run.
10
-	 *
11
-	 * @var DateTime
12
-	 */
13
-	private $scheduled_date = NULL;
8
+    /**
9
+     * The date & time the schedule is set to run.
10
+     *
11
+     * @var DateTime
12
+     */
13
+    private $scheduled_date = NULL;
14 14
 
15
-	/**
16
-	 * Timestamp equivalent of @see $this->scheduled_date
17
-	 *
18
-	 * @var int
19
-	 */
20
-	protected $scheduled_timestamp = NULL;
15
+    /**
16
+     * Timestamp equivalent of @see $this->scheduled_date
17
+     *
18
+     * @var int
19
+     */
20
+    protected $scheduled_timestamp = NULL;
21 21
 
22
-	/**
23
-	 * @param DateTime $date The date & time to run the action.
24
-	 */
25
-	public function __construct( DateTime $date ) {
26
-		$this->scheduled_date = $date;
27
-	}
22
+    /**
23
+     * @param DateTime $date The date & time to run the action.
24
+     */
25
+    public function __construct( DateTime $date ) {
26
+        $this->scheduled_date = $date;
27
+    }
28 28
 
29
-	/**
30
-	 * Check if a schedule should recur.
31
-	 *
32
-	 * @return bool
33
-	 */
34
-	abstract public function is_recurring();
29
+    /**
30
+     * Check if a schedule should recur.
31
+     *
32
+     * @return bool
33
+     */
34
+    abstract public function is_recurring();
35 35
 
36
-	/**
37
-	 * Calculate when the next instance of this schedule would run based on a given date & time.
38
-	 *
39
-	 * @param DateTime $after
40
-	 * @return DateTime
41
-	 */
42
-	abstract protected function calculate_next( DateTime $after );
36
+    /**
37
+     * Calculate when the next instance of this schedule would run based on a given date & time.
38
+     *
39
+     * @param DateTime $after
40
+     * @return DateTime
41
+     */
42
+    abstract protected function calculate_next( DateTime $after );
43 43
 
44
-	/**
45
-	 * Get the next date & time when this schedule should run after a given date & time.
46
-	 *
47
-	 * @param DateTime $after
48
-	 * @return DateTime|null
49
-	 */
50
-	public function get_next( DateTime $after ) {
51
-		$after = clone $after;
52
-		if ( $after > $this->scheduled_date ) {
53
-			$after = $this->calculate_next( $after );
54
-			return $after;
55
-		}
56
-		return clone $this->scheduled_date;
57
-	}
44
+    /**
45
+     * Get the next date & time when this schedule should run after a given date & time.
46
+     *
47
+     * @param DateTime $after
48
+     * @return DateTime|null
49
+     */
50
+    public function get_next( DateTime $after ) {
51
+        $after = clone $after;
52
+        if ( $after > $this->scheduled_date ) {
53
+            $after = $this->calculate_next( $after );
54
+            return $after;
55
+        }
56
+        return clone $this->scheduled_date;
57
+    }
58 58
 
59
-	/**
60
-	 * Get the date & time the schedule is set to run.
61
-	 *
62
-	 * @return DateTime|null
63
-	 */
64
-	public function get_date() {
65
-		return $this->scheduled_date;
66
-	}
59
+    /**
60
+     * Get the date & time the schedule is set to run.
61
+     *
62
+     * @return DateTime|null
63
+     */
64
+    public function get_date() {
65
+        return $this->scheduled_date;
66
+    }
67 67
 
68
-	/**
69
-	 * For PHP 5.2 compat, since DateTime objects can't be serialized
70
-	 * @return array
71
-	 */
72
-	public function __sleep() {
73
-		$this->scheduled_timestamp = $this->scheduled_date->getTimestamp();
74
-		return array(
75
-			'scheduled_timestamp',
76
-		);
77
-	}
68
+    /**
69
+     * For PHP 5.2 compat, since DateTime objects can't be serialized
70
+     * @return array
71
+     */
72
+    public function __sleep() {
73
+        $this->scheduled_timestamp = $this->scheduled_date->getTimestamp();
74
+        return array(
75
+            'scheduled_timestamp',
76
+        );
77
+    }
78 78
 
79
-	public function __wakeup() {
80
-		$this->scheduled_date = as_get_datetime_object( $this->scheduled_timestamp );
81
-		unset( $this->scheduled_timestamp );
82
-	}
79
+    public function __wakeup() {
80
+        $this->scheduled_date = as_get_datetime_object( $this->scheduled_timestamp );
81
+        unset( $this->scheduled_timestamp );
82
+    }
83 83
 }
Please login to merge, or discard this patch.