PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 3.2.5
Jetpack – WP Security, Backup, Speed, & Growth v3.2.5
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 / class.jetpack-sync.php
class.jetpack-sync.php
852 lines 25.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Request that a piece of data on this WordPress install be synced back to the
5 * Jetpack server for remote processing/notifications/etc
6 */
7 class Jetpack_Sync {
8 // What modules want to sync what content
9 var $sync_conditions = array( 'posts' => array(), 'comments' => array() );
10
11 // We keep track of all the options registered for sync so that we can sync them all if needed
12 var $sync_options = array();
13
14 // Keep trac of status transitions, which we wouldn't always know about on the Jetpack Servers but are important when deciding what to do with the sync.
15 var $post_transitions = array();
16 var $comment_transitions = array();
17
18 // Objects to sync
19 var $sync = array();
20
21 function __construct() {
22 // WP Cron action. Only used on upgrade
23 add_action( 'jetpack_sync_all_registered_options', array( $this, 'sync_all_registered_options' ) );
24 }
25
26 /* Static Methods for Modules */
27
28 /**
29 * @param string $file __FILE__
30 * @param array settings:
31 * post_types => array( post_type slugs ): The post types to sync. Default: post, page
32 * post_stati => array( post_status slugs ): The post stati to sync. Default: publish
33 */
34 static function sync_posts( $file, array $settings = null ) {
35 if( is_network_admin() ) return;
36 $jetpack = Jetpack::init();
37 $args = func_get_args();
38 return call_user_func_array( array( $jetpack->sync, 'posts' ), $args );
39 }
40
41 /**
42 * @param string $file __FILE__
43 * @param array settings:
44 * post_types => array( post_type slugs ): The post types to sync. Default: post, page
45 * post_stati => array( post_status slugs ): The post stati to sync. Default: publish
46 * comment_types => array( comment_type slugs ): The comment types to sync. Default: '', comment, trackback, pingback
47 * comment_stati => array( comment_status slugs ): The comment stati to sync. Default: approved
48 */
49 static function sync_comments( $file, array $settings = null ) {
50 if( is_network_admin() ) return;
51 $jetpack = Jetpack::init();
52 $args = func_get_args();
53 return call_user_func_array( array( $jetpack->sync, 'comments' ), $args );
54 }
55
56 /**
57 * @param string $file __FILE__
58 * @param string $option, Option name to sync
59 * @param string $option ...
60 */
61 static function sync_options( $file, $option /*, $option, ... */ ) {
62 if( is_network_admin() ) return;
63 $jetpack = Jetpack::init();
64 $args = func_get_args();
65 return call_user_func_array( array( $jetpack->sync, 'options' ), $args );
66 }
67
68 /* Internal Methods */
69
70 /**
71 * Create a sync object/request
72 *
73 * @param string $object Type of object to sync -- [ post | comment | option ]
74 * @param int $id Unique identifier
75 * @param array $settings
76 */
77 function register( $object, $id = false, array $settings = null ) {
78 // Since we've registered something for sync, hook it up to execute on shutdown if we haven't already
79 if ( !$this->sync ) {
80 if ( function_exists( 'ignore_user_abort' ) ) {
81 ignore_user_abort( true );
82 }
83 add_action( 'shutdown', array( $this, 'sync' ), 9 ); // Right before async XML-RPC
84 }
85
86 $defaults = array(
87 'on_behalf_of' => array(), // What modules want this data
88 );
89 $settings = wp_parse_args( $settings, $defaults );
90
91 if ( !isset( $this->sync[$object] ) ) {
92 $this->sync[$object] = array();
93 }
94
95 // Store the settings for this object
96 if (
97 // First time for this object
98 !isset( $this->sync[$object][$id] )
99 ) {
100 // Easy: store the current settings
101 $this->sync[$object][$id] = $settings;
102 } else {
103 // Not as easy: we have to manually merge the settings from previous runs for this object with the settings for this run
104
105 $this->sync[$object][$id]['on_behalf_of'] = array_unique( array_merge( $this->sync[$object][$id]['on_behalf_of'], $settings['on_behalf_of'] ) );
106 }
107
108 $delete_prefix = 'delete_';
109 if ( 0 === strpos( $object, $delete_prefix ) ) {
110 $unset_object = substr( $object, strlen( $delete_prefix ) );
111 } else {
112 $unset_object = "{$delete_prefix}{$object}";
113 }
114
115 // Ensure post ... delete_post yields a delete operation
116 // Ensure delete_post ... post yields a sync post operation
117 // Ensure update_option() ... delete_option() ends up as a delete
118 // Ensure delete_option() ... update_option() ends up as an update
119 // Etc.
120 unset( $this->sync[$unset_object][$id] );
121
122 return true;
123 }
124
125 function get_common_sync_data() {
126 $available_modules = Jetpack::get_available_modules();
127 $active_modules = Jetpack::get_active_modules();
128 $modules = array();
129 foreach ( $available_modules as $available_module ) {
130 $modules[$available_module] = in_array( $available_module, $active_modules );
131 }
132 $modules['vaultpress'] = class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' );
133
134 $sync_data = array(
135 'modules' => $modules,
136 'version' => JETPACK__VERSION,
137 'is_multisite' => is_multisite(),
138 );
139
140 return $sync_data;
141 }
142
143 /**
144 * Set up all the data and queue it for the outgoing XML-RPC request
145 */
146 function sync() {
147 if ( !$this->sync ) {
148 return false;
149 }
150
151 $sync_data = $this->get_common_sync_data();
152
153 $wp_importing = defined( 'WP_IMPORTING' ) && WP_IMPORTING;
154
155 foreach ( $this->sync as $sync_operation_type => $sync_operations ) {
156 switch ( $sync_operation_type ) {
157 case 'post':
158 if ( $wp_importing ) {
159 break;
160 }
161
162 $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
163 $GLOBALS['post'] = null;
164 foreach ( $sync_operations as $post_id => $settings ) {
165 $sync_data['post'][$post_id] = $this->get_post( $post_id );
166 if ( isset( $this->post_transitions[$post_id] ) ) {
167 $sync_data['post'][$post_id]['transitions'] = $this->post_transitions[$post_id];
168 } else {
169 $sync_data['post'][$post_id]['transitions'] = array( false, false );
170 }
171 $sync_data['post'][$post_id]['on_behalf_of'] = $settings['on_behalf_of'];
172 }
173 $GLOBALS['post'] = $global_post;
174 unset( $global_post );
175 break;
176 case 'comment':
177 if ( $wp_importing ) {
178 break;
179 }
180
181 $global_comment = isset( $GLOBALS['comment'] ) ? $GLOBALS['comment'] : null;
182 unset( $GLOBALS['comment'] );
183 foreach ( $sync_operations as $comment_id => $settings ) {
184 $sync_data['comment'][$comment_id] = $this->get_comment( $comment_id );
185 if ( isset( $this->comment_transitions[$comment_id] ) ) {
186 $sync_data['comment'][$comment_id]['transitions'] = $this->comment_transitions[$comment_id];
187 } else {
188 $sync_data['comment'][$comment_id]['transitions'] = array( false, false );
189 }
190 $sync_data['comment'][$comment_id]['on_behalf_of'] = $settings['on_behalf_of'];
191 }
192 $GLOBALS['comment'] = $global_comment;
193 unset( $global_comment );
194 break;
195 case 'option' :
196 foreach ( $sync_operations as $option => $settings ) {
197 $sync_data['option'][$option] = array( 'value' => get_option( $option ) );
198 }
199 break;
200
201 case 'delete_post':
202 case 'delete_comment':
203 foreach ( $sync_operations as $object_id => $settings ) {
204 $sync_data[$sync_operation_type][$object_id] = array( 'on_behalf_of' => $settings['on_behalf_of'] );
205 }
206 break;
207 case 'delete_option' :
208 foreach ( $sync_operations as $object_id => $settings ) {
209 $sync_data[$sync_operation_type][$object_id] = true;
210 }
211 break;
212 }
213 }
214
215 Jetpack::xmlrpc_async_call( 'jetpack.syncContent', $sync_data );
216 }
217
218 /**
219 * Format and return content data from a direct xmlrpc request for it.
220 *
221 * @param array $content_ids: array( 'posts' => array of ids, 'comments' => array of ids, 'options' => array of options )
222 */
223 function get_content( $content_ids ) {
224 $sync_data = $this->get_common_sync_data();
225
226 if ( isset( $content_ids['posts'] ) ) {
227 foreach ( $content_ids['posts'] as $id ) {
228 $sync_data['post'][$id] = $this->get_post( $id );
229 }
230 }
231
232 if ( isset( $content_ids['comments'] ) ) {
233 foreach ( $content_ids['comments'] as $id ) {
234 $sync_data['comment'][$id] = $this->get_post( $id );
235 }
236 }
237
238 if ( isset( $content_ids['options'] ) ) {
239 foreach ( $content_ids['options'] as $option ) {
240 $sync_data['option'][$option] = array( 'value' => get_option( $option ) );
241 }
242 }
243
244 return $sync_data;
245 }
246
247 /**
248 * Helper method for registering a post for sync
249 *
250 * @param int $id wp_posts.ID
251 * @param array $settings Sync data
252 */
253 function register_post( $id, array $settings = null ) {
254 $id = (int) $id;
255 if ( !$id ) {
256 return false;
257 }
258
259 $post = get_post( $id );
260 if ( !$post ) {
261 return false;
262 }
263
264 $settings = wp_parse_args( $settings, array(
265 'on_behalf_of' => array(),
266 ) );
267
268 return $this->register( 'post', $id, $settings );
269 }
270
271 /**
272 * Helper method for registering a comment for sync
273 *
274 * @param int $id wp_comments.comment_ID
275 * @param array $settings Sync data
276 */
277 function register_comment( $id, array $settings = null ) {
278 $id = (int) $id;
279 if ( !$id ) {
280 return false;
281 }
282
283 $comment = get_comment( $id );
284 if ( !$comment || empty( $comment->comment_post_ID ) ) {
285 return false;
286 }
287
288 $post = get_post( $comment->comment_post_ID );
289 if ( !$post ) {
290 return false;
291 }
292
293 $settings = wp_parse_args( $settings, array(
294 'on_behalf_of' => array(),
295 ) );
296
297 return $this->register( 'comment', $id, $settings );
298 }
299
300 /* Posts Sync */
301
302 function posts( $file, array $settings = null ) {
303 $module_slug = Jetpack::get_module_slug( $file );
304
305 $defaults = array(
306 'post_types' => array( 'post', 'page' ),
307 'post_stati' => array( 'publish' ),
308 );
309
310 $this->sync_conditions['posts'][$module_slug] = wp_parse_args( $settings, $defaults );
311
312 add_action( 'transition_post_status', array( $this, 'transition_post_status_action' ), 10, 3 );
313 add_action( 'delete_post', array( $this, 'delete_post_action' ) );
314 }
315
316 function delete_post_action( $post_id ) {
317 $post = get_post( $post_id );
318 if ( !$post ) {
319 return $this->register( 'delete_post', (int) $post_id );
320 }
321
322 $this->transition_post_status_action( 'delete', $post->post_status, $post );
323 }
324
325 function transition_post_status_action( $new_status, $old_status, $post ) {
326 $sync = $this->get_post_sync_operation( $new_status, $old_status, $post, $this->sync_conditions['posts'] );
327 if ( !$sync ) {
328 // No module wants to sync this post
329 return false;
330 }
331
332 // Track post transitions
333 if ( isset( $this->post_transitions[$post->ID] ) ) {
334 // status changed more than once - keep tha most recent $new_status
335 $this->post_transitions[$post->ID][0] = $new_status;
336 } else {
337 $this->post_transitions[$post->ID] = array( $new_status, $old_status );
338 }
339
340 $operation = $sync['operation'];
341 unset( $sync['operation'] );
342
343 switch ( $operation ) {
344 case 'delete' :
345 return $this->register( 'delete_post', (int) $post->ID, $sync );
346 case 'submit' :
347 return $this->register_post( (int) $post->ID, $sync );
348 }
349 }
350
351 function get_post_sync_operation( $new_status, $old_status, $post, $module_conditions ) {
352 $delete_on_behalf_of = array();
353 $submit_on_behalf_of = array();
354 $delete_stati = array( 'delete' );
355
356 foreach ( $module_conditions as $module => $conditions ) {
357 if ( !in_array( $post->post_type, $conditions['post_types'] ) ) {
358 continue;
359 }
360
361 $deleted_post = in_array( $new_status, $delete_stati );
362
363 if ( $deleted_post ) {
364 $delete_on_behalf_of[] = $module;
365 } else {
366 clean_post_cache( $post->ID );
367 $new_status = get_post_status( $post->ID ); // Inherited status is resolved here
368 }
369
370 $old_status_in_stati = in_array( $old_status, $conditions['post_stati'] );
371 $new_status_in_stati = in_array( $new_status, $conditions['post_stati'] );
372
373 if ( $old_status_in_stati && !$new_status_in_stati ) {
374 // Jetpack no longer needs the post
375 if ( !$deleted_post ) {
376 $delete_on_behalf_of[] = $module;
377 } // else, we've already flagged it above
378 continue;
379 }
380
381 if ( !$new_status_in_stati ) {
382 continue;
383 }
384
385 // At this point, we know we want to sync the post, not delete it
386 $submit_on_behalf_of[] = $module;
387 }
388
389 if ( !empty( $submit_on_behalf_of ) ) {
390 return array( 'operation' => 'submit', 'on_behalf_of' => $submit_on_behalf_of );
391 }
392
393 if ( !empty( $delete_on_behalf_of ) ) {
394 return array( 'operation' => 'delete', 'on_behalf_of' => $delete_on_behalf_of );
395 }
396
397 return false;
398 }
399
400 /**
401 * Get a post and associated data in the standard JP format.
402 * Cannot be called statically
403 *
404 * @param int $id Post ID
405 * @return Array containing full post details
406 */
407 function get_post( $id ) {
408 $post_obj = get_post( $id );
409 if ( !$post_obj )
410 return false;
411
412 if ( is_callable( $post_obj, 'to_array' ) ) {
413 // WP >= 3.5
414 $post = $post_obj->to_array();
415 } else {
416 // WP < 3.5
417 $post = get_object_vars( $post_obj );
418 }
419
420 if ( 0 < strlen( $post['post_password'] ) ) {
421 $post['post_password'] = 'auto-' . wp_generate_password( 10, false ); // We don't want the real password. Just pass something random.
422 }
423
424 // local optimizations
425 unset(
426 $post['filter'],
427 $post['ancestors'],
428 $post['post_content_filtered'],
429 $post['to_ping'],
430 $post['pinged']
431 );
432
433 if ( $this->is_post_public( $post ) ) {
434 $post['post_is_public'] = Jetpack_Options::get_option( 'public' );
435 } else {
436 //obscure content
437 $post['post_content'] = '';
438 $post['post_excerpt'] = '';
439 $post['post_is_public'] = false;
440 }
441 $post_type_obj = get_post_type_object( $post['post_type'] );
442 $post['post_is_excluded_from_search'] = $post_type_obj->exclude_from_search;
443
444 $post['tax'] = array();
445 $taxonomies = get_object_taxonomies( $post_obj );
446 foreach ( $taxonomies as $taxonomy ) {
447 $terms = get_object_term_cache( $post_obj->ID, $taxonomy );
448 if ( empty( $terms ) )
449 $terms = wp_get_object_terms( $post_obj->ID, $taxonomy );
450 $term_names = array();
451 foreach ( $terms as $term ) {
452 $term_names[] = $term->name;
453 }
454 $post['tax'][$taxonomy] = $term_names;
455 }
456
457 $meta = get_post_meta( $post_obj->ID, false );
458 $post['meta'] = array();
459 foreach ( $meta as $key => $value ) {
460 $post['meta'][$key] = array_map( 'maybe_unserialize', $value );
461 }
462
463 $post['extra'] = array(
464 'author' => get_the_author_meta( 'display_name', $post_obj->post_author ),
465 'author_email' => get_the_author_meta( 'email', $post_obj->post_author ),
466 );
467
468 if ( $fid = get_post_thumbnail_id( $id ) ) {
469 $feature = wp_get_attachment_image_src( $fid, 'large' );
470 if ( !empty( $feature[0] ) )
471 $post['extra']['featured_image'] = $feature[0];
472 }
473
474 $post['permalink'] = get_permalink( $post_obj->ID );
475 $post['shortlink'] = wp_get_shortlink( $post_obj->ID );
476 $post['module_custom_data'] = apply_filters( 'jetpack_sync_post_module_custom_data', array(), $post_obj );
477 return $post;
478 }
479
480 /**
481 * Decide whether a post/page/attachment is visible to the public.
482 *
483 * @param array $post
484 * @return bool
485 */
486 function is_post_public( $post ) {
487 if ( !is_array( $post ) ) {
488 $post = (array) $post;
489 }
490
491 if ( 0 < strlen( $post['post_password'] ) )
492 return false;
493 if ( ! in_array( $post['post_type'], get_post_types( array( 'public' => true ) ) ) )
494 return false;
495 $post_status = get_post_status( $post['ID'] ); // Inherited status is resolved here.
496 if ( ! in_array( $post_status, get_post_stati( array( 'public' => true ) ) ) )
497 return false;
498 return true;
499 }
500
501 /* Comments Sync */
502
503 function comments( $file, array $settings = null ) {
504 $module_slug = Jetpack::get_module_slug( $file );
505
506 $defaults = array(
507 'post_types' => array( 'post', 'page' ), // For what post types will we sync comments?
508 'post_stati' => array( 'publish' ), // For what post stati will we sync comments?
509 'comment_types' => array( '', 'comment', 'trackback', 'pingback' ), // What comment types will we sync?
510 'comment_stati' => array( 'approved' ), // What comment stati will we sync?
511 );
512
513 $settings = wp_parse_args( $settings, $defaults );
514
515 $this->sync_conditions['comments'][$module_slug] = $settings;
516
517 add_action( 'wp_insert_comment', array( $this, 'wp_insert_comment_action' ), 10, 2 );
518 add_action( 'transition_comment_status', array( $this, 'transition_comment_status_action' ), 10, 3 );
519 add_action( 'edit_comment', array( $this, 'edit_comment_action' ) );
520 }
521
522 /*
523 * This is really annoying. If you edit a comment, but don't change the status, WordPress doesn't fire the transition_comment_status hook.
524 * That means we have to catch these comments on the edit_comment hook, but ignore comments on that hook when the transition_comment_status does fire.
525 */
526 function edit_comment_action( $comment_id ) {
527 $comment = get_comment( $comment_id );
528 $new_status = $this->translate_comment_status( $comment->comment_approved );
529 add_action( "comment_{$new_status}_{$comment->comment_type}", array( $this, 'transition_comment_status_for_comments_whose_status_does_not_change' ), 10, 2 );
530 }
531
532 function wp_insert_comment_action( $comment_id, $comment ) {
533 $this->transition_comment_status_action( $comment->comment_approved, 'new', $comment );
534 }
535
536 function transition_comment_status_for_comments_whose_status_does_not_change( $comment_id, $comment ) {
537 if ( isset( $this->comment_transitions[$comment_id] ) ) {
538 return $this->transition_comment_status_action( $comment->comment_approved, $this->comment_transitions[$comment_id][1], $comment );
539 }
540
541 return $this->transition_comment_status_action( $comment->comment_approved, $comment->comment_approved, $comment );
542 }
543
544 function translate_comment_status( $status ) {
545 switch ( (string) $status ) {
546 case '0' :
547 case 'hold' :
548 return 'unapproved';
549 case '1' :
550 case 'approve' :
551 return 'approved';
552 }
553
554 return $status;
555 }
556
557 function transition_comment_status_action( $new_status, $old_status, $comment ) {
558 $post = get_post( $comment->comment_post_ID );
559 if ( !$post ) {
560 return false;
561 }
562
563 foreach ( array( 'new_status', 'old_status' ) as $_status ) {
564 $$_status = $this->translate_comment_status( $$_status );
565 }
566
567 // Track comment transitions
568 if ( isset( $this->comment_transitions[$comment->comment_ID] ) ) {
569 // status changed more than once - keep tha most recent $new_status
570 $this->comment_transitions[$comment->comment_ID][0] = $new_status;
571 } else {
572 $this->comment_transitions[$comment->comment_ID] = array( $new_status, $old_status );
573 }
574
575 $post_sync = $this->get_post_sync_operation( $post->post_status, '_jetpack_test_sync', $post, $this->sync_conditions['comments'] );
576
577 if ( !$post_sync ) {
578 // No module wants to sync this comment because its post doesn't match any sync conditions
579 return false;
580 }
581
582 if ( 'delete' == $post_sync['operation'] ) {
583 // Had we been looking at post sync operations (instead of comment sync operations),
584 // this comment's post would have been deleted. Don't sync the comment.
585 return false;
586 }
587
588 $delete_on_behalf_of = array();
589 $submit_on_behalf_of = array();
590 $delete_stati = array( 'delete' );
591
592 foreach ( $this->sync_conditions['comments'] as $module => $conditions ) {
593 if ( !in_array( $comment->comment_type, $conditions['comment_types'] ) ) {
594 continue;
595 }
596
597 $deleted_comment = in_array( $new_status, $delete_stati );
598
599 if ( $deleted_comment ) {
600 $delete_on_behalf_of[] = $module;
601 }
602
603 $old_status_in_stati = in_array( $old_status, $conditions['comment_stati'] );
604 $new_status_in_stati = in_array( $new_status, $conditions['comment_stati'] );
605
606 if ( $old_status_in_stati && !$new_status_in_stati ) {
607 // Jetpack no longer needs the comment
608 if ( !$deleted_comment ) {
609 $delete_on_behalf_of[] = $module;
610 } // else, we've already flagged it above
611 continue;
612 }
613
614 if ( !$new_status_in_stati ) {
615 continue;
616 }
617
618 // At this point, we know we want to sync the comment, not delete it
619 $submit_on_behalf_of[] = $module;
620 }
621
622 if ( ! empty( $submit_on_behalf_of ) ) {
623 $this->register_post( $comment->comment_post_ID, array( 'on_behalf_of' => $submit_on_behalf_of ) );
624 return $this->register_comment( $comment->comment_ID, array( 'on_behalf_of' => $submit_on_behalf_of ) );
625 }
626
627 if ( !empty( $delete_on_behalf_of ) ) {
628 return $this->register( 'delete_comment', $comment->comment_ID, array( 'on_behalf_of' => $delete_on_behalf_of ) );
629 }
630
631 return false;
632 }
633
634 /**
635 * Get a comment and associated data in the standard JP format.
636 * Cannot be called statically
637 *
638 * @param int $id Comment ID
639 * @return Array containing full comment details
640 */
641 function get_comment( $id ) {
642 $comment_obj = get_comment( $id );
643 if ( !$comment_obj )
644 return false;
645 $comment = get_object_vars( $comment_obj );
646
647 $meta = get_comment_meta( $id, false );
648 $comment['meta'] = array();
649 foreach ( $meta as $key => $value ) {
650 $comment['meta'][$key] = array_map( 'maybe_unserialize', $value );
651 }
652
653 return $comment;
654 }
655
656 /* Options Sync */
657
658 /* Ah... so much simpler than Posts and Comments :) */
659 function options( $file, $option /*, $option, ... */ ) {
660 $options = func_get_args();
661 $file = array_shift( $options );
662
663 $module_slug = Jetpack::get_module_slug( $file );
664
665 if ( !isset( $this->sync_options[$module_slug] ) ) {
666 $this->sync_options[$module_slug] = array();
667 }
668
669 foreach ( $options as $option ) {
670 $this->sync_options[$module_slug][] = $option;
671 add_action( "delete_option_{$option}", array( $this, 'deleted_option_action' ) );
672 add_action( "update_option_{$option}", array( $this, 'updated_option_action' ) );
673 add_action( "add_option_{$option}", array( $this, 'added_option_action' ) );
674 }
675
676 $this->sync_options[$module_slug] = array_unique( $this->sync_options[$module_slug] );
677 }
678
679 function deleted_option_action( $option ) {
680 $this->register( 'delete_option', $option );
681 }
682
683 function updated_option_action( $old_value ) {
684 // The value of $option isn't passed to the filter
685 // Calculate it
686 $option = current_filter();
687 $prefix = 'update_option_';
688 if ( 0 !== strpos( $option, $prefix ) ) {
689 return;
690 }
691 $option = substr( $option, strlen( $prefix ) );
692
693 $this->added_option_action( $option );
694 }
695
696 function added_option_action( $option ) {
697 $this->register( 'option', $option );
698 }
699
700 function sync_all_module_options( $module_slug ) {
701 if ( empty( $this->sync_options[$module_slug] ) ) {
702 return;
703 }
704
705 foreach ( $this->sync_options[$module_slug] as $option ) {
706 $this->added_option_action( $option );
707 }
708 }
709
710 function sync_all_registered_options( $options = array() ) {
711 if ( 'jetpack_sync_all_registered_options' == current_filter() ) {
712 $all_registered_options = array_unique( call_user_func_array( 'array_merge', $this->sync_options ) );
713 foreach ( $all_registered_options as $option ) {
714 $this->added_option_action( $option );
715 }
716 } else {
717 wp_schedule_single_event( time(), 'jetpack_sync_all_registered_options', array( $this->sync_options ) );
718 }
719 }
720
721 public function reindex_needed() {
722 return ( $this->_get_post_count_local() != $this->_get_post_count_cloud() );
723 }
724
725 public function reindex_trigger() {
726 $response = array( 'status' => 'ERROR' );
727
728 // Force a privacy check
729 Jetpack::check_privacy( JETPACK__PLUGIN_FILE );
730
731 Jetpack::load_xml_rpc_client();
732 $client = new Jetpack_IXR_Client( array(
733 'user_id' => JETPACK_MASTER_USER,
734 ) );
735
736 $client->query( 'jetpack.reindexTrigger' );
737
738 if ( !$client->isError() ) {
739 $response = $client->getResponse();
740 Jetpack_Options::update_option( 'sync_bulk_reindexing', true );
741 }
742
743 return $response;
744 }
745
746 public function reindex_status() {
747 $response = array( 'status' => 'ERROR' );
748
749 // Assume reindexing is done if it was not triggered in the first place
750 if ( false === Jetpack_Options::get_option( 'sync_bulk_reindexing' ) ) {
751 return array( 'status' => 'DONE' );
752 }
753
754 Jetpack::load_xml_rpc_client();
755 $client = new Jetpack_IXR_Client( array(
756 'user_id' => JETPACK_MASTER_USER,
757 ) );
758
759 $client->query( 'jetpack.reindexStatus' );
760
761 if ( !$client->isError() ) {
762 $response = $client->getResponse();
763 if ( 'DONE' == $response['status'] ) {
764 Jetpack_Options::delete_option( 'sync_bulk_reindexing' );
765 }
766 }
767
768 return $response;
769 }
770
771 public function reindex_ui() {
772 $strings = json_encode( array(
773 'WAITING' => array(
774 'action' => __( 'Refresh Status', 'jetpack' ),
775 'status' => __( 'Indexing request queued and waiting&hellip;', 'jetpack' ),
776 ),
777 'INDEXING' => array(
778 'action' => __( 'Refresh Status', 'jetpack' ),
779 'status' => __( 'Indexing posts', 'jetpack' ),
780 ),
781 'DONE' => array(
782 'action' => __( 'Reindex Posts', 'jetpack' ),
783 'status' => __( 'Posts indexed.', 'jetpack' ),
784 ),
785 'ERROR' => array(
786 'action' => __( 'Refresh Status', 'jetpack' ),
787 'status' => __( 'Status unknown.', 'jetpack' ),
788 ),
789 'ERROR:LARGE' => array(
790 'action' => __( 'Refresh Status', 'jetpack' ),
791 'status' => __( 'This site is too large, please contact Jetpack support to sync.', 'jetpack' ),
792 ),
793 ) );
794
795 wp_enqueue_script(
796 'jetpack_sync_reindex_control',
797 plugins_url( '_inc/jquery.jetpack-sync.js', JETPACK__PLUGIN_FILE ),
798 array( 'jquery' ),
799 JETPACK__VERSION
800 );
801
802 $template = <<<EOT
803 <p class="jetpack_sync_reindex_control" id="jetpack_sync_reindex_control" data-strings="%s">
804 <input type="submit" class="jetpack_sync_reindex_control_action button" value="%s" disabled />
805 <span class="jetpack_sync_reindex_control_status">&hellip;</span>
806 </p>
807 EOT;
808
809 return sprintf(
810 $template,
811 esc_attr( $strings ),
812 esc_attr__( 'Refresh Status', 'jetpack' )
813 );
814 }
815
816 private function _get_post_count_local() {
817 global $wpdb;
818 return (int) $wpdb->get_var(
819 "SELECT count(*)
820 FROM {$wpdb->posts}
821 WHERE post_status = 'publish' AND post_password = ''"
822 );
823 }
824
825 private function _get_post_count_cloud() {
826 $blog_id = Jetpack::init()->get_option( 'id' );
827
828 $body = array(
829 'size' => 1,
830 );
831
832 $response = wp_remote_post(
833 "https://public-api.wordpress.com/rest/v1/sites/$blog_id/search",
834 array(
835 'timeout' => 10,
836 'user-agent' => 'jetpack_related_posts',
837 'sslverify' => true,
838 'body' => $body,
839 )
840 );
841
842 if ( is_wp_error( $response ) ) {
843 return 0;
844 }
845
846 $results = json_decode( wp_remote_retrieve_body( $response ), true );
847
848 return (int) $results['results']['total'];
849 }
850
851 }
852