Passed
Push — master ( 60cf75...7ee9b2 )
by Brian
128:01 queued 122:23
created
classes/data-stores/ActionScheduler_wpPostStore_PostStatusRegistrar.php 1 patch
Indentation   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -5,54 +5,54 @@
 block discarded – undo
5 5
  * @codeCoverageIgnore
6 6
  */
7 7
 class ActionScheduler_wpPostStore_PostStatusRegistrar {
8
-	public function register() {
9
-		register_post_status( ActionScheduler_Store::STATUS_RUNNING, array_merge( $this->post_status_args(), $this->post_status_running_labels() ) );
10
-		register_post_status( ActionScheduler_Store::STATUS_FAILED, array_merge( $this->post_status_args(), $this->post_status_failed_labels() ) );
11
-	}
8
+    public function register() {
9
+        register_post_status( ActionScheduler_Store::STATUS_RUNNING, array_merge( $this->post_status_args(), $this->post_status_running_labels() ) );
10
+        register_post_status( ActionScheduler_Store::STATUS_FAILED, array_merge( $this->post_status_args(), $this->post_status_failed_labels() ) );
11
+    }
12 12
 
13
-	/**
14
-	 * Build the args array for the post type definition
15
-	 *
16
-	 * @return array
17
-	 */
18
-	protected function post_status_args() {
19
-		$args = array(
20
-			'public'                    => false,
21
-			'exclude_from_search'       => false,
22
-			'show_in_admin_all_list'    => true,
23
-			'show_in_admin_status_list' => true,
24
-		);
13
+    /**
14
+     * Build the args array for the post type definition
15
+     *
16
+     * @return array
17
+     */
18
+    protected function post_status_args() {
19
+        $args = array(
20
+            'public'                    => false,
21
+            'exclude_from_search'       => false,
22
+            'show_in_admin_all_list'    => true,
23
+            'show_in_admin_status_list' => true,
24
+        );
25 25
 
26
-		return apply_filters( 'action_scheduler_post_status_args', $args );
27
-	}
26
+        return apply_filters( 'action_scheduler_post_status_args', $args );
27
+    }
28 28
 
29
-	/**
30
-	 * Build the args array for the post type definition
31
-	 *
32
-	 * @return array
33
-	 */
34
-	protected function post_status_failed_labels() {
35
-		$labels = array(
36
-			'label'       => _x( 'Failed', 'post', 'action-scheduler' ),
37
-			/* translators: %s: count */
38
-			'label_count' => _n_noop( 'Failed <span class="count">(%s)</span>', 'Failed <span class="count">(%s)</span>', 'action-scheduler' ),
39
-		);
29
+    /**
30
+     * Build the args array for the post type definition
31
+     *
32
+     * @return array
33
+     */
34
+    protected function post_status_failed_labels() {
35
+        $labels = array(
36
+            'label'       => _x( 'Failed', 'post', 'action-scheduler' ),
37
+            /* translators: %s: count */
38
+            'label_count' => _n_noop( 'Failed <span class="count">(%s)</span>', 'Failed <span class="count">(%s)</span>', 'action-scheduler' ),
39
+        );
40 40
 
41
-		return apply_filters( 'action_scheduler_post_status_failed_labels', $labels );
42
-	}
41
+        return apply_filters( 'action_scheduler_post_status_failed_labels', $labels );
42
+    }
43 43
 
44
-	/**
45
-	 * Build the args array for the post type definition
46
-	 *
47
-	 * @return array
48
-	 */
49
-	protected function post_status_running_labels() {
50
-		$labels = array(
51
-			'label'       => _x( 'In-Progress', 'post', 'action-scheduler' ),
52
-			/* translators: %s: count */
53
-			'label_count' => _n_noop( 'In-Progress <span class="count">(%s)</span>', 'In-Progress <span class="count">(%s)</span>', 'action-scheduler' ),
54
-		);
44
+    /**
45
+     * Build the args array for the post type definition
46
+     *
47
+     * @return array
48
+     */
49
+    protected function post_status_running_labels() {
50
+        $labels = array(
51
+            'label'       => _x( 'In-Progress', 'post', 'action-scheduler' ),
52
+            /* translators: %s: count */
53
+            'label_count' => _n_noop( 'In-Progress <span class="count">(%s)</span>', 'In-Progress <span class="count">(%s)</span>', 'action-scheduler' ),
54
+        );
55 55
 
56
-		return apply_filters( 'action_scheduler_post_status_running_labels', $labels );
57
-	}
56
+        return apply_filters( 'action_scheduler_post_status_running_labels', $labels );
57
+    }
58 58
 }
Please login to merge, or discard this patch.
action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php 1 patch
Indentation   +360 added lines, -360 removed lines patch added patch discarded remove patch
@@ -13,368 +13,368 @@
 block discarded – undo
13 13
  * @since 3.0.0
14 14
  */
15 15
 class ActionScheduler_HybridStore extends Store {
16
-	const DEMARKATION_OPTION = 'action_scheduler_hybrid_store_demarkation';
17
-
18
-	private $primary_store;
19
-	private $secondary_store;
20
-	private $migration_runner;
21
-
22
-	/**
23
-	 * @var int The dividing line between IDs of actions created
24
-	 *          by the primary and secondary stores.
25
-	 *
26
-	 * Methods that accept an action ID will compare the ID against
27
-	 * this to determine which store will contain that ID. In almost
28
-	 * all cases, the ID should come from the primary store, but if
29
-	 * client code is bypassing the API functions and fetching IDs
30
-	 * from elsewhere, then there is a chance that an unmigrated ID
31
-	 * might be requested.
32
-	 */
33
-	private $demarkation_id = 0;
34
-
35
-	/**
36
-	 * ActionScheduler_HybridStore constructor.
37
-	 *
38
-	 * @param Config $config Migration config object.
39
-	 */
40
-	public function __construct( Config $config = null ) {
41
-		$this->demarkation_id = (int) get_option( self::DEMARKATION_OPTION, 0 );
42
-		if ( empty( $config ) ) {
43
-			$config = Controller::instance()->get_migration_config_object();
44
-		}
45
-		$this->primary_store    = $config->get_destination_store();
46
-		$this->secondary_store  = $config->get_source_store();
47
-		$this->migration_runner = new Runner( $config );
48
-	}
49
-
50
-	/**
51
-	 * Initialize the table data store tables.
52
-	 *
53
-	 * @codeCoverageIgnore
54
-	 */
55
-	public function init() {
56
-		add_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10, 2 );
57
-		$this->primary_store->init();
58
-		$this->secondary_store->init();
59
-		remove_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10 );
60
-	}
61
-
62
-	/**
63
-	 * When the actions table is created, set its autoincrement
64
-	 * value to be one higher than the posts table to ensure that
65
-	 * there are no ID collisions.
66
-	 *
67
-	 * @param string $table_name
68
-	 * @param string $table_suffix
69
-	 *
70
-	 * @return void
71
-	 * @codeCoverageIgnore
72
-	 */
73
-	public function set_autoincrement( $table_name, $table_suffix ) {
74
-		if ( ActionScheduler_StoreSchema::ACTIONS_TABLE === $table_suffix ) {
75
-			if ( empty( $this->demarkation_id ) ) {
76
-				$this->demarkation_id = $this->set_demarkation_id();
77
-			}
78
-			/** @var \wpdb $wpdb */
79
-			global $wpdb;
80
-			$wpdb->insert(
81
-				$wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
82
-				[
83
-					'action_id' => $this->demarkation_id,
84
-					'hook'      => '',
85
-					'status'    => '',
86
-				]
87
-			);
88
-			$wpdb->delete(
89
-				$wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
90
-				[ 'action_id' => $this->demarkation_id ]
91
-			);
92
-		}
93
-	}
94
-
95
-	/**
96
-	 * Store the demarkation id in WP options.
97
-	 *
98
-	 * @param int $id The ID to set as the demarkation point between the two stores
99
-	 *                Leave null to use the next ID from the WP posts table.
100
-	 *
101
-	 * @return int The new ID.
102
-	 *
103
-	 * @codeCoverageIgnore
104
-	 */
105
-	private function set_demarkation_id( $id = null ) {
106
-		if ( empty( $id ) ) {
107
-			/** @var \wpdb $wpdb */
108
-			global $wpdb;
109
-			$id = (int) $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" );
110
-			$id ++;
111
-		}
112
-		update_option( self::DEMARKATION_OPTION, $id );
113
-
114
-		return $id;
115
-	}
116
-
117
-	/**
118
-	 * Find the first matching action from the secondary store.
119
-	 * If it exists, migrate it to the primary store immediately.
120
-	 * After it migrates, the secondary store will logically contain
121
-	 * the next matching action, so return the result thence.
122
-	 *
123
-	 * @param string $hook
124
-	 * @param array  $params
125
-	 *
126
-	 * @return string
127
-	 */
128
-	public function find_action( $hook, $params = [] ) {
129
-		$found_unmigrated_action = $this->secondary_store->find_action( $hook, $params );
130
-		if ( ! empty( $found_unmigrated_action ) ) {
131
-			$this->migrate( [ $found_unmigrated_action ] );
132
-		}
133
-
134
-		return $this->primary_store->find_action( $hook, $params );
135
-	}
136
-
137
-	/**
138
-	 * Find actions matching the query in the secondary source first.
139
-	 * If any are found, migrate them immediately. Then the secondary
140
-	 * store will contain the canonical results.
141
-	 *
142
-	 * @param array $query
143
-	 * @param string $query_type Whether to select or count the results. Default, select.
144
-	 *
145
-	 * @return int[]
146
-	 */
147
-	public function query_actions( $query = [], $query_type = 'select' ) {
148
-		$found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' );
149
-		if ( ! empty( $found_unmigrated_actions ) ) {
150
-			$this->migrate( $found_unmigrated_actions );
151
-		}
152
-
153
-		return $this->primary_store->query_actions( $query, $query_type );
154
-	}
155
-
156
-	/**
157
-	 * Get a count of all actions in the store, grouped by status
158
-	 *
159
-	 * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status.
160
-	 */
161
-	public function action_counts() {
162
-		$unmigrated_actions_count = $this->secondary_store->action_counts();
163
-		$migrated_actions_count   = $this->primary_store->action_counts();
164
-		$actions_count_by_status  = array();
165
-
166
-		foreach ( $this->get_status_labels() as $status_key => $status_label ) {
167
-
168
-			$count = 0;
169
-
170
-			if ( isset( $unmigrated_actions_count[ $status_key ] ) ) {
171
-				$count += $unmigrated_actions_count[ $status_key ];
172
-			}
173
-
174
-			if ( isset( $migrated_actions_count[ $status_key ] ) ) {
175
-				$count += $migrated_actions_count[ $status_key ];
176
-			}
177
-
178
-			$actions_count_by_status[ $status_key ] = $count;
179
-		}
180
-
181
-		$actions_count_by_status = array_filter( $actions_count_by_status );
182
-
183
-		return $actions_count_by_status;
184
-	}
185
-
186
-	/**
187
-	 * If any actions would have been claimed by the secondary store,
188
-	 * migrate them immediately, then ask the primary store for the
189
-	 * canonical claim.
190
-	 *
191
-	 * @param int           $max_actions
192
-	 * @param DateTime|null $before_date
193
-	 *
194
-	 * @return ActionScheduler_ActionClaim
195
-	 */
196
-	public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
197
-		$claim = $this->secondary_store->stake_claim( $max_actions, $before_date, $hooks, $group );
198
-
199
-		$claimed_actions = $claim->get_actions();
200
-		if ( ! empty( $claimed_actions ) ) {
201
-			$this->migrate( $claimed_actions );
202
-		}
203
-
204
-		$this->secondary_store->release_claim( $claim );
205
-
206
-		return $this->primary_store->stake_claim( $max_actions, $before_date, $hooks, $group );
207
-	}
208
-
209
-	/**
210
-	 * Migrate a list of actions to the table data store.
211
-	 *
212
-	 * @param array $action_ids List of action IDs.
213
-	 */
214
-	private function migrate( $action_ids ) {
215
-		$this->migration_runner->migrate_actions( $action_ids );
216
-	}
217
-
218
-	/**
219
-	 * Save an action to the primary store.
220
-	 *
221
-	 * @param ActionScheduler_Action $action Action object to be saved.
222
-	 * @param DateTime               $date Optional. Schedule date. Default null.
223
-	 */
224
-	public function save_action( ActionScheduler_Action $action, DateTime $date = null ) {
225
-		return $this->primary_store->save_action( $action, $date );
226
-	}
227
-
228
-	/**
229
-	 * Retrieve an existing action whether migrated or not.
230
-	 *
231
-	 * @param int $action_id Action ID.
232
-	 */
233
-	public function fetch_action( $action_id ) {
234
-		if ( $action_id < $this->demarkation_id ) {
235
-			return $this->secondary_store->fetch_action( $action_id );
236
-		} else {
237
-			return $this->primary_store->fetch_action( $action_id );
238
-		}
239
-	}
240
-
241
-	/**
242
-	 * Cancel an existing action whether migrated or not.
243
-	 *
244
-	 * @param int $action_id Action ID.
245
-	 */
246
-	public function cancel_action( $action_id ) {
247
-		if ( $action_id < $this->demarkation_id ) {
248
-			$this->secondary_store->cancel_action( $action_id );
249
-		} else {
250
-			$this->primary_store->cancel_action( $action_id );
251
-		}
252
-	}
253
-
254
-	/**
255
-	 * Delete an existing action whether migrated or not.
256
-	 *
257
-	 * @param int $action_id Action ID.
258
-	 */
259
-	public function delete_action( $action_id ) {
260
-		if ( $action_id < $this->demarkation_id ) {
261
-			$this->secondary_store->delete_action( $action_id );
262
-		} else {
263
-			$this->primary_store->delete_action( $action_id );
264
-		}
265
-	}
266
-
267
-	/**
268
-	 * Get the schedule date an existing action whether migrated or not.
269
-	 *
270
-	 * @param int $action_id Action ID.
271
-	 */
272
-	public function get_date( $action_id ) {
273
-		if ( $action_id < $this->demarkation_id ) {
274
-			return $this->secondary_store->get_date( $action_id );
275
-		} else {
276
-			return $this->primary_store->get_date( $action_id );
277
-		}
278
-	}
279
-
280
-	/**
281
-	 * Mark an existing action as failed whether migrated or not.
282
-	 *
283
-	 * @param int $action_id Action ID.
284
-	 */
285
-	public function mark_failure( $action_id ) {
286
-		if ( $action_id < $this->demarkation_id ) {
287
-			$this->secondary_store->mark_failure( $action_id );
288
-		} else {
289
-			$this->primary_store->mark_failure( $action_id );
290
-		}
291
-	}
292
-
293
-	/**
294
-	 * Log the execution of an existing action whether migrated or not.
295
-	 *
296
-	 * @param int $action_id Action ID.
297
-	 */
298
-	public function log_execution( $action_id ) {
299
-		if ( $action_id < $this->demarkation_id ) {
300
-			$this->secondary_store->log_execution( $action_id );
301
-		} else {
302
-			$this->primary_store->log_execution( $action_id );
303
-		}
304
-	}
305
-
306
-	/**
307
-	 * Mark an existing action complete whether migrated or not.
308
-	 *
309
-	 * @param int $action_id Action ID.
310
-	 */
311
-	public function mark_complete( $action_id ) {
312
-		if ( $action_id < $this->demarkation_id ) {
313
-			$this->secondary_store->mark_complete( $action_id );
314
-		} else {
315
-			$this->primary_store->mark_complete( $action_id );
316
-		}
317
-	}
318
-
319
-	/**
320
-	 * Get an existing action status whether migrated or not.
321
-	 *
322
-	 * @param int $action_id Action ID.
323
-	 */
324
-	public function get_status( $action_id ) {
325
-		if ( $action_id < $this->demarkation_id ) {
326
-			return $this->secondary_store->get_status( $action_id );
327
-		} else {
328
-			return $this->primary_store->get_status( $action_id );
329
-		}
330
-	}
331
-
332
-
333
-	/* * * * * * * * * * * * * * * * * * * * * * * * * * *
16
+    const DEMARKATION_OPTION = 'action_scheduler_hybrid_store_demarkation';
17
+
18
+    private $primary_store;
19
+    private $secondary_store;
20
+    private $migration_runner;
21
+
22
+    /**
23
+     * @var int The dividing line between IDs of actions created
24
+     *          by the primary and secondary stores.
25
+     *
26
+     * Methods that accept an action ID will compare the ID against
27
+     * this to determine which store will contain that ID. In almost
28
+     * all cases, the ID should come from the primary store, but if
29
+     * client code is bypassing the API functions and fetching IDs
30
+     * from elsewhere, then there is a chance that an unmigrated ID
31
+     * might be requested.
32
+     */
33
+    private $demarkation_id = 0;
34
+
35
+    /**
36
+     * ActionScheduler_HybridStore constructor.
37
+     *
38
+     * @param Config $config Migration config object.
39
+     */
40
+    public function __construct( Config $config = null ) {
41
+        $this->demarkation_id = (int) get_option( self::DEMARKATION_OPTION, 0 );
42
+        if ( empty( $config ) ) {
43
+            $config = Controller::instance()->get_migration_config_object();
44
+        }
45
+        $this->primary_store    = $config->get_destination_store();
46
+        $this->secondary_store  = $config->get_source_store();
47
+        $this->migration_runner = new Runner( $config );
48
+    }
49
+
50
+    /**
51
+     * Initialize the table data store tables.
52
+     *
53
+     * @codeCoverageIgnore
54
+     */
55
+    public function init() {
56
+        add_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10, 2 );
57
+        $this->primary_store->init();
58
+        $this->secondary_store->init();
59
+        remove_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10 );
60
+    }
61
+
62
+    /**
63
+     * When the actions table is created, set its autoincrement
64
+     * value to be one higher than the posts table to ensure that
65
+     * there are no ID collisions.
66
+     *
67
+     * @param string $table_name
68
+     * @param string $table_suffix
69
+     *
70
+     * @return void
71
+     * @codeCoverageIgnore
72
+     */
73
+    public function set_autoincrement( $table_name, $table_suffix ) {
74
+        if ( ActionScheduler_StoreSchema::ACTIONS_TABLE === $table_suffix ) {
75
+            if ( empty( $this->demarkation_id ) ) {
76
+                $this->demarkation_id = $this->set_demarkation_id();
77
+            }
78
+            /** @var \wpdb $wpdb */
79
+            global $wpdb;
80
+            $wpdb->insert(
81
+                $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
82
+                [
83
+                    'action_id' => $this->demarkation_id,
84
+                    'hook'      => '',
85
+                    'status'    => '',
86
+                ]
87
+            );
88
+            $wpdb->delete(
89
+                $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
90
+                [ 'action_id' => $this->demarkation_id ]
91
+            );
92
+        }
93
+    }
94
+
95
+    /**
96
+     * Store the demarkation id in WP options.
97
+     *
98
+     * @param int $id The ID to set as the demarkation point between the two stores
99
+     *                Leave null to use the next ID from the WP posts table.
100
+     *
101
+     * @return int The new ID.
102
+     *
103
+     * @codeCoverageIgnore
104
+     */
105
+    private function set_demarkation_id( $id = null ) {
106
+        if ( empty( $id ) ) {
107
+            /** @var \wpdb $wpdb */
108
+            global $wpdb;
109
+            $id = (int) $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" );
110
+            $id ++;
111
+        }
112
+        update_option( self::DEMARKATION_OPTION, $id );
113
+
114
+        return $id;
115
+    }
116
+
117
+    /**
118
+     * Find the first matching action from the secondary store.
119
+     * If it exists, migrate it to the primary store immediately.
120
+     * After it migrates, the secondary store will logically contain
121
+     * the next matching action, so return the result thence.
122
+     *
123
+     * @param string $hook
124
+     * @param array  $params
125
+     *
126
+     * @return string
127
+     */
128
+    public function find_action( $hook, $params = [] ) {
129
+        $found_unmigrated_action = $this->secondary_store->find_action( $hook, $params );
130
+        if ( ! empty( $found_unmigrated_action ) ) {
131
+            $this->migrate( [ $found_unmigrated_action ] );
132
+        }
133
+
134
+        return $this->primary_store->find_action( $hook, $params );
135
+    }
136
+
137
+    /**
138
+     * Find actions matching the query in the secondary source first.
139
+     * If any are found, migrate them immediately. Then the secondary
140
+     * store will contain the canonical results.
141
+     *
142
+     * @param array $query
143
+     * @param string $query_type Whether to select or count the results. Default, select.
144
+     *
145
+     * @return int[]
146
+     */
147
+    public function query_actions( $query = [], $query_type = 'select' ) {
148
+        $found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' );
149
+        if ( ! empty( $found_unmigrated_actions ) ) {
150
+            $this->migrate( $found_unmigrated_actions );
151
+        }
152
+
153
+        return $this->primary_store->query_actions( $query, $query_type );
154
+    }
155
+
156
+    /**
157
+     * Get a count of all actions in the store, grouped by status
158
+     *
159
+     * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status.
160
+     */
161
+    public function action_counts() {
162
+        $unmigrated_actions_count = $this->secondary_store->action_counts();
163
+        $migrated_actions_count   = $this->primary_store->action_counts();
164
+        $actions_count_by_status  = array();
165
+
166
+        foreach ( $this->get_status_labels() as $status_key => $status_label ) {
167
+
168
+            $count = 0;
169
+
170
+            if ( isset( $unmigrated_actions_count[ $status_key ] ) ) {
171
+                $count += $unmigrated_actions_count[ $status_key ];
172
+            }
173
+
174
+            if ( isset( $migrated_actions_count[ $status_key ] ) ) {
175
+                $count += $migrated_actions_count[ $status_key ];
176
+            }
177
+
178
+            $actions_count_by_status[ $status_key ] = $count;
179
+        }
180
+
181
+        $actions_count_by_status = array_filter( $actions_count_by_status );
182
+
183
+        return $actions_count_by_status;
184
+    }
185
+
186
+    /**
187
+     * If any actions would have been claimed by the secondary store,
188
+     * migrate them immediately, then ask the primary store for the
189
+     * canonical claim.
190
+     *
191
+     * @param int           $max_actions
192
+     * @param DateTime|null $before_date
193
+     *
194
+     * @return ActionScheduler_ActionClaim
195
+     */
196
+    public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
197
+        $claim = $this->secondary_store->stake_claim( $max_actions, $before_date, $hooks, $group );
198
+
199
+        $claimed_actions = $claim->get_actions();
200
+        if ( ! empty( $claimed_actions ) ) {
201
+            $this->migrate( $claimed_actions );
202
+        }
203
+
204
+        $this->secondary_store->release_claim( $claim );
205
+
206
+        return $this->primary_store->stake_claim( $max_actions, $before_date, $hooks, $group );
207
+    }
208
+
209
+    /**
210
+     * Migrate a list of actions to the table data store.
211
+     *
212
+     * @param array $action_ids List of action IDs.
213
+     */
214
+    private function migrate( $action_ids ) {
215
+        $this->migration_runner->migrate_actions( $action_ids );
216
+    }
217
+
218
+    /**
219
+     * Save an action to the primary store.
220
+     *
221
+     * @param ActionScheduler_Action $action Action object to be saved.
222
+     * @param DateTime               $date Optional. Schedule date. Default null.
223
+     */
224
+    public function save_action( ActionScheduler_Action $action, DateTime $date = null ) {
225
+        return $this->primary_store->save_action( $action, $date );
226
+    }
227
+
228
+    /**
229
+     * Retrieve an existing action whether migrated or not.
230
+     *
231
+     * @param int $action_id Action ID.
232
+     */
233
+    public function fetch_action( $action_id ) {
234
+        if ( $action_id < $this->demarkation_id ) {
235
+            return $this->secondary_store->fetch_action( $action_id );
236
+        } else {
237
+            return $this->primary_store->fetch_action( $action_id );
238
+        }
239
+    }
240
+
241
+    /**
242
+     * Cancel an existing action whether migrated or not.
243
+     *
244
+     * @param int $action_id Action ID.
245
+     */
246
+    public function cancel_action( $action_id ) {
247
+        if ( $action_id < $this->demarkation_id ) {
248
+            $this->secondary_store->cancel_action( $action_id );
249
+        } else {
250
+            $this->primary_store->cancel_action( $action_id );
251
+        }
252
+    }
253
+
254
+    /**
255
+     * Delete an existing action whether migrated or not.
256
+     *
257
+     * @param int $action_id Action ID.
258
+     */
259
+    public function delete_action( $action_id ) {
260
+        if ( $action_id < $this->demarkation_id ) {
261
+            $this->secondary_store->delete_action( $action_id );
262
+        } else {
263
+            $this->primary_store->delete_action( $action_id );
264
+        }
265
+    }
266
+
267
+    /**
268
+     * Get the schedule date an existing action whether migrated or not.
269
+     *
270
+     * @param int $action_id Action ID.
271
+     */
272
+    public function get_date( $action_id ) {
273
+        if ( $action_id < $this->demarkation_id ) {
274
+            return $this->secondary_store->get_date( $action_id );
275
+        } else {
276
+            return $this->primary_store->get_date( $action_id );
277
+        }
278
+    }
279
+
280
+    /**
281
+     * Mark an existing action as failed whether migrated or not.
282
+     *
283
+     * @param int $action_id Action ID.
284
+     */
285
+    public function mark_failure( $action_id ) {
286
+        if ( $action_id < $this->demarkation_id ) {
287
+            $this->secondary_store->mark_failure( $action_id );
288
+        } else {
289
+            $this->primary_store->mark_failure( $action_id );
290
+        }
291
+    }
292
+
293
+    /**
294
+     * Log the execution of an existing action whether migrated or not.
295
+     *
296
+     * @param int $action_id Action ID.
297
+     */
298
+    public function log_execution( $action_id ) {
299
+        if ( $action_id < $this->demarkation_id ) {
300
+            $this->secondary_store->log_execution( $action_id );
301
+        } else {
302
+            $this->primary_store->log_execution( $action_id );
303
+        }
304
+    }
305
+
306
+    /**
307
+     * Mark an existing action complete whether migrated or not.
308
+     *
309
+     * @param int $action_id Action ID.
310
+     */
311
+    public function mark_complete( $action_id ) {
312
+        if ( $action_id < $this->demarkation_id ) {
313
+            $this->secondary_store->mark_complete( $action_id );
314
+        } else {
315
+            $this->primary_store->mark_complete( $action_id );
316
+        }
317
+    }
318
+
319
+    /**
320
+     * Get an existing action status whether migrated or not.
321
+     *
322
+     * @param int $action_id Action ID.
323
+     */
324
+    public function get_status( $action_id ) {
325
+        if ( $action_id < $this->demarkation_id ) {
326
+            return $this->secondary_store->get_status( $action_id );
327
+        } else {
328
+            return $this->primary_store->get_status( $action_id );
329
+        }
330
+    }
331
+
332
+
333
+    /* * * * * * * * * * * * * * * * * * * * * * * * * * *
334 334
 	 * All claim-related functions should operate solely
335 335
 	 * on the primary store.
336 336
 	 * * * * * * * * * * * * * * * * * * * * * * * * * * */
337 337
 
338
-	/**
339
-	 * Get the claim count from the table data store.
340
-	 */
341
-	public function get_claim_count() {
342
-		return $this->primary_store->get_claim_count();
343
-	}
344
-
345
-	/**
346
-	 * Retrieve the claim ID for an action from the table data store.
347
-	 *
348
-	 * @param int $action_id Action ID.
349
-	 */
350
-	public function get_claim_id( $action_id ) {
351
-		return $this->primary_store->get_claim_id( $action_id );
352
-	}
353
-
354
-	/**
355
-	 * Release a claim in the table data store.
356
-	 *
357
-	 * @param ActionScheduler_ActionClaim $claim Claim object.
358
-	 */
359
-	public function release_claim( ActionScheduler_ActionClaim $claim ) {
360
-		$this->primary_store->release_claim( $claim );
361
-	}
362
-
363
-	/**
364
-	 * Release claims on an action in the table data store.
365
-	 *
366
-	 * @param int $action_id Action ID.
367
-	 */
368
-	public function unclaim_action( $action_id ) {
369
-		$this->primary_store->unclaim_action( $action_id );
370
-	}
371
-
372
-	/**
373
-	 * Retrieve a list of action IDs by claim.
374
-	 *
375
-	 * @param int $claim_id Claim ID.
376
-	 */
377
-	public function find_actions_by_claim_id( $claim_id ) {
378
-		return $this->primary_store->find_actions_by_claim_id( $claim_id );
379
-	}
338
+    /**
339
+     * Get the claim count from the table data store.
340
+     */
341
+    public function get_claim_count() {
342
+        return $this->primary_store->get_claim_count();
343
+    }
344
+
345
+    /**
346
+     * Retrieve the claim ID for an action from the table data store.
347
+     *
348
+     * @param int $action_id Action ID.
349
+     */
350
+    public function get_claim_id( $action_id ) {
351
+        return $this->primary_store->get_claim_id( $action_id );
352
+    }
353
+
354
+    /**
355
+     * Release a claim in the table data store.
356
+     *
357
+     * @param ActionScheduler_ActionClaim $claim Claim object.
358
+     */
359
+    public function release_claim( ActionScheduler_ActionClaim $claim ) {
360
+        $this->primary_store->release_claim( $claim );
361
+    }
362
+
363
+    /**
364
+     * Release claims on an action in the table data store.
365
+     *
366
+     * @param int $action_id Action ID.
367
+     */
368
+    public function unclaim_action( $action_id ) {
369
+        $this->primary_store->unclaim_action( $action_id );
370
+    }
371
+
372
+    /**
373
+     * Retrieve a list of action IDs by claim.
374
+     *
375
+     * @param int $claim_id Claim ID.
376
+     */
377
+    public function find_actions_by_claim_id( $claim_id ) {
378
+        return $this->primary_store->find_actions_by_claim_id( $claim_id );
379
+    }
380 380
 }
Please login to merge, or discard this patch.
libraries/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php 1 patch
Indentation   +134 added lines, -134 removed lines patch added patch discarded remove patch
@@ -9,138 +9,138 @@
 block discarded – undo
9 9
  */
10 10
 class ActionScheduler_DBLogger extends ActionScheduler_Logger {
11 11
 
12
-	/**
13
-	 * Add a record to an action log.
14
-	 *
15
-	 * @param int      $action_id Action ID.
16
-	 * @param string   $message Message to be saved in the log entry.
17
-	 * @param DateTime $date Timestamp of the log entry.
18
-	 *
19
-	 * @return int     The log entry ID.
20
-	 */
21
-	public function log( $action_id, $message, DateTime $date = null ) {
22
-		if ( empty( $date ) ) {
23
-			$date = as_get_datetime_object();
24
-		} else {
25
-			$date = clone $date;
26
-		}
27
-
28
-		$date_gmt = $date->format( 'Y-m-d H:i:s' );
29
-		ActionScheduler_TimezoneHelper::set_local_timezone( $date );
30
-		$date_local = $date->format( 'Y-m-d H:i:s' );
31
-
32
-		/** @var \wpdb $wpdb */
33
-		global $wpdb;
34
-		$wpdb->insert( $wpdb->actionscheduler_logs, [
35
-			'action_id'      => $action_id,
36
-			'message'        => $message,
37
-			'log_date_gmt'   => $date_gmt,
38
-			'log_date_local' => $date_local,
39
-		], [ '%d', '%s', '%s', '%s' ] );
40
-
41
-		return $wpdb->insert_id;
42
-	}
43
-
44
-	/**
45
-	 * Retrieve an action log entry.
46
-	 *
47
-	 * @param int $entry_id Log entry ID.
48
-	 *
49
-	 * @return ActionScheduler_LogEntry
50
-	 */
51
-	public function get_entry( $entry_id ) {
52
-		/** @var \wpdb $wpdb */
53
-		global $wpdb;
54
-		$entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE log_id=%d", $entry_id ) );
55
-
56
-		return $this->create_entry_from_db_record( $entry );
57
-	}
58
-
59
-	/**
60
-	 * Create an action log entry from a database record.
61
-	 *
62
-	 * @param object $record Log entry database record object.
63
-	 *
64
-	 * @return ActionScheduler_LogEntry
65
-	 */
66
-	private function create_entry_from_db_record( $record ) {
67
-		if ( empty( $record ) ) {
68
-			return new ActionScheduler_NullLogEntry();
69
-		}
70
-
71
-		$date = as_get_datetime_object( $record->log_date_gmt );
72
-
73
-		return new ActionScheduler_LogEntry( $record->action_id, $record->message, $date );
74
-	}
75
-
76
-	/**
77
-	 * Retrieve the an action's log entries from the database.
78
-	 *
79
-	 * @param int $action_id Action ID.
80
-	 *
81
-	 * @return ActionScheduler_LogEntry[]
82
-	 */
83
-	public function get_logs( $action_id ) {
84
-		/** @var \wpdb $wpdb */
85
-		global $wpdb;
86
-
87
-		$records = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE action_id=%d", $action_id ) );
88
-
89
-		return array_map( [ $this, 'create_entry_from_db_record' ], $records );
90
-	}
91
-
92
-	/**
93
-	 * Initialize the data store.
94
-	 *
95
-	 * @codeCoverageIgnore
96
-	 */
97
-	public function init() {
98
-
99
-		$table_maker = new ActionScheduler_LoggerSchema();
100
-		$table_maker->register_tables();
101
-
102
-		parent::init();
103
-
104
-		add_action( 'action_scheduler_deleted_action', [ $this, 'clear_deleted_action_logs' ], 10, 1 );
105
-	}
106
-
107
-	/**
108
-	 * Delete the action logs for an action.
109
-	 *
110
-	 * @param int $action_id Action ID.
111
-	 */
112
-	public function clear_deleted_action_logs( $action_id ) {
113
-		/** @var \wpdb $wpdb */
114
-		global $wpdb;
115
-		$wpdb->delete( $wpdb->actionscheduler_logs, [ 'action_id' => $action_id, ], [ '%d' ] );
116
-	}
117
-
118
-	/**
119
-	 * Bulk add cancel action log entries.
120
-	 *
121
-	 * @param array $action_ids List of action ID.
122
-	 */
123
-	public function bulk_log_cancel_actions( $action_ids ) {
124
-		if ( empty( $action_ids ) ) {
125
-			return;
126
-		}
127
-
128
-		/** @var \wpdb $wpdb */
129
-		global $wpdb;
130
-		$date     = as_get_datetime_object();
131
-		$date_gmt = $date->format( 'Y-m-d H:i:s' );
132
-		ActionScheduler_TimezoneHelper::set_local_timezone( $date );
133
-		$date_local = $date->format( 'Y-m-d H:i:s' );
134
-		$message    = __( 'action canceled', 'action-scheduler' );
135
-		$format     = '(%d, ' . $wpdb->prepare( '%s, %s, %s', $message, $date_gmt, $date_local ) . ')';
136
-		$sql_query  = "INSERT {$wpdb->actionscheduler_logs} (action_id, message, log_date_gmt, log_date_local) VALUES ";
137
-		$value_rows = [];
138
-
139
-		foreach ( $action_ids as $action_id ) {
140
-			$value_rows[] = $wpdb->prepare( $format, $action_id );
141
-		}
142
-		$sql_query .= implode( ',', $value_rows );
143
-
144
-		$wpdb->query( $sql_query );
145
-	}
12
+    /**
13
+     * Add a record to an action log.
14
+     *
15
+     * @param int      $action_id Action ID.
16
+     * @param string   $message Message to be saved in the log entry.
17
+     * @param DateTime $date Timestamp of the log entry.
18
+     *
19
+     * @return int     The log entry ID.
20
+     */
21
+    public function log( $action_id, $message, DateTime $date = null ) {
22
+        if ( empty( $date ) ) {
23
+            $date = as_get_datetime_object();
24
+        } else {
25
+            $date = clone $date;
26
+        }
27
+
28
+        $date_gmt = $date->format( 'Y-m-d H:i:s' );
29
+        ActionScheduler_TimezoneHelper::set_local_timezone( $date );
30
+        $date_local = $date->format( 'Y-m-d H:i:s' );
31
+
32
+        /** @var \wpdb $wpdb */
33
+        global $wpdb;
34
+        $wpdb->insert( $wpdb->actionscheduler_logs, [
35
+            'action_id'      => $action_id,
36
+            'message'        => $message,
37
+            'log_date_gmt'   => $date_gmt,
38
+            'log_date_local' => $date_local,
39
+        ], [ '%d', '%s', '%s', '%s' ] );
40
+
41
+        return $wpdb->insert_id;
42
+    }
43
+
44
+    /**
45
+     * Retrieve an action log entry.
46
+     *
47
+     * @param int $entry_id Log entry ID.
48
+     *
49
+     * @return ActionScheduler_LogEntry
50
+     */
51
+    public function get_entry( $entry_id ) {
52
+        /** @var \wpdb $wpdb */
53
+        global $wpdb;
54
+        $entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE log_id=%d", $entry_id ) );
55
+
56
+        return $this->create_entry_from_db_record( $entry );
57
+    }
58
+
59
+    /**
60
+     * Create an action log entry from a database record.
61
+     *
62
+     * @param object $record Log entry database record object.
63
+     *
64
+     * @return ActionScheduler_LogEntry
65
+     */
66
+    private function create_entry_from_db_record( $record ) {
67
+        if ( empty( $record ) ) {
68
+            return new ActionScheduler_NullLogEntry();
69
+        }
70
+
71
+        $date = as_get_datetime_object( $record->log_date_gmt );
72
+
73
+        return new ActionScheduler_LogEntry( $record->action_id, $record->message, $date );
74
+    }
75
+
76
+    /**
77
+     * Retrieve the an action's log entries from the database.
78
+     *
79
+     * @param int $action_id Action ID.
80
+     *
81
+     * @return ActionScheduler_LogEntry[]
82
+     */
83
+    public function get_logs( $action_id ) {
84
+        /** @var \wpdb $wpdb */
85
+        global $wpdb;
86
+
87
+        $records = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE action_id=%d", $action_id ) );
88
+
89
+        return array_map( [ $this, 'create_entry_from_db_record' ], $records );
90
+    }
91
+
92
+    /**
93
+     * Initialize the data store.
94
+     *
95
+     * @codeCoverageIgnore
96
+     */
97
+    public function init() {
98
+
99
+        $table_maker = new ActionScheduler_LoggerSchema();
100
+        $table_maker->register_tables();
101
+
102
+        parent::init();
103
+
104
+        add_action( 'action_scheduler_deleted_action', [ $this, 'clear_deleted_action_logs' ], 10, 1 );
105
+    }
106
+
107
+    /**
108
+     * Delete the action logs for an action.
109
+     *
110
+     * @param int $action_id Action ID.
111
+     */
112
+    public function clear_deleted_action_logs( $action_id ) {
113
+        /** @var \wpdb $wpdb */
114
+        global $wpdb;
115
+        $wpdb->delete( $wpdb->actionscheduler_logs, [ 'action_id' => $action_id, ], [ '%d' ] );
116
+    }
117
+
118
+    /**
119
+     * Bulk add cancel action log entries.
120
+     *
121
+     * @param array $action_ids List of action ID.
122
+     */
123
+    public function bulk_log_cancel_actions( $action_ids ) {
124
+        if ( empty( $action_ids ) ) {
125
+            return;
126
+        }
127
+
128
+        /** @var \wpdb $wpdb */
129
+        global $wpdb;
130
+        $date     = as_get_datetime_object();
131
+        $date_gmt = $date->format( 'Y-m-d H:i:s' );
132
+        ActionScheduler_TimezoneHelper::set_local_timezone( $date );
133
+        $date_local = $date->format( 'Y-m-d H:i:s' );
134
+        $message    = __( 'action canceled', 'action-scheduler' );
135
+        $format     = '(%d, ' . $wpdb->prepare( '%s, %s, %s', $message, $date_gmt, $date_local ) . ')';
136
+        $sql_query  = "INSERT {$wpdb->actionscheduler_logs} (action_id, message, log_date_gmt, log_date_local) VALUES ";
137
+        $value_rows = [];
138
+
139
+        foreach ( $action_ids as $action_id ) {
140
+            $value_rows[] = $wpdb->prepare( $format, $action_id );
141
+        }
142
+        $sql_query .= implode( ',', $value_rows );
143
+
144
+        $wpdb->query( $sql_query );
145
+    }
146 146
 }
Please login to merge, or discard this patch.
classes/data-stores/ActionScheduler_wpPostStore_TaxonomyRegistrar.php 1 patch
Indentation   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -5,22 +5,22 @@
 block discarded – undo
5 5
  * @codeCoverageIgnore
6 6
  */
7 7
 class ActionScheduler_wpPostStore_TaxonomyRegistrar {
8
-	public function register() {
9
-		register_taxonomy( ActionScheduler_wpPostStore::GROUP_TAXONOMY, ActionScheduler_wpPostStore::POST_TYPE, $this->taxonomy_args() );
10
-	}
8
+    public function register() {
9
+        register_taxonomy( ActionScheduler_wpPostStore::GROUP_TAXONOMY, ActionScheduler_wpPostStore::POST_TYPE, $this->taxonomy_args() );
10
+    }
11 11
 
12
-	protected function taxonomy_args() {
13
-		$args = array(
14
-			'label' => __( 'Action Group', 'action-scheduler' ),
15
-			'public' => false,
16
-			'hierarchical' => false,
17
-			'show_admin_column' => true,
18
-			'query_var' => false,
19
-			'rewrite' => false,
20
-		);
12
+    protected function taxonomy_args() {
13
+        $args = array(
14
+            'label' => __( 'Action Group', 'action-scheduler' ),
15
+            'public' => false,
16
+            'hierarchical' => false,
17
+            'show_admin_column' => true,
18
+            'query_var' => false,
19
+            'rewrite' => false,
20
+        );
21 21
 
22
-		$args = apply_filters( 'action_scheduler_taxonomy_args', $args );
23
-		return $args;
24
-	}
22
+        $args = apply_filters( 'action_scheduler_taxonomy_args', $args );
23
+        return $args;
24
+    }
25 25
 }
26
- 
27 26
\ No newline at end of file
27
+    
28 28
\ No newline at end of file
Please login to merge, or discard this patch.
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', 'action-scheduler' ),
20
-			'description' => __( 'Scheduled actions are hooks triggered on a cetain date and time.', 'action-scheduler' ),
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', 'action-scheduler' ),
31
-				'singular_name' => __( 'Scheduled Action', 'action-scheduler' ),
32
-				'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'action-scheduler' ),
33
-				'add_new' => __( 'Add', 'action-scheduler' ),
34
-				'add_new_item' => __( 'Add New Scheduled Action', 'action-scheduler' ),
35
-				'edit' => __( 'Edit', 'action-scheduler' ),
36
-				'edit_item' => __( 'Edit Scheduled Action', 'action-scheduler' ),
37
-				'new_item' => __( 'New Scheduled Action', 'action-scheduler' ),
38
-				'view' => __( 'View Action', 'action-scheduler' ),
39
-				'view_item' => __( 'View Action', 'action-scheduler' ),
40
-				'search_items' => __( 'Search Scheduled Actions', 'action-scheduler' ),
41
-				'not_found' => __( 'No actions found', 'action-scheduler' ),
42
-				'not_found_in_trash' => __( 'No actions found in trash', 'action-scheduler' ),
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', 'action-scheduler' ),
20
+            'description' => __( 'Scheduled actions are hooks triggered on a cetain date and time.', 'action-scheduler' ),
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', 'action-scheduler' ),
31
+                'singular_name' => __( 'Scheduled Action', 'action-scheduler' ),
32
+                'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'action-scheduler' ),
33
+                'add_new' => __( 'Add', 'action-scheduler' ),
34
+                'add_new_item' => __( 'Add New Scheduled Action', 'action-scheduler' ),
35
+                'edit' => __( 'Edit', 'action-scheduler' ),
36
+                'edit_item' => __( 'Edit Scheduled Action', 'action-scheduler' ),
37
+                'new_item' => __( 'New Scheduled Action', 'action-scheduler' ),
38
+                'view' => __( 'View Action', 'action-scheduler' ),
39
+                'view_item' => __( 'View Action', 'action-scheduler' ),
40
+                'search_items' => __( 'Search Scheduled Actions', 'action-scheduler' ),
41
+                'not_found' => __( 'No actions found', 'action-scheduler' ),
42
+                'not_found_in_trash' => __( 'No actions found in trash', 'action-scheduler' ),
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.
libraries/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php 1 patch
Indentation   +833 added lines, -833 removed lines patch added patch discarded remove patch
@@ -9,837 +9,837 @@
 block discarded – undo
9 9
  */
10 10
 class ActionScheduler_DBStore extends ActionScheduler_Store {
11 11
 
12
-	/** @var int */
13
-	protected static $max_args_length = 8000;
14
-
15
-	/** @var int */
16
-	protected static $max_index_length = 191;
17
-
18
-	/**
19
-	 * Initialize the data store
20
-	 *
21
-	 * @codeCoverageIgnore
22
-	 */
23
-	public function init() {
24
-		$table_maker = new ActionScheduler_StoreSchema();
25
-		$table_maker->register_tables();
26
-	}
27
-
28
-	/**
29
-	 * Save an action.
30
-	 *
31
-	 * @param ActionScheduler_Action $action Action object.
32
-	 * @param DateTime               $date Optional schedule date. Default null.
33
-	 *
34
-	 * @return int Action ID.
35
-	 */
36
-	public function save_action( ActionScheduler_Action $action, \DateTime $date = null ) {
37
-		try {
38
-
39
-			$this->validate_action( $action );
40
-
41
-			/** @var \wpdb $wpdb */
42
-			global $wpdb;
43
-			$data = [
44
-				'hook'                 => $action->get_hook(),
45
-				'status'               => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ),
46
-				'scheduled_date_gmt'   => $this->get_scheduled_date_string( $action, $date ),
47
-				'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ),
48
-				'schedule'             => serialize( $action->get_schedule() ),
49
-				'group_id'             => $this->get_group_id( $action->get_group() ),
50
-			];
51
-			$args = wp_json_encode( $action->get_args() );
52
-			if ( strlen( $args ) <= static::$max_index_length ) {
53
-				$data['args'] = $args;
54
-			} else {
55
-				$data['args']          = $this->hash_args( $args );
56
-				$data['extended_args'] = $args;
57
-			}
58
-
59
-			$table_name = ! empty( $wpdb->actionscheduler_actions ) ? $wpdb->actionscheduler_actions : $wpdb->prefix . 'actionscheduler_actions';
60
-			$wpdb->insert( $table_name, $data );
61
-			$action_id = $wpdb->insert_id;
62
-
63
-			if ( is_wp_error( $action_id ) ) {
64
-				throw new RuntimeException( $action_id->get_error_message() );
65
-			}
66
-			elseif ( empty( $action_id ) ) {
67
-				throw new RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'action-scheduler' ) );
68
-			}
69
-
70
-			do_action( 'action_scheduler_stored_action', $action_id );
71
-
72
-			return $action_id;
73
-		} catch ( \Exception $e ) {
74
-			/* translators: %s: error message */
75
-			throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
76
-		}
77
-	}
78
-
79
-	/**
80
-	 * Generate a hash from json_encoded $args using MD5 as this isn't for security.
81
-	 *
82
-	 * @param string $args JSON encoded action args.
83
-	 * @return string
84
-	 */
85
-	protected function hash_args( $args ) {
86
-		return md5( $args );
87
-	}
88
-
89
-	/**
90
-	 * Get action args query param value from action args.
91
-	 *
92
-	 * @param array $args Action args.
93
-	 * @return string
94
-	 */
95
-	protected function get_args_for_query( $args ) {
96
-		$encoded = wp_json_encode( $args );
97
-		if ( strlen( $encoded ) <= static::$max_index_length ) {
98
-			return $encoded;
99
-		}
100
-		return $this->hash_args( $encoded );
101
-	}
102
-	/**
103
-	 * Get a group's ID based on its name/slug.
104
-	 *
105
-	 * @param string $slug The string name of a group.
106
-	 * @param bool $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group.
107
-	 *
108
-	 * @return int The group's ID, if it exists or is created, or 0 if it does not exist and is not created.
109
-	 */
110
-	protected function get_group_id( $slug, $create_if_not_exists = true ) {
111
-		if ( empty( $slug ) ) {
112
-			return 0;
113
-		}
114
-		/** @var \wpdb $wpdb */
115
-		global $wpdb;
116
-		$group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) );
117
-		if ( empty( $group_id ) && $create_if_not_exists ) {
118
-			$group_id = $this->create_group( $slug );
119
-		}
120
-
121
-		return $group_id;
122
-	}
123
-
124
-	/**
125
-	 * Create an action group.
126
-	 *
127
-	 * @param string $slug Group slug.
128
-	 *
129
-	 * @return int Group ID.
130
-	 */
131
-	protected function create_group( $slug ) {
132
-		/** @var \wpdb $wpdb */
133
-		global $wpdb;
134
-		$wpdb->insert( $wpdb->actionscheduler_groups, [ 'slug' => $slug ] );
135
-
136
-		return (int) $wpdb->insert_id;
137
-	}
138
-
139
-	/**
140
-	 * Retrieve an action.
141
-	 *
142
-	 * @param int $action_id Action ID.
143
-	 *
144
-	 * @return ActionScheduler_Action
145
-	 */
146
-	public function fetch_action( $action_id ) {
147
-		/** @var \wpdb $wpdb */
148
-		global $wpdb;
149
-		$data = $wpdb->get_row( $wpdb->prepare(
150
-			"SELECT a.*, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.action_id=%d",
151
-			$action_id
152
-		) );
153
-
154
-		if ( empty( $data ) ) {
155
-			return $this->get_null_action();
156
-		}
157
-
158
-		if ( ! empty( $data->extended_args ) ) {
159
-			$data->args = $data->extended_args;
160
-			unset( $data->extended_args );
161
-		}
162
-
163
-		try {
164
-			$action = $this->make_action_from_db_record( $data );
165
-		} catch ( ActionScheduler_InvalidActionException $exception ) {
166
-			do_action( 'action_scheduler_failed_fetch_action', $action_id, $exception );
167
-			return $this->get_null_action();
168
-		}
169
-
170
-		return $action;
171
-	}
172
-
173
-	/**
174
-	 * Create a null action.
175
-	 *
176
-	 * @return ActionScheduler_NullAction
177
-	 */
178
-	protected function get_null_action() {
179
-		return new ActionScheduler_NullAction();
180
-	}
181
-
182
-	/**
183
-	 * Create an action from a database record.
184
-	 *
185
-	 * @param object $data Action database record.
186
-	 *
187
-	 * @return ActionScheduler_Action|ActionScheduler_CanceledAction|ActionScheduler_FinishedAction
188
-	 */
189
-	protected function make_action_from_db_record( $data ) {
190
-
191
-		$hook     = $data->hook;
192
-		$args     = json_decode( $data->args, true );
193
-		$schedule = unserialize( $data->schedule );
194
-
195
-		$this->validate_args( $args, $data->action_id );
196
-		$this->validate_schedule( $schedule, $data->action_id );
197
-
198
-		if ( empty( $schedule ) ) {
199
-			$schedule = new ActionScheduler_NullSchedule();
200
-		}
201
-		$group = $data->group ? $data->group : '';
202
-
203
-		return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group );
204
-	}
205
-
206
-	/**
207
-	 * Find an action.
208
-	 *
209
-	 * @param string $hook Action hook.
210
-	 * @param array  $params Parameters of the action to find.
211
-	 *
212
-	 * @return string|null ID of the next action matching the criteria or NULL if not found.
213
-	 */
214
-	public function find_action( $hook, $params = [] ) {
215
-		$params = wp_parse_args( $params, [
216
-			'args'   => null,
217
-			'status' => self::STATUS_PENDING,
218
-			'group'  => '',
219
-		] );
220
-
221
-		/** @var wpdb $wpdb */
222
-		global $wpdb;
223
-		$query = "SELECT a.action_id FROM {$wpdb->actionscheduler_actions} a";
224
-		$args  = [];
225
-		if ( ! empty( $params[ 'group' ] ) ) {
226
-			$query  .= " INNER JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id AND g.slug=%s";
227
-			$args[] = $params[ 'group' ];
228
-		}
229
-		$query  .= " WHERE a.hook=%s";
230
-		$args[] = $hook;
231
-		if ( ! is_null( $params[ 'args' ] ) ) {
232
-			$query  .= " AND a.args=%s";
233
-			$args[] = $this->get_args_for_query( $params[ 'args' ] );
234
-		}
235
-
236
-		$order = 'ASC';
237
-		if ( ! empty( $params[ 'status' ] ) ) {
238
-			$query  .= " AND a.status=%s";
239
-			$args[] = $params[ 'status' ];
240
-
241
-			if ( self::STATUS_PENDING == $params[ 'status' ] ) {
242
-				$order = 'ASC'; // Find the next action that matches.
243
-			} else {
244
-				$order = 'DESC'; // Find the most recent action that matches.
245
-			}
246
-		}
247
-
248
-		$query .= " ORDER BY scheduled_date_gmt $order LIMIT 1";
249
-
250
-		$query = $wpdb->prepare( $query, $args );
251
-
252
-		$id = $wpdb->get_var( $query );
253
-
254
-		return $id;
255
-	}
256
-
257
-	/**
258
-	 * Returns the SQL statement to query (or count) actions.
259
-	 *
260
-	 * @param array  $query Filtering options.
261
-	 * @param string $select_or_count  Whether the SQL should select and return the IDs or just the row count.
262
-	 *
263
-	 * @return string SQL statement already properly escaped.
264
-	 */
265
-	protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
266
-
267
-		if ( ! in_array( $select_or_count, array( 'select', 'count' ) ) ) {
268
-			throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'action-scheduler' ) );
269
-		}
270
-
271
-		$query = wp_parse_args( $query, [
272
-			'hook'             => '',
273
-			'args'             => null,
274
-			'date'             => null,
275
-			'date_compare'     => '<=',
276
-			'modified'         => null,
277
-			'modified_compare' => '<=',
278
-			'group'            => '',
279
-			'status'           => '',
280
-			'claimed'          => null,
281
-			'per_page'         => 5,
282
-			'offset'           => 0,
283
-			'orderby'          => 'date',
284
-			'order'            => 'ASC',
285
-		] );
286
-
287
-		/** @var \wpdb $wpdb */
288
-		global $wpdb;
289
-		$sql  = ( 'count' === $select_or_count ) ? 'SELECT count(a.action_id)' : 'SELECT a.action_id';
290
-		$sql .= " FROM {$wpdb->actionscheduler_actions} a";
291
-		$sql_params = [];
292
-
293
-		if ( ! empty( $query[ 'group' ] ) || 'group' === $query[ 'orderby' ] ) {
294
-			$sql .= " LEFT JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id";
295
-		}
296
-
297
-		$sql .= " WHERE 1=1";
298
-
299
-		if ( ! empty( $query[ 'group' ] ) ) {
300
-			$sql          .= " AND g.slug=%s";
301
-			$sql_params[] = $query[ 'group' ];
302
-		}
303
-
304
-		if ( $query[ 'hook' ] ) {
305
-			$sql          .= " AND a.hook=%s";
306
-			$sql_params[] = $query[ 'hook' ];
307
-		}
308
-		if ( ! is_null( $query[ 'args' ] ) ) {
309
-			$sql          .= " AND a.args=%s";
310
-			$sql_params[] = $this->get_args_for_query( $query[ 'args' ] );
311
-		}
312
-
313
-		if ( $query[ 'status' ] ) {
314
-			$sql          .= " AND a.status=%s";
315
-			$sql_params[] = $query[ 'status' ];
316
-		}
317
-
318
-		if ( $query[ 'date' ] instanceof \DateTime ) {
319
-			$date = clone $query[ 'date' ];
320
-			$date->setTimezone( new \DateTimeZone( 'UTC' ) );
321
-			$date_string  = $date->format( 'Y-m-d H:i:s' );
322
-			$comparator   = $this->validate_sql_comparator( $query[ 'date_compare' ] );
323
-			$sql          .= " AND a.scheduled_date_gmt $comparator %s";
324
-			$sql_params[] = $date_string;
325
-		}
326
-
327
-		if ( $query[ 'modified' ] instanceof \DateTime ) {
328
-			$modified = clone $query[ 'modified' ];
329
-			$modified->setTimezone( new \DateTimeZone( 'UTC' ) );
330
-			$date_string  = $modified->format( 'Y-m-d H:i:s' );
331
-			$comparator   = $this->validate_sql_comparator( $query[ 'modified_compare' ] );
332
-			$sql          .= " AND a.last_attempt_gmt $comparator %s";
333
-			$sql_params[] = $date_string;
334
-		}
335
-
336
-		if ( $query[ 'claimed' ] === true ) {
337
-			$sql .= " AND a.claim_id != 0";
338
-		} elseif ( $query[ 'claimed' ] === false ) {
339
-			$sql .= " AND a.claim_id = 0";
340
-		} elseif ( ! is_null( $query[ 'claimed' ] ) ) {
341
-			$sql          .= " AND a.claim_id = %d";
342
-			$sql_params[] = $query[ 'claimed' ];
343
-		}
344
-
345
-		if ( ! empty( $query['search'] ) ) {
346
-			$sql .= " AND (a.hook LIKE %s OR (a.extended_args IS NULL AND a.args LIKE %s) OR a.extended_args LIKE %s";
347
-			for( $i = 0; $i < 3; $i++ ) {
348
-				$sql_params[] = sprintf( '%%%s%%', $query['search'] );
349
-			}
350
-
351
-			$search_claim_id = (int) $query['search'];
352
-			if ( $search_claim_id ) {
353
-				$sql .= ' OR a.claim_id = %d';
354
-				$sql_params[] = $search_claim_id;
355
-			}
356
-
357
-			$sql .= ')';
358
-		}
359
-
360
-		if ( 'select' === $select_or_count ) {
361
-			switch ( $query['orderby'] ) {
362
-				case 'hook':
363
-					$orderby = 'a.hook';
364
-					break;
365
-				case 'group':
366
-					$orderby = 'g.slug';
367
-					break;
368
-				case 'modified':
369
-					$orderby = 'a.last_attempt_gmt';
370
-					break;
371
-				case 'date':
372
-				default:
373
-					$orderby = 'a.scheduled_date_gmt';
374
-					break;
375
-			}
376
-			if ( strtoupper( $query[ 'order' ] ) == 'ASC' ) {
377
-				$order = 'ASC';
378
-			} else {
379
-				$order = 'DESC';
380
-			}
381
-			$sql .= " ORDER BY $orderby $order";
382
-			if ( $query[ 'per_page' ] > 0 ) {
383
-				$sql          .= " LIMIT %d, %d";
384
-				$sql_params[] = $query[ 'offset' ];
385
-				$sql_params[] = $query[ 'per_page' ];
386
-			}
387
-		}
388
-
389
-		if ( ! empty( $sql_params ) ) {
390
-			$sql = $wpdb->prepare( $sql, $sql_params );
391
-		}
392
-
393
-		return $sql;
394
-	}
395
-
396
-	/**
397
-	 * Query for action count of list of action IDs.
398
-	 *
399
-	 * @param array  $query Query parameters.
400
-	 * @param string $query_type Whether to select or count the results. Default, select.
401
-	 *
402
-	 * @return null|string|array The IDs of actions matching the query
403
-	 */
404
-	public function query_actions( $query = [], $query_type = 'select' ) {
405
-		/** @var wpdb $wpdb */
406
-		global $wpdb;
407
-
408
-		$sql = $this->get_query_actions_sql( $query, $query_type );
409
-
410
-		return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql );
411
-	}
412
-
413
-	/**
414
-	 * Get a count of all actions in the store, grouped by status.
415
-	 *
416
-	 * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status.
417
-	 */
418
-	public function action_counts() {
419
-		global $wpdb;
420
-
421
-		$sql  = "SELECT a.status, count(a.status) as 'count'";
422
-		$sql .= " FROM {$wpdb->actionscheduler_actions} a";
423
-		$sql .= " GROUP BY a.status";
424
-
425
-		$actions_count_by_status = array();
426
-		$action_stati_and_labels = $this->get_status_labels();
427
-
428
-		foreach ( $wpdb->get_results( $sql ) as $action_data ) {
429
-			// Ignore any actions with invalid status
430
-			if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) {
431
-				$actions_count_by_status[ $action_data->status ] = $action_data->count;
432
-			}
433
-		}
434
-
435
-		return $actions_count_by_status;
436
-	}
437
-
438
-	/**
439
-	 * Cancel an action.
440
-	 *
441
-	 * @param int $action_id Action ID.
442
-	 *
443
-	 * @return void
444
-	 */
445
-	public function cancel_action( $action_id ) {
446
-		/** @var \wpdb $wpdb */
447
-		global $wpdb;
448
-
449
-		$updated = $wpdb->update(
450
-			$wpdb->actionscheduler_actions,
451
-			[ 'status' => self::STATUS_CANCELED ],
452
-			[ 'action_id' => $action_id ],
453
-			[ '%s' ],
454
-			[ '%d' ]
455
-		);
456
-		if ( empty( $updated ) ) {
457
-			/* translators: %s: action ID */
458
-			throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
459
-		}
460
-		do_action( 'action_scheduler_canceled_action', $action_id );
461
-	}
462
-
463
-	/**
464
-	 * Cancel pending actions by hook.
465
-	 *
466
-	 * @since 3.0.0
467
-	 *
468
-	 * @param string $hook Hook name.
469
-	 *
470
-	 * @return void
471
-	 */
472
-	public function cancel_actions_by_hook( $hook ) {
473
-		$this->bulk_cancel_actions( [ 'hook' => $hook ] );
474
-	}
475
-
476
-	/**
477
-	 * Cancel pending actions by group.
478
-	 *
479
-	 * @param string $group Group slug.
480
-	 *
481
-	 * @return void
482
-	 */
483
-	public function cancel_actions_by_group( $group ) {
484
-		$this->bulk_cancel_actions( [ 'group' => $group ] );
485
-	}
486
-
487
-	/**
488
-	 * Bulk cancel actions.
489
-	 *
490
-	 * @since 3.0.0
491
-	 *
492
-	 * @param array $query_args Query parameters.
493
-	 */
494
-	protected function bulk_cancel_actions( $query_args ) {
495
-		/** @var \wpdb $wpdb */
496
-		global $wpdb;
497
-
498
-		if ( ! is_array( $query_args ) ) {
499
-			return;
500
-		}
501
-
502
-		// Don't cancel actions that are already canceled.
503
-		if ( isset( $query_args['status'] ) && $query_args['status'] == self::STATUS_CANCELED ) {
504
-			return;
505
-		}
506
-
507
-		$action_ids = true;
508
-		$query_args = wp_parse_args(
509
-			$query_args,
510
-			[
511
-				'per_page' => 1000,
512
-				'status' => self::STATUS_PENDING,
513
-			]
514
-		);
515
-
516
-		while ( $action_ids ) {
517
-			$action_ids = $this->query_actions( $query_args );
518
-			if ( empty( $action_ids ) ) {
519
-				break;
520
-			}
521
-
522
-			$format     = array_fill( 0, count( $action_ids ), '%d' );
523
-			$query_in   = '(' . implode( ',', $format ) . ')';
524
-			$parameters = $action_ids;
525
-			array_unshift( $parameters, self::STATUS_CANCELED );
526
-
527
-			$wpdb->query(
528
-				$wpdb->prepare( // wpcs: PreparedSQLPlaceholders replacement count ok.
529
-					"UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}",
530
-					$parameters
531
-				)
532
-			);
533
-
534
-			do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
535
-		}
536
-	}
537
-
538
-	/**
539
-	 * Delete an action.
540
-	 *
541
-	 * @param int $action_id Action ID.
542
-	 */
543
-	public function delete_action( $action_id ) {
544
-		/** @var \wpdb $wpdb */
545
-		global $wpdb;
546
-		$deleted = $wpdb->delete( $wpdb->actionscheduler_actions, [ 'action_id' => $action_id ], [ '%d' ] );
547
-		if ( empty( $deleted ) ) {
548
-			throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
549
-		}
550
-		do_action( 'action_scheduler_deleted_action', $action_id );
551
-	}
552
-
553
-	/**
554
-	 * Get the schedule date for an action.
555
-	 *
556
-	 * @param string $action_id Action ID.
557
-	 *
558
-	 * @throws \InvalidArgumentException
559
-	 * @return \DateTime The local date the action is scheduled to run, or the date that it ran.
560
-	 */
561
-	public function get_date( $action_id ) {
562
-		$date = $this->get_date_gmt( $action_id );
563
-		ActionScheduler_TimezoneHelper::set_local_timezone( $date );
564
-		return $date;
565
-	}
566
-
567
-	/**
568
-	 * Get the GMT schedule date for an action.
569
-	 *
570
-	 * @param int $action_id Action ID.
571
-	 *
572
-	 * @throws \InvalidArgumentException
573
-	 * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran.
574
-	 */
575
-	protected function get_date_gmt( $action_id ) {
576
-		/** @var \wpdb $wpdb */
577
-		global $wpdb;
578
-		$record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) );
579
-		if ( empty( $record ) ) {
580
-			throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
581
-		}
582
-		if ( $record->status == self::STATUS_PENDING ) {
583
-			return as_get_datetime_object( $record->scheduled_date_gmt );
584
-		} else {
585
-			return as_get_datetime_object( $record->last_attempt_gmt );
586
-		}
587
-	}
588
-
589
-	/**
590
-	 * Stake a claim on actions.
591
-	 *
592
-	 * @param int       $max_actions Maximum number of action to include in claim.
593
-	 * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now.
594
-	 *
595
-	 * @return ActionScheduler_ActionClaim
596
-	 */
597
-	public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
598
-		$claim_id = $this->generate_claim_id();
599
-		$this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
600
-		$action_ids = $this->find_actions_by_claim_id( $claim_id );
601
-
602
-		return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
603
-	}
604
-
605
-	/**
606
-	 * Generate a new action claim.
607
-	 *
608
-	 * @return int Claim ID.
609
-	 */
610
-	protected function generate_claim_id() {
611
-		/** @var \wpdb $wpdb */
612
-		global $wpdb;
613
-		$now = as_get_datetime_object();
614
-		$wpdb->insert( $wpdb->actionscheduler_claims, [ 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ] );
615
-
616
-		return $wpdb->insert_id;
617
-	}
618
-
619
-	/**
620
-	 * Mark actions claimed.
621
-	 *
622
-	 * @param string    $claim_id Claim Id.
623
-	 * @param int       $limit Number of action to include in claim.
624
-	 * @param \DateTime $before_date Should use UTC timezone.
625
-	 *
626
-	 * @return int The number of actions that were claimed.
627
-	 * @throws \RuntimeException
628
-	 */
629
-	protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
630
-		/** @var \wpdb $wpdb */
631
-		global $wpdb;
632
-
633
-		$now  = as_get_datetime_object();
634
-		$date = is_null( $before_date ) ? $now : clone $before_date;
635
-
636
-		// can't use $wpdb->update() because of the <= condition
637
-		$update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
638
-		$params = array(
639
-			$claim_id,
640
-			$now->format( 'Y-m-d H:i:s' ),
641
-			current_time( 'mysql' ),
642
-		);
643
-
644
-		$where    = "WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s";
645
-		$params[] = $date->format( 'Y-m-d H:i:s' );
646
-		$params[] = self::STATUS_PENDING;
647
-
648
-		if ( ! empty( $hooks ) ) {
649
-			$placeholders = array_fill( 0, count( $hooks ), '%s' );
650
-			$where       .= ' AND hook IN (' . join( ', ', $placeholders ) . ')';
651
-			$params       = array_merge( $params, array_values( $hooks ) );
652
-		}
653
-
654
-		if ( ! empty( $group ) ) {
655
-
656
-			$group_id = $this->get_group_id( $group, false );
657
-
658
-			// throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour
659
-			if ( empty( $group_id ) ) {
660
-				/* translators: %s: group name */
661
-				throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
662
-			}
663
-
664
-			$where   .= ' AND group_id = %d';
665
-			$params[] = $group_id;
666
-		}
667
-
668
-		$order    = "ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC LIMIT %d";
669
-		$params[] = $limit;
670
-
671
-		$sql = $wpdb->prepare( "{$update} {$where} {$order}", $params );
672
-
673
-		$rows_affected = $wpdb->query( $sql );
674
-		if ( $rows_affected === false ) {
675
-			throw new \RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
676
-		}
677
-
678
-		return (int) $rows_affected;
679
-	}
680
-
681
-	/**
682
-	 * Get the number of active claims.
683
-	 *
684
-	 * @return int
685
-	 */
686
-	public function get_claim_count() {
687
-		global $wpdb;
688
-
689
-		$sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)";
690
-		$sql = $wpdb->prepare( $sql, [ self::STATUS_PENDING, self::STATUS_RUNNING ] );
691
-
692
-		return (int) $wpdb->get_var( $sql );
693
-	}
694
-
695
-	/**
696
-	 * Return an action's claim ID, as stored in the claim_id column.
697
-	 *
698
-	 * @param string $action_id Action ID.
699
-	 * @return mixed
700
-	 */
701
-	public function get_claim_id( $action_id ) {
702
-		/** @var \wpdb $wpdb */
703
-		global $wpdb;
704
-
705
-		$sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
706
-		$sql = $wpdb->prepare( $sql, $action_id );
707
-
708
-		return (int) $wpdb->get_var( $sql );
709
-	}
710
-
711
-	/**
712
-	 * Retrieve the action IDs of action in a claim.
713
-	 *
714
-	 * @param string $claim_id Claim ID.
715
-	 *
716
-	 * @return int[]
717
-	 */
718
-	public function find_actions_by_claim_id( $claim_id ) {
719
-		/** @var \wpdb $wpdb */
720
-		global $wpdb;
721
-
722
-		$sql = "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id=%d";
723
-		$sql = $wpdb->prepare( $sql, $claim_id );
724
-
725
-		$action_ids = $wpdb->get_col( $sql );
726
-
727
-		return array_map( 'intval', $action_ids );
728
-	}
729
-
730
-	/**
731
-	 * Release actions from a claim and delete the claim.
732
-	 *
733
-	 * @param ActionScheduler_ActionClaim $claim Claim object.
734
-	 */
735
-	public function release_claim( ActionScheduler_ActionClaim $claim ) {
736
-		/** @var \wpdb $wpdb */
737
-		global $wpdb;
738
-		$wpdb->update( $wpdb->actionscheduler_actions, [ 'claim_id' => 0 ], [ 'claim_id' => $claim->get_id() ], [ '%d' ], [ '%d' ] );
739
-		$wpdb->delete( $wpdb->actionscheduler_claims, [ 'claim_id' => $claim->get_id() ], [ '%d' ] );
740
-	}
741
-
742
-	/**
743
-	 * Remove the claim from an action.
744
-	 *
745
-	 * @param int $action_id Action ID.
746
-	 *
747
-	 * @return void
748
-	 */
749
-	public function unclaim_action( $action_id ) {
750
-		/** @var \wpdb $wpdb */
751
-		global $wpdb;
752
-		$wpdb->update(
753
-			$wpdb->actionscheduler_actions,
754
-			[ 'claim_id' => 0 ],
755
-			[ 'action_id' => $action_id ],
756
-			[ '%s' ],
757
-			[ '%d' ]
758
-		);
759
-	}
760
-
761
-	/**
762
-	 * Mark an action as failed.
763
-	 *
764
-	 * @param int $action_id Action ID.
765
-	 */
766
-	public function mark_failure( $action_id ) {
767
-		/** @var \wpdb $wpdb */
768
-		global $wpdb;
769
-		$updated = $wpdb->update(
770
-			$wpdb->actionscheduler_actions,
771
-			[ 'status' => self::STATUS_FAILED ],
772
-			[ 'action_id' => $action_id ],
773
-			[ '%s' ],
774
-			[ '%d' ]
775
-		);
776
-		if ( empty( $updated ) ) {
777
-			throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
778
-		}
779
-	}
780
-
781
-	/**
782
-	 * Add execution message to action log.
783
-	 *
784
-	 * @param int $action_id Action ID.
785
-	 *
786
-	 * @return void
787
-	 */
788
-	public function log_execution( $action_id ) {
789
-		/** @var \wpdb $wpdb */
790
-		global $wpdb;
791
-
792
-		$sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d";
793
-		$sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id );
794
-		$wpdb->query( $sql );
795
-	}
796
-
797
-	/**
798
-	 * Mark an action as complete.
799
-	 *
800
-	 * @param int $action_id Action ID.
801
-	 *
802
-	 * @return void
803
-	 */
804
-	public function mark_complete( $action_id ) {
805
-		/** @var \wpdb $wpdb */
806
-		global $wpdb;
807
-		$updated = $wpdb->update(
808
-			$wpdb->actionscheduler_actions,
809
-			[
810
-				'status'             => self::STATUS_COMPLETE,
811
-				'last_attempt_gmt'   => current_time( 'mysql', true ),
812
-				'last_attempt_local' => current_time( 'mysql' ),
813
-			],
814
-			[ 'action_id' => $action_id ],
815
-			[ '%s' ],
816
-			[ '%d' ]
817
-		);
818
-		if ( empty( $updated ) ) {
819
-			throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
820
-		}
821
-	}
822
-
823
-	/**
824
-	 * Get an action's status.
825
-	 *
826
-	 * @param int $action_id Action ID.
827
-	 *
828
-	 * @return string
829
-	 */
830
-	public function get_status( $action_id ) {
831
-		/** @var \wpdb $wpdb */
832
-		global $wpdb;
833
-		$sql    = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
834
-		$sql    = $wpdb->prepare( $sql, $action_id );
835
-		$status = $wpdb->get_var( $sql );
836
-
837
-		if ( $status === null ) {
838
-			throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
839
-		} elseif ( empty( $status ) ) {
840
-			throw new \RuntimeException( __( 'Unknown status found for action.', 'action-scheduler' ) );
841
-		} else {
842
-			return $status;
843
-		}
844
-	}
12
+    /** @var int */
13
+    protected static $max_args_length = 8000;
14
+
15
+    /** @var int */
16
+    protected static $max_index_length = 191;
17
+
18
+    /**
19
+     * Initialize the data store
20
+     *
21
+     * @codeCoverageIgnore
22
+     */
23
+    public function init() {
24
+        $table_maker = new ActionScheduler_StoreSchema();
25
+        $table_maker->register_tables();
26
+    }
27
+
28
+    /**
29
+     * Save an action.
30
+     *
31
+     * @param ActionScheduler_Action $action Action object.
32
+     * @param DateTime               $date Optional schedule date. Default null.
33
+     *
34
+     * @return int Action ID.
35
+     */
36
+    public function save_action( ActionScheduler_Action $action, \DateTime $date = null ) {
37
+        try {
38
+
39
+            $this->validate_action( $action );
40
+
41
+            /** @var \wpdb $wpdb */
42
+            global $wpdb;
43
+            $data = [
44
+                'hook'                 => $action->get_hook(),
45
+                'status'               => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ),
46
+                'scheduled_date_gmt'   => $this->get_scheduled_date_string( $action, $date ),
47
+                'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ),
48
+                'schedule'             => serialize( $action->get_schedule() ),
49
+                'group_id'             => $this->get_group_id( $action->get_group() ),
50
+            ];
51
+            $args = wp_json_encode( $action->get_args() );
52
+            if ( strlen( $args ) <= static::$max_index_length ) {
53
+                $data['args'] = $args;
54
+            } else {
55
+                $data['args']          = $this->hash_args( $args );
56
+                $data['extended_args'] = $args;
57
+            }
58
+
59
+            $table_name = ! empty( $wpdb->actionscheduler_actions ) ? $wpdb->actionscheduler_actions : $wpdb->prefix . 'actionscheduler_actions';
60
+            $wpdb->insert( $table_name, $data );
61
+            $action_id = $wpdb->insert_id;
62
+
63
+            if ( is_wp_error( $action_id ) ) {
64
+                throw new RuntimeException( $action_id->get_error_message() );
65
+            }
66
+            elseif ( empty( $action_id ) ) {
67
+                throw new RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'action-scheduler' ) );
68
+            }
69
+
70
+            do_action( 'action_scheduler_stored_action', $action_id );
71
+
72
+            return $action_id;
73
+        } catch ( \Exception $e ) {
74
+            /* translators: %s: error message */
75
+            throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
76
+        }
77
+    }
78
+
79
+    /**
80
+     * Generate a hash from json_encoded $args using MD5 as this isn't for security.
81
+     *
82
+     * @param string $args JSON encoded action args.
83
+     * @return string
84
+     */
85
+    protected function hash_args( $args ) {
86
+        return md5( $args );
87
+    }
88
+
89
+    /**
90
+     * Get action args query param value from action args.
91
+     *
92
+     * @param array $args Action args.
93
+     * @return string
94
+     */
95
+    protected function get_args_for_query( $args ) {
96
+        $encoded = wp_json_encode( $args );
97
+        if ( strlen( $encoded ) <= static::$max_index_length ) {
98
+            return $encoded;
99
+        }
100
+        return $this->hash_args( $encoded );
101
+    }
102
+    /**
103
+     * Get a group's ID based on its name/slug.
104
+     *
105
+     * @param string $slug The string name of a group.
106
+     * @param bool $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group.
107
+     *
108
+     * @return int The group's ID, if it exists or is created, or 0 if it does not exist and is not created.
109
+     */
110
+    protected function get_group_id( $slug, $create_if_not_exists = true ) {
111
+        if ( empty( $slug ) ) {
112
+            return 0;
113
+        }
114
+        /** @var \wpdb $wpdb */
115
+        global $wpdb;
116
+        $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) );
117
+        if ( empty( $group_id ) && $create_if_not_exists ) {
118
+            $group_id = $this->create_group( $slug );
119
+        }
120
+
121
+        return $group_id;
122
+    }
123
+
124
+    /**
125
+     * Create an action group.
126
+     *
127
+     * @param string $slug Group slug.
128
+     *
129
+     * @return int Group ID.
130
+     */
131
+    protected function create_group( $slug ) {
132
+        /** @var \wpdb $wpdb */
133
+        global $wpdb;
134
+        $wpdb->insert( $wpdb->actionscheduler_groups, [ 'slug' => $slug ] );
135
+
136
+        return (int) $wpdb->insert_id;
137
+    }
138
+
139
+    /**
140
+     * Retrieve an action.
141
+     *
142
+     * @param int $action_id Action ID.
143
+     *
144
+     * @return ActionScheduler_Action
145
+     */
146
+    public function fetch_action( $action_id ) {
147
+        /** @var \wpdb $wpdb */
148
+        global $wpdb;
149
+        $data = $wpdb->get_row( $wpdb->prepare(
150
+            "SELECT a.*, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.action_id=%d",
151
+            $action_id
152
+        ) );
153
+
154
+        if ( empty( $data ) ) {
155
+            return $this->get_null_action();
156
+        }
157
+
158
+        if ( ! empty( $data->extended_args ) ) {
159
+            $data->args = $data->extended_args;
160
+            unset( $data->extended_args );
161
+        }
162
+
163
+        try {
164
+            $action = $this->make_action_from_db_record( $data );
165
+        } catch ( ActionScheduler_InvalidActionException $exception ) {
166
+            do_action( 'action_scheduler_failed_fetch_action', $action_id, $exception );
167
+            return $this->get_null_action();
168
+        }
169
+
170
+        return $action;
171
+    }
172
+
173
+    /**
174
+     * Create a null action.
175
+     *
176
+     * @return ActionScheduler_NullAction
177
+     */
178
+    protected function get_null_action() {
179
+        return new ActionScheduler_NullAction();
180
+    }
181
+
182
+    /**
183
+     * Create an action from a database record.
184
+     *
185
+     * @param object $data Action database record.
186
+     *
187
+     * @return ActionScheduler_Action|ActionScheduler_CanceledAction|ActionScheduler_FinishedAction
188
+     */
189
+    protected function make_action_from_db_record( $data ) {
190
+
191
+        $hook     = $data->hook;
192
+        $args     = json_decode( $data->args, true );
193
+        $schedule = unserialize( $data->schedule );
194
+
195
+        $this->validate_args( $args, $data->action_id );
196
+        $this->validate_schedule( $schedule, $data->action_id );
197
+
198
+        if ( empty( $schedule ) ) {
199
+            $schedule = new ActionScheduler_NullSchedule();
200
+        }
201
+        $group = $data->group ? $data->group : '';
202
+
203
+        return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group );
204
+    }
205
+
206
+    /**
207
+     * Find an action.
208
+     *
209
+     * @param string $hook Action hook.
210
+     * @param array  $params Parameters of the action to find.
211
+     *
212
+     * @return string|null ID of the next action matching the criteria or NULL if not found.
213
+     */
214
+    public function find_action( $hook, $params = [] ) {
215
+        $params = wp_parse_args( $params, [
216
+            'args'   => null,
217
+            'status' => self::STATUS_PENDING,
218
+            'group'  => '',
219
+        ] );
220
+
221
+        /** @var wpdb $wpdb */
222
+        global $wpdb;
223
+        $query = "SELECT a.action_id FROM {$wpdb->actionscheduler_actions} a";
224
+        $args  = [];
225
+        if ( ! empty( $params[ 'group' ] ) ) {
226
+            $query  .= " INNER JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id AND g.slug=%s";
227
+            $args[] = $params[ 'group' ];
228
+        }
229
+        $query  .= " WHERE a.hook=%s";
230
+        $args[] = $hook;
231
+        if ( ! is_null( $params[ 'args' ] ) ) {
232
+            $query  .= " AND a.args=%s";
233
+            $args[] = $this->get_args_for_query( $params[ 'args' ] );
234
+        }
235
+
236
+        $order = 'ASC';
237
+        if ( ! empty( $params[ 'status' ] ) ) {
238
+            $query  .= " AND a.status=%s";
239
+            $args[] = $params[ 'status' ];
240
+
241
+            if ( self::STATUS_PENDING == $params[ 'status' ] ) {
242
+                $order = 'ASC'; // Find the next action that matches.
243
+            } else {
244
+                $order = 'DESC'; // Find the most recent action that matches.
245
+            }
246
+        }
247
+
248
+        $query .= " ORDER BY scheduled_date_gmt $order LIMIT 1";
249
+
250
+        $query = $wpdb->prepare( $query, $args );
251
+
252
+        $id = $wpdb->get_var( $query );
253
+
254
+        return $id;
255
+    }
256
+
257
+    /**
258
+     * Returns the SQL statement to query (or count) actions.
259
+     *
260
+     * @param array  $query Filtering options.
261
+     * @param string $select_or_count  Whether the SQL should select and return the IDs or just the row count.
262
+     *
263
+     * @return string SQL statement already properly escaped.
264
+     */
265
+    protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
266
+
267
+        if ( ! in_array( $select_or_count, array( 'select', 'count' ) ) ) {
268
+            throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'action-scheduler' ) );
269
+        }
270
+
271
+        $query = wp_parse_args( $query, [
272
+            'hook'             => '',
273
+            'args'             => null,
274
+            'date'             => null,
275
+            'date_compare'     => '<=',
276
+            'modified'         => null,
277
+            'modified_compare' => '<=',
278
+            'group'            => '',
279
+            'status'           => '',
280
+            'claimed'          => null,
281
+            'per_page'         => 5,
282
+            'offset'           => 0,
283
+            'orderby'          => 'date',
284
+            'order'            => 'ASC',
285
+        ] );
286
+
287
+        /** @var \wpdb $wpdb */
288
+        global $wpdb;
289
+        $sql  = ( 'count' === $select_or_count ) ? 'SELECT count(a.action_id)' : 'SELECT a.action_id';
290
+        $sql .= " FROM {$wpdb->actionscheduler_actions} a";
291
+        $sql_params = [];
292
+
293
+        if ( ! empty( $query[ 'group' ] ) || 'group' === $query[ 'orderby' ] ) {
294
+            $sql .= " LEFT JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id";
295
+        }
296
+
297
+        $sql .= " WHERE 1=1";
298
+
299
+        if ( ! empty( $query[ 'group' ] ) ) {
300
+            $sql          .= " AND g.slug=%s";
301
+            $sql_params[] = $query[ 'group' ];
302
+        }
303
+
304
+        if ( $query[ 'hook' ] ) {
305
+            $sql          .= " AND a.hook=%s";
306
+            $sql_params[] = $query[ 'hook' ];
307
+        }
308
+        if ( ! is_null( $query[ 'args' ] ) ) {
309
+            $sql          .= " AND a.args=%s";
310
+            $sql_params[] = $this->get_args_for_query( $query[ 'args' ] );
311
+        }
312
+
313
+        if ( $query[ 'status' ] ) {
314
+            $sql          .= " AND a.status=%s";
315
+            $sql_params[] = $query[ 'status' ];
316
+        }
317
+
318
+        if ( $query[ 'date' ] instanceof \DateTime ) {
319
+            $date = clone $query[ 'date' ];
320
+            $date->setTimezone( new \DateTimeZone( 'UTC' ) );
321
+            $date_string  = $date->format( 'Y-m-d H:i:s' );
322
+            $comparator   = $this->validate_sql_comparator( $query[ 'date_compare' ] );
323
+            $sql          .= " AND a.scheduled_date_gmt $comparator %s";
324
+            $sql_params[] = $date_string;
325
+        }
326
+
327
+        if ( $query[ 'modified' ] instanceof \DateTime ) {
328
+            $modified = clone $query[ 'modified' ];
329
+            $modified->setTimezone( new \DateTimeZone( 'UTC' ) );
330
+            $date_string  = $modified->format( 'Y-m-d H:i:s' );
331
+            $comparator   = $this->validate_sql_comparator( $query[ 'modified_compare' ] );
332
+            $sql          .= " AND a.last_attempt_gmt $comparator %s";
333
+            $sql_params[] = $date_string;
334
+        }
335
+
336
+        if ( $query[ 'claimed' ] === true ) {
337
+            $sql .= " AND a.claim_id != 0";
338
+        } elseif ( $query[ 'claimed' ] === false ) {
339
+            $sql .= " AND a.claim_id = 0";
340
+        } elseif ( ! is_null( $query[ 'claimed' ] ) ) {
341
+            $sql          .= " AND a.claim_id = %d";
342
+            $sql_params[] = $query[ 'claimed' ];
343
+        }
344
+
345
+        if ( ! empty( $query['search'] ) ) {
346
+            $sql .= " AND (a.hook LIKE %s OR (a.extended_args IS NULL AND a.args LIKE %s) OR a.extended_args LIKE %s";
347
+            for( $i = 0; $i < 3; $i++ ) {
348
+                $sql_params[] = sprintf( '%%%s%%', $query['search'] );
349
+            }
350
+
351
+            $search_claim_id = (int) $query['search'];
352
+            if ( $search_claim_id ) {
353
+                $sql .= ' OR a.claim_id = %d';
354
+                $sql_params[] = $search_claim_id;
355
+            }
356
+
357
+            $sql .= ')';
358
+        }
359
+
360
+        if ( 'select' === $select_or_count ) {
361
+            switch ( $query['orderby'] ) {
362
+                case 'hook':
363
+                    $orderby = 'a.hook';
364
+                    break;
365
+                case 'group':
366
+                    $orderby = 'g.slug';
367
+                    break;
368
+                case 'modified':
369
+                    $orderby = 'a.last_attempt_gmt';
370
+                    break;
371
+                case 'date':
372
+                default:
373
+                    $orderby = 'a.scheduled_date_gmt';
374
+                    break;
375
+            }
376
+            if ( strtoupper( $query[ 'order' ] ) == 'ASC' ) {
377
+                $order = 'ASC';
378
+            } else {
379
+                $order = 'DESC';
380
+            }
381
+            $sql .= " ORDER BY $orderby $order";
382
+            if ( $query[ 'per_page' ] > 0 ) {
383
+                $sql          .= " LIMIT %d, %d";
384
+                $sql_params[] = $query[ 'offset' ];
385
+                $sql_params[] = $query[ 'per_page' ];
386
+            }
387
+        }
388
+
389
+        if ( ! empty( $sql_params ) ) {
390
+            $sql = $wpdb->prepare( $sql, $sql_params );
391
+        }
392
+
393
+        return $sql;
394
+    }
395
+
396
+    /**
397
+     * Query for action count of list of action IDs.
398
+     *
399
+     * @param array  $query Query parameters.
400
+     * @param string $query_type Whether to select or count the results. Default, select.
401
+     *
402
+     * @return null|string|array The IDs of actions matching the query
403
+     */
404
+    public function query_actions( $query = [], $query_type = 'select' ) {
405
+        /** @var wpdb $wpdb */
406
+        global $wpdb;
407
+
408
+        $sql = $this->get_query_actions_sql( $query, $query_type );
409
+
410
+        return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql );
411
+    }
412
+
413
+    /**
414
+     * Get a count of all actions in the store, grouped by status.
415
+     *
416
+     * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status.
417
+     */
418
+    public function action_counts() {
419
+        global $wpdb;
420
+
421
+        $sql  = "SELECT a.status, count(a.status) as 'count'";
422
+        $sql .= " FROM {$wpdb->actionscheduler_actions} a";
423
+        $sql .= " GROUP BY a.status";
424
+
425
+        $actions_count_by_status = array();
426
+        $action_stati_and_labels = $this->get_status_labels();
427
+
428
+        foreach ( $wpdb->get_results( $sql ) as $action_data ) {
429
+            // Ignore any actions with invalid status
430
+            if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) {
431
+                $actions_count_by_status[ $action_data->status ] = $action_data->count;
432
+            }
433
+        }
434
+
435
+        return $actions_count_by_status;
436
+    }
437
+
438
+    /**
439
+     * Cancel an action.
440
+     *
441
+     * @param int $action_id Action ID.
442
+     *
443
+     * @return void
444
+     */
445
+    public function cancel_action( $action_id ) {
446
+        /** @var \wpdb $wpdb */
447
+        global $wpdb;
448
+
449
+        $updated = $wpdb->update(
450
+            $wpdb->actionscheduler_actions,
451
+            [ 'status' => self::STATUS_CANCELED ],
452
+            [ 'action_id' => $action_id ],
453
+            [ '%s' ],
454
+            [ '%d' ]
455
+        );
456
+        if ( empty( $updated ) ) {
457
+            /* translators: %s: action ID */
458
+            throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
459
+        }
460
+        do_action( 'action_scheduler_canceled_action', $action_id );
461
+    }
462
+
463
+    /**
464
+     * Cancel pending actions by hook.
465
+     *
466
+     * @since 3.0.0
467
+     *
468
+     * @param string $hook Hook name.
469
+     *
470
+     * @return void
471
+     */
472
+    public function cancel_actions_by_hook( $hook ) {
473
+        $this->bulk_cancel_actions( [ 'hook' => $hook ] );
474
+    }
475
+
476
+    /**
477
+     * Cancel pending actions by group.
478
+     *
479
+     * @param string $group Group slug.
480
+     *
481
+     * @return void
482
+     */
483
+    public function cancel_actions_by_group( $group ) {
484
+        $this->bulk_cancel_actions( [ 'group' => $group ] );
485
+    }
486
+
487
+    /**
488
+     * Bulk cancel actions.
489
+     *
490
+     * @since 3.0.0
491
+     *
492
+     * @param array $query_args Query parameters.
493
+     */
494
+    protected function bulk_cancel_actions( $query_args ) {
495
+        /** @var \wpdb $wpdb */
496
+        global $wpdb;
497
+
498
+        if ( ! is_array( $query_args ) ) {
499
+            return;
500
+        }
501
+
502
+        // Don't cancel actions that are already canceled.
503
+        if ( isset( $query_args['status'] ) && $query_args['status'] == self::STATUS_CANCELED ) {
504
+            return;
505
+        }
506
+
507
+        $action_ids = true;
508
+        $query_args = wp_parse_args(
509
+            $query_args,
510
+            [
511
+                'per_page' => 1000,
512
+                'status' => self::STATUS_PENDING,
513
+            ]
514
+        );
515
+
516
+        while ( $action_ids ) {
517
+            $action_ids = $this->query_actions( $query_args );
518
+            if ( empty( $action_ids ) ) {
519
+                break;
520
+            }
521
+
522
+            $format     = array_fill( 0, count( $action_ids ), '%d' );
523
+            $query_in   = '(' . implode( ',', $format ) . ')';
524
+            $parameters = $action_ids;
525
+            array_unshift( $parameters, self::STATUS_CANCELED );
526
+
527
+            $wpdb->query(
528
+                $wpdb->prepare( // wpcs: PreparedSQLPlaceholders replacement count ok.
529
+                    "UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}",
530
+                    $parameters
531
+                )
532
+            );
533
+
534
+            do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
535
+        }
536
+    }
537
+
538
+    /**
539
+     * Delete an action.
540
+     *
541
+     * @param int $action_id Action ID.
542
+     */
543
+    public function delete_action( $action_id ) {
544
+        /** @var \wpdb $wpdb */
545
+        global $wpdb;
546
+        $deleted = $wpdb->delete( $wpdb->actionscheduler_actions, [ 'action_id' => $action_id ], [ '%d' ] );
547
+        if ( empty( $deleted ) ) {
548
+            throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
549
+        }
550
+        do_action( 'action_scheduler_deleted_action', $action_id );
551
+    }
552
+
553
+    /**
554
+     * Get the schedule date for an action.
555
+     *
556
+     * @param string $action_id Action ID.
557
+     *
558
+     * @throws \InvalidArgumentException
559
+     * @return \DateTime The local date the action is scheduled to run, or the date that it ran.
560
+     */
561
+    public function get_date( $action_id ) {
562
+        $date = $this->get_date_gmt( $action_id );
563
+        ActionScheduler_TimezoneHelper::set_local_timezone( $date );
564
+        return $date;
565
+    }
566
+
567
+    /**
568
+     * Get the GMT schedule date for an action.
569
+     *
570
+     * @param int $action_id Action ID.
571
+     *
572
+     * @throws \InvalidArgumentException
573
+     * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran.
574
+     */
575
+    protected function get_date_gmt( $action_id ) {
576
+        /** @var \wpdb $wpdb */
577
+        global $wpdb;
578
+        $record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) );
579
+        if ( empty( $record ) ) {
580
+            throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
581
+        }
582
+        if ( $record->status == self::STATUS_PENDING ) {
583
+            return as_get_datetime_object( $record->scheduled_date_gmt );
584
+        } else {
585
+            return as_get_datetime_object( $record->last_attempt_gmt );
586
+        }
587
+    }
588
+
589
+    /**
590
+     * Stake a claim on actions.
591
+     *
592
+     * @param int       $max_actions Maximum number of action to include in claim.
593
+     * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now.
594
+     *
595
+     * @return ActionScheduler_ActionClaim
596
+     */
597
+    public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
598
+        $claim_id = $this->generate_claim_id();
599
+        $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
600
+        $action_ids = $this->find_actions_by_claim_id( $claim_id );
601
+
602
+        return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
603
+    }
604
+
605
+    /**
606
+     * Generate a new action claim.
607
+     *
608
+     * @return int Claim ID.
609
+     */
610
+    protected function generate_claim_id() {
611
+        /** @var \wpdb $wpdb */
612
+        global $wpdb;
613
+        $now = as_get_datetime_object();
614
+        $wpdb->insert( $wpdb->actionscheduler_claims, [ 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ] );
615
+
616
+        return $wpdb->insert_id;
617
+    }
618
+
619
+    /**
620
+     * Mark actions claimed.
621
+     *
622
+     * @param string    $claim_id Claim Id.
623
+     * @param int       $limit Number of action to include in claim.
624
+     * @param \DateTime $before_date Should use UTC timezone.
625
+     *
626
+     * @return int The number of actions that were claimed.
627
+     * @throws \RuntimeException
628
+     */
629
+    protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
630
+        /** @var \wpdb $wpdb */
631
+        global $wpdb;
632
+
633
+        $now  = as_get_datetime_object();
634
+        $date = is_null( $before_date ) ? $now : clone $before_date;
635
+
636
+        // can't use $wpdb->update() because of the <= condition
637
+        $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
638
+        $params = array(
639
+            $claim_id,
640
+            $now->format( 'Y-m-d H:i:s' ),
641
+            current_time( 'mysql' ),
642
+        );
643
+
644
+        $where    = "WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s";
645
+        $params[] = $date->format( 'Y-m-d H:i:s' );
646
+        $params[] = self::STATUS_PENDING;
647
+
648
+        if ( ! empty( $hooks ) ) {
649
+            $placeholders = array_fill( 0, count( $hooks ), '%s' );
650
+            $where       .= ' AND hook IN (' . join( ', ', $placeholders ) . ')';
651
+            $params       = array_merge( $params, array_values( $hooks ) );
652
+        }
653
+
654
+        if ( ! empty( $group ) ) {
655
+
656
+            $group_id = $this->get_group_id( $group, false );
657
+
658
+            // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour
659
+            if ( empty( $group_id ) ) {
660
+                /* translators: %s: group name */
661
+                throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
662
+            }
663
+
664
+            $where   .= ' AND group_id = %d';
665
+            $params[] = $group_id;
666
+        }
667
+
668
+        $order    = "ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC LIMIT %d";
669
+        $params[] = $limit;
670
+
671
+        $sql = $wpdb->prepare( "{$update} {$where} {$order}", $params );
672
+
673
+        $rows_affected = $wpdb->query( $sql );
674
+        if ( $rows_affected === false ) {
675
+            throw new \RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
676
+        }
677
+
678
+        return (int) $rows_affected;
679
+    }
680
+
681
+    /**
682
+     * Get the number of active claims.
683
+     *
684
+     * @return int
685
+     */
686
+    public function get_claim_count() {
687
+        global $wpdb;
688
+
689
+        $sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)";
690
+        $sql = $wpdb->prepare( $sql, [ self::STATUS_PENDING, self::STATUS_RUNNING ] );
691
+
692
+        return (int) $wpdb->get_var( $sql );
693
+    }
694
+
695
+    /**
696
+     * Return an action's claim ID, as stored in the claim_id column.
697
+     *
698
+     * @param string $action_id Action ID.
699
+     * @return mixed
700
+     */
701
+    public function get_claim_id( $action_id ) {
702
+        /** @var \wpdb $wpdb */
703
+        global $wpdb;
704
+
705
+        $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
706
+        $sql = $wpdb->prepare( $sql, $action_id );
707
+
708
+        return (int) $wpdb->get_var( $sql );
709
+    }
710
+
711
+    /**
712
+     * Retrieve the action IDs of action in a claim.
713
+     *
714
+     * @param string $claim_id Claim ID.
715
+     *
716
+     * @return int[]
717
+     */
718
+    public function find_actions_by_claim_id( $claim_id ) {
719
+        /** @var \wpdb $wpdb */
720
+        global $wpdb;
721
+
722
+        $sql = "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id=%d";
723
+        $sql = $wpdb->prepare( $sql, $claim_id );
724
+
725
+        $action_ids = $wpdb->get_col( $sql );
726
+
727
+        return array_map( 'intval', $action_ids );
728
+    }
729
+
730
+    /**
731
+     * Release actions from a claim and delete the claim.
732
+     *
733
+     * @param ActionScheduler_ActionClaim $claim Claim object.
734
+     */
735
+    public function release_claim( ActionScheduler_ActionClaim $claim ) {
736
+        /** @var \wpdb $wpdb */
737
+        global $wpdb;
738
+        $wpdb->update( $wpdb->actionscheduler_actions, [ 'claim_id' => 0 ], [ 'claim_id' => $claim->get_id() ], [ '%d' ], [ '%d' ] );
739
+        $wpdb->delete( $wpdb->actionscheduler_claims, [ 'claim_id' => $claim->get_id() ], [ '%d' ] );
740
+    }
741
+
742
+    /**
743
+     * Remove the claim from an action.
744
+     *
745
+     * @param int $action_id Action ID.
746
+     *
747
+     * @return void
748
+     */
749
+    public function unclaim_action( $action_id ) {
750
+        /** @var \wpdb $wpdb */
751
+        global $wpdb;
752
+        $wpdb->update(
753
+            $wpdb->actionscheduler_actions,
754
+            [ 'claim_id' => 0 ],
755
+            [ 'action_id' => $action_id ],
756
+            [ '%s' ],
757
+            [ '%d' ]
758
+        );
759
+    }
760
+
761
+    /**
762
+     * Mark an action as failed.
763
+     *
764
+     * @param int $action_id Action ID.
765
+     */
766
+    public function mark_failure( $action_id ) {
767
+        /** @var \wpdb $wpdb */
768
+        global $wpdb;
769
+        $updated = $wpdb->update(
770
+            $wpdb->actionscheduler_actions,
771
+            [ 'status' => self::STATUS_FAILED ],
772
+            [ 'action_id' => $action_id ],
773
+            [ '%s' ],
774
+            [ '%d' ]
775
+        );
776
+        if ( empty( $updated ) ) {
777
+            throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
778
+        }
779
+    }
780
+
781
+    /**
782
+     * Add execution message to action log.
783
+     *
784
+     * @param int $action_id Action ID.
785
+     *
786
+     * @return void
787
+     */
788
+    public function log_execution( $action_id ) {
789
+        /** @var \wpdb $wpdb */
790
+        global $wpdb;
791
+
792
+        $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d";
793
+        $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id );
794
+        $wpdb->query( $sql );
795
+    }
796
+
797
+    /**
798
+     * Mark an action as complete.
799
+     *
800
+     * @param int $action_id Action ID.
801
+     *
802
+     * @return void
803
+     */
804
+    public function mark_complete( $action_id ) {
805
+        /** @var \wpdb $wpdb */
806
+        global $wpdb;
807
+        $updated = $wpdb->update(
808
+            $wpdb->actionscheduler_actions,
809
+            [
810
+                'status'             => self::STATUS_COMPLETE,
811
+                'last_attempt_gmt'   => current_time( 'mysql', true ),
812
+                'last_attempt_local' => current_time( 'mysql' ),
813
+            ],
814
+            [ 'action_id' => $action_id ],
815
+            [ '%s' ],
816
+            [ '%d' ]
817
+        );
818
+        if ( empty( $updated ) ) {
819
+            throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
820
+        }
821
+    }
822
+
823
+    /**
824
+     * Get an action's status.
825
+     *
826
+     * @param int $action_id Action ID.
827
+     *
828
+     * @return string
829
+     */
830
+    public function get_status( $action_id ) {
831
+        /** @var \wpdb $wpdb */
832
+        global $wpdb;
833
+        $sql    = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
834
+        $sql    = $wpdb->prepare( $sql, $action_id );
835
+        $status = $wpdb->get_var( $sql );
836
+
837
+        if ( $status === null ) {
838
+            throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
839
+        } elseif ( empty( $status ) ) {
840
+            throw new \RuntimeException( __( 'Unknown status found for action.', 'action-scheduler' ) );
841
+        } else {
842
+            return $status;
843
+        }
844
+    }
845 845
 }
Please login to merge, or discard this patch.
action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php 1 patch
Indentation   +232 added lines, -232 removed lines patch added patch discarded remove patch
@@ -4,237 +4,237 @@
 block discarded – undo
4 4
  * Class ActionScheduler_wpCommentLogger
5 5
  */
6 6
 class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
7
-	const AGENT = 'ActionScheduler';
8
-	const TYPE = 'action_log';
9
-
10
-	/**
11
-	 * @param string $action_id
12
-	 * @param string $message
13
-	 * @param DateTime $date
14
-	 *
15
-	 * @return string The log entry ID
16
-	 */
17
-	public function log( $action_id, $message, DateTime $date = NULL ) {
18
-		if ( empty($date) ) {
19
-			$date = as_get_datetime_object();
20
-		} else {
21
-			$date = as_get_datetime_object( clone $date );
22
-		}
23
-		$comment_id = $this->create_wp_comment( $action_id, $message, $date );
24
-		return $comment_id;
25
-	}
26
-
27
-	protected function create_wp_comment( $action_id, $message, DateTime $date ) {
28
-
29
-		$comment_date_gmt = $date->format('Y-m-d H:i:s');
30
-		ActionScheduler_TimezoneHelper::set_local_timezone( $date );
31
-		$comment_data = array(
32
-			'comment_post_ID' => $action_id,
33
-			'comment_date' => $date->format('Y-m-d H:i:s'),
34
-			'comment_date_gmt' => $comment_date_gmt,
35
-			'comment_author' => self::AGENT,
36
-			'comment_content' => $message,
37
-			'comment_agent' => self::AGENT,
38
-			'comment_type' => self::TYPE,
39
-		);
40
-		return wp_insert_comment($comment_data);
41
-	}
42
-
43
-	/**
44
-	 * @param string $entry_id
45
-	 *
46
-	 * @return ActionScheduler_LogEntry
47
-	 */
48
-	public function get_entry( $entry_id ) {
49
-		$comment = $this->get_comment( $entry_id );
50
-		if ( empty($comment) || $comment->comment_type != self::TYPE ) {
51
-			return new ActionScheduler_NullLogEntry();
52
-		}
53
-
54
-		$date = as_get_datetime_object( $comment->comment_date_gmt );
55
-		ActionScheduler_TimezoneHelper::set_local_timezone( $date );
56
-		return new ActionScheduler_LogEntry( $comment->comment_post_ID, $comment->comment_content, $date );
57
-	}
58
-
59
-	/**
60
-	 * @param string $action_id
61
-	 *
62
-	 * @return ActionScheduler_LogEntry[]
63
-	 */
64
-	public function get_logs( $action_id ) {
65
-		$status = 'all';
66
-		if ( get_post_status($action_id) == 'trash' ) {
67
-			$status = 'post-trashed';
68
-		}
69
-		$comments = get_comments(array(
70
-			'post_id' => $action_id,
71
-			'orderby' => 'comment_date_gmt',
72
-			'order' => 'ASC',
73
-			'type' => self::TYPE,
74
-			'status' => $status,
75
-		));
76
-		$logs = array();
77
-		foreach ( $comments as $c ) {
78
-			$entry = $this->get_entry( $c );
79
-			if ( !empty($entry) ) {
80
-				$logs[] = $entry;
81
-			}
82
-		}
83
-		return $logs;
84
-	}
85
-
86
-	protected function get_comment( $comment_id ) {
87
-		return get_comment( $comment_id );
88
-	}
89
-
90
-
91
-
92
-	/**
93
-	 * @param WP_Comment_Query $query
94
-	 */
95
-	public function filter_comment_queries( $query ) {
96
-		foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) {
97
-			if ( !empty($query->query_vars[$key]) ) {
98
-				return; // don't slow down queries that wouldn't include action_log comments anyway
99
-			}
100
-		}
101
-		$query->query_vars['action_log_filter'] = TRUE;
102
-		add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 );
103
-	}
104
-
105
-	/**
106
-	 * @param array $clauses
107
-	 * @param WP_Comment_Query $query
108
-	 *
109
-	 * @return array
110
-	 */
111
-	public function filter_comment_query_clauses( $clauses, $query ) {
112
-		if ( !empty($query->query_vars['action_log_filter']) ) {
113
-			$clauses['where'] .= $this->get_where_clause();
114
-		}
115
-		return $clauses;
116
-	}
117
-
118
-	/**
119
-	 * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not
120
-	 * the WP_Comment_Query class handled by @see self::filter_comment_queries().
121
-	 *
122
-	 * @param string $where
123
-	 * @param WP_Query $query
124
-	 *
125
-	 * @return string
126
-	 */
127
-	public function filter_comment_feed( $where, $query ) {
128
-		if ( is_comment_feed() ) {
129
-			$where .= $this->get_where_clause();
130
-		}
131
-		return $where;
132
-	}
133
-
134
-	/**
135
-	 * Return a SQL clause to exclude Action Scheduler comments.
136
-	 *
137
-	 * @return string
138
-	 */
139
-	protected function get_where_clause() {
140
-		global $wpdb;
141
-		return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE );
142
-	}
143
-
144
-	/**
145
-	 * Remove action log entries from wp_count_comments()
146
-	 *
147
-	 * @param array $stats
148
-	 * @param int $post_id
149
-	 *
150
-	 * @return object
151
-	 */
152
-	public function filter_comment_count( $stats, $post_id ) {
153
-		global $wpdb;
154
-
155
-		if ( 0 === $post_id ) {
156
-			$stats = $this->get_comment_count();
157
-		}
158
-
159
-		return $stats;
160
-	}
161
-
162
-	/**
163
-	 * Retrieve the comment counts from our cache, or the database if the cached version isn't set.
164
-	 *
165
-	 * @return object
166
-	 */
167
-	protected function get_comment_count() {
168
-		global $wpdb;
169
-
170
-		$stats = get_transient( 'as_comment_count' );
171
-
172
-		if ( ! $stats ) {
173
-			$stats = array();
174
-
175
-			$count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A );
176
-
177
-			$total = 0;
178
-			$stats = array();
179
-			$approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
180
-
181
-			foreach ( (array) $count as $row ) {
182
-				// Don't count post-trashed toward totals
183
-				if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) {
184
-					$total += $row['num_comments'];
185
-				}
186
-				if ( isset( $approved[ $row['comment_approved'] ] ) ) {
187
-					$stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments'];
188
-				}
189
-			}
190
-
191
-			$stats['total_comments'] = $total;
192
-			$stats['all']            = $total;
193
-
194
-			foreach ( $approved as $key ) {
195
-				if ( empty( $stats[ $key ] ) ) {
196
-					$stats[ $key ] = 0;
197
-				}
198
-			}
199
-
200
-			$stats = (object) $stats;
201
-			set_transient( 'as_comment_count', $stats );
202
-		}
203
-
204
-		return $stats;
205
-	}
206
-
207
-	/**
208
-	 * Delete comment count cache whenever there is new comment or the status of a comment changes. Cache
209
-	 * will be regenerated next time ActionScheduler_wpCommentLogger::filter_comment_count() is called.
210
-	 */
211
-	public function delete_comment_count_cache() {
212
-		delete_transient( 'as_comment_count' );
213
-	}
214
-
215
-	/**
216
-	 * @codeCoverageIgnore
217
-	 */
218
-	public function init() {
219
-		add_action( 'action_scheduler_before_process_queue', array( $this, 'disable_comment_counting' ), 10, 0 );
220
-		add_action( 'action_scheduler_after_process_queue', array( $this, 'enable_comment_counting' ), 10, 0 );
221
-
222
-		parent::init();
223
-
224
-		add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 );
225
-		add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs
226
-		add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 );
227
-
228
-		// Delete comments count cache whenever there is a new comment or a comment status changes
229
-		add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) );
230
-		add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) );
231
-	}
232
-
233
-	public function disable_comment_counting() {
234
-		wp_defer_comment_counting(true);
235
-	}
236
-	public function enable_comment_counting() {
237
-		wp_defer_comment_counting(false);
238
-	}
7
+    const AGENT = 'ActionScheduler';
8
+    const TYPE = 'action_log';
9
+
10
+    /**
11
+     * @param string $action_id
12
+     * @param string $message
13
+     * @param DateTime $date
14
+     *
15
+     * @return string The log entry ID
16
+     */
17
+    public function log( $action_id, $message, DateTime $date = NULL ) {
18
+        if ( empty($date) ) {
19
+            $date = as_get_datetime_object();
20
+        } else {
21
+            $date = as_get_datetime_object( clone $date );
22
+        }
23
+        $comment_id = $this->create_wp_comment( $action_id, $message, $date );
24
+        return $comment_id;
25
+    }
26
+
27
+    protected function create_wp_comment( $action_id, $message, DateTime $date ) {
28
+
29
+        $comment_date_gmt = $date->format('Y-m-d H:i:s');
30
+        ActionScheduler_TimezoneHelper::set_local_timezone( $date );
31
+        $comment_data = array(
32
+            'comment_post_ID' => $action_id,
33
+            'comment_date' => $date->format('Y-m-d H:i:s'),
34
+            'comment_date_gmt' => $comment_date_gmt,
35
+            'comment_author' => self::AGENT,
36
+            'comment_content' => $message,
37
+            'comment_agent' => self::AGENT,
38
+            'comment_type' => self::TYPE,
39
+        );
40
+        return wp_insert_comment($comment_data);
41
+    }
42
+
43
+    /**
44
+     * @param string $entry_id
45
+     *
46
+     * @return ActionScheduler_LogEntry
47
+     */
48
+    public function get_entry( $entry_id ) {
49
+        $comment = $this->get_comment( $entry_id );
50
+        if ( empty($comment) || $comment->comment_type != self::TYPE ) {
51
+            return new ActionScheduler_NullLogEntry();
52
+        }
53
+
54
+        $date = as_get_datetime_object( $comment->comment_date_gmt );
55
+        ActionScheduler_TimezoneHelper::set_local_timezone( $date );
56
+        return new ActionScheduler_LogEntry( $comment->comment_post_ID, $comment->comment_content, $date );
57
+    }
58
+
59
+    /**
60
+     * @param string $action_id
61
+     *
62
+     * @return ActionScheduler_LogEntry[]
63
+     */
64
+    public function get_logs( $action_id ) {
65
+        $status = 'all';
66
+        if ( get_post_status($action_id) == 'trash' ) {
67
+            $status = 'post-trashed';
68
+        }
69
+        $comments = get_comments(array(
70
+            'post_id' => $action_id,
71
+            'orderby' => 'comment_date_gmt',
72
+            'order' => 'ASC',
73
+            'type' => self::TYPE,
74
+            'status' => $status,
75
+        ));
76
+        $logs = array();
77
+        foreach ( $comments as $c ) {
78
+            $entry = $this->get_entry( $c );
79
+            if ( !empty($entry) ) {
80
+                $logs[] = $entry;
81
+            }
82
+        }
83
+        return $logs;
84
+    }
85
+
86
+    protected function get_comment( $comment_id ) {
87
+        return get_comment( $comment_id );
88
+    }
89
+
90
+
91
+
92
+    /**
93
+     * @param WP_Comment_Query $query
94
+     */
95
+    public function filter_comment_queries( $query ) {
96
+        foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) {
97
+            if ( !empty($query->query_vars[$key]) ) {
98
+                return; // don't slow down queries that wouldn't include action_log comments anyway
99
+            }
100
+        }
101
+        $query->query_vars['action_log_filter'] = TRUE;
102
+        add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 );
103
+    }
104
+
105
+    /**
106
+     * @param array $clauses
107
+     * @param WP_Comment_Query $query
108
+     *
109
+     * @return array
110
+     */
111
+    public function filter_comment_query_clauses( $clauses, $query ) {
112
+        if ( !empty($query->query_vars['action_log_filter']) ) {
113
+            $clauses['where'] .= $this->get_where_clause();
114
+        }
115
+        return $clauses;
116
+    }
117
+
118
+    /**
119
+     * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not
120
+     * the WP_Comment_Query class handled by @see self::filter_comment_queries().
121
+     *
122
+     * @param string $where
123
+     * @param WP_Query $query
124
+     *
125
+     * @return string
126
+     */
127
+    public function filter_comment_feed( $where, $query ) {
128
+        if ( is_comment_feed() ) {
129
+            $where .= $this->get_where_clause();
130
+        }
131
+        return $where;
132
+    }
133
+
134
+    /**
135
+     * Return a SQL clause to exclude Action Scheduler comments.
136
+     *
137
+     * @return string
138
+     */
139
+    protected function get_where_clause() {
140
+        global $wpdb;
141
+        return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE );
142
+    }
143
+
144
+    /**
145
+     * Remove action log entries from wp_count_comments()
146
+     *
147
+     * @param array $stats
148
+     * @param int $post_id
149
+     *
150
+     * @return object
151
+     */
152
+    public function filter_comment_count( $stats, $post_id ) {
153
+        global $wpdb;
154
+
155
+        if ( 0 === $post_id ) {
156
+            $stats = $this->get_comment_count();
157
+        }
158
+
159
+        return $stats;
160
+    }
161
+
162
+    /**
163
+     * Retrieve the comment counts from our cache, or the database if the cached version isn't set.
164
+     *
165
+     * @return object
166
+     */
167
+    protected function get_comment_count() {
168
+        global $wpdb;
169
+
170
+        $stats = get_transient( 'as_comment_count' );
171
+
172
+        if ( ! $stats ) {
173
+            $stats = array();
174
+
175
+            $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A );
176
+
177
+            $total = 0;
178
+            $stats = array();
179
+            $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
180
+
181
+            foreach ( (array) $count as $row ) {
182
+                // Don't count post-trashed toward totals
183
+                if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) {
184
+                    $total += $row['num_comments'];
185
+                }
186
+                if ( isset( $approved[ $row['comment_approved'] ] ) ) {
187
+                    $stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments'];
188
+                }
189
+            }
190
+
191
+            $stats['total_comments'] = $total;
192
+            $stats['all']            = $total;
193
+
194
+            foreach ( $approved as $key ) {
195
+                if ( empty( $stats[ $key ] ) ) {
196
+                    $stats[ $key ] = 0;
197
+                }
198
+            }
199
+
200
+            $stats = (object) $stats;
201
+            set_transient( 'as_comment_count', $stats );
202
+        }
203
+
204
+        return $stats;
205
+    }
206
+
207
+    /**
208
+     * Delete comment count cache whenever there is new comment or the status of a comment changes. Cache
209
+     * will be regenerated next time ActionScheduler_wpCommentLogger::filter_comment_count() is called.
210
+     */
211
+    public function delete_comment_count_cache() {
212
+        delete_transient( 'as_comment_count' );
213
+    }
214
+
215
+    /**
216
+     * @codeCoverageIgnore
217
+     */
218
+    public function init() {
219
+        add_action( 'action_scheduler_before_process_queue', array( $this, 'disable_comment_counting' ), 10, 0 );
220
+        add_action( 'action_scheduler_after_process_queue', array( $this, 'enable_comment_counting' ), 10, 0 );
221
+
222
+        parent::init();
223
+
224
+        add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 );
225
+        add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs
226
+        add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 );
227
+
228
+        // Delete comments count cache whenever there is a new comment or a comment status changes
229
+        add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) );
230
+        add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) );
231
+    }
232
+
233
+    public function disable_comment_counting() {
234
+        wp_defer_comment_counting(true);
235
+    }
236
+    public function enable_comment_counting() {
237
+        wp_defer_comment_counting(false);
238
+    }
239 239
 
240 240
 }
Please login to merge, or discard this patch.
action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php 1 patch
Indentation   +844 added lines, -844 removed lines patch added patch discarded remove patch
@@ -4,857 +4,857 @@
 block discarded – undo
4 4
  * Class ActionScheduler_wpPostStore
5 5
  */
6 6
 class ActionScheduler_wpPostStore extends ActionScheduler_Store {
7
-	const POST_TYPE = 'scheduled-action';
8
-	const GROUP_TAXONOMY = 'action-group';
9
-	const SCHEDULE_META_KEY = '_action_manager_schedule';
10
-	const DEPENDENCIES_MET = 'as-post-store-dependencies-met';
11
-
12
-	/** @var DateTimeZone */
13
-	protected $local_timezone = NULL;
14
-
15
-	public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ){
16
-		try {
17
-			$this->validate_action( $action );
18
-			$post_array = $this->create_post_array( $action, $scheduled_date );
19
-			$post_id = $this->save_post_array( $post_array );
20
-			$this->save_post_schedule( $post_id, $action->get_schedule() );
21
-			$this->save_action_group( $post_id, $action->get_group() );
22
-			do_action( 'action_scheduler_stored_action', $post_id );
23
-			return $post_id;
24
-		} catch ( Exception $e ) {
25
-			throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
26
-		}
27
-	}
28
-
29
-	protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
30
-		$post = array(
31
-			'post_type' => self::POST_TYPE,
32
-			'post_title' => $action->get_hook(),
33
-			'post_content' => json_encode($action->get_args()),
34
-			'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
35
-			'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
36
-			'post_date'     => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
37
-		);
38
-		return $post;
39
-	}
40
-
41
-	protected function save_post_array( $post_array ) {
42
-		add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
43
-		add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
44
-
45
-		$has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
46
-
47
-		if ( $has_kses ) {
48
-			// Prevent KSES from corrupting JSON in post_content.
49
-			kses_remove_filters();
50
-		}
51
-
52
-		$post_id = wp_insert_post($post_array);
53
-
54
-		if ( $has_kses ) {
55
-			kses_init_filters();
56
-		}
57
-
58
-		remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
59
-		remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
60
-
61
-		if ( is_wp_error($post_id) || empty($post_id) ) {
62
-			throw new RuntimeException( __( 'Unable to save action.', 'action-scheduler' ) );
63
-		}
64
-		return $post_id;
65
-	}
66
-
67
-	public function filter_insert_post_data( $postdata ) {
68
-		if ( $postdata['post_type'] == self::POST_TYPE ) {
69
-			$postdata['post_author'] = 0;
70
-			if ( $postdata['post_status'] == 'future' ) {
71
-				$postdata['post_status'] = 'publish';
72
-			}
73
-		}
74
-		return $postdata;
75
-	}
76
-
77
-	/**
78
-	 * Create a (probably unique) post name for scheduled actions in a more performant manner than wp_unique_post_slug().
79
-	 *
80
-	 * When an action's post status is transitioned to something other than 'draft', 'pending' or 'auto-draft, like 'publish'
81
-	 * or 'failed' or 'trash', WordPress will find a unique slug (stored in post_name column) using the wp_unique_post_slug()
82
-	 * function. This is done to ensure URL uniqueness. The approach taken by wp_unique_post_slug() is to iterate over existing
83
-	 * post_name values that match, and append a number 1 greater than the largest. This makes sense when manually creating a
84
-	 * post from the Edit Post screen. It becomes a bottleneck when automatically processing thousands of actions, with a
85
-	 * database containing thousands of related post_name values.
86
-	 *
87
-	 * WordPress 5.1 introduces the 'pre_wp_unique_post_slug' filter for plugins to address this issue.
88
-	 *
89
-	 * We can short-circuit WordPress's wp_unique_post_slug() approach using the 'pre_wp_unique_post_slug' filter. This
90
-	 * method is available to be used as a callback on that filter. It provides a more scalable approach to generating a
91
-	 * post_name/slug that is probably unique. Because Action Scheduler never actually uses the post_name field, or an
92
-	 * action's slug, being probably unique is good enough.
93
-	 *
94
-	 * For more backstory on this issue, see:
95
-	 * - https://github.com/woocommerce/action-scheduler/issues/44 and
96
-	 * - https://core.trac.wordpress.org/ticket/21112
97
-	 *
98
-	 * @param string $override_slug Short-circuit return value.
99
-	 * @param string $slug          The desired slug (post_name).
100
-	 * @param int    $post_ID       Post ID.
101
-	 * @param string $post_status   The post status.
102
-	 * @param string $post_type     Post type.
103
-	 * @return string
104
-	 */
105
-	public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
106
-		if ( self::POST_TYPE == $post_type ) {
107
-			$override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
108
-		}
109
-		return $override_slug;
110
-	}
111
-
112
-	protected function save_post_schedule( $post_id, $schedule ) {
113
-		update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
114
-	}
115
-
116
-	protected function save_action_group( $post_id, $group ) {
117
-		if ( empty($group) ) {
118
-			wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, FALSE );
119
-		} else {
120
-			wp_set_object_terms( $post_id, array($group), self::GROUP_TAXONOMY, FALSE );
121
-		}
122
-	}
123
-
124
-	public function fetch_action( $action_id ) {
125
-		$post = $this->get_post( $action_id );
126
-		if ( empty($post) || $post->post_type != self::POST_TYPE ) {
127
-			return $this->get_null_action();
128
-		}
129
-
130
-		try {
131
-			$action = $this->make_action_from_post( $post );
132
-		} catch ( ActionScheduler_InvalidActionException $exception ) {
133
-			do_action( 'action_scheduler_failed_fetch_action', $post->ID, $exception );
134
-			return $this->get_null_action();
135
-		}
136
-
137
-		return $action;
138
-	}
139
-
140
-	protected function get_post( $action_id ) {
141
-		if ( empty($action_id) ) {
142
-			return NULL;
143
-		}
144
-		return get_post($action_id);
145
-	}
146
-
147
-	protected function get_null_action() {
148
-		return new ActionScheduler_NullAction();
149
-	}
150
-
151
-	protected function make_action_from_post( $post ) {
152
-		$hook = $post->post_title;
153
-
154
-		$args = json_decode( $post->post_content, true );
155
-		$this->validate_args( $args, $post->ID );
156
-
157
-		$schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
158
-		$this->validate_schedule( $schedule, $post->ID );
159
-
160
-		$group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array('fields' => 'names') );
161
-		$group = empty( $group ) ? '' : reset($group);
162
-
163
-		return ActionScheduler::factory()->get_stored_action( $this->get_action_status_by_post_status( $post->post_status ), $hook, $args, $schedule, $group );
164
-	}
165
-
166
-	/**
167
-	 * @param string $post_status
168
-	 *
169
-	 * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
170
-	 * @return string
171
-	 */
172
-	protected function get_action_status_by_post_status( $post_status ) {
173
-
174
-		switch ( $post_status ) {
175
-			case 'publish' :
176
-				$action_status = self::STATUS_COMPLETE;
177
-				break;
178
-			case 'trash' :
179
-				$action_status = self::STATUS_CANCELED;
180
-				break;
181
-			default :
182
-				if ( ! array_key_exists( $post_status, $this->get_status_labels() ) ) {
183
-					throw new InvalidArgumentException( sprintf( 'Invalid post status: "%s". No matching action status available.', $post_status ) );
184
-				}
185
-				$action_status = $post_status;
186
-				break;
187
-		}
188
-
189
-		return $action_status;
190
-	}
191
-
192
-	/**
193
-	 * @param string $action_status
194
-	 * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
195
-	 * @return string
196
-	 */
197
-	protected function get_post_status_by_action_status( $action_status ) {
198
-
199
-		switch ( $action_status ) {
200
-			case self::STATUS_COMPLETE :
201
-				$post_status = 'publish';
202
-				break;
203
-			case self::STATUS_CANCELED :
204
-				$post_status = 'trash';
205
-				break;
206
-			default :
207
-				if ( ! array_key_exists( $action_status, $this->get_status_labels() ) ) {
208
-					throw new InvalidArgumentException( sprintf( 'Invalid action status: "%s".', $action_status ) );
209
-				}
210
-				$post_status = $action_status;
211
-				break;
212
-		}
213
-
214
-		return $post_status;
215
-	}
216
-
217
-	/**
218
-	 * @param string $hook
219
-	 * @param array $params
220
-	 *
221
-	 * @return string ID of the next action matching the criteria or NULL if not found
222
-	 */
223
-	public function find_action( $hook, $params = array() ) {
224
-		$params = wp_parse_args( $params, array(
225
-			'args' => NULL,
226
-			'status' => ActionScheduler_Store::STATUS_PENDING,
227
-			'group' => '',
228
-		));
229
-		/** @var wpdb $wpdb */
230
-		global $wpdb;
231
-		$query = "SELECT p.ID FROM {$wpdb->posts} p";
232
-		$args = array();
233
-		if ( !empty($params['group']) ) {
234
-			$query .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
235
-			$query .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
236
-			$query .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id AND t.slug=%s";
237
-			$args[] = $params['group'];
238
-		}
239
-		$query .= " WHERE p.post_title=%s";
240
-		$args[] = $hook;
241
-		$query .= " AND p.post_type=%s";
242
-		$args[] = self::POST_TYPE;
243
-		if ( !is_null($params['args']) ) {
244
-			$query .= " AND p.post_content=%s";
245
-			$args[] = json_encode($params['args']);
246
-		}
247
-
248
-		if ( ! empty( $params['status'] ) ) {
249
-			$query .= " AND p.post_status=%s";
250
-			$args[] = $this->get_post_status_by_action_status( $params['status'] );
251
-		}
252
-
253
-		switch ( $params['status'] ) {
254
-			case self::STATUS_COMPLETE:
255
-			case self::STATUS_RUNNING:
256
-			case self::STATUS_FAILED:
257
-				$order = 'DESC'; // Find the most recent action that matches
258
-				break;
259
-			case self::STATUS_PENDING:
260
-			default:
261
-				$order = 'ASC'; // Find the next action that matches
262
-				break;
263
-		}
264
-		$query .= " ORDER BY post_date_gmt $order LIMIT 1";
265
-
266
-		$query = $wpdb->prepare( $query, $args );
267
-
268
-		$id = $wpdb->get_var($query);
269
-		return $id;
270
-	}
271
-
272
-	/**
273
-	 * Returns the SQL statement to query (or count) actions.
274
-	 *
275
-	 * @param array $query Filtering options
276
-	 * @param string $select_or_count  Whether the SQL should select and return the IDs or just the row count
277
-	 * @throws InvalidArgumentException if $select_or_count not count or select
278
-	 * @return string SQL statement. The returned SQL is already properly escaped.
279
-	 */
280
-	protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
281
-
282
-		if ( ! in_array( $select_or_count, array( 'select', 'count' ) ) ) {
283
-			throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) );
284
-		}
285
-
286
-		$query = wp_parse_args( $query, array(
287
-			'hook' => '',
288
-			'args' => NULL,
289
-			'date' => NULL,
290
-			'date_compare' => '<=',
291
-			'modified' => NULL,
292
-			'modified_compare' => '<=',
293
-			'group' => '',
294
-			'status' => '',
295
-			'claimed' => NULL,
296
-			'per_page' => 5,
297
-			'offset' => 0,
298
-			'orderby' => 'date',
299
-			'order' => 'ASC',
300
-			'search' => '',
301
-		) );
302
-
303
-		/** @var wpdb $wpdb */
304
-		global $wpdb;
305
-		$sql  = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
306
-		$sql .= "FROM {$wpdb->posts} p";
307
-		$sql_params = array();
308
-		if ( empty( $query['group'] ) && 'group' === $query['orderby'] ) {
309
-			$sql .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
310
-			$sql .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
311
-			$sql .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
312
-		} elseif ( ! empty( $query['group'] ) ) {
313
-			$sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
314
-			$sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
315
-			$sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
316
-			$sql .= " AND t.slug=%s";
317
-			$sql_params[] = $query['group'];
318
-		}
319
-		$sql .= " WHERE post_type=%s";
320
-		$sql_params[] = self::POST_TYPE;
321
-		if ( $query['hook'] ) {
322
-			$sql .= " AND p.post_title=%s";
323
-			$sql_params[] = $query['hook'];
324
-		}
325
-		if ( !is_null($query['args']) ) {
326
-			$sql .= " AND p.post_content=%s";
327
-			$sql_params[] = json_encode($query['args']);
328
-		}
329
-
330
-		if ( ! empty( $query['status'] ) ) {
331
-			$sql .= " AND p.post_status=%s";
332
-			$sql_params[] = $this->get_post_status_by_action_status( $query['status'] );
333
-		}
334
-
335
-		if ( $query['date'] instanceof DateTime ) {
336
-			$date = clone $query['date'];
337
-			$date->setTimezone( new DateTimeZone('UTC') );
338
-			$date_string = $date->format('Y-m-d H:i:s');
339
-			$comparator = $this->validate_sql_comparator($query['date_compare']);
340
-			$sql .= " AND p.post_date_gmt $comparator %s";
341
-			$sql_params[] = $date_string;
342
-		}
343
-
344
-		if ( $query['modified'] instanceof DateTime ) {
345
-			$modified = clone $query['modified'];
346
-			$modified->setTimezone( new DateTimeZone('UTC') );
347
-			$date_string = $modified->format('Y-m-d H:i:s');
348
-			$comparator = $this->validate_sql_comparator($query['modified_compare']);
349
-			$sql .= " AND p.post_modified_gmt $comparator %s";
350
-			$sql_params[] = $date_string;
351
-		}
352
-
353
-		if ( $query['claimed'] === TRUE ) {
354
-			$sql .= " AND p.post_password != ''";
355
-		} elseif ( $query['claimed'] === FALSE ) {
356
-			$sql .= " AND p.post_password = ''";
357
-		} elseif ( !is_null($query['claimed']) ) {
358
-			$sql .= " AND p.post_password = %s";
359
-			$sql_params[] = $query['claimed'];
360
-		}
361
-
362
-		if ( ! empty( $query['search'] ) ) {
363
-			$sql .= " AND (p.post_title LIKE %s OR p.post_content LIKE %s OR p.post_password LIKE %s)";
364
-			for( $i = 0; $i < 3; $i++ ) {
365
-				$sql_params[] = sprintf( '%%%s%%', $query['search'] );
366
-			}
367
-		}
368
-
369
-		if ( 'select' === $select_or_count ) {
370
-			switch ( $query['orderby'] ) {
371
-				case 'hook':
372
-					$orderby = 'p.post_title';
373
-					break;
374
-				case 'group':
375
-					$orderby = 't.name';
376
-					break;
377
-				case 'status':
378
-					$orderby = 'p.post_status';
379
-					break;
380
-				case 'modified':
381
-					$orderby = 'p.post_modified';
382
-					break;
383
-				case 'claim_id':
384
-					$orderby = 'p.post_password';
385
-					break;
386
-				case 'schedule':
387
-				case 'date':
388
-				default:
389
-					$orderby = 'p.post_date_gmt';
390
-					break;
391
-			}
392
-			if ( 'ASC' === strtoupper( $query['order'] ) ) {
393
-				$order = 'ASC';
394
-			} else {
395
-				$order = 'DESC';
396
-			}
397
-			$sql .= " ORDER BY $orderby $order";
398
-			if ( $query['per_page'] > 0 ) {
399
-				$sql .= " LIMIT %d, %d";
400
-				$sql_params[] = $query['offset'];
401
-				$sql_params[] = $query['per_page'];
402
-			}
403
-		}
404
-
405
-		return $wpdb->prepare( $sql, $sql_params );
406
-	}
407
-
408
-	/**
409
-	 * @param array $query
410
-	 * @param string $query_type Whether to select or count the results. Default, select.
411
-	 * @return string|array The IDs of actions matching the query
412
-	 */
413
-	public function query_actions( $query = array(), $query_type = 'select' ) {
414
-		/** @var wpdb $wpdb */
415
-		global $wpdb;
416
-
417
-		$sql = $this->get_query_actions_sql( $query, $query_type );
418
-
419
-		return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql );
420
-	}
421
-
422
-	/**
423
-	 * Get a count of all actions in the store, grouped by status
424
-	 *
425
-	 * @return array
426
-	 */
427
-	public function action_counts() {
428
-
429
-		$action_counts_by_status = array();
430
-		$action_stati_and_labels = $this->get_status_labels();
431
-		$posts_count_by_status   = (array) wp_count_posts( self::POST_TYPE, 'readable' );
432
-
433
-		foreach ( $posts_count_by_status as $post_status_name => $count ) {
434
-
435
-			try {
436
-				$action_status_name = $this->get_action_status_by_post_status( $post_status_name );
437
-			} catch ( Exception $e ) {
438
-				// Ignore any post statuses that aren't for actions
439
-				continue;
440
-			}
441
-			if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) {
442
-				$action_counts_by_status[ $action_status_name ] = $count;
443
-			}
444
-		}
445
-
446
-		return $action_counts_by_status;
447
-	}
448
-
449
-	/**
450
-	 * @param string $action_id
451
-	 *
452
-	 * @throws InvalidArgumentException
453
-	 */
454
-	public function cancel_action( $action_id ) {
455
-		$post = get_post( $action_id );
456
-		if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
457
-			throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
458
-		}
459
-		do_action( 'action_scheduler_canceled_action', $action_id );
460
-		add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
461
-		wp_trash_post( $action_id );
462
-		remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
463
-	}
464
-
465
-	public function delete_action( $action_id ) {
466
-		$post = get_post( $action_id );
467
-		if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
468
-			throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
469
-		}
470
-		do_action( 'action_scheduler_deleted_action', $action_id );
471
-
472
-		wp_delete_post( $action_id, TRUE );
473
-	}
474
-
475
-	/**
476
-	 * @param string $action_id
477
-	 *
478
-	 * @throws InvalidArgumentException
479
-	 * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
480
-	 */
481
-	public function get_date( $action_id ) {
482
-		$next = $this->get_date_gmt( $action_id );
483
-		return ActionScheduler_TimezoneHelper::set_local_timezone( $next );
484
-	}
485
-
486
-	/**
487
-	 * @param string $action_id
488
-	 *
489
-	 * @throws InvalidArgumentException
490
-	 * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
491
-	 */
492
-	public function get_date_gmt( $action_id ) {
493
-		$post = get_post( $action_id );
494
-		if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
495
-			throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
496
-		}
497
-		if ( $post->post_status == 'publish' ) {
498
-			return as_get_datetime_object( $post->post_modified_gmt );
499
-		} else {
500
-			return as_get_datetime_object( $post->post_date_gmt );
501
-		}
502
-	}
503
-
504
-	/**
505
-	 * @param int      $max_actions
506
-	 * @param DateTime $before_date Jobs must be schedule before this date. Defaults to now.
507
-	 * @param array    $hooks       Claim only actions with a hook or hooks.
508
-	 * @param string   $group       Claim only actions in the given group.
509
-	 *
510
-	 * @return ActionScheduler_ActionClaim
511
-	 * @throws RuntimeException When there is an error staking a claim.
512
-	 * @throws InvalidArgumentException When the given group is not valid.
513
-	 */
514
-	public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
515
-		$claim_id = $this->generate_claim_id();
516
-		$this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
517
-		$action_ids = $this->find_actions_by_claim_id( $claim_id );
518
-
519
-		return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
520
-	}
521
-
522
-	/**
523
-	 * @return int
524
-	 */
525
-	public function get_claim_count(){
526
-		global $wpdb;
527
-
528
-		$sql = "SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')";
529
-		$sql = $wpdb->prepare( $sql, array( self::POST_TYPE ) );
530
-
531
-		return $wpdb->get_var( $sql );
532
-	}
533
-
534
-	protected function generate_claim_id() {
535
-		$claim_id = md5(microtime(true) . rand(0,1000));
536
-		return substr($claim_id, 0, 20); // to fit in db field with 20 char limit
537
-	}
538
-
539
-	/**
540
-	 * @param string   $claim_id
541
-	 * @param int      $limit
542
-	 * @param DateTime $before_date Should use UTC timezone.
543
-	 * @param array    $hooks       Claim only actions with a hook or hooks.
544
-	 * @param string   $group       Claim only actions in the given group.
545
-	 *
546
-	 * @return int The number of actions that were claimed
547
-	 * @throws RuntimeException When there is a database error.
548
-	 * @throws InvalidArgumentException When the group is invalid.
549
-	 */
550
-	protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) {
551
-		// Set up initial variables.
552
-		$date      = null === $before_date ? as_get_datetime_object() : clone $before_date;
553
-		$limit_ids = ! empty( $group );
554
-		$ids       = $limit_ids ? $this->get_actions_by_group( $group, $limit, $date ) : array();
555
-
556
-		// If limiting by IDs and no posts found, then return early since we have nothing to update.
557
-		if ( $limit_ids && 0 === count( $ids ) ) {
558
-			return 0;
559
-		}
560
-
561
-		/** @var wpdb $wpdb */
562
-		global $wpdb;
563
-
564
-		/*
7
+    const POST_TYPE = 'scheduled-action';
8
+    const GROUP_TAXONOMY = 'action-group';
9
+    const SCHEDULE_META_KEY = '_action_manager_schedule';
10
+    const DEPENDENCIES_MET = 'as-post-store-dependencies-met';
11
+
12
+    /** @var DateTimeZone */
13
+    protected $local_timezone = NULL;
14
+
15
+    public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ){
16
+        try {
17
+            $this->validate_action( $action );
18
+            $post_array = $this->create_post_array( $action, $scheduled_date );
19
+            $post_id = $this->save_post_array( $post_array );
20
+            $this->save_post_schedule( $post_id, $action->get_schedule() );
21
+            $this->save_action_group( $post_id, $action->get_group() );
22
+            do_action( 'action_scheduler_stored_action', $post_id );
23
+            return $post_id;
24
+        } catch ( Exception $e ) {
25
+            throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
26
+        }
27
+    }
28
+
29
+    protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
30
+        $post = array(
31
+            'post_type' => self::POST_TYPE,
32
+            'post_title' => $action->get_hook(),
33
+            'post_content' => json_encode($action->get_args()),
34
+            'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
35
+            'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
36
+            'post_date'     => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
37
+        );
38
+        return $post;
39
+    }
40
+
41
+    protected function save_post_array( $post_array ) {
42
+        add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
43
+        add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
44
+
45
+        $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
46
+
47
+        if ( $has_kses ) {
48
+            // Prevent KSES from corrupting JSON in post_content.
49
+            kses_remove_filters();
50
+        }
51
+
52
+        $post_id = wp_insert_post($post_array);
53
+
54
+        if ( $has_kses ) {
55
+            kses_init_filters();
56
+        }
57
+
58
+        remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
59
+        remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
60
+
61
+        if ( is_wp_error($post_id) || empty($post_id) ) {
62
+            throw new RuntimeException( __( 'Unable to save action.', 'action-scheduler' ) );
63
+        }
64
+        return $post_id;
65
+    }
66
+
67
+    public function filter_insert_post_data( $postdata ) {
68
+        if ( $postdata['post_type'] == self::POST_TYPE ) {
69
+            $postdata['post_author'] = 0;
70
+            if ( $postdata['post_status'] == 'future' ) {
71
+                $postdata['post_status'] = 'publish';
72
+            }
73
+        }
74
+        return $postdata;
75
+    }
76
+
77
+    /**
78
+     * Create a (probably unique) post name for scheduled actions in a more performant manner than wp_unique_post_slug().
79
+     *
80
+     * When an action's post status is transitioned to something other than 'draft', 'pending' or 'auto-draft, like 'publish'
81
+     * or 'failed' or 'trash', WordPress will find a unique slug (stored in post_name column) using the wp_unique_post_slug()
82
+     * function. This is done to ensure URL uniqueness. The approach taken by wp_unique_post_slug() is to iterate over existing
83
+     * post_name values that match, and append a number 1 greater than the largest. This makes sense when manually creating a
84
+     * post from the Edit Post screen. It becomes a bottleneck when automatically processing thousands of actions, with a
85
+     * database containing thousands of related post_name values.
86
+     *
87
+     * WordPress 5.1 introduces the 'pre_wp_unique_post_slug' filter for plugins to address this issue.
88
+     *
89
+     * We can short-circuit WordPress's wp_unique_post_slug() approach using the 'pre_wp_unique_post_slug' filter. This
90
+     * method is available to be used as a callback on that filter. It provides a more scalable approach to generating a
91
+     * post_name/slug that is probably unique. Because Action Scheduler never actually uses the post_name field, or an
92
+     * action's slug, being probably unique is good enough.
93
+     *
94
+     * For more backstory on this issue, see:
95
+     * - https://github.com/woocommerce/action-scheduler/issues/44 and
96
+     * - https://core.trac.wordpress.org/ticket/21112
97
+     *
98
+     * @param string $override_slug Short-circuit return value.
99
+     * @param string $slug          The desired slug (post_name).
100
+     * @param int    $post_ID       Post ID.
101
+     * @param string $post_status   The post status.
102
+     * @param string $post_type     Post type.
103
+     * @return string
104
+     */
105
+    public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
106
+        if ( self::POST_TYPE == $post_type ) {
107
+            $override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
108
+        }
109
+        return $override_slug;
110
+    }
111
+
112
+    protected function save_post_schedule( $post_id, $schedule ) {
113
+        update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
114
+    }
115
+
116
+    protected function save_action_group( $post_id, $group ) {
117
+        if ( empty($group) ) {
118
+            wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, FALSE );
119
+        } else {
120
+            wp_set_object_terms( $post_id, array($group), self::GROUP_TAXONOMY, FALSE );
121
+        }
122
+    }
123
+
124
+    public function fetch_action( $action_id ) {
125
+        $post = $this->get_post( $action_id );
126
+        if ( empty($post) || $post->post_type != self::POST_TYPE ) {
127
+            return $this->get_null_action();
128
+        }
129
+
130
+        try {
131
+            $action = $this->make_action_from_post( $post );
132
+        } catch ( ActionScheduler_InvalidActionException $exception ) {
133
+            do_action( 'action_scheduler_failed_fetch_action', $post->ID, $exception );
134
+            return $this->get_null_action();
135
+        }
136
+
137
+        return $action;
138
+    }
139
+
140
+    protected function get_post( $action_id ) {
141
+        if ( empty($action_id) ) {
142
+            return NULL;
143
+        }
144
+        return get_post($action_id);
145
+    }
146
+
147
+    protected function get_null_action() {
148
+        return new ActionScheduler_NullAction();
149
+    }
150
+
151
+    protected function make_action_from_post( $post ) {
152
+        $hook = $post->post_title;
153
+
154
+        $args = json_decode( $post->post_content, true );
155
+        $this->validate_args( $args, $post->ID );
156
+
157
+        $schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
158
+        $this->validate_schedule( $schedule, $post->ID );
159
+
160
+        $group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array('fields' => 'names') );
161
+        $group = empty( $group ) ? '' : reset($group);
162
+
163
+        return ActionScheduler::factory()->get_stored_action( $this->get_action_status_by_post_status( $post->post_status ), $hook, $args, $schedule, $group );
164
+    }
165
+
166
+    /**
167
+     * @param string $post_status
168
+     *
169
+     * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
170
+     * @return string
171
+     */
172
+    protected function get_action_status_by_post_status( $post_status ) {
173
+
174
+        switch ( $post_status ) {
175
+            case 'publish' :
176
+                $action_status = self::STATUS_COMPLETE;
177
+                break;
178
+            case 'trash' :
179
+                $action_status = self::STATUS_CANCELED;
180
+                break;
181
+            default :
182
+                if ( ! array_key_exists( $post_status, $this->get_status_labels() ) ) {
183
+                    throw new InvalidArgumentException( sprintf( 'Invalid post status: "%s". No matching action status available.', $post_status ) );
184
+                }
185
+                $action_status = $post_status;
186
+                break;
187
+        }
188
+
189
+        return $action_status;
190
+    }
191
+
192
+    /**
193
+     * @param string $action_status
194
+     * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
195
+     * @return string
196
+     */
197
+    protected function get_post_status_by_action_status( $action_status ) {
198
+
199
+        switch ( $action_status ) {
200
+            case self::STATUS_COMPLETE :
201
+                $post_status = 'publish';
202
+                break;
203
+            case self::STATUS_CANCELED :
204
+                $post_status = 'trash';
205
+                break;
206
+            default :
207
+                if ( ! array_key_exists( $action_status, $this->get_status_labels() ) ) {
208
+                    throw new InvalidArgumentException( sprintf( 'Invalid action status: "%s".', $action_status ) );
209
+                }
210
+                $post_status = $action_status;
211
+                break;
212
+        }
213
+
214
+        return $post_status;
215
+    }
216
+
217
+    /**
218
+     * @param string $hook
219
+     * @param array $params
220
+     *
221
+     * @return string ID of the next action matching the criteria or NULL if not found
222
+     */
223
+    public function find_action( $hook, $params = array() ) {
224
+        $params = wp_parse_args( $params, array(
225
+            'args' => NULL,
226
+            'status' => ActionScheduler_Store::STATUS_PENDING,
227
+            'group' => '',
228
+        ));
229
+        /** @var wpdb $wpdb */
230
+        global $wpdb;
231
+        $query = "SELECT p.ID FROM {$wpdb->posts} p";
232
+        $args = array();
233
+        if ( !empty($params['group']) ) {
234
+            $query .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
235
+            $query .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
236
+            $query .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id AND t.slug=%s";
237
+            $args[] = $params['group'];
238
+        }
239
+        $query .= " WHERE p.post_title=%s";
240
+        $args[] = $hook;
241
+        $query .= " AND p.post_type=%s";
242
+        $args[] = self::POST_TYPE;
243
+        if ( !is_null($params['args']) ) {
244
+            $query .= " AND p.post_content=%s";
245
+            $args[] = json_encode($params['args']);
246
+        }
247
+
248
+        if ( ! empty( $params['status'] ) ) {
249
+            $query .= " AND p.post_status=%s";
250
+            $args[] = $this->get_post_status_by_action_status( $params['status'] );
251
+        }
252
+
253
+        switch ( $params['status'] ) {
254
+            case self::STATUS_COMPLETE:
255
+            case self::STATUS_RUNNING:
256
+            case self::STATUS_FAILED:
257
+                $order = 'DESC'; // Find the most recent action that matches
258
+                break;
259
+            case self::STATUS_PENDING:
260
+            default:
261
+                $order = 'ASC'; // Find the next action that matches
262
+                break;
263
+        }
264
+        $query .= " ORDER BY post_date_gmt $order LIMIT 1";
265
+
266
+        $query = $wpdb->prepare( $query, $args );
267
+
268
+        $id = $wpdb->get_var($query);
269
+        return $id;
270
+    }
271
+
272
+    /**
273
+     * Returns the SQL statement to query (or count) actions.
274
+     *
275
+     * @param array $query Filtering options
276
+     * @param string $select_or_count  Whether the SQL should select and return the IDs or just the row count
277
+     * @throws InvalidArgumentException if $select_or_count not count or select
278
+     * @return string SQL statement. The returned SQL is already properly escaped.
279
+     */
280
+    protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
281
+
282
+        if ( ! in_array( $select_or_count, array( 'select', 'count' ) ) ) {
283
+            throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) );
284
+        }
285
+
286
+        $query = wp_parse_args( $query, array(
287
+            'hook' => '',
288
+            'args' => NULL,
289
+            'date' => NULL,
290
+            'date_compare' => '<=',
291
+            'modified' => NULL,
292
+            'modified_compare' => '<=',
293
+            'group' => '',
294
+            'status' => '',
295
+            'claimed' => NULL,
296
+            'per_page' => 5,
297
+            'offset' => 0,
298
+            'orderby' => 'date',
299
+            'order' => 'ASC',
300
+            'search' => '',
301
+        ) );
302
+
303
+        /** @var wpdb $wpdb */
304
+        global $wpdb;
305
+        $sql  = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
306
+        $sql .= "FROM {$wpdb->posts} p";
307
+        $sql_params = array();
308
+        if ( empty( $query['group'] ) && 'group' === $query['orderby'] ) {
309
+            $sql .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
310
+            $sql .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
311
+            $sql .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
312
+        } elseif ( ! empty( $query['group'] ) ) {
313
+            $sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
314
+            $sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
315
+            $sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
316
+            $sql .= " AND t.slug=%s";
317
+            $sql_params[] = $query['group'];
318
+        }
319
+        $sql .= " WHERE post_type=%s";
320
+        $sql_params[] = self::POST_TYPE;
321
+        if ( $query['hook'] ) {
322
+            $sql .= " AND p.post_title=%s";
323
+            $sql_params[] = $query['hook'];
324
+        }
325
+        if ( !is_null($query['args']) ) {
326
+            $sql .= " AND p.post_content=%s";
327
+            $sql_params[] = json_encode($query['args']);
328
+        }
329
+
330
+        if ( ! empty( $query['status'] ) ) {
331
+            $sql .= " AND p.post_status=%s";
332
+            $sql_params[] = $this->get_post_status_by_action_status( $query['status'] );
333
+        }
334
+
335
+        if ( $query['date'] instanceof DateTime ) {
336
+            $date = clone $query['date'];
337
+            $date->setTimezone( new DateTimeZone('UTC') );
338
+            $date_string = $date->format('Y-m-d H:i:s');
339
+            $comparator = $this->validate_sql_comparator($query['date_compare']);
340
+            $sql .= " AND p.post_date_gmt $comparator %s";
341
+            $sql_params[] = $date_string;
342
+        }
343
+
344
+        if ( $query['modified'] instanceof DateTime ) {
345
+            $modified = clone $query['modified'];
346
+            $modified->setTimezone( new DateTimeZone('UTC') );
347
+            $date_string = $modified->format('Y-m-d H:i:s');
348
+            $comparator = $this->validate_sql_comparator($query['modified_compare']);
349
+            $sql .= " AND p.post_modified_gmt $comparator %s";
350
+            $sql_params[] = $date_string;
351
+        }
352
+
353
+        if ( $query['claimed'] === TRUE ) {
354
+            $sql .= " AND p.post_password != ''";
355
+        } elseif ( $query['claimed'] === FALSE ) {
356
+            $sql .= " AND p.post_password = ''";
357
+        } elseif ( !is_null($query['claimed']) ) {
358
+            $sql .= " AND p.post_password = %s";
359
+            $sql_params[] = $query['claimed'];
360
+        }
361
+
362
+        if ( ! empty( $query['search'] ) ) {
363
+            $sql .= " AND (p.post_title LIKE %s OR p.post_content LIKE %s OR p.post_password LIKE %s)";
364
+            for( $i = 0; $i < 3; $i++ ) {
365
+                $sql_params[] = sprintf( '%%%s%%', $query['search'] );
366
+            }
367
+        }
368
+
369
+        if ( 'select' === $select_or_count ) {
370
+            switch ( $query['orderby'] ) {
371
+                case 'hook':
372
+                    $orderby = 'p.post_title';
373
+                    break;
374
+                case 'group':
375
+                    $orderby = 't.name';
376
+                    break;
377
+                case 'status':
378
+                    $orderby = 'p.post_status';
379
+                    break;
380
+                case 'modified':
381
+                    $orderby = 'p.post_modified';
382
+                    break;
383
+                case 'claim_id':
384
+                    $orderby = 'p.post_password';
385
+                    break;
386
+                case 'schedule':
387
+                case 'date':
388
+                default:
389
+                    $orderby = 'p.post_date_gmt';
390
+                    break;
391
+            }
392
+            if ( 'ASC' === strtoupper( $query['order'] ) ) {
393
+                $order = 'ASC';
394
+            } else {
395
+                $order = 'DESC';
396
+            }
397
+            $sql .= " ORDER BY $orderby $order";
398
+            if ( $query['per_page'] > 0 ) {
399
+                $sql .= " LIMIT %d, %d";
400
+                $sql_params[] = $query['offset'];
401
+                $sql_params[] = $query['per_page'];
402
+            }
403
+        }
404
+
405
+        return $wpdb->prepare( $sql, $sql_params );
406
+    }
407
+
408
+    /**
409
+     * @param array $query
410
+     * @param string $query_type Whether to select or count the results. Default, select.
411
+     * @return string|array The IDs of actions matching the query
412
+     */
413
+    public function query_actions( $query = array(), $query_type = 'select' ) {
414
+        /** @var wpdb $wpdb */
415
+        global $wpdb;
416
+
417
+        $sql = $this->get_query_actions_sql( $query, $query_type );
418
+
419
+        return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql );
420
+    }
421
+
422
+    /**
423
+     * Get a count of all actions in the store, grouped by status
424
+     *
425
+     * @return array
426
+     */
427
+    public function action_counts() {
428
+
429
+        $action_counts_by_status = array();
430
+        $action_stati_and_labels = $this->get_status_labels();
431
+        $posts_count_by_status   = (array) wp_count_posts( self::POST_TYPE, 'readable' );
432
+
433
+        foreach ( $posts_count_by_status as $post_status_name => $count ) {
434
+
435
+            try {
436
+                $action_status_name = $this->get_action_status_by_post_status( $post_status_name );
437
+            } catch ( Exception $e ) {
438
+                // Ignore any post statuses that aren't for actions
439
+                continue;
440
+            }
441
+            if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) {
442
+                $action_counts_by_status[ $action_status_name ] = $count;
443
+            }
444
+        }
445
+
446
+        return $action_counts_by_status;
447
+    }
448
+
449
+    /**
450
+     * @param string $action_id
451
+     *
452
+     * @throws InvalidArgumentException
453
+     */
454
+    public function cancel_action( $action_id ) {
455
+        $post = get_post( $action_id );
456
+        if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
457
+            throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
458
+        }
459
+        do_action( 'action_scheduler_canceled_action', $action_id );
460
+        add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
461
+        wp_trash_post( $action_id );
462
+        remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
463
+    }
464
+
465
+    public function delete_action( $action_id ) {
466
+        $post = get_post( $action_id );
467
+        if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
468
+            throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
469
+        }
470
+        do_action( 'action_scheduler_deleted_action', $action_id );
471
+
472
+        wp_delete_post( $action_id, TRUE );
473
+    }
474
+
475
+    /**
476
+     * @param string $action_id
477
+     *
478
+     * @throws InvalidArgumentException
479
+     * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
480
+     */
481
+    public function get_date( $action_id ) {
482
+        $next = $this->get_date_gmt( $action_id );
483
+        return ActionScheduler_TimezoneHelper::set_local_timezone( $next );
484
+    }
485
+
486
+    /**
487
+     * @param string $action_id
488
+     *
489
+     * @throws InvalidArgumentException
490
+     * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
491
+     */
492
+    public function get_date_gmt( $action_id ) {
493
+        $post = get_post( $action_id );
494
+        if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
495
+            throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
496
+        }
497
+        if ( $post->post_status == 'publish' ) {
498
+            return as_get_datetime_object( $post->post_modified_gmt );
499
+        } else {
500
+            return as_get_datetime_object( $post->post_date_gmt );
501
+        }
502
+    }
503
+
504
+    /**
505
+     * @param int      $max_actions
506
+     * @param DateTime $before_date Jobs must be schedule before this date. Defaults to now.
507
+     * @param array    $hooks       Claim only actions with a hook or hooks.
508
+     * @param string   $group       Claim only actions in the given group.
509
+     *
510
+     * @return ActionScheduler_ActionClaim
511
+     * @throws RuntimeException When there is an error staking a claim.
512
+     * @throws InvalidArgumentException When the given group is not valid.
513
+     */
514
+    public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
515
+        $claim_id = $this->generate_claim_id();
516
+        $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
517
+        $action_ids = $this->find_actions_by_claim_id( $claim_id );
518
+
519
+        return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
520
+    }
521
+
522
+    /**
523
+     * @return int
524
+     */
525
+    public function get_claim_count(){
526
+        global $wpdb;
527
+
528
+        $sql = "SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')";
529
+        $sql = $wpdb->prepare( $sql, array( self::POST_TYPE ) );
530
+
531
+        return $wpdb->get_var( $sql );
532
+    }
533
+
534
+    protected function generate_claim_id() {
535
+        $claim_id = md5(microtime(true) . rand(0,1000));
536
+        return substr($claim_id, 0, 20); // to fit in db field with 20 char limit
537
+    }
538
+
539
+    /**
540
+     * @param string   $claim_id
541
+     * @param int      $limit
542
+     * @param DateTime $before_date Should use UTC timezone.
543
+     * @param array    $hooks       Claim only actions with a hook or hooks.
544
+     * @param string   $group       Claim only actions in the given group.
545
+     *
546
+     * @return int The number of actions that were claimed
547
+     * @throws RuntimeException When there is a database error.
548
+     * @throws InvalidArgumentException When the group is invalid.
549
+     */
550
+    protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) {
551
+        // Set up initial variables.
552
+        $date      = null === $before_date ? as_get_datetime_object() : clone $before_date;
553
+        $limit_ids = ! empty( $group );
554
+        $ids       = $limit_ids ? $this->get_actions_by_group( $group, $limit, $date ) : array();
555
+
556
+        // If limiting by IDs and no posts found, then return early since we have nothing to update.
557
+        if ( $limit_ids && 0 === count( $ids ) ) {
558
+            return 0;
559
+        }
560
+
561
+        /** @var wpdb $wpdb */
562
+        global $wpdb;
563
+
564
+        /*
565 565
 		 * Build up custom query to update the affected posts. Parameters are built as a separate array
566 566
 		 * to make it easier to identify where they are in the query.
567 567
 		 *
568 568
 		 * We can't use $wpdb->update() here because of the "ID IN ..." clause.
569 569
 		 */
570
-		$update = "UPDATE {$wpdb->posts} SET post_password = %s, post_modified_gmt = %s, post_modified = %s";
571
-		$params = array(
572
-			$claim_id,
573
-			current_time( 'mysql', true ),
574
-			current_time( 'mysql' ),
575
-		);
576
-
577
-		// Build initial WHERE clause.
578
-		$where    = "WHERE post_type = %s AND post_status = %s AND post_password = ''";
579
-		$params[] = self::POST_TYPE;
580
-		$params[] = ActionScheduler_Store::STATUS_PENDING;
581
-
582
-		if ( ! empty( $hooks ) ) {
583
-			$placeholders = array_fill( 0, count( $hooks ), '%s' );
584
-			$where       .= ' AND post_title IN (' . join( ', ', $placeholders ) . ')';
585
-			$params       = array_merge( $params, array_values( $hooks ) );
586
-		}
587
-
588
-		/*
570
+        $update = "UPDATE {$wpdb->posts} SET post_password = %s, post_modified_gmt = %s, post_modified = %s";
571
+        $params = array(
572
+            $claim_id,
573
+            current_time( 'mysql', true ),
574
+            current_time( 'mysql' ),
575
+        );
576
+
577
+        // Build initial WHERE clause.
578
+        $where    = "WHERE post_type = %s AND post_status = %s AND post_password = ''";
579
+        $params[] = self::POST_TYPE;
580
+        $params[] = ActionScheduler_Store::STATUS_PENDING;
581
+
582
+        if ( ! empty( $hooks ) ) {
583
+            $placeholders = array_fill( 0, count( $hooks ), '%s' );
584
+            $where       .= ' AND post_title IN (' . join( ', ', $placeholders ) . ')';
585
+            $params       = array_merge( $params, array_values( $hooks ) );
586
+        }
587
+
588
+        /*
589 589
 		 * Add the IDs to the WHERE clause. IDs not escaped because they came directly from a prior DB query.
590 590
 		 *
591 591
 		 * If we're not limiting by IDs, then include the post_date_gmt clause.
592 592
 		 */
593
-		if ( $limit_ids ) {
594
-			$where .= ' AND ID IN (' . join( ',', $ids ) . ')';
595
-		} else {
596
-			$where .= ' AND post_date_gmt <= %s';
597
-			$params[] = $date->format( 'Y-m-d H:i:s' );
598
-		}
599
-
600
-		// Add the ORDER BY clause and,ms limit.
601
-		$order    = 'ORDER BY menu_order ASC, post_date_gmt ASC, ID ASC LIMIT %d';
602
-		$params[] = $limit;
603
-
604
-		// Run the query and gather results.
605
-		$rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) );
606
-		if ( $rows_affected === false ) {
607
-			throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
608
-		}
609
-
610
-		return (int) $rows_affected;
611
-	}
612
-
613
-	/**
614
-	 * Get IDs of actions within a certain group and up to a certain date/time.
615
-	 *
616
-	 * @param string   $group The group to use in finding actions.
617
-	 * @param int      $limit The number of actions to retrieve.
618
-	 * @param DateTime $date  DateTime object representing cutoff time for actions. Actions retrieved will be
619
-	 *                        up to and including this DateTime.
620
-	 *
621
-	 * @return array IDs of actions in the appropriate group and before the appropriate time.
622
-	 * @throws InvalidArgumentException When the group does not exist.
623
-	 */
624
-	protected function get_actions_by_group( $group, $limit, DateTime $date ) {
625
-		// Ensure the group exists before continuing.
626
-		if ( ! term_exists( $group, self::GROUP_TAXONOMY )) {
627
-			throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
628
-		}
629
-
630
-		// Set up a query for post IDs to use later.
631
-		$query      = new WP_Query();
632
-		$query_args = array(
633
-			'fields'           => 'ids',
634
-			'post_type'        => self::POST_TYPE,
635
-			'post_status'      => ActionScheduler_Store::STATUS_PENDING,
636
-			'has_password'     => false,
637
-			'posts_per_page'   => $limit * 3,
638
-			'suppress_filters' => true,
639
-			'no_found_rows'    => true,
640
-			'orderby'          => array(
641
-				'menu_order' => 'ASC',
642
-				'date'       => 'ASC',
643
-				'ID'         => 'ASC',
644
-			),
645
-			'date_query'       => array(
646
-				'column' => 'post_date_gmt',
647
-				'before' => $date->format( 'Y-m-d H:i' ),
648
-				'inclusive' => true,
649
-			),
650
-			'tax_query' => array(
651
-				array(
652
-					'taxonomy'         => self::GROUP_TAXONOMY,
653
-					'field'            => 'slug',
654
-					'terms'            => $group,
655
-					'include_children' => false,
656
-				),
657
-			),
658
-		);
659
-
660
-		return $query->query( $query_args );
661
-	}
662
-
663
-	/**
664
-	 * @param string $claim_id
665
-	 * @return array
666
-	 */
667
-	public function find_actions_by_claim_id( $claim_id ) {
668
-		/** @var wpdb $wpdb */
669
-		global $wpdb;
670
-		$sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s";
671
-		$sql = $wpdb->prepare( $sql, array( self::POST_TYPE, $claim_id ) );
672
-		$action_ids = $wpdb->get_col( $sql );
673
-		return $action_ids;
674
-	}
675
-
676
-	public function release_claim( ActionScheduler_ActionClaim $claim ) {
677
-		$action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
678
-		if ( empty( $action_ids ) ) {
679
-			return; // nothing to do
680
-		}
681
-		$action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
682
-		/** @var wpdb $wpdb */
683
-		global $wpdb;
684
-		$sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s";
685
-		$sql = $wpdb->prepare( $sql, array( $claim->get_id() ) );
686
-		$result = $wpdb->query( $sql );
687
-		if ( $result === false ) {
688
-			/* translators: %s: claim ID */
689
-			throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) );
690
-		}
691
-	}
692
-
693
-	/**
694
-	 * @param string $action_id
695
-	 */
696
-	public function unclaim_action( $action_id ) {
697
-		/** @var wpdb $wpdb */
698
-		global $wpdb;
699
-		$sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s";
700
-		$sql = $wpdb->prepare( $sql, $action_id, self::POST_TYPE );
701
-		$result = $wpdb->query( $sql );
702
-		if ( $result === false ) {
703
-			/* translators: %s: action ID */
704
-			throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) );
705
-		}
706
-	}
707
-
708
-	public function mark_failure( $action_id ) {
709
-		/** @var wpdb $wpdb */
710
-		global $wpdb;
711
-		$sql = "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s";
712
-		$sql = $wpdb->prepare( $sql, self::STATUS_FAILED, $action_id, self::POST_TYPE );
713
-		$result = $wpdb->query( $sql );
714
-		if ( $result === false ) {
715
-			/* translators: %s: action ID */
716
-			throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) );
717
-		}
718
-	}
719
-
720
-	/**
721
-	 * Return an action's claim ID, as stored in the post password column
722
-	 *
723
-	 * @param string $action_id
724
-	 * @return mixed
725
-	 */
726
-	public function get_claim_id( $action_id ) {
727
-		return $this->get_post_column( $action_id, 'post_password' );
728
-	}
729
-
730
-	/**
731
-	 * Return an action's status, as stored in the post status column
732
-	 *
733
-	 * @param string $action_id
734
-	 * @return mixed
735
-	 */
736
-	public function get_status( $action_id ) {
737
-		$status = $this->get_post_column( $action_id, 'post_status' );
738
-
739
-		if ( $status === null ) {
740
-			throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
741
-		}
742
-
743
-		return $this->get_action_status_by_post_status( $status );
744
-	}
745
-
746
-	private function get_post_column( $action_id, $column_name ) {
747
-		/** @var \wpdb $wpdb */
748
-		global $wpdb;
749
-		return $wpdb->get_var( $wpdb->prepare( "SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", $action_id, self::POST_TYPE ) );
750
-	}
751
-
752
-	/**
753
-	 * @param string $action_id
754
-	 */
755
-	public function log_execution( $action_id ) {
756
-		/** @var wpdb $wpdb */
757
-		global $wpdb;
758
-
759
-		$sql = "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s";
760
-		$sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time('mysql', true), current_time('mysql'), $action_id, self::POST_TYPE );
761
-		$wpdb->query($sql);
762
-	}
763
-
764
-	/**
765
-	 * Record that an action was completed.
766
-	 *
767
-	 * @param int $action_id ID of the completed action.
768
-	 * @throws InvalidArgumentException|RuntimeException
769
-	 */
770
-	public function mark_complete( $action_id ) {
771
-		$post = get_post( $action_id );
772
-		if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
773
-			throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
774
-		}
775
-		add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
776
-		add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
777
-		$result = wp_update_post(array(
778
-			'ID' => $action_id,
779
-			'post_status' => 'publish',
780
-		), TRUE);
781
-		remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
782
-		remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
783
-		if ( is_wp_error( $result ) ) {
784
-			throw new RuntimeException( $result->get_error_message() );
785
-		}
786
-	}
787
-
788
-	/**
789
-	 * Mark action as migrated when there is an error deleting the action.
790
-	 *
791
-	 * @param int $action_id Action ID.
792
-	 */
793
-	public function mark_migrated( $action_id ) {
794
-		wp_update_post(
795
-			array(
796
-				'ID'          => $action_id,
797
-				'post_status' => 'migrated'
798
-			)
799
-		);
800
-	}
801
-
802
-	/**
803
-	 * Determine whether the post store can be migrated.
804
-	 *
805
-	 * @return bool
806
-	 */
807
-	public function migration_dependencies_met( $setting ) {
808
-		global $wpdb;
809
-
810
-		$dependencies_met = get_transient( self::DEPENDENCIES_MET );
811
-		if ( empty( $dependencies_met ) ) {
812
-			$maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 );
813
-			$found_action        = $wpdb->get_var(
814
-				$wpdb->prepare(
815
-					"SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1",
816
-					$maximum_args_length,
817
-					self::POST_TYPE
818
-				)
819
-			);
820
-			$dependencies_met = $found_action ? 'no' : 'yes';
821
-			set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS );
822
-		}
823
-
824
-		return 'yes' == $dependencies_met ? $setting : false;
825
-	}
826
-
827
-	/**
828
-	 * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
829
-	 *
830
-	 * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
831
-	 * as we prepare to move to custom tables, and can use an indexed VARCHAR column instead, we want to warn
832
-	 * developers of this impending requirement.
833
-	 *
834
-	 * @param ActionScheduler_Action $action
835
-	 */
836
-	protected function validate_action( ActionScheduler_Action $action ) {
837
-		try {
838
-			parent::validate_action( $action );
839
-		} catch ( Exception $e ) {
840
-			$message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() );
841
-			_doing_it_wrong( 'ActionScheduler_Action::$args', $message, '2.1.0' );
842
-		}
843
-	}
844
-
845
-	/**
846
-	 * @codeCoverageIgnore
847
-	 */
848
-	public function init() {
849
-		add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) );
850
-
851
-		$post_type_registrar = new ActionScheduler_wpPostStore_PostTypeRegistrar();
852
-		$post_type_registrar->register();
853
-
854
-		$post_status_registrar = new ActionScheduler_wpPostStore_PostStatusRegistrar();
855
-		$post_status_registrar->register();
856
-
857
-		$taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar();
858
-		$taxonomy_registrar->register();
859
-	}
593
+        if ( $limit_ids ) {
594
+            $where .= ' AND ID IN (' . join( ',', $ids ) . ')';
595
+        } else {
596
+            $where .= ' AND post_date_gmt <= %s';
597
+            $params[] = $date->format( 'Y-m-d H:i:s' );
598
+        }
599
+
600
+        // Add the ORDER BY clause and,ms limit.
601
+        $order    = 'ORDER BY menu_order ASC, post_date_gmt ASC, ID ASC LIMIT %d';
602
+        $params[] = $limit;
603
+
604
+        // Run the query and gather results.
605
+        $rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) );
606
+        if ( $rows_affected === false ) {
607
+            throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
608
+        }
609
+
610
+        return (int) $rows_affected;
611
+    }
612
+
613
+    /**
614
+     * Get IDs of actions within a certain group and up to a certain date/time.
615
+     *
616
+     * @param string   $group The group to use in finding actions.
617
+     * @param int      $limit The number of actions to retrieve.
618
+     * @param DateTime $date  DateTime object representing cutoff time for actions. Actions retrieved will be
619
+     *                        up to and including this DateTime.
620
+     *
621
+     * @return array IDs of actions in the appropriate group and before the appropriate time.
622
+     * @throws InvalidArgumentException When the group does not exist.
623
+     */
624
+    protected function get_actions_by_group( $group, $limit, DateTime $date ) {
625
+        // Ensure the group exists before continuing.
626
+        if ( ! term_exists( $group, self::GROUP_TAXONOMY )) {
627
+            throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
628
+        }
629
+
630
+        // Set up a query for post IDs to use later.
631
+        $query      = new WP_Query();
632
+        $query_args = array(
633
+            'fields'           => 'ids',
634
+            'post_type'        => self::POST_TYPE,
635
+            'post_status'      => ActionScheduler_Store::STATUS_PENDING,
636
+            'has_password'     => false,
637
+            'posts_per_page'   => $limit * 3,
638
+            'suppress_filters' => true,
639
+            'no_found_rows'    => true,
640
+            'orderby'          => array(
641
+                'menu_order' => 'ASC',
642
+                'date'       => 'ASC',
643
+                'ID'         => 'ASC',
644
+            ),
645
+            'date_query'       => array(
646
+                'column' => 'post_date_gmt',
647
+                'before' => $date->format( 'Y-m-d H:i' ),
648
+                'inclusive' => true,
649
+            ),
650
+            'tax_query' => array(
651
+                array(
652
+                    'taxonomy'         => self::GROUP_TAXONOMY,
653
+                    'field'            => 'slug',
654
+                    'terms'            => $group,
655
+                    'include_children' => false,
656
+                ),
657
+            ),
658
+        );
659
+
660
+        return $query->query( $query_args );
661
+    }
662
+
663
+    /**
664
+     * @param string $claim_id
665
+     * @return array
666
+     */
667
+    public function find_actions_by_claim_id( $claim_id ) {
668
+        /** @var wpdb $wpdb */
669
+        global $wpdb;
670
+        $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s";
671
+        $sql = $wpdb->prepare( $sql, array( self::POST_TYPE, $claim_id ) );
672
+        $action_ids = $wpdb->get_col( $sql );
673
+        return $action_ids;
674
+    }
675
+
676
+    public function release_claim( ActionScheduler_ActionClaim $claim ) {
677
+        $action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
678
+        if ( empty( $action_ids ) ) {
679
+            return; // nothing to do
680
+        }
681
+        $action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
682
+        /** @var wpdb $wpdb */
683
+        global $wpdb;
684
+        $sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s";
685
+        $sql = $wpdb->prepare( $sql, array( $claim->get_id() ) );
686
+        $result = $wpdb->query( $sql );
687
+        if ( $result === false ) {
688
+            /* translators: %s: claim ID */
689
+            throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) );
690
+        }
691
+    }
692
+
693
+    /**
694
+     * @param string $action_id
695
+     */
696
+    public function unclaim_action( $action_id ) {
697
+        /** @var wpdb $wpdb */
698
+        global $wpdb;
699
+        $sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s";
700
+        $sql = $wpdb->prepare( $sql, $action_id, self::POST_TYPE );
701
+        $result = $wpdb->query( $sql );
702
+        if ( $result === false ) {
703
+            /* translators: %s: action ID */
704
+            throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) );
705
+        }
706
+    }
707
+
708
+    public function mark_failure( $action_id ) {
709
+        /** @var wpdb $wpdb */
710
+        global $wpdb;
711
+        $sql = "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s";
712
+        $sql = $wpdb->prepare( $sql, self::STATUS_FAILED, $action_id, self::POST_TYPE );
713
+        $result = $wpdb->query( $sql );
714
+        if ( $result === false ) {
715
+            /* translators: %s: action ID */
716
+            throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) );
717
+        }
718
+    }
719
+
720
+    /**
721
+     * Return an action's claim ID, as stored in the post password column
722
+     *
723
+     * @param string $action_id
724
+     * @return mixed
725
+     */
726
+    public function get_claim_id( $action_id ) {
727
+        return $this->get_post_column( $action_id, 'post_password' );
728
+    }
729
+
730
+    /**
731
+     * Return an action's status, as stored in the post status column
732
+     *
733
+     * @param string $action_id
734
+     * @return mixed
735
+     */
736
+    public function get_status( $action_id ) {
737
+        $status = $this->get_post_column( $action_id, 'post_status' );
738
+
739
+        if ( $status === null ) {
740
+            throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
741
+        }
742
+
743
+        return $this->get_action_status_by_post_status( $status );
744
+    }
745
+
746
+    private function get_post_column( $action_id, $column_name ) {
747
+        /** @var \wpdb $wpdb */
748
+        global $wpdb;
749
+        return $wpdb->get_var( $wpdb->prepare( "SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", $action_id, self::POST_TYPE ) );
750
+    }
751
+
752
+    /**
753
+     * @param string $action_id
754
+     */
755
+    public function log_execution( $action_id ) {
756
+        /** @var wpdb $wpdb */
757
+        global $wpdb;
758
+
759
+        $sql = "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s";
760
+        $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time('mysql', true), current_time('mysql'), $action_id, self::POST_TYPE );
761
+        $wpdb->query($sql);
762
+    }
763
+
764
+    /**
765
+     * Record that an action was completed.
766
+     *
767
+     * @param int $action_id ID of the completed action.
768
+     * @throws InvalidArgumentException|RuntimeException
769
+     */
770
+    public function mark_complete( $action_id ) {
771
+        $post = get_post( $action_id );
772
+        if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
773
+            throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
774
+        }
775
+        add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
776
+        add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
777
+        $result = wp_update_post(array(
778
+            'ID' => $action_id,
779
+            'post_status' => 'publish',
780
+        ), TRUE);
781
+        remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
782
+        remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
783
+        if ( is_wp_error( $result ) ) {
784
+            throw new RuntimeException( $result->get_error_message() );
785
+        }
786
+    }
787
+
788
+    /**
789
+     * Mark action as migrated when there is an error deleting the action.
790
+     *
791
+     * @param int $action_id Action ID.
792
+     */
793
+    public function mark_migrated( $action_id ) {
794
+        wp_update_post(
795
+            array(
796
+                'ID'          => $action_id,
797
+                'post_status' => 'migrated'
798
+            )
799
+        );
800
+    }
801
+
802
+    /**
803
+     * Determine whether the post store can be migrated.
804
+     *
805
+     * @return bool
806
+     */
807
+    public function migration_dependencies_met( $setting ) {
808
+        global $wpdb;
809
+
810
+        $dependencies_met = get_transient( self::DEPENDENCIES_MET );
811
+        if ( empty( $dependencies_met ) ) {
812
+            $maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 );
813
+            $found_action        = $wpdb->get_var(
814
+                $wpdb->prepare(
815
+                    "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1",
816
+                    $maximum_args_length,
817
+                    self::POST_TYPE
818
+                )
819
+            );
820
+            $dependencies_met = $found_action ? 'no' : 'yes';
821
+            set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS );
822
+        }
823
+
824
+        return 'yes' == $dependencies_met ? $setting : false;
825
+    }
826
+
827
+    /**
828
+     * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
829
+     *
830
+     * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
831
+     * as we prepare to move to custom tables, and can use an indexed VARCHAR column instead, we want to warn
832
+     * developers of this impending requirement.
833
+     *
834
+     * @param ActionScheduler_Action $action
835
+     */
836
+    protected function validate_action( ActionScheduler_Action $action ) {
837
+        try {
838
+            parent::validate_action( $action );
839
+        } catch ( Exception $e ) {
840
+            $message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() );
841
+            _doing_it_wrong( 'ActionScheduler_Action::$args', $message, '2.1.0' );
842
+        }
843
+    }
844
+
845
+    /**
846
+     * @codeCoverageIgnore
847
+     */
848
+    public function init() {
849
+        add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) );
850
+
851
+        $post_type_registrar = new ActionScheduler_wpPostStore_PostTypeRegistrar();
852
+        $post_type_registrar->register();
853
+
854
+        $post_status_registrar = new ActionScheduler_wpPostStore_PostStatusRegistrar();
855
+        $post_status_registrar->register();
856
+
857
+        $taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar();
858
+        $taxonomy_registrar->register();
859
+    }
860 860
 }
Please login to merge, or discard this patch.
libraries/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php 1 patch
Indentation   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -8,28 +8,28 @@  discard block
 block discarded – undo
8 8
  * Creates a custom table for storing action logs
9 9
  */
10 10
 class ActionScheduler_LoggerSchema extends ActionScheduler_Abstract_Schema {
11
-	const LOG_TABLE = 'actionscheduler_logs';
11
+    const LOG_TABLE = 'actionscheduler_logs';
12 12
 
13
-	/**
14
-	 * @var int Increment this value to trigger a schema update.
15
-	 */
16
-	protected $schema_version = 2;
13
+    /**
14
+     * @var int Increment this value to trigger a schema update.
15
+     */
16
+    protected $schema_version = 2;
17 17
 
18
-	public function __construct() {
19
-		$this->tables = [
20
-			self::LOG_TABLE,
21
-		];
22
-	}
18
+    public function __construct() {
19
+        $this->tables = [
20
+            self::LOG_TABLE,
21
+        ];
22
+    }
23 23
 
24
-	protected function get_table_definition( $table ) {
25
-		global $wpdb;
26
-		$table_name       = $wpdb->$table;
27
-		$charset_collate  = $wpdb->get_charset_collate();
28
-		switch ( $table ) {
24
+    protected function get_table_definition( $table ) {
25
+        global $wpdb;
26
+        $table_name       = $wpdb->$table;
27
+        $charset_collate  = $wpdb->get_charset_collate();
28
+        switch ( $table ) {
29 29
 
30
-			case self::LOG_TABLE:
30
+            case self::LOG_TABLE:
31 31
 
32
-				return "CREATE TABLE {$table_name} (
32
+                return "CREATE TABLE {$table_name} (
33 33
 				        log_id bigint(20) unsigned NOT NULL auto_increment,
34 34
 				        action_id bigint(20) unsigned NOT NULL,
35 35
 				        message text NOT NULL,
@@ -40,8 +40,8 @@  discard block
 block discarded – undo
40 40
 				        KEY log_date_gmt (log_date_gmt)
41 41
 				        ) $charset_collate";
42 42
 
43
-			default:
44
-				return '';
45
-		}
46
-	}
43
+            default:
44
+                return '';
45
+        }
46
+    }
47 47
 }
Please login to merge, or discard this patch.