Completed
Push — add/api/custom-taxonomies ( 892668...9a5833 )
by
unknown
10:04
created

Jetpack_Sync_Full::expand_constants()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
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 1
dl 0
loc 6
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_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_transient_name = 'jetpack_full_sync_progress';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $status_transient_name.

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