PluginProbe
WANotifier for Forms and Actions / 3.2.0
WANotifier for Forms and Actions v3.2.0
3.2.0 3.1.1 3.1.0 3.0.4 2.7.10 2.7.11 2.7.12 2.7.13 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 3.0.0 3.0.1 3.0.2 3.0.3 trunk 0.1.0 0.1.1 1.0.0 1.0.1 All 68 releases
notifier / libraries / action-scheduler / classes / data-stores / ActionScheduler_HybridStore.php

ActionScheduler_HybridStore.php in WANotifier for Forms and Actions 3.2.0, at libraries/action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php

473 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use ActionScheduler_Store as Store;
4 use Action_Scheduler\Migration\Runner;
5 use Action_Scheduler\Migration\Config;
6 use Action_Scheduler\Migration\Controller;
7
8 /**
9 * Class ActionScheduler_HybridStore
10 *
11 * A wrapper around multiple stores that fetches data from both.
12 *
13 * @since 3.0.0
14 */
15 class ActionScheduler_HybridStore extends Store {
16 const DEMARKATION_OPTION = 'action_scheduler_hybrid_store_demarkation';
17
18 /**
19 * Primary store instance.
20 *
21 * @var ActionScheduler_Store
22 */
23 private $primary_store;
24
25 /**
26 * Secondary store instance.
27 *
28 * @var ActionScheduler_Store
29 */
30 private $secondary_store;
31
32 /**
33 * Runner instance.
34 *
35 * @var Action_Scheduler\Migration\Runner
36 */
37 private $migration_runner;
38
39 /**
40 * The dividing line between IDs of actions created
41 * by the primary and secondary stores.
42 *
43 * @var int
44 *
45 * Methods that accept an action ID will compare the ID against
46 * this to determine which store will contain that ID. In almost
47 * all cases, the ID should come from the primary store, but if
48 * client code is bypassing the API functions and fetching IDs
49 * from elsewhere, then there is a chance that an unmigrated ID
50 * might be requested.
51 */
52 private $demarkation_id = 0;
53
54 /**
55 * ActionScheduler_HybridStore constructor.
56 *
57 * @param Config|null $config Migration config object.
58 */
59 public function __construct( ?Config $config = null ) {
60 $this->demarkation_id = (int) get_option( self::DEMARKATION_OPTION, 0 );
61 if ( empty( $config ) ) {
62 $config = Controller::instance()->get_migration_config_object();
63 }
64 $this->primary_store = $config->get_destination_store();
65 $this->secondary_store = $config->get_source_store();
66 $this->migration_runner = new Runner( $config );
67 }
68
69 /**
70 * Initialize the table data store tables.
71 *
72 * @codeCoverageIgnore
73 */
74 public function init() {
75 add_action( 'action_scheduler/created_table', array( $this, 'set_autoincrement' ), 10, 2 );
76 $this->primary_store->init();
77 $this->secondary_store->init();
78 remove_action( 'action_scheduler/created_table', array( $this, 'set_autoincrement' ), 10 );
79 }
80
81 /**
82 * Flush all store caches.
83 */
84 public function flush_caches() {
85 $this->primary_store->flush_caches();
86 $this->secondary_store->flush_caches();
87 }
88
89 /**
90 * When the actions table is created, set its autoincrement
91 * value to be one higher than the posts table to ensure that
92 * there are no ID collisions.
93 *
94 * @param string $table_name Table name.
95 * @param string $table_suffix Suffix of table name.
96 *
97 * @return void
98 * @codeCoverageIgnore
99 */
100 public function set_autoincrement( $table_name, $table_suffix ) {
101 if ( ActionScheduler_StoreSchema::ACTIONS_TABLE === $table_suffix ) {
102 if ( empty( $this->demarkation_id ) ) {
103 $this->demarkation_id = $this->set_demarkation_id();
104 }
105
106 /**
107 * Global.
108 *
109 * @var \wpdb $wpdb
110 */
111 global $wpdb;
112
113 /**
114 * A default date of '0000-00-00 00:00:00' is invalid in MySQL 5.7 when configured with
115 * sql_mode including both STRICT_TRANS_TABLES and NO_ZERO_DATE.
116 */
117 $default_date = new DateTime( 'tomorrow' );
118 $null_action = new ActionScheduler_NullAction();
119 $date_gmt = $this->get_scheduled_date_string( $null_action, $default_date );
120 $date_local = $this->get_scheduled_date_string_local( $null_action, $default_date );
121
122 $row_count = $wpdb->insert(
123 $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
124 array(
125 'action_id' => $this->demarkation_id,
126 'hook' => '',
127 'status' => '',
128 'scheduled_date_gmt' => $date_gmt,
129 'scheduled_date_local' => $date_local,
130 'last_attempt_gmt' => $date_gmt,
131 'last_attempt_local' => $date_local,
132 )
133 );
134 if ( $row_count > 0 ) {
135 $wpdb->delete(
136 $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
137 array( 'action_id' => $this->demarkation_id )
138 );
139 }
140 }
141 }
142
143 /**
144 * Store the demarkation id in WP options.
145 *
146 * @param int $id The ID to set as the demarkation point between the two stores
147 * Leave null to use the next ID from the WP posts table.
148 *
149 * @return int The new ID.
150 *
151 * @codeCoverageIgnore
152 */
153 private function set_demarkation_id( $id = null ) {
154 if ( empty( $id ) ) {
155 /**
156 * Global.
157 *
158 * @var \wpdb $wpdb
159 */
160 global $wpdb;
161
162 $id = (int) $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" );
163 $id++;
164 }
165 update_option( self::DEMARKATION_OPTION, $id );
166
167 return $id;
168 }
169
170 /**
171 * Find the first matching action from the secondary store.
172 * If it exists, migrate it to the primary store immediately.
173 * After it migrates, the secondary store will logically contain
174 * the next matching action, so return the result thence.
175 *
176 * @param string $hook Action's hook.
177 * @param array $params Action's arguments.
178 *
179 * @return string
180 */
181 public function find_action( $hook, $params = array() ) {
182 $found_unmigrated_action = $this->secondary_store->find_action( $hook, $params );
183 if ( ! empty( $found_unmigrated_action ) ) {
184 $this->migrate( array( $found_unmigrated_action ) );
185 }
186
187 return $this->primary_store->find_action( $hook, $params );
188 }
189
190 /**
191 * Find actions matching the query in the secondary source first.
192 * If any are found, migrate them immediately. Then the secondary
193 * store will contain the canonical results.
194 *
195 * @param array $query Query arguments.
196 * @param string $query_type Whether to select or count the results. Default, select.
197 *
198 * @return int[]
199 */
200 public function query_actions( $query = array(), $query_type = 'select' ) {
201 $found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' );
202 if ( ! empty( $found_unmigrated_actions ) ) {
203 $this->migrate( $found_unmigrated_actions );
204 }
205
206 return $this->primary_store->query_actions( $query, $query_type );
207 }
208
209 /**
210 * Get a count of all actions in the store, grouped by status
211 *
212 * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status.
213 */
214 public function action_counts() {
215 $unmigrated_actions_count = $this->secondary_store->action_counts();
216 $migrated_actions_count = $this->primary_store->action_counts();
217 $actions_count_by_status = array();
218
219 foreach ( $this->get_status_labels() as $status_key => $status_label ) {
220
221 $count = 0;
222
223 if ( isset( $unmigrated_actions_count[ $status_key ] ) ) {
224 $count += $unmigrated_actions_count[ $status_key ];
225 }
226
227 if ( isset( $migrated_actions_count[ $status_key ] ) ) {
228 $count += $migrated_actions_count[ $status_key ];
229 }
230
231 $actions_count_by_status[ $status_key ] = $count;
232 }
233
234 $actions_count_by_status = array_filter( $actions_count_by_status );
235
236 return $actions_count_by_status;
237 }
238
239 /**
240 * If any actions would have been claimed by the secondary store,
241 * migrate them immediately, then ask the primary store for the
242 * canonical claim.
243 *
244 * @param int $max_actions Maximum number of actions to claim.
245 * @param null|DateTime $before_date Latest timestamp of actions to claim.
246 * @param string[] $hooks Hook of actions to claim.
247 * @param string $group Group of actions to claim.
248 *
249 * @return ActionScheduler_ActionClaim
250 */
251 public function stake_claim( $max_actions = 10, ?DateTime $before_date = null, $hooks = array(), $group = '' ) {
252 $claim = $this->secondary_store->stake_claim( $max_actions, $before_date, $hooks, $group );
253
254 $claimed_actions = $claim->get_actions();
255 if ( ! empty( $claimed_actions ) ) {
256 $this->migrate( $claimed_actions );
257 }
258
259 $this->secondary_store->release_claim( $claim );
260
261 return $this->primary_store->stake_claim( $max_actions, $before_date, $hooks, $group );
262 }
263
264 /**
265 * Migrate a list of actions to the table data store.
266 *
267 * @param array $action_ids List of action IDs.
268 */
269 private function migrate( $action_ids ) {
270 $this->migration_runner->migrate_actions( $action_ids );
271 }
272
273 /**
274 * Save an action to the primary store.
275 *
276 * @param ActionScheduler_Action $action Action object to be saved.
277 * @param DateTime|null $date Optional. Schedule date. Default null.
278 *
279 * @return int The action ID
280 */
281 public function save_action( ActionScheduler_Action $action, ?DateTime $date = null ) {
282 return $this->primary_store->save_action( $action, $date );
283 }
284
285 /**
286 * Retrieve an existing action whether migrated or not.
287 *
288 * @param int $action_id Action ID.
289 */
290 public function fetch_action( $action_id ) {
291 $store = $this->get_store_from_action_id( $action_id, true );
292 if ( $store ) {
293 return $store->fetch_action( $action_id );
294 } else {
295 return new ActionScheduler_NullAction();
296 }
297 }
298
299 /**
300 * Cancel an existing action whether migrated or not.
301 *
302 * @param int $action_id Action ID.
303 */
304 public function cancel_action( $action_id ) {
305 $store = $this->get_store_from_action_id( $action_id );
306 if ( $store ) {
307 $store->cancel_action( $action_id );
308 }
309 }
310
311 /**
312 * Delete an existing action whether migrated or not.
313 *
314 * @param int $action_id Action ID.
315 */
316 public function delete_action( $action_id ) {
317 $store = $this->get_store_from_action_id( $action_id );
318 if ( $store ) {
319 $store->delete_action( $action_id );
320 }
321 }
322
323 /**
324 * Get the schedule date an existing action whether migrated or not.
325 *
326 * @param int $action_id Action ID.
327 */
328 public function get_date( $action_id ) {
329 $store = $this->get_store_from_action_id( $action_id );
330 if ( $store ) {
331 return $store->get_date( $action_id );
332 } else {
333 return null;
334 }
335 }
336
337 /**
338 * Mark an existing action as failed whether migrated or not.
339 *
340 * @param int $action_id Action ID.
341 */
342 public function mark_failure( $action_id ) {
343 $store = $this->get_store_from_action_id( $action_id );
344 if ( $store ) {
345 $store->mark_failure( $action_id );
346 }
347 }
348
349 /**
350 * Log the execution of an existing action whether migrated or not.
351 *
352 * @param int $action_id Action ID.
353 */
354 public function log_execution( $action_id ) {
355 $store = $this->get_store_from_action_id( $action_id );
356 if ( $store ) {
357 $store->log_execution( $action_id );
358 }
359 }
360
361 /**
362 * Mark an existing action complete whether migrated or not.
363 *
364 * @param int $action_id Action ID.
365 */
366 public function mark_complete( $action_id ) {
367 $store = $this->get_store_from_action_id( $action_id );
368 if ( $store ) {
369 $store->mark_complete( $action_id );
370 }
371 }
372
373 /**
374 * Get an existing action status whether migrated or not.
375 *
376 * @param int $action_id Action ID.
377 */
378 public function get_status( $action_id ) {
379 $store = $this->get_store_from_action_id( $action_id );
380 if ( $store ) {
381 return $store->get_status( $action_id );
382 }
383 return null;
384 }
385
386 /**
387 * Return which store an action is stored in.
388 *
389 * @param int $action_id ID of the action.
390 * @param bool $primary_first Optional flag indicating search the primary store first.
391 * @return ActionScheduler_Store
392 */
393 protected function get_store_from_action_id( $action_id, $primary_first = false ) {
394 if ( $primary_first ) {
395 $stores = array(
396 $this->primary_store,
397 $this->secondary_store,
398 );
399 } elseif ( $action_id < $this->demarkation_id ) {
400 $stores = array(
401 $this->secondary_store,
402 $this->primary_store,
403 );
404 } else {
405 $stores = array(
406 $this->primary_store,
407 );
408 }
409
410 foreach ( $stores as $store ) {
411 try {
412 // Probe by fetching the action status: if entry is corrupted, the object construction is not feasible.
413 if ( null !== $store->get_status( $action_id ) ) {
414 return $store;
415 }
416 } catch ( \InvalidArgumentException |\RuntimeException $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
417 // A missing or corrupted/empty status just means this store can't resolve the action; keep probing.
418 }
419 }
420 return null;
421 }
422
423 /**
424 * * * * * * * * * * * * * * * * * * * * * * * * * * *
425 * All claim-related functions should operate solely
426 * on the primary store.
427 * * * * * * * * * * * * * * * * * * * * * * * * * * *
428 */
429
430 /**
431 * Get the claim count from the table data store.
432 */
433 public function get_claim_count() {
434 return $this->primary_store->get_claim_count();
435 }
436
437 /**
438 * Retrieve the claim ID for an action from the table data store.
439 *
440 * @param int $action_id Action ID.
441 */
442 public function get_claim_id( $action_id ) {
443 return $this->primary_store->get_claim_id( $action_id );
444 }
445
446 /**
447 * Release a claim in the table data store on any pending actions.
448 *
449 * @param ActionScheduler_ActionClaim $claim Claim object.
450 */
451 public function release_claim( ActionScheduler_ActionClaim $claim ) {
452 $this->primary_store->release_claim( $claim );
453 }
454
455 /**
456 * Release claims on an action in the table data store.
457 *
458 * @param int $action_id Action ID.
459 */
460 public function unclaim_action( $action_id ) {
461 $this->primary_store->unclaim_action( $action_id );
462 }
463
464 /**
465 * Retrieve a list of action IDs by claim.
466 *
467 * @param int $claim_id Claim ID.
468 */
469 public function find_actions_by_claim_id( $claim_id ) {
470 return $this->primary_store->find_actions_by_claim_id( $claim_id );
471 }
472 }
473