Completed
Push — branch-4.2 ( 27b4f6...575b15 )
by
unknown
107:35 queued 98:38
created

Jetpack_Sync_Module_Full_Sync::get_sender()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 7
rs 9.4285
1
<?php
2
3
/**
4
 * This class does a full resync of the database by
5
 * enqueuing an outbound action for every single object
6
 * that we care about.
7
 *
8
 * This class contains a few non-obvious optimisations that should be explained:
9
 * - we fire an action called jetpack_full_sync_start so that WPCOM can erase the contents of the cached database
10
 * - for each object type, we obtain a full list of object IDs to sync via a single API call (hoping that since they're ints, they can all fit in RAM)
11
 * - we load the full objects for those IDs in chunks of Jetpack_Sync_Full::ARRAY_CHUNK_SIZE (to reduce the number of MySQL calls)
12
 * - we fire a trigger for the entire array which the Jetpack_Sync_Sender then serializes and queues.
13
 */
14
15
require_once 'class.jetpack-sync-wp-replicastore.php';
16
17
class Jetpack_Sync_Module_Full_Sync extends Jetpack_Sync_Module {
18
	const ARRAY_CHUNK_SIZE = 10;
19
	static $status_option = 'jetpack_full_sync_status';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $status_option.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
20
	static $transient_timeout = 3600; // an hour
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $transient_timeout.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
21
	static $modules = array(
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $modules.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
22
		'wp_version',
23
		'constants',
24
		'functions',
25
		'options',
26
		'posts',
27
		'comments',
28
		'themes',
29
		'updates',
30
		'users',
31
		'terms',
32
		'network_options',
33
	);
34
35
	private $sender;
36
37
	public function name() {
38
		return 'full-sync';
39
	}
40
41
	function init_listeners( $callable ) {
42
		// synthetic actions for full sync
43
		add_action( 'jetpack_full_sync_start', $callable );
44
		add_action( 'jetpack_full_sync_end', $callable );
45
	}
46
47
	function init_before_send() {
48
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_post_ids' ) );
49
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'expand_comment_ids' ) );
50
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_options', array( $this, 'expand_options' ) );
51
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_constants', array( $this, 'expand_constants' ) );
52
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_callables', array( $this, 'expand_callables' ) );
53
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
54
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_updates', array( $this, 'expand_updates' ) );
55
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_network_options', array(
56
			$this,
57
			'expand_network_options'
58
		) );
59
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_terms', array( $this, 'expand_term_ids' ) );
60
61
		// this is triggered after actions have been processed on the server
62
		add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
63
	}
64
65
	function start() {
66
		if( ! $this->should_start_full_sync() ) {
67
			return false;
68
		}
69
		/**
70
		 * Fires when a full sync begins. This action is serialized
71
		 * and sent to the server so that it can clear the replica storage,
72
		 * and/or reset other data.
73
		 *
74
		 * @since 4.2.0
75
		 */
76
		do_action( 'jetpack_full_sync_start' );
77
		$this->set_status_queuing_started();
78
79
		$this->enqueue_all_constants();
80
		$this->enqueue_all_functions();
81
		$this->enqueue_all_options();
82
		if ( is_multisite() ) {
83
			$this->enqueue_all_network_options();
84
		}
85
		$this->enqueue_all_terms();
86
		$this->enqueue_all_theme_info();
87
		$this->enqueue_all_users();
88
		$this->enqueue_all_posts();
89
		$this->enqueue_all_comments();
90
		$this->enqueue_all_updates();
91
92
		$this->set_status_queuing_finished();
93
94
		$store = new Jetpack_Sync_WP_Replicastore();
95
		do_action( 'jetpack_full_sync_end', $store->checksum_all() );
96
		return true;
97
	}
98
99
	private function should_start_full_sync() {
100
		$status = $this->get_status();
101
		// We should try sync if we haven't started it yet or if we have finished it.
102
		if( is_null( $status['started'] ) || is_integer( $status['finished'] ) ) {
103
			return true;
104
		}
105
		return false;
106
	}
107
108
	private function get_sender() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
109
		if ( ! $this->sender ) {
110
			$this->sender = Jetpack_Sync_Sender::getInstance();
111
		}
112
113
		return $this->sender;
114
	}
115
116
	private function enqueue_all_constants() {
117
		$total = Jetpack_Sync_Modules::get_module( "constants" )->full_sync();
118
		$this->update_queue_progress( 'constants', $total );
119
	}
120
121
	private function enqueue_all_functions() {
122
		$total = Jetpack_Sync_Modules::get_module( "callables" )->full_sync();
123
		$this->update_queue_progress( 'functions', $total );
124
	}
125
126
	private function enqueue_all_options() {
127
		$total = Jetpack_Sync_Modules::get_module( "options" )->full_sync();
128
		$this->update_queue_progress( 'options', $total );
129
	}
130
131
	private function enqueue_all_network_options() {
132
		$total = Jetpack_Sync_Modules::get_module( "options" )->full_sync_network();
133
		$this->update_queue_progress( 'network_options', $total );
134
	}
135
136
	private function enqueue_all_terms() {
137
		global $wpdb;
138
139
		$taxonomies = get_taxonomies();
140
		$total_chunks_counter = 0;
141
		foreach ( $taxonomies as $taxonomy ) {
142
			// I hope this is never bigger than RAM...
143
			$term_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s", $taxonomy ) ); // Should we set a limit here?
144
			// Request posts in groups of N for efficiency
145
			$chunked_term_ids = array_chunk( $term_ids, self::ARRAY_CHUNK_SIZE );
146
147
			// Send each chunk as an array of objects
148
			foreach ( $chunked_term_ids as $chunk ) {
149
				do_action( 'jetpack_full_sync_terms', $chunk, $taxonomy );
150
				$total_chunks_counter++;
151
			}
152
		}
153
154
		$this->update_queue_progress( 'terms', $total_chunks_counter );
155
156
	}
157
158
	private function enqueue_all_posts() {
159
		global $wpdb;
160
161
		$post_type_sql = Jetpack_Sync_Defaults::get_blacklisted_post_types_sql();
162
		$total = $this->enqueue_all_ids_as_action( 'jetpack_full_sync_posts', $wpdb->posts, 'ID', $post_type_sql );
163
		$this->update_queue_progress( 'posts', $total );
164
165
	}
166
167
	private function enqueue_all_ids_as_action( $action_name, $table_name, $id_field, $where_sql ) {
168
		global $wpdb;
169
170
		if ( ! $where_sql ) {
171
			$where_sql = "1 = 1";
172
		}
173
174
		$items_per_page = 500;
175
		$page = 1;
176
		$offset = ( $page * $items_per_page ) - $items_per_page;
177
		$chunk_count = 0;
178
		while( $ids = $wpdb->get_col( "SELECT {$id_field} FROM {$table_name} WHERE {$where_sql} ORDER BY {$id_field} asc LIMIT {$offset}, {$items_per_page}" ) ) {
179
			// Request posts in groups of N for efficiency
180
			$chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE );
181
182
			// Send each chunk as an array of objects
183
			foreach ( $chunked_ids as $chunk ) {
184
				/**
185
			 	 * Fires with a chunk of object IDs during full sync.
186
			 	 * These are expanded to full objects before upload
187
			 	 *
188
			 	 * @since 4.2.0
189
			 	 */
190
				do_action( $action_name, $chunk );
191
				$chunk_count++;
192
			}
193
194
			$page += 1;
195
			$offset = ( $page * $items_per_page ) - $items_per_page;
196
		}
197
		return $chunk_count;
198
	}
199
200
	public function expand_post_ids( $args ) {
201
		$post_ids = $args[0];
202
203
		$posts = array_map( array( 'WP_Post', 'get_instance' ), $post_ids );
204
		$posts = array_map( array( Jetpack_Sync_Modules::get_module( "posts" ), 'filter_post_content_and_add_links' ), $posts );
205
206
		return array(
207
			$posts,
208
			$this->get_metadata( $post_ids, 'post' ),
209
			$this->get_term_relationships( $post_ids )
210
		);
211
	}
212
213
	private function enqueue_all_comments() {
214
		global $wpdb;
215
216
		$total = $this->enqueue_all_ids_as_action( 'jetpack_full_sync_comments', $wpdb->comments, 'comment_ID', null );
217
		$this->update_queue_progress( 'comments', $total );
218
	}
219
220
	public function expand_comment_ids( $args ) {
221
		$comment_ids = $args[0];
222
		$comments    = get_comments( array(
223
			'include_unapproved' => true,
224
			'comment__in'        => $comment_ids,
225
		) );
226
227
		return array(
228
			$comments,
229
			$this->get_metadata( $comment_ids, 'comment' ),
230
		);
231
	}
232
233
	public function expand_term_ids( $args ) {
234
		global $wp_version;
235
		$term_ids = $args[0];
236
		$taxonomy = $args[1];
237
		// version 4.5 or higher
238
		if ( version_compare( $wp_version, 4.5, '>=' ) ) {
239
			$terms = get_terms( array(
240
				'taxonomy'   => $taxonomy,
241
				'hide_empty' => false,
242
				'include'    => $term_ids
243
			) );
244
		} else {
245
			$terms = get_terms( $taxonomy, array(
246
				'hide_empty' => false,
247
				'include'    => $term_ids
248
			) );
249
		}
250
251
		return $terms;
252
	}
253
254
	public function expand_options( $args ) {
255
		if ( $args[0] ) {
256
			return Jetpack_Sync_Modules::get_module( "options" )->get_all_options();
257
		}
258
259
		return $args;
260
	}
261
262
	public function expand_constants( $args ) {
263
		if ( $args[0] ) {
264
			return Jetpack_Sync_Modules::get_module( "constants" )->get_all_constants();
265
		}
266
		return $args;
267
	}
268
269
	public function expand_callables( $args ) {
270
		if ( $args[0] ) {
271
			return Jetpack_Sync_Modules::get_module( "callables" )->get_all_callables();
272
		}
273
		return $args;
274
	}
275
276
	private function enqueue_all_users() {
277
		global $wpdb;
278
		$total = $this->enqueue_all_ids_as_action( 'jetpack_full_sync_users', $wpdb->users, 'ID', null );
279
		$this->update_queue_progress( 'users', $total );
280
	}
281
282
	public function expand_users( $args ) {
283
		$user_ids = $args[0];
284
		return array_map( array( Jetpack_Sync_Modules::get_module( "users" ), 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
285
	}
286
287
	public function expand_network_options( $args ) {
288
		if ( $args[0] ) {
289
			return Jetpack_Sync_Modules::get_module( "options" )->get_all_network_options();
290
		}
291
292
		return $args;
293
	}
294
295
	private function get_metadata( $ids, $meta_type ) {
296
		global $wpdb;
297
		$table = _get_meta_table( $meta_type );
298
		$id    = $meta_type . '_id';
299
		if ( ! $table ) {
300
			return array();
301
		}
302
303
		return $wpdb->get_results( "SELECT $id, meta_key, meta_value, meta_id FROM $table WHERE $id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . " )", OBJECT );
304
	}
305
306
	private function get_term_relationships( $ids ) {
307
		global $wpdb;
308
309
		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 );
310
	}
311
312
	// TODO:
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
313
	private function enqueue_all_theme_info() {
314
		$total = Jetpack_Sync_Modules::get_module( "themes" )->send_theme_info();
315
		$this->update_queue_progress( 'themes', $total );
316
	}
317
318
	private function enqueue_all_updates() {
319
320
		// check for updates
321
		$total = Jetpack_Sync_Modules::get_module( "updates" )->full_sync();
322
		$this->update_queue_progress( 'updates', $total );
323
	}
324
325
	public function expand_updates( $args ) {
326
		if ( $args[0] ) {
327
			return Jetpack_Sync_Modules::get_module( "updates" )->get_all_updates();
328
		}
329
330
		return $args;
331
	}
332
333
	function update_sent_progress_action( $actions_sent ) {
334
		$modules_count = array();
335
		$status = $this->get_status();
336
		if ( is_null( $status['started'] ) || $status['finished'] ) {
337
			return;
338
		}
339
340
		if ( in_array( 'jetpack_full_sync_start', $actions_sent ) ) {
341
			$this->set_status_sending_started();
342
			$status['sent_started'] = time();
343
		}
344
345
		foreach( $actions_sent as $action ) {
346
			$module_key = $this->action_to_modules( $action );
347
			if ( $module_key ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $module_key of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
348
				$modules_count[ $module_key ] = isset( $modules_count[ $module_key ] ) ?  $modules_count[ $module_key ] + 1 : 1;
349
			}
350
351
		}
352
		foreach( $modules_count as $module => $count ) {
353
			$status[ 'sent' ][ $module ] = $this->update_sent_progress( $module, $count );
354
		}
355
356
		if ( in_array( 'jetpack_full_sync_end', $actions_sent ) ) {
357
			$this->set_status_sending_finished();
358
			$status['finished'] = time();
359
		}
360
361
		$this->update_status( $status );
362
	}
363
364
	function action_to_modules( $action ) {
365
		switch( $action ) {
366
			case 'jetpack_full_sync_constants':
367
				return 'constants';
368
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
369
370
			case 'jetpack_full_sync_callables':
371
				return 'functions';
372
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
373
374
			case 'jetpack_full_sync_options':
375
				return 'options';
376
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
377
378
			case 'jetpack_full_sync_network_options':
379
				return 'network_options';
380
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
381
382
			case 'jetpack_full_sync_terms':
383
				return 'terms';
384
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
385
386
			case 'jetpack_sync_current_theme_support':
387
				return 'themes';
388
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
389
390
			case 'jetpack_full_sync_users':
391
				return 'users';
392
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
393
394
			case 'jetpack_full_sync_posts':
395
				return 'posts';
396
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
397
398
			case 'jetpack_full_sync_comments':
399
				return 'comments';
400
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
401
402
			case 'jetpack_full_sync_updates':
403
				return 'updates';
404
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
405
406
		}
407
		return null;
408
	}
409
410
	private function set_status_queuing_started() {
411
		$status = $this->initial_status;
412
		$status[ 'started' ] = time();
413
		$this->update_status( $status );
414
	}
415
416
	private function set_status_queuing_finished() {
417
		$this->update_status( array( 'queue_finished' => time() ) );
418
	}
419
420
	// these are called by the Sync Client when it sees that the full sync start/end actions have actually been transmitted
421
	public function set_status_sending_started() {
422
		/**
423
		 * Fires when the full_sync_start action is actually transmitted.
424
		 * This is useful for telling the user the status of full synchronization.
425
		 *
426
		 * @since 4.2.0
427
		 */
428
429
		do_action( 'jetpack_full_sync_start_sent' );
430
431
	}
432
433
	public function set_status_sending_finished() {
434
		/**
435
		 * Fires when the full_sync_end action is actually transmitted.
436
		 * This is useful for telling the user the status of full synchronization.
437
		 *
438
		 * @since 4.2.0
439
		 */
440
		do_action( 'jetpack_full_sync_end_sent' );
441
	}
442
443
	private $initial_status = array(
444
		'started' => null,
445
		'queue_finished' => null,
446
		'sent_started' => null,
447
		'finished' => null,
448
		'sent' => array(),
449
		'queue' => array(),
450
	);
451
452
	public function get_status() {
453
		return get_option( self::$status_option, $this->initial_status );
454
	}
455
456
457
	public function update_status( $status ) {
458
		return update_option(
459
			self::$status_option,
460
			array_merge( $this->get_status(), $status )
461
		);
462
	}
463
464
	private function clear_status() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
465
		delete_option( self::$status_option );
466
	}
467
468
	public function update_queue_progress( $module, $data ) {
469
		$status = $this->get_status();
470
		if ( isset( $status['queue'][ $module ] ) )  {
471
			$status['queue'][ $module ] = $data + $status['queue'][ $module ];
472
		} else {
473
			$status['queue'][ $module ] = $data;
474
		}
475
476
		return $this->update_status( $status );
477
	}
478
479
	public function update_sent_progress( $module, $data ) {
480
		$status = $this->get_status();
481
		if ( isset( $status['sent'][ $module ] ) )  {
482
			return $data + $status['sent'][ $module ];
483
		} else {
484
			return $data;
485
		}
486
	}
487
488
}
489