Completed
Push — fix/inline-docs-410 ( f96891...63b75c )
by
unknown
43:24 queued 33:40
created

Jetpack_Sync_Full::init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 13
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 17
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_Full {
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
	// singleton functions
36
	private static $instance;
37
	private $sender;
38
39
	public static function getInstance() {
40
		if ( null === self::$instance ) {
41
			self::$instance = new self();
42
		}
43
44
		return self::$instance;
45
	}
46
47
	protected function __construct() {
48
		$this->init();
49
	}
50
51
	function init() {
52
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_post_ids' ) );
53
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'expand_comment_ids' ) );
54
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_options', array( $this, 'expand_options' ) );
55
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_constants', array( $this, 'expand_constants' ) );
56
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_callables', array( $this, 'expand_callables' ) );
57
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
58
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_updates', array( $this, 'expand_updates' ) );
59
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_network_options', array(
60
			$this,
61
			'expand_network_options'
62
		) );
63
64
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_terms', array( $this, 'expand_term_ids' ) );
65
66
		add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
67
	}
68
69
	function start() {
70
		if( ! $this->should_start_full_sync() ) {
71
			return;
72
		}
73
		/**
74
		 * Fires when a full sync begins. This action is serialized
75
		 * and sent to the server so that it can clear the replica storage,
76
		 * and/or reset other data.
77
		 *
78
		 * @since 4.1
79
		 */
80
		do_action( 'jetpack_full_sync_start' );
81
		$this->set_status_queuing_started();
82
83
		$this->enqueue_all_constants();
84
		$this->enqueue_all_functions();
85
		$this->enqueue_all_options();
86
		if ( is_multisite() ) {
87
			$this->enqueue_all_network_options();
88
		}
89
		$this->enqueue_all_terms();
90
		$this->enqueue_all_theme_info();
91
		$this->enqueue_all_users();
92
		$this->enqueue_all_posts();
93
		$this->enqueue_all_comments();
94
		$this->enqueue_all_updates();
95
96
		$this->set_status_queuing_finished();
97
98
		$store = new Jetpack_Sync_WP_Replicastore();
99
		do_action( 'jetpack_full_sync_end', $store->checksum_all() );
100
	}
101
102
	private function should_start_full_sync() {
103
		$status = $this->get_status();
104
		// We should try sync if we haven't started it yet or if we have finished it.
105
		if( is_null( $status['started'] ) || is_integer( $status['finished'] ) ) {
106
			return true;
107
		}
108
		return false;
109
	}
110
111
	private function get_sender() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
112
		if ( ! $this->sender ) {
113
			$this->sender = Jetpack_Sync_Sender::getInstance();
114
		}
115
116
		return $this->sender;
117
	}
118
119
	private function enqueue_all_constants() {
120
		$total = Jetpack_Sync_Modules::get_module( "constants" )->full_sync();
121
		$this->update_queue_progress( 'constants', $total );
122
	}
123
124
	private function enqueue_all_functions() {
125
		$total = Jetpack_Sync_Modules::get_module( "callables" )->full_sync();
126
		$this->update_queue_progress( 'functions', $total );
127
	}
128
129
	private function enqueue_all_options() {
130
		$total = Jetpack_Sync_Modules::get_module( "options" )->full_sync();
131
		$this->update_queue_progress( 'options', $total );
132
	}
133
134
	private function enqueue_all_network_options() {
135
		$total = Jetpack_Sync_Modules::get_module( "options" )->full_sync_network();
136
		$this->update_queue_progress( 'network_options', $total );
137
	}
138
139
	private function enqueue_all_terms() {
140
		global $wpdb;
141
142
		$taxonomies = get_taxonomies();
143
		$total_chunks_counter = 0;
144
		foreach ( $taxonomies as $taxonomy ) {
145
			// I hope this is never bigger than RAM...
146
			$term_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s", $taxonomy ) ); // Should we set a limit here?
147
			// Request posts in groups of N for efficiency
148
			$chunked_term_ids = array_chunk( $term_ids, self::ARRAY_CHUNK_SIZE );
149
150
			// Send each chunk as an array of objects
151
			foreach ( $chunked_term_ids as $chunk ) {
152
				do_action( 'jetpack_full_sync_terms', $chunk, $taxonomy );
153
				$total_chunks_counter++;
154
			}
155
		}
156
157
		$this->update_queue_progress( 'terms', $total_chunks_counter );
158
159
	}
160
161
	private function enqueue_all_posts() {
162
		global $wpdb;
163
164
		$post_type_sql = Jetpack_Sync_Defaults::get_blacklisted_post_types_sql();
165
		$total = $this->enqueue_all_ids_as_action( 'jetpack_full_sync_posts', $wpdb->posts, 'ID', $post_type_sql );
166
		$this->update_queue_progress( 'posts', $total );
167
168
	}
169
170
	private function enqueue_all_ids_as_action( $action_name, $table_name, $id_field, $where_sql ) {
171
		global $wpdb;
172
173
		if ( ! $where_sql ) {
174
			$where_sql = "1 = 1";
175
		}
176
177
		$items_per_page = 500;
178
		$page = 1;
179
		$offset = ( $page * $items_per_page ) - $items_per_page;
180
		$chunk_count = 0;
181
		while( $ids = $wpdb->get_col( "SELECT {$id_field} FROM {$table_name} WHERE {$where_sql} ORDER BY {$id_field} asc LIMIT {$offset}, {$items_per_page}" ) ) {
182
			// Request posts in groups of N for efficiency
183
			$chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE );
184
185
			// Send each chunk as an array of objects
186
			foreach ( $chunked_ids as $chunk ) {
187
				/**
188
			 	 * Fires with a chunk of object IDs during full sync.
189
			 	 * These are expanded to full objects before upload
190
			 	 *
191
			 	 * @since 4.1
192
			 	 */
193
				do_action( $action_name, $chunk );
194
				$chunk_count++;
195
			}
196
197
			$page += 1;
198
			$offset = ( $page * $items_per_page ) - $items_per_page;
199
		}
200
		return $chunk_count;
201
	}
202
203
	public function expand_post_ids( $args ) {
204
		$post_ids = $args[0];
205
206
		$posts = array_map( array( 'WP_Post', 'get_instance' ), $post_ids );
207
		$posts = array_map( array( Jetpack_Sync_Modules::get_module( "posts" ), 'filter_post_content_and_add_links' ), $posts );
208
209
		return array(
210
			$posts,
211
			$this->get_metadata( $post_ids, 'post' ),
212
			$this->get_term_relationships( $post_ids )
213
		);
214
	}
215
216
	private function enqueue_all_comments() {
217
		global $wpdb;
218
219
		$total = $this->enqueue_all_ids_as_action( 'jetpack_full_sync_comments', $wpdb->comments, 'comment_ID', null );
220
		$this->update_queue_progress( 'comments', $total );
221
	}
222
223
	public function expand_comment_ids( $args ) {
224
		$comment_ids = $args[0];
225
		$comments    = get_comments( array(
226
			'include_unapproved' => true,
227
			'comment__in'        => $comment_ids,
228
		) );
229
230
		return array(
231
			$comments,
232
			$this->get_metadata( $comment_ids, 'comment' ),
233
		);
234
	}
235
236
	public function expand_term_ids( $args ) {
237
		global $wp_version;
238
		$term_ids = $args[0];
239
		$taxonomy = $args[1];
240
		// version 4.5 or higher
241
		if ( version_compare( $wp_version, 4.5, '>=' ) ) {
242
			$terms = get_terms( array(
243
				'taxonomy'   => $taxonomy,
244
				'hide_empty' => false,
245
				'include'    => $term_ids
246
			) );
247
		} else {
248
			$terms = get_terms( $taxonomy, array(
249
				'hide_empty' => false,
250
				'include'    => $term_ids
251
			) );
252
		}
253
254
		return $terms;
255
	}
256
257
	public function expand_options( $args ) {
258
		if ( $args[0] ) {
259
			return Jetpack_Sync_Modules::get_module( "options" )->get_all_options();
260
		}
261
262
		return $args;
263
	}
264
265
	public function expand_constants( $args ) {
266
		if ( $args[0] ) {
267
			return Jetpack_Sync_Modules::get_module( "constants" )->get_all_constants();
268
		}
269
		return $args;
270
	}
271
272
	public function expand_callables( $args ) {
273
		if ( $args[0] ) {
274
			return Jetpack_Sync_Modules::get_module( "callables" )->get_all_callables();
275
		}
276
		return $args;
277
	}
278
279
	private function enqueue_all_users() {
280
		global $wpdb;
281
		$total = $this->enqueue_all_ids_as_action( 'jetpack_full_sync_users', $wpdb->users, 'ID', null );
282
		$this->update_queue_progress( 'users', $total );
283
	}
284
285
	public function expand_users( $args ) {
286
		$user_ids = $args[0];
287
		return array_map( array( Jetpack_Sync_Modules::get_module( "users" ), 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
288
	}
289
290
	public function expand_network_options( $args ) {
291
		if ( $args[0] ) {
292
			return Jetpack_Sync_Modules::get_module( "options" )->get_all_network_options();
293
		}
294
295
		return $args;
296
	}
297
298
	private function get_metadata( $ids, $meta_type ) {
299
		global $wpdb;
300
		$table = _get_meta_table( $meta_type );
301
		$id    = $meta_type . '_id';
302
		if ( ! $table ) {
303
			return array();
304
		}
305
306
		return $wpdb->get_results( "SELECT $id, meta_key, meta_value, meta_id FROM $table WHERE $id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . " )", OBJECT );
307
	}
308
309
	private function get_term_relationships( $ids ) {
310
		global $wpdb;
311
312
		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 );
313
	}
314
315
	// 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...
316
	private function enqueue_all_theme_info() {
317
		$total = Jetpack_Sync_Modules::get_module( "themes" )->send_theme_info();
318
		$this->update_queue_progress( 'themes', $total );
319
	}
320
321
	private function enqueue_all_updates() {
322
323
		// check for updates
324
		$total = Jetpack_Sync_Modules::get_module( "updates" )->full_sync();
325
		$this->update_queue_progress( 'updates', $total );
326
	}
327
328
	public function expand_updates( $args ) {
329
		if ( $args[0] ) {
330
			return Jetpack_Sync_Modules::get_module( "updates" )->get_all_updates();
331
		}
332
333
		return $args;
334
	}
335
	
336
	function update_sent_progress_action( $actions_sent ) {
337
		$modules_count = array();
338
		$status = $this->get_status();
339
		if ( is_null( $status['started'] ) || $status['finished'] ) {
340
			return;
341
		}
342
343
		if ( in_array( 'jetpack_full_sync_start', $actions_sent ) ) {
344
			$this->set_status_sending_started();
345
			$status['sent_started'] = time();
346
		}
347
348
		foreach( $actions_sent as $action ) {
349
			$module_key = $this->action_to_modules( $action );
350
			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...
351
				$modules_count[ $module_key ] = isset( $modules_count[ $module_key ] ) ?  $modules_count[ $module_key ] + 1 : 1;
352
			}
353
354
		}
355
		foreach( $modules_count as $module => $count ) {
356
			$status[ 'sent' ][ $module ] = $this->update_sent_progress( $module, $count );
357
		}
358
359
		if ( in_array( 'jetpack_full_sync_end', $actions_sent ) ) {
360
			$this->set_status_sending_finished();
361
			$status['finished'] = time();
362
		}
363
		
364
		$this->update_status( $status );
365
	}
366
367
	function action_to_modules( $action ) {
368
		switch( $action ) {
369
			case 'jetpack_full_sync_constants':
370
				return 'constants';
371
				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...
372
373
			case 'jetpack_full_sync_callables':
374
				return 'functions';
375
				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...
376
377
			case 'jetpack_full_sync_options':
378
				return 'options';
379
				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...
380
381
			case 'jetpack_full_sync_network_options':
382
				return 'network_options';
383
				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...
384
385
			case 'jetpack_full_sync_terms':
386
				return 'terms';
387
				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...
388
389
			case 'jetpack_sync_current_theme_support':
390
				return 'themes';
391
				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...
392
393
			case 'jetpack_full_sync_users':
394
				return 'users';
395
				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...
396
397
			case 'jetpack_full_sync_posts':
398
				return 'posts';
399
				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...
400
401
			case 'jetpack_full_sync_comments':
402
				return 'comments';
403
				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...
404
405
			case 'jetpack_full_sync_updates':
406
				return 'updates';
407
				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...
408
409
		}
410
		return null;
411
	}
412
413
	private function set_status_queuing_started() {
414
		$status = $this->initial_status;
415
		$status[ 'started' ] = time();
416
		$this->update_status( $status );
417
	}
418
419
	private function set_status_queuing_finished() {
420
		$this->update_status( array( 'queue_finished' => time() ) );
421
	}
422
423
	// these are called by the Sync Client when it sees that the full sync start/end actions have actually been transmitted
424
	public function set_status_sending_started() {
425
		/**
426
		 * Fires when the full_sync_start action is actually transmitted.
427
		 * This is useful for telling the user the status of full synchronization.
428
		 *
429
		 * @since 4.1
430
		 */
431
432
		do_action( 'jetpack_full_sync_start_sent' );
433
434
	}
435
436
	public function set_status_sending_finished() {
437
		/**
438
		 * Fires when the full_sync_end action is actually transmitted.
439
		 * This is useful for telling the user the status of full synchronization.
440
		 *
441
		 * @since 4.1
442
		 */
443
		do_action( 'jetpack_full_sync_end_sent' );
444
	}
445
	
446
	private $initial_status = array(
447
		'started' => null,
448
		'queue_finished' => null,
449
		'sent_started' => null,
450
		'finished' => null,
451
		'sent' => array(),
452
		'queue' => array(),
453
	);
454
455
	public function get_status() {
456
		return get_option( self::$status_option, $this->initial_status );
457
	}
458
459
460
	public function update_status( $status ) {
461
		return update_option(
462
			self::$status_option,
463
			array_merge( $this->get_status(), $status )
464
		);
465
	}
466
467
	private function clear_status() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
468
		delete_option( self::$status_option );
469
	}
470
471
	public function update_queue_progress( $module, $data ) {
472
		$status = $this->get_status();
473
		if ( isset( $status['queue'][ $module ] ) )  {
474
			$status['queue'][ $module ] = $data + $status['queue'][ $module ];
475
		} else {
476
			$status['queue'][ $module ] = $data;
477
		}
478
479
		return $this->update_status( $status );
480
	}
481
482
	public function update_sent_progress( $module, $data ) {
483
		$status = $this->get_status();
484
		if ( isset( $status['sent'][ $module ] ) )  {
485
			return $data + $status['sent'][ $module ];
486
		} else {
487
			return $data;
488
		}
489
	}
490
	
491
}
492