Jetpack_Sync

Request that a piece of data on this WordPress install be synced back to the Jetpack server for remote processing/notifications/etc.

Defined (1)

The class is defined in the following location(s).

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