Completed
Push — update/remove-after-the-deadli... ( 6e49cc...508f50 )
by
unknown
283:10 queued 275:44
created

sync/class.jetpack-sync-module-full-sync.php (3 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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, and its related class Jetpack_Sync_Module, contain 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 page through the object IDs and enqueue them by firing some monitored actions
11
 * - we load the full objects for those IDs in chunks of Jetpack_Sync_Module::ARRAY_CHUNK_SIZE (to reduce the number of MySQL calls)
12
 * - we fire a trigger for the entire array which the Jetpack_Sync_Listener then serializes and queues.
13
 */
14
15
class Jetpack_Sync_Module_Full_Sync extends Jetpack_Sync_Module {
16
	const STATUS_OPTION_PREFIX = 'jetpack_sync_full_';
17
	const FULL_SYNC_TIMEOUT    = 3600;
18
19
	public function name() {
20
		return 'full-sync';
21
	}
22
23
	function init_full_sync_listeners( $callable ) {
24
		// synthetic actions for full sync
25
		add_action( 'jetpack_full_sync_start', $callable, 10, 2 );
26
		add_action( 'jetpack_full_sync_end', $callable, 10, 2 );
27
		add_action( 'jetpack_full_sync_cancelled', $callable );
28
	}
29
30
	function init_before_send() {
31
		// this is triggered after actions have been processed on the server
32
		add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
33
	}
34
35
	function start( $module_configs = null ) {
36
		$was_already_running = $this->is_started() && ! $this->is_finished();
37
38
		// remove all evidence of previous full sync items and status
39
		$this->reset_data();
40
41
		if ( $was_already_running ) {
42
			/**
43
			 * Fires when a full sync is cancelled.
44
			 *
45
			 * @since 4.2.0
46
			 */
47
			do_action( 'jetpack_full_sync_cancelled' );
48
		}
49
50
		$this->update_status_option( 'started', time() );
51
		$this->update_status_option( 'params', $module_configs );
52
53
		$enqueue_status   = array();
54
		$full_sync_config = array();
55
56
		// default value is full sync
57
		if ( ! is_array( $module_configs ) ) {
58
			$module_configs = array();
59
			foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
60
				$module_configs[ $module->name() ] = true;
61
			}
62
		}
63
64
		// set default configuration, calculate totals, and save configuration if totals > 0
65
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
66
			$module_name   = $module->name();
67
			$module_config = isset( $module_configs[ $module_name ] ) ? $module_configs[ $module_name ] : false;
68
69
			if ( ! $module_config ) {
70
				continue;
71
			}
72
73
			if ( 'users' === $module_name && 'initial' === $module_config ) {
74
				$module_config = $module->get_initial_sync_user_config();
75
			}
76
77
			$enqueue_status[ $module_name ] = false;
78
79
			$total_items = $module->estimate_full_sync_actions( $module_config );
80
81
			// if there's information to process, configure this module
82
			if ( ! is_null( $total_items ) && $total_items > 0 ) {
83
				$full_sync_config[ $module_name ] = $module_config;
84
				$enqueue_status[ $module_name ]   = array(
85
					$total_items,   // total
86
					0,              // queued
87
					false,          // current state
88
				);
89
			}
90
		}
91
92
		$this->set_config( $full_sync_config );
93
		$this->set_enqueue_status( $enqueue_status );
94
95
		$range = $this->get_content_range( $full_sync_config );
96
		/**
97
		 * Fires when a full sync begins. This action is serialized
98
		 * and sent to the server so that it knows a full sync is coming.
99
		 *
100
		 * @since 4.2.0
101
		 * @since 7.3.0 Added $range arg.
102
		 *
103
		 * @param $full_sync_config - array
104
		 * @param $range array
105
		 */
106
		do_action( 'jetpack_full_sync_start', $full_sync_config, $range );
107
108
		$this->continue_enqueuing( $full_sync_config, $enqueue_status );
109
110
		return true;
111
	}
112
113
	function continue_enqueuing( $configs = null, $enqueue_status = null ) {
114
		if ( ! $this->is_started() || $this->get_status_option( 'queue_finished' ) ) {
115
			return;
116
		}
117
118
		// if full sync queue is full, don't enqueue more items
119
		$max_queue_size_full_sync = Jetpack_Sync_Settings::get_setting( 'max_queue_size_full_sync' );
120
		$full_sync_queue          = new Jetpack_Sync_Queue( 'full_sync' );
121
122
		$available_queue_slots = $max_queue_size_full_sync - $full_sync_queue->size();
123
124
		if ( $available_queue_slots <= 0 ) {
125
			return;
126
		} else {
127
			$remaining_items_to_enqueue = min( Jetpack_Sync_Settings::get_setting( 'max_enqueue_full_sync' ), $available_queue_slots );
128
		}
129
130
		if ( ! $configs ) {
131
			$configs = $this->get_config();
132
		}
133
134
		if ( ! $enqueue_status ) {
135
			$enqueue_status = $this->get_enqueue_status();
136
		}
137
138
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
139
			$module_name = $module->name();
140
141
			// skip module if not configured for this sync or module is done
142
			if ( ! isset( $configs[ $module_name ] )
143
				|| // no module config
144
					! $configs[ $module_name ]
145
				|| // no enqueue status
146
					! $enqueue_status[ $module_name ]
147
				|| // finished enqueuing this module
148
					true === $enqueue_status[ $module_name ][2] ) {
149
				continue;
150
			}
151
152
			list( $items_enqueued, $next_enqueue_state ) = $module->enqueue_full_sync_actions( $configs[ $module_name ], $remaining_items_to_enqueue, $enqueue_status[ $module_name ][2] );
153
154
			$enqueue_status[ $module_name ][2] = $next_enqueue_state;
155
156
			// if items were processed, subtract them from the limit
157
			if ( ! is_null( $items_enqueued ) && $items_enqueued > 0 ) {
158
				$enqueue_status[ $module_name ][1] += $items_enqueued;
159
				$remaining_items_to_enqueue        -= $items_enqueued;
160
			}
161
162
			// stop processing if we've reached our limit of items to enqueue
163
			if ( 0 >= $remaining_items_to_enqueue ) {
164
				$this->set_enqueue_status( $enqueue_status );
165
				return;
166
			}
167
		}
168
169
		$this->set_enqueue_status( $enqueue_status );
170
171
		// setting autoload to true means that it's faster to check whether we should continue enqueuing
172
		$this->update_status_option( 'queue_finished', time(), true );
173
174
		$range = $this->get_content_range( $configs );
175
176
		/**
177
		 * Fires when a full sync ends. This action is serialized
178
		 * and sent to the server.
179
		 *
180
		 * @since 4.2.0
181
		 * @since 7.3.0 Added $range arg.
182
		 *
183
		 * @param args ''
184
		 * @param $range array 
185
		 */
186
		do_action( 'jetpack_full_sync_end', '', $range );
187
	}
188
189
	function get_range( $type ) {
190
		global $wpdb;
191
		if ( ! in_array( $type, array( 'comments', 'posts' ) ) ) {
192
			return array();
193
		}
194
195
		switch ( $type ) {
196
			case 'posts':
197
				$table     = $wpdb->posts;
198
				$id        = 'ID';
199
				$where_sql = Jetpack_Sync_Settings::get_blacklisted_post_types_sql();
200
201
				break;
202
			case 'comments':
203
				$table     = $wpdb->comments;
204
				$id        = 'comment_ID';
205
				$where_sql = Jetpack_Sync_Settings::get_comments_filter_sql();
206
				break;
207
		}
208
		$results = $wpdb->get_results( "SELECT MAX({$id}) as max, MIN({$id}) as min, COUNT({$id}) as count FROM {$table} WHERE {$where_sql}" );
0 ignored issues
show
The variable $id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
The variable $table does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
The variable $where_sql does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
209
		if ( isset( $results[0] ) ) {
210
			return $results[0];
211
		}
212
213
		return array();
214
	}
215
216
	private function get_content_range( $config ) {
217
		$range = array();
218
		// Only when we are sending the whole range do we want to send also the range
219 View Code Duplication
		if ( isset( $config['posts'] ) && $config['posts'] === true ) {
220
			$range['posts'] = $this->get_range( 'posts' );
221
		}
222
223 View Code Duplication
		if ( isset( $config['comments'] ) && $config['comments'] === true ) {
224
			$range['comments'] = $this->get_range( 'comments' );
225
		}
226
		return $range;
227
	}
228
229
	function update_sent_progress_action( $actions ) {
230
231
		// quick way to map to first items with an array of arrays
232
		$actions_with_counts = array_count_values( array_filter( array_map( array( $this, 'get_action_name' ), $actions ) ) );
233
234
		if ( ! $this->is_started() || $this->is_finished() ) {
235
			return;
236
		}
237
238
		if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) {
239
			$this->update_status_option( 'send_started', time() );
240
		}
241
242
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
243
			$module_actions     = $module->get_full_sync_actions();
244
			$status_option_name = "{$module->name()}_sent";
245
			$items_sent         = $this->get_status_option( $status_option_name, 0 );
246
247
			foreach ( $module_actions as $module_action ) {
248
				if ( isset( $actions_with_counts[ $module_action ] ) ) {
249
					$items_sent += $actions_with_counts[ $module_action ];
250
				}
251
			}
252
253
			if ( $items_sent > 0 ) {
254
				$this->update_status_option( $status_option_name, $items_sent );
255
			}
256
		}
257
258
		if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) {
259
			$this->update_status_option( 'finished', time() );
260
		}
261
	}
262
263
	public function get_action_name( $queue_item ) {
264
		if ( is_array( $queue_item ) && isset( $queue_item[0] ) ) {
265
			return $queue_item[0];
266
		}
267
		return false;
268
	}
269
270
	public function is_started() {
271
		return ! ! $this->get_status_option( 'started' );
272
	}
273
274
	public function is_finished() {
275
		return ! ! $this->get_status_option( 'finished' );
276
	}
277
278
	public function get_status() {
279
		$status = array(
280
			'started'        => $this->get_status_option( 'started' ),
281
			'queue_finished' => $this->get_status_option( 'queue_finished' ),
282
			'send_started'   => $this->get_status_option( 'send_started' ),
283
			'finished'       => $this->get_status_option( 'finished' ),
284
			'sent'           => array(),
285
			'queue'          => array(),
286
			'config'         => $this->get_status_option( 'params' ),
287
			'total'          => array(),
288
		);
289
290
		$enqueue_status = $this->get_enqueue_status();
291
292
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
293
			$name = $module->name();
294
295
			if ( ! isset( $enqueue_status[ $name ] ) ) {
296
				continue;
297
			}
298
299
			list( $total, $queued, $state ) = $enqueue_status[ $name ];
300
301
			if ( $total ) {
302
				$status['total'][ $name ] = $total;
303
			}
304
305
			if ( $queued ) {
306
				$status['queue'][ $name ] = $queued;
307
			}
308
309
			if ( $sent = $this->get_status_option( "{$name}_sent" ) ) {
310
				$status['sent'][ $name ] = $sent;
311
			}
312
		}
313
314
		return $status;
315
	}
316
317
	public function clear_status() {
318
		$prefix = self::STATUS_OPTION_PREFIX;
319
		Jetpack_Options::delete_raw_option( "{$prefix}_started" );
320
		Jetpack_Options::delete_raw_option( "{$prefix}_params" );
321
		Jetpack_Options::delete_raw_option( "{$prefix}_queue_finished" );
322
		Jetpack_Options::delete_raw_option( "{$prefix}_send_started" );
323
		Jetpack_Options::delete_raw_option( "{$prefix}_finished" );
324
325
		$this->delete_enqueue_status();
326
327
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
328
			Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent" );
329
		}
330
	}
331
332
	public function reset_data() {
333
		$this->clear_status();
334
		$this->delete_config();
335
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php';
336
		$listener = Jetpack_Sync_Listener::get_instance();
337
		$listener->get_full_sync_queue()->reset();
338
	}
339
340
	private function get_status_option( $name, $default = null ) {
341
		$value = Jetpack_Options::get_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $default );
342
343
		return is_numeric( $value ) ? intval( $value ) : $value;
344
	}
345
346
	private function update_status_option( $name, $value, $autoload = false ) {
347
		Jetpack_Options::update_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $value, $autoload );
348
	}
349
350
	private function set_enqueue_status( $new_status ) {
351
		Jetpack_Options::update_raw_option( 'jetpack_sync_full_enqueue_status', $new_status );
352
	}
353
354
	private function delete_enqueue_status() {
355
		return Jetpack_Options::delete_raw_option( 'jetpack_sync_full_enqueue_status' );
356
	}
357
358
	private function get_enqueue_status() {
359
		return Jetpack_Options::get_raw_option( 'jetpack_sync_full_enqueue_status' );
360
	}
361
362
	private function set_config( $config ) {
363
		Jetpack_Options::update_raw_option( 'jetpack_sync_full_config', $config );
364
	}
365
366
	private function delete_config() {
367
		return Jetpack_Options::delete_raw_option( 'jetpack_sync_full_config' );
368
	}
369
370
	private function get_config() {
371
		return Jetpack_Options::get_raw_option( 'jetpack_sync_full_config' );
372
	}
373
374
	private function write_option( $name, $value ) {
375
		// we write our own option updating code to bypass filters/caching/etc on set_option/get_option
376
		global $wpdb;
377
		$serialized_value = maybe_serialize( $value );
378
		// try updating, if no update then insert
379
		// TODO: try to deal with the fact that unchanged values can return updated_num = 0
380
		// below we used "insert ignore" to at least suppress the resulting error
381
		$updated_num = $wpdb->query(
382
			$wpdb->prepare(
383
				"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
384
				$serialized_value,
385
				$name
386
			)
387
		);
388
389
		if ( ! $updated_num ) {
390
			$updated_num = $wpdb->query(
391
				$wpdb->prepare(
392
					"INSERT IGNORE INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, 'no' )",
393
					$name,
394
					$serialized_value
395
				)
396
			);
397
		}
398
		return $updated_num;
399
	}
400
401
	private function read_option( $name, $default = null ) {
402
		global $wpdb;
403
		$value = $wpdb->get_var(
404
			$wpdb->prepare(
405
				"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
406
				$name
407
			)
408
		);
409
		$value = maybe_unserialize( $value );
410
411
		if ( $value === null && $default !== null ) {
412
			return $default;
413
		}
414
415
		return $value;
416
	}
417
}
418