Passed
Push — master ( 495efb...ace576 )
by Brian
06:14 queued 01:46
created

ActionScheduler_HybridStore::init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
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
	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
	 * All claim-related functions should operate solely
335
	 * on the primary store.
336
	 * * * * * * * * * * * * * * * * * * * * * * * * * * */
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
	}
380
}
381