Completed
Push — branch-4.2 ( 1c7f7d...820ca9 )
by Jeremy
14:43 queued 04:22
created

Jetpack_Sync_Full::update_sent_progress_action()   D

Complexity

Conditions 9
Paths 33

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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