PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.0.4
Jetpack – WP Security, Backup, Speed, & Growth v6.0.4
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / sync / class.jetpack-sync-sender.php
class.jetpack-sync-sender.php
393 lines 12.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 require_once dirname( __FILE__ ) . '/class.jetpack-sync-queue.php';
4 require_once dirname( __FILE__ ) . '/class.jetpack-sync-defaults.php';
5 require_once dirname( __FILE__ ) . '/class.jetpack-sync-json-deflate-array-codec.php';
6 require_once dirname( __FILE__ ) . '/class.jetpack-sync-modules.php';
7 require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
8
9 /**
10 * This class grabs pending actions from the queue and sends them
11 */
12 class Jetpack_Sync_Sender {
13
14 const NEXT_SYNC_TIME_OPTION_NAME = 'jetpack_next_sync_time';
15 const WPCOM_ERROR_SYNC_DELAY = 60;
16 const QUEUE_LOCKED_SYNC_DELAY = 10;
17
18 private $dequeue_max_bytes;
19 private $upload_max_bytes;
20 private $upload_max_rows;
21 private $max_dequeue_time;
22 private $sync_wait_time;
23 private $sync_wait_threshold;
24 private $enqueue_wait_time;
25 private $sync_queue;
26 private $full_sync_queue;
27 private $codec;
28 private $old_user;
29
30 // singleton functions
31 private static $instance;
32
33 public static function get_instance() {
34 if ( null === self::$instance ) {
35 self::$instance = new self();
36 }
37
38 return self::$instance;
39 }
40
41 // this is necessary because you can't use "new" when you declare instance properties >:(
42 protected function __construct() {
43 $this->set_defaults();
44 $this->init();
45 }
46
47 private function init() {
48 add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_set_user_from_token' ), 1 );
49 add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_clear_user_from_token' ), 20 );
50 foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
51 $module->init_before_send();
52 }
53 }
54
55 public function maybe_set_user_from_token( ) {
56 $jetpack = Jetpack::init();
57 $verified_user = $jetpack->verify_xml_rpc_signature();
58 if ( Jetpack_Constants::is_true( 'XMLRPC_REQUEST' ) &&
59 ! is_wp_error( $verified_user )
60 && $verified_user
61 ) {
62 $old_user = wp_get_current_user();
63 $this->old_user = isset( $old_user->ID ) ? $old_user->ID : 0;
64 wp_set_current_user( $verified_user['user_id'] );
65 }
66 }
67
68 public function maybe_clear_user_from_token() {
69 if ( isset( $this->old_user ) ) {
70 wp_set_current_user( $this->old_user );
71 }
72 }
73
74 public function get_next_sync_time( $queue_name ) {
75 return (double) get_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, 0 );
76 }
77
78 public function set_next_sync_time( $time, $queue_name ) {
79 return update_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, $time, true );
80 }
81
82 public function do_full_sync() {
83 if ( ! Jetpack_Sync_Modules::get_module( 'full-sync' ) ) {
84 return;
85 }
86 $this->continue_full_sync_enqueue();
87 return $this->do_sync_and_set_delays( $this->full_sync_queue );
88 }
89
90 private function continue_full_sync_enqueue() {
91 if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
92 return false;
93 }
94
95 if ( $this->get_next_sync_time( 'full-sync-enqueue' ) > microtime( true ) ) {
96 return false;
97 }
98
99 Jetpack_Sync_Modules::get_module( 'full-sync' )->continue_enqueuing();
100
101 $this->set_next_sync_time( time() + $this->get_enqueue_wait_time(), 'full-sync-enqueue' );
102 }
103
104 public function do_sync() {
105 return $this->do_sync_and_set_delays( $this->sync_queue );
106 }
107
108 public function do_sync_and_set_delays( $queue ) {
109 // don't sync if importing
110 if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
111 return new WP_Error( 'is_importing' );
112 }
113
114 // don't sync if we are throttled
115 if ( $this->get_next_sync_time( $queue->id ) > microtime( true ) ) {
116 return new WP_Error( 'sync_throttled' );
117 }
118
119 $start_time = microtime( true );
120
121 Jetpack_Sync_Settings::set_is_syncing( true );
122
123 $sync_result = $this->do_sync_for_queue( $queue );
124
125 Jetpack_Sync_Settings::set_is_syncing( false );
126
127 $exceeded_sync_wait_threshold = ( microtime( true ) - $start_time ) > (double) $this->get_sync_wait_threshold();
128
129 if ( is_wp_error( $sync_result ) ) {
130 if ( 'unclosed_buffer' === $sync_result->get_error_code() ) {
131 $this->set_next_sync_time( time() + self::QUEUE_LOCKED_SYNC_DELAY, $queue->id );
132 }
133 if ( 'wpcom_error' === $sync_result->get_error_code() ) {
134 $this->set_next_sync_time( time() + self::WPCOM_ERROR_SYNC_DELAY, $queue->id );
135 }
136 } elseif ( $exceeded_sync_wait_threshold ) {
137 // if we actually sent data and it took a while, wait before sending again
138 $this->set_next_sync_time( time() + $this->get_sync_wait_time(), $queue->id );
139 }
140
141 return $sync_result;
142 }
143
144 public function get_items_to_send( $buffer, $encode = true ) {
145 // track how long we've been processing so we can avoid request timeouts
146 $start_time = microtime( true );
147 $upload_size = 0;
148 $items_to_send = array();
149 $items = $buffer->get_items();
150 // set up current screen to avoid errors rendering content
151 require_once( ABSPATH . 'wp-admin/includes/class-wp-screen.php' );
152 require_once( ABSPATH . 'wp-admin/includes/screen.php' );
153 set_current_screen( 'sync' );
154 $skipped_items_ids = array();
155 // we estimate the total encoded size as we go by encoding each item individually
156 // this is expensive, but the only way to really know :/
157 foreach ( $items as $key => $item ) {
158 // Suspending cache addition help prevent overloading in memory cache of large sites.
159 wp_suspend_cache_addition( true );
160 /**
161 * Modify the data within an action before it is serialized and sent to the server
162 * For example, during full sync this expands Post ID's into full Post objects,
163 * so that we don't have to serialize the whole object into the queue.
164 *
165 * @since 4.2.0
166 *
167 * @param array The action parameters
168 * @param int The ID of the user who triggered the action
169 */
170 $item[1] = apply_filters( 'jetpack_sync_before_send_' . $item[0], $item[1], $item[2] );
171 wp_suspend_cache_addition( false );
172 if ( $item[1] === false ) {
173 $skipped_items_ids[] = $key;
174 continue;
175 }
176 $encoded_item = $encode ? $this->codec->encode( $item ) : $item;
177 $upload_size += strlen( $encoded_item );
178 if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) {
179 break;
180 }
181 $items_to_send[ $key ] = $encoded_item;
182 if ( microtime(true) - $start_time > $this->max_dequeue_time ) {
183 break;
184 }
185 }
186
187 return array( $items_to_send, $skipped_items_ids, $items, microtime( true ) - $start_time );
188 }
189
190 public function do_sync_for_queue( $queue ) {
191
192 do_action( 'jetpack_sync_before_send_queue_' . $queue->id );
193 if ( $queue->size() === 0 ) {
194 return new WP_Error( 'empty_queue_' . $queue->id );
195 }
196 // now that we're sure we are about to sync, try to
197 // ignore user abort so we can avoid getting into a
198 // bad state
199 if ( function_exists( 'ignore_user_abort' ) ) {
200 ignore_user_abort( true );
201 }
202
203 $checkout_start_time = microtime( true );
204
205 $buffer = $queue->checkout_with_memory_limit( $this->dequeue_max_bytes, $this->upload_max_rows );
206
207 if ( ! $buffer ) {
208 // buffer has no items
209 return new WP_Error( 'empty_buffer' );
210 }
211
212 if ( is_wp_error( $buffer ) ) {
213 return $buffer;
214 }
215
216 $checkout_duration = microtime( true ) - $checkout_start_time;
217
218 list( $items_to_send, $skipped_items_ids, $items, $preprocess_duration ) = $this->get_items_to_send( $buffer, true );
219
220 /**
221 * Fires when data is ready to send to the server.
222 * Return false or WP_Error to abort the sync (e.g. if there's an error)
223 * The items will be automatically re-sent later
224 *
225 * @since 4.2.0
226 *
227 * @param array $data The action buffer
228 * @param string $codec The codec name used to encode the data
229 * @param double $time The current time
230 * @param string $queue The queue used to send ('sync' or 'full_sync')
231 */
232 Jetpack_Sync_Settings::set_is_sending( true );
233 $processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->codec->name(), microtime( true ), $queue->id, $checkout_duration, $preprocess_duration );
234 Jetpack_Sync_Settings::set_is_sending( false );
235
236 if ( ! $processed_item_ids || is_wp_error( $processed_item_ids ) ) {
237 $checked_in_item_ids = $queue->checkin( $buffer );
238 if ( is_wp_error( $checked_in_item_ids ) ) {
239 error_log( 'Error checking in buffer: ' . $checked_in_item_ids->get_error_message() );
240 $queue->force_checkin();
241 }
242 if ( is_wp_error( $processed_item_ids ) ) {
243 return new WP_Error( 'wpcom_error', $processed_item_ids->get_error_code() );
244 }
245 // returning a WP_Error('wpcom_error') is a sign to the caller that we should wait a while
246 // before syncing again
247 return new WP_Error( 'wpcom_error', 'jetpack_sync_send_data_false' );
248 } else {
249 // detect if the last item ID was an error
250 $had_wp_error = is_wp_error( end( $processed_item_ids ) );
251 if ( $had_wp_error ) {
252 $wp_error = array_pop( $processed_item_ids );
253 }
254 // also checkin any items that were skipped
255 if ( count( $skipped_items_ids ) > 0 ) {
256 $processed_item_ids = array_merge( $processed_item_ids, $skipped_items_ids );
257 }
258 $processed_items = array_intersect_key( $items, array_flip( $processed_item_ids ) );
259 /**
260 * Allows us to keep track of all the actions that have been sent.
261 * Allows us to calculate the progress of specific actions.
262 *
263 * @since 4.2.0
264 *
265 * @param array $processed_actions The actions that we send successfully.
266 */
267 do_action( 'jetpack_sync_processed_actions', $processed_items );
268 $queue->close( $buffer, $processed_item_ids );
269 // returning a WP_Error is a sign to the caller that we should wait a while
270 // before syncing again
271 if ( $had_wp_error ) {
272 return new WP_Error( 'wpcom_error', $wp_error->get_error_code() );
273 }
274 }
275 return true;
276 }
277
278 function get_sync_queue() {
279 return $this->sync_queue;
280 }
281
282 function get_full_sync_queue() {
283 return $this->full_sync_queue;
284 }
285
286 function get_codec() {
287 return $this->codec;
288 }
289
290 function send_checksum() {
291 require_once 'class.jetpack-sync-wp-replicastore.php';
292 $store = new Jetpack_Sync_WP_Replicastore();
293 do_action( 'jetpack_sync_checksum', $store->checksum_all() );
294 }
295
296 function reset_sync_queue() {
297 $this->sync_queue->reset();
298 }
299
300 function reset_full_sync_queue() {
301 $this->full_sync_queue->reset();
302 }
303
304 function set_dequeue_max_bytes( $size ) {
305 $this->dequeue_max_bytes = $size;
306 }
307
308 // in bytes
309 function set_upload_max_bytes( $max_bytes ) {
310 $this->upload_max_bytes = $max_bytes;
311 }
312
313 // in rows
314 function set_upload_max_rows( $max_rows ) {
315 $this->upload_max_rows = $max_rows;
316 }
317
318 // in seconds
319 function set_sync_wait_time( $seconds ) {
320 $this->sync_wait_time = $seconds;
321 }
322
323 function get_sync_wait_time() {
324 return $this->sync_wait_time;
325 }
326
327 function set_enqueue_wait_time( $seconds ) {
328 $this->enqueue_wait_time = $seconds;
329 }
330
331 function get_enqueue_wait_time() {
332 return $this->enqueue_wait_time;
333 }
334
335 // in seconds
336 function set_sync_wait_threshold( $seconds ) {
337 $this->sync_wait_threshold = $seconds;
338 }
339
340 function get_sync_wait_threshold() {
341 return $this->sync_wait_threshold;
342 }
343
344 // in seconds
345 function set_max_dequeue_time( $seconds ) {
346 $this->max_dequeue_time = $seconds;
347 }
348
349 function set_defaults() {
350 $this->sync_queue = new Jetpack_Sync_Queue( 'sync' );
351 $this->full_sync_queue = new Jetpack_Sync_Queue( 'full_sync' );
352 $this->codec = new Jetpack_Sync_JSON_Deflate_Array_Codec();
353
354 // saved settings
355 Jetpack_Sync_Settings::set_importing( null );
356 $settings = Jetpack_Sync_Settings::get_settings();
357 $this->set_dequeue_max_bytes( $settings['dequeue_max_bytes'] );
358 $this->set_upload_max_bytes( $settings['upload_max_bytes'] );
359 $this->set_upload_max_rows( $settings['upload_max_rows'] );
360 $this->set_sync_wait_time( $settings['sync_wait_time'] );
361 $this->set_enqueue_wait_time( $settings['enqueue_wait_time'] );
362 $this->set_sync_wait_threshold( $settings['sync_wait_threshold'] );
363 $this->set_max_dequeue_time( Jetpack_Sync_Defaults::get_max_sync_execution_time() );
364 }
365
366 function reset_data() {
367 $this->reset_sync_queue();
368 $this->reset_full_sync_queue();
369
370 foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
371 $module->reset_data();
372 }
373
374 foreach ( array( 'sync', 'full_sync', 'full-sync-enqueue' ) as $queue_name ) {
375 delete_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name );
376 }
377
378 Jetpack_Sync_Settings::reset_data();
379 }
380
381 function uninstall() {
382 // Lets delete all the other fun stuff like transient and option and the sync queue
383 $this->reset_data();
384
385 // delete the full sync status
386 delete_option( 'jetpack_full_sync_status' );
387
388 // clear the sync cron.
389 wp_clear_scheduled_hook( 'jetpack_sync_cron' );
390 wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
391 }
392 }
393