Completed
Push — try/lower-full-sync-mem-use ( 42a9da )
by
unknown
26:28 queued 15:05
created

Jetpack_Sync_Full::enqueue_all_ids_as_action()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 18
c 1
b 0
f 0
nc 6
nop 5
dl 0
loc 29
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
				do_action( $action_name, $chunk );
195
				$counter += count( $chunked_ids );
196
			}
197
198
			$page += 1;
199
			$offset = ( $page * $items_per_page ) - $items_per_page;
200
		}
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( $this->get_client(), 'filter_post_content_and_add_links' ), $posts );
208
209
		return array(
210
			'posts'      => $posts,
211
			'post_metas' => $this->get_metadata( $post_ids, 'post' ),
212
			'terms'      => $this->get_term_relationships( $post_ids )
213
		);
214
	}
215
216
	private function enqueue_all_comments() {
217
		global $wpdb;
218
		$this->set_status( 'comments', 0 );
219
		$this->enqueue_all_ids_as_action( 'jetpack_full_sync_comments', $wpdb->comments, 'comment_ID', null, 'comments' );
220
		$this->set_status( 'comments', 100 );
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
		$comments = array_map( array( $this->get_client(), 'filter_comment_and_add_hc_meta' ), $comments );
230
231
		return array(
232
			'comments'      => $comments,
233
			'comment_metas' => $this->get_metadata( $comment_ids, 'comment' ),
234
		);
235
	}
236
237
	public function expand_term_ids( $args ) {
238
		global $wp_version;
239
		$term_ids = $args[0];
240
		$taxonomy = $args[1];
241
		// version 4.5 or higher
242
		if ( version_compare( $wp_version, 4.5, '>=' ) ) {
243
			$terms = get_terms( array(
244
				'taxonomy'   => $taxonomy,
245
				'hide_empty' => false,
246
				'include'    => $term_ids
247
			) );
248
		} else {
249
			$terms = get_terms( $taxonomy, array(
250
				'hide_empty' => false,
251
				'include'    => $term_ids
252
			) );
253
		}
254
255
		return $terms;
256
	}
257
258
	public function expand_options( $args ) {
259
		if ( $args[0] ) {
260
			return $this->get_client()->get_all_options();
261
		}
262
263
		return $args;
264
	}
265
266
	private function enqueue_all_users() {
267
		$this->set_status( 'users', 0 );
268
269
		$user_ids          = get_users( array( 'fields' => 'ID' ) );
270
		$chunked_users_ids = array_chunk( $user_ids, self::ARRAY_CHUNK_SIZE );
271
272
		$counter = 0;
273
		$total   = count( $chunked_users_ids );
274
275
		foreach ( $chunked_users_ids as $chunk ) {
276
			$this->set_status( 'users', ( $counter / $total ) * 100 );
277
			/**
278
			 * Fires with a chunk of user IDs during full sync.
279
			 * These get expanded to full user objects before upload (minus passwords)
280
			 *
281
			 * @since 4.1
282
			 */
283
			do_action( 'jetpack_full_sync_users', $chunk );
284
			$counter += 1;
285
		}
286
287
		$this->set_status( 'users', 100 );
288
	}
289
290
	public function expand_users( $args ) {
291
		$user_ids = $args[0];
292
293
		return array_map( array( $this->get_client(), 'sanitize_user' ), get_users( array( 'include' => $user_ids ) ) );
294
	}
295
296
	public function expand_network_options( $args ) {
297
		if ( $args[0] ) {
298
			return $this->get_client()->get_all_network_options();
299
		}
300
301
		return $args;
302
	}
303
304
	private function get_metadata( $ids, $meta_type ) {
305
		global $wpdb;
306
		$table = _get_meta_table( $meta_type );
307
		$id    = $meta_type . '_id';
308
		if ( ! $table ) {
309
			return array();
310
		}
311
312
		return $wpdb->get_results( "SELECT $id, meta_key, meta_value, meta_id FROM $table WHERE $id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . " )", OBJECT );
313
	}
314
315
	private function get_term_relationships( $ids ) {
316
		global $wpdb;
317
318
		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 );
319
	}
320
321
	// 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...
322
	private function enqueue_all_theme_info() {
323
		$this->set_status( 'themes', 0 );
324
		$this->get_client()->send_theme_info();
325
		$this->set_status( 'themes', 100 );
326
	}
327
328
	private function enqueue_all_updates() {
329
		$this->set_status( 'updates', 0 );
330
		// check for updates
331
		wp_update_plugins();
332
		wp_update_themes();
333
		_maybe_update_core();
334
		$this->set_status( 'updates', 100 );
335
	}
336
337
	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...
338
		set_transient( self::$status_transient_name . '_' . $name,
339
			array(
340
				'progress' => $percent,
341
				// 'count' => $count, 
342
				// 'total' => $total 
343
			),
344
			self::$transient_timeout
345
		);
346
	}
347
348
	private function set_status_queuing_started() {
349
		set_transient( self::$status_transient_name, array( 'phase' => 'queuing started' ), self::$transient_timeout );
350
	}
351
352
	private function set_status_queuing_finished() {
353
		set_transient( self::$status_transient_name, array( 'phase' => 'queuing finished' ), self::$transient_timeout );
354
	}
355
356
	// these are called by the Sync Client when it sees that the full sync start/end actions have actually been transmitted
357
	public function set_status_sending_started() {
358
		/**
359
		 * Fires when the full_sync_start 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_start_sent' );
365
		set_transient( self::$status_transient_name, array( 'phase' => 'sending started' ), self::$transient_timeout );
366
	}
367
368
	public function set_status_sending_finished() {
369
		/**
370
		 * Fires when the full_sync_end action is actually transmitted.
371
		 * This is useful for telling the user the status of full synchronization.
372
		 *
373
		 * @since 4.1
374
		 */
375
		do_action( 'jetpack_full_sync_end_sent' );
376
		set_transient( self::$status_transient_name, array( 'phase' => 'sending finished' ), self::$transient_timeout );
377
	}
378
379
	public function get_status() {
380
		$status = get_transient( self::$status_transient_name );
381
		if ( ! is_array( $status ) ) {
382
			return array( 'phase' => 'not started' );
383
		}
384
385
		return $status;
386
	}
387
388
	public function get_module_status( $module ) {
389
		return get_transient( self::$status_transient_name . '_' . $module );
390
	}
391
392
	public function get_complete_status() {
393
		return array_merge(
394
			$this->get_status(),
395
			array_combine(
396
				Jetpack_Sync_Full::$modules,
397
				array_map( array( $this, 'get_module_status' ), Jetpack_Sync_Full::$modules )
398
			)
399
		);
400
	}
401
}
402