Completed
Push — add/sso-analytics ( 683a91...e67d7d )
by
unknown
162:06 queued 152:34
created

Jetpack_Sync_Full   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 383
Duplicated Lines 10.97 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 42
loc 383
rs 8.3673
wmc 45
lcom 2
cbo 3

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstance() 0 7 2
A __construct() 0 3 1
A init() 0 11 1
B start() 0 33 2
A get_client() 0 7 2
A enqueue_wp_version() 0 6 1
A enqueue_all_constants() 0 5 1
A enqueue_all_functions() 0 5 1
A enqueue_all_options() 0 5 1
A enqueue_all_network_options() 0 5 1
B enqueue_all_terms() 0 28 3
A enqueue_all_posts() 23 23 2
A expand_post_ids() 0 12 1
A enqueue_all_comments() 19 19 2
A expand_comment_ids() 0 12 1
A expand_term_ids() 0 20 2
A expand_options() 0 7 2
A enqueue_all_users() 0 23 2
A expand_users() 0 5 1
A expand_network_options() 0 7 2
A get_metadata() 0 10 2
A get_term_relationships() 0 5 1
A enqueue_all_theme_info() 0 5 1
A enqueue_all_updates() 0 8 1
A set_status() 0 10 1
A set_status_queuing_started() 0 3 1
A set_status_queuing_finished() 0 3 1
A set_status_sending_started() 0 10 1
A set_status_sending_finished() 0 10 1
A get_status() 0 8 2
A get_module_status() 0 3 1
A get_complete_status() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Sync_Full often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Sync_Full, and based on these observations, apply Extract Interface, too.

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
	static $array_chunk_size = 5;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $array_chunk_size.

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...
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 $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_users', array( $this, 'expand_users' ) );
56
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_network_options', array(
57
			$this,
58
			'expand_network_options'
59
		) );
60
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_terms', array( $this, 'expand_term_ids' ) );
61
	}
62
63
	function start() {
64
		/**
65
		 * Fires when a full sync begins. This action is serialized
66
		 * and sent to the server so that it can clear the replica storage,
67
		 * and/or reset other data.
68
		 *
69
		 * @since 4.1
70
		 */
71
		do_action( 'jetpack_full_sync_start' );
72
73
		$this->set_status_queuing_started();
74
75
		$this->enqueue_wp_version();
76
		$this->enqueue_all_constants();
77
		$this->enqueue_all_functions();
78
		$this->enqueue_all_options();
79
80
		if ( is_multisite() ) {
81
			$this->enqueue_all_network_options();
82
		}
83
84
		$this->enqueue_all_terms();
85
		$this->enqueue_all_theme_info();
86
		$this->enqueue_all_users();
87
		$this->enqueue_all_posts();
88
		$this->enqueue_all_comments();
89
		$this->enqueue_all_updates();
90
91
		$this->set_status_queuing_finished();
92
93
		$store = new Jetpack_Sync_WP_Replicastore();
94
		do_action( 'jetpack_full_sync_end', $store->checksum_all() );
95
	}
96
97
	private function get_client() {
98
		if ( ! $this->client ) {
99
			$this->client = Jetpack_Sync_Client::getInstance();
100
		}
101
102
		return $this->client;
103
	}
104
105
	private function enqueue_wp_version() {
106
		$this->set_status( 'wp_version', 0 );
107
		global $wp_version;
108
		do_action( 'jetpack_sync_wp_version', $wp_version );
109
		$this->set_status( 'wp_version', 100 );
110
	}
111
112
	private function enqueue_all_constants() {
113
		$this->set_status( 'constants', 0 );
114
		$this->get_client()->force_sync_constants();
115
		$this->set_status( 'constants', 100 );
116
	}
117
118
	private function enqueue_all_functions() {
119
		$this->set_status( 'functions', 0 );
120
		$this->get_client()->force_sync_callables();
121
		$this->set_status( 'functions', 100 );
122
	}
123
124
	private function enqueue_all_options() {
125
		$this->set_status( 'options', 0 );
126
		$this->get_client()->force_sync_options();
127
		$this->set_status( 'options', 100 );
128
	}
129
130
	private function enqueue_all_network_options() {
131
		$this->set_status( 'network_options', 0 );
132
		$this->get_client()->force_sync_network_options();
133
		$this->set_status( 'network_options', 100 );
134
	}
135
136
	private function enqueue_all_terms() {
137
		$this->set_status( 'terms', 0 );
138
		global $wpdb;
139
140
		$taxonomies = get_taxonomies();
141
142
		$taxonomy_counter = 0;
143
		$total_count      = count( $taxonomies );
144
145
		foreach ( $taxonomies as $taxonomy ) {
146
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
			$total_chunks  = count( $chunked_term_ids );
153
			$chunk_counter = 0;
154
			// Send each chunk as an array of objects
155
			foreach ( $chunked_term_ids as $chunk ) {
156
				$this->set_status( 'terms', ( ( $taxonomy_counter / $total_count ) + ( ( $chunk_counter / $total_chunks ) / $total_count ) ) * 100 );
157
				do_action( 'jetpack_full_sync_terms', $chunk, $taxonomy );
158
				$chunk_counter ++;
159
			}
160
			$taxonomy_counter ++;
161
		}
162
		$this->set_status( 'terms', 100 );
163
	}
164
165 View Code Duplication
	private function enqueue_all_posts() {
166
		$this->set_status( 'posts', 0 );
167
		global $wpdb;
168
169
		// I hope this is never bigger than RAM...
170
		$post_type_sql = Jetpack_Sync_Defaults::get_blacklisted_post_types_sql();
171
		$post_ids      = $wpdb->get_col( "SELECT id FROM $wpdb->posts WHERE $post_type_sql" ); // Should we set a limit here?
172
173
		// Request posts in groups of N for efficiency
174
		$chunked_post_ids = array_chunk( $post_ids, self::$array_chunk_size );
175
176
		$counter = 0;
177
		$total   = count( $chunked_post_ids );
178
179
		// Send each chunk as an array of objects
180
		foreach ( $chunked_post_ids as $chunk ) {
181
			$this->set_status( 'posts', ( $counter / $total ) * 100 );
182
			do_action( 'jetpack_full_sync_posts', $chunk );
183
			$counter += 1;
184
		}
185
186
		$this->set_status( 'posts', 100 );
187
	}
188
189
	public function expand_post_ids( $args ) {
190
		$post_ids = $args[0];
191
192
		$posts = array_map( array( 'WP_Post', 'get_instance' ), $post_ids );
193
		$posts = array_map( array( $this->get_client(), 'filter_post_content_and_add_links' ), $posts );
194
195
		return array(
196
			'posts'      => $posts,
197
			'post_metas' => $this->get_metadata( $post_ids, 'post' ),
198
			'terms'      => $this->get_term_relationships( $post_ids )
199
		);
200
	}
201
202 View Code Duplication
	private function enqueue_all_comments() {
203
		$this->set_status( 'comments', 0 );
204
205
		global $wpdb;
206
207
		$comment_ids         = $wpdb->get_col( "SELECT comment_id FROM $wpdb->comments" ); // Should we set a limit here?
208
		$chunked_comment_ids = array_chunk( $comment_ids, self::$array_chunk_size );
209
210
		$counter = 0;
211
		$total   = count( $chunked_comment_ids );
212
213
		foreach ( $chunked_comment_ids as $chunk ) {
214
			$this->set_status( 'comments', ( $counter / $total ) * 100 );
215
			do_action( 'jetpack_full_sync_comments', $chunk );
216
			$counter += 1;
217
		}
218
219
		$this->set_status( 'comments', 100 );
220
	}
221
222
	public function expand_comment_ids( $args ) {
223
		$comment_ids = $args[0];
224
		$comments    = get_comments( array(
225
			'include_unapproved' => true,
226
			'comment__in'        => $comment_ids,
227
		) );
228
229
		return array(
230
			'comments'      => $comments,
231
			'comment_metas' => $this->get_metadata( $comment_ids, 'comment' ),
232
		);
233
	}
234
235
	public function expand_term_ids( $args ) {
236
		global $wp_version;
237
		$term_ids = $args[0];
238
		$taxonomy = $args[1];
239
		// version 4.5 or higher
240
		if ( version_compare( $wp_version, 4.5, '>=' ) ) {
241
			$terms = get_terms( array(
242
				'taxonomy'   => $taxonomy,
243
				'hide_empty' => false,
244
				'include'    => $term_ids
245
			) );
246
		} else {
247
			$terms = get_terms( $taxonomy, array(
248
				'hide_empty' => false,
249
				'include'    => $term_ids
250
			) );
251
		}
252
253
		return $terms;
254
	}
255
256
	public function expand_options( $args ) {
257
		if ( $args[0] ) {
258
			return $this->get_client()->get_all_options();
259
		}
260
261
		return $args;
262
	}
263
264
	private function enqueue_all_users() {
265
		$this->set_status( 'users', 0 );
266
267
		$user_ids          = get_users( array( 'fields' => 'ID' ) );
268
		$chunked_users_ids = array_chunk( $user_ids, self::$array_chunk_size );
269
270
		$counter = 0;
271
		$total   = count( $chunked_users_ids );
272
273
		foreach ( $chunked_users_ids as $chunk ) {
274
			$this->set_status( 'users', ( $counter / $total ) * 100 );
275
			/**
276
			 * Fires with a chunk of user IDs during full sync.
277
			 * These get expanded to full user objects before upload (minus passwords)
278
			 *
279
			 * @since 4.1
280
			 */
281
			do_action( 'jetpack_full_sync_users', $chunk );
282
			$counter += 1;
283
		}
284
285
		$this->set_status( 'users', 100 );
286
	}
287
288
	public function expand_users( $args ) {
289
		$user_ids = $args[0];
290
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 * 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 * 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
		$this->set_status( 'themes', 0 );
322
		$this->get_client()->send_theme_info();
323
		$this->set_status( 'themes', 100 );
324
	}
325
326
	private function enqueue_all_updates() {
327
		$this->set_status( 'updates', 0 );
328
		// check for updates
329
		wp_update_plugins();
330
		wp_update_themes();
331
		_maybe_update_core();
332
		$this->set_status( 'updates', 100 );
333
	}
334
335
	private function set_status( $name, $percent, $count = 1, $total = 1 ) {
0 ignored issues
show
Unused Code introduced by
The parameter $count is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $total is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
336
		set_transient( self::$status_transient_name . '_' . $name,
337
			array(
338
				'progress' => $percent,
339
				// 'count' => $count, 
340
				// 'total' => $total 
341
			),
342
			self::$transient_timeout
343
		);
344
	}
345
346
	private function set_status_queuing_started() {
347
		set_transient( self::$status_transient_name, array( 'phase' => 'queuing started' ), self::$transient_timeout );
348
	}
349
350
	private function set_status_queuing_finished() {
351
		set_transient( self::$status_transient_name, array( 'phase' => 'queuing finished' ), self::$transient_timeout );
352
	}
353
354
	// these are called by the Sync Client when it sees that the full sync start/end actions have actually been transmitted
355
	public function set_status_sending_started() {
356
		/**
357
		 * Fires when the full_sync_start action is actually transmitted.
358
		 * This is useful for telling the user the status of full synchronization.
359
		 *
360
		 * @since 4.1
361
		 */
362
		do_action( 'jetpack_full_sync_start_sent' );
363
		set_transient( self::$status_transient_name, array( 'phase' => 'sending started' ), self::$transient_timeout );
364
	}
365
366
	public function set_status_sending_finished() {
367
		/**
368
		 * Fires when the full_sync_end action is actually transmitted.
369
		 * This is useful for telling the user the status of full synchronization.
370
		 *
371
		 * @since 4.1
372
		 */
373
		do_action( 'jetpack_full_sync_end_sent' );
374
		set_transient( self::$status_transient_name, array( 'phase' => 'sending finished' ), self::$transient_timeout );
375
	}
376
377
	public function get_status() {
378
		$status = get_transient( self::$status_transient_name );
379
		if ( ! is_array( $status ) ) {
380
			return array( 'phase' => 'not started' );
381
		}
382
383
		return $status;
384
	}
385
386
	public function get_module_status( $module ) {
387
		return get_transient( self::$status_transient_name . '_' . $module );
388
	}
389
390
	public function get_complete_status() {
391
		return array_merge(
392
			$this->get_status(),
393
			array_combine(
394
				Jetpack_Sync_Full::$modules,
395
				array_map( array( $this, 'get_module_status' ), Jetpack_Sync_Full::$modules )
396
			)
397
		);
398
	}
399
}
400