Completed
Push — add/amp-ads-support ( 39a9c8...65ae21 )
by
unknown
15:32 queued 08:17
created

Module   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 360
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 360
rs 9.84
c 0
b 0
f 0
wmc 32
lcom 0
cbo 1

21 Methods

Rating   Name   Duplication   Size   Complexity  
name() 0 1 ?
A get_object_by_id() 0 3 1
A init_listeners() 0 2 1
A init_full_sync_listeners() 0 2 1
A init_before_send() 0 2 1
A set_defaults() 0 2 1
A reset_data() 0 2 1
A enqueue_full_sync_actions() 0 4 1
A estimate_full_sync_actions() 0 4 1
A get_full_sync_actions() 0 3 1
A count_actions() 0 3 1
A get_check_sum() 0 3 1
A still_valid_checksum() 0 7 3
B enqueue_all_ids_as_action() 0 41 5
A get_chunks_with_preceding_end() 0 12 2
A get_metadata() 0 21 2
A init_listeners_for_meta_type() 0 5 1
A init_meta_whitelist_handler() 0 5 1
A get_term_relationships() 0 6 1
A unserialize_meta() 0 4 1
A get_objects_by_id() 0 17 5
1
<?php
2
/**
3
 * A base abstraction of a sync module.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Modules;
9
10
use Automattic\Jetpack\Sync\Listener;
11
12
/**
13
 * Basic methods implemented by Jetpack Sync extensions.
14
 *
15
 * @abstract
16
 */
17
abstract class Module {
18
	/**
19
	 * Number of items per chunk when grouping objects for performance reasons.
20
	 *
21
	 * @access public
22
	 *
23
	 * @var int
24
	 */
25
	const ARRAY_CHUNK_SIZE = 10;
26
27
	/**
28
	 * Sync module name.
29
	 *
30
	 * @access public
31
	 *
32
	 * @return string
33
	 */
34
	abstract public function name();
35
36
	// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
37
38
	/**
39
	 * Retrieve a sync object by its ID.
40
	 *
41
	 * @access public
42
	 *
43
	 * @param string $object_type Type of the sync object.
44
	 * @param int    $id          ID of the sync object.
45
	 * @return mixed Object, or false if the object is invalid.
46
	 */
47
	public function get_object_by_id( $object_type, $id ) {
48
		return false;
49
	}
50
51
	/**
52
	 * Initialize callables action listeners.
53
	 * Override these to set up listeners and set/reset data/defaults.
54
	 *
55
	 * @access public
56
	 *
57
	 * @param callable $callable Action handler callable.
58
	 */
59
	public function init_listeners( $callable ) {
60
	}
61
62
	/**
63
	 * Initialize module action listeners for full sync.
64
	 *
65
	 * @access public
66
	 *
67
	 * @param callable $callable Action handler callable.
68
	 */
69
	public function init_full_sync_listeners( $callable ) {
70
	}
71
72
	/**
73
	 * Initialize the module in the sender.
74
	 *
75
	 * @access public
76
	 */
77
	public function init_before_send() {
78
	}
79
80
	/**
81
	 * Set module defaults.
82
	 *
83
	 * @access public
84
	 */
85
	public function set_defaults() {
86
	}
87
88
	/**
89
	 * Perform module cleanup.
90
	 * Usually triggered when uninstalling the plugin.
91
	 *
92
	 * @access public
93
	 */
94
	public function reset_data() {
95
	}
96
97
	/**
98
	 * Enqueue the module actions for full sync.
99
	 *
100
	 * @access public
101
	 *
102
	 * @param array   $config               Full sync configuration for this sync module.
103
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
104
	 * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
105
	 * @return array Number of actions enqueued, and next module state.
106
	 */
107
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
108
		// In subclasses, return the number of actions enqueued, and next module state (true == done).
109
		return array( null, true );
110
	}
111
112
	/**
113
	 * Retrieve an estimated number of actions that will be enqueued.
114
	 *
115
	 * @access public
116
	 *
117
	 * @param array $config Full sync configuration for this sync module.
118
	 * @return array Number of items yet to be enqueued.
119
	 */
120
	public function estimate_full_sync_actions( $config ) {
121
		// In subclasses, return the number of items yet to be enqueued.
122
		return null;
123
	}
124
125
	// phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
126
127
	/**
128
	 * Retrieve the actions that will be sent for this module during a full sync.
129
	 *
130
	 * @access public
131
	 *
132
	 * @return array Full sync actions of this module.
133
	 */
134
	public function get_full_sync_actions() {
135
		return array();
136
	}
137
138
	/**
139
	 * Get the number of actions that we care about.
140
	 *
141
	 * @access protected
142
	 *
143
	 * @param array $action_names     Action names we're interested in.
144
	 * @param array $actions_to_count Unfiltered list of actions we want to count.
145
	 * @return array Number of actions that we're interested in.
146
	 */
147
	protected function count_actions( $action_names, $actions_to_count ) {
148
		return count( array_intersect( $action_names, $actions_to_count ) );
149
	}
150
151
	/**
152
	 * Calculate the checksum of one or more values.
153
	 *
154
	 * @access protected
155
	 *
156
	 * @param mixed $values Values to calculate checksum for.
157
	 * @return int The checksum.
158
	 */
159
	protected function get_check_sum( $values ) {
160
		return crc32( wp_json_encode( jetpack_json_wrap( $values ) ) );
161
	}
162
163
	/**
164
	 * Whether a particular checksum in a set of checksums is valid.
165
	 *
166
	 * @access protected
167
	 *
168
	 * @param array  $sums_to_check Array of checksums.
169
	 * @param string $name          Name of the checksum.
170
	 * @param int    $new_sum       Checksum to compare against.
171
	 * @return boolean Whether the checksum is valid.
172
	 */
173
	protected function still_valid_checksum( $sums_to_check, $name, $new_sum ) {
174
		if ( isset( $sums_to_check[ $name ] ) && $sums_to_check[ $name ] === $new_sum ) {
175
			return true;
176
		}
177
178
		return false;
179
	}
180
181
	/**
182
	 * Enqueue all items of a sync type as an action.
183
	 *
184
	 * @access protected
185
	 *
186
	 * @param string  $action_name          Name of the action.
187
	 * @param string  $table_name           Name of the database table.
188
	 * @param string  $id_field             Name of the ID field in the database.
189
	 * @param string  $where_sql            The SQL WHERE clause to filter to the desired items.
190
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue in the same time.
191
	 * @param boolean $state                Whether enqueueing has finished.
192
	 * @return array Array, containing the number of chunks and TRUE, indicating enqueueing has finished.
193
	 */
194
	protected function enqueue_all_ids_as_action( $action_name, $table_name, $id_field, $where_sql, $max_items_to_enqueue, $state ) {
195
		global $wpdb;
196
197
		if ( ! $where_sql ) {
198
			$where_sql = '1 = 1';
199
		}
200
201
		$items_per_page        = 1000;
202
		$page                  = 1;
203
		$chunk_count           = 0;
204
		$previous_interval_end = $state ? $state : '~0';
205
		$listener              = Listener::get_instance();
206
207
		// Count down from max_id to min_id so we get newest posts/comments/etc first.
208
		// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
209
		while ( $ids = $wpdb->get_col( "SELECT {$id_field} FROM {$table_name} WHERE {$where_sql} AND {$id_field} < {$previous_interval_end} ORDER BY {$id_field} DESC LIMIT {$items_per_page}" ) ) {
210
			// Request posts in groups of N for efficiency.
211
			$chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE );
212
213
			// If we hit our row limit, process and return.
214
			if ( $chunk_count + count( $chunked_ids ) >= $max_items_to_enqueue ) {
215
				$remaining_items_count                      = $max_items_to_enqueue - $chunk_count;
216
				$remaining_items                            = array_slice( $chunked_ids, 0, $remaining_items_count );
217
				$remaining_items_with_previous_interval_end = $this->get_chunks_with_preceding_end( $remaining_items, $previous_interval_end );
218
				$listener->bulk_enqueue_full_sync_actions( $action_name, $remaining_items_with_previous_interval_end );
219
220
				$last_chunk = end( $remaining_items );
221
				return array( $remaining_items_count + $chunk_count, end( $last_chunk ) );
222
			}
223
			$chunked_ids_with_previous_end = $this->get_chunks_with_preceding_end( $chunked_ids, $previous_interval_end );
224
225
			$listener->bulk_enqueue_full_sync_actions( $action_name, $chunked_ids_with_previous_end );
226
227
			$chunk_count += count( $chunked_ids );
228
			$page++;
229
			// The $ids are ordered in descending order.
230
			$previous_interval_end = end( $ids );
231
		}
232
233
		return array( $chunk_count, true );
234
	}
235
236
	/**
237
	 * Retrieve chunk IDs with previous interval end.
238
	 *
239
	 * @access private
240
	 *
241
	 * @param array $chunks                All remaining items.
242
	 * @param int   $previous_interval_end The last item from the previous interval.
243
	 * @return array Chunk IDs with the previous interval end.
244
	 */
245
	private function get_chunks_with_preceding_end( $chunks, $previous_interval_end ) {
246
		$chunks_with_ends = array();
247
		foreach ( $chunks as $chunk ) {
248
			$chunks_with_ends[] = array(
249
				'ids'          => $chunk,
250
				'previous_end' => $previous_interval_end,
251
			);
252
			// Chunks are ordered in descending order.
253
			$previous_interval_end = end( $chunk );
254
		}
255
		return $chunks_with_ends;
256
	}
257
258
	/**
259
	 * Get metadata of a particular object type within the designated meta key whitelist.
260
	 *
261
	 * @access protected
262
	 *
263
	 * @todo Refactor to use $wpdb->prepare() on the SQL query.
264
	 *
265
	 * @param array  $ids                Object IDs.
266
	 * @param string $meta_type          Meta type.
267
	 * @param array  $meta_key_whitelist Meta key whitelist.
268
	 * @return array Unserialized meta values.
269
	 */
270
	protected function get_metadata( $ids, $meta_type, $meta_key_whitelist ) {
271
		global $wpdb;
272
		$table = _get_meta_table( $meta_type );
273
		$id    = $meta_type . '_id';
274
		if ( ! $table ) {
275
			return array();
276
		}
277
278
		$private_meta_whitelist_sql = "'" . implode( "','", array_map( 'esc_sql', $meta_key_whitelist ) ) . "'";
279
280
		return array_map(
281
			array( $this, 'unserialize_meta' ),
282
			$wpdb->get_results(
283
				// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
284
				"SELECT $id, meta_key, meta_value, meta_id FROM $table WHERE $id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . ' )' .
285
				" AND meta_key IN ( $private_meta_whitelist_sql ) ",
286
				// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
287
				OBJECT
288
			)
289
		);
290
	}
291
292
	/**
293
	 * Initialize listeners for the particular meta type.
294
	 *
295
	 * @access public
296
	 *
297
	 * @param string   $meta_type Meta type.
298
	 * @param callable $callable  Action handler callable.
299
	 */
300
	public function init_listeners_for_meta_type( $meta_type, $callable ) {
301
		add_action( "added_{$meta_type}_meta", $callable, 10, 4 );
302
		add_action( "updated_{$meta_type}_meta", $callable, 10, 4 );
303
		add_action( "deleted_{$meta_type}_meta", $callable, 10, 4 );
304
	}
305
306
	/**
307
	 * Initialize meta whitelist handler for the particular meta type.
308
	 *
309
	 * @access public
310
	 *
311
	 * @param string   $meta_type         Meta type.
312
	 * @param callable $whitelist_handler Action handler callable.
313
	 */
314
	public function init_meta_whitelist_handler( $meta_type, $whitelist_handler ) {
315
		add_filter( "jetpack_sync_before_enqueue_added_{$meta_type}_meta", $whitelist_handler );
316
		add_filter( "jetpack_sync_before_enqueue_updated_{$meta_type}_meta", $whitelist_handler );
317
		add_filter( "jetpack_sync_before_enqueue_deleted_{$meta_type}_meta", $whitelist_handler );
318
	}
319
320
	/**
321
	 * Retrieve the term relationships for the specified object IDs.
322
	 *
323
	 * @access protected
324
	 *
325
	 * @todo This feels too specific to be in the abstract sync Module class. Move it?
326
	 *
327
	 * @param array $ids Object IDs.
328
	 * @return array Term relationships - object ID and term taxonomy ID pairs.
329
	 */
330
	protected function get_term_relationships( $ids ) {
331
		global $wpdb;
332
333
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
334
		return $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . ' )', OBJECT );
335
	}
336
337
	/**
338
	 * Unserialize the value of a meta object, if necessary.
339
	 *
340
	 * @access public
341
	 *
342
	 * @param object $meta Meta object.
343
	 * @return object Meta object with possibly unserialized value.
344
	 */
345
	public function unserialize_meta( $meta ) {
346
		$meta->meta_value = maybe_unserialize( $meta->meta_value );
347
		return $meta;
348
	}
349
350
	/**
351
	 * Retrieve a set of objects by their IDs.
352
	 *
353
	 * @access public
354
	 *
355
	 * @param string $object_type Object type.
356
	 * @param array  $ids         Object IDs.
357
	 * @return array Array of objects.
358
	 */
359
	public function get_objects_by_id( $object_type, $ids ) {
360
		if ( empty( $ids ) || empty( $object_type ) ) {
361
			return array();
362
		}
363
364
		$objects = array();
365
		foreach ( (array) $ids as $id ) {
366
			$object = $this->get_object_by_id( $object_type, $id );
367
368
			// Only add object if we have the object.
369
			if ( $object ) {
370
				$objects[ $id ] = $object;
371
			}
372
		}
373
374
		return $objects;
375
	}
376
}
377