Completed
Push — update/grey-out-elements-unava... ( 06b29b...1fb283 )
by
unknown
16:50 queued 06:18
created

Jetpack_Sync_Full::enqueue_all_ids_as_action()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 4
eloc 18
c 2
b 0
f 1
nc 6
nop 5
dl 0
loc 35
rs 8.5806
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 $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
	private function enqueue_all_posts() {
166
		global $wpdb;
167
		$this->set_status( 'posts', 0 );
168
		$post_type_sql = Jetpack_Sync_Defaults::get_blacklisted_post_types_sql();
169
		$this->enqueue_all_ids_as_action( 'jetpack_full_sync_posts', $wpdb->posts, 'ID', $post_type_sql, 'posts' );
170
		$this->set_status( 'posts', 100 );
171
	}
172
173
	private function enqueue_all_ids_as_action( $action_name, $table_name, $id_field, $where_sql, $status_name ) {
174
		global $wpdb;
175
176
		if ( ! $where_sql ) {
177
			$where_sql = "1 = 1";
178
		}
179
180
		$total = $wpdb->get_var( "SELECT count(*) FROM {$table_name} WHERE {$where_sql}" );
181
		$items_per_page = 500;
182
		$page = 1;
183
		$counter = 1;
184
		$offset = ( $page * $items_per_page ) - $items_per_page;
185
186
		while( $ids = $wpdb->get_col( "SELECT {$id_field} FROM {$table_name} WHERE {$where_sql} ORDER BY {$id_field} asc LIMIT {$offset}, {$items_per_page}" ) ) {
187
			print_r($ids, true);
188
			// Request posts in groups of N for efficiency
189
			$chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE );
190
191
			// Send each chunk as an array of objects
192
			foreach ( $chunked_ids as $chunk ) {
193
				$this->set_status( $status_name, ( $counter / $total ) * 100 );
194
				/**
195
			 	 * Fires with a chunk of object IDs during full sync.
196
			 	 * These are expanded to full objects before upload
197
			 	 *
198
			 	 * @since 4.1
199
			 	 */
200
				do_action( $action_name, $chunk );
201
				$counter += count( $chunked_ids );
202
			}
203
204
			$page += 1;
205
			$offset = ( $page * $items_per_page ) - $items_per_page;
206
		}
207
	}
208
209
	public function expand_post_ids( $args ) {
210
		$post_ids = $args[0];
211
212
		$posts = array_map( array( 'WP_Post', 'get_instance' ), $post_ids );
213
		$posts = array_map( array( $this->get_client(), 'filter_post_content_and_add_links' ), $posts );
214
215
		return array(
216
			'posts'      => $posts,
217
			'post_metas' => $this->get_metadata( $post_ids, 'post' ),
218
			'terms'      => $this->get_term_relationships( $post_ids )
219
		);
220
	}
221
222
	private function enqueue_all_comments() {
223
		global $wpdb;
224
		$this->set_status( 'comments', 0 );
225
		$this->enqueue_all_ids_as_action( 'jetpack_full_sync_comments', $wpdb->comments, 'comment_ID', null, 'comments' );
226
		$this->set_status( 'comments', 100 );
227
	}
228
229
	public function expand_comment_ids( $args ) {
230
		$comment_ids = $args[0];
231
		$comments    = get_comments( array(
232
			'include_unapproved' => true,
233
			'comment__in'        => $comment_ids,
234
		) );
235
		$comments = array_map( array( $this->get_client(), 'filter_comment_and_add_hc_meta' ), $comments );
236
237
		return array(
238
			'comments'      => $comments,
239
			'comment_metas' => $this->get_metadata( $comment_ids, 'comment' ),
240
		);
241
	}
242
243
	public function expand_term_ids( $args ) {
244
		global $wp_version;
245
		$term_ids = $args[0];
246
		$taxonomy = $args[1];
247
		// version 4.5 or higher
248
		if ( version_compare( $wp_version, 4.5, '>=' ) ) {
249
			$terms = get_terms( array(
250
				'taxonomy'   => $taxonomy,
251
				'hide_empty' => false,
252
				'include'    => $term_ids
253
			) );
254
		} else {
255
			$terms = get_terms( $taxonomy, array(
256
				'hide_empty' => false,
257
				'include'    => $term_ids
258
			) );
259
		}
260
261
		return $terms;
262
	}
263
264
	public function expand_options( $args ) {
265
		if ( $args[0] ) {
266
			return $this->get_client()->get_all_options();
267
		}
268
269
		return $args;
270
	}
271
272
	private function enqueue_all_users() {
273
		global $wpdb;
274
		$this->set_status( 'users', 0 );
275
		$this->enqueue_all_ids_as_action( 'jetpack_full_sync_users', $wpdb->users, 'ID', null, 'users' );
276
		$this->set_status( 'users', 100 );
277
	}
278
279
	public function expand_users( $args ) {
280
		$user_ids = $args[0];
281
282
		return array_map( array( $this->get_client(), 'sanitize_user' ), get_users( array( 'include' => $user_ids ) ) );
283
	}
284
285
	public function expand_network_options( $args ) {
286
		if ( $args[0] ) {
287
			return $this->get_client()->get_all_network_options();
288
		}
289
290
		return $args;
291
	}
292
293
	private function get_metadata( $ids, $meta_type ) {
294
		global $wpdb;
295
		$table = _get_meta_table( $meta_type );
296
		$id    = $meta_type . '_id';
297
		if ( ! $table ) {
298
			return array();
299
		}
300
301
		return $wpdb->get_results( "SELECT $id, meta_key, meta_value, meta_id FROM $table WHERE $id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . " )", OBJECT );
302
	}
303
304
	private function get_term_relationships( $ids ) {
305
		global $wpdb;
306
307
		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 );
308
	}
309
310
	// 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...
311
	private function enqueue_all_theme_info() {
312
		$this->set_status( 'themes', 0 );
313
		$this->get_client()->send_theme_info();
314
		$this->set_status( 'themes', 100 );
315
	}
316
317
	private function enqueue_all_updates() {
318
		$this->set_status( 'updates', 0 );
319
		// check for updates
320
		wp_update_plugins();
321
		wp_update_themes();
322
		_maybe_update_core();
323
		$this->set_status( 'updates', 100 );
324
	}
325
326
	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...
327
		set_transient( self::$status_transient_name . '_' . $name,
328
			array(
329
				'progress' => $percent,
330
				// 'count' => $count, 
331
				// 'total' => $total 
332
			),
333
			self::$transient_timeout
334
		);
335
	}
336
337
	private function set_status_queuing_started() {
338
		set_transient( self::$status_transient_name, array( 'phase' => 'queuing started' ), self::$transient_timeout );
339
	}
340
341
	private function set_status_queuing_finished() {
342
		set_transient( self::$status_transient_name, array( 'phase' => 'queuing finished' ), self::$transient_timeout );
343
	}
344
345
	// these are called by the Sync Client when it sees that the full sync start/end actions have actually been transmitted
346
	public function set_status_sending_started() {
347
		/**
348
		 * Fires when the full_sync_start action is actually transmitted.
349
		 * This is useful for telling the user the status of full synchronization.
350
		 *
351
		 * @since 4.1
352
		 */
353
		do_action( 'jetpack_full_sync_start_sent' );
354
		set_transient( self::$status_transient_name, array( 'phase' => 'sending started' ), self::$transient_timeout );
355
	}
356
357
	public function set_status_sending_finished() {
358
		/**
359
		 * Fires when the full_sync_end action is actually transmitted.
360
		 * This is useful for telling the user the status of full synchronization.
361
		 *
362
		 * @since 4.1
363
		 */
364
		do_action( 'jetpack_full_sync_end_sent' );
365
		set_transient( self::$status_transient_name, array( 'phase' => 'sending finished' ), self::$transient_timeout );
366
	}
367
368
	public function get_status() {
369
		$status = get_transient( self::$status_transient_name );
370
		if ( ! is_array( $status ) ) {
371
			return array( 'phase' => 'not started' );
372
		}
373
374
		return $status;
375
	}
376
377
	public function get_module_status( $module ) {
378
		return get_transient( self::$status_transient_name . '_' . $module );
379
	}
380
381
	public function get_complete_status() {
382
		return array_merge(
383
			$this->get_status(),
384
			array_combine(
385
				Jetpack_Sync_Full::$modules,
386
				array_map( array( $this, 'get_module_status' ), Jetpack_Sync_Full::$modules )
387
			)
388
		);
389
	}
390
}
391