Passed
Push — main ( aaef5c...e4c121 )
by TARIQ
71:39
created
classes/data-stores/ActionScheduler_wpPostStore_PostTypeRegistrar.php 1 patch
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -5,46 +5,46 @@
 block discarded – undo
5 5
  * @codeCoverageIgnore
6 6
  */
7 7
 class ActionScheduler_wpPostStore_PostTypeRegistrar {
8
-	public function register() {
9
-		register_post_type( ActionScheduler_wpPostStore::POST_TYPE, $this->post_type_args() );
10
-	}
8
+    public function register() {
9
+        register_post_type( ActionScheduler_wpPostStore::POST_TYPE, $this->post_type_args() );
10
+    }
11 11
 
12
-	/**
13
-	 * Build the args array for the post type definition
14
-	 *
15
-	 * @return array
16
-	 */
17
-	protected function post_type_args() {
18
-		$args = array(
19
-			'label' => __( 'Scheduled Actions', 'woocommerce' ),
20
-			'description' => __( 'Scheduled actions are hooks triggered on a cetain date and time.', 'woocommerce' ),
21
-			'public' => false,
22
-			'map_meta_cap' => true,
23
-			'hierarchical' => false,
24
-			'supports' => array('title', 'editor','comments'),
25
-			'rewrite' => false,
26
-			'query_var' => false,
27
-			'can_export' => true,
28
-			'ep_mask' => EP_NONE,
29
-			'labels' => array(
30
-				'name' => __( 'Scheduled Actions', 'woocommerce' ),
31
-				'singular_name' => __( 'Scheduled Action', 'woocommerce' ),
32
-				'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'woocommerce' ),
33
-				'add_new' => __( 'Add', 'woocommerce' ),
34
-				'add_new_item' => __( 'Add New Scheduled Action', 'woocommerce' ),
35
-				'edit' => __( 'Edit', 'woocommerce' ),
36
-				'edit_item' => __( 'Edit Scheduled Action', 'woocommerce' ),
37
-				'new_item' => __( 'New Scheduled Action', 'woocommerce' ),
38
-				'view' => __( 'View Action', 'woocommerce' ),
39
-				'view_item' => __( 'View Action', 'woocommerce' ),
40
-				'search_items' => __( 'Search Scheduled Actions', 'woocommerce' ),
41
-				'not_found' => __( 'No actions found', 'woocommerce' ),
42
-				'not_found_in_trash' => __( 'No actions found in trash', 'woocommerce' ),
43
-			),
44
-		);
12
+    /**
13
+     * Build the args array for the post type definition
14
+     *
15
+     * @return array
16
+     */
17
+    protected function post_type_args() {
18
+        $args = array(
19
+            'label' => __( 'Scheduled Actions', 'woocommerce' ),
20
+            'description' => __( 'Scheduled actions are hooks triggered on a cetain date and time.', 'woocommerce' ),
21
+            'public' => false,
22
+            'map_meta_cap' => true,
23
+            'hierarchical' => false,
24
+            'supports' => array('title', 'editor','comments'),
25
+            'rewrite' => false,
26
+            'query_var' => false,
27
+            'can_export' => true,
28
+            'ep_mask' => EP_NONE,
29
+            'labels' => array(
30
+                'name' => __( 'Scheduled Actions', 'woocommerce' ),
31
+                'singular_name' => __( 'Scheduled Action', 'woocommerce' ),
32
+                'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'woocommerce' ),
33
+                'add_new' => __( 'Add', 'woocommerce' ),
34
+                'add_new_item' => __( 'Add New Scheduled Action', 'woocommerce' ),
35
+                'edit' => __( 'Edit', 'woocommerce' ),
36
+                'edit_item' => __( 'Edit Scheduled Action', 'woocommerce' ),
37
+                'new_item' => __( 'New Scheduled Action', 'woocommerce' ),
38
+                'view' => __( 'View Action', 'woocommerce' ),
39
+                'view_item' => __( 'View Action', 'woocommerce' ),
40
+                'search_items' => __( 'Search Scheduled Actions', 'woocommerce' ),
41
+                'not_found' => __( 'No actions found', 'woocommerce' ),
42
+                'not_found_in_trash' => __( 'No actions found in trash', 'woocommerce' ),
43
+            ),
44
+        );
45 45
 
46
-		$args = apply_filters('action_scheduler_post_type_args', $args);
47
-		return $args;
48
-	}
46
+        $args = apply_filters('action_scheduler_post_type_args', $args);
47
+        return $args;
48
+    }
49 49
 }
50
- 
51 50
\ No newline at end of file
51
+    
52 52
\ No newline at end of file
Please login to merge, or discard this patch.
packages/action-scheduler/classes/ActionScheduler_QueueRunner.php 1 patch
Indentation   +190 added lines, -190 removed lines patch added patch discarded remove patch
@@ -4,194 +4,194 @@
 block discarded – undo
4 4
  * Class ActionScheduler_QueueRunner
5 5
  */
6 6
 class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
7
-	const WP_CRON_HOOK = 'action_scheduler_run_queue';
8
-
9
-	const WP_CRON_SCHEDULE = 'every_minute';
10
-
11
-	/** @var ActionScheduler_AsyncRequest_QueueRunner */
12
-	protected $async_request;
13
-
14
-	/** @var ActionScheduler_QueueRunner  */
15
-	private static $runner = null;
16
-
17
-	/**
18
-	 * @return ActionScheduler_QueueRunner
19
-	 * @codeCoverageIgnore
20
-	 */
21
-	public static function instance() {
22
-		if ( empty(self::$runner) ) {
23
-			$class = apply_filters('action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner');
24
-			self::$runner = new $class();
25
-		}
26
-		return self::$runner;
27
-	}
28
-
29
-	/**
30
-	 * ActionScheduler_QueueRunner constructor.
31
-	 *
32
-	 * @param ActionScheduler_Store             $store
33
-	 * @param ActionScheduler_FatalErrorMonitor $monitor
34
-	 * @param ActionScheduler_QueueCleaner      $cleaner
35
-	 */
36
-	public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null, ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) {
37
-		parent::__construct( $store, $monitor, $cleaner );
38
-
39
-		if ( is_null( $async_request ) ) {
40
-			$async_request = new ActionScheduler_AsyncRequest_QueueRunner( $this->store );
41
-		}
42
-
43
-		$this->async_request = $async_request;
44
-	}
45
-
46
-	/**
47
-	 * @codeCoverageIgnore
48
-	 */
49
-	public function init() {
50
-
51
-		add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) );
52
-
53
-		// Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param
54
-		$next_timestamp = wp_next_scheduled( self::WP_CRON_HOOK );
55
-		if ( $next_timestamp ) {
56
-			wp_unschedule_event( $next_timestamp, self::WP_CRON_HOOK );
57
-		}
58
-
59
-		$cron_context = array( 'WP Cron' );
60
-
61
-		if ( ! wp_next_scheduled( self::WP_CRON_HOOK, $cron_context ) ) {
62
-			$schedule = apply_filters( 'action_scheduler_run_schedule', self::WP_CRON_SCHEDULE );
63
-			wp_schedule_event( time(), $schedule, self::WP_CRON_HOOK, $cron_context );
64
-		}
65
-
66
-		add_action( self::WP_CRON_HOOK, array( self::instance(), 'run' ) );
67
-		$this->hook_dispatch_async_request();
68
-	}
69
-
70
-	/**
71
-	 * Hook check for dispatching an async request.
72
-	 */
73
-	public function hook_dispatch_async_request() {
74
-		add_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
75
-	}
76
-
77
-	/**
78
-	 * Unhook check for dispatching an async request.
79
-	 */
80
-	public function unhook_dispatch_async_request() {
81
-		remove_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
82
-	}
83
-
84
-	/**
85
-	 * Check if we should dispatch an async request to process actions.
86
-	 *
87
-	 * This method is attached to 'shutdown', so is called frequently. To avoid slowing down
88
-	 * the site, it mitigates the work performed in each request by:
89
-	 * 1. checking if it's in the admin context and then
90
-	 * 2. haven't run on the 'shutdown' hook within the lock time (60 seconds by default)
91
-	 * 3. haven't exceeded the number of allowed batches.
92
-	 *
93
-	 * The order of these checks is important, because they run from a check on a value:
94
-	 * 1. in memory - is_admin() maps to $GLOBALS or the WP_ADMIN constant
95
-	 * 2. in memory - transients use autoloaded options by default
96
-	 * 3. from a database query - has_maximum_concurrent_batches() run the query
97
-	 *    $this->store->get_claim_count() to find the current number of claims in the DB.
98
-	 *
99
-	 * If all of these conditions are met, then we request an async runner check whether it
100
-	 * should dispatch a request to process pending actions.
101
-	 */
102
-	public function maybe_dispatch_async_request() {
103
-		if ( is_admin() && ! ActionScheduler::lock()->is_locked( 'async-request-runner' ) ) {
104
-			// Only start an async queue at most once every 60 seconds
105
-			ActionScheduler::lock()->set( 'async-request-runner' );
106
-			$this->async_request->maybe_dispatch();
107
-		}
108
-	}
109
-
110
-	/**
111
-	 * Process actions in the queue. Attached to self::WP_CRON_HOOK i.e. 'action_scheduler_run_queue'
112
-	 *
113
-	 * The $context param of this method defaults to 'WP Cron', because prior to Action Scheduler 3.0.0
114
-	 * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context
115
-	 * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK,
116
-	 * should set a context as the first parameter. For an example of this, refer to the code seen in
117
-	 * @see ActionScheduler_AsyncRequest_QueueRunner::handle()
118
-	 *
119
-	 * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
120
-	 *        Generally, this should be capitalised and not localised as it's a proper noun.
121
-	 * @return int The number of actions processed.
122
-	 */
123
-	public function run( $context = 'WP Cron' ) {
124
-		ActionScheduler_Compatibility::raise_memory_limit();
125
-		ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() );
126
-		do_action( 'action_scheduler_before_process_queue' );
127
-		$this->run_cleanup();
128
-		$processed_actions = 0;
129
-		if ( false === $this->has_maximum_concurrent_batches() ) {
130
-			$batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 );
131
-			do {
132
-				$processed_actions_in_batch = $this->do_batch( $batch_size, $context );
133
-				$processed_actions         += $processed_actions_in_batch;
134
-			} while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $processed_actions ) ); // keep going until we run out of actions, time, or memory
135
-		}
136
-
137
-		do_action( 'action_scheduler_after_process_queue' );
138
-		return $processed_actions;
139
-	}
140
-
141
-	/**
142
-	 * Process a batch of actions pending in the queue.
143
-	 *
144
-	 * Actions are processed by claiming a set of pending actions then processing each one until either the batch
145
-	 * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded().
146
-	 *
147
-	 * @param int $size The maximum number of actions to process in the batch.
148
-	 * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
149
-	 *        Generally, this should be capitalised and not localised as it's a proper noun.
150
-	 * @return int The number of actions processed.
151
-	 */
152
-	protected function do_batch( $size = 100, $context = '' ) {
153
-		$claim = $this->store->stake_claim($size);
154
-		$this->monitor->attach($claim);
155
-		$processed_actions = 0;
156
-
157
-		foreach ( $claim->get_actions() as $action_id ) {
158
-			// bail if we lost the claim
159
-			if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ) ) ) {
160
-				break;
161
-			}
162
-			$this->process_action( $action_id, $context );
163
-			$processed_actions++;
164
-
165
-			if ( $this->batch_limits_exceeded( $processed_actions ) ) {
166
-				break;
167
-			}
168
-		}
169
-		$this->store->release_claim($claim);
170
-		$this->monitor->detach();
171
-		$this->clear_caches();
172
-		return $processed_actions;
173
-	}
174
-
175
-	/**
176
-	 * Running large batches can eat up memory, as WP adds data to its object cache.
177
-	 *
178
-	 * If using a persistent object store, this has the side effect of flushing that
179
-	 * as well, so this is disabled by default. To enable:
180
-	 *
181
-	 * add_filter( 'action_scheduler_queue_runner_flush_cache', '__return_true' );
182
-	 */
183
-	protected function clear_caches() {
184
-		if ( ! wp_using_ext_object_cache() || apply_filters( 'action_scheduler_queue_runner_flush_cache', false ) ) {
185
-			wp_cache_flush();
186
-		}
187
-	}
188
-
189
-	public function add_wp_cron_schedule( $schedules ) {
190
-		$schedules['every_minute'] = array(
191
-			'interval' => 60, // in seconds
192
-			'display'  => __( 'Every minute', 'woocommerce' ),
193
-		);
194
-
195
-		return $schedules;
196
-	}
7
+    const WP_CRON_HOOK = 'action_scheduler_run_queue';
8
+
9
+    const WP_CRON_SCHEDULE = 'every_minute';
10
+
11
+    /** @var ActionScheduler_AsyncRequest_QueueRunner */
12
+    protected $async_request;
13
+
14
+    /** @var ActionScheduler_QueueRunner  */
15
+    private static $runner = null;
16
+
17
+    /**
18
+     * @return ActionScheduler_QueueRunner
19
+     * @codeCoverageIgnore
20
+     */
21
+    public static function instance() {
22
+        if ( empty(self::$runner) ) {
23
+            $class = apply_filters('action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner');
24
+            self::$runner = new $class();
25
+        }
26
+        return self::$runner;
27
+    }
28
+
29
+    /**
30
+     * ActionScheduler_QueueRunner constructor.
31
+     *
32
+     * @param ActionScheduler_Store             $store
33
+     * @param ActionScheduler_FatalErrorMonitor $monitor
34
+     * @param ActionScheduler_QueueCleaner      $cleaner
35
+     */
36
+    public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null, ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) {
37
+        parent::__construct( $store, $monitor, $cleaner );
38
+
39
+        if ( is_null( $async_request ) ) {
40
+            $async_request = new ActionScheduler_AsyncRequest_QueueRunner( $this->store );
41
+        }
42
+
43
+        $this->async_request = $async_request;
44
+    }
45
+
46
+    /**
47
+     * @codeCoverageIgnore
48
+     */
49
+    public function init() {
50
+
51
+        add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) );
52
+
53
+        // Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param
54
+        $next_timestamp = wp_next_scheduled( self::WP_CRON_HOOK );
55
+        if ( $next_timestamp ) {
56
+            wp_unschedule_event( $next_timestamp, self::WP_CRON_HOOK );
57
+        }
58
+
59
+        $cron_context = array( 'WP Cron' );
60
+
61
+        if ( ! wp_next_scheduled( self::WP_CRON_HOOK, $cron_context ) ) {
62
+            $schedule = apply_filters( 'action_scheduler_run_schedule', self::WP_CRON_SCHEDULE );
63
+            wp_schedule_event( time(), $schedule, self::WP_CRON_HOOK, $cron_context );
64
+        }
65
+
66
+        add_action( self::WP_CRON_HOOK, array( self::instance(), 'run' ) );
67
+        $this->hook_dispatch_async_request();
68
+    }
69
+
70
+    /**
71
+     * Hook check for dispatching an async request.
72
+     */
73
+    public function hook_dispatch_async_request() {
74
+        add_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
75
+    }
76
+
77
+    /**
78
+     * Unhook check for dispatching an async request.
79
+     */
80
+    public function unhook_dispatch_async_request() {
81
+        remove_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
82
+    }
83
+
84
+    /**
85
+     * Check if we should dispatch an async request to process actions.
86
+     *
87
+     * This method is attached to 'shutdown', so is called frequently. To avoid slowing down
88
+     * the site, it mitigates the work performed in each request by:
89
+     * 1. checking if it's in the admin context and then
90
+     * 2. haven't run on the 'shutdown' hook within the lock time (60 seconds by default)
91
+     * 3. haven't exceeded the number of allowed batches.
92
+     *
93
+     * The order of these checks is important, because they run from a check on a value:
94
+     * 1. in memory - is_admin() maps to $GLOBALS or the WP_ADMIN constant
95
+     * 2. in memory - transients use autoloaded options by default
96
+     * 3. from a database query - has_maximum_concurrent_batches() run the query
97
+     *    $this->store->get_claim_count() to find the current number of claims in the DB.
98
+     *
99
+     * If all of these conditions are met, then we request an async runner check whether it
100
+     * should dispatch a request to process pending actions.
101
+     */
102
+    public function maybe_dispatch_async_request() {
103
+        if ( is_admin() && ! ActionScheduler::lock()->is_locked( 'async-request-runner' ) ) {
104
+            // Only start an async queue at most once every 60 seconds
105
+            ActionScheduler::lock()->set( 'async-request-runner' );
106
+            $this->async_request->maybe_dispatch();
107
+        }
108
+    }
109
+
110
+    /**
111
+     * Process actions in the queue. Attached to self::WP_CRON_HOOK i.e. 'action_scheduler_run_queue'
112
+     *
113
+     * The $context param of this method defaults to 'WP Cron', because prior to Action Scheduler 3.0.0
114
+     * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context
115
+     * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK,
116
+     * should set a context as the first parameter. For an example of this, refer to the code seen in
117
+     * @see ActionScheduler_AsyncRequest_QueueRunner::handle()
118
+     *
119
+     * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
120
+     *        Generally, this should be capitalised and not localised as it's a proper noun.
121
+     * @return int The number of actions processed.
122
+     */
123
+    public function run( $context = 'WP Cron' ) {
124
+        ActionScheduler_Compatibility::raise_memory_limit();
125
+        ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() );
126
+        do_action( 'action_scheduler_before_process_queue' );
127
+        $this->run_cleanup();
128
+        $processed_actions = 0;
129
+        if ( false === $this->has_maximum_concurrent_batches() ) {
130
+            $batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 );
131
+            do {
132
+                $processed_actions_in_batch = $this->do_batch( $batch_size, $context );
133
+                $processed_actions         += $processed_actions_in_batch;
134
+            } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $processed_actions ) ); // keep going until we run out of actions, time, or memory
135
+        }
136
+
137
+        do_action( 'action_scheduler_after_process_queue' );
138
+        return $processed_actions;
139
+    }
140
+
141
+    /**
142
+     * Process a batch of actions pending in the queue.
143
+     *
144
+     * Actions are processed by claiming a set of pending actions then processing each one until either the batch
145
+     * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded().
146
+     *
147
+     * @param int $size The maximum number of actions to process in the batch.
148
+     * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
149
+     *        Generally, this should be capitalised and not localised as it's a proper noun.
150
+     * @return int The number of actions processed.
151
+     */
152
+    protected function do_batch( $size = 100, $context = '' ) {
153
+        $claim = $this->store->stake_claim($size);
154
+        $this->monitor->attach($claim);
155
+        $processed_actions = 0;
156
+
157
+        foreach ( $claim->get_actions() as $action_id ) {
158
+            // bail if we lost the claim
159
+            if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ) ) ) {
160
+                break;
161
+            }
162
+            $this->process_action( $action_id, $context );
163
+            $processed_actions++;
164
+
165
+            if ( $this->batch_limits_exceeded( $processed_actions ) ) {
166
+                break;
167
+            }
168
+        }
169
+        $this->store->release_claim($claim);
170
+        $this->monitor->detach();
171
+        $this->clear_caches();
172
+        return $processed_actions;
173
+    }
174
+
175
+    /**
176
+     * Running large batches can eat up memory, as WP adds data to its object cache.
177
+     *
178
+     * If using a persistent object store, this has the side effect of flushing that
179
+     * as well, so this is disabled by default. To enable:
180
+     *
181
+     * add_filter( 'action_scheduler_queue_runner_flush_cache', '__return_true' );
182
+     */
183
+    protected function clear_caches() {
184
+        if ( ! wp_using_ext_object_cache() || apply_filters( 'action_scheduler_queue_runner_flush_cache', false ) ) {
185
+            wp_cache_flush();
186
+        }
187
+    }
188
+
189
+    public function add_wp_cron_schedule( $schedules ) {
190
+        $schedules['every_minute'] = array(
191
+            'interval' => 60, // in seconds
192
+            'display'  => __( 'Every minute', 'woocommerce' ),
193
+        );
194
+
195
+        return $schedules;
196
+    }
197 197
 }
Please login to merge, or discard this patch.
packages/action-scheduler/classes/abstracts/ActionScheduler_Logger.php 1 patch
Indentation   +168 added lines, -168 removed lines patch added patch discarded remove patch
@@ -5,172 +5,172 @@
 block discarded – undo
5 5
  * @codeCoverageIgnore
6 6
  */
7 7
 abstract class ActionScheduler_Logger {
8
-	private static $logger = NULL;
9
-
10
-	/**
11
-	 * @return ActionScheduler_Logger
12
-	 */
13
-	public static function instance() {
14
-		if ( empty(self::$logger) ) {
15
-			$class = apply_filters('action_scheduler_logger_class', 'ActionScheduler_wpCommentLogger');
16
-			self::$logger = new $class();
17
-		}
18
-		return self::$logger;
19
-	}
20
-
21
-	/**
22
-	 * @param string $action_id
23
-	 * @param string $message
24
-	 * @param DateTime $date
25
-	 *
26
-	 * @return string The log entry ID
27
-	 */
28
-	abstract public function log( $action_id, $message, DateTime $date = NULL );
29
-
30
-	/**
31
-	 * @param string $entry_id
32
-	 *
33
-	 * @return ActionScheduler_LogEntry
34
-	 */
35
-	abstract public function get_entry( $entry_id );
36
-
37
-	/**
38
-	 * @param string $action_id
39
-	 *
40
-	 * @return ActionScheduler_LogEntry[]
41
-	 */
42
-	abstract public function get_logs( $action_id );
43
-
44
-
45
-	/**
46
-	 * @codeCoverageIgnore
47
-	 */
48
-	public function init() {
49
-		$this->hook_stored_action();
50
-		add_action( 'action_scheduler_canceled_action', array( $this, 'log_canceled_action' ), 10, 1 );
51
-		add_action( 'action_scheduler_begin_execute', array( $this, 'log_started_action' ), 10, 2 );
52
-		add_action( 'action_scheduler_after_execute', array( $this, 'log_completed_action' ), 10, 3 );
53
-		add_action( 'action_scheduler_failed_execution', array( $this, 'log_failed_action' ), 10, 3 );
54
-		add_action( 'action_scheduler_failed_action', array( $this, 'log_timed_out_action' ), 10, 2 );
55
-		add_action( 'action_scheduler_unexpected_shutdown', array( $this, 'log_unexpected_shutdown' ), 10, 2 );
56
-		add_action( 'action_scheduler_reset_action', array( $this, 'log_reset_action' ), 10, 1 );
57
-		add_action( 'action_scheduler_execution_ignored', array( $this, 'log_ignored_action' ), 10, 2 );
58
-		add_action( 'action_scheduler_failed_fetch_action', array( $this, 'log_failed_fetch_action' ), 10, 2 );
59
-		add_action( 'action_scheduler_failed_to_schedule_next_instance', array( $this, 'log_failed_schedule_next_instance' ), 10, 2 );
60
-		add_action( 'action_scheduler_bulk_cancel_actions', array( $this, 'bulk_log_cancel_actions' ), 10, 1 );
61
-	}
62
-
63
-	public function hook_stored_action() {
64
-		add_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) );
65
-	}
66
-
67
-	public function unhook_stored_action() {
68
-		remove_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) );
69
-	}
70
-
71
-	public function log_stored_action( $action_id ) {
72
-		$this->log( $action_id, __( 'action created', 'woocommerce' ) );
73
-	}
74
-
75
-	public function log_canceled_action( $action_id ) {
76
-		$this->log( $action_id, __( 'action canceled', 'woocommerce' ) );
77
-	}
78
-
79
-	public function log_started_action( $action_id, $context = '' ) {
80
-		if ( ! empty( $context ) ) {
81
-			/* translators: %s: context */
82
-			$message = sprintf( __( 'action started via %s', 'woocommerce' ), $context );
83
-		} else {
84
-			$message = __( 'action started', 'woocommerce' );
85
-		}
86
-		$this->log( $action_id, $message );
87
-	}
88
-
89
-	public function log_completed_action( $action_id, $action = NULL, $context = '' ) {
90
-		if ( ! empty( $context ) ) {
91
-			/* translators: %s: context */
92
-			$message = sprintf( __( 'action complete via %s', 'woocommerce' ), $context );
93
-		} else {
94
-			$message = __( 'action complete', 'woocommerce' );
95
-		}
96
-		$this->log( $action_id, $message );
97
-	}
98
-
99
-	public function log_failed_action( $action_id, Exception $exception, $context = '' ) {
100
-		if ( ! empty( $context ) ) {
101
-			/* translators: 1: context 2: exception message */
102
-			$message = sprintf( __( 'action failed via %1$s: %2$s', 'woocommerce' ), $context, $exception->getMessage() );
103
-		} else {
104
-			/* translators: %s: exception message */
105
-			$message = sprintf( __( 'action failed: %s', 'woocommerce' ), $exception->getMessage() );
106
-		}
107
-		$this->log( $action_id, $message );
108
-	}
109
-
110
-	public function log_timed_out_action( $action_id, $timeout ) {
111
-		/* translators: %s: amount of time */
112
-		$this->log( $action_id, sprintf( __( 'action marked as failed after %s seconds. Unknown error occurred. Check server, PHP and database error logs to diagnose cause.', 'woocommerce' ), $timeout ) );
113
-	}
114
-
115
-	public function log_unexpected_shutdown( $action_id, $error ) {
116
-		if ( ! empty( $error ) ) {
117
-			/* translators: 1: error message 2: filename 3: line */
118
-			$this->log( $action_id, sprintf( __( 'unexpected shutdown: PHP Fatal error %1$s in %2$s on line %3$s', 'woocommerce' ), $error['message'], $error['file'], $error['line'] ) );
119
-		}
120
-	}
121
-
122
-	public function log_reset_action( $action_id ) {
123
-		$this->log( $action_id, __( 'action reset', 'woocommerce' ) );
124
-	}
125
-
126
-	public function log_ignored_action( $action_id, $context = '' ) {
127
-		if ( ! empty( $context ) ) {
128
-			/* translators: %s: context */
129
-			$message = sprintf( __( 'action ignored via %s', 'woocommerce' ), $context );
130
-		} else {
131
-			$message = __( 'action ignored', 'woocommerce' );
132
-		}
133
-		$this->log( $action_id, $message );
134
-	}
135
-
136
-	/**
137
-	 * @param string $action_id
138
-	 * @param Exception|NULL $exception The exception which occured when fetching the action. NULL by default for backward compatibility.
139
-	 *
140
-	 * @return ActionScheduler_LogEntry[]
141
-	 */
142
-	public function log_failed_fetch_action( $action_id, Exception $exception = NULL ) {
143
-
144
-		if ( ! is_null( $exception ) ) {
145
-			/* translators: %s: exception message */
146
-			$log_message = sprintf( __( 'There was a failure fetching this action: %s', 'woocommerce' ), $exception->getMessage() );
147
-		} else {
148
-			$log_message = __( 'There was a failure fetching this action', 'woocommerce' );
149
-		}
150
-
151
-		$this->log( $action_id, $log_message );
152
-	}
153
-
154
-	public function log_failed_schedule_next_instance( $action_id, Exception $exception ) {
155
-		/* translators: %s: exception message */
156
-		$this->log( $action_id, sprintf( __( 'There was a failure scheduling the next instance of this action: %s', 'woocommerce' ), $exception->getMessage() ) );
157
-	}
158
-
159
-	/**
160
-	 * Bulk add cancel action log entries.
161
-	 *
162
-	 * Implemented here for backward compatibility. Should be implemented in parent loggers
163
-	 * for more performant bulk logging.
164
-	 *
165
-	 * @param array $action_ids List of action ID.
166
-	 */
167
-	public function bulk_log_cancel_actions( $action_ids ) {
168
-		if ( empty( $action_ids ) ) {
169
-			return;
170
-		}
171
-
172
-		foreach ( $action_ids as $action_id ) {
173
-			$this->log_canceled_action( $action_id );
174
-		}
175
-	}
8
+    private static $logger = NULL;
9
+
10
+    /**
11
+     * @return ActionScheduler_Logger
12
+     */
13
+    public static function instance() {
14
+        if ( empty(self::$logger) ) {
15
+            $class = apply_filters('action_scheduler_logger_class', 'ActionScheduler_wpCommentLogger');
16
+            self::$logger = new $class();
17
+        }
18
+        return self::$logger;
19
+    }
20
+
21
+    /**
22
+     * @param string $action_id
23
+     * @param string $message
24
+     * @param DateTime $date
25
+     *
26
+     * @return string The log entry ID
27
+     */
28
+    abstract public function log( $action_id, $message, DateTime $date = NULL );
29
+
30
+    /**
31
+     * @param string $entry_id
32
+     *
33
+     * @return ActionScheduler_LogEntry
34
+     */
35
+    abstract public function get_entry( $entry_id );
36
+
37
+    /**
38
+     * @param string $action_id
39
+     *
40
+     * @return ActionScheduler_LogEntry[]
41
+     */
42
+    abstract public function get_logs( $action_id );
43
+
44
+
45
+    /**
46
+     * @codeCoverageIgnore
47
+     */
48
+    public function init() {
49
+        $this->hook_stored_action();
50
+        add_action( 'action_scheduler_canceled_action', array( $this, 'log_canceled_action' ), 10, 1 );
51
+        add_action( 'action_scheduler_begin_execute', array( $this, 'log_started_action' ), 10, 2 );
52
+        add_action( 'action_scheduler_after_execute', array( $this, 'log_completed_action' ), 10, 3 );
53
+        add_action( 'action_scheduler_failed_execution', array( $this, 'log_failed_action' ), 10, 3 );
54
+        add_action( 'action_scheduler_failed_action', array( $this, 'log_timed_out_action' ), 10, 2 );
55
+        add_action( 'action_scheduler_unexpected_shutdown', array( $this, 'log_unexpected_shutdown' ), 10, 2 );
56
+        add_action( 'action_scheduler_reset_action', array( $this, 'log_reset_action' ), 10, 1 );
57
+        add_action( 'action_scheduler_execution_ignored', array( $this, 'log_ignored_action' ), 10, 2 );
58
+        add_action( 'action_scheduler_failed_fetch_action', array( $this, 'log_failed_fetch_action' ), 10, 2 );
59
+        add_action( 'action_scheduler_failed_to_schedule_next_instance', array( $this, 'log_failed_schedule_next_instance' ), 10, 2 );
60
+        add_action( 'action_scheduler_bulk_cancel_actions', array( $this, 'bulk_log_cancel_actions' ), 10, 1 );
61
+    }
62
+
63
+    public function hook_stored_action() {
64
+        add_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) );
65
+    }
66
+
67
+    public function unhook_stored_action() {
68
+        remove_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) );
69
+    }
70
+
71
+    public function log_stored_action( $action_id ) {
72
+        $this->log( $action_id, __( 'action created', 'woocommerce' ) );
73
+    }
74
+
75
+    public function log_canceled_action( $action_id ) {
76
+        $this->log( $action_id, __( 'action canceled', 'woocommerce' ) );
77
+    }
78
+
79
+    public function log_started_action( $action_id, $context = '' ) {
80
+        if ( ! empty( $context ) ) {
81
+            /* translators: %s: context */
82
+            $message = sprintf( __( 'action started via %s', 'woocommerce' ), $context );
83
+        } else {
84
+            $message = __( 'action started', 'woocommerce' );
85
+        }
86
+        $this->log( $action_id, $message );
87
+    }
88
+
89
+    public function log_completed_action( $action_id, $action = NULL, $context = '' ) {
90
+        if ( ! empty( $context ) ) {
91
+            /* translators: %s: context */
92
+            $message = sprintf( __( 'action complete via %s', 'woocommerce' ), $context );
93
+        } else {
94
+            $message = __( 'action complete', 'woocommerce' );
95
+        }
96
+        $this->log( $action_id, $message );
97
+    }
98
+
99
+    public function log_failed_action( $action_id, Exception $exception, $context = '' ) {
100
+        if ( ! empty( $context ) ) {
101
+            /* translators: 1: context 2: exception message */
102
+            $message = sprintf( __( 'action failed via %1$s: %2$s', 'woocommerce' ), $context, $exception->getMessage() );
103
+        } else {
104
+            /* translators: %s: exception message */
105
+            $message = sprintf( __( 'action failed: %s', 'woocommerce' ), $exception->getMessage() );
106
+        }
107
+        $this->log( $action_id, $message );
108
+    }
109
+
110
+    public function log_timed_out_action( $action_id, $timeout ) {
111
+        /* translators: %s: amount of time */
112
+        $this->log( $action_id, sprintf( __( 'action marked as failed after %s seconds. Unknown error occurred. Check server, PHP and database error logs to diagnose cause.', 'woocommerce' ), $timeout ) );
113
+    }
114
+
115
+    public function log_unexpected_shutdown( $action_id, $error ) {
116
+        if ( ! empty( $error ) ) {
117
+            /* translators: 1: error message 2: filename 3: line */
118
+            $this->log( $action_id, sprintf( __( 'unexpected shutdown: PHP Fatal error %1$s in %2$s on line %3$s', 'woocommerce' ), $error['message'], $error['file'], $error['line'] ) );
119
+        }
120
+    }
121
+
122
+    public function log_reset_action( $action_id ) {
123
+        $this->log( $action_id, __( 'action reset', 'woocommerce' ) );
124
+    }
125
+
126
+    public function log_ignored_action( $action_id, $context = '' ) {
127
+        if ( ! empty( $context ) ) {
128
+            /* translators: %s: context */
129
+            $message = sprintf( __( 'action ignored via %s', 'woocommerce' ), $context );
130
+        } else {
131
+            $message = __( 'action ignored', 'woocommerce' );
132
+        }
133
+        $this->log( $action_id, $message );
134
+    }
135
+
136
+    /**
137
+     * @param string $action_id
138
+     * @param Exception|NULL $exception The exception which occured when fetching the action. NULL by default for backward compatibility.
139
+     *
140
+     * @return ActionScheduler_LogEntry[]
141
+     */
142
+    public function log_failed_fetch_action( $action_id, Exception $exception = NULL ) {
143
+
144
+        if ( ! is_null( $exception ) ) {
145
+            /* translators: %s: exception message */
146
+            $log_message = sprintf( __( 'There was a failure fetching this action: %s', 'woocommerce' ), $exception->getMessage() );
147
+        } else {
148
+            $log_message = __( 'There was a failure fetching this action', 'woocommerce' );
149
+        }
150
+
151
+        $this->log( $action_id, $log_message );
152
+    }
153
+
154
+    public function log_failed_schedule_next_instance( $action_id, Exception $exception ) {
155
+        /* translators: %s: exception message */
156
+        $this->log( $action_id, sprintf( __( 'There was a failure scheduling the next instance of this action: %s', 'woocommerce' ), $exception->getMessage() ) );
157
+    }
158
+
159
+    /**
160
+     * Bulk add cancel action log entries.
161
+     *
162
+     * Implemented here for backward compatibility. Should be implemented in parent loggers
163
+     * for more performant bulk logging.
164
+     *
165
+     * @param array $action_ids List of action ID.
166
+     */
167
+    public function bulk_log_cancel_actions( $action_ids ) {
168
+        if ( empty( $action_ids ) ) {
169
+            return;
170
+        }
171
+
172
+        foreach ( $action_ids as $action_id ) {
173
+            $this->log_canceled_action( $action_id );
174
+        }
175
+    }
176 176
 }
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.
packages/action-scheduler/classes/abstracts/ActionScheduler_Store.php 1 patch
Indentation   +414 added lines, -414 removed lines patch added patch discarded remove patch
@@ -5,418 +5,418 @@
 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 int 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
-	 * Find an action.
40
-	 *
41
-	 * Note: the query ordering changes based on the passed 'status' value.
42
-	 *
43
-	 * @param string $hook Action hook.
44
-	 * @param array  $params Parameters of the action to find.
45
-	 *
46
-	 * @return string|null ID of the next action matching the criteria or NULL if not found.
47
-	 */
48
-	public function find_action( $hook, $params = array() ) {
49
-		$params = wp_parse_args(
50
-			$params,
51
-			array(
52
-				'args'   => null,
53
-				'status' => self::STATUS_PENDING,
54
-				'group'  => '',
55
-			)
56
-		);
57
-
58
-		// These params are fixed for this method.
59
-		$params['hook']     = $hook;
60
-		$params['orderby']  = 'date';
61
-		$params['per_page'] = 1;
62
-
63
-		if ( ! empty( $params['status'] ) ) {
64
-			if ( self::STATUS_PENDING === $params['status'] ) {
65
-				$params['order'] = 'ASC'; // Find the next action that matches.
66
-			} else {
67
-				$params['order'] = 'DESC'; // Find the most recent action that matches.
68
-			}
69
-		}
70
-
71
-		$results = $this->query_actions( $params );
72
-
73
-		return empty( $results ) ? null : $results[0];
74
-	}
75
-
76
-	/**
77
-	 * Query for action count or list of action IDs.
78
-	 *
79
-	 * @since x.x.x $query['status'] accepts array of statuses instead of a single status.
80
-	 *
81
-	 * @param array  $query {
82
-	 *      Query filtering options.
83
-	 *
84
-	 *      @type string       $hook             The name of the actions. Optional.
85
-	 *      @type string|array $status           The status or statuses of the actions. Optional.
86
-	 *      @type array        $args             The args array of the actions. Optional.
87
-	 *      @type DateTime     $date             The scheduled date of the action. Used in UTC timezone. Optional.
88
-	 *      @type string       $date_compare     Operator for selecting by $date param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='.
89
-	 *      @type DateTime     $modified         The last modified date of the action. Used in UTC timezone. Optional.
90
-	 *      @type string       $modified_compare Operator for comparing $modified param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='.
91
-	 *      @type string       $group            The group the action belongs to. Optional.
92
-	 *      @type bool|int     $claimed          TRUE to find claimed actions, FALSE to find unclaimed actions, an int to find a specific claim ID. Optional.
93
-	 *      @type int          $per_page         Number of results to return. Defaults to 5.
94
-	 *      @type int          $offset           The query pagination offset. Defaults to 0.
95
-	 *      @type int          $orderby          Accepted values are 'hook', 'group', 'modified', 'date' or 'none'. Defaults to 'date'.
96
-	 *      @type string       $order            Accepted values are 'ASC' or 'DESC'. Defaults to 'ASC'.
97
-	 * }
98
-	 * @param string $query_type Whether to select or count the results. Default, select.
99
-	 *
100
-	 * @return string|array|null The IDs of actions matching the query. Null on failure.
101
-	 */
102
-	abstract public function query_actions( $query = array(), $query_type = 'select' );
103
-
104
-	/**
105
-	 * Run query to get a single action ID.
106
-	 *
107
-	 * @since x.x.x
108
-	 *
109
-	 * @see ActionScheduler_Store::query_actions for $query arg usage but 'per_page' and 'offset' can't be used.
110
-	 *
111
-	 * @param array $query Query parameters.
112
-	 *
113
-	 * @return int|null
114
-	 */
115
-	public function query_action( $query ) {
116
-		$query['per_page'] = 1;
117
-		$query['offset']   = 0;
118
-		$results           = $this->query_actions( $query );
119
-
120
-		if ( empty( $results ) ) {
121
-			return null;
122
-		} else {
123
-			return (int) $results[0];
124
-		}
125
-	}
126
-
127
-	/**
128
-	 * Get a count of all actions in the store, grouped by status
129
-	 *
130
-	 * @return array
131
-	 */
132
-	abstract public function action_counts();
133
-
134
-	/**
135
-	 * @param string $action_id
136
-	 */
137
-	abstract public function cancel_action( $action_id );
138
-
139
-	/**
140
-	 * @param string $action_id
141
-	 */
142
-	abstract public function delete_action( $action_id );
143
-
144
-	/**
145
-	 * @param string $action_id
146
-	 *
147
-	 * @return DateTime The date the action is schedule to run, or the date that it ran.
148
-	 */
149
-	abstract public function get_date( $action_id );
150
-
151
-
152
-	/**
153
-	 * @param int      $max_actions
154
-	 * @param DateTime $before_date Claim only actions schedule before the given date. Defaults to now.
155
-	 * @param array    $hooks       Claim only actions with a hook or hooks.
156
-	 * @param string   $group       Claim only actions in the given group.
157
-	 *
158
-	 * @return ActionScheduler_ActionClaim
159
-	 */
160
-	abstract public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' );
161
-
162
-	/**
163
-	 * @return int
164
-	 */
165
-	abstract public function get_claim_count();
166
-
167
-	/**
168
-	 * @param ActionScheduler_ActionClaim $claim
169
-	 */
170
-	abstract public function release_claim( ActionScheduler_ActionClaim $claim );
171
-
172
-	/**
173
-	 * @param string $action_id
174
-	 */
175
-	abstract public function unclaim_action( $action_id );
176
-
177
-	/**
178
-	 * @param string $action_id
179
-	 */
180
-	abstract public function mark_failure( $action_id );
181
-
182
-	/**
183
-	 * @param string $action_id
184
-	 */
185
-	abstract public function log_execution( $action_id );
186
-
187
-	/**
188
-	 * @param string $action_id
189
-	 */
190
-	abstract public function mark_complete( $action_id );
191
-
192
-	/**
193
-	 * @param string $action_id
194
-	 *
195
-	 * @return string
196
-	 */
197
-	abstract public function get_status( $action_id );
198
-
199
-	/**
200
-	 * @param string $action_id
201
-	 * @return mixed
202
-	 */
203
-	abstract public function get_claim_id( $action_id );
204
-
205
-	/**
206
-	 * @param string $claim_id
207
-	 * @return array
208
-	 */
209
-	abstract public function find_actions_by_claim_id( $claim_id );
210
-
211
-	/**
212
-	 * @param string $comparison_operator
213
-	 * @return string
214
-	 */
215
-	protected function validate_sql_comparator( $comparison_operator ) {
216
-		if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) {
217
-			return $comparison_operator;
218
-		}
219
-		return '=';
220
-	}
221
-
222
-	/**
223
-	 * Get the time MySQL formated date/time string for an action's (next) scheduled date.
224
-	 *
225
-	 * @param ActionScheduler_Action $action
226
-	 * @param DateTime $scheduled_date (optional)
227
-	 * @return string
228
-	 */
229
-	protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
230
-		$next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
231
-		if ( ! $next ) {
232
-			return '0000-00-00 00:00:00';
233
-		}
234
-		$next->setTimezone( new DateTimeZone( 'UTC' ) );
235
-
236
-		return $next->format( 'Y-m-d H:i:s' );
237
-	}
238
-
239
-	/**
240
-	 * Get the time MySQL formated date/time string for an action's (next) scheduled date.
241
-	 *
242
-	 * @param ActionScheduler_Action $action
243
-	 * @param DateTime $scheduled_date (optional)
244
-	 * @return string
245
-	 */
246
-	protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
247
-		$next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
248
-		if ( ! $next ) {
249
-			return '0000-00-00 00:00:00';
250
-		}
251
-
252
-		ActionScheduler_TimezoneHelper::set_local_timezone( $next );
253
-		return $next->format( 'Y-m-d H:i:s' );
254
-	}
255
-
256
-	/**
257
-	 * Validate that we could decode action arguments.
258
-	 *
259
-	 * @param mixed $args      The decoded arguments.
260
-	 * @param int   $action_id The action ID.
261
-	 *
262
-	 * @throws ActionScheduler_InvalidActionException When the decoded arguments are invalid.
263
-	 */
264
-	protected function validate_args( $args, $action_id ) {
265
-		// Ensure we have an array of args.
266
-		if ( ! is_array( $args ) ) {
267
-			throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id );
268
-		}
269
-
270
-		// Validate JSON decoding if possible.
271
-		if ( function_exists( 'json_last_error' ) && JSON_ERROR_NONE !== json_last_error() ) {
272
-			throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id, $args );
273
-		}
274
-	}
275
-
276
-	/**
277
-	 * Validate a ActionScheduler_Schedule object.
278
-	 *
279
-	 * @param mixed $schedule  The unserialized ActionScheduler_Schedule object.
280
-	 * @param int   $action_id The action ID.
281
-	 *
282
-	 * @throws ActionScheduler_InvalidActionException When the schedule is invalid.
283
-	 */
284
-	protected function validate_schedule( $schedule, $action_id ) {
285
-		if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) {
286
-			throw ActionScheduler_InvalidActionException::from_schedule( $action_id, $schedule );
287
-		}
288
-	}
289
-
290
-	/**
291
-	 * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
292
-	 *
293
-	 * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
294
-	 * with custom tables, we use an indexed VARCHAR column instead.
295
-	 *
296
-	 * @param  ActionScheduler_Action $action Action to be validated.
297
-	 * @throws InvalidArgumentException When json encoded args is too long.
298
-	 */
299
-	protected function validate_action( ActionScheduler_Action $action ) {
300
-		if ( strlen( json_encode( $action->get_args() ) ) > static::$max_args_length ) {
301
-			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.', 'woocommerce' ), static::$max_args_length ) );
302
-		}
303
-	}
304
-
305
-	/**
306
-	 * Cancel pending actions by hook.
307
-	 *
308
-	 * @since 3.0.0
309
-	 *
310
-	 * @param string $hook Hook name.
311
-	 *
312
-	 * @return void
313
-	 */
314
-	public function cancel_actions_by_hook( $hook ) {
315
-		$action_ids = true;
316
-		while ( ! empty( $action_ids ) ) {
317
-			$action_ids = $this->query_actions(
318
-				array(
319
-					'hook'     => $hook,
320
-					'status'   => self::STATUS_PENDING,
321
-					'per_page' => 1000,
322
-					'orderby'  => 'action_id',
323
-				)
324
-			);
325
-
326
-			$this->bulk_cancel_actions( $action_ids );
327
-		}
328
-	}
329
-
330
-	/**
331
-	 * Cancel pending actions by group.
332
-	 *
333
-	 * @since 3.0.0
334
-	 *
335
-	 * @param string $group Group slug.
336
-	 *
337
-	 * @return void
338
-	 */
339
-	public function cancel_actions_by_group( $group ) {
340
-		$action_ids = true;
341
-		while ( ! empty( $action_ids ) ) {
342
-			$action_ids = $this->query_actions(
343
-				array(
344
-					'group'    => $group,
345
-					'status'   => self::STATUS_PENDING,
346
-					'per_page' => 1000,
347
-					'orderby'  => 'action_id',
348
-				)
349
-			);
350
-
351
-			$this->bulk_cancel_actions( $action_ids );
352
-		}
353
-	}
354
-
355
-	/**
356
-	 * Cancel a set of action IDs.
357
-	 *
358
-	 * @since 3.0.0
359
-	 *
360
-	 * @param array $action_ids List of action IDs.
361
-	 *
362
-	 * @return void
363
-	 */
364
-	private function bulk_cancel_actions( $action_ids ) {
365
-		foreach ( $action_ids as $action_id ) {
366
-			$this->cancel_action( $action_id );
367
-		}
368
-
369
-		do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
370
-	}
371
-
372
-	/**
373
-	 * @return array
374
-	 */
375
-	public function get_status_labels() {
376
-		return array(
377
-			self::STATUS_COMPLETE => __( 'Complete', 'woocommerce' ),
378
-			self::STATUS_PENDING  => __( 'Pending', 'woocommerce' ),
379
-			self::STATUS_RUNNING  => __( 'In-progress', 'woocommerce' ),
380
-			self::STATUS_FAILED   => __( 'Failed', 'woocommerce' ),
381
-			self::STATUS_CANCELED => __( 'Canceled', 'woocommerce' ),
382
-		);
383
-	}
384
-
385
-	/**
386
-	 * Check if there are any pending scheduled actions due to run.
387
-	 *
388
-	 * @param ActionScheduler_Action $action
389
-	 * @param DateTime $scheduled_date (optional)
390
-	 * @return string
391
-	 */
392
-	public function has_pending_actions_due() {
393
-		$pending_actions = $this->query_actions( array(
394
-			'date'    => as_get_datetime_object(),
395
-			'status'  => ActionScheduler_Store::STATUS_PENDING,
396
-			'orderby' => 'none',
397
-		) );
398
-
399
-		return ! empty( $pending_actions );
400
-	}
401
-
402
-	/**
403
-	 * Callable initialization function optionally overridden in derived classes.
404
-	 */
405
-	public function init() {}
406
-
407
-	/**
408
-	 * Callable function to mark an action as migrated optionally overridden in derived classes.
409
-	 */
410
-	public function mark_migrated( $action_id ) {}
411
-
412
-	/**
413
-	 * @return ActionScheduler_Store
414
-	 */
415
-	public static function instance() {
416
-		if ( empty( self::$store ) ) {
417
-			$class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS );
418
-			self::$store = new $class();
419
-		}
420
-		return self::$store;
421
-	}
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 int 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
+     * Find an action.
40
+     *
41
+     * Note: the query ordering changes based on the passed 'status' value.
42
+     *
43
+     * @param string $hook Action hook.
44
+     * @param array  $params Parameters of the action to find.
45
+     *
46
+     * @return string|null ID of the next action matching the criteria or NULL if not found.
47
+     */
48
+    public function find_action( $hook, $params = array() ) {
49
+        $params = wp_parse_args(
50
+            $params,
51
+            array(
52
+                'args'   => null,
53
+                'status' => self::STATUS_PENDING,
54
+                'group'  => '',
55
+            )
56
+        );
57
+
58
+        // These params are fixed for this method.
59
+        $params['hook']     = $hook;
60
+        $params['orderby']  = 'date';
61
+        $params['per_page'] = 1;
62
+
63
+        if ( ! empty( $params['status'] ) ) {
64
+            if ( self::STATUS_PENDING === $params['status'] ) {
65
+                $params['order'] = 'ASC'; // Find the next action that matches.
66
+            } else {
67
+                $params['order'] = 'DESC'; // Find the most recent action that matches.
68
+            }
69
+        }
70
+
71
+        $results = $this->query_actions( $params );
72
+
73
+        return empty( $results ) ? null : $results[0];
74
+    }
75
+
76
+    /**
77
+     * Query for action count or list of action IDs.
78
+     *
79
+     * @since x.x.x $query['status'] accepts array of statuses instead of a single status.
80
+     *
81
+     * @param array  $query {
82
+     *      Query filtering options.
83
+     *
84
+     *      @type string       $hook             The name of the actions. Optional.
85
+     *      @type string|array $status           The status or statuses of the actions. Optional.
86
+     *      @type array        $args             The args array of the actions. Optional.
87
+     *      @type DateTime     $date             The scheduled date of the action. Used in UTC timezone. Optional.
88
+     *      @type string       $date_compare     Operator for selecting by $date param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='.
89
+     *      @type DateTime     $modified         The last modified date of the action. Used in UTC timezone. Optional.
90
+     *      @type string       $modified_compare Operator for comparing $modified param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='.
91
+     *      @type string       $group            The group the action belongs to. Optional.
92
+     *      @type bool|int     $claimed          TRUE to find claimed actions, FALSE to find unclaimed actions, an int to find a specific claim ID. Optional.
93
+     *      @type int          $per_page         Number of results to return. Defaults to 5.
94
+     *      @type int          $offset           The query pagination offset. Defaults to 0.
95
+     *      @type int          $orderby          Accepted values are 'hook', 'group', 'modified', 'date' or 'none'. Defaults to 'date'.
96
+     *      @type string       $order            Accepted values are 'ASC' or 'DESC'. Defaults to 'ASC'.
97
+     * }
98
+     * @param string $query_type Whether to select or count the results. Default, select.
99
+     *
100
+     * @return string|array|null The IDs of actions matching the query. Null on failure.
101
+     */
102
+    abstract public function query_actions( $query = array(), $query_type = 'select' );
103
+
104
+    /**
105
+     * Run query to get a single action ID.
106
+     *
107
+     * @since x.x.x
108
+     *
109
+     * @see ActionScheduler_Store::query_actions for $query arg usage but 'per_page' and 'offset' can't be used.
110
+     *
111
+     * @param array $query Query parameters.
112
+     *
113
+     * @return int|null
114
+     */
115
+    public function query_action( $query ) {
116
+        $query['per_page'] = 1;
117
+        $query['offset']   = 0;
118
+        $results           = $this->query_actions( $query );
119
+
120
+        if ( empty( $results ) ) {
121
+            return null;
122
+        } else {
123
+            return (int) $results[0];
124
+        }
125
+    }
126
+
127
+    /**
128
+     * Get a count of all actions in the store, grouped by status
129
+     *
130
+     * @return array
131
+     */
132
+    abstract public function action_counts();
133
+
134
+    /**
135
+     * @param string $action_id
136
+     */
137
+    abstract public function cancel_action( $action_id );
138
+
139
+    /**
140
+     * @param string $action_id
141
+     */
142
+    abstract public function delete_action( $action_id );
143
+
144
+    /**
145
+     * @param string $action_id
146
+     *
147
+     * @return DateTime The date the action is schedule to run, or the date that it ran.
148
+     */
149
+    abstract public function get_date( $action_id );
150
+
151
+
152
+    /**
153
+     * @param int      $max_actions
154
+     * @param DateTime $before_date Claim only actions schedule before the given date. Defaults to now.
155
+     * @param array    $hooks       Claim only actions with a hook or hooks.
156
+     * @param string   $group       Claim only actions in the given group.
157
+     *
158
+     * @return ActionScheduler_ActionClaim
159
+     */
160
+    abstract public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' );
161
+
162
+    /**
163
+     * @return int
164
+     */
165
+    abstract public function get_claim_count();
166
+
167
+    /**
168
+     * @param ActionScheduler_ActionClaim $claim
169
+     */
170
+    abstract public function release_claim( ActionScheduler_ActionClaim $claim );
171
+
172
+    /**
173
+     * @param string $action_id
174
+     */
175
+    abstract public function unclaim_action( $action_id );
176
+
177
+    /**
178
+     * @param string $action_id
179
+     */
180
+    abstract public function mark_failure( $action_id );
181
+
182
+    /**
183
+     * @param string $action_id
184
+     */
185
+    abstract public function log_execution( $action_id );
186
+
187
+    /**
188
+     * @param string $action_id
189
+     */
190
+    abstract public function mark_complete( $action_id );
191
+
192
+    /**
193
+     * @param string $action_id
194
+     *
195
+     * @return string
196
+     */
197
+    abstract public function get_status( $action_id );
198
+
199
+    /**
200
+     * @param string $action_id
201
+     * @return mixed
202
+     */
203
+    abstract public function get_claim_id( $action_id );
204
+
205
+    /**
206
+     * @param string $claim_id
207
+     * @return array
208
+     */
209
+    abstract public function find_actions_by_claim_id( $claim_id );
210
+
211
+    /**
212
+     * @param string $comparison_operator
213
+     * @return string
214
+     */
215
+    protected function validate_sql_comparator( $comparison_operator ) {
216
+        if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) {
217
+            return $comparison_operator;
218
+        }
219
+        return '=';
220
+    }
221
+
222
+    /**
223
+     * Get the time MySQL formated date/time string for an action's (next) scheduled date.
224
+     *
225
+     * @param ActionScheduler_Action $action
226
+     * @param DateTime $scheduled_date (optional)
227
+     * @return string
228
+     */
229
+    protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
230
+        $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
231
+        if ( ! $next ) {
232
+            return '0000-00-00 00:00:00';
233
+        }
234
+        $next->setTimezone( new DateTimeZone( 'UTC' ) );
235
+
236
+        return $next->format( 'Y-m-d H:i:s' );
237
+    }
238
+
239
+    /**
240
+     * Get the time MySQL formated date/time string for an action's (next) scheduled date.
241
+     *
242
+     * @param ActionScheduler_Action $action
243
+     * @param DateTime $scheduled_date (optional)
244
+     * @return string
245
+     */
246
+    protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
247
+        $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
248
+        if ( ! $next ) {
249
+            return '0000-00-00 00:00:00';
250
+        }
251
+
252
+        ActionScheduler_TimezoneHelper::set_local_timezone( $next );
253
+        return $next->format( 'Y-m-d H:i:s' );
254
+    }
255
+
256
+    /**
257
+     * Validate that we could decode action arguments.
258
+     *
259
+     * @param mixed $args      The decoded arguments.
260
+     * @param int   $action_id The action ID.
261
+     *
262
+     * @throws ActionScheduler_InvalidActionException When the decoded arguments are invalid.
263
+     */
264
+    protected function validate_args( $args, $action_id ) {
265
+        // Ensure we have an array of args.
266
+        if ( ! is_array( $args ) ) {
267
+            throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id );
268
+        }
269
+
270
+        // Validate JSON decoding if possible.
271
+        if ( function_exists( 'json_last_error' ) && JSON_ERROR_NONE !== json_last_error() ) {
272
+            throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id, $args );
273
+        }
274
+    }
275
+
276
+    /**
277
+     * Validate a ActionScheduler_Schedule object.
278
+     *
279
+     * @param mixed $schedule  The unserialized ActionScheduler_Schedule object.
280
+     * @param int   $action_id The action ID.
281
+     *
282
+     * @throws ActionScheduler_InvalidActionException When the schedule is invalid.
283
+     */
284
+    protected function validate_schedule( $schedule, $action_id ) {
285
+        if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) {
286
+            throw ActionScheduler_InvalidActionException::from_schedule( $action_id, $schedule );
287
+        }
288
+    }
289
+
290
+    /**
291
+     * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
292
+     *
293
+     * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
294
+     * with custom tables, we use an indexed VARCHAR column instead.
295
+     *
296
+     * @param  ActionScheduler_Action $action Action to be validated.
297
+     * @throws InvalidArgumentException When json encoded args is too long.
298
+     */
299
+    protected function validate_action( ActionScheduler_Action $action ) {
300
+        if ( strlen( json_encode( $action->get_args() ) ) > static::$max_args_length ) {
301
+            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.', 'woocommerce' ), static::$max_args_length ) );
302
+        }
303
+    }
304
+
305
+    /**
306
+     * Cancel pending actions by hook.
307
+     *
308
+     * @since 3.0.0
309
+     *
310
+     * @param string $hook Hook name.
311
+     *
312
+     * @return void
313
+     */
314
+    public function cancel_actions_by_hook( $hook ) {
315
+        $action_ids = true;
316
+        while ( ! empty( $action_ids ) ) {
317
+            $action_ids = $this->query_actions(
318
+                array(
319
+                    'hook'     => $hook,
320
+                    'status'   => self::STATUS_PENDING,
321
+                    'per_page' => 1000,
322
+                    'orderby'  => 'action_id',
323
+                )
324
+            );
325
+
326
+            $this->bulk_cancel_actions( $action_ids );
327
+        }
328
+    }
329
+
330
+    /**
331
+     * Cancel pending actions by group.
332
+     *
333
+     * @since 3.0.0
334
+     *
335
+     * @param string $group Group slug.
336
+     *
337
+     * @return void
338
+     */
339
+    public function cancel_actions_by_group( $group ) {
340
+        $action_ids = true;
341
+        while ( ! empty( $action_ids ) ) {
342
+            $action_ids = $this->query_actions(
343
+                array(
344
+                    'group'    => $group,
345
+                    'status'   => self::STATUS_PENDING,
346
+                    'per_page' => 1000,
347
+                    'orderby'  => 'action_id',
348
+                )
349
+            );
350
+
351
+            $this->bulk_cancel_actions( $action_ids );
352
+        }
353
+    }
354
+
355
+    /**
356
+     * Cancel a set of action IDs.
357
+     *
358
+     * @since 3.0.0
359
+     *
360
+     * @param array $action_ids List of action IDs.
361
+     *
362
+     * @return void
363
+     */
364
+    private function bulk_cancel_actions( $action_ids ) {
365
+        foreach ( $action_ids as $action_id ) {
366
+            $this->cancel_action( $action_id );
367
+        }
368
+
369
+        do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
370
+    }
371
+
372
+    /**
373
+     * @return array
374
+     */
375
+    public function get_status_labels() {
376
+        return array(
377
+            self::STATUS_COMPLETE => __( 'Complete', 'woocommerce' ),
378
+            self::STATUS_PENDING  => __( 'Pending', 'woocommerce' ),
379
+            self::STATUS_RUNNING  => __( 'In-progress', 'woocommerce' ),
380
+            self::STATUS_FAILED   => __( 'Failed', 'woocommerce' ),
381
+            self::STATUS_CANCELED => __( 'Canceled', 'woocommerce' ),
382
+        );
383
+    }
384
+
385
+    /**
386
+     * Check if there are any pending scheduled actions due to run.
387
+     *
388
+     * @param ActionScheduler_Action $action
389
+     * @param DateTime $scheduled_date (optional)
390
+     * @return string
391
+     */
392
+    public function has_pending_actions_due() {
393
+        $pending_actions = $this->query_actions( array(
394
+            'date'    => as_get_datetime_object(),
395
+            'status'  => ActionScheduler_Store::STATUS_PENDING,
396
+            'orderby' => 'none',
397
+        ) );
398
+
399
+        return ! empty( $pending_actions );
400
+    }
401
+
402
+    /**
403
+     * Callable initialization function optionally overridden in derived classes.
404
+     */
405
+    public function init() {}
406
+
407
+    /**
408
+     * Callable function to mark an action as migrated optionally overridden in derived classes.
409
+     */
410
+    public function mark_migrated( $action_id ) {}
411
+
412
+    /**
413
+     * @return ActionScheduler_Store
414
+     */
415
+    public static function instance() {
416
+        if ( empty( self::$store ) ) {
417
+            $class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS );
418
+            self::$store = new $class();
419
+        }
420
+        return self::$store;
421
+    }
422 422
 }
Please login to merge, or discard this patch.
woocommerce/packages/action-scheduler/classes/abstracts/ActionScheduler.php 1 patch
Indentation   +293 added lines, -293 removed lines patch added patch discarded remove patch
@@ -8,297 +8,297 @@
 block discarded – undo
8 8
  * @codeCoverageIgnore
9 9
  */
10 10
 abstract class ActionScheduler {
11
-	private static $plugin_file = '';
12
-	/** @var ActionScheduler_ActionFactory */
13
-	private static $factory = NULL;
14
-	/** @var bool */
15
-	private static $data_store_initialized = false;
16
-
17
-	public static function factory() {
18
-		if ( !isset(self::$factory) ) {
19
-			self::$factory = new ActionScheduler_ActionFactory();
20
-		}
21
-		return self::$factory;
22
-	}
23
-
24
-	public static function store() {
25
-		return ActionScheduler_Store::instance();
26
-	}
27
-
28
-	public static function lock() {
29
-		return ActionScheduler_Lock::instance();
30
-	}
31
-
32
-	public static function logger() {
33
-		return ActionScheduler_Logger::instance();
34
-	}
35
-
36
-	public static function runner() {
37
-		return ActionScheduler_QueueRunner::instance();
38
-	}
39
-
40
-	public static function admin_view() {
41
-		return ActionScheduler_AdminView::instance();
42
-	}
43
-
44
-	/**
45
-	 * Get the absolute system path to the plugin directory, or a file therein
46
-	 * @static
47
-	 * @param string $path
48
-	 * @return string
49
-	 */
50
-	public static function plugin_path( $path ) {
51
-		$base = dirname(self::$plugin_file);
52
-		if ( $path ) {
53
-			return trailingslashit($base).$path;
54
-		} else {
55
-			return untrailingslashit($base);
56
-		}
57
-	}
58
-
59
-	/**
60
-	 * Get the absolute URL to the plugin directory, or a file therein
61
-	 * @static
62
-	 * @param string $path
63
-	 * @return string
64
-	 */
65
-	public static function plugin_url( $path ) {
66
-		return plugins_url($path, self::$plugin_file);
67
-	}
68
-
69
-	public static function autoload( $class ) {
70
-		$d           = DIRECTORY_SEPARATOR;
71
-		$classes_dir = self::plugin_path( 'classes' . $d );
72
-		$separator   = strrpos( $class, '\\' );
73
-		if ( false !== $separator ) {
74
-			if ( 0 !== strpos( $class, 'Action_Scheduler' ) ) {
75
-				return;
76
-			}
77
-			$class = substr( $class, $separator + 1 );
78
-		}
79
-
80
-		if ( 'Deprecated' === substr( $class, -10 ) ) {
81
-			$dir = self::plugin_path( 'deprecated' . $d );
82
-		} elseif ( self::is_class_abstract( $class ) ) {
83
-			$dir = $classes_dir . 'abstracts' . $d;
84
-		} elseif ( self::is_class_migration( $class ) ) {
85
-			$dir = $classes_dir . 'migration' . $d;
86
-		} elseif ( 'Schedule' === substr( $class, -8 ) ) {
87
-			$dir = $classes_dir . 'schedules' . $d;
88
-		} elseif ( 'Action' === substr( $class, -6 ) ) {
89
-			$dir = $classes_dir . 'actions' . $d;
90
-		} elseif ( 'Schema' === substr( $class, -6 ) ) {
91
-			$dir = $classes_dir . 'schema' . $d;
92
-		} elseif ( strpos( $class, 'ActionScheduler' ) === 0 ) {
93
-			$segments = explode( '_', $class );
94
-			$type = isset( $segments[ 1 ] ) ? $segments[ 1 ] : '';
95
-
96
-			switch ( $type ) {
97
-				case 'WPCLI':
98
-					$dir = $classes_dir . 'WP_CLI' . $d;
99
-					break;
100
-				case 'DBLogger':
101
-				case 'DBStore':
102
-				case 'HybridStore':
103
-				case 'wpPostStore':
104
-				case 'wpCommentLogger':
105
-					$dir = $classes_dir . 'data-stores' . $d;
106
-					break;
107
-				default:
108
-					$dir = $classes_dir;
109
-					break;
110
-			}
111
-		} elseif ( self::is_class_cli( $class ) ) {
112
-			$dir = $classes_dir . 'WP_CLI' . $d;
113
-		} elseif ( strpos( $class, 'CronExpression' ) === 0 ) {
114
-			$dir = self::plugin_path( 'lib' . $d . 'cron-expression' . $d );
115
-		} elseif ( strpos( $class, 'WP_Async_Request' ) === 0 ) {
116
-			$dir = self::plugin_path( 'lib' . $d );
117
-		} else {
118
-			return;
119
-		}
120
-
121
-		if ( file_exists( $dir . "{$class}.php" ) ) {
122
-			include( $dir . "{$class}.php" );
123
-			return;
124
-		}
125
-	}
126
-
127
-	/**
128
-	 * Initialize the plugin
129
-	 *
130
-	 * @static
131
-	 * @param string $plugin_file
132
-	 */
133
-	public static function init( $plugin_file ) {
134
-		self::$plugin_file = $plugin_file;
135
-		spl_autoload_register( array( __CLASS__, 'autoload' ) );
136
-
137
-		/**
138
-		 * Fires in the early stages of Action Scheduler init hook.
139
-		 */
140
-		do_action( 'action_scheduler_pre_init' );
141
-
142
-		require_once( self::plugin_path( 'functions.php' ) );
143
-		ActionScheduler_DataController::init();
144
-
145
-		$store      = self::store();
146
-		$logger     = self::logger();
147
-		$runner     = self::runner();
148
-		$admin_view = self::admin_view();
149
-
150
-		// Ensure initialization on plugin activation.
151
-		if ( ! did_action( 'init' ) ) {
152
-			add_action( 'init', array( $admin_view, 'init' ), 0, 0 ); // run before $store::init()
153
-			add_action( 'init', array( $store, 'init' ), 1, 0 );
154
-			add_action( 'init', array( $logger, 'init' ), 1, 0 );
155
-			add_action( 'init', array( $runner, 'init' ), 1, 0 );
156
-		} else {
157
-			$admin_view->init();
158
-			$store->init();
159
-			$logger->init();
160
-			$runner->init();
161
-		}
162
-
163
-		if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) {
164
-			require_once( self::plugin_path( 'deprecated/functions.php' ) );
165
-		}
166
-
167
-		if ( defined( 'WP_CLI' ) && WP_CLI ) {
168
-			WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' );
169
-			if ( ! ActionScheduler_DataController::is_migration_complete() && Controller::instance()->allow_migration() ) {
170
-				$command = new Migration_Command();
171
-				$command->register();
172
-			}
173
-		}
174
-
175
-		self::$data_store_initialized = true;
176
-
177
-		/**
178
-		 * Handle WP comment cleanup after migration.
179
-		 */
180
-		if ( is_a( $logger, 'ActionScheduler_DBLogger' ) && ActionScheduler_DataController::is_migration_complete() && ActionScheduler_WPCommentCleaner::has_logs() ) {
181
-			ActionScheduler_WPCommentCleaner::init();
182
-		}
183
-
184
-		add_action( 'action_scheduler/migration_complete', 'ActionScheduler_WPCommentCleaner::maybe_schedule_cleanup' );
185
-	}
186
-
187
-	/**
188
-	 * Check whether the AS data store has been initialized.
189
-	 *
190
-	 * @param string $function_name The name of the function being called. Optional. Default `null`.
191
-	 * @return bool
192
-	 */
193
-	public static function is_initialized( $function_name = null ) {
194
-		if ( ! self::$data_store_initialized && ! empty( $function_name ) ) {
195
-			$message = sprintf( __( '%s() was called before the Action Scheduler data store was initialized', 'woocommerce' ), esc_attr( $function_name ) );
196
-			error_log( $message, E_WARNING );
197
-		}
198
-
199
-		return self::$data_store_initialized;
200
-	}
201
-
202
-	/**
203
-	 * Determine if the class is one of our abstract classes.
204
-	 *
205
-	 * @since 3.0.0
206
-	 *
207
-	 * @param string $class The class name.
208
-	 *
209
-	 * @return bool
210
-	 */
211
-	protected static function is_class_abstract( $class ) {
212
-		static $abstracts = array(
213
-			'ActionScheduler'                            => true,
214
-			'ActionScheduler_Abstract_ListTable'         => true,
215
-			'ActionScheduler_Abstract_QueueRunner'       => true,
216
-			'ActionScheduler_Abstract_Schedule'          => true,
217
-			'ActionScheduler_Abstract_RecurringSchedule' => true,
218
-			'ActionScheduler_Lock'                       => true,
219
-			'ActionScheduler_Logger'                     => true,
220
-			'ActionScheduler_Abstract_Schema'            => true,
221
-			'ActionScheduler_Store'                      => true,
222
-			'ActionScheduler_TimezoneHelper'             => true,
223
-		);
224
-
225
-		return isset( $abstracts[ $class ] ) && $abstracts[ $class ];
226
-	}
227
-
228
-	/**
229
-	 * Determine if the class is one of our migration classes.
230
-	 *
231
-	 * @since 3.0.0
232
-	 *
233
-	 * @param string $class The class name.
234
-	 *
235
-	 * @return bool
236
-	 */
237
-	protected static function is_class_migration( $class ) {
238
-		static $migration_segments = array(
239
-			'ActionMigrator'  => true,
240
-			'BatchFetcher'    => true,
241
-			'DBStoreMigrator' => true,
242
-			'DryRun'          => true,
243
-			'LogMigrator'     => true,
244
-			'Config'          => true,
245
-			'Controller'      => true,
246
-			'Runner'          => true,
247
-			'Scheduler'       => true,
248
-		);
249
-
250
-		$segments = explode( '_', $class );
251
-		$segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class;
252
-
253
-		return isset( $migration_segments[ $segment ] ) && $migration_segments[ $segment ];
254
-	}
255
-
256
-	/**
257
-	 * Determine if the class is one of our WP CLI classes.
258
-	 *
259
-	 * @since 3.0.0
260
-	 *
261
-	 * @param string $class The class name.
262
-	 *
263
-	 * @return bool
264
-	 */
265
-	protected static function is_class_cli( $class ) {
266
-		static $cli_segments = array(
267
-			'QueueRunner' => true,
268
-			'Command'     => true,
269
-			'ProgressBar' => true,
270
-		);
271
-
272
-		$segments = explode( '_', $class );
273
-		$segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class;
274
-
275
-		return isset( $cli_segments[ $segment ] ) && $cli_segments[ $segment ];
276
-	}
277
-
278
-	final public function __clone() {
279
-		trigger_error("Singleton. No cloning allowed!", E_USER_ERROR);
280
-	}
281
-
282
-	final public function __wakeup() {
283
-		trigger_error("Singleton. No serialization allowed!", E_USER_ERROR);
284
-	}
285
-
286
-	final private function __construct() {}
287
-
288
-	/** Deprecated **/
289
-
290
-	public static function get_datetime_object( $when = null, $timezone = 'UTC' ) {
291
-		_deprecated_function( __METHOD__, '2.0', 'wcs_add_months()' );
292
-		return as_get_datetime_object( $when, $timezone );
293
-	}
294
-
295
-	/**
296
-	 * Issue deprecated warning if an Action Scheduler function is called in the shutdown hook.
297
-	 *
298
-	 * @param string $function_name The name of the function being called.
299
-	 * @deprecated 3.1.6.
300
-	 */
301
-	public static function check_shutdown_hook( $function_name ) {
302
-		_deprecated_function( __FUNCTION__, '3.1.6' );
303
-	}
11
+    private static $plugin_file = '';
12
+    /** @var ActionScheduler_ActionFactory */
13
+    private static $factory = NULL;
14
+    /** @var bool */
15
+    private static $data_store_initialized = false;
16
+
17
+    public static function factory() {
18
+        if ( !isset(self::$factory) ) {
19
+            self::$factory = new ActionScheduler_ActionFactory();
20
+        }
21
+        return self::$factory;
22
+    }
23
+
24
+    public static function store() {
25
+        return ActionScheduler_Store::instance();
26
+    }
27
+
28
+    public static function lock() {
29
+        return ActionScheduler_Lock::instance();
30
+    }
31
+
32
+    public static function logger() {
33
+        return ActionScheduler_Logger::instance();
34
+    }
35
+
36
+    public static function runner() {
37
+        return ActionScheduler_QueueRunner::instance();
38
+    }
39
+
40
+    public static function admin_view() {
41
+        return ActionScheduler_AdminView::instance();
42
+    }
43
+
44
+    /**
45
+     * Get the absolute system path to the plugin directory, or a file therein
46
+     * @static
47
+     * @param string $path
48
+     * @return string
49
+     */
50
+    public static function plugin_path( $path ) {
51
+        $base = dirname(self::$plugin_file);
52
+        if ( $path ) {
53
+            return trailingslashit($base).$path;
54
+        } else {
55
+            return untrailingslashit($base);
56
+        }
57
+    }
58
+
59
+    /**
60
+     * Get the absolute URL to the plugin directory, or a file therein
61
+     * @static
62
+     * @param string $path
63
+     * @return string
64
+     */
65
+    public static function plugin_url( $path ) {
66
+        return plugins_url($path, self::$plugin_file);
67
+    }
68
+
69
+    public static function autoload( $class ) {
70
+        $d           = DIRECTORY_SEPARATOR;
71
+        $classes_dir = self::plugin_path( 'classes' . $d );
72
+        $separator   = strrpos( $class, '\\' );
73
+        if ( false !== $separator ) {
74
+            if ( 0 !== strpos( $class, 'Action_Scheduler' ) ) {
75
+                return;
76
+            }
77
+            $class = substr( $class, $separator + 1 );
78
+        }
79
+
80
+        if ( 'Deprecated' === substr( $class, -10 ) ) {
81
+            $dir = self::plugin_path( 'deprecated' . $d );
82
+        } elseif ( self::is_class_abstract( $class ) ) {
83
+            $dir = $classes_dir . 'abstracts' . $d;
84
+        } elseif ( self::is_class_migration( $class ) ) {
85
+            $dir = $classes_dir . 'migration' . $d;
86
+        } elseif ( 'Schedule' === substr( $class, -8 ) ) {
87
+            $dir = $classes_dir . 'schedules' . $d;
88
+        } elseif ( 'Action' === substr( $class, -6 ) ) {
89
+            $dir = $classes_dir . 'actions' . $d;
90
+        } elseif ( 'Schema' === substr( $class, -6 ) ) {
91
+            $dir = $classes_dir . 'schema' . $d;
92
+        } elseif ( strpos( $class, 'ActionScheduler' ) === 0 ) {
93
+            $segments = explode( '_', $class );
94
+            $type = isset( $segments[ 1 ] ) ? $segments[ 1 ] : '';
95
+
96
+            switch ( $type ) {
97
+                case 'WPCLI':
98
+                    $dir = $classes_dir . 'WP_CLI' . $d;
99
+                    break;
100
+                case 'DBLogger':
101
+                case 'DBStore':
102
+                case 'HybridStore':
103
+                case 'wpPostStore':
104
+                case 'wpCommentLogger':
105
+                    $dir = $classes_dir . 'data-stores' . $d;
106
+                    break;
107
+                default:
108
+                    $dir = $classes_dir;
109
+                    break;
110
+            }
111
+        } elseif ( self::is_class_cli( $class ) ) {
112
+            $dir = $classes_dir . 'WP_CLI' . $d;
113
+        } elseif ( strpos( $class, 'CronExpression' ) === 0 ) {
114
+            $dir = self::plugin_path( 'lib' . $d . 'cron-expression' . $d );
115
+        } elseif ( strpos( $class, 'WP_Async_Request' ) === 0 ) {
116
+            $dir = self::plugin_path( 'lib' . $d );
117
+        } else {
118
+            return;
119
+        }
120
+
121
+        if ( file_exists( $dir . "{$class}.php" ) ) {
122
+            include( $dir . "{$class}.php" );
123
+            return;
124
+        }
125
+    }
126
+
127
+    /**
128
+     * Initialize the plugin
129
+     *
130
+     * @static
131
+     * @param string $plugin_file
132
+     */
133
+    public static function init( $plugin_file ) {
134
+        self::$plugin_file = $plugin_file;
135
+        spl_autoload_register( array( __CLASS__, 'autoload' ) );
136
+
137
+        /**
138
+         * Fires in the early stages of Action Scheduler init hook.
139
+         */
140
+        do_action( 'action_scheduler_pre_init' );
141
+
142
+        require_once( self::plugin_path( 'functions.php' ) );
143
+        ActionScheduler_DataController::init();
144
+
145
+        $store      = self::store();
146
+        $logger     = self::logger();
147
+        $runner     = self::runner();
148
+        $admin_view = self::admin_view();
149
+
150
+        // Ensure initialization on plugin activation.
151
+        if ( ! did_action( 'init' ) ) {
152
+            add_action( 'init', array( $admin_view, 'init' ), 0, 0 ); // run before $store::init()
153
+            add_action( 'init', array( $store, 'init' ), 1, 0 );
154
+            add_action( 'init', array( $logger, 'init' ), 1, 0 );
155
+            add_action( 'init', array( $runner, 'init' ), 1, 0 );
156
+        } else {
157
+            $admin_view->init();
158
+            $store->init();
159
+            $logger->init();
160
+            $runner->init();
161
+        }
162
+
163
+        if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) {
164
+            require_once( self::plugin_path( 'deprecated/functions.php' ) );
165
+        }
166
+
167
+        if ( defined( 'WP_CLI' ) && WP_CLI ) {
168
+            WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' );
169
+            if ( ! ActionScheduler_DataController::is_migration_complete() && Controller::instance()->allow_migration() ) {
170
+                $command = new Migration_Command();
171
+                $command->register();
172
+            }
173
+        }
174
+
175
+        self::$data_store_initialized = true;
176
+
177
+        /**
178
+         * Handle WP comment cleanup after migration.
179
+         */
180
+        if ( is_a( $logger, 'ActionScheduler_DBLogger' ) && ActionScheduler_DataController::is_migration_complete() && ActionScheduler_WPCommentCleaner::has_logs() ) {
181
+            ActionScheduler_WPCommentCleaner::init();
182
+        }
183
+
184
+        add_action( 'action_scheduler/migration_complete', 'ActionScheduler_WPCommentCleaner::maybe_schedule_cleanup' );
185
+    }
186
+
187
+    /**
188
+     * Check whether the AS data store has been initialized.
189
+     *
190
+     * @param string $function_name The name of the function being called. Optional. Default `null`.
191
+     * @return bool
192
+     */
193
+    public static function is_initialized( $function_name = null ) {
194
+        if ( ! self::$data_store_initialized && ! empty( $function_name ) ) {
195
+            $message = sprintf( __( '%s() was called before the Action Scheduler data store was initialized', 'woocommerce' ), esc_attr( $function_name ) );
196
+            error_log( $message, E_WARNING );
197
+        }
198
+
199
+        return self::$data_store_initialized;
200
+    }
201
+
202
+    /**
203
+     * Determine if the class is one of our abstract classes.
204
+     *
205
+     * @since 3.0.0
206
+     *
207
+     * @param string $class The class name.
208
+     *
209
+     * @return bool
210
+     */
211
+    protected static function is_class_abstract( $class ) {
212
+        static $abstracts = array(
213
+            'ActionScheduler'                            => true,
214
+            'ActionScheduler_Abstract_ListTable'         => true,
215
+            'ActionScheduler_Abstract_QueueRunner'       => true,
216
+            'ActionScheduler_Abstract_Schedule'          => true,
217
+            'ActionScheduler_Abstract_RecurringSchedule' => true,
218
+            'ActionScheduler_Lock'                       => true,
219
+            'ActionScheduler_Logger'                     => true,
220
+            'ActionScheduler_Abstract_Schema'            => true,
221
+            'ActionScheduler_Store'                      => true,
222
+            'ActionScheduler_TimezoneHelper'             => true,
223
+        );
224
+
225
+        return isset( $abstracts[ $class ] ) && $abstracts[ $class ];
226
+    }
227
+
228
+    /**
229
+     * Determine if the class is one of our migration classes.
230
+     *
231
+     * @since 3.0.0
232
+     *
233
+     * @param string $class The class name.
234
+     *
235
+     * @return bool
236
+     */
237
+    protected static function is_class_migration( $class ) {
238
+        static $migration_segments = array(
239
+            'ActionMigrator'  => true,
240
+            'BatchFetcher'    => true,
241
+            'DBStoreMigrator' => true,
242
+            'DryRun'          => true,
243
+            'LogMigrator'     => true,
244
+            'Config'          => true,
245
+            'Controller'      => true,
246
+            'Runner'          => true,
247
+            'Scheduler'       => true,
248
+        );
249
+
250
+        $segments = explode( '_', $class );
251
+        $segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class;
252
+
253
+        return isset( $migration_segments[ $segment ] ) && $migration_segments[ $segment ];
254
+    }
255
+
256
+    /**
257
+     * Determine if the class is one of our WP CLI classes.
258
+     *
259
+     * @since 3.0.0
260
+     *
261
+     * @param string $class The class name.
262
+     *
263
+     * @return bool
264
+     */
265
+    protected static function is_class_cli( $class ) {
266
+        static $cli_segments = array(
267
+            'QueueRunner' => true,
268
+            'Command'     => true,
269
+            'ProgressBar' => true,
270
+        );
271
+
272
+        $segments = explode( '_', $class );
273
+        $segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class;
274
+
275
+        return isset( $cli_segments[ $segment ] ) && $cli_segments[ $segment ];
276
+    }
277
+
278
+    final public function __clone() {
279
+        trigger_error("Singleton. No cloning allowed!", E_USER_ERROR);
280
+    }
281
+
282
+    final public function __wakeup() {
283
+        trigger_error("Singleton. No serialization allowed!", E_USER_ERROR);
284
+    }
285
+
286
+    final private function __construct() {}
287
+
288
+    /** Deprecated **/
289
+
290
+    public static function get_datetime_object( $when = null, $timezone = 'UTC' ) {
291
+        _deprecated_function( __METHOD__, '2.0', 'wcs_add_months()' );
292
+        return as_get_datetime_object( $when, $timezone );
293
+    }
294
+
295
+    /**
296
+     * Issue deprecated warning if an Action Scheduler function is called in the shutdown hook.
297
+     *
298
+     * @param string $function_name The name of the function being called.
299
+     * @deprecated 3.1.6.
300
+     */
301
+    public static function check_shutdown_hook( $function_name ) {
302
+        _deprecated_function( __FUNCTION__, '3.1.6' );
303
+    }
304 304
 }
Please login to merge, or discard this patch.
action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php 1 patch
Indentation   +741 added lines, -741 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,744 +23,744 @@  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
-	 * @var string
30
-	 */
31
-	protected $table_name;
32
-
33
-	/**
34
-	 * Package name, used to get options from WP_List_Table::get_items_per_page.
35
-	 *
36
-	 * @var string
37
-	 */
38
-	protected $package;
39
-
40
-	/**
41
-	 * How many items do we render per page?
42
-	 *
43
-	 * @var int
44
-	 */
45
-	protected $items_per_page = 10;
46
-
47
-	/**
48
-	 * Enables search in this table listing. If this array
49
-	 * is empty it means the listing is not searchable.
50
-	 *
51
-	 * @var array
52
-	 */
53
-	protected $search_by = array();
54
-
55
-	/**
56
-	 * Columns to show in the table listing. It is a key => value pair. The
57
-	 * key must much the table column name and the value is the label, which is
58
-	 * automatically translated.
59
-	 *
60
-	 * @var array
61
-	 */
62
-	protected $columns = array();
63
-
64
-	/**
65
-	 * Defines the row-actions. It expects an array where the key
66
-	 * is the column name and the value is an array of actions.
67
-	 *
68
-	 * The array of actions are key => value, where key is the method name
69
-	 * (with the prefix row_action_<key>) and the value is the label
70
-	 * and title.
71
-	 *
72
-	 * @var array
73
-	 */
74
-	protected $row_actions = array();
75
-
76
-	/**
77
-	 * The Primary key of our table
78
-	 *
79
-	 * @var string
80
-	 */
81
-	protected $ID = 'ID';
82
-
83
-	/**
84
-	 * Enables sorting, it expects an array
85
-	 * of columns (the column names are the values)
86
-	 *
87
-	 * @var array
88
-	 */
89
-	protected $sort_by = array();
90
-
91
-	/**
92
-	 * The default sort order
93
-	 *
94
-	 * @var string
95
-	 */
96
-	protected $filter_by = array();
97
-
98
-	/**
99
-	 * The status name => count combinations for this table's items. Used to display status filters.
100
-	 *
101
-	 * @var array
102
-	 */
103
-	protected $status_counts = array();
104
-
105
-	/**
106
-	 * Notices to display when loading the table. Array of arrays of form array( 'class' => {updated|error}, 'message' => 'This is the notice text display.' ).
107
-	 *
108
-	 * @var array
109
-	 */
110
-	protected $admin_notices = array();
111
-
112
-	/**
113
-	 * Localised string displayed in the <h1> element above the able.
114
-	 *
115
-	 * @var string
116
-	 */
117
-	protected $table_header;
118
-
119
-	/**
120
-	 * Enables bulk actions. It must be an array where the key is the action name
121
-	 * and the value is the label (which is translated automatically). It is important
122
-	 * to notice that it will check that the method exists (`bulk_$name`) and will throw
123
-	 * an exception if it does not exists.
124
-	 *
125
-	 * This class will automatically check if the current request has a bulk action, will do the
126
-	 * validations and afterwards will execute the bulk method, with two arguments. The first argument
127
-	 * is the array with primary keys, the second argument is a string with a list of the primary keys,
128
-	 * escaped and ready to use (with `IN`).
129
-	 *
130
-	 * @var array
131
-	 */
132
-	protected $bulk_actions = array();
133
-
134
-	/**
135
-	 * Makes translation easier, it basically just wraps
136
-	 * `_x` with some default (the package name).
137
-	 *
138
-	 * @param string $text The new text to translate.
139
-	 * @param string $context The context of the text.
140
-	 * @return string|void The translated text.
141
-	 *
142
-	 * @deprecated 3.0.0 Use `_x()` instead.
143
-	 */
144
-	protected function translate( $text, $context = '' ) {
145
-		return $text;
146
-	}
147
-
148
-	/**
149
-	 * Reads `$this->bulk_actions` and returns an array that WP_List_Table understands. It
150
-	 * also validates that the bulk method handler exists. It throws an exception because
151
-	 * this is a library meant for developers and missing a bulk method is a development-time error.
152
-	 *
153
-	 * @return array
154
-	 *
155
-	 * @throws RuntimeException Throws RuntimeException when the bulk action does not have a callback method.
156
-	 */
157
-	protected function get_bulk_actions() {
158
-		$actions = array();
159
-
160
-		foreach ( $this->bulk_actions as $action => $label ) {
161
-			if ( ! is_callable( array( $this, 'bulk_' . $action ) ) ) {
162
-				throw new RuntimeException( "The bulk action $action does not have a callback method" );
163
-			}
164
-
165
-			$actions[ $action ] = $label;
166
-		}
167
-
168
-		return $actions;
169
-	}
170
-
171
-	/**
172
-	 * Checks if the current request has a bulk action. If that is the case it will validate and will
173
-	 * execute the bulk method handler. Regardless if the action is valid or not it will redirect to
174
-	 * the previous page removing the current arguments that makes this request a bulk action.
175
-	 */
176
-	protected function process_bulk_action() {
177
-		global $wpdb;
178
-		// Detect when a bulk action is being triggered.
179
-		$action = $this->current_action();
180
-		if ( ! $action ) {
181
-			return;
182
-		}
183
-
184
-		check_admin_referer( 'bulk-' . $this->_args['plural'] );
185
-
186
-		$method = 'bulk_' . $action;
187
-		if ( array_key_exists( $action, $this->bulk_actions ) && is_callable( array( $this, $method ) ) && ! empty( $_GET['ID'] ) && is_array( $_GET['ID'] ) ) {
188
-			$ids_sql = '(' . implode( ',', array_fill( 0, count( $_GET['ID'] ), '%s' ) ) . ')';
189
-			$id      = array_map( 'absint', $_GET['ID'] );
190
-			$this->$method( $id, $wpdb->prepare( $ids_sql, $id ) ); //phpcs:ignore WordPress.DB.PreparedSQL
191
-		}
192
-
193
-		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
194
-			wp_safe_redirect(
195
-				remove_query_arg(
196
-					array( '_wp_http_referer', '_wpnonce', 'ID', 'action', 'action2' ),
197
-					esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
198
-				)
199
-			);
200
-			exit;
201
-		}
202
-	}
203
-
204
-	/**
205
-	 * Default code for deleting entries.
206
-	 * validated already by process_bulk_action()
207
-	 *
208
-	 * @param array  $ids ids of the items to delete.
209
-	 * @param string $ids_sql the sql for the ids.
210
-	 * @return void
211
-	 */
212
-	protected function bulk_delete( array $ids, $ids_sql ) {
213
-		$store = ActionScheduler::store();
214
-		foreach ( $ids as $action_id ) {
215
-			$store->delete( $action_id );
216
-		}
217
-	}
218
-
219
-	/**
220
-	 * Prepares the _column_headers property which is used by WP_Table_List at rendering.
221
-	 * It merges the columns and the sortable columns.
222
-	 */
223
-	protected function prepare_column_headers() {
224
-		$this->_column_headers = array(
225
-			$this->get_columns(),
226
-			get_hidden_columns( $this->screen ),
227
-			$this->get_sortable_columns(),
228
-		);
229
-	}
230
-
231
-	/**
232
-	 * Reads $this->sort_by and returns the columns name in a format that WP_Table_List
233
-	 * expects
234
-	 */
235
-	public function get_sortable_columns() {
236
-		$sort_by = array();
237
-		foreach ( $this->sort_by as $column ) {
238
-			$sort_by[ $column ] = array( $column, true );
239
-		}
240
-		return $sort_by;
241
-	}
242
-
243
-	/**
244
-	 * Returns the columns names for rendering. It adds a checkbox for selecting everything
245
-	 * as the first column
246
-	 */
247
-	public function get_columns() {
248
-		$columns = array_merge(
249
-			array( 'cb' => '<input type="checkbox" />' ),
250
-			$this->columns
251
-		);
252
-
253
-		return $columns;
254
-	}
255
-
256
-	/**
257
-	 * Get prepared LIMIT clause for items query
258
-	 *
259
-	 * @global wpdb $wpdb
260
-	 *
261
-	 * @return string Prepared LIMIT clause for items query.
262
-	 */
263
-	protected function get_items_query_limit() {
264
-		global $wpdb;
265
-
266
-		$per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
267
-		return $wpdb->prepare( 'LIMIT %d', $per_page );
268
-	}
269
-
270
-	/**
271
-	 * Returns the number of items to offset/skip for this current view.
272
-	 *
273
-	 * @return int
274
-	 */
275
-	protected function get_items_offset() {
276
-		$per_page     = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
277
-		$current_page = $this->get_pagenum();
278
-		if ( 1 < $current_page ) {
279
-			$offset = $per_page * ( $current_page - 1 );
280
-		} else {
281
-			$offset = 0;
282
-		}
283
-
284
-		return $offset;
285
-	}
286
-
287
-	/**
288
-	 * Get prepared OFFSET clause for items query
289
-	 *
290
-	 * @global wpdb $wpdb
291
-	 *
292
-	 * @return string Prepared OFFSET clause for items query.
293
-	 */
294
-	protected function get_items_query_offset() {
295
-		global $wpdb;
296
-
297
-		return $wpdb->prepare( 'OFFSET %d', $this->get_items_offset() );
298
-	}
299
-
300
-	/**
301
-	 * Prepares the ORDER BY sql statement. It uses `$this->sort_by` to know which
302
-	 * columns are sortable. This requests validates the orderby $_GET parameter is a valid
303
-	 * column and sortable. It will also use order (ASC|DESC) using DESC by default.
304
-	 */
305
-	protected function get_items_query_order() {
306
-		if ( empty( $this->sort_by ) ) {
307
-			return '';
308
-		}
309
-
310
-		$orderby = esc_sql( $this->get_request_orderby() );
311
-		$order   = esc_sql( $this->get_request_order() );
312
-
313
-		return "ORDER BY {$orderby} {$order}";
314
-	}
315
-
316
-	/**
317
-	 * Return the sortable column specified for this request to order the results by, if any.
318
-	 *
319
-	 * @return string
320
-	 */
321
-	protected function get_request_orderby() {
322
-
323
-		$valid_sortable_columns = array_values( $this->sort_by );
324
-
325
-		if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
326
-			$orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
327
-		} else {
328
-			$orderby = $valid_sortable_columns[0];
329
-		}
330
-
331
-		return $orderby;
332
-	}
333
-
334
-	/**
335
-	 * Return the sortable column order specified for this request.
336
-	 *
337
-	 * @return string
338
-	 */
339
-	protected function get_request_order() {
340
-
341
-		if ( ! empty( $_GET['order'] ) && 'desc' === strtolower( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
342
-			$order = 'DESC';
343
-		} else {
344
-			$order = 'ASC';
345
-		}
346
-
347
-		return $order;
348
-	}
349
-
350
-	/**
351
-	 * Return the status filter for this request, if any.
352
-	 *
353
-	 * @return string
354
-	 */
355
-	protected function get_request_status() {
356
-		$status = ( ! empty( $_GET['status'] ) ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
357
-		return $status;
358
-	}
359
-
360
-	/**
361
-	 * Return the search filter for this request, if any.
362
-	 *
363
-	 * @return string
364
-	 */
365
-	protected function get_request_search_query() {
366
-		$search_query = ( ! empty( $_GET['s'] ) ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
367
-		return $search_query;
368
-	}
369
-
370
-	/**
371
-	 * Process and return the columns name. This is meant for using with SQL, this means it
372
-	 * always includes the primary key.
373
-	 *
374
-	 * @return array
375
-	 */
376
-	protected function get_table_columns() {
377
-		$columns = array_keys( $this->columns );
378
-		if ( ! in_array( $this->ID, $columns, true ) ) {
379
-			$columns[] = $this->ID;
380
-		}
381
-
382
-		return $columns;
383
-	}
384
-
385
-	/**
386
-	 * Check if the current request is doing a "full text" search. If that is the case
387
-	 * prepares the SQL to search texts using LIKE.
388
-	 *
389
-	 * If the current request does not have any search or if this list table does not support
390
-	 * that feature it will return an empty string.
391
-	 *
392
-	 * @return string
393
-	 */
394
-	protected function get_items_query_search() {
395
-		global $wpdb;
396
-
397
-		if ( empty( $_GET['s'] ) || empty( $this->search_by ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
398
-			return '';
399
-		}
400
-
401
-		$search_string = sanitize_text_field( wp_unslash( $_GET['s'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
402
-
403
-		$filter = array();
404
-		foreach ( $this->search_by as $column ) {
405
-			$wild     = '%';
406
-			$sql_like = $wild . $wpdb->esc_like( $search_string ) . $wild;
407
-			$filter[] = $wpdb->prepare( '`' . $column . '` LIKE %s', $sql_like ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.NotPrepared
408
-		}
409
-		return implode( ' OR ', $filter );
410
-	}
411
-
412
-	/**
413
-	 * Prepares the SQL to filter rows by the options defined at `$this->filter_by`. Before trusting
414
-	 * any data sent by the user it validates that it is a valid option.
415
-	 */
416
-	protected function get_items_query_filters() {
417
-		global $wpdb;
418
-
419
-		if ( ! $this->filter_by || empty( $_GET['filter_by'] ) || ! is_array( $_GET['filter_by'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
420
-			return '';
421
-		}
422
-
423
-		$filter = array();
424
-
425
-		foreach ( $this->filter_by as $column => $options ) {
426
-			if ( empty( $_GET['filter_by'][ $column ] ) || empty( $options[ $_GET['filter_by'][ $column ] ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
427
-				continue;
428
-			}
429
-
430
-			$filter[] = $wpdb->prepare( "`$column` = %s", sanitize_text_field( wp_unslash( $_GET['filter_by'][ $column ] ) ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
431
-		}
432
-
433
-		return implode( ' AND ', $filter );
434
-
435
-	}
436
-
437
-	/**
438
-	 * Prepares the data to feed WP_Table_List.
439
-	 *
440
-	 * This has the core for selecting, sorting and filting data. To keep the code simple
441
-	 * its logic is split among many methods (get_items_query_*).
442
-	 *
443
-	 * Beside populating the items this function will also count all the records that matches
444
-	 * the filtering criteria and will do fill the pagination variables.
445
-	 */
446
-	public function prepare_items() {
447
-		global $wpdb;
448
-
449
-		$this->process_bulk_action();
450
-
451
-		$this->process_row_actions();
452
-
453
-		if ( ! empty( $_REQUEST['_wp_http_referer'] && ! empty( $_SERVER['REQUEST_URI'] ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
454
-			// _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
455
-			wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
456
-			exit;
457
-		}
458
-
459
-		$this->prepare_column_headers();
460
-
461
-		$limit   = $this->get_items_query_limit();
462
-		$offset  = $this->get_items_query_offset();
463
-		$order   = $this->get_items_query_order();
464
-		$where   = array_filter(
465
-			array(
466
-				$this->get_items_query_search(),
467
-				$this->get_items_query_filters(),
468
-			)
469
-		);
470
-		$columns = '`' . implode( '`, `', $this->get_table_columns() ) . '`';
471
-
472
-		if ( ! empty( $where ) ) {
473
-			$where = 'WHERE (' . implode( ') AND (', $where ) . ')';
474
-		} else {
475
-			$where = '';
476
-		}
477
-
478
-		$sql = "SELECT $columns FROM {$this->table_name} {$where} {$order} {$limit} {$offset}";
479
-
480
-		$this->set_items( $wpdb->get_results( $sql, ARRAY_A ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
481
-
482
-		$query_count = "SELECT COUNT({$this->ID}) FROM {$this->table_name} {$where}";
483
-		$total_items = $wpdb->get_var( $query_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
484
-		$per_page    = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
485
-		$this->set_pagination_args(
486
-			array(
487
-				'total_items' => $total_items,
488
-				'per_page'    => $per_page,
489
-				'total_pages' => ceil( $total_items / $per_page ),
490
-			)
491
-		);
492
-	}
493
-
494
-	/**
495
-	 * Display the table.
496
-	 *
497
-	 * @param string $which The name of the table.
498
-	 */
499
-	public function extra_tablenav( $which ) {
500
-		if ( ! $this->filter_by || 'top' !== $which ) {
501
-			return;
502
-		}
503
-
504
-		echo '<div class="alignleft actions">';
505
-
506
-		foreach ( $this->filter_by as $id => $options ) {
507
-			$default = ! empty( $_GET['filter_by'][ $id ] ) ? sanitize_text_field( wp_unslash( $_GET['filter_by'][ $id ] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
508
-			if ( empty( $options[ $default ] ) ) {
509
-				$default = '';
510
-			}
511
-
512
-			echo '<select name="filter_by[' . esc_attr( $id ) . ']" class="first" id="filter-by-' . esc_attr( $id ) . '">';
513
-
514
-			foreach ( $options as $value => $label ) {
515
-				echo '<option value="' . esc_attr( $value ) . '" ' . esc_html( $value === $default ? 'selected' : '' ) . '>'
516
-					. esc_html( $label )
517
-				. '</option>';
518
-			}
519
-
520
-			echo '</select>';
521
-		}
522
-
523
-		submit_button( esc_html__( 'Filter', 'woocommerce' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
524
-		echo '</div>';
525
-	}
526
-
527
-	/**
528
-	 * Set the data for displaying. It will attempt to unserialize (There is a chance that some columns
529
-	 * are serialized). This can be override in child classes for futher data transformation.
530
-	 *
531
-	 * @param array $items Items array.
532
-	 */
533
-	protected function set_items( array $items ) {
534
-		$this->items = array();
535
-		foreach ( $items as $item ) {
536
-			$this->items[ $item[ $this->ID ] ] = array_map( 'maybe_unserialize', $item );
537
-		}
538
-	}
539
-
540
-	/**
541
-	 * Renders the checkbox for each row, this is the first column and it is named ID regardless
542
-	 * of how the primary key is named (to keep the code simpler). The bulk actions will do the proper
543
-	 * name transformation though using `$this->ID`.
544
-	 *
545
-	 * @param array $row The row to render.
546
-	 */
547
-	public function column_cb( $row ) {
548
-		return '<input name="ID[]" type="checkbox" value="' . esc_attr( $row[ $this->ID ] ) . '" />';
549
-	}
550
-
551
-	/**
552
-	 * Renders the row-actions.
553
-	 *
554
-	 * This method renders the action menu, it reads the definition from the $row_actions property,
555
-	 * and it checks that the row action method exists before rendering it.
556
-	 *
557
-	 * @param array  $row Row to be rendered.
558
-	 * @param string $column_name Column name.
559
-	 * @return string
560
-	 */
561
-	protected function maybe_render_actions( $row, $column_name ) {
562
-		if ( empty( $this->row_actions[ $column_name ] ) ) {
563
-			return;
564
-		}
565
-
566
-		$row_id = $row[ $this->ID ];
567
-
568
-		$actions      = '<div class="row-actions">';
569
-		$action_count = 0;
570
-		foreach ( $this->row_actions[ $column_name ] as $action_key => $action ) {
571
-
572
-			$action_count++;
573
-
574
-			if ( ! method_exists( $this, 'row_action_' . $action_key ) ) {
575
-				continue;
576
-			}
577
-
578
-			$action_link = ! empty( $action['link'] ) ? $action['link'] : add_query_arg(
579
-				array(
580
-					'row_action' => $action_key,
581
-					'row_id'     => $row_id,
582
-					'nonce'      => wp_create_nonce( $action_key . '::' . $row_id ),
583
-				)
584
-			);
585
-			$span_class  = ! empty( $action['class'] ) ? $action['class'] : $action_key;
586
-			$separator   = ( $action_count < count( $this->row_actions[ $column_name ] ) ) ? ' | ' : '';
587
-
588
-			$actions .= sprintf( '<span class="%s">', esc_attr( $span_class ) );
589
-			$actions .= sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', esc_url( $action_link ), esc_attr( $action['desc'] ), esc_html( $action['name'] ) );
590
-			$actions .= sprintf( '%s</span>', $separator );
591
-		}
592
-		$actions .= '</div>';
593
-		return $actions;
594
-	}
595
-
596
-	/**
597
-	 * Process the bulk actions.
598
-	 *
599
-	 * @return void
600
-	 */
601
-	protected function process_row_actions() {
602
-		$parameters = array( 'row_action', 'row_id', 'nonce' );
603
-		foreach ( $parameters as $parameter ) {
604
-			if ( empty( $_REQUEST[ $parameter ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
605
-				return;
606
-			}
607
-		}
608
-
609
-		$action = sanitize_text_field( wp_unslash( $_REQUEST['row_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
610
-		$row_id = sanitize_text_field( wp_unslash( $_REQUEST['row_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
611
-		$nonce  = sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
612
-		$method = 'row_action_' . $action; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
613
-
614
-		if ( wp_verify_nonce( $nonce, $action . '::' . $row_id ) && method_exists( $this, $method ) ) {
615
-			$this->$method( sanitize_text_field( wp_unslash( $row_id ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
616
-		}
617
-
618
-		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
619
-			wp_safe_redirect(
620
-				remove_query_arg(
621
-					array( 'row_id', 'row_action', 'nonce' ),
622
-					esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
623
-				)
624
-			);
625
-			exit;
626
-		}
627
-	}
628
-
629
-	/**
630
-	 * Default column formatting, it will escape everythig for security.
631
-	 *
632
-	 * @param array  $item The item array.
633
-	 * @param string $column_name Column name to display.
634
-	 *
635
-	 * @return string
636
-	 */
637
-	public function column_default( $item, $column_name ) {
638
-		$column_html  = esc_html( $item[ $column_name ] );
639
-		$column_html .= $this->maybe_render_actions( $item, $column_name );
640
-		return $column_html;
641
-	}
642
-
643
-	/**
644
-	 * Display the table heading and search query, if any
645
-	 */
646
-	protected function display_header() {
647
-		echo '<h1 class="wp-heading-inline">' . esc_attr( $this->table_header ) . '</h1>';
648
-		if ( $this->get_request_search_query() ) {
649
-			/* translators: %s: search query */
650
-			echo '<span class="subtitle">' . esc_attr( sprintf( __( 'Search results for "%s"', 'woocommerce' ), $this->get_request_search_query() ) ) . '</span>';
651
-		}
652
-		echo '<hr class="wp-header-end">';
653
-	}
654
-
655
-	/**
656
-	 * Display the table heading and search query, if any
657
-	 */
658
-	protected function display_admin_notices() {
659
-		foreach ( $this->admin_notices as $notice ) {
660
-			echo '<div id="message" class="' . esc_attr( $notice['class'] ) . '">';
661
-			echo '	<p>' . wp_kses_post( $notice['message'] ) . '</p>';
662
-			echo '</div>';
663
-		}
664
-	}
665
-
666
-	/**
667
-	 * Prints the available statuses so the user can click to filter.
668
-	 */
669
-	protected function display_filter_by_status() {
670
-
671
-		$status_list_items = array();
672
-		$request_status    = $this->get_request_status();
673
-
674
-		// Helper to set 'all' filter when not set on status counts passed in.
675
-		if ( ! isset( $this->status_counts['all'] ) ) {
676
-			$this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts;
677
-		}
678
-
679
-		foreach ( $this->status_counts as $status_name => $count ) {
680
-
681
-			if ( 0 === $count ) {
682
-				continue;
683
-			}
684
-
685
-			if ( $status_name === $request_status || ( empty( $request_status ) && 'all' === $status_name ) ) {
686
-				$status_list_item = '<li class="%1$s"><strong>%3$s</strong> (%4$d)</li>';
687
-			} else {
688
-				$status_list_item = '<li class="%1$s"><a href="%2$s">%3$s</a> (%4$d)</li>';
689
-			}
690
-
691
-			$status_filter_url   = ( 'all' === $status_name ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_name );
692
-			$status_filter_url   = remove_query_arg( array( 'paged', 's' ), $status_filter_url );
693
-			$status_list_items[] = sprintf( $status_list_item, esc_attr( $status_name ), esc_url( $status_filter_url ), esc_html( ucfirst( $status_name ) ), absint( $count ) );
694
-		}
695
-
696
-		if ( $status_list_items ) {
697
-			echo '<ul class="subsubsub">';
698
-			echo implode( " | \n", $status_list_items ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
699
-			echo '</ul>';
700
-		}
701
-	}
702
-
703
-	/**
704
-	 * Renders the table list, we override the original class to render the table inside a form
705
-	 * and to render any needed HTML (like the search box). By doing so the callee of a function can simple
706
-	 * forget about any extra HTML.
707
-	 */
708
-	protected function display_table() {
709
-		echo '<form id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="get">';
710
-		foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
711
-			if ( '_' === $key[0] || 'paged' === $key || 'ID' === $key ) {
712
-				continue;
713
-			}
714
-			echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $value ) . '" />';
715
-		}
716
-		if ( ! empty( $this->search_by ) ) {
717
-			echo $this->search_box( $this->get_search_box_button_text(), 'plugin' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
718
-		}
719
-		parent::display();
720
-		echo '</form>';
721
-	}
722
-
723
-	/**
724
-	 * Process any pending actions.
725
-	 */
726
-	public function process_actions() {
727
-		$this->process_bulk_action();
728
-		$this->process_row_actions();
729
-
730
-		if ( ! empty( $_REQUEST['_wp_http_referer'] ) && ! empty( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
731
-			// _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
732
-			wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
733
-			exit;
734
-		}
735
-	}
736
-
737
-	/**
738
-	 * Render the list table page, including header, notices, status filters and table.
739
-	 */
740
-	public function display_page() {
741
-		$this->prepare_items();
742
-
743
-		echo '<div class="wrap">';
744
-		$this->display_header();
745
-		$this->display_admin_notices();
746
-		$this->display_filter_by_status();
747
-		$this->display_table();
748
-		echo '</div>';
749
-	}
750
-
751
-	/**
752
-	 * Get the text to display in the search box on the list table.
753
-	 */
754
-	protected function get_search_box_placeholder() {
755
-		return esc_html__( 'Search', 'woocommerce' );
756
-	}
757
-
758
-	/**
759
-	 * Gets the screen per_page option name.
760
-	 *
761
-	 * @return string
762
-	 */
763
-	protected function get_per_page_option_name() {
764
-		return $this->package . '_items_per_page';
765
-	}
26
+    /**
27
+     * The table name
28
+     *
29
+     * @var string
30
+     */
31
+    protected $table_name;
32
+
33
+    /**
34
+     * Package name, used to get options from WP_List_Table::get_items_per_page.
35
+     *
36
+     * @var string
37
+     */
38
+    protected $package;
39
+
40
+    /**
41
+     * How many items do we render per page?
42
+     *
43
+     * @var int
44
+     */
45
+    protected $items_per_page = 10;
46
+
47
+    /**
48
+     * Enables search in this table listing. If this array
49
+     * is empty it means the listing is not searchable.
50
+     *
51
+     * @var array
52
+     */
53
+    protected $search_by = array();
54
+
55
+    /**
56
+     * Columns to show in the table listing. It is a key => value pair. The
57
+     * key must much the table column name and the value is the label, which is
58
+     * automatically translated.
59
+     *
60
+     * @var array
61
+     */
62
+    protected $columns = array();
63
+
64
+    /**
65
+     * Defines the row-actions. It expects an array where the key
66
+     * is the column name and the value is an array of actions.
67
+     *
68
+     * The array of actions are key => value, where key is the method name
69
+     * (with the prefix row_action_<key>) and the value is the label
70
+     * and title.
71
+     *
72
+     * @var array
73
+     */
74
+    protected $row_actions = array();
75
+
76
+    /**
77
+     * The Primary key of our table
78
+     *
79
+     * @var string
80
+     */
81
+    protected $ID = 'ID';
82
+
83
+    /**
84
+     * Enables sorting, it expects an array
85
+     * of columns (the column names are the values)
86
+     *
87
+     * @var array
88
+     */
89
+    protected $sort_by = array();
90
+
91
+    /**
92
+     * The default sort order
93
+     *
94
+     * @var string
95
+     */
96
+    protected $filter_by = array();
97
+
98
+    /**
99
+     * The status name => count combinations for this table's items. Used to display status filters.
100
+     *
101
+     * @var array
102
+     */
103
+    protected $status_counts = array();
104
+
105
+    /**
106
+     * Notices to display when loading the table. Array of arrays of form array( 'class' => {updated|error}, 'message' => 'This is the notice text display.' ).
107
+     *
108
+     * @var array
109
+     */
110
+    protected $admin_notices = array();
111
+
112
+    /**
113
+     * Localised string displayed in the <h1> element above the able.
114
+     *
115
+     * @var string
116
+     */
117
+    protected $table_header;
118
+
119
+    /**
120
+     * Enables bulk actions. It must be an array where the key is the action name
121
+     * and the value is the label (which is translated automatically). It is important
122
+     * to notice that it will check that the method exists (`bulk_$name`) and will throw
123
+     * an exception if it does not exists.
124
+     *
125
+     * This class will automatically check if the current request has a bulk action, will do the
126
+     * validations and afterwards will execute the bulk method, with two arguments. The first argument
127
+     * is the array with primary keys, the second argument is a string with a list of the primary keys,
128
+     * escaped and ready to use (with `IN`).
129
+     *
130
+     * @var array
131
+     */
132
+    protected $bulk_actions = array();
133
+
134
+    /**
135
+     * Makes translation easier, it basically just wraps
136
+     * `_x` with some default (the package name).
137
+     *
138
+     * @param string $text The new text to translate.
139
+     * @param string $context The context of the text.
140
+     * @return string|void The translated text.
141
+     *
142
+     * @deprecated 3.0.0 Use `_x()` instead.
143
+     */
144
+    protected function translate( $text, $context = '' ) {
145
+        return $text;
146
+    }
147
+
148
+    /**
149
+     * Reads `$this->bulk_actions` and returns an array that WP_List_Table understands. It
150
+     * also validates that the bulk method handler exists. It throws an exception because
151
+     * this is a library meant for developers and missing a bulk method is a development-time error.
152
+     *
153
+     * @return array
154
+     *
155
+     * @throws RuntimeException Throws RuntimeException when the bulk action does not have a callback method.
156
+     */
157
+    protected function get_bulk_actions() {
158
+        $actions = array();
159
+
160
+        foreach ( $this->bulk_actions as $action => $label ) {
161
+            if ( ! is_callable( array( $this, 'bulk_' . $action ) ) ) {
162
+                throw new RuntimeException( "The bulk action $action does not have a callback method" );
163
+            }
164
+
165
+            $actions[ $action ] = $label;
166
+        }
167
+
168
+        return $actions;
169
+    }
170
+
171
+    /**
172
+     * Checks if the current request has a bulk action. If that is the case it will validate and will
173
+     * execute the bulk method handler. Regardless if the action is valid or not it will redirect to
174
+     * the previous page removing the current arguments that makes this request a bulk action.
175
+     */
176
+    protected function process_bulk_action() {
177
+        global $wpdb;
178
+        // Detect when a bulk action is being triggered.
179
+        $action = $this->current_action();
180
+        if ( ! $action ) {
181
+            return;
182
+        }
183
+
184
+        check_admin_referer( 'bulk-' . $this->_args['plural'] );
185
+
186
+        $method = 'bulk_' . $action;
187
+        if ( array_key_exists( $action, $this->bulk_actions ) && is_callable( array( $this, $method ) ) && ! empty( $_GET['ID'] ) && is_array( $_GET['ID'] ) ) {
188
+            $ids_sql = '(' . implode( ',', array_fill( 0, count( $_GET['ID'] ), '%s' ) ) . ')';
189
+            $id      = array_map( 'absint', $_GET['ID'] );
190
+            $this->$method( $id, $wpdb->prepare( $ids_sql, $id ) ); //phpcs:ignore WordPress.DB.PreparedSQL
191
+        }
192
+
193
+        if ( isset( $_SERVER['REQUEST_URI'] ) ) {
194
+            wp_safe_redirect(
195
+                remove_query_arg(
196
+                    array( '_wp_http_referer', '_wpnonce', 'ID', 'action', 'action2' ),
197
+                    esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
198
+                )
199
+            );
200
+            exit;
201
+        }
202
+    }
203
+
204
+    /**
205
+     * Default code for deleting entries.
206
+     * validated already by process_bulk_action()
207
+     *
208
+     * @param array  $ids ids of the items to delete.
209
+     * @param string $ids_sql the sql for the ids.
210
+     * @return void
211
+     */
212
+    protected function bulk_delete( array $ids, $ids_sql ) {
213
+        $store = ActionScheduler::store();
214
+        foreach ( $ids as $action_id ) {
215
+            $store->delete( $action_id );
216
+        }
217
+    }
218
+
219
+    /**
220
+     * Prepares the _column_headers property which is used by WP_Table_List at rendering.
221
+     * It merges the columns and the sortable columns.
222
+     */
223
+    protected function prepare_column_headers() {
224
+        $this->_column_headers = array(
225
+            $this->get_columns(),
226
+            get_hidden_columns( $this->screen ),
227
+            $this->get_sortable_columns(),
228
+        );
229
+    }
230
+
231
+    /**
232
+     * Reads $this->sort_by and returns the columns name in a format that WP_Table_List
233
+     * expects
234
+     */
235
+    public function get_sortable_columns() {
236
+        $sort_by = array();
237
+        foreach ( $this->sort_by as $column ) {
238
+            $sort_by[ $column ] = array( $column, true );
239
+        }
240
+        return $sort_by;
241
+    }
242
+
243
+    /**
244
+     * Returns the columns names for rendering. It adds a checkbox for selecting everything
245
+     * as the first column
246
+     */
247
+    public function get_columns() {
248
+        $columns = array_merge(
249
+            array( 'cb' => '<input type="checkbox" />' ),
250
+            $this->columns
251
+        );
252
+
253
+        return $columns;
254
+    }
255
+
256
+    /**
257
+     * Get prepared LIMIT clause for items query
258
+     *
259
+     * @global wpdb $wpdb
260
+     *
261
+     * @return string Prepared LIMIT clause for items query.
262
+     */
263
+    protected function get_items_query_limit() {
264
+        global $wpdb;
265
+
266
+        $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
267
+        return $wpdb->prepare( 'LIMIT %d', $per_page );
268
+    }
269
+
270
+    /**
271
+     * Returns the number of items to offset/skip for this current view.
272
+     *
273
+     * @return int
274
+     */
275
+    protected function get_items_offset() {
276
+        $per_page     = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
277
+        $current_page = $this->get_pagenum();
278
+        if ( 1 < $current_page ) {
279
+            $offset = $per_page * ( $current_page - 1 );
280
+        } else {
281
+            $offset = 0;
282
+        }
283
+
284
+        return $offset;
285
+    }
286
+
287
+    /**
288
+     * Get prepared OFFSET clause for items query
289
+     *
290
+     * @global wpdb $wpdb
291
+     *
292
+     * @return string Prepared OFFSET clause for items query.
293
+     */
294
+    protected function get_items_query_offset() {
295
+        global $wpdb;
296
+
297
+        return $wpdb->prepare( 'OFFSET %d', $this->get_items_offset() );
298
+    }
299
+
300
+    /**
301
+     * Prepares the ORDER BY sql statement. It uses `$this->sort_by` to know which
302
+     * columns are sortable. This requests validates the orderby $_GET parameter is a valid
303
+     * column and sortable. It will also use order (ASC|DESC) using DESC by default.
304
+     */
305
+    protected function get_items_query_order() {
306
+        if ( empty( $this->sort_by ) ) {
307
+            return '';
308
+        }
309
+
310
+        $orderby = esc_sql( $this->get_request_orderby() );
311
+        $order   = esc_sql( $this->get_request_order() );
312
+
313
+        return "ORDER BY {$orderby} {$order}";
314
+    }
315
+
316
+    /**
317
+     * Return the sortable column specified for this request to order the results by, if any.
318
+     *
319
+     * @return string
320
+     */
321
+    protected function get_request_orderby() {
322
+
323
+        $valid_sortable_columns = array_values( $this->sort_by );
324
+
325
+        if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
326
+            $orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
327
+        } else {
328
+            $orderby = $valid_sortable_columns[0];
329
+        }
330
+
331
+        return $orderby;
332
+    }
333
+
334
+    /**
335
+     * Return the sortable column order specified for this request.
336
+     *
337
+     * @return string
338
+     */
339
+    protected function get_request_order() {
340
+
341
+        if ( ! empty( $_GET['order'] ) && 'desc' === strtolower( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
342
+            $order = 'DESC';
343
+        } else {
344
+            $order = 'ASC';
345
+        }
346
+
347
+        return $order;
348
+    }
349
+
350
+    /**
351
+     * Return the status filter for this request, if any.
352
+     *
353
+     * @return string
354
+     */
355
+    protected function get_request_status() {
356
+        $status = ( ! empty( $_GET['status'] ) ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
357
+        return $status;
358
+    }
359
+
360
+    /**
361
+     * Return the search filter for this request, if any.
362
+     *
363
+     * @return string
364
+     */
365
+    protected function get_request_search_query() {
366
+        $search_query = ( ! empty( $_GET['s'] ) ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
367
+        return $search_query;
368
+    }
369
+
370
+    /**
371
+     * Process and return the columns name. This is meant for using with SQL, this means it
372
+     * always includes the primary key.
373
+     *
374
+     * @return array
375
+     */
376
+    protected function get_table_columns() {
377
+        $columns = array_keys( $this->columns );
378
+        if ( ! in_array( $this->ID, $columns, true ) ) {
379
+            $columns[] = $this->ID;
380
+        }
381
+
382
+        return $columns;
383
+    }
384
+
385
+    /**
386
+     * Check if the current request is doing a "full text" search. If that is the case
387
+     * prepares the SQL to search texts using LIKE.
388
+     *
389
+     * If the current request does not have any search or if this list table does not support
390
+     * that feature it will return an empty string.
391
+     *
392
+     * @return string
393
+     */
394
+    protected function get_items_query_search() {
395
+        global $wpdb;
396
+
397
+        if ( empty( $_GET['s'] ) || empty( $this->search_by ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
398
+            return '';
399
+        }
400
+
401
+        $search_string = sanitize_text_field( wp_unslash( $_GET['s'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
402
+
403
+        $filter = array();
404
+        foreach ( $this->search_by as $column ) {
405
+            $wild     = '%';
406
+            $sql_like = $wild . $wpdb->esc_like( $search_string ) . $wild;
407
+            $filter[] = $wpdb->prepare( '`' . $column . '` LIKE %s', $sql_like ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.NotPrepared
408
+        }
409
+        return implode( ' OR ', $filter );
410
+    }
411
+
412
+    /**
413
+     * Prepares the SQL to filter rows by the options defined at `$this->filter_by`. Before trusting
414
+     * any data sent by the user it validates that it is a valid option.
415
+     */
416
+    protected function get_items_query_filters() {
417
+        global $wpdb;
418
+
419
+        if ( ! $this->filter_by || empty( $_GET['filter_by'] ) || ! is_array( $_GET['filter_by'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
420
+            return '';
421
+        }
422
+
423
+        $filter = array();
424
+
425
+        foreach ( $this->filter_by as $column => $options ) {
426
+            if ( empty( $_GET['filter_by'][ $column ] ) || empty( $options[ $_GET['filter_by'][ $column ] ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
427
+                continue;
428
+            }
429
+
430
+            $filter[] = $wpdb->prepare( "`$column` = %s", sanitize_text_field( wp_unslash( $_GET['filter_by'][ $column ] ) ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
431
+        }
432
+
433
+        return implode( ' AND ', $filter );
434
+
435
+    }
436
+
437
+    /**
438
+     * Prepares the data to feed WP_Table_List.
439
+     *
440
+     * This has the core for selecting, sorting and filting data. To keep the code simple
441
+     * its logic is split among many methods (get_items_query_*).
442
+     *
443
+     * Beside populating the items this function will also count all the records that matches
444
+     * the filtering criteria and will do fill the pagination variables.
445
+     */
446
+    public function prepare_items() {
447
+        global $wpdb;
448
+
449
+        $this->process_bulk_action();
450
+
451
+        $this->process_row_actions();
452
+
453
+        if ( ! empty( $_REQUEST['_wp_http_referer'] && ! empty( $_SERVER['REQUEST_URI'] ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
454
+            // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
455
+            wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
456
+            exit;
457
+        }
458
+
459
+        $this->prepare_column_headers();
460
+
461
+        $limit   = $this->get_items_query_limit();
462
+        $offset  = $this->get_items_query_offset();
463
+        $order   = $this->get_items_query_order();
464
+        $where   = array_filter(
465
+            array(
466
+                $this->get_items_query_search(),
467
+                $this->get_items_query_filters(),
468
+            )
469
+        );
470
+        $columns = '`' . implode( '`, `', $this->get_table_columns() ) . '`';
471
+
472
+        if ( ! empty( $where ) ) {
473
+            $where = 'WHERE (' . implode( ') AND (', $where ) . ')';
474
+        } else {
475
+            $where = '';
476
+        }
477
+
478
+        $sql = "SELECT $columns FROM {$this->table_name} {$where} {$order} {$limit} {$offset}";
479
+
480
+        $this->set_items( $wpdb->get_results( $sql, ARRAY_A ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
481
+
482
+        $query_count = "SELECT COUNT({$this->ID}) FROM {$this->table_name} {$where}";
483
+        $total_items = $wpdb->get_var( $query_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
484
+        $per_page    = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
485
+        $this->set_pagination_args(
486
+            array(
487
+                'total_items' => $total_items,
488
+                'per_page'    => $per_page,
489
+                'total_pages' => ceil( $total_items / $per_page ),
490
+            )
491
+        );
492
+    }
493
+
494
+    /**
495
+     * Display the table.
496
+     *
497
+     * @param string $which The name of the table.
498
+     */
499
+    public function extra_tablenav( $which ) {
500
+        if ( ! $this->filter_by || 'top' !== $which ) {
501
+            return;
502
+        }
503
+
504
+        echo '<div class="alignleft actions">';
505
+
506
+        foreach ( $this->filter_by as $id => $options ) {
507
+            $default = ! empty( $_GET['filter_by'][ $id ] ) ? sanitize_text_field( wp_unslash( $_GET['filter_by'][ $id ] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
508
+            if ( empty( $options[ $default ] ) ) {
509
+                $default = '';
510
+            }
511
+
512
+            echo '<select name="filter_by[' . esc_attr( $id ) . ']" class="first" id="filter-by-' . esc_attr( $id ) . '">';
513
+
514
+            foreach ( $options as $value => $label ) {
515
+                echo '<option value="' . esc_attr( $value ) . '" ' . esc_html( $value === $default ? 'selected' : '' ) . '>'
516
+                    . esc_html( $label )
517
+                . '</option>';
518
+            }
519
+
520
+            echo '</select>';
521
+        }
522
+
523
+        submit_button( esc_html__( 'Filter', 'woocommerce' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
524
+        echo '</div>';
525
+    }
526
+
527
+    /**
528
+     * Set the data for displaying. It will attempt to unserialize (There is a chance that some columns
529
+     * are serialized). This can be override in child classes for futher data transformation.
530
+     *
531
+     * @param array $items Items array.
532
+     */
533
+    protected function set_items( array $items ) {
534
+        $this->items = array();
535
+        foreach ( $items as $item ) {
536
+            $this->items[ $item[ $this->ID ] ] = array_map( 'maybe_unserialize', $item );
537
+        }
538
+    }
539
+
540
+    /**
541
+     * Renders the checkbox for each row, this is the first column and it is named ID regardless
542
+     * of how the primary key is named (to keep the code simpler). The bulk actions will do the proper
543
+     * name transformation though using `$this->ID`.
544
+     *
545
+     * @param array $row The row to render.
546
+     */
547
+    public function column_cb( $row ) {
548
+        return '<input name="ID[]" type="checkbox" value="' . esc_attr( $row[ $this->ID ] ) . '" />';
549
+    }
550
+
551
+    /**
552
+     * Renders the row-actions.
553
+     *
554
+     * This method renders the action menu, it reads the definition from the $row_actions property,
555
+     * and it checks that the row action method exists before rendering it.
556
+     *
557
+     * @param array  $row Row to be rendered.
558
+     * @param string $column_name Column name.
559
+     * @return string
560
+     */
561
+    protected function maybe_render_actions( $row, $column_name ) {
562
+        if ( empty( $this->row_actions[ $column_name ] ) ) {
563
+            return;
564
+        }
565
+
566
+        $row_id = $row[ $this->ID ];
567
+
568
+        $actions      = '<div class="row-actions">';
569
+        $action_count = 0;
570
+        foreach ( $this->row_actions[ $column_name ] as $action_key => $action ) {
571
+
572
+            $action_count++;
573
+
574
+            if ( ! method_exists( $this, 'row_action_' . $action_key ) ) {
575
+                continue;
576
+            }
577
+
578
+            $action_link = ! empty( $action['link'] ) ? $action['link'] : add_query_arg(
579
+                array(
580
+                    'row_action' => $action_key,
581
+                    'row_id'     => $row_id,
582
+                    'nonce'      => wp_create_nonce( $action_key . '::' . $row_id ),
583
+                )
584
+            );
585
+            $span_class  = ! empty( $action['class'] ) ? $action['class'] : $action_key;
586
+            $separator   = ( $action_count < count( $this->row_actions[ $column_name ] ) ) ? ' | ' : '';
587
+
588
+            $actions .= sprintf( '<span class="%s">', esc_attr( $span_class ) );
589
+            $actions .= sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', esc_url( $action_link ), esc_attr( $action['desc'] ), esc_html( $action['name'] ) );
590
+            $actions .= sprintf( '%s</span>', $separator );
591
+        }
592
+        $actions .= '</div>';
593
+        return $actions;
594
+    }
595
+
596
+    /**
597
+     * Process the bulk actions.
598
+     *
599
+     * @return void
600
+     */
601
+    protected function process_row_actions() {
602
+        $parameters = array( 'row_action', 'row_id', 'nonce' );
603
+        foreach ( $parameters as $parameter ) {
604
+            if ( empty( $_REQUEST[ $parameter ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
605
+                return;
606
+            }
607
+        }
608
+
609
+        $action = sanitize_text_field( wp_unslash( $_REQUEST['row_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
610
+        $row_id = sanitize_text_field( wp_unslash( $_REQUEST['row_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
611
+        $nonce  = sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
612
+        $method = 'row_action_' . $action; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
613
+
614
+        if ( wp_verify_nonce( $nonce, $action . '::' . $row_id ) && method_exists( $this, $method ) ) {
615
+            $this->$method( sanitize_text_field( wp_unslash( $row_id ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
616
+        }
617
+
618
+        if ( isset( $_SERVER['REQUEST_URI'] ) ) {
619
+            wp_safe_redirect(
620
+                remove_query_arg(
621
+                    array( 'row_id', 'row_action', 'nonce' ),
622
+                    esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
623
+                )
624
+            );
625
+            exit;
626
+        }
627
+    }
628
+
629
+    /**
630
+     * Default column formatting, it will escape everythig for security.
631
+     *
632
+     * @param array  $item The item array.
633
+     * @param string $column_name Column name to display.
634
+     *
635
+     * @return string
636
+     */
637
+    public function column_default( $item, $column_name ) {
638
+        $column_html  = esc_html( $item[ $column_name ] );
639
+        $column_html .= $this->maybe_render_actions( $item, $column_name );
640
+        return $column_html;
641
+    }
642
+
643
+    /**
644
+     * Display the table heading and search query, if any
645
+     */
646
+    protected function display_header() {
647
+        echo '<h1 class="wp-heading-inline">' . esc_attr( $this->table_header ) . '</h1>';
648
+        if ( $this->get_request_search_query() ) {
649
+            /* translators: %s: search query */
650
+            echo '<span class="subtitle">' . esc_attr( sprintf( __( 'Search results for "%s"', 'woocommerce' ), $this->get_request_search_query() ) ) . '</span>';
651
+        }
652
+        echo '<hr class="wp-header-end">';
653
+    }
654
+
655
+    /**
656
+     * Display the table heading and search query, if any
657
+     */
658
+    protected function display_admin_notices() {
659
+        foreach ( $this->admin_notices as $notice ) {
660
+            echo '<div id="message" class="' . esc_attr( $notice['class'] ) . '">';
661
+            echo '	<p>' . wp_kses_post( $notice['message'] ) . '</p>';
662
+            echo '</div>';
663
+        }
664
+    }
665
+
666
+    /**
667
+     * Prints the available statuses so the user can click to filter.
668
+     */
669
+    protected function display_filter_by_status() {
670
+
671
+        $status_list_items = array();
672
+        $request_status    = $this->get_request_status();
673
+
674
+        // Helper to set 'all' filter when not set on status counts passed in.
675
+        if ( ! isset( $this->status_counts['all'] ) ) {
676
+            $this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts;
677
+        }
678
+
679
+        foreach ( $this->status_counts as $status_name => $count ) {
680
+
681
+            if ( 0 === $count ) {
682
+                continue;
683
+            }
684
+
685
+            if ( $status_name === $request_status || ( empty( $request_status ) && 'all' === $status_name ) ) {
686
+                $status_list_item = '<li class="%1$s"><strong>%3$s</strong> (%4$d)</li>';
687
+            } else {
688
+                $status_list_item = '<li class="%1$s"><a href="%2$s">%3$s</a> (%4$d)</li>';
689
+            }
690
+
691
+            $status_filter_url   = ( 'all' === $status_name ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_name );
692
+            $status_filter_url   = remove_query_arg( array( 'paged', 's' ), $status_filter_url );
693
+            $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_name ), esc_url( $status_filter_url ), esc_html( ucfirst( $status_name ) ), absint( $count ) );
694
+        }
695
+
696
+        if ( $status_list_items ) {
697
+            echo '<ul class="subsubsub">';
698
+            echo implode( " | \n", $status_list_items ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
699
+            echo '</ul>';
700
+        }
701
+    }
702
+
703
+    /**
704
+     * Renders the table list, we override the original class to render the table inside a form
705
+     * and to render any needed HTML (like the search box). By doing so the callee of a function can simple
706
+     * forget about any extra HTML.
707
+     */
708
+    protected function display_table() {
709
+        echo '<form id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="get">';
710
+        foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
711
+            if ( '_' === $key[0] || 'paged' === $key || 'ID' === $key ) {
712
+                continue;
713
+            }
714
+            echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $value ) . '" />';
715
+        }
716
+        if ( ! empty( $this->search_by ) ) {
717
+            echo $this->search_box( $this->get_search_box_button_text(), 'plugin' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
718
+        }
719
+        parent::display();
720
+        echo '</form>';
721
+    }
722
+
723
+    /**
724
+     * Process any pending actions.
725
+     */
726
+    public function process_actions() {
727
+        $this->process_bulk_action();
728
+        $this->process_row_actions();
729
+
730
+        if ( ! empty( $_REQUEST['_wp_http_referer'] ) && ! empty( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
731
+            // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
732
+            wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
733
+            exit;
734
+        }
735
+    }
736
+
737
+    /**
738
+     * Render the list table page, including header, notices, status filters and table.
739
+     */
740
+    public function display_page() {
741
+        $this->prepare_items();
742
+
743
+        echo '<div class="wrap">';
744
+        $this->display_header();
745
+        $this->display_admin_notices();
746
+        $this->display_filter_by_status();
747
+        $this->display_table();
748
+        echo '</div>';
749
+    }
750
+
751
+    /**
752
+     * Get the text to display in the search box on the list table.
753
+     */
754
+    protected function get_search_box_placeholder() {
755
+        return esc_html__( 'Search', 'woocommerce' );
756
+    }
757
+
758
+    /**
759
+     * Gets the screen per_page option name.
760
+     *
761
+     * @return string
762
+     */
763
+    protected function get_per_page_option_name() {
764
+        return $this->package . '_items_per_page';
765
+    }
766 766
 }
Please login to merge, or discard this patch.
action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php 1 patch
Indentation   +157 added lines, -157 removed lines patch added patch discarded remove patch
@@ -12,161 +12,161 @@
 block discarded – undo
12 12
  */
13 13
 abstract class ActionScheduler_Abstract_Schema {
14 14
 
15
-	/**
16
-	 * @var int Increment this value in derived class to trigger a schema update.
17
-	 */
18
-	protected $schema_version = 1;
19
-
20
-	/**
21
-	 * @var string Schema version stored in database.
22
-	 */
23
-	protected $db_version;
24
-
25
-	/**
26
-	 * @var array Names of tables that will be registered by this class.
27
-	 */
28
-	protected $tables = [];
29
-
30
-	/**
31
-	 * Can optionally be used by concrete classes to carry out additional initialization work
32
-	 * as needed.
33
-	 */
34
-	public function init() {}
35
-
36
-	/**
37
-	 * Register tables with WordPress, and create them if needed.
38
-	 *
39
-	 * @param bool $force_update Optional. Default false. Use true to always run the schema update.
40
-	 *
41
-	 * @return void
42
-	 */
43
-	public function register_tables( $force_update = false ) {
44
-		global $wpdb;
45
-
46
-		// make WP aware of our tables
47
-		foreach ( $this->tables as $table ) {
48
-			$wpdb->tables[] = $table;
49
-			$name           = $this->get_full_table_name( $table );
50
-			$wpdb->$table   = $name;
51
-		}
52
-
53
-		// create the tables
54
-		if ( $this->schema_update_required() || $force_update ) {
55
-			foreach ( $this->tables as $table ) {
56
-				/**
57
-				 * Allow custom processing before updating a table schema.
58
-				 *
59
-				 * @param string $table Name of table being updated.
60
-				 * @param string $db_version Existing version of the table being updated.
61
-				 */
62
-				do_action( 'action_scheduler_before_schema_update', $table, $this->db_version );
63
-				$this->update_table( $table );
64
-			}
65
-			$this->mark_schema_update_complete();
66
-		}
67
-	}
68
-
69
-	/**
70
-	 * @param string $table The name of the table
71
-	 *
72
-	 * @return string The CREATE TABLE statement, suitable for passing to dbDelta
73
-	 */
74
-	abstract protected function get_table_definition( $table );
75
-
76
-	/**
77
-	 * Determine if the database schema is out of date
78
-	 * by comparing the integer found in $this->schema_version
79
-	 * with the option set in the WordPress options table
80
-	 *
81
-	 * @return bool
82
-	 */
83
-	private function schema_update_required() {
84
-		$option_name      = 'schema-' . static::class;
85
-		$this->db_version = get_option( $option_name, 0 );
86
-
87
-		// Check for schema option stored by the Action Scheduler Custom Tables plugin in case site has migrated from that plugin with an older schema
88
-		if ( 0 === $this->db_version ) {
89
-
90
-			$plugin_option_name = 'schema-';
91
-
92
-			switch ( static::class ) {
93
-				case 'ActionScheduler_StoreSchema' :
94
-					$plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Store_Table_Maker';
95
-					break;
96
-				case 'ActionScheduler_LoggerSchema' :
97
-					$plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Logger_Table_Maker';
98
-					break;
99
-			}
100
-
101
-			$this->db_version = get_option( $plugin_option_name, 0 );
102
-
103
-			delete_option( $plugin_option_name );
104
-		}
105
-
106
-		return version_compare( $this->db_version, $this->schema_version, '<' );
107
-	}
108
-
109
-	/**
110
-	 * Update the option in WordPress to indicate that
111
-	 * our schema is now up to date
112
-	 *
113
-	 * @return void
114
-	 */
115
-	private function mark_schema_update_complete() {
116
-		$option_name = 'schema-' . static::class;
117
-
118
-		// work around race conditions and ensure that our option updates
119
-		$value_to_save = (string) $this->schema_version . '.0.' . time();
120
-
121
-		update_option( $option_name, $value_to_save );
122
-	}
123
-
124
-	/**
125
-	 * Update the schema for the given table
126
-	 *
127
-	 * @param string $table The name of the table to update
128
-	 *
129
-	 * @return void
130
-	 */
131
-	private function update_table( $table ) {
132
-		require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
133
-		$definition = $this->get_table_definition( $table );
134
-		if ( $definition ) {
135
-			$updated = dbDelta( $definition );
136
-			foreach ( $updated as $updated_table => $update_description ) {
137
-				if ( strpos( $update_description, 'Created table' ) === 0 ) {
138
-					do_action( 'action_scheduler/created_table', $updated_table, $table );
139
-				}
140
-			}
141
-		}
142
-	}
143
-
144
-	/**
145
-	 * @param string $table
146
-	 *
147
-	 * @return string The full name of the table, including the
148
-	 *                table prefix for the current blog
149
-	 */
150
-	protected function get_full_table_name( $table ) {
151
-		return $GLOBALS[ 'wpdb' ]->prefix . $table;
152
-	}
153
-
154
-	/**
155
-	 * Confirms that all of the tables registered by this schema class have been created.
156
-	 *
157
-	 * @return bool
158
-	 */
159
-	public function tables_exist() {
160
-		global $wpdb;
161
-
162
-		$existing_tables = $wpdb->get_col( 'SHOW TABLES' );
163
-		$expected_tables = array_map(
164
-			function ( $table_name ) use ( $wpdb ) {
165
-				return $wpdb->prefix . $table_name;
166
-			},
167
-			$this->tables
168
-		);
169
-
170
-		return count( array_intersect( $existing_tables, $expected_tables ) ) === count( $expected_tables );
171
-	}
15
+    /**
16
+     * @var int Increment this value in derived class to trigger a schema update.
17
+     */
18
+    protected $schema_version = 1;
19
+
20
+    /**
21
+     * @var string Schema version stored in database.
22
+     */
23
+    protected $db_version;
24
+
25
+    /**
26
+     * @var array Names of tables that will be registered by this class.
27
+     */
28
+    protected $tables = [];
29
+
30
+    /**
31
+     * Can optionally be used by concrete classes to carry out additional initialization work
32
+     * as needed.
33
+     */
34
+    public function init() {}
35
+
36
+    /**
37
+     * Register tables with WordPress, and create them if needed.
38
+     *
39
+     * @param bool $force_update Optional. Default false. Use true to always run the schema update.
40
+     *
41
+     * @return void
42
+     */
43
+    public function register_tables( $force_update = false ) {
44
+        global $wpdb;
45
+
46
+        // make WP aware of our tables
47
+        foreach ( $this->tables as $table ) {
48
+            $wpdb->tables[] = $table;
49
+            $name           = $this->get_full_table_name( $table );
50
+            $wpdb->$table   = $name;
51
+        }
52
+
53
+        // create the tables
54
+        if ( $this->schema_update_required() || $force_update ) {
55
+            foreach ( $this->tables as $table ) {
56
+                /**
57
+                 * Allow custom processing before updating a table schema.
58
+                 *
59
+                 * @param string $table Name of table being updated.
60
+                 * @param string $db_version Existing version of the table being updated.
61
+                 */
62
+                do_action( 'action_scheduler_before_schema_update', $table, $this->db_version );
63
+                $this->update_table( $table );
64
+            }
65
+            $this->mark_schema_update_complete();
66
+        }
67
+    }
68
+
69
+    /**
70
+     * @param string $table The name of the table
71
+     *
72
+     * @return string The CREATE TABLE statement, suitable for passing to dbDelta
73
+     */
74
+    abstract protected function get_table_definition( $table );
75
+
76
+    /**
77
+     * Determine if the database schema is out of date
78
+     * by comparing the integer found in $this->schema_version
79
+     * with the option set in the WordPress options table
80
+     *
81
+     * @return bool
82
+     */
83
+    private function schema_update_required() {
84
+        $option_name      = 'schema-' . static::class;
85
+        $this->db_version = get_option( $option_name, 0 );
86
+
87
+        // Check for schema option stored by the Action Scheduler Custom Tables plugin in case site has migrated from that plugin with an older schema
88
+        if ( 0 === $this->db_version ) {
89
+
90
+            $plugin_option_name = 'schema-';
91
+
92
+            switch ( static::class ) {
93
+                case 'ActionScheduler_StoreSchema' :
94
+                    $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Store_Table_Maker';
95
+                    break;
96
+                case 'ActionScheduler_LoggerSchema' :
97
+                    $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Logger_Table_Maker';
98
+                    break;
99
+            }
100
+
101
+            $this->db_version = get_option( $plugin_option_name, 0 );
102
+
103
+            delete_option( $plugin_option_name );
104
+        }
105
+
106
+        return version_compare( $this->db_version, $this->schema_version, '<' );
107
+    }
108
+
109
+    /**
110
+     * Update the option in WordPress to indicate that
111
+     * our schema is now up to date
112
+     *
113
+     * @return void
114
+     */
115
+    private function mark_schema_update_complete() {
116
+        $option_name = 'schema-' . static::class;
117
+
118
+        // work around race conditions and ensure that our option updates
119
+        $value_to_save = (string) $this->schema_version . '.0.' . time();
120
+
121
+        update_option( $option_name, $value_to_save );
122
+    }
123
+
124
+    /**
125
+     * Update the schema for the given table
126
+     *
127
+     * @param string $table The name of the table to update
128
+     *
129
+     * @return void
130
+     */
131
+    private function update_table( $table ) {
132
+        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
133
+        $definition = $this->get_table_definition( $table );
134
+        if ( $definition ) {
135
+            $updated = dbDelta( $definition );
136
+            foreach ( $updated as $updated_table => $update_description ) {
137
+                if ( strpos( $update_description, 'Created table' ) === 0 ) {
138
+                    do_action( 'action_scheduler/created_table', $updated_table, $table );
139
+                }
140
+            }
141
+        }
142
+    }
143
+
144
+    /**
145
+     * @param string $table
146
+     *
147
+     * @return string The full name of the table, including the
148
+     *                table prefix for the current blog
149
+     */
150
+    protected function get_full_table_name( $table ) {
151
+        return $GLOBALS[ 'wpdb' ]->prefix . $table;
152
+    }
153
+
154
+    /**
155
+     * Confirms that all of the tables registered by this schema class have been created.
156
+     *
157
+     * @return bool
158
+     */
159
+    public function tables_exist() {
160
+        global $wpdb;
161
+
162
+        $existing_tables = $wpdb->get_col( 'SHOW TABLES' );
163
+        $expected_tables = array_map(
164
+            function ( $table_name ) use ( $wpdb ) {
165
+                return $wpdb->prefix . $table_name;
166
+            },
167
+            $this->tables
168
+        );
169
+
170
+        return count( array_intersect( $existing_tables, $expected_tables ) ) === count( $expected_tables );
171
+    }
172 172
 }
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.