From ad3f35663e4e2b80a92166b590d3e2052b5aab91 Mon Sep 17 00:00:00 2001 From: "Anthony G. Basile" Date: Tue, 23 Aug 2016 20:02:23 -0400 Subject: Update plugin jetpack to 4.2.2 --- plugins/jetpack/3rd-party/3rd-party.php | 4 +- plugins/jetpack/3rd-party/bitly.php | 4 +- plugins/jetpack/3rd-party/polldaddy.php | 7 + plugins/jetpack/3rd-party/vaultpress.php | 13 - plugins/jetpack/_inc/jquery.jetpack-sync.js | 68 -- plugins/jetpack/_inc/lib/tonesque.php | 22 +- plugins/jetpack/changelog.txt | 89 +- plugins/jetpack/class.jetpack-admin.php | 3 +- .../class.jetpack-bbpress-json-api-compat.php | 10 - plugins/jetpack/class.jetpack-cli.php | 8 +- plugins/jetpack/class.jetpack-client-server.php | 34 +- plugins/jetpack/class.jetpack-debugger.php | 102 +- plugins/jetpack/class.jetpack-heartbeat.php | 4 + .../jetpack/class.jetpack-modules-list-table.php | 3 + plugins/jetpack/class.jetpack-options.php | 32 +- plugins/jetpack/class.jetpack-signature.php | 6 +- plugins/jetpack/class.jetpack-sync.php | 1104 -------------------- plugins/jetpack/class.jetpack-twitter-cards.php | 2 +- plugins/jetpack/class.jetpack-user-agent.php | 90 +- plugins/jetpack/class.jetpack-xmlrpc-server.php | 54 +- plugins/jetpack/class.jetpack.php | 760 ++++---------- plugins/jetpack/css/jetpack-admin.css.map | 2 +- plugins/jetpack/css/jetpack-rtl.css | 2 +- plugins/jetpack/css/jetpack.css | 2 +- plugins/jetpack/functions.opengraph.php | 4 +- plugins/jetpack/jetpack.php | 9 +- plugins/jetpack/json-endpoints.php | 127 ++- .../class.wpcom-json-api-comment-endpoint.php | 2 + .../class.wpcom-json-api-get-site-endpoint.php | 5 +- .../class.wpcom-json-api-post-endpoint.php | 1 - ...class.wpcom-json-api-site-settings-endpoint.php | 4 +- ...lass.wpcom-json-api-update-comment-endpoint.php | 2 +- .../class.jetpack-json-api-plugins-endpoint.php | 3 +- ...s.jetpack-json-api-plugins-install-endpoint.php | 3 +- .../class.jetpack-json-api-sync-endpoint.php | 179 +++- .../jetpack/json-api-jetpack-endpoints.php | 118 +++ plugins/jetpack/modules/after-the-deadline.php | 2 +- .../jetpack/modules/carousel/jetpack-carousel.js | 8 + .../jetpack/modules/carousel/jetpack-carousel.php | 26 +- plugins/jetpack/modules/comments.php | 9 - .../modules/contact-form/grunion-contact-form.php | 44 +- .../modules/custom-css/csstidy/class.csstidy.php | 2 +- .../custom-css/csstidy/class.csstidy_optimise.php | 2 +- .../custom-css/csstidy/class.csstidy_print.php | 2 +- .../modules/custom-css/csstidy/data-wp.inc.php | 1 + plugins/jetpack/modules/custom-css/custom-css.php | 11 +- .../modules/custom-post-types/portfolios.php | 14 +- .../modules/custom-post-types/testimonial.php | 2 +- plugins/jetpack/modules/enhanced-distribution.php | 24 +- plugins/jetpack/modules/gravatar-hovercards.php | 2 +- plugins/jetpack/modules/infinite-scroll.php | 2 +- .../jetpack/modules/infinite-scroll/infinity.js | 12 +- .../jetpack/modules/infinite-scroll/infinity.php | 25 +- .../infinite-scroll/themes/twentyeleven.php | 6 +- .../infinite-scroll/themes/twentyfifteen.php | 6 +- .../infinite-scroll/themes/twentyfourteen.php | 4 +- .../infinite-scroll/themes/twentysixteen.php | 6 +- .../modules/infinite-scroll/themes/twentyten.php | 6 +- .../infinite-scroll/themes/twentythirteen.php | 4 +- .../infinite-scroll/themes/twentytwelve.php | 6 +- plugins/jetpack/modules/json-api.php | 5 +- plugins/jetpack/modules/likes.php | 144 +-- plugins/jetpack/modules/manage.php | 13 - plugins/jetpack/modules/manage/confirm-admin.php | 5 + plugins/jetpack/modules/markdown/easy-markdown.php | 6 +- plugins/jetpack/modules/minileven.php | 6 +- plugins/jetpack/modules/minileven/minileven.php | 2 +- .../minileven/theme/pub/minileven/footer.php | 2 +- plugins/jetpack/modules/monitor.php | 3 - plugins/jetpack/modules/notes.php | 23 - plugins/jetpack/modules/post-by-email.php | 7 - plugins/jetpack/modules/protect.php | 10 +- plugins/jetpack/modules/publicize.php | 33 - .../modules/publicize/publicize-jetpack.php | 38 +- plugins/jetpack/modules/publicize/publicize.php | 6 +- plugins/jetpack/modules/related-posts.php | 16 - .../related-posts/jetpack-related-posts.php | 4 +- plugins/jetpack/modules/sharedaddy/sharedaddy.php | 2 +- .../jetpack/modules/sharedaddy/sharing-sources.php | 22 +- plugins/jetpack/modules/sharedaddy/sharing.css | 3 +- plugins/jetpack/modules/sharedaddy/sharing.js | 6 +- plugins/jetpack/modules/shortcodes.php | 7 +- plugins/jetpack/modules/shortcodes/cartodb.php | 21 +- plugins/jetpack/modules/shortcodes/dailymotion.php | 114 +- plugins/jetpack/modules/shortcodes/slideshow.php | 2 +- plugins/jetpack/modules/shortcodes/soundcloud.php | 2 +- plugins/jetpack/modules/shortcodes/vimeo.php | 8 +- plugins/jetpack/modules/shortcodes/wufoo.php | 2 +- plugins/jetpack/modules/shortcodes/youtube.php | 23 +- plugins/jetpack/modules/site-icon.php | 2 - .../modules/site-icon/jetpack-site-icon.php | 2 +- plugins/jetpack/modules/sitemaps/sitemap-xsl.php | 32 +- plugins/jetpack/modules/sso.php | 8 +- .../jetpack/modules/sso/jetpack-sso-login-rtl.css | 6 + .../modules/sso/jetpack-sso-login-rtl.min.css | 2 +- plugins/jetpack/modules/sso/jetpack-sso-login.css | 6 + plugins/jetpack/modules/sso/jetpack-sso-login.js | 2 +- .../jetpack/modules/sso/jetpack-sso-login.min.css | 2 +- plugins/jetpack/modules/stats.php | 23 - plugins/jetpack/modules/subscriptions.php | 18 - .../modules/theme-tools/random-redirect.php | 6 +- .../modules/theme-tools/site-breadcrumbs.php | 70 +- .../modules/tiled-gallery/tiled-gallery.php | 2 +- .../verification-tools/blog-verification-tools.php | 2 +- .../modules/videopress/js/videopress-admin.js | 2 +- plugins/jetpack/modules/widgets/contact-info.php | 72 +- .../widgets/contact-info/contact-info-admin.js | 8 + .../widgets/contact-info/contact-info-map.css | 15 +- .../widgets/contact-info/contact-info-map.js | 41 - .../jetpack/modules/widgets/gravatar-profile.php | 2 +- .../jetpack/modules/widgets/social-media-icons.php | 2 +- plugins/jetpack/modules/widgets/top-posts.php | 2 +- .../jetpack/modules/widgets/top-posts/style.css | 2 +- .../jetpack/modules/widgets/twitter-timeline.php | 2 +- plugins/jetpack/readme.txt | 76 +- plugins/jetpack/sal/class.json-api-links.php | 8 +- plugins/jetpack/sal/class.json-api-post-base.php | 1 - plugins/jetpack/sal/class.json-api-site-base.php | 2 +- .../sal/class.json-api-site-jetpack-base.php | 39 +- .../jetpack/sal/class.json-api-site-jetpack.php | 33 +- .../jetpack/sync/class.jetpack-sync-actions.php | 285 +++++ .../jetpack/sync/class.jetpack-sync-defaults.php | 276 +++++ .../jetpack/sync/class.jetpack-sync-functions.php | 157 +++ .../sync/class.jetpack-sync-json-deflate-codec.php | 58 + .../jetpack/sync/class.jetpack-sync-listener.php | 207 ++++ .../sync/class.jetpack-sync-module-attachments.php | 28 + .../sync/class.jetpack-sync-module-callables.php | 148 +++ .../sync/class.jetpack-sync-module-comments.php | 131 +++ .../sync/class.jetpack-sync-module-constants.php | 124 +++ .../sync/class.jetpack-sync-module-full-sync.php | 289 +++++ .../sync/class.jetpack-sync-module-meta.php | 38 + .../class.jetpack-sync-module-network-options.php | 112 ++ .../sync/class.jetpack-sync-module-options.php | 141 +++ .../sync/class.jetpack-sync-module-plugins.php | 14 + .../sync/class.jetpack-sync-module-posts.php | 135 +++ .../sync/class.jetpack-sync-module-protect.php | 16 + .../sync/class.jetpack-sync-module-stats.php | 28 + .../sync/class.jetpack-sync-module-terms.php | 112 ++ .../sync/class.jetpack-sync-module-themes.php | 71 ++ .../sync/class.jetpack-sync-module-updates.php | 85 ++ .../sync/class.jetpack-sync-module-users.php | 217 ++++ plugins/jetpack/sync/class.jetpack-sync-module.php | 107 ++ .../jetpack/sync/class.jetpack-sync-modules.php | 90 ++ plugins/jetpack/sync/class.jetpack-sync-queue.php | 419 ++++++++ plugins/jetpack/sync/class.jetpack-sync-sender.php | 321 ++++++ plugins/jetpack/sync/class.jetpack-sync-server.php | 106 ++ .../jetpack/sync/class.jetpack-sync-settings.php | 130 +++ plugins/jetpack/sync/class.jetpack-sync-users.php | 81 ++ .../sync/class.jetpack-sync-wp-replicastore.php | 702 +++++++++++++ .../jetpack/sync/interface.jetpack-sync-codec.php | 14 + .../sync/interface.jetpack-sync-replicastore.php | 129 +++ plugins/jetpack/uninstall.php | 10 +- plugins/jetpack/views/admin/my-jetpack-page.php | 1 + 153 files changed, 6405 insertions(+), 2510 deletions(-) create mode 100644 plugins/jetpack/3rd-party/polldaddy.php delete mode 100644 plugins/jetpack/3rd-party/vaultpress.php delete mode 100644 plugins/jetpack/_inc/jquery.jetpack-sync.js delete mode 100644 plugins/jetpack/class.jetpack-sync.php create mode 100644 plugins/jetpack/modules/widgets/contact-info/contact-info-admin.js delete mode 100644 plugins/jetpack/modules/widgets/contact-info/contact-info-map.js create mode 100644 plugins/jetpack/sync/class.jetpack-sync-actions.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-defaults.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-functions.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-json-deflate-codec.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-listener.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-attachments.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-callables.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-comments.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-constants.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-full-sync.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-meta.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-network-options.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-options.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-plugins.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-posts.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-protect.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-stats.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-terms.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-themes.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-updates.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module-users.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-module.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-modules.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-queue.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-sender.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-server.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-settings.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-users.php create mode 100644 plugins/jetpack/sync/class.jetpack-sync-wp-replicastore.php create mode 100644 plugins/jetpack/sync/interface.jetpack-sync-codec.php create mode 100644 plugins/jetpack/sync/interface.jetpack-sync-replicastore.php diff --git a/plugins/jetpack/3rd-party/3rd-party.php b/plugins/jetpack/3rd-party/3rd-party.php index 2bab75b8..117f573b 100644 --- a/plugins/jetpack/3rd-party/3rd-party.php +++ b/plugins/jetpack/3rd-party/3rd-party.php @@ -10,4 +10,6 @@ require_once( JETPACK__PLUGIN_DIR . '3rd-party/wpml.php' ); require_once( JETPACK__PLUGIN_DIR . '3rd-party/bitly.php' ); require_once( JETPACK__PLUGIN_DIR . '3rd-party/bbpress.php' ); require_once( JETPACK__PLUGIN_DIR . '3rd-party/woocommerce.php' ); -require_once( JETPACK__PLUGIN_DIR . '3rd-party/vaultpress.php' ); + +// We can't load this conditionally since polldaddy add the call in class constuctor. +require_once( JETPACK__PLUGIN_DIR . '3rd-party/polldaddy.php' ); diff --git a/plugins/jetpack/3rd-party/bitly.php b/plugins/jetpack/3rd-party/bitly.php index eeca3b7f..d9b744a5 100644 --- a/plugins/jetpack/3rd-party/bitly.php +++ b/plugins/jetpack/3rd-party/bitly.php @@ -2,7 +2,7 @@ /* * Fixes issues with the Official Bitly for WordPress - * http://wordpress.org/plugins/bitly/ + * https://wordpress.org/plugins/bitly/ */ if( class_exists( 'Bitly' ) ) { @@ -10,7 +10,7 @@ if( class_exists( 'Bitly' ) ) { if ( method_exists( $GLOBALS['bitly'], 'og_tags' ) ) { remove_action( 'wp_head', array( $GLOBALS['bitly'], 'og_tags' ) ); } - + add_action( 'wp_head', 'jetpack_bitly_og_tag', 100 ); } diff --git a/plugins/jetpack/3rd-party/polldaddy.php b/plugins/jetpack/3rd-party/polldaddy.php new file mode 100644 index 00000000..ec484829 --- /dev/null +++ b/plugins/jetpack/3rd-party/polldaddy.php @@ -0,0 +1,7 @@ +auto_register_option, $vaultpress->option_name ); -} diff --git a/plugins/jetpack/_inc/jquery.jetpack-sync.js b/plugins/jetpack/_inc/jquery.jetpack-sync.js deleted file mode 100644 index 2bbaa7db..00000000 --- a/plugins/jetpack/_inc/jquery.jetpack-sync.js +++ /dev/null @@ -1,68 +0,0 @@ -/* global ajaxurl */ -jQuery( document ).ready( function($) { - var update = function( cooldown ) { - var self = $( '.jetpack_sync_reindex_control' ), - data; - - self - .find( '.jetpack_sync_reindex_control_action' ) - .attr( 'disabled', true ); - - self - .find( '.jetpack_sync_reindex_control_status' ) - .html( '…' ); - - if ( 'DONE' === self.data( 'status' ) ) { - data = { action:'jetpack-sync-reindex-trigger' }; - } else { - data = { action:'jetpack-sync-reindex-status' }; - } - - $.getJSON( - ajaxurl, - data, - function( response ) { - var self = $( '.jetpack_sync_reindex_control' ), - strings, - status; - - if ( 0 === self.length ) { - return; - } - - strings = self.data( 'strings' ); - status = strings[response.status].status; - - if ( 'INDEXING' === response.status ) { - status += ' (' + Math.floor( 100 * response.posts.imported / response.posts.total ) + '%)'; - } - - self - .data( 'status', response.status ); - - self - .find( '.jetpack_sync_reindex_control_action' ) - .val( strings[response.status].action ); - - self - .find( '.jetpack_sync_reindex_control_status' ) - .text( status ); - - setTimeout( function() { - $( '.jetpack_sync_reindex_control' ) - .find( '.jetpack_sync_reindex_control_action' ) - .attr( 'disabled', false ); - }, cooldown ); - } - ); - }; - - $( '.jetpack_sync_reindex_control' ) - .find( '.jetpack_sync_reindex_control_action' ) - .live( 'click', function( event ) { - event.preventDefault(); - update( 5000 ); - } ); - - update( 1000 ); -} ); diff --git a/plugins/jetpack/_inc/lib/tonesque.php b/plugins/jetpack/_inc/lib/tonesque.php index 8f867a1f..157f65c1 100644 --- a/plugins/jetpack/_inc/lib/tonesque.php +++ b/plugins/jetpack/_inc/lib/tonesque.php @@ -37,27 +37,7 @@ class Tonesque { } public static function imagecreatefromurl( $image_url ) { - // Grab the extension - $file = strtolower( pathinfo( $image_url, PATHINFO_EXTENSION ) ); - $file = explode( '?', $file ); - $file = $file[ 0 ]; - - switch ( $file ) { - case 'gif' : - $image_obj = imagecreatefromgif( $image_url ); - break; - case 'png' : - $image_obj = imagecreatefrompng( $image_url ); - break; - case 'jpg' : - case 'jpeg' : - $image_obj = imagecreatefromjpeg( $image_url ); - break; - default: - return false; - } - - return $image_obj; + return imagecreatefromstring( file_get_contents( $image_url ) ); } /** diff --git a/plugins/jetpack/changelog.txt b/plugins/jetpack/changelog.txt index de01d3b1..ddad9561 100644 --- a/plugins/jetpack/changelog.txt +++ b/plugins/jetpack/changelog.txt @@ -1,5 +1,82 @@ == Changelog == += 4.2.2 = + +* Release date: August 19th, 2016 + +**Bug Fixes:** + +* We fixed the code which displays the Facebook share count to accomodate Facebook's new data structure. +* We fixed an issue which caused PHP notices to get logged for users of the Twenty Fourteen theme. +* We fixed an issue with the Minileven mobile theme which was preventing it from loading. +* Improved Sync performance. +* Increase security by sanitizing a URL used in the SSO process. + += 4.2.1 = + +* Release date: August 17th, 2016 + +**Bug Fixes:** + +* We fixed a conflict between Jetpack and W3 Total Cache. +* We fixed some issues with Publicize and Custom Post Types. +* Very large Multisite networks with lots of users can now be synchronized with WordPress.com. +* We improved the synchronization process between your site and WordPress.com. + += 4.2 = + +* Release date: August 10th, 2016 + +**Performance Enhancements:** + +* We’ve improved Jetpack’s performance by making calls to the database more efficient; essentially, Jetpack is doing less on each page load, making things faster. #4281, #4316 +* We’ve ensured that every feature uses information that is up to date by completely refactoring the way information was synchronized between your site and WordPress.com. +* We've improved the way Jetpack queries for information about features, which results in less overall queries. + +**Exciting Feature and UI Improvements:** + +* We now track your visitor views of Carousel images in stats. +* You can now customize advanced typographic settings like ligatures in the Custom CSS editor with new support for the `font-feature-settings` property. +* We’ve improved the experience when you don’t actually have enough posts to Infinitely Scroll. +* Our Contact Info Widget allows you to enter a Google Maps API Key which is now required by Google if you want to display a map. + +**Security:** + +* We’re continuing our efforts to harden Jetpack security, by implementing the `hash_equals()` function to avoid timing attacks when comparing strings. We also improved security on CSVs exported from your contact form. + +**Slightly Less Exciting Feature Improvements:** + +* The Cartodb shortcode has been changed to match the new product name, Carto. +* The YouTube shortcode now uses the content width defined by the theme when available, even if an embed size was defined in an old version of WordPress. +* Breadcrumbs now support hierarchical post types and taxonomies. +* We’ve added the Portfolio Post Type to the WordPress.com REST API whitelist. +* There are a few new parameters for the Dailymotion shortcode. + +**Improved Compatibility:** + +* We now work well with WP Stagecoach staging sites, so you should not see any future impact on production sites. +* We had some PHP notices popping up in the WooCommerce plugin wizard screen, these are gone. + +**Bug Fixes:** + +* We stopped loading compatibility stylesheets on the default theme's singular views for Infinite Scroll. +* Debug tests forwarded through the contact form in the Jetpack Debug menu are now successfully sent to the support team. +* We’ve removed the PHP notices you might have seen when moderating comments. +* There are no longer PHP notices cropping up when publishing via Cron. +* We’ve fixed the official Sharing buttons so they now line up just right. +* The PHP warnings of Sitemaps stylesheets have been eliminated. +* We’ve done away with the warnings that appeared when Tonesque processes a file which claims to be one filetype, but is actually another. +* We’ve exterminated PHP notices that appeared when using Random Redirect, as well as when the author wasn't set. + += 4.1.1 = + +* Release date: July 7th, 2016 + +**Bug Fixes:** + +* SSO: Use high-resolution Gravatar images on the log-in form on Retina devices. +* Publicize: improve reliability of Publicize when publishing new posts. + = 4.1 = * Release date: July 6th, 2016 @@ -103,7 +180,7 @@ Bug Fixes: * We accidentally removed the ability for Open Graph to select images from slideshows, it’s up and running again. * There was an issue where Open Graph meta tags weren’t being set when your homepage is a “Static Front Page”, it’s working again. * In rare cases when developers were customizing Photon they were seeing a PHP notice when arguments were passed as a string rather than an array. This has been fixed. -* We’ve fixed an issue where Protect’s backup math form wasn’t showing on custom frontend login forms. +* We’ve fixed an issue where Protect’s backup math form wasn’t showing on custom front end login forms. * When setting up WooCommerce you might have seen a Related Posts notice which didn’t belong. We’ve eliminated them. * If you’ve been using our sharing tool with unofficial sharing buttons you might have noticed your sharing numbers were missing. They’re now back. * In unique situations where special characters were used in sitemap stylesheets an error would occur; that has been remedied. @@ -1282,7 +1359,7 @@ Release Post: http://wp.me/p1moTy-oR * Bug Fix: Contact Form - RTL styles * Bug Fix: Contact Form - Better handle MP6 icons * Bug Fix: Custom CSS - array_shift() took a variable by reference, so avoid passing it the result of a function -* Bug Fix: Custom CSS - Allow case-insensitive CSS properties (ref) +* Bug Fix: Custom CSS - Allow case-insensitive CSS properties (ref) * Bug Fix: Infinite Scroll - Maintain main query's `post__not_in` values when querying posts for IS * Bug Fix: Infinite Scroll - Ensure that IS's `pre_get_posts` method isn't applied in the admin. Also fixes an incorrect use of `add_filter()` where `add_action()` was meant. Fixes #1696-plugins * Bug Fix: Infinite Scroll - CSS update - IS footer was too large in Firefox @@ -1475,7 +1552,7 @@ Release Post: http://wp.me/p1moTy-lT Release Date: December 14, 2012 Release Post: http://wp.me/p1moTy-lJ -* Enhancement: Infinite Scroll: support [VideoPress](http://wordpress.org/extend/plugins/video/) plugin. +* Enhancement: Infinite Scroll: support [VideoPress](https://wordpress.org/plugins/video/) plugin. * Enhancement: Photon: Apply to all images retrieved from the Media Library. * Enhancement: Photon: Retina image support. * Enhancement: Custom CSS: Refined editor interface. @@ -1495,7 +1572,7 @@ Release Post: http://wp.me/p1moTy-lu Release Date: November 21, 2012 Release Post: http://wp.me/p1moTy-lc -* Enhancement: Photon: Support for the [Lazy Load](http://wordpress.org/extend/plugins/lazy-load/) plugin. +* Enhancement: Photon: Support for the [Lazy Load](https://wordpress.org/plugins/lazy-load/) plugin. * Bug Fix: Photon: Fix warped images with un- or under-specified dimensions. * Bug Fix: Photon: Fix warped images with pre-photonized URLs; don't try to photonize them twice. * Bug Fix: Infinite Scroll: Check a child theme's parent theme for infinite scroll support. @@ -1552,7 +1629,7 @@ Release Post: http://wp.me/p1moTy-hC * Enhancement: Contact Form: Overhaul of the contact form code to fix incompatibilites with other plugins. * Bug Fix: Only allow users with manage_options permission to enable/disable modules * Bug Fix: Custom CSS: allow '/' in media query units; e.g. (-o-min-device-pixel-ratio: 3/2) -* Bug Fix: Custom CSS: leave comments alone in CSS when editing but minify on the frontend +* Bug Fix: Custom CSS: leave comments alone in CSS when editing but minify on the front end * Bug Fix: Sharing: Keep "more" pane open so Google+ Button isn't obscured * Bug Fix: Carousel: Make sure the original size is used, even when it is exceedingly large. * Bug Fix: Exclude iPad from Twitter on iPhone mobile browsing @@ -1788,7 +1865,7 @@ Release Post: http://wp.me/p1moTy-8x * Enhancement: Stats: More responsive stats dashboard. * Enhancement: Shortcodes: Google Maps, VideoPress * Enhancement: Sharing: Google+, LinkedIn -* Enhancement: Enhanced Distribution: Added Jetpack blogs to http://en.wordpress.com/firehose/ +* Enhancement: Enhanced Distribution: Added Jetpack blogs to https://en.wordpress.com/firehose/ * Bug Fix: Spelling and Grammar: WordPress 3.3 compatibility. * Bug Fix: Translatable module names/descriptinos. * Bug Fix: Correctly detect host's ability to make outgoing HTTPS requests. diff --git a/plugins/jetpack/class.jetpack-admin.php b/plugins/jetpack/class.jetpack-admin.php index 6e46ee38..81a792cc 100644 --- a/plugins/jetpack/class.jetpack-admin.php +++ b/plugins/jetpack/class.jetpack-admin.php @@ -36,14 +36,13 @@ class Jetpack_Admin { if ( isset( $_POST['jetpack-set-master-user'] ) ) { add_action( 'init', array( $this->my_jetpack_page, 'jetpack_my_jetpack_change_user' ) ); } - + // Add hooks for admin menus add_action( 'admin_menu', array( $this->landing_page, 'add_actions' ), 998 ); add_action( 'jetpack_admin_menu', array( $this, 'admin_menu_debugger' ) ); add_action( 'jetpack_admin_menu', array( $this->settings_page, 'add_actions' ) ); add_action( 'jetpack_admin_menu', array( $this->my_jetpack_page, 'add_actions' ) ); - // Add redirect to current page for activation/deactivation of modules add_action( 'jetpack_pre_activate_module', array( $this, 'fix_redirect' ), 10, 2 ); add_action( 'jetpack_pre_deactivate_module', array( $this, 'fix_redirect' ) ); diff --git a/plugins/jetpack/class.jetpack-bbpress-json-api-compat.php b/plugins/jetpack/class.jetpack-bbpress-json-api-compat.php index 3c7935df..0038874b 100644 --- a/plugins/jetpack/class.jetpack-bbpress-json-api-compat.php +++ b/plugins/jetpack/class.jetpack-bbpress-json-api-compat.php @@ -21,11 +21,6 @@ class bbPress_Jetpack_REST_API { } function allow_bbpress_post_types( $allowed_post_types ) { - - // only run for REST API requests - if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) - return $allowed_post_types; - $allowed_post_types[] = 'forum'; $allowed_post_types[] = 'topic'; $allowed_post_types[] = 'reply'; @@ -33,11 +28,6 @@ class bbPress_Jetpack_REST_API { } function allow_bbpress_public_metadata( $allowed_meta_keys ) { - - // only run for REST API requests - if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) - return $allowed_meta_keys; - $allowed_meta_keys[] = '_bbp_forum_id'; $allowed_meta_keys[] = '_bbp_topic_id'; $allowed_meta_keys[] = '_bbp_status'; diff --git a/plugins/jetpack/class.jetpack-cli.php b/plugins/jetpack/class.jetpack-cli.php index 3824411d..6f3fb45f 100644 --- a/plugins/jetpack/class.jetpack-cli.php +++ b/plugins/jetpack/class.jetpack-cli.php @@ -215,7 +215,7 @@ class Jetpack_CLI extends WP_CLI_Command { _e( "Resetting default modules...\n", "jetpack" ); usleep( 500000 ); // Take a breath $default_modules = Jetpack::get_default_modules(); - Jetpack_Options::update_option( 'active_modules', $default_modules ); + Jetpack::update_active_modules( $default_modules ); WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) ); // Jumpstart option is special @@ -224,7 +224,7 @@ class Jetpack_CLI extends WP_CLI_Command { break; case 'modules': $default_modules = Jetpack::get_default_modules(); - Jetpack_Options::update_option( 'active_modules', $default_modules ); + Jetpack::update_active_modules( $default_modules ); WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) ); break; case 'prompt': @@ -306,7 +306,7 @@ class Jetpack_CLI extends WP_CLI_Command { break; case 'activate_all': $modules = Jetpack::get_available_modules(); - Jetpack_Options::update_option( 'active_modules', $modules ); + Jetpack::update_active_modules( $modules ); WP_CLI::success( __( 'All modules activated!', 'jetpack' ) ); break; case 'deactivate': @@ -316,7 +316,7 @@ class Jetpack_CLI extends WP_CLI_Command { WP_CLI::success( sprintf( __( '%s has been deactivated.', 'jetpack' ), $module['name'] ) ); break; case 'deactivate_all': - Jetpack_Options::update_option( 'active_modules', '' ); + Jetpack::delete_active_modules(); WP_CLI::success( __( 'All modules deactivated!', 'jetpack' ) ); break; case 'toggle': diff --git a/plugins/jetpack/class.jetpack-client-server.php b/plugins/jetpack/class.jetpack-client-server.php index 02ea5f64..b71c33ab 100644 --- a/plugins/jetpack/class.jetpack-client-server.php +++ b/plugins/jetpack/class.jetpack-client-server.php @@ -12,8 +12,7 @@ class Jetpack_Client_Server { function client_authorize() { $data = stripslashes_deep( $_GET ); $data['auth_type'] = 'client'; - $jetpack = $this->get_jetpack(); - $role = $jetpack->translate_current_user_to_role(); + $role = Jetpack::translate_current_user_to_role(); $redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : ''; $this->check_admin_referer( "jetpack-authorize_{$role}_{$redirect}" ); @@ -29,6 +28,15 @@ class Jetpack_Client_Server { $this->wp_safe_redirect( Jetpack::admin_url() ); } + /** + * Fires after the Jetpack client is authorized to communicate with WordPress.com. + * + * @since 4.2.0 + * + * @param int Jetpack Blog ID. + */ + do_action( 'jetpack_client_authorized', Jetpack_Options::get_option( 'id' ) ); + $this->do_exit(); } @@ -48,7 +56,7 @@ class Jetpack_Client_Server { update_option( 'jetpack_unique_connection', $jetpack_unique_connection ); //track unique connection - $jetpack = Jetpack::init(); + $jetpack = $this->get_jetpack();; $jetpack->stat( 'connections', 'unique-connection' ); $jetpack->do_stats( 'server_side' ); @@ -58,14 +66,13 @@ class Jetpack_Client_Server { $jetpack_unique_connection['connected'] += 1; Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection ); - $jetpack = $this->get_jetpack(); - $role = $jetpack->translate_current_user_to_role(); + $role = Jetpack::translate_current_user_to_role(); if ( ! $role ) { return new Jetpack_Error( 'no_role', 'Invalid request.', 400 ); } - $cap = $jetpack->translate_role_to_cap( $role ); + $cap = Jetpack::translate_role_to_cap( $role ); if ( ! $cap ) { return new Jetpack_Error( 'no_cap', 'Invalid request.', 400 ); } @@ -116,17 +123,13 @@ class Jetpack_Client_Server { $redirect_on_activation_error = ( 'client' === $data['auth_type'] ) ? true : false; if ( $active_modules = Jetpack_Options::get_option( 'active_modules' ) ) { - Jetpack_Options::delete_option( 'active_modules' ); + Jetpack::delete_active_modules(); Jetpack::activate_default_modules( 999, 1, $active_modules, $redirect_on_activation_error ); } else { Jetpack::activate_default_modules( false, false, array(), $redirect_on_activation_error ); } - - // Sync all registers options and constants - /** This action is documented in class.jetpack.php */ - do_action( 'jetpack_sync_all_registered_options' ); - + // Start nonce cleaner wp_clear_scheduled_hook( 'jetpack_clean_nonces' ); wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' ); @@ -158,8 +161,7 @@ class Jetpack_Client_Server { * @return object|WP_Error */ function get_token( $data ) { - $jetpack = $this->get_jetpack(); - $role = $jetpack->translate_current_user_to_role(); + $role = Jetpack::translate_current_user_to_role(); if ( ! $role ) { return new Jetpack_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack' ) ); @@ -236,11 +238,11 @@ class Jetpack_Client_Server { return new Jetpack_Error( 'scope', 'Malformed Scope', $code ); } - if ( $jetpack->sign_role( $role ) !== $json->scope ) { + if ( Jetpack::sign_role( $role ) !== $json->scope ) { return new Jetpack_Error( 'scope', 'Invalid Scope', $code ); } - if ( ! $cap = $jetpack->translate_role_to_cap( $role ) ) { + if ( ! $cap = Jetpack::translate_role_to_cap( $role ) ) { return new Jetpack_Error( 'scope', 'No Cap', $code ); } diff --git a/plugins/jetpack/class.jetpack-debugger.php b/plugins/jetpack/class.jetpack-debugger.php index aabbff33..b336defa 100644 --- a/plugins/jetpack/class.jetpack-debugger.php +++ b/plugins/jetpack/class.jetpack-debugger.php @@ -18,6 +18,27 @@ class Jetpack_Debugger { } } + static function seconds_to_time( $seconds ) { + $units = array( + "week" => 7*24*3600, + "day" => 24*3600, + "hour" => 3600, + "minute" => 60, + "second" => 1, + ); + // specifically handle zero + if ( $seconds == 0 ) return "0 seconds"; + $human_readable = ""; + foreach ( $units as $name => $divisor ) { + if ( $quot = intval( $seconds / $divisor) ) { + $human_readable .= "$quot $name"; + $human_readable .= ( abs( $quot ) > 1 ? "s" : "" ) . ", "; + $seconds -= $quot * $divisor; + } + } + return substr( $human_readable, 0, -2 ); + } + public static function jetpack_increase_timeout() { return 30; // seconds } @@ -60,6 +81,40 @@ class Jetpack_Debugger { $debug_info .= "\r\n" . esc_html( "SITE_URL: " . site_url() ); $debug_info .= "\r\n" . esc_html( "HOME_URL: " . home_url() ); + $debug_info .= "\r\n"; + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-modules.php'; + $sync_module = Jetpack_Sync_Modules::get_module( 'full-sync' ); + $sync_statuses = $sync_module->get_status(); + $human_readable_sync_status = array(); + foreach( $sync_statuses as $sync_status => $sync_status_value ) { + $human_readable_sync_status[ $sync_status ] = + in_array( $sync_status, array( 'started', 'queue_finished', 'sent_started', 'finished' ) ) + ? date( 'r', $sync_status_value ) : $sync_status_value ; + } + + $debug_info .= "\r\n". sprintf( esc_html__( 'Jetpack Sync Full Status: `%1$s`', 'jetpack' ), print_r( $human_readable_sync_status, 1 ) ); + + $next_schedules = wp_next_scheduled( 'jetpack_sync_full' ); + if( $next_schedules ) { + $debug_info .= "\r\n". sprintf( esc_html__( 'Next Jetpack Full Sync Schedule: `%1$s`', 'jetpack' ), date( 'r', $next_schedules ) ); + } else { + $debug_info .= "\r\n". esc_html__( "Next Jetpack Full Sync Schedule: Not Scheduled", 'jetpack' ); + } + + require_once JETPACK__PLUGIN_DIR. 'sync/class.jetpack-sync-sender.php'; + + $queue = Jetpack_Sync_Sender::get_instance()->get_sync_queue(); + + $debug_info .= "\r\n". sprintf( esc_html__( 'Sync Queue size: %1$s', 'jetpack' ), $queue->size() ); + $debug_info .= "\r\n". sprintf( esc_html__( 'Sync Queue lag: %1$s', 'jetpack' ), self::seconds_to_time( $queue->lag() ) ); + + $full_sync_queue = Jetpack_Sync_Sender::get_instance()->get_full_sync_queue(); + + $debug_info .= "\r\n". sprintf( esc_html__( 'Full Sync Queue size: %1$s', 'jetpack' ), $full_sync_queue->size() ); + $debug_info .= "\r\n". sprintf( esc_html__( 'Full Sync Queue lag: %1$s', 'jetpack' ), self::seconds_to_time( $full_sync_queue->lag() ) ); + + $debug_info .= "\r\n"; + foreach ( array ( 'HTTP_HOST', 'SERVER_PORT', @@ -172,7 +227,7 @@ class Jetpack_Debugger {

    -
  1. known conflicts with Jetpack – check the list. (You can also browse the Jetpack support pages or Jetpack support forum to see if others have experienced and solved the problem.)', 'jetpack' ), 'http://jetpack.com/support/getting-started-with-jetpack/known-issues/', 'http://jetpack.com/support/getting-started-with-jetpack/known-issues/', 'http://jetpack.com/support/', 'http://wordpress.org/support/plugin/jetpack' ); ?>
  2. +
  3. known conflicts with Jetpack – check the list. (You can also browse the Jetpack support pages or Jetpack support forum to see if others have experienced and solved the problem.)', 'jetpack' ), 'http://jetpack.com/support/getting-started-with-jetpack/known-issues/', 'http://jetpack.com/support/getting-started-with-jetpack/known-issues/', 'http://jetpack.com/support/', 'https://wordpress.org/support/plugin/jetpack' ); ?>
  4. @@ -203,11 +258,6 @@ class Jetpack_Debugger {

    %s\'s WordPress.com account.', 'jetpack' ), esc_html( Jetpack::get_master_user_email() ) ); ?>

    -
    -
    style="display:none" > @@ -236,7 +286,7 @@ class Jetpack_Debugger { ?>
    - +
    @@ -264,7 +314,7 @@ class Jetpack_Debugger {
    - +
    @@ -274,7 +324,7 @@ class Jetpack_Debugger {
    @@ -444,7 +478,7 @@ class Jetpack_Debugger { if ( validation_error ) { return false; } - message.val( message.val() + "\r\n\r\n----------------------------------------------\r\n\r\nDEBUG INFO:\r\n" + $('#debug_info').val() ); + message.val( message.val() + "\r\n\r\n----------------------------------------------\r\n\r\nDEBUG INFO:\r\n" + $('#debug_form_info').val() ); return true; }); diff --git a/plugins/jetpack/class.jetpack-heartbeat.php b/plugins/jetpack/class.jetpack-heartbeat.php index 2c1fa4f2..2df8c8d9 100644 --- a/plugins/jetpack/class.jetpack-heartbeat.php +++ b/plugins/jetpack/class.jetpack-heartbeat.php @@ -139,6 +139,10 @@ class Jetpack_Heartbeat { $return["{$prefix}module-{$slug}"] = Jetpack::is_module_active( $slug ) ? 'on' : 'off'; } + require_once dirname(__FILE__).'/sync/class.jetpack-sync-wp-replicastore.php'; + $store = new Jetpack_Sync_WP_Replicastore(); + $return["{$prefix}sync-checksum"] = json_encode( $store->checksum_all() ); + return $return; } diff --git a/plugins/jetpack/class.jetpack-modules-list-table.php b/plugins/jetpack/class.jetpack-modules-list-table.php index ee8d7d16..c99f3bfc 100644 --- a/plugins/jetpack/class.jetpack-modules-list-table.php +++ b/plugins/jetpack/class.jetpack-modules-list-table.php @@ -150,6 +150,9 @@ class Jetpack_Modules_List_Table extends WP_List_Table { continue; } $key = sanitize_title( $title ); + if ( 'centralized-management' === $key && Jetpack::is_module_active( 'manage' ) ) { + continue; + } $display_title = esc_html( wptexturize( $title ) ); $url = esc_url( add_query_arg( 'module_tag', urlencode( $title ) ) ); $current = ''; diff --git a/plugins/jetpack/class.jetpack-options.php b/plugins/jetpack/class.jetpack-options.php index 120063d4..76e440db 100644 --- a/plugins/jetpack/class.jetpack-options.php +++ b/plugins/jetpack/class.jetpack-options.php @@ -23,7 +23,6 @@ class Jetpack_Options { 'wpcc_options', 'relatedposts', 'file_data', - 'security_report', 'autoupdate_plugins', // (array) An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated 'autoupdate_themes', // (array) An array of theme ids ( eg. twentyfourteen ) that should be autoupdated 'autoupdate_core', // (bool) Whether or not to autoupdate core @@ -63,8 +62,6 @@ class Jetpack_Options { 'identity_crisis_whitelist', // (array) An array of options, each having an array of the values whitelisted for it. 'gplus_authors', // (array) The Google+ authorship information for connected users. 'last_heartbeat', // (int) The timestamp of the last heartbeat that fired. - 'last_security_report', // (int) The timestamp of the last security report that was run. - 'sync_bulk_reindexing', // (bool) If a bulk reindex is currently underway. 'jumpstart', // (string) A flag for whether or not to show the Jump Start. Accepts: new_connection, jumpstart_activated, jetpack_action_taken, jumpstart_dismissed. 'hide_jitm' // (array) A list of just in time messages that we should not show because they have been dismissed by the user ); @@ -121,6 +118,26 @@ class Jetpack_Options { return $default; } + /** + * Returns the requested option, and ensures it's autoloaded in the future. + * This does _not_ adjust the prefix in any way (does not prefix jetpack_%) + * + * @param string $name Option name + * @param mixed $default (optional) + * + * @return mixed|void + */ + public static function get_option_and_ensure_autoload( $name, $default ) { + $value = get_option( $name ); + + if ( $value === false && $default !== false ) { + update_option( $name, $default ); + $value = $default; + } + + return $value; + } + private static function update_grouped_option( $group, $name, $value ) { $options = get_option( self::$grouped_options[ $group ] ); if ( ! is_array( $options ) ) { @@ -149,14 +166,7 @@ class Jetpack_Options { */ do_action( 'pre_update_jetpack_option_' . $name, $name, $value ); if ( self::is_valid( $name, 'non_compact' ) ) { - /** - * Allowing update_option to change autoload status only shipped in WordPress v4.2 - * @link https://github.com/WordPress/WordPress/commit/305cf8b95 - */ - if ( version_compare( $GLOBALS['wp_version'], '4.2', '>=' ) ) { - return update_option( "jetpack_$name", $value, $autoload ); - } - return update_option( "jetpack_$name", $value ); + return update_option( "jetpack_$name", $value, $autoload ); } foreach ( array_keys( self::$grouped_options ) as $group ) { diff --git a/plugins/jetpack/class.jetpack-signature.php b/plugins/jetpack/class.jetpack-signature.php index 45c74a12..d8940aaa 100644 --- a/plugins/jetpack/class.jetpack-signature.php +++ b/plugins/jetpack/class.jetpack-signature.php @@ -43,14 +43,14 @@ class Jetpack_Signature { // with SSL termination proxies (self-served, Cloudflare, etc.) don't need to fiddle with // the JETPACK_SIGNATURE__HTTPS_PORT constant. The code also implies we can't talk to a // site at https://example.com:80/ (which would be a strange configuration). - // JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the backend webserver's port + // JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port // if the site is behind a proxy running on port 443 without - // X-Forwarded-Port and the backend's port is *not* 80. It's better, + // X-Forwarded-Port and the back end's port is *not* 80. It's better, // though, to configure the proxy to send X-Forwarded-Port. $port = in_array( $host_port, array( 443, 80, JETPACK_SIGNATURE__HTTPS_PORT ) ) ? '' : $host_port; } else { // 80: Standard Port - // JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the backend webserver's port + // JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port // if the site is behind a proxy running on port 80 without // X-Forwarded-Port. It's better, though, to configure the proxy to // send X-Forwarded-Port. diff --git a/plugins/jetpack/class.jetpack-sync.php b/plugins/jetpack/class.jetpack-sync.php deleted file mode 100644 index 99b10004..00000000 --- a/plugins/jetpack/class.jetpack-sync.php +++ /dev/null @@ -1,1104 +0,0 @@ - array(), 'comments' => array() ); - - // We keep track of all the options registered for sync so that we can sync them all if needed - public $sync_options = array(); - - public $sync_constants = array(); - - // 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. - public $post_transitions = array(); - public $comment_transitions = array(); - - // Objects to sync - public $sync = array(); - - function __construct() { - // WP Cron action. Only used on upgrade - add_action( 'jetpack_sync_all_registered_options', array( $this, 'sync_all_registered_options' ) ); - add_action( 'jetpack_heartbeat', array( $this, 'sync_all_registered_options' ) ); - - // Sync constants on heartbeat and plugin upgrade and connects - add_action( 'init', array( $this, 'register_constants_as_options' ) ); - add_action( 'jetpack_sync_all_registered_options', array( $this, 'sync_all_constants' ) ); - add_action( 'jetpack_heartbeat', array( $this, 'sync_all_constants' ) ); - - add_action( 'jetpack_activate_module', array( $this, 'sync_module_constants' ), 10, 1 ); - } - -/* Static Methods for Modules */ - - /** - * @param string $file __FILE__ - * @param array settings: - * post_types => array( post_type slugs ): The post types to sync. Default: post, page - * post_stati => array( post_status slugs ): The post stati to sync. Default: publish - */ - static function sync_posts( $file, array $settings = null ) { - if ( is_network_admin() ) return; - $jetpack = Jetpack::init(); - $args = func_get_args(); - return call_user_func_array( array( $jetpack->sync, 'posts' ), $args ); - } - - /** - * @param string $file __FILE__ - * @param array settings: - * post_types => array( post_type slugs ): The post types to sync. Default: post, page - * post_stati => array( post_status slugs ): The post stati to sync. Default: publish - * comment_types => array( comment_type slugs ): The comment types to sync. Default: '', comment, trackback, pingback - * comment_stati => array( comment_status slugs ): The comment stati to sync. Default: approved - */ - static function sync_comments( $file, array $settings = null ) { - if ( is_network_admin() ) return; - $jetpack = Jetpack::init(); - $args = func_get_args(); - return call_user_func_array( array( $jetpack->sync, 'comments' ), $args ); - } - - /** - * @param string $file __FILE__ - * @param string $option, Option name to sync - * @param string $option ... - */ - static function sync_options( $file, $option /*, $option, ... */ ) { - if ( is_network_admin() ) return; - $jetpack = Jetpack::init(); - $args = func_get_args(); - return call_user_func_array( array( $jetpack->sync, 'options' ), $args ); - } - /** - * @param string $file __FILE__ - * @param string $option, Option name to sync - * @param string $option ... - */ - static function sync_constant( $file, $constant ) { - if ( is_network_admin() ) return; - $jetpack = Jetpack::init(); - $args = func_get_args(); - return call_user_func_array( array( $jetpack->sync, 'constant' ), $args ); - } - -/* Internal Methods */ - - /** - * Create a sync object/request - * - * @param string $object Type of object to sync -- [ post | comment | option ] - * @param int $id Unique identifier - * @param array $settings - */ - function register( $object, $id = false, array $settings = null ) { - // Since we've registered something for sync, hook it up to execute on shutdown if we haven't already - if ( !$this->sync ) { - if ( function_exists( 'ignore_user_abort' ) ) { - ignore_user_abort( true ); - } - add_action( 'shutdown', array( $this, 'sync' ), 9 ); // Right before async XML-RPC - } - - $defaults = array( - 'on_behalf_of' => array(), // What modules want this data - ); - $settings = wp_parse_args( $settings, $defaults ); - - if ( !isset( $this->sync[$object] ) ) { - $this->sync[$object] = array(); - } - - // Store the settings for this object - if ( - // First time for this object - !isset( $this->sync[$object][$id] ) - ) { - // Easy: store the current settings - $this->sync[$object][$id] = $settings; - } else { - // Not as easy: we have to manually merge the settings from previous runs for this object with the settings for this run - - $this->sync[$object][$id]['on_behalf_of'] = array_unique( array_merge( $this->sync[$object][$id]['on_behalf_of'], $settings['on_behalf_of'] ) ); - } - - $delete_prefix = 'delete_'; - if ( 0 === strpos( $object, $delete_prefix ) ) { - $unset_object = substr( $object, strlen( $delete_prefix ) ); - } else { - $unset_object = "{$delete_prefix}{$object}"; - } - - // Ensure post ... delete_post yields a delete operation - // Ensure delete_post ... post yields a sync post operation - // Ensure update_option() ... delete_option() ends up as a delete - // Ensure delete_option() ... update_option() ends up as an update - // Etc. - unset( $this->sync[$unset_object][$id] ); - - return true; - } - - function get_common_sync_data() { - $available_modules = Jetpack::get_available_modules(); - $active_modules = Jetpack::get_active_modules(); - $modules = array(); - foreach ( $available_modules as $available_module ) { - $modules[$available_module] = in_array( $available_module, $active_modules ); - } - $modules['vaultpress'] = class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ); - - $sync_data = array( - 'modules' => $modules, - 'version' => JETPACK__VERSION, - 'is_multisite' => is_multisite(), - ); - - return $sync_data; - } - - /** - * Set up all the data and queue it for the outgoing XML-RPC request - */ - function sync() { - if ( !$this->sync ) { - return false; - } - - // Don't sync anything from a staging site. - if ( Jetpack::is_development_mode() || Jetpack::is_staging_site() ) { - return false; - } - - $sync_data = $this->get_common_sync_data(); - - $wp_importing = defined( 'WP_IMPORTING' ) && WP_IMPORTING; - - foreach ( $this->sync as $sync_operation_type => $sync_operations ) { - switch ( $sync_operation_type ) { - case 'post': - if ( $wp_importing ) { - break; - } - - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; - $GLOBALS['post'] = null; - foreach ( $sync_operations as $post_id => $settings ) { - $sync_data['post'][$post_id] = $this->get_post( $post_id ); - if ( isset( $this->post_transitions[$post_id] ) ) { - $sync_data['post'][$post_id]['transitions'] = $this->post_transitions[$post_id]; - } else { - $sync_data['post'][$post_id]['transitions'] = array( false, false ); - } - $sync_data['post'][$post_id]['on_behalf_of'] = $settings['on_behalf_of']; - } - $GLOBALS['post'] = $global_post; - unset( $global_post ); - break; - case 'comment': - if ( $wp_importing ) { - break; - } - - $global_comment = isset( $GLOBALS['comment'] ) ? $GLOBALS['comment'] : null; - unset( $GLOBALS['comment'] ); - foreach ( $sync_operations as $comment_id => $settings ) { - $sync_data['comment'][$comment_id] = $this->get_comment( $comment_id ); - if ( isset( $this->comment_transitions[$comment_id] ) ) { - $sync_data['comment'][$comment_id]['transitions'] = $this->comment_transitions[$comment_id]; - } else { - $sync_data['comment'][$comment_id]['transitions'] = array( false, false ); - } - $sync_data['comment'][$comment_id]['on_behalf_of'] = $settings['on_behalf_of']; - } - $GLOBALS['comment'] = $global_comment; - unset( $global_comment ); - break; - case 'option' : - foreach ( $sync_operations as $option => $settings ) { - $sync_data['option'][ $option ] = array( 'value' => get_option( $option ) ); - } - break; - - case 'constant' : - foreach( $sync_operations as $constant => $settings ) { - $sync_data['constant'][ $constant ] = array( 'value' => $this->get_constant( $constant ) ); - } - break; - - case 'delete_post': - case 'delete_comment': - foreach ( $sync_operations as $object_id => $settings ) { - $sync_data[$sync_operation_type][$object_id] = array( 'on_behalf_of' => $settings['on_behalf_of'] ); - } - break; - case 'delete_option' : - foreach ( $sync_operations as $object_id => $settings ) { - $sync_data[$sync_operation_type][$object_id] = true; - } - break; - } - } - Jetpack::xmlrpc_async_call( 'jetpack.syncContent', $sync_data ); - } - - /** - * Format and return content data from a direct xmlrpc request for it. - * - * @param array $content_ids: array( 'posts' => array of ids, 'comments' => array of ids, 'options' => array of options ) - */ - function get_content( $content_ids ) { - $sync_data = $this->get_common_sync_data(); - - if ( isset( $content_ids['posts'] ) ) { - foreach ( $content_ids['posts'] as $id ) { - $sync_data['post'][$id] = $this->get_post( $id ); - } - } - - if ( isset( $content_ids['comments'] ) ) { - foreach ( $content_ids['comments'] as $id ) { - $sync_data['comment'][$id] = $this->get_post( $id ); - } - } - - if ( isset( $content_ids['options'] ) ) { - foreach ( $content_ids['options'] as $option ) { - $sync_data['option'][$option] = array( 'value' => get_option( $option ) ); - } - } - - return $sync_data; - } - - /** - * Helper method for registering a post for sync - * - * @param int $id wp_posts.ID - * @param array $settings Sync data - */ - function register_post( $id, array $settings = null ) { - $id = (int) $id; - if ( !$id ) { - return false; - } - - $post = get_post( $id ); - if ( !$post ) { - return false; - } - - $settings = wp_parse_args( $settings, array( - 'on_behalf_of' => array(), - ) ); - - return $this->register( 'post', $id, $settings ); - } - - /** - * Helper method for registering a comment for sync - * - * @param int $id wp_comments.comment_ID - * @param array $settings Sync data - */ - function register_comment( $id, array $settings = null ) { - $id = (int) $id; - if ( !$id ) { - return false; - } - - $comment = get_comment( $id ); - if ( !$comment || empty( $comment->comment_post_ID ) ) { - return false; - } - - $post = get_post( $comment->comment_post_ID ); - if ( !$post ) { - return false; - } - - $settings = wp_parse_args( $settings, array( - 'on_behalf_of' => array(), - ) ); - - return $this->register( 'comment', $id, $settings ); - } - -/* Posts Sync */ - - function posts( $file, array $settings = null ) { - $module_slug = Jetpack::get_module_slug( $file ); - - $defaults = array( - 'post_types' => array( 'post', 'page' ), - 'post_stati' => array( 'publish' ), - ); - - $this->sync_conditions['posts'][$module_slug] = wp_parse_args( $settings, $defaults ); - - add_action( 'transition_post_status', array( $this, 'transition_post_status_action' ), 10, 3 ); - add_action( 'delete_post', array( $this, 'delete_post_action' ) ); - } - - function delete_post_action( $post_id ) { - $post = get_post( $post_id ); - if ( !$post ) { - return $this->register( 'delete_post', (int) $post_id ); - } - - $this->transition_post_status_action( 'delete', $post->post_status, $post ); - } - - function transition_post_status_action( $new_status, $old_status, $post ) { - $sync = $this->get_post_sync_operation( $new_status, $old_status, $post, $this->sync_conditions['posts'] ); - if ( !$sync ) { - // No module wants to sync this post - return false; - } - - // Track post transitions - if ( isset( $this->post_transitions[$post->ID] ) ) { - // status changed more than once - keep tha most recent $new_status - $this->post_transitions[$post->ID][0] = $new_status; - } else { - $this->post_transitions[$post->ID] = array( $new_status, $old_status ); - } - - $operation = $sync['operation']; - unset( $sync['operation'] ); - - switch ( $operation ) { - case 'delete' : - return $this->register( 'delete_post', (int) $post->ID, $sync ); - case 'submit' : - return $this->register_post( (int) $post->ID, $sync ); - } - } - - function get_post_sync_operation( $new_status, $old_status, $post, $module_conditions ) { - $delete_on_behalf_of = array(); - $submit_on_behalf_of = array(); - $delete_stati = array( 'delete' ); - $cache_cleared = false; - - foreach ( $module_conditions as $module => $conditions ) { - if ( !in_array( $post->post_type, $conditions['post_types'] ) ) { - continue; - } - - $deleted_post = in_array( $new_status, $delete_stati ); - - if ( $deleted_post ) { - $delete_on_behalf_of[] = $module; - } else { - if ( ! $cache_cleared ) { - // inefficient to clear cache more than once - clean_post_cache( $post->ID ); - $cache_cleared = true; - } - $new_status = get_post_status( $post->ID ); // Inherited status is resolved here - } - - $old_status_in_stati = in_array( $old_status, $conditions['post_stati'] ); - $new_status_in_stati = in_array( $new_status, $conditions['post_stati'] ); - - if ( $old_status_in_stati && !$new_status_in_stati ) { - // Jetpack no longer needs the post - if ( !$deleted_post ) { - $delete_on_behalf_of[] = $module; - } // else, we've already flagged it above - continue; - } - - if ( !$new_status_in_stati ) { - continue; - } - - // At this point, we know we want to sync the post, not delete it - $submit_on_behalf_of[] = $module; - } - - if ( !empty( $submit_on_behalf_of ) ) { - return array( 'operation' => 'submit', 'on_behalf_of' => $submit_on_behalf_of ); - } - - if ( !empty( $delete_on_behalf_of ) ) { - return array( 'operation' => 'delete', 'on_behalf_of' => $delete_on_behalf_of ); - } - - return false; - } - - /** - * Get a post and associated data in the standard JP format. - * Cannot be called statically - * - * @param int $id Post ID - * @return Array containing full post details - */ - function get_post( $id ) { - $post_obj = get_post( $id ); - if ( !$post_obj ) - return false; - - if ( is_callable( $post_obj, 'to_array' ) ) { - // WP >= 3.5 - $post = $post_obj->to_array(); - } else { - // WP < 3.5 - $post = get_object_vars( $post_obj ); - } - - if ( 0 < strlen( $post['post_password'] ) ) { - $post['post_password'] = 'auto-' . wp_generate_password( 10, false ); // We don't want the real password. Just pass something random. - } - - // local optimizations - unset( - $post['filter'], - $post['ancestors'], - $post['post_content_filtered'], - $post['to_ping'], - $post['pinged'] - ); - - if ( $this->is_post_public( $post ) ) { - $post['post_is_public'] = Jetpack_Options::get_option( 'public' ); - } else { - //obscure content - $post['post_content'] = ''; - $post['post_excerpt'] = ''; - $post['post_is_public'] = false; - } - $post_type_obj = get_post_type_object( $post['post_type'] ); - $post['post_is_excluded_from_search'] = $post_type_obj->exclude_from_search; - - $post['tax'] = array(); - $taxonomies = get_object_taxonomies( $post_obj ); - foreach ( $taxonomies as $taxonomy ) { - $terms = get_object_term_cache( $post_obj->ID, $taxonomy ); - if ( empty( $terms ) ) - $terms = wp_get_object_terms( $post_obj->ID, $taxonomy ); - $term_names = array(); - foreach ( $terms as $term ) { - $term_names[] = $term->name; - } - $post['tax'][$taxonomy] = $term_names; - } - - $meta = get_post_meta( $post_obj->ID, false ); - $post['meta'] = array(); - foreach ( $meta as $key => $value ) { - $post['meta'][$key] = array_map( 'maybe_unserialize', $value ); - } - - $post['extra'] = array( - 'author' => get_the_author_meta( 'display_name', $post_obj->post_author ), - 'author_email' => get_the_author_meta( 'email', $post_obj->post_author ), - 'dont_email_post_to_subs' => get_post_meta( $post_obj->ID, '_jetpack_dont_email_post_to_subs', true ), - ); - - if ( $fid = get_post_thumbnail_id( $id ) ) { - $feature = wp_get_attachment_image_src( $fid, 'large' ); - if ( ! empty( $feature[0] ) ) { - $post['extra']['featured_image'] = $feature[0]; - } - - $attachment = get_post( $fid ); - if ( ! empty( $attachment ) ) { - $metadata = wp_get_attachment_metadata( $fid ); - - $post['extra']['post_thumbnail'] = array( - 'ID' => (int) $fid, - 'URL' => (string) wp_get_attachment_url( $fid ), - 'guid' => (string) $attachment->guid, - 'mime_type' => (string) $attachment->post_mime_type, - 'width' => (int) isset( $metadata['width'] ) ? $metadata['width'] : 0, - 'height' => (int) isset( $metadata['height'] ) ? $metadata['height'] : 0, - ); - - if ( isset( $metadata['duration'] ) ) { - $post['extra']['post_thumbnail'] = (int) $metadata['duration']; - } - - /** - * Filters the Post Thumbnail information returned for a specific post. - * - * @since 3.3.0 - * - * @param array $post['extra']['post_thumbnail'] { - * Array of details about the Post Thumbnail. - * @param int ID Post Thumbnail ID. - * @param string URL Post thumbnail URL. - * @param string guid Post thumbnail guid. - * @param string mime_type Post thumbnail mime type. - * @param int width Post thumbnail width. - * @param int height Post thumbnail height. - * } - */ - $post['extra']['post_thumbnail'] = (object) apply_filters( 'get_attachment', $post['extra']['post_thumbnail'] ); - } - } - - $post['permalink'] = get_permalink( $post_obj->ID ); - $post['shortlink'] = wp_get_shortlink( $post_obj->ID ); - /** - * Allow modules to send extra info on the sync post process. - * - * @since 2.8.0 - * - * @param array $args Array of custom data to attach to a post. - * @param Object $post_obj Object returned by get_post() for a given post ID. - */ - $post['module_custom_data'] = apply_filters( 'jetpack_sync_post_module_custom_data', array(), $post_obj ); - return $post; - } - - /** - * Decide whether a post/page/attachment is visible to the public. - * - * @param array $post - * @return bool - */ - function is_post_public( $post ) { - if ( !is_array( $post ) ) { - $post = (array) $post; - } - - if ( 0 < strlen( $post['post_password'] ) ) - return false; - if ( ! in_array( $post['post_type'], get_post_types( array( 'public' => true ) ) ) ) - return false; - $post_status = get_post_status( $post['ID'] ); // Inherited status is resolved here. - if ( ! in_array( $post_status, get_post_stati( array( 'public' => true ) ) ) ) - return false; - return true; - } - -/* Comments Sync */ - - function comments( $file, array $settings = null ) { - $module_slug = Jetpack::get_module_slug( $file ); - - $defaults = array( - 'post_types' => array( 'post', 'page' ), // For what post types will we sync comments? - 'post_stati' => array( 'publish' ), // For what post stati will we sync comments? - 'comment_types' => array( '', 'comment', 'trackback', 'pingback' ), // What comment types will we sync? - 'comment_stati' => array( 'approved' ), // What comment stati will we sync? - ); - - $settings = wp_parse_args( $settings, $defaults ); - - $this->sync_conditions['comments'][$module_slug] = $settings; - - add_action( 'wp_insert_comment', array( $this, 'wp_insert_comment_action' ), 10, 2 ); - add_action( 'transition_comment_status', array( $this, 'transition_comment_status_action' ), 10, 3 ); - add_action( 'edit_comment', array( $this, 'edit_comment_action' ) ); - } - - /* - * This is really annoying. If you edit a comment, but don't change the status, WordPress doesn't fire the transition_comment_status hook. - * 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. - */ - function edit_comment_action( $comment_id ) { - $comment = get_comment( $comment_id ); - $new_status = $this->translate_comment_status( $comment->comment_approved ); - add_action( "comment_{$new_status}_{$comment->comment_type}", array( $this, 'transition_comment_status_for_comments_whose_status_does_not_change' ), 10, 2 ); - } - - function wp_insert_comment_action( $comment_id, $comment ) { - $this->transition_comment_status_action( $comment->comment_approved, 'new', $comment ); - } - - function transition_comment_status_for_comments_whose_status_does_not_change( $comment_id, $comment ) { - if ( isset( $this->comment_transitions[$comment_id] ) ) { - return $this->transition_comment_status_action( $comment->comment_approved, $this->comment_transitions[$comment_id][1], $comment ); - } - - return $this->transition_comment_status_action( $comment->comment_approved, $comment->comment_approved, $comment ); - } - - function translate_comment_status( $status ) { - switch ( (string) $status ) { - case '0' : - case 'hold' : - return 'unapproved'; - case '1' : - case 'approve' : - return 'approved'; - } - - return $status; - } - - function transition_comment_status_action( $new_status, $old_status, $comment ) { - $post = get_post( $comment->comment_post_ID ); - if ( !$post ) { - return false; - } - - foreach ( array( 'new_status', 'old_status' ) as $_status ) { - $$_status = $this->translate_comment_status( $$_status ); - } - - // Track comment transitions - if ( isset( $this->comment_transitions[$comment->comment_ID] ) ) { - // status changed more than once - keep tha most recent $new_status - $this->comment_transitions[$comment->comment_ID][0] = $new_status; - } else { - $this->comment_transitions[$comment->comment_ID] = array( $new_status, $old_status ); - } - - $post_sync = $this->get_post_sync_operation( $post->post_status, '_jetpack_test_sync', $post, $this->sync_conditions['comments'] ); - - if ( !$post_sync ) { - // No module wants to sync this comment because its post doesn't match any sync conditions - return false; - } - - if ( 'delete' == $post_sync['operation'] ) { - // Had we been looking at post sync operations (instead of comment sync operations), - // this comment's post would have been deleted. Don't sync the comment. - return false; - } - - $delete_on_behalf_of = array(); - $submit_on_behalf_of = array(); - $delete_stati = array( 'delete' ); - - foreach ( $this->sync_conditions['comments'] as $module => $conditions ) { - if ( !in_array( $comment->comment_type, $conditions['comment_types'] ) ) { - continue; - } - - $deleted_comment = in_array( $new_status, $delete_stati ); - - if ( $deleted_comment ) { - $delete_on_behalf_of[] = $module; - } - - $old_status_in_stati = in_array( $old_status, $conditions['comment_stati'] ); - $new_status_in_stati = in_array( $new_status, $conditions['comment_stati'] ); - - if ( $old_status_in_stati && !$new_status_in_stati ) { - // Jetpack no longer needs the comment - if ( !$deleted_comment ) { - $delete_on_behalf_of[] = $module; - } // else, we've already flagged it above - continue; - } - - if ( !$new_status_in_stati ) { - continue; - } - - // At this point, we know we want to sync the comment, not delete it - $submit_on_behalf_of[] = $module; - } - - if ( ! empty( $submit_on_behalf_of ) ) { - $this->register_post( $comment->comment_post_ID, array( 'on_behalf_of' => $submit_on_behalf_of ) ); - return $this->register_comment( $comment->comment_ID, array( 'on_behalf_of' => $submit_on_behalf_of ) ); - } - - if ( !empty( $delete_on_behalf_of ) ) { - return $this->register( 'delete_comment', $comment->comment_ID, array( 'on_behalf_of' => $delete_on_behalf_of ) ); - } - - return false; - } - - /** - * Get a comment and associated data in the standard JP format. - * Cannot be called statically - * - * @param int $id Comment ID - * @return Array containing full comment details - */ - function get_comment( $id ) { - $comment_obj = get_comment( $id ); - if ( !$comment_obj ) - return false; - $comment = get_object_vars( $comment_obj ); - - $meta = get_comment_meta( $id, false ); - $comment['meta'] = array(); - foreach ( $meta as $key => $value ) { - $comment['meta'][$key] = array_map( 'maybe_unserialize', $value ); - } - - return $comment; - } - -/* Options Sync */ - - /* Ah... so much simpler than Posts and Comments :) */ - function options( $file, $option /*, $option, ... */ ) { - $options = func_get_args(); - $file = array_shift( $options ); - - $module_slug = Jetpack::get_module_slug( $file ); - - if ( !isset( $this->sync_options[$module_slug] ) ) { - $this->sync_options[$module_slug] = array(); - } - - foreach ( $options as $option ) { - $this->sync_options[$module_slug][] = $option; - add_action( "delete_option_{$option}", array( $this, 'deleted_option_action' ) ); - add_action( "update_option_{$option}", array( $this, 'updated_option_action' ) ); - add_action( "add_option_{$option}", array( $this, 'added_option_action' ) ); - } - - $this->sync_options[$module_slug] = array_unique( $this->sync_options[$module_slug] ); - } - - function deleted_option_action( $option ) { - $this->register( 'delete_option', $option ); - } - - function updated_option_action() { - // The value of $option isn't passed to the filter - // Calculate it - $option = current_filter(); - $prefix = 'update_option_'; - if ( 0 !== strpos( $option, $prefix ) ) { - return; - } - $option = substr( $option, strlen( $prefix ) ); - - $this->added_option_action( $option ); - } - - function added_option_action( $option ) { - $this->register( 'option', $option ); - } - - function sync_all_module_options( $module_slug ) { - if ( empty( $this->sync_options[$module_slug] ) ) { - return; - } - - foreach ( $this->sync_options[$module_slug] as $option ) { - $this->added_option_action( $option ); - } - } - - function sync_all_registered_options() { - if ( 'jetpack_sync_all_registered_options' == current_filter() ) { - add_action( 'shutdown', array( $this, 'register_all_options' ), 8 ); - } else { - wp_schedule_single_event( time(), 'jetpack_sync_all_registered_options', array( $this->sync_options ) ); - } - } - - /** - * All the options that are defined in modules as well as class.jetpack.php will get synced. - * Registers all options to be synced. - */ - function register_all_options() { - $all_registered_options = array_unique( call_user_func_array( 'array_merge', $this->sync_options ) ); - foreach ( $all_registered_options as $option ) { - $this->added_option_action( $option ); - } - } - -/* Constants Sync */ - - function get_all_constants() { - return array( - 'EMPTY_TRASH_DAYS', - 'WP_POST_REVISIONS', - 'AUTOMATIC_UPDATER_DISABLED', - 'ABSPATH', - 'WP_CONTENT_DIR', - 'FS_METHOD', - 'DISALLOW_FILE_EDIT', - 'DISALLOW_FILE_MODS', - 'WP_AUTO_UPDATE_CORE', - 'WP_HTTP_BLOCK_EXTERNAL', - 'WP_ACCESSIBLE_HOSTS', - ); - } - /** - * This lets us get the constant value like get_option( 'jetpack_constant_CONSTANT' ); - * Not the best way to get the constant value but necessery in some cases like in the API. - */ - function register_constants_as_options() { - foreach( $this->get_all_constants() as $constant ) { - add_filter( 'pre_option_jetpack_constant_'. $constant, array( $this, 'get_default_constant' ) ); - } - } - - function sync_all_constants() { - // add the constant to sync. - foreach( $this->get_all_constants() as $constant ) { - $this->register_constant( $constant ); - } - add_action( 'shutdown', array( $this, 'register_all_module_constants' ), 8 ); - } - - /** - * Returns default values of Constants - */ - function default_constant( $constant ) { - switch( $constant ) { - case 'WP_AUTO_UPDATE_CORE': - return 'minor'; - break; - - default: - return null; - break; - } - } - - function register_all_module_constants() { - // also add the contstants from each module to be synced. - foreach( $this->sync_constants as $module ) { - foreach( $module as $constant ) { - $this->register_constant( $constant ); - } - } - } - - /** - * Sync constants required by the module that was just activated. - * If you add Jetpack_Sync::sync_constant( __FILE__, 'HELLO_WORLD' ); - * to the module it will start syncing the constant after the constant has been updated. - * - * This function gets called on module activation. - */ - function sync_module_constants( $module ) { - - if ( isset( $this->sync_constants[ $module ] ) && is_array( $this->sync_constants[ $module ] ) ) { - // also add the contstants from each module to be synced. - foreach( $this->sync_constants[ $module ] as $constant ) { - $this->register_constant( $constant ); - } - } - } - - public function reindex_needed() { - return ( $this->_get_post_count_local() != $this->_get_post_count_cloud() ); - } - - public function reindex_trigger() { - $response = array( 'status' => 'ERROR' ); - - // Force a privacy check - Jetpack::check_privacy( JETPACK__PLUGIN_FILE ); - - Jetpack::load_xml_rpc_client(); - $client = new Jetpack_IXR_Client( array( - 'user_id' => JETPACK_MASTER_USER, - ) ); - - $client->query( 'jetpack.reindexTrigger' ); - - if ( !$client->isError() ) { - $response = $client->getResponse(); - Jetpack_Options::update_option( 'sync_bulk_reindexing', true ); - } - - return $response; - } - - public function reindex_status() { - $response = array( 'status' => 'ERROR' ); - - // Assume reindexing is done if it was not triggered in the first place - if ( false === Jetpack_Options::get_option( 'sync_bulk_reindexing' ) ) { - return array( 'status' => 'DONE' ); - } - - Jetpack::load_xml_rpc_client(); - $client = new Jetpack_IXR_Client( array( - 'user_id' => JETPACK_MASTER_USER, - ) ); - - $client->query( 'jetpack.reindexStatus' ); - - if ( !$client->isError() ) { - $response = $client->getResponse(); - if ( 'DONE' == $response['status'] ) { - Jetpack_Options::delete_option( 'sync_bulk_reindexing' ); - } - } - - return $response; - } - - public function reindex_ui() { - $strings = json_encode( array( - 'WAITING' => array( - 'action' => __( 'Refresh Status', 'jetpack' ), - 'status' => __( 'Indexing request queued and waiting…', 'jetpack' ), - ), - 'INDEXING' => array( - 'action' => __( 'Refresh Status', 'jetpack' ), - 'status' => __( 'Indexing posts', 'jetpack' ), - ), - 'DONE' => array( - 'action' => __( 'Reindex Posts', 'jetpack' ), - 'status' => __( 'Posts indexed.', 'jetpack' ), - ), - 'ERROR' => array( - 'action' => __( 'Refresh Status', 'jetpack' ), - 'status' => __( 'Status unknown.', 'jetpack' ), - ), - 'ERROR:LARGE' => array( - 'action' => __( 'Refresh Status', 'jetpack' ), - 'status' => __( 'This site is too large, please contact Jetpack support to sync.', 'jetpack' ), - ), - ) ); - - wp_enqueue_script( - 'jetpack_sync_reindex_control', - plugins_url( '_inc/jquery.jetpack-sync.js', JETPACK__PLUGIN_FILE ), - array( 'jquery' ), - JETPACK__VERSION - ); - - $template = << - - -

    -EOT; - - return sprintf( - $template, - esc_attr( $strings ), - esc_attr__( 'Refresh Status', 'jetpack' ) - ); - } - - private function _get_post_count_local() { - global $wpdb; - return (int) $wpdb->get_var( - "SELECT count(*) - FROM {$wpdb->posts} - WHERE post_status = 'publish' AND post_password = ''" - ); - } - - private function _get_post_count_cloud() { - $blog_id = Jetpack::init()->get_option( 'id' ); - - $body = array( - 'size' => 1, - ); - - $response = wp_remote_post( - "https://public-api.wordpress.com/rest/v1/sites/$blog_id/search", - array( - 'timeout' => 10, - 'user-agent' => 'jetpack_related_posts', - 'sslverify' => true, - 'body' => $body, - ) - ); - - if ( is_wp_error( $response ) ) { - return 0; - } - - $results = json_decode( wp_remote_retrieve_body( $response ), true ); - - return isset( $results['results'] ) && isset( $results['results']['total'] ) ? (int) $results['results']['total'] : 0; - } - - /** - * Sometimes we need to fake options to be able to sync data with .com - * This is a helper function. That will make it easier to do just that. - * - * It will make sure that the options are synced when do_action( 'jetpack_sync_all_registered_options' ); - * - * Which should happen everytime we update Jetpack to a new version or daily by Jetpack_Heartbeat. - * - * $callback is a function that is passed into a filter that returns the value of the option. - * This value should never be false. Since we want to short circuit the get_option function - * to return the value of the our callback. - * - * You can also trigger an update when a something else changes by calling the - * do_action( 'add_option_jetpack_' . $option, 'jetpack_'.$option, $callback_function ); - * on the action that should that would trigger the update. - * - * - * @param string $option Option will always be prefixed with Jetpack and be saved on .com side - * @param string or array $callback - */ - function mock_option( $option , $callback ) { - add_filter( 'pre_option_jetpack_'. $option, $callback ); - // This shouldn't happen but if it does we return the same as before. - add_filter( 'option_jetpack_'. $option, $callback ); - // Instead of passing a file we just pass in a string. - $this->options( 'mock-option' , 'jetpack_' . $option ); - - } - /** - * Sometimes you need to sync constants to .com - * Using the function will allow you to do just that. - * - * @param 'string' $constant Constants defined in code. - * - */ - function register_constant( $constant ) { - $this->register( 'constant', $constant ); - } - - function get_default_constant() { - $filter = current_filter(); - // We don't know what the constant is so we get it from the current filter. - if ( 'pre_option_jetpack_constant_' === substr( $filter, 0, 28 ) ) { - $constant = substr( $filter, 28 ); - if ( defined( $constant ) ) { - // If constant is set to false we will not shortcut the get_option function and will return the default value. - // Hance we set it to null. Which in most cases would produce the same result. - return false === constant( $constant ) ? null : constant( $constant ); - } - return $this->default_constant( $constant ); - } - } - /** - * Simular to $this->options() function. - * Add the constant to be synced to .com when we activate the module. - * As well as on heartbeat and plugin upgrade and connection to .com. - * - * @param string $file - * @param string $constant - */ - function constant( $file, $constant ) { - $constants = func_get_args(); - $file = array_shift( $constants ); - - $module_slug = Jetpack::get_module_slug( $file ); - - if ( ! isset( $this->sync_constants[ $module_slug ] ) ) { - $this->sync_constants[ $module_slug ] = array(); - } - - foreach ( $constants as $constant ) { - $this->sync_constants[ $module_slug ][] = $constant; - } - } - - /** - * Helper function to return the constants value. - * - * @param string $constant - * @return value of the constant or null if the constant is set to false or doesn't exits. - */ - static function get_constant( $constant ) { - if ( defined( $constant ) ) { - return constant( $constant ); - } - - return null; - } -} diff --git a/plugins/jetpack/class.jetpack-twitter-cards.php b/plugins/jetpack/class.jetpack-twitter-cards.php index 3e99ba17..e3096179 100644 --- a/plugins/jetpack/class.jetpack-twitter-cards.php +++ b/plugins/jetpack/class.jetpack-twitter-cards.php @@ -178,7 +178,7 @@ class Jetpack_Twitter_Cards { } static function site_tag() { - $site_tag = get_option( 'jetpack-twitter-cards-site-tag' ); + $site_tag = Jetpack_Options::get_option_and_ensure_autoload( 'jetpack-twitter-cards-site-tag', '' ); if ( empty( $site_tag ) ) { if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { return 'wordpressdotcom'; diff --git a/plugins/jetpack/class.jetpack-user-agent.php b/plugins/jetpack/class.jetpack-user-agent.php index 64aea25c..490d85e2 100644 --- a/plugins/jetpack/class.jetpack-user-agent.php +++ b/plugins/jetpack/class.jetpack-user-agent.php @@ -1,47 +1,89 @@ false, 'dumb' => false, 'any' => false ); - static $first_run = true; + static $kinds = array( 'smart' => false, 'dumb' => false, 'any' => false ); + static $first_run = true; static $matched_agent = ''; + // If an invalid kind is passed in, reset it to default. + if ( ! isset( $kinds[ $kind ] ) ) { + $kind = 'any'; + } + + if ( function_exists( 'apply_filters' ) ) { + /** + * Filter the value of jetpack_is_mobile before it is calculated. + * + * Passing a truthy value to the filter will short-circuit determining the + * mobile type, returning the passed value instead. + * + * @since 4.2.0 + * + * @param bool|string $matches Boolean if current UA matches $kind or not. If + * $return_matched_agent is true, should return the UA string + * @param string $kind Category of mobile device being checked + * @param bool $return_matched_agent Boolean indicating if the UA should be returned + */ + $pre = apply_filters( 'pre_jetpack_is_mobile', null, $kind, $return_matched_agent ); + + if ( null !== $pre ) { + return $pre; + } + } + $ua_info = new Jetpack_User_Agent_Info(); - if ( empty( $_SERVER['HTTP_USER_AGENT'] ) || strpos( strtolower( $_SERVER['HTTP_USER_AGENT'] ), 'ipad' ) ) + if ( empty( $_SERVER['HTTP_USER_AGENT'] ) || strpos( strtolower( $_SERVER['HTTP_USER_AGENT'] ), 'ipad' ) ) { return false; + } // Remove Samsung Galaxy tablets (SCH-I800) from being mobile devices - if ( strpos( strtolower( $_SERVER['HTTP_USER_AGENT'] ) , 'sch-i800') ) + if ( strpos( strtolower( $_SERVER['HTTP_USER_AGENT'] ) , 'sch-i800') ) { return false; + } - if( $ua_info->is_android_tablet() && $ua_info->is_kindle_touch() === false ) + if( $ua_info->is_android_tablet() && $ua_info->is_kindle_touch() === false ) { return false; + } - if( $ua_info->is_blackberry_tablet() ) + if( $ua_info->is_blackberry_tablet() ) { return false; + } if ( $first_run ) { $first_run = false; //checks for iPhoneTier devices & RichCSS devices if ( $ua_info->isTierIphone() || $ua_info->isTierRichCSS() ) { - $kinds['smart'] = true; - $matched_agent = $ua_info->matched_agent; + $kinds['smart'] = true; + $matched_agent = $ua_info->matched_agent; } - if ( !$kinds['smart'] ) { + if ( ! $kinds['smart'] ) { // if smart, we are not dumb so no need to check $dumb_agents = $ua_info->dumb_agents; - $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] ); + $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] ); + foreach ( $dumb_agents as $dumb_agent ) { if ( false !== strpos( $agent, $dumb_agent ) ) { $kinds['dumb'] = true; $matched_agent = $dumb_agent; + break; } } - if ( !$kinds['dumb'] ) { + if ( ! $kinds['dumb'] ) { if ( isset( $_SERVER['HTTP_X_WAP_PROFILE'] ) ) { $kinds['dumb'] = true; $matched_agent = 'http_x_wap_profile'; @@ -52,14 +94,32 @@ function jetpack_is_mobile( $kind = 'any', $return_matched_agent = false ) { } } - if ( $kinds['dumb'] || $kinds['smart'] ) + if ( $kinds['dumb'] || $kinds['smart'] ) { $kinds['any'] = true; + } } - if ( $return_matched_agent ) - return $matched_agent; + $value = $kinds[ $kind ]; + + if ( $return_matched_agent ) { + $value = $matched_agent; + } + + if ( function_exists( 'apply_filters' ) ) { + /** + * Filter the value of jetpack_is_mobile + * + * @since 4.2.0 + * + * @param bool|string $matches Boolean if current UA matches $kind or not. If + * $return_matched_agent is true, should return the UA string + * @param string $kind Category of mobile device being checked + * @param bool $return_matched_agent Boolean indicating if the UA should be returned + */ + $value = apply_filters( 'jetpack_is_mobile', $value, $kind, $return_matched_agent ); + } - return $kinds[$kind]; + return $value; } class Jetpack_User_Agent_Info { diff --git a/plugins/jetpack/class.jetpack-xmlrpc-server.php b/plugins/jetpack/class.jetpack-xmlrpc-server.php index 9ecad85b..9f3ba88a 100644 --- a/plugins/jetpack/class.jetpack-xmlrpc-server.php +++ b/plugins/jetpack/class.jetpack-xmlrpc-server.php @@ -28,10 +28,6 @@ class Jetpack_XMLRPC_Server { 'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ), 'jetpack.featuresAvailable' => array( $this, 'features_available' ), 'jetpack.featuresEnabled' => array( $this, 'features_enabled' ), - 'jetpack.getPost' => array( $this, 'get_post' ), - 'jetpack.getPosts' => array( $this, 'get_posts' ), - 'jetpack.getComment' => array( $this, 'get_comment' ), - 'jetpack.getComments' => array( $this, 'get_comments' ), 'jetpack.disconnectBlog' => array( $this, 'disconnect_blog' ), 'jetpack.unlinkUser' => array( $this, 'unlink_user' ), ) ); @@ -301,7 +297,7 @@ class Jetpack_XMLRPC_Server { 'code' => (string) $api_user_code, ) ), $jetpack_token->secret ); - if ( $hmac !== $verify ) { + if ( ! hash_equals( $hmac, $verify ) ) { return false; } @@ -359,54 +355,6 @@ class Jetpack_XMLRPC_Server { return $modules; } - function get_post( $id ) { - if ( !$id = (int) $id ) { - return false; - } - - $jetpack = Jetpack::init(); - - $post = $jetpack->sync->get_post( $id ); - return $post; - } - - function get_posts( $args ) { - list( $post_ids ) = $args; - $post_ids = array_map( 'intval', (array) $post_ids ); - $jp = Jetpack::init(); - $sync_data = $jp->sync->get_content( array( 'posts' => $post_ids ) ); - - return $sync_data; - } - - function get_comment( $id ) { - if ( !$id = (int) $id ) { - return false; - } - - $jetpack = Jetpack::init(); - - $comment = $jetpack->sync->get_comment( $id ); - if ( !is_array( $comment ) ) - return false; - - $post = $jetpack->sync->get_post( $comment['comment_post_ID'] ); - if ( !$post ) { - return false; - } - - return $comment; - } - - function get_comments( $args ) { - list( $comment_ids ) = $args; - $comment_ids = array_map( 'intval', (array) $comment_ids ); - $jp = Jetpack::init(); - $sync_data = $jp->sync->get_content( array( 'comments' => $comment_ids ) ); - - return $sync_data; - } - function update_attachment_parent( $args ) { $attachment_id = (int) $args[0]; $parent_id = (int) $args[1]; diff --git a/plugins/jetpack/class.jetpack.php b/plugins/jetpack/class.jetpack.php index 19ad2637..c93bfab6 100644 --- a/plugins/jetpack/class.jetpack.php +++ b/plugins/jetpack/class.jetpack.php @@ -71,7 +71,7 @@ class Jetpack { 'latex' => array( 'wp-latex/wp-latex.php', 'WP LaTeX' ) ); - public $capability_translations = array( + static $capability_translations = array( 'administrator' => 'manage_options', 'editor' => 'edit_others_posts', 'author' => 'publish_posts', @@ -196,6 +196,8 @@ class Jetpack { 'add-meta-tags/add-meta-tags.php', // Add Meta Tags 'autodescription/autodescription.php', // The SEO Framework 'easy-facebook-share-thumbnails/esft.php', // Easy Facebook Share Thumbnail + 'heateor-open-graph-meta-tags/heateor-open-graph-meta-tags.php', + // Open Graph Meta Tags by Heateor 'facebook/facebook.php', // Facebook (official plugin) 'facebook-awd/AWD_facebook.php', // Facebook AWD All in one 'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php', @@ -278,13 +280,6 @@ class Jetpack { */ public $stats = array(); - /** - * Allows us to build a temporary security report - * - * @var array - */ - static $security_report = array(); - /** * Jetpack_Sync object */ @@ -316,7 +311,6 @@ class Jetpack { self::$instance = new Jetpack; self::$instance->plugin_upgrade(); - } return self::$instance; @@ -334,30 +328,83 @@ class Jetpack { $unfiltered_modules = Jetpack::get_active_modules(); $modules = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) ); if ( array_diff( $unfiltered_modules, $modules ) ) { - Jetpack_Options::update_option( 'active_modules', $modules ); + Jetpack::update_active_modules( $modules ); } - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) ); - /** - * Fires when synchronizing all registered options and constants. - * - * @since 3.3.0 - */ - do_action( 'jetpack_sync_all_registered_options' ); + // Reset cached module data + Jetpack_Options::delete_option( 'file_data' ); + add_action( 'init', array( __CLASS__, 'activate_new_modules' ) ); Jetpack::maybe_set_version_option(); } } } static function activate_manage( ) { - if ( did_action( 'init' ) || current_filter() == 'init' ) { self::activate_module( 'manage', false, false ); } else if ( ! has_action( 'init' , array( __CLASS__, 'activate_manage' ) ) ) { add_action( 'init', array( __CLASS__, 'activate_manage' ) ); } + } + + static function update_active_modules( $modules ) { + $current_modules = Jetpack_Options::get_option( 'active_modules', array() ); + + $success = Jetpack_Options::update_option( 'active_modules', array_unique( $modules ) ); + + if ( is_array( $modules ) && is_array( $current_modules ) ) { + $new_active_modules = array_diff( $modules, $current_modules ); + foreach( $new_active_modules as $module ) { + /** + * Fires when a specific module is activated. + * + * @since 1.9.0 + * + * @param string $module Module slug. + * @param boolean $success whether the module was activated. @since 4.2 + */ + do_action( 'jetpack_activate_module', $module, $success ); + + /** + * Fires when a module is activated. + * The dynamic part of the filter, $module, is the module slug. + * + * @since 1.9.0 + * + * @param string $module Module slug. + */ + do_action( "jetpack_activate_module_$module", $module ); + } + + $new_deactive_modules = array_diff( $current_modules, $modules ); + foreach( $new_deactive_modules as $module ) { + /** + * Fired after a module has been deactivated. + * + * @since 4.2.0 + * + * @param string $module Module slug. + * @param boolean $success whether the module was deactivated. + */ + do_action( 'jetpack_deactivate_module', $module, $success ); + /** + * Fires when a module is deactivated. + * The dynamic part of the filter, $module, is the module slug. + * + * @since 1.9.0 + * + * @param string $module Module slug. + */ + do_action( "jetpack_deactivate_module_$module", $module ); + } + } + + return $success; + } + static function delete_active_modules() { + self::update_active_modules( array() ); } /** @@ -369,19 +416,6 @@ class Jetpack { */ add_action( 'init', array( $this, 'deprecated_hooks' ) ); - /** - * We need sync object even in Multisite mode - */ - $this->sync = new Jetpack_Sync; - - /** - * Trigger a wp_version sync when updating WP versions - **/ - add_action( 'upgrader_process_complete', array( 'Jetpack', 'update_get_wp_version' ), 10, 2 ); - $this->sync->mock_option( 'wp_version', array( 'Jetpack', 'get_wp_version' ) ); - - add_action( 'init', array( $this, 'sync_update_data') ); - add_action( 'init', array( $this, 'sync_theme_data' ) ); /* * Load things that should only be in Network Admin. @@ -392,85 +426,8 @@ class Jetpack { */ if( is_multisite() ) { Jetpack_Network::init(); - - // Only sync this info if we are on a multi site - // @since 3.7 - $this->sync->mock_option( 'network_name', array( 'Jetpack', 'network_name' ) ); - $this->sync->mock_option( 'network_allow_new_registrations', array( 'Jetpack', 'network_allow_new_registrations' ) ); - $this->sync->mock_option( 'network_add_new_users', array( 'Jetpack', 'network_add_new_users' ) ); - $this->sync->mock_option( 'network_site_upload_space', array( 'Jetpack', 'network_site_upload_space' ) ); - $this->sync->mock_option( 'network_upload_file_types', array( 'Jetpack', 'network_upload_file_types' ) ); - $this->sync->mock_option( 'network_enable_administration_menus', array( 'Jetpack', 'network_enable_administration_menus' ) ); - - if( is_network_admin() ) { - // Sync network site data if it is updated or not. - add_action( 'update_wpmu_options', array( $this, 'update_jetpack_network_settings' ) ); - return; // End here to prevent single site actions from firing - } - } - - - $theme_slug = get_option( 'stylesheet' ); - - - // Modules should do Jetpack_Sync::sync_options( __FILE__, $option, ... ); instead - // We access the "internal" method here only because the Jetpack object isn't instantiated yet - $this->sync->options( - JETPACK__PLUGIN_DIR . 'jetpack.php', - 'home', - 'siteurl', - 'blogname', - 'gmt_offset', - 'timezone_string', - 'security_report', - 'stylesheet', - "theme_mods_{$theme_slug}", - 'jetpack_sync_non_public_post_stati', - 'jetpack_options', - 'site_icon', // (int) - ID of core's Site Icon attachment ID - 'default_post_format', - 'default_category', - 'large_size_w', - 'large_size_h', - 'thumbnail_size_w', - 'thumbnail_size_h', - 'medium_size_w', - 'medium_size_h', - 'thumbnail_crop', - 'image_default_link_type' - ); - - foreach( Jetpack_Options::get_option_names( 'non-compact' ) as $option ) { - $this->sync->options( __FILE__, 'jetpack_' . $option ); } - /** - * Sometimes you want to sync data to .com without adding options to .org sites. - * The mock option allows you to do just that. - */ - $this->sync->mock_option( 'is_main_network', array( $this, 'is_main_network_option' ) ); - $this->sync->mock_option( 'is_multi_site', array( $this, 'is_multisite' ) ); - $this->sync->mock_option( 'main_network_site', array( $this, 'jetpack_main_network_site_option' ) ); - $this->sync->mock_option( 'single_user_site', array( 'Jetpack', 'is_single_user_site' ) ); - $this->sync->mock_option( 'stat_data', array( $this, 'get_stat_data' ) ); - - $this->sync->mock_option( 'has_file_system_write_access', array( 'Jetpack', 'file_system_write_access' ) ); - $this->sync->mock_option( 'is_version_controlled', array( 'Jetpack', 'is_version_controlled' ) ); - $this->sync->mock_option( 'max_upload_size', 'wp_max_upload_size' ); - $this->sync->mock_option( 'content_width', array( 'Jetpack', 'get_content_width' ) ); - - /** - * Trigger an update to the main_network_site when we update the blogname of a site. - * - */ - add_action( 'update_option_siteurl', array( $this, 'update_jetpack_main_network_site_option' ) ); - - add_action( 'update_option', array( $this, 'log_settings_change' ), 10, 3 ); - - // Update the settings everytime the we register a new user to the site or we delete a user. - add_action( 'user_register', array( $this, 'is_single_user_site_invalidate' ) ); - add_action( 'deleted_user', array( $this, 'is_single_user_site_invalidate' ) ); - // Unlink user before deleting the user from .com add_action( 'deleted_user', array( $this, 'unlink_user' ), 10, 1 ); add_action( 'remove_user_from_blog', array( $this, 'unlink_user' ), 10, 1 ); @@ -533,9 +490,6 @@ class Jetpack { // Filter the dashboard meta box order to swap the new one in in place of the old one. add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) ); - add_action( 'wp_ajax_jetpack-sync-reindex-trigger', array( $this, 'sync_reindex_trigger' ) ); - add_action( 'wp_ajax_jetpack-sync-reindex-status', array( $this, 'sync_reindex_status' ) ); - // returns HTTPS support status add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) ); @@ -562,12 +516,12 @@ class Jetpack { add_action( 'customize_controls_enqueue_scripts', array( $this, 'devicepx' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'devicepx' ) ); - add_action( 'jetpack_activate_module', array( $this, 'activate_module_actions' ) ); - add_action( 'plugins_loaded', array( $this, 'extra_oembed_providers' ), 100 ); add_action( 'jetpack_notices', array( $this, 'show_development_mode_notice' ) ); + add_action( 'jetpack_notices', array( $this, 'show_sync_lag_notice' ) ); + /** * These actions run checks to load additional files. * They check for external files or plugins, so they need to run as late as possible. @@ -598,33 +552,6 @@ class Jetpack { add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles` } - // Sync Core Icon: Detect changes in Core's Site Icon and make it syncable. - add_action( 'add_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) ); - add_action( 'update_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) ); - add_action( 'delete_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) ); - add_action( 'jetpack_heartbeat', array( $this, 'jetpack_sync_core_icon' ) ); - - } - - /* - * Make sure any site icon added to core can get - * synced back to dotcom, so we can display it there. - */ - function jetpack_sync_core_icon() { - if ( function_exists( 'get_site_icon_url' ) ) { - $url = get_site_icon_url(); - } else { - return; - } - - require_once( JETPACK__PLUGIN_DIR . 'modules/site-icon/site-icon-functions.php' ); - // If there's a core icon, maybe update the option. If not, fall back to Jetpack's. - if ( ! empty( $url ) && $url !== jetpack_site_icon_url() ) { - // This is the option that is synced with dotcom - Jetpack_Options::update_option( 'site_icon_url', $url ); - } else if ( empty( $url ) && did_action( 'delete_option_site_icon' ) ) { - Jetpack_Options::delete_option( 'site_icon_url' ); - } } function jetpack_admin_ajax_tracks_callback() { @@ -930,13 +857,7 @@ class Jetpack { // Don't let anyone authenticate $_COOKIE = array(); remove_all_filters( 'authenticate' ); - - /** - * For the moment, remove Limit Login Attempts if its xmlrpc for Jetpack. - * If Limit Login Attempts is installed as a mu-plugin, it can occasionally - * generate false-positives. - */ - remove_filter( 'wp_login_failed', 'limit_login_failed' ); + remove_all_actions( 'wp_login_failed' ); if ( Jetpack::is_active() ) { // Allow Jetpack authentication @@ -1003,7 +924,7 @@ class Jetpack { jetpack_register_genericons(); /** - * Register the social logos + * Register the social logos */ require_once( JETPACK__PLUGIN_DIR . '_inc/social-logos.php' ); jetpack_register_social_logos(); @@ -1200,51 +1121,15 @@ class Jetpack { * @return null */ function update_jetpack_main_network_site_option() { - // do_action( 'add_option_$option', '$option', '$value-of-the-option' ); - /** - * Fires when the site URL is updated. - * Determines if the site is the main site of a Mulitiste network. - * - * @since 3.3.0 - * - * @param string jetpack_main_network_site. - * @param string network_site_url() Site URL for the "main" site of the current Multisite network. - */ - do_action( 'add_option_jetpack_main_network_site', 'jetpack_main_network_site', network_site_url() ); - /** - * Fires when the site URL is updated. - * Determines if the is part of a multi network. - * - * @since 3.3.0 - * - * @param string jetpack_is_main_network. - * @param bool Jetpack::is_multi_network() Is the site part of a multi network. - */ - do_action( 'add_option_jetpack_is_main_network', 'jetpack_is_main_network', (string) (bool) Jetpack::is_multi_network() ); - /** - * Fires when the site URL is updated. - * Determines if the site is part of a multisite network. - * - * @since 3.4.0 - * - * @param string jetpack_is_multi_site. - * @param bool is_multisite() Is the site part of a mutlisite network. - */ - do_action( 'add_option_jetpack_is_multi_site', 'jetpack_is_multi_site', (string) (bool) is_multisite() ); + _deprecated_function( __METHOD__, 'jetpack-4.2' ); } /** * Triggered after a user updates the network settings via Network Settings Admin Page * */ function update_jetpack_network_settings() { + _deprecated_function( __METHOD__, 'jetpack-4.2' ); // Only sync this info for the main network site. - do_action( 'add_option_jetpack_network_name', 'jetpack_network_name', Jetpack::network_name() ); - do_action( 'add_option_jetpack_network_allow_new_registrations', 'jetpack_network_allow_new_registrations', Jetpack::network_allow_new_registrations() ); - do_action( 'add_option_jetpack_network_add_new_users', 'jetpack_network_add_new_users', Jetpack::network_add_new_users() ); - do_action( 'add_option_jetpack_network_site_upload_space', 'jetpack_network_site_upload_space', Jetpack::network_site_upload_space() ); - do_action( 'add_option_jetpack_network_upload_file_types', 'jetpack_network_upload_file_types', Jetpack::network_upload_file_types() ); - do_action( 'add_option_jetpack_network_enable_administration_menus', 'jetpack_network_enable_administration_menus', Jetpack::network_enable_administration_menus() ); - } /** @@ -1253,13 +1138,9 @@ class Jetpack { * @return bool */ public static function is_single_user_site() { - - $user_query = new WP_User_Query( array( - 'blog_id' => get_current_blog_id(), - 'fields' => 'ID', - 'number' => 2 - ) ); - return 1 === (int) $user_query->get_total(); + global $wpdb; + $some_users = $wpdb->get_var( "select count(*) from (select user_id from $wpdb->usermeta where meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) as someusers" ); + return 1 === (int) $some_users; } /** @@ -1292,17 +1173,8 @@ class Jetpack { * @return string ( '1' | '0' ) **/ public static function is_version_controlled() { - - if ( !class_exists( 'WP_Automatic_Updater' ) ) { - require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); - } - $updater = new WP_Automatic_Updater(); - $is_version_controlled = strval( $updater->is_vcs_checkout( $context = ABSPATH ) ); - // transients should not be empty - if ( empty( $is_version_controlled ) ) { - $is_version_controlled = '0'; - } - return $is_version_controlled; + _deprecated_function( __METHOD__, 'jetpack-4.2', 'Jetpack_Sync_Functions::is_version_controlled' ); + return (string) (int) Jetpack_Sync_Functions::is_version_controlled(); } /** @@ -1310,59 +1182,10 @@ class Jetpack { * @return string ( '1' | '0' ) */ public static function featured_images_enabled() { + _deprecated_function( __METHOD__, 'jetpack-4.2' ); return current_theme_supports( 'post-thumbnails' ) ? '1' : '0'; } - /* - * Sync back wp_version - */ - public static function get_wp_version() { - global $wp_version; - return $wp_version; - } - - /** - * Keeps wp_version in sync with .com when WordPress core updates - **/ - public static function update_get_wp_version( $update, $meta_data ) { - if ( 'update' === $meta_data['action'] && 'core' === $meta_data['type'] ) { - /** This action is documented in wp-includes/option.php */ - /** - * This triggers the sync for the jetpack version - * See Jetpack_Sync options method for more info. - */ - do_action( 'add_option_jetpack_wp_version', 'jetpack_wp_version', (string) Jetpack::get_wp_version() ); - } - } - - /** - * Triggers a sync of update counts and update details - */ - function sync_update_data() { - // Anytime WordPress saves update data, we'll want to sync update data - add_action( 'set_site_transient_update_plugins', array( 'Jetpack', 'refresh_update_data' ) ); - add_action( 'set_site_transient_update_themes', array( 'Jetpack', 'refresh_update_data' ) ); - add_action( 'set_site_transient_update_core', array( 'Jetpack', 'refresh_update_data' ) ); - // Anytime a connection to jetpack is made, sync the update data - add_action( 'jetpack_site_registered', array( 'Jetpack', 'refresh_update_data' ) ); - // Anytime the Jetpack Version changes, sync the the update data - add_action( 'updating_jetpack_version', array( 'Jetpack', 'refresh_update_data' ) ); - - if ( current_user_can( 'update_core' ) && current_user_can( 'update_plugins' ) && current_user_can( 'update_themes' ) ) { - $this->sync->mock_option( 'updates', array( 'Jetpack', 'get_updates' ) ); - } - - $this->sync->mock_option( 'update_details', array( 'Jetpack', 'get_update_details' ) ); - } - - /** - * Triggers a sync of information specific to the current theme. - */ - function sync_theme_data() { - add_action( 'switch_theme', array( 'Jetpack', 'refresh_theme_data' ) ); - $this->sync->mock_option( 'featured_images_enabled', array( 'Jetpack', 'featured_images_enabled' ) ); - } - /** * jetpack_updates is saved in the following schema: * @@ -1404,58 +1227,12 @@ class Jetpack { } public static function refresh_update_data() { - if ( current_user_can( 'update_core' ) && current_user_can( 'update_plugins' ) && current_user_can( 'update_themes' ) ) { - /** - * Fires whenever the amount of updates needed for a site changes. - * Syncs an array that includes the number of theme, plugin, and core updates available, as well as the latest core version available. - * - * @since 3.7.0 - * - * @param string jetpack_updates - * @param array Update counts calculated by Jetpack::get_updates - */ - do_action( 'add_option_jetpack_updates', 'jetpack_updates', Jetpack::get_updates() ); - } - /** - * Fires whenever the amount of updates needed for a site changes. - * Syncs an array of core, theme, and plugin data, and which of each is out of date - * - * @since 3.7.0 - * - * @param string jetpack_update_details - * @param array Update details calculated by Jetpack::get_update_details - */ - do_action( 'add_option_jetpack_update_details', 'jetpack_update_details', Jetpack::get_update_details() ); - } + _deprecated_function( __METHOD__, 'jetpack-4.2' ); - public static function refresh_theme_data() { - /** - * Fires whenever a theme change is made. - * - * @since 3.8.1 - * - * @param string featured_images_enabled - * @param boolean Whether featured images are enabled or not - */ - do_action( 'add_option_jetpack_featured_images_enabled', 'jetpack_featured_images_enabled', Jetpack::featured_images_enabled() ); } - /** - * Invalides the transient as well as triggers the update of the mock option. - * - * @return null - */ - function is_single_user_site_invalidate() { - /** - * Fires when a user is added or removed from a site. - * Determines if the site is a single user site. - * - * @since 3.4.0 - * - * @param string jetpack_single_user_site. - * @param bool Jetpack::is_single_user_site() Is the current site a single user site. - */ - do_action( 'update_option_jetpack_single_user_site', 'jetpack_single_user_site', (bool) Jetpack::is_single_user_site() ); + public static function refresh_theme_data() { + _deprecated_function( __METHOD__, 'jetpack-4.2' ); } /** @@ -1537,6 +1314,25 @@ class Jetpack { } } + public static function show_sync_lag_notice() { + if ( ! Jetpack::is_active() && Jetpack::is_staging_site() && Jetpack::is_development_mode() ) { + return; + } + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-listener.php'; + $listener = Jetpack_Sync_Listener::get_instance(); + $queue = $listener->get_sync_queue(); + + if ( ! $listener->can_add_to_queue( $queue ) ) { // Display notice if the lag is large then 24 hours. + $contact_url = admin_url( "admin.php?page=jetpack-debugger&contact=1¬e=Jetpack is not able to talk to WordPress.com." ); + $notice = sprintf( + __( 'Oh no! Jetpack is unable to communicate with WordPress.com. This affects a number of features you may be using. Please check your server logs for errors and contact Jetpack support.', 'jetpack' ), + esc_url( $contact_url ) + ); + echo '

    ' . $notice . '

    '; + } + } + /** * Whether Jetpack's version maps to a public release, or a development version. */ @@ -1552,6 +1348,7 @@ class Jetpack { if ( ! $user_id ) { return false; } + return (bool) Jetpack_Data::get_access_token( $user_id ); } @@ -1623,47 +1420,8 @@ class Jetpack { * Synchronize connected user role changes */ function user_role_change( $user_id ) { - if ( Jetpack::is_active() && Jetpack::is_user_connected( $user_id ) ) { - $current_user_id = get_current_user_id(); - wp_set_current_user( $user_id ); - $role = $this->translate_current_user_to_role(); - $signed_role = $this->sign_role( $role ); - wp_set_current_user( $current_user_id ); - - $master_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER ); - $master_user_id = absint( $master_token->external_user_id ); - - if ( ! $master_user_id ) - return; // this shouldn't happen - - Jetpack::xmlrpc_async_call( 'jetpack.updateRole', $user_id, $signed_role ); - //@todo retry on failure - - //try to choose a new master if we're demoting the current one - if ( $user_id == $master_user_id && 'administrator' != $role ) { - $query = new WP_User_Query( - array( - 'fields' => array( 'id' ), - 'role' => 'administrator', - 'orderby' => 'id', - 'exclude' => array( $master_user_id ), - ) - ); - $new_master = false; - foreach ( $query->results as $result ) { - $uid = absint( $result->id ); - if ( $uid && Jetpack::is_user_connected( $uid ) ) { - $new_master = $uid; - break; - } - } - - if ( $new_master ) { - Jetpack_Options::update_option( 'master_user', $new_master ); - } - // else disconnect..? - } - } + _deprecated_function( __METHOD__, 'jetpack-4.2', 'Jetpack_Sync_Users::user_role_change()' ); + Jetpack_Sync_Users::user_role_change( $user_id ); } /** @@ -1729,7 +1487,7 @@ class Jetpack { if ( ! @include( Jetpack::get_module_path( $module ) ) ) { unset( $modules[ $index ] ); - Jetpack_Options::update_option( 'active_modules', array_values( $modules ) ); + self::update_active_modules( array_values( $modules ) ); continue; } @@ -1810,7 +1568,8 @@ class Jetpack { if ( ! function_exists( 'get_plugins' ) ) { require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); } - $all_plugins = get_plugins(); + /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */ + $all_plugins = apply_filters( 'all_plugins', get_plugins() ); $active_plugins = Jetpack::get_active_plugins(); $plugins = array(); @@ -1936,53 +1695,6 @@ class Jetpack { } } - - - - /* - * - * Jetpack Security Reports - * - * Allowed types: login_form, backup, file_scanning, spam - * - * Args for login_form and spam: 'blocked'=>(int)(optional), 'status'=>(string)(ok, warning, error), 'message'=>(optional, disregarded if status is ok, allowed tags: a, em, strong) - * - * Args for backup and file_scanning: 'last'=>(timestamp)(optional), 'next'=>(timestamp)(optional), 'status'=>(string)(ok, warning, error), 'message'=>(optional, disregarded if status is ok, allowed tags: a, em, strong) - * - * - * Example code to submit a security report: - * - * function akismet_submit_jetpack_security_report() { - * Jetpack::submit_security_report( 'spam', __FILE__, $args = array( 'blocked' => 138284, status => 'ok' ) ); - * } - * add_action( 'jetpack_security_report', 'akismet_submit_jetpack_security_report' ); - * - */ - - - /** - * Calls for security report submissions. - * - * @return null - */ - public static function perform_security_reporting() { - $no_check_needed = get_site_transient( 'security_report_performed_recently' ); - - if ( $no_check_needed ) { - return; - } - - /** - * Fires before a security report is created. - * - * @since 3.4.0 - */ - do_action( 'jetpack_security_report' ); - - Jetpack_Options::update_option( 'security_report', self::$security_report ); - set_site_transient( 'security_report_performed_recently', 1, 15 * MINUTE_IN_SECONDS ); - } - /** * Allows plugins to submit security reports. * @@ -1991,79 +1703,9 @@ class Jetpack { * @param array $args See definitions above */ public static function submit_security_report( $type = '', $plugin_file = '', $args = array() ) { - - if( !doing_action( 'jetpack_security_report' ) ) { - return new WP_Error( 'not_collecting_report', 'Not currently collecting security reports. Please use the jetpack_security_report hook.' ); - } - - if( !is_string( $type ) || !is_string( $plugin_file ) ) { - return new WP_Error( 'invalid_security_report', 'Invalid Security Report' ); - } - - if( !function_exists( 'get_plugin_data' ) ) { - include( ABSPATH . 'wp-admin/includes/plugin.php' ); - } - - //Get rid of any non-allowed args - $args = array_intersect_key( $args, array_flip( array( 'blocked', 'last', 'next', 'status', 'message' ) ) ); - - $plugin = get_plugin_data( $plugin_file ); - - if ( !$plugin['Name'] ) { - return new WP_Error( 'security_report_missing_plugin_name', 'Invalid Plugin File Provided' ); - } - - // Sanitize everything to make sure we're not syncing something wonky - $type = sanitize_key( $type ); - - $args['plugin'] = $plugin; - - // Cast blocked, last and next as integers. - // Last and next should be in unix timestamp format - if ( isset( $args['blocked'] ) ) { - $args['blocked'] = (int) $args['blocked']; - } - if ( isset( $args['last'] ) ) { - $args['last'] = (int) $args['last']; - } - if ( isset( $args['next'] ) ) { - $args['next'] = (int) $args['next']; - } - if ( !in_array( $args['status'], array( 'ok', 'warning', 'error' ) ) ) { - $args['status'] = 'ok'; - } - if ( isset( $args['message'] ) ) { - - if( $args['status'] == 'ok' ) { - unset( $args['message'] ); - } - - $allowed_html = array( - 'a' => array( - 'href' => array(), - 'title' => array() - ), - 'em' => array(), - 'strong' => array(), - ); - - $args['message'] = wp_kses( $args['message'], $allowed_html ); - } - - $plugin_name = $plugin[ 'Name' ]; - - self::$security_report[ $type ][ $plugin_name ] = $args; - } - - /** - * Collects a new report if needed, then returns it. - */ - public function get_security_report() { - self::perform_security_reporting(); - return Jetpack_Options::get_option( 'security_report' ); + _deprecated_function( __FUNCTION__, 'jetpack-4.2', null ); } - /* Jetpack Options API */ public static function get_option_names( $type = 'compact' ) { @@ -2539,23 +2181,14 @@ class Jetpack { public static function get_file_data( $file, $headers ) { //Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated $file_name = basename( $file ); - $file_data_option = Jetpack_Options::get_option( 'file_data', array() ); - $key = md5( $file_name . serialize( $headers ) ); - $refresh_cache = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 ); + $file_data = Jetpack_Options::get_option( 'file_data', array() ); - // If we don't need to refresh the cache, and already have the value, short-circuit! - if ( ! $refresh_cache && isset( $file_data_option[ JETPACK__VERSION ][ $key ] ) ) { - return $file_data_option[ JETPACK__VERSION ][ $key ]; + if ( ! array_key_exists( $file_name, $file_data ) ) { + $file_data[ $file_name ] = get_file_data( $file, $headers ); + Jetpack_Options::update_option( 'file_data', $file_data ); } - $data = get_file_data( $file, $headers ); - - // Strip out any old Jetpack versions that are cluttering the option. - $file_data_option = array_intersect_key( (array) $file_data_option, array( JETPACK__VERSION => null ) ); - $file_data_option[ JETPACK__VERSION ][ $key ] = $data; - Jetpack_Options::update_option( 'file_data', $file_data_option ); - - return $data; + return $file_data[ $file_name ]; } /** @@ -2717,7 +2350,7 @@ class Jetpack { foreach ( $modules as $module ) { if ( did_action( "jetpack_module_loaded_$module" ) ) { $active[] = $module; - Jetpack_Options::update_option( 'active_modules', array_unique( $active ) ); + self::update_active_modules( $active ); continue; } @@ -2749,14 +2382,7 @@ class Jetpack { Jetpack::state( 'module', $module ); ob_start(); require $file; - /** - * Fires when a specific module is activated. - * - * @since 1.9.0 - * - * @param string $module Module slug. - */ - do_action( 'jetpack_activate_module', $module ); + $active[] = $module; $state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules'; if ( $active_state = Jetpack::state( $state ) ) { @@ -2766,7 +2392,8 @@ class Jetpack { } $active_state[] = $module; Jetpack::state( $state, implode( ',', $active_state ) ); - Jetpack_Options::update_option( 'active_modules', array_unique( $active ) ); + Jetpack::update_active_modules( $active ); + ob_end_clean(); } Jetpack::state( 'error', false ); @@ -2841,10 +2468,10 @@ class Jetpack { Jetpack::catch_errors( true ); ob_start(); require Jetpack::get_module_path( $module ); - /** This action is documented in class.jetpack.php */ - do_action( 'jetpack_activate_module', $module ); + $active[] = $module; - Jetpack_Options::update_option( 'active_modules', array_unique( $active ) ); + Jetpack::update_active_modules( $active ); + Jetpack::state( 'error', false ); // the override Jetpack::state( 'message', 'module_activated' ); Jetpack::state( 'module', $module ); @@ -2871,17 +2498,7 @@ class Jetpack { } function activate_module_actions( $module ) { - /** - * Fires when a module is activated. - * The dynamic part of the filter, $module, is the module slug. - * - * @since 1.9.0 - * - * @param string $module Module slug. - */ - do_action( "jetpack_activate_module_$module", $module ); - - $this->sync->sync_all_module_options( $module ); + _deprecated_function( __METHOD__, 'jeptack-4.2' ); } public static function deactivate_module( $module ) { @@ -2899,16 +2516,6 @@ class Jetpack { $active = Jetpack::get_active_modules(); $new = array_filter( array_diff( $active, (array) $module ) ); - /** - * Fires when a module is deactivated. - * The dynamic part of the filter, $module, is the module slug. - * - * @since 1.9.0 - * - * @param string $module Module slug. - */ - do_action( "jetpack_deactivate_module_$module", $module ); - // A flag for Jump Start so it's not shown again. if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) { Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' ); @@ -2919,7 +2526,7 @@ class Jetpack { $jetpack->do_stats( 'server_side' ); } - return Jetpack_Options::update_option( 'active_modules', array_unique( $new ) ); + return self::update_active_modules( $new ); } public static function enable_module_configurable( $module ) { @@ -3108,6 +2715,10 @@ p { Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection ); + // Delete all the sync related data. Since it could be taking up space. + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php'; + Jetpack_Sync_Sender::get_instance()->uninstall(); + // Disable the Heartbeat cron Jetpack_Heartbeat::init()->deactivate(); } @@ -3281,17 +2892,23 @@ p { /** * Return stat data for WPCOM sync */ - function get_stat_data() { + public static function get_stat_data( $encode = true ) { $heartbeat_data = Jetpack_Heartbeat::generate_stats_array(); - $additional_data = $this->get_additional_stat_data(); + $additional_data = self::get_additional_stat_data(); + + $merged_data = array_merge( $heartbeat_data, $additional_data ); - return json_encode( array_merge( $heartbeat_data, $additional_data ) ); + if ( $encode ) { + return json_encode( $merged_data ); + } + + return $merged_data; } /** * Get additional stat data to sync to WPCOM */ - function get_additional_stat_data( $prefix = '' ) { + public static function get_additional_stat_data( $prefix = '' ) { $return["{$prefix}themes"] = Jetpack::get_parsed_theme_data(); $return["{$prefix}plugins-extra"] = Jetpack::get_parsed_plugin_data(); $return["{$prefix}users"] = count_users(); @@ -3308,7 +2925,7 @@ p { // If the plugin is not connected, display a connect message. if ( // the plugin was auto-activated and needs its candy - Jetpack_Options::get_option( 'do_activate' ) + Jetpack_Options::get_option_and_ensure_autoload( 'do_activate', '0' ) || // the plugin is active, but was never activated. Probably came from a site-wide network activation ! Jetpack_Options::get_option( 'activated' ) @@ -3353,9 +2970,6 @@ p { if ( Jetpack::is_active() || Jetpack::is_development_mode() ) { // Artificially throw errors in certain whitelisted cases during plugin activation add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) ); - - // Kick off synchronization of user role when it changes - add_action( 'set_user_role', array( $this, 'user_role_change' ) ); } // Jetpack Manage Activation Screen from .com @@ -4557,8 +4171,8 @@ p { return $url; } - function translate_current_user_to_role() { - foreach ( $this->capability_translations as $role => $cap ) { + static function translate_current_user_to_role() { + foreach ( self::$capability_translations as $role => $cap ) { if ( current_user_can( $role ) || current_user_can( $cap ) ) { return $role; } @@ -4567,15 +4181,15 @@ p { return false; } - function translate_role_to_cap( $role ) { - if ( ! isset( $this->capability_translations[$role] ) ) { + static function translate_role_to_cap( $role ) { + if ( ! isset( self::$capability_translations[$role] ) ) { return false; } - return $this->capability_translations[$role]; + return self::$capability_translations[$role]; } - function sign_role( $role ) { + static function sign_role( $role ) { if ( ! $user_id = (int) get_current_user_id() ) { return false; } @@ -4613,8 +4227,8 @@ p { $gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() ); } - $role = $this->translate_current_user_to_role(); - $signed_role = $this->sign_role( $role ); + $role = self::translate_current_user_to_role(); + $signed_role = self::sign_role( $role ); $user = wp_get_current_user(); @@ -4626,7 +4240,7 @@ p { $secrets = Jetpack::init()->generate_secrets( 'authorize' ); @list( $secret ) = explode( ':', $secrets ); - + $site_icon = ( function_exists( 'has_site_icon') && has_site_icon() ) ? get_site_icon_url() : false; @@ -4891,24 +4505,6 @@ p { return ( $a['sort'] < $b['sort'] ) ? -1 : 1; } - function sync_reindex_trigger() { - if ( $this->current_user_is_connection_owner() && current_user_can( 'manage_options' ) ) { - echo json_encode( $this->sync->reindex_trigger() ); - } else { - echo '{"status":"ERROR"}'; - } - exit; - } - - function sync_reindex_status(){ - if ( $this->current_user_is_connection_owner() && current_user_can( 'manage_options' ) ) { - echo json_encode( $this->sync->reindex_status() ); - } else { - echo '{"status":"ERROR"}'; - } - exit; - } - function ajax_recheck_ssl() { check_ajax_referer( 'recheck-ssl', 'ajax-nonce' ); $result = Jetpack::permit_ssl( true ); @@ -5018,7 +4614,7 @@ p {

    - connection debugger or troubleshooting tips.', 'jetpack' ), + connection debugger or troubleshooting tips.', 'jetpack' ), esc_url( Jetpack::admin_url( array( 'page' => 'jetpack-debugger' ) ) ), esc_url( 'https://jetpack.com/support/getting-started-with-jetpack/troubleshooting-tips/' ) ); ?>

    @@ -5246,6 +4842,12 @@ p { list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) ); if ( JETPACK__VERSION != $version ) { Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() ); + + if ( version_compare( JETPACK__VERSION, $version, '>' ) ) { + /** This action is documented in class.jetpack.php */ + do_action( 'updating_jetpack_version', JETPACK__VERSION, $version ); + } + return true; } return false; @@ -6044,8 +5646,6 @@ p { if ( is_array( $identity_options ) ) { foreach( $identity_options as $identity_option ) { - Jetpack_Sync::sync_options( __FILE__, $identity_option ); - /** * Fires when a shadow site option is updated. * These options are updated via the Identity Crisis UI. @@ -6165,13 +5765,13 @@ p { * Written so that we don't have re-check $key and $value params every time * we want to check if this site is whitelisted, for example in footer.php * - * @return bool True = already whitelsisted False = not whitelisted + * @return bool True = already whitelisted False = not whitelisted */ public static function is_staging_site() { $is_staging = false; $current_whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist' ); - if ( $current_whitelist ) { + if ( $current_whitelist && ! get_transient( 'jetpack_checked_is_staging' ) ) { $options_to_check = Jetpack::identity_crisis_options_to_check(); $cloud_options = Jetpack::init()->get_cloud_site_options( $options_to_check ); @@ -6181,15 +5781,18 @@ p { break; } } + // set a flag so we don't check again for an hour + set_transient( 'jetpack_checked_is_staging', 1, HOUR_IN_SECONDS ); } $known_staging = array( 'urls' => array( - '#\.staging\.wpengine\.com$#i', + '#\.staging\.wpengine\.com$#i', // WP Engine ), 'constants' => array( - 'IS_WPE_SNAPSHOT', - 'KINSTA_DEV_ENV', - 'JETPACK_STAGING_MODE', + 'IS_WPE_SNAPSHOT', // WP Engine + 'KINSTA_DEV_ENV', // Kinsta.com + 'WPSTAGECOACH_STAGING', // WP Stagecoach + 'JETPACK_STAGING_MODE', // Generic ) ); /** @@ -6430,21 +6033,6 @@ p { return false; } - /** - * Sends a ping to the Jetpack servers to toggle on/off remote portions - * required by some modules. - * - * @param string $module_slug - */ - public function toggle_module_on_wpcom( $module_slug ) { - Jetpack::init()->sync->register( 'noop' ); - - if ( false !== strpos( current_filter(), 'jetpack_activate_module_' ) ) { - self::check_privacy( $module_slug ); - } - - } - /** * Throws warnings for deprecated hooks to be removed from Jetpack */ @@ -6458,11 +6046,25 @@ p { * If there is no replacement us null for replacement_name */ $deprecated_list = array( - 'jetpack_bail_on_shortcode' => 'jetpack_shortcodes_to_include', - 'wpl_sharing_2014_1' => null, - 'jetpack-tools-to-include' => 'jetpack_tools_to_include', - 'jetpack_identity_crisis_options_to_check' => null, - 'audio_player_default_colors' => null, + 'jetpack_bail_on_shortcode' => 'jetpack_shortcodes_to_include', + 'wpl_sharing_2014_1' => null, + 'jetpack-tools-to-include' => 'jetpack_tools_to_include', + 'jetpack_identity_crisis_options_to_check' => null, + 'update_option_jetpack_single_user_site' => null, + 'audio_player_default_colors' => null, + 'add_option_jetpack_featured_images_enabled' => null, + 'add_option_jetpack_update_details' => null, + 'add_option_jetpack_updates' => null, + 'add_option_jetpack_network_name' => null, + 'add_option_jetpack_network_allow_new_registrations' => null, + 'add_option_jetpack_network_add_new_users' => null, + 'add_option_jetpack_network_site_upload_space' => null, + 'add_option_jetpack_network_upload_file_types' => null, + 'add_option_jetpack_network_enable_administration_menus' => null, + 'add_option_jetpack_is_multi_site' => null, + 'add_option_jetpack_is_main_network' => null, + 'add_option_jetpack_main_network_site' => null, + 'jetpack_sync_all_registered_options' => null, ); // This is a silly loop depth. Better way? @@ -6556,7 +6158,7 @@ p { } /** - * This methods removes all of the registered css files on the frontend + * This methods removes all of the registered css files on the front end * from Jetpack in favor of using a single file. In effect "imploding" * all the files into one file. * @@ -7068,7 +6670,7 @@ p { */ function jetpack_user_col_style() { global $current_screen; - if ( 'users' == $current_screen->base ) { ?> + if ( ! empty( $current_screen->base ) && 'users' == $current_screen->base ) { ?> - - - + } + ID, $uniqid ); $html = "'; - // Lets make sure that the script is enqued + // Let's make sure that the script is enqueued wp_enqueue_script( 'jetpack_likes_queuehandler' ); return $content . $html; @@ -1020,8 +967,8 @@ class Jetpack_Likes { * similar logic and filters apply here, too. */ function is_likes_visible() { + global $post, $wp_current_filter; // Used to apply 'sharing_show' filter - global $post, $wp_current_filter; // Used to apply 'sharing_show' filter // @todo: Remove this block when 4.5 is the minimum global $wp_version; $comment_popup = false; @@ -1122,7 +1069,7 @@ class Jetpack_Likes { * * @param bool $option Are Likes enabled sitewide. */ - return (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) ); + return (bool) apply_filters( 'wpl_is_enabled_sitewide', ! Jetpack_Options::get_option_and_ensure_autoload( 'disabled_likes', 0 ) ); } /** @@ -1145,7 +1092,6 @@ class Jetpack_Likes { /** * Returns if comment likes are enabled. Defaults to 'off' - * @todo decide what the default should be * @return boolean true if we should show comment likes, false if not */ function is_comments_enabled() { diff --git a/plugins/jetpack/modules/manage.php b/plugins/jetpack/modules/manage.php index e084e9d4..efc81419 100644 --- a/plugins/jetpack/modules/manage.php +++ b/plugins/jetpack/modules/manage.php @@ -12,9 +12,6 @@ * Feature: Recommended * Additional Search Queries: manage, management, remote */ - -add_action( 'jetpack_activate_module_manage', array( Jetpack::init(), 'toggle_module_on_wpcom' ) ); -add_action( 'jetpack_deactivate_module_manage', array( Jetpack::init(), 'toggle_module_on_wpcom' ) ); add_action( 'customize_register', 'add_wpcom_to_allowed_redirect_hosts' ); // Add wordpress.com to the safe redirect whitelist if the Manage module is enabled @@ -34,16 +31,6 @@ function allow_wpcom_domain( $domains ) { return array_unique( $domains ); } -// Re add sync for non public posts when the optin is selected in Calypso. -// This will only work if you have manage enabled as well. -if ( Jetpack_Options::get_option( 'sync_non_public_post_stati' ) ) { - $sync_options = array( - 'post_types' => get_post_types( array( 'public' => true ) ), - 'post_stati' => get_post_stati(), - ); - Jetpack_Sync::sync_posts( __FILE__, $sync_options ); -} - Jetpack::module_configuration_screen( 'manage', 'jetpack_manage_config_screen' ); function jetpack_manage_config_screen() { include ( JETPACK__PLUGIN_DIR . 'modules/manage/confirm-admin.php' ); diff --git a/plugins/jetpack/modules/manage/confirm-admin.php b/plugins/jetpack/modules/manage/confirm-admin.php index ed7e2e98..c8f105f9 100644 --- a/plugins/jetpack/modules/manage/confirm-admin.php +++ b/plugins/jetpack/modules/manage/confirm-admin.php @@ -22,6 +22,11 @@ switch( $section ) { $link_title = __( 'Manage Your Plugins', 'jetpack' ); break; + case 'plugins-setup': + $link = 'https://wordpress.com/plugins/setup/' . $normalized_site_url; + $link_title = __( 'Back to Plan Setup', 'jetpack' ); + break; + case 'themes': $link = 'https://wordpress.com/design/' . $normalized_site_url; $link_title = __( 'Manage Your Themes', 'jetpack' ); diff --git a/plugins/jetpack/modules/markdown/easy-markdown.php b/plugins/jetpack/modules/markdown/easy-markdown.php index 43b41c20..f3870ce0 100644 --- a/plugins/jetpack/modules/markdown/easy-markdown.php +++ b/plugins/jetpack/modules/markdown/easy-markdown.php @@ -16,7 +16,7 @@ Author URI: http://automattic.com/ * http://www.opensource.org/licenses/gpl-license.php * * This is an add-on for WordPress - * http://wordpress.org/ + * https://wordpress.org/ * * ********************************************************************** * This program is free software; you can redistribute it and/or modify @@ -311,7 +311,7 @@ class WPCom_Markdown { * @return boolean */ public function is_posting_enabled() { - return (bool) get_option( self::POST_OPTION, '' ); + return (bool) Jetpack_Options::get_option_and_ensure_autoload( self::POST_OPTION, '' ); } /** @@ -319,7 +319,7 @@ class WPCom_Markdown { * @return boolean */ public function is_commenting_enabled() { - return (bool) get_option( self::COMMENT_OPTION, '' ); + return (bool) Jetpack_Options::get_option_and_ensure_autoload( self::COMMENT_OPTION, '' ); } /** diff --git a/plugins/jetpack/modules/minileven.php b/plugins/jetpack/modules/minileven.php index 674e7cf7..9788fd5c 100644 --- a/plugins/jetpack/modules/minileven.php +++ b/plugins/jetpack/modules/minileven.php @@ -16,7 +16,7 @@ function jetpack_load_minileven() { include dirname( __FILE__ ) . "/minileven/minileven.php"; - if ( get_option( 'wp_mobile_app_promos' ) != '1' ) + if ( Jetpack_Options::get_option_and_ensure_autoload( 'wp_mobile_app_promos', '0' ) != '1' ) remove_action( 'wp_mobile_theme_footer', 'jetpack_mobile_app_promo' ); } @@ -129,6 +129,8 @@ function minileven_enabled( $wp_mobile_disable_option ) { return true; } -add_filter( 'option_wp_mobile_disable', 'minileven_enabled' ); +if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + add_filter( 'option_wp_mobile_disable', 'minileven_enabled' ); +} jetpack_load_minileven(); diff --git a/plugins/jetpack/modules/minileven/minileven.php b/plugins/jetpack/modules/minileven/minileven.php index 16559472..8b609d97 100644 --- a/plugins/jetpack/modules/minileven/minileven.php +++ b/plugins/jetpack/modules/minileven/minileven.php @@ -34,7 +34,7 @@ function jetpack_check_mobile() { return false; if ( jetpack_mobile_exclude() ) return false; - if ( 1 == get_option('wp_mobile_disable') ) + if ( 1 == Jetpack_Options::get_option_and_ensure_autoload( 'wp_mobile_disable', '0' ) ) return false; if ( isset($_COOKIE['akm_mobile']) && $_COOKIE['akm_mobile'] == 'true' ) return true; diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php index 762fd733..c25fa83a 100644 --- a/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php @@ -56,7 +56,7 @@ do_action( 'minileven_credits' ); ?> - +
    diff --git a/plugins/jetpack/modules/monitor.php b/plugins/jetpack/modules/monitor.php index 93a93f17..79a9d15e 100644 --- a/plugins/jetpack/modules/monitor.php +++ b/plugins/jetpack/modules/monitor.php @@ -12,9 +12,6 @@ * Additional Search Queries: monitor, uptime, downtime, monitoring */ -add_action( 'jetpack_activate_module_monitor', array( Jetpack::init(), 'toggle_module_on_wpcom' ) ); -add_action( 'jetpack_deactivate_module_monitor', array( Jetpack::init(), 'toggle_module_on_wpcom' ) ); - class Jetpack_Monitor { public $module = 'monitor'; diff --git a/plugins/jetpack/modules/notes.php b/plugins/jetpack/modules/notes.php index d37e5de3..af403dea 100644 --- a/plugins/jetpack/modules/notes.php +++ b/plugins/jetpack/modules/notes.php @@ -12,19 +12,6 @@ if ( !defined( 'JETPACK_NOTES__CACHE_BUSTER' ) ) define( 'JETPACK_NOTES__CACHE_BUSTER', JETPACK__VERSION . '-' . gmdate( 'oW' ) ); -Jetpack_Sync::sync_options( __FILE__, - 'home', - 'blogname', - 'siteurl', - 'permalink_structure', - 'category_base', - 'tag_base', - 'comment_moderation', - 'default_comment_status', - 'thread_comments', - 'thread_comments_depth' -); - class Jetpack_Notifications { public $jetpack = false; @@ -101,16 +88,6 @@ class Jetpack_Notifications { } } - Jetpack_Sync::sync_posts( __FILE__, array( - 'post_types' => $filt_post_types, - 'post_stati' => array( 'publish' ), - ) ); - Jetpack_Sync::sync_comments( __FILE__, array( - 'post_types' => $filt_post_types, - 'post_stati' => array( 'publish' ), - 'comment_stati' => array( 'approve', 'approved', '1', 'hold', 'unapproved', 'unapprove', '0', 'spam', 'trash' ), - ) ); - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) return; diff --git a/plugins/jetpack/modules/post-by-email.php b/plugins/jetpack/modules/post-by-email.php index 6a7bae26..17aa532c 100644 --- a/plugins/jetpack/modules/post-by-email.php +++ b/plugins/jetpack/modules/post-by-email.php @@ -12,8 +12,6 @@ */ add_action( 'jetpack_modules_loaded', array( 'Jetpack_Post_By_Email', 'init' ) ); -add_action( 'jetpack_activate_module_post-by-email', array( 'Jetpack_Post_By_Email', 'module_toggle' ) ); -add_action( 'jetpack_deactivate_module_post-by-email', array( 'Jetpack_Post_By_Email', 'module_toggle' ) ); Jetpack::enable_module_configurable( __FILE__ ); Jetpack::module_configuration_load( __FILE__, array( 'Jetpack_Post_By_Email', 'configuration_redirect' ) ); @@ -33,11 +31,6 @@ class Jetpack_Post_By_Email { add_action( 'init', array( &$this, 'action_init' ) ); } - static function module_toggle() { - $jetpack = Jetpack::init(); - $jetpack->sync->register( 'noop' ); - } - static function configuration_redirect() { wp_safe_redirect( get_edit_profile_url( get_current_user_id() ) . '#post-by-email' ); exit; diff --git a/plugins/jetpack/modules/protect.php b/plugins/jetpack/modules/protect.php index 4829e016..0745d456 100644 --- a/plugins/jetpack/modules/protect.php +++ b/plugins/jetpack/modules/protect.php @@ -49,9 +49,8 @@ class Jetpack_Protect_Module { private function __construct() { add_action( 'jetpack_activate_module_protect', array ( $this, 'on_activation' ) ); add_action( 'jetpack_deactivate_module_protect', array ( $this, 'on_deactivation' ) ); - add_action( 'init', array ( $this, 'maybe_get_protect_key' ) ); add_action( 'jetpack_modules_loaded', array ( $this, 'modules_loaded' ) ); - add_action( 'init', array ( $this, 'check_use_math' ) ); + add_action( 'login_init', array ( $this, 'check_use_math' ) ); add_filter( 'authenticate', array ( $this, 'check_preauth' ), 10, 3 ); add_action( 'wp_login', array ( $this, 'log_successful_login' ), 10, 2 ); add_action( 'wp_login_failed', array ( $this, 'log_failed_attempt' ) ); @@ -91,9 +90,12 @@ class Jetpack_Protect_Module { public function maybe_get_protect_key() { if ( get_site_option( 'jetpack_protect_activating', false ) && ! get_site_option( 'jetpack_protect_key', false ) ) { - $this->get_protect_key(); + $key = $this->get_protect_key(); delete_site_option( 'jetpack_protect_activating' ); + return $key; } + + return get_site_option( 'jetpack_protect_key' ); } /** @@ -649,7 +651,7 @@ class Jetpack_Protect_Module { function protect_call( $action = 'check_ip', $request = array () ) { global $wp_version, $wpdb, $current_user; - $api_key = get_site_option( 'jetpack_protect_key' ); + $api_key = $this->maybe_get_protect_key(); $user_agent = "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' ); diff --git a/plugins/jetpack/modules/publicize.php b/plugins/jetpack/modules/publicize.php index 9709e300..674708da 100644 --- a/plugins/jetpack/modules/publicize.php +++ b/plugins/jetpack/modules/publicize.php @@ -24,7 +24,6 @@ class Jetpack_Publicize { if ( $this->in_jetpack && method_exists( 'Jetpack', 'module_configuration_load' ) ) { Jetpack::enable_module_configurable( __FILE__ ); Jetpack::module_configuration_load( __FILE__, array( $this, 'jetpack_configuration_load' ) ); - add_action( 'init', array( $this, 'sync_posts_init' ), 999 ); } require_once dirname( __FILE__ ) . '/publicize/publicize.php'; @@ -42,9 +41,6 @@ class Jetpack_Publicize { // Jetpack specific checks / hooks if ( $this->in_jetpack) { - add_action( 'jetpack_activate_module_publicize', array( $this, 'module_state_toggle' ) ); - add_action( 'jetpack_deactivate_module_publicize', array( $this, 'module_state_toggle' ) ); - add_filter( 'jetpack_sync_post_module_custom_data', array( $this, 'sync_post_module_custom_data' ), 10, 2 ); // if sharedaddy isn't active, the sharing menu hasn't been added yet $active = Jetpack::get_active_modules(); if ( in_array( 'publicize', $active ) && !in_array( 'sharedaddy', $active ) ) @@ -52,35 +48,6 @@ class Jetpack_Publicize { } } - function sync_posts_init() { - $post_types = array( 'post', 'page' ); - $all_post_types = get_post_types(); - foreach ( $all_post_types as $post_type ) { - // sync Custom Post Types that support publicize - if ( post_type_supports( $post_type, 'publicize' ) ) { - $post_types[] = $post_type; - } - } - Jetpack_Sync::sync_posts( __FILE__, array( - 'post_types' => $post_types, - ) ); - } - - function sync_post_module_custom_data( $custom_data, $post ) { - if ( post_type_supports( get_post_type( $post ), 'publicize' ) ) { - $custom_data['cpt_publicizeable'] = true; - } - return $custom_data; - } - - function module_state_toggle() { - // extra check that we are on the JP blog, just incase - if ( class_exists( 'Jetpack' ) && $this->in_jetpack ) { - $jetpack = Jetpack::init(); - $jetpack->sync->register( 'noop' ); - } - } - function jetpack_configuration_load() { wp_safe_redirect( menu_page_url( 'sharing', false ) ); exit; diff --git a/plugins/jetpack/modules/publicize/publicize-jetpack.php b/plugins/jetpack/modules/publicize/publicize-jetpack.php index f21ca16e..ab371433 100644 --- a/plugins/jetpack/modules/publicize/publicize-jetpack.php +++ b/plugins/jetpack/modules/publicize/publicize-jetpack.php @@ -27,7 +27,7 @@ class Publicize extends Publicize_Base { add_filter( 'publicize_checkbox_default', array( $this, 'publicize_checkbox_default' ), 10, 4 ); - add_action( 'transition_post_status', array( $this, 'save_publicized' ), 10, 3 ); + add_action( 'wp_insert_post', array( $this, 'save_publicized' ), 11, 3 ); add_filter( 'jetpack_twitter_cards_site_tag', array( $this, 'enhaced_twitter_cards_site_tag' ) ); @@ -359,7 +359,25 @@ class Publicize extends Publicize_Base { } function flag_post_for_publicize( $new_status, $old_status, $post ) { - // Stub only. Doesn't need to do anything on Jetpack Client + if ( 'publish' == $new_status && 'publish' != $old_status ) { + /** + * Determines whether a post being published gets publicized. + * + * Side-note: Possibly our most alliterative filter name. + * + * @module publicize + * + * @since 4.1.0 + * + * @param bool $should_publicize Should the post be publicized? Default to true. + * @param WP_POST $post Current Post object. + */ + $should_publicize = apply_filters( 'publicize_should_publicize_published_post', true, $post ); + + if ( $should_publicize ) { + update_post_meta( $post->ID, $this->PENDING, true ); + } + } } function test_connection( $service_name, $connection ) { @@ -405,9 +423,21 @@ class Publicize extends Publicize_Base { * Save a flag locally to indicate that this post has already been Publicized via the selected * connections. */ - function save_publicized( $new_status, $old_status, $post ) { + function save_publicized( $post_ID, $post, $update ) { // Only do this when a post transitions to being published - if ( 'publish' == $new_status && 'publish' != $old_status ) { + if ( get_post_meta( $post->ID, $this->PENDING ) ) { + $connected_services = Jetpack_Options::get_option( 'publicize_connections' ); + if ( ! empty( $connected_services ) ) { + /** + * Fires when a post is saved that has is marked as pending publicizing + * + * @since 4.1.0 + * + * @param int The post ID + */ + do_action( 'jetpack_publicize_post', $post->ID ); + } + delete_post_meta( $post->ID, $this->PENDING ); update_post_meta( $post->ID, $this->POST_DONE . 'all', true ); } } diff --git a/plugins/jetpack/modules/publicize/publicize.php b/plugins/jetpack/modules/publicize/publicize.php index f78bdd9f..6b558f25 100644 --- a/plugins/jetpack/modules/publicize/publicize.php +++ b/plugins/jetpack/modules/publicize/publicize.php @@ -326,7 +326,11 @@ abstract class Publicize_Base { } // Did this request happen via wp-admin? - $from_web = 'post' == strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST[$this->ADMIN_PAGE] ); + $from_web = isset( $_SERVER['REQUEST_METHOD'] ) + && + 'post' == strtolower( $_SERVER['REQUEST_METHOD'] ) + && + isset( $_POST[$this->ADMIN_PAGE] ); if ( ( $from_web || defined( 'POST_BY_EMAIL' ) ) && isset( $_POST['wpas_title'] ) ) { if ( empty( $_POST['wpas_title'] ) ) { diff --git a/plugins/jetpack/modules/related-posts.php b/plugins/jetpack/modules/related-posts.php index ac6ccbf7..8df4b32b 100644 --- a/plugins/jetpack/modules/related-posts.php +++ b/plugins/jetpack/modules/related-posts.php @@ -38,19 +38,6 @@ class Jetpack_RelatedPosts_Module { */ private function __construct() { add_action( 'jetpack_module_loaded_related-posts', array( $this, 'action_on_load' ) ); - add_action( 'jetpack_activate_module_related-posts', array( $this, 'action_on_activate' ) ); - } - - /** - * This action triggers when module is activated. - * - * @uses Jetpack::init, Jetpack_Sync::reindex_needed, Jetpack_Sync::reindex_trigger - * @return null - */ - public function action_on_activate() { - if ( Jetpack::init()->sync->reindex_needed() ) { - Jetpack::init()->sync->reindex_trigger(); - } } /** @@ -67,9 +54,6 @@ class Jetpack_RelatedPosts_Module { // Enable "Configure" button on module card Jetpack::enable_module_configurable( __FILE__ ); Jetpack::module_configuration_load( __FILE__, array( $this, 'module_configuration_load' ) ); - - // Sync new posts - Jetpack_Sync::sync_posts( __FILE__ ); } } diff --git a/plugins/jetpack/modules/related-posts/jetpack-related-posts.php b/plugins/jetpack/modules/related-posts/jetpack-related-posts.php index 5a5d1ae5..f8eaff22 100644 --- a/plugins/jetpack/modules/related-posts/jetpack-related-posts.php +++ b/plugins/jetpack/modules/related-posts/jetpack-related-posts.php @@ -105,7 +105,7 @@ class Jetpack_RelatedPosts { } /** - * Load related posts assets if it's a elegiable frontend page or execute search and return JSON if it's an endpoint request. + * Load related posts assets if it's a elegiable front end page or execute search and return JSON if it's an endpoint request. * * @global $_GET * @action wp @@ -231,7 +231,7 @@ EOT; */ public function get_options() { if ( null === $this->_options ) { - $this->_options = Jetpack_Options::get_option( 'relatedposts' ); + $this->_options = Jetpack_Options::get_option( 'relatedposts', array() ); if ( ! is_array( $this->_options ) ) $this->_options = array(); if ( ! isset( $this->_options['enabled'] ) ) diff --git a/plugins/jetpack/modules/sharedaddy/sharedaddy.php b/plugins/jetpack/modules/sharedaddy/sharedaddy.php index ffd7bbc4..18c04d38 100644 --- a/plugins/jetpack/modules/sharedaddy/sharedaddy.php +++ b/plugins/jetpack/modules/sharedaddy/sharedaddy.php @@ -200,7 +200,7 @@ function sharing_restrict_to_single( $services ) { } function sharing_init() { - if ( get_option( 'sharedaddy_disable_resources' ) ) { + if ( Jetpack_Options::get_option_and_ensure_autoload( 'sharedaddy_disable_resources', '0' ) ) { add_filter( 'sharing_js', 'sharing_disable_js' ); remove_action( 'wp_head', 'sharing_add_header', 1 ); } diff --git a/plugins/jetpack/modules/sharedaddy/sharing-sources.php b/plugins/jetpack/modules/sharedaddy/sharing-sources.php index 1fc6cef0..4e6f421b 100644 --- a/plugins/jetpack/modules/sharedaddy/sharing-sources.php +++ b/plugins/jetpack/modules/sharedaddy/sharing-sources.php @@ -329,15 +329,13 @@ abstract class Sharing_Source { $opts = implode( ',', $opts ); ?> get_widget_type() ) : ?> get_link( 'whatsapp://send?text=' . rawurlencode( $this->get_share_title( $post->ID ) ) . ' ' . rawurlencode( get_permalink( $post->ID ) ), _x( 'WhatsApp', 'share to', 'jetpack' ), __( 'Click to share on WhatsApp', 'jetpack' ) ); + return $this->get_link( 'whatsapp://send?text=' . rawurlencode( $this->get_share_title( $post->ID ) ) . ' ' . rawurlencode( $this->get_share_url( $post->ID ) ), _x( 'WhatsApp', 'share to', 'jetpack' ), __( 'Click to share on WhatsApp', 'jetpack' ) ); } } diff --git a/plugins/jetpack/modules/sharedaddy/sharing.css b/plugins/jetpack/modules/sharedaddy/sharing.css index 06845483..ab8f6592 100644 --- a/plugins/jetpack/modules/sharedaddy/sharing.css +++ b/plugins/jetpack/modules/sharedaddy/sharing.css @@ -77,7 +77,6 @@ body.highlander-dark h3.sd-title:before { .sd-social-official .sd-content>ul>li .digg_button >a { /* official Digg button no longer works, needs cleaning */ text-decoration: none !important; display: inline-block; - margin: 0 5px 5px 0; font-size: 12px; font-family: "Open Sans", sans-serif; font-weight: normal; @@ -182,7 +181,7 @@ body.highlander-dark h3.sd-title:before { } .sd-content ul li { - margin: 0 !important; + margin: 0 5px 5px 0; padding: 0; } diff --git a/plugins/jetpack/modules/sharedaddy/sharing.js b/plugins/jetpack/modules/sharedaddy/sharing.js index ac287a24..34913e81 100644 --- a/plugins/jetpack/modules/sharedaddy/sharing.js +++ b/plugins/jetpack/modules/sharedaddy/sharing.js @@ -72,7 +72,7 @@ if ( sharing_js_options && sharing_js_options.counts ) { } for ( url in data ) { - if ( ! data.hasOwnProperty( url ) || ! data[ url ].shares ) { + if ( ! data.hasOwnProperty( url ) || ! data[ url ].share.share_count ) { continue; } @@ -82,7 +82,7 @@ if ( sharing_js_options && sharing_js_options.counts ) { continue; } - WPCOMSharing.inject_share_count( 'sharing-facebook-' + WPCOM_sharing_counts[ permalink ], data[ url ].shares ); + WPCOMSharing.inject_share_count( 'sharing-facebook-' + WPCOM_sharing_counts[ permalink ], data[ url ].share.share_count ); } }, update_linkedin_count : function( data ) { @@ -129,7 +129,7 @@ var updateLinkedInCount = function( data ) { } ); $body = $( document.body ).on( 'post-load', WPCOMSharing_do ); - $( document ).on( 'ready', function() { + $( document ).ready( function() { $sharing_email = $( '#sharing_email' ); $body.append( $sharing_email ); WPCOMSharing_do(); diff --git a/plugins/jetpack/modules/shortcodes.php b/plugins/jetpack/modules/shortcodes.php index f2181997..1a12604e 100644 --- a/plugins/jetpack/modules/shortcodes.php +++ b/plugins/jetpack/modules/shortcodes.php @@ -39,12 +39,12 @@ function shortcode_new_to_old_params( $params, $old_format_support = false ) { } function jetpack_load_shortcodes() { - global $wp_version; - $shortcode_includes = array(); foreach ( Jetpack::glob_php( dirname( __FILE__ ) . '/shortcodes' ) as $file ) { - $shortcode_includes[] = $file; + $filename = substr( basename( $file ), 0, -4 ); + + $shortcode_includes[ $filename ] = $file; } /** @@ -53,6 +53,7 @@ function jetpack_load_shortcodes() { * @module shortcodes * * @since 2.2.1 + * @since 4.2.0 Added filename without extension as array key. * * @param array $shortcode_includes An array of which shortcodes to include. */ diff --git a/plugins/jetpack/modules/shortcodes/cartodb.php b/plugins/jetpack/modules/shortcodes/cartodb.php index 74292830..6f9e4f5e 100644 --- a/plugins/jetpack/modules/shortcodes/cartodb.php +++ b/plugins/jetpack/modules/shortcodes/cartodb.php @@ -2,17 +2,20 @@ /* - * CartoDB + * Carto (formerly CartoDB) * - * example URL: http://osm2.cartodb.com/viz/08aef918-94da-11e4-ad83-0e0c41326911/public_map + * example URL: http://osm2.carto.com/viz/08aef918-94da-11e4-ad83-0e0c41326911/public_map * * possible patterns: - * [username].cartodb.com/viz/[map-id]/public_map - * [username].cartodb.com/viz/[map-id]/embed_map - * [username].cartodb.com/viz/[map-id]/map - * [organization].cartodb.com/u/[username]/viz/[map-id]/public_map - * [organization].cartodb.com/u/[username]/viz/[map-id]/embed_map - * [organization].cartodb.com/u/[username]/viz/[map-id]/map + * [username].carto.com/viz/[map-id]/public_map + * [username].carto.com/viz/[map-id]/embed_map + * [username].carto.com/viz/[map-id]/map + * [organization].carto.com/u/[username]/viz/[map-id]/public_map + * [organization].carto.com/u/[username]/viz/[map-id]/embed_map + * [organization].carto.com/u/[username]/viz/[map-id]/map + * + * On July 8th, 2016 CartoDB changed its primary domain from cartodb.com to carto.com + * So this shortcode still supports the cartodb.com domain for oembeds. */ -wp_oembed_add_provider( '#https?://(?:www\.)?[^/^\.]+\.cartodb\.com/\S+#i', 'https://services.cartodb.com/oembed', true ); \ No newline at end of file +wp_oembed_add_provider( '#https?://(?:www\.)?[^/^\.]+\.carto(db)?\.com/\S+#i', 'https://services.carto.com/oembed', true ); \ No newline at end of file diff --git a/plugins/jetpack/modules/shortcodes/dailymotion.php b/plugins/jetpack/modules/shortcodes/dailymotion.php index bd4612ec..9a904dc7 100644 --- a/plugins/jetpack/modules/shortcodes/dailymotion.php +++ b/plugins/jetpack/modules/shortcodes/dailymotion.php @@ -75,6 +75,11 @@ add_filter( 'pre_kses', 'dailymotion_embed_to_shortcode' ); * * The new style is now: * [dailymotion id=x8oma9 title=2 user=3 video=4] + * + * Supported parameters for player customization: width, height, + * autoplay, endscreen-enable, mute, sharing-enabled, start, subtitles-default, + * ui-highlight, ui-logo, ui-start-screen-info, ui-theme + * see https://developer.dailymotion.com/player#player-parameters * @todo: Update code to sniff for iframe embeds and convert those to shortcodes. * * @param array $atts @@ -93,40 +98,115 @@ function dailymotion_shortcode( $atts ) { $params = shortcode_new_to_old_params( $atts ); parse_str( $params, $atts_new ); - foreach( $atts_new as $k => $v ) { + foreach ( $atts_new as $k => $v ) { $atts[ $k ] = $v; } } - if ( isset( $atts['id'] ) ) { - $id = $atts['id']; + $atts = shortcode_atts( + array( + 'id' => '', // string + 'width' => '', // int + 'height' => '', // int + 'title' => '', // string + 'user' => '', // string + 'video' => '', // string + 'autoplay' => 0, // int + 'endscreen-enable' => 1, // int + 'mute' => 0, // int + 'sharing-enable' => 1, // int + 'start' => '', // int + 'subtitles-default' => '', // string + 'ui-highlight' => '', // string + 'ui-logo' => 1, // int + 'ui-start-screen-info' => 0, // int + 'ui-theme' => '', // string + ), $atts, 'dailymotion' + ); + + if ( isset( $atts['id'] ) && ! empty( $atts['id'] ) ) { + $id = urlencode( $atts['id'] ); } else { return ''; } - if ( ! empty( $content_width ) ) { - $width = min( 425, intval( $content_width ) ); - } else { - $width = 425; + /*set width and height using provided parameters if any */ + $width = isset( $atts['width'] ) ? intval( $atts['width'] ) : 0 ; + $height = isset( $atts['height'] ) ? intval( $atts['height'] ) : 0 ; + + if ( ! $width && ! $height ) { + if ( ! empty( $content_width ) ) { + $width = absint( $content_width ); + } else { + $width = 425; + } + $height = $width / 425 * 334; + } elseif ( ! $height ) { + $height = $width / 425 * 334; + } elseif ( ! $width ) { + $width = $height / 334 * 425; + } + + /** + * Let's add parameters if needed. + * + * @see https://developer.dailymotion.com/player + */ + $player_params = array(); + + if ( isset( $atts['autoplay'] ) && '1' === $atts['autoplay'] ) { + $player_params['autoplay'] = '1'; + } + if ( isset( $atts['endscreen-enable'] ) && '0' === $atts['endscreen-enable'] ) { + $player_params['endscreen-enable'] = '0'; + } + if ( isset( $atts['mute'] ) && '1' === $atts['mute'] ) { + $player_params['mute'] = '1'; } + if ( isset( $atts['sharing-enable'] ) && '0' === $atts['sharing-enable'] ) { + $player_params['sharing-enable'] = '0'; + } + if ( isset( $atts['start'] ) && ! empty( $atts['start'] ) ) { + $player_params['start'] = abs( intval( $atts['start'] ) ); + } + if ( isset( $atts['subtitles-default'] ) && ! empty( $atts['subtitles-default'] ) ) { + $player_params['subtitles-default'] = esc_attr( $atts['subtitles-default'] ); + } + if ( isset( $atts['ui-highlight'] ) && ! empty( $atts['ui-highlight'] ) ) { + $player_params['ui-highlight'] = esc_attr( $atts['ui-highlight'] ); + } + if ( isset( $atts['ui-logo'] ) && '0' === $atts['ui-logo'] ) { + $player_params['ui-logo'] = '0'; + } + if ( isset( $atts['ui-start-screen-info'] ) && '0' === $atts['ui-start-screen-info'] ) { + $player_params['ui-start-screen-info'] = '0'; + } + if ( isset( $atts['ui-theme'] ) && in_array( strtolower( $atts['ui-theme'] ), array( 'dark', 'light' ) ) ) { + $player_params['ui-theme'] = esc_attr( $atts['ui-theme'] ); + } + + // Add those parameters to the Video URL. + $video_url = add_query_arg( + $player_params, + 'https://www.dailymotion.com/embed/video/' . $id + ); - $height = ( 425 == $width ) ? 334 : ( $width / 425 ) * 334; - $id = urlencode( $id ); + $output = ''; if ( preg_match( '/^[A-Za-z0-9]+$/', $id ) ) { - $output = ''; - $after = ''; + $output .= ''; if ( array_key_exists( 'video', $atts ) && $video = preg_replace( '/[^-a-z0-9_]/i', '', $atts['video'] ) && array_key_exists( 'title', $atts ) && $title = wp_kses( $atts['title'], array() ) ) { - $after .= '
    ' . esc_html( $title ) . ''; + $output .= '
    ' . esc_html( $title ) . ''; } if ( array_key_exists( 'user', $atts ) && $user = preg_replace( '/[^-a-z0-9_]/i', '', $atts['user'] ) ) { - $after .= '
    Uploaded by ' . esc_html( $user ) . ''; + /* translators: %s is a Dailymotion user name */ + $output .= '
    ' . wp_kses( sprintf( __( 'Uploaded by %s', 'jetpack' ), '' . esc_html( $user ) . '' ), array( 'a' => array( 'href' => true, 'target' => true ) ) ) . ''; } } - return $output . $after; + return $output; } add_shortcode( 'dailymotion', 'dailymotion_shortcode' ); @@ -143,13 +223,13 @@ function dailymotion_channel_shortcode( $atts ) { switch( $atts['type'] ) { case 'grid': - return ''; + return ''; break; case 'carousel': - return ''; + return ''; break; default: - return ''; + return ''; } } diff --git a/plugins/jetpack/modules/shortcodes/slideshow.php b/plugins/jetpack/modules/shortcodes/slideshow.php index 0ff568d3..949af189 100644 --- a/plugins/jetpack/modules/shortcodes/slideshow.php +++ b/plugins/jetpack/modules/shortcodes/slideshow.php @@ -122,7 +122,7 @@ class Jetpack_Slideshow_Shortcode { 'trans' => 'fade', 'order' => 'ASC', 'orderby' => 'menu_order ID', - 'id' => $post->ID, + 'id' => isset( $post->ID ) ? $post->ID : null, 'include' => '', 'exclude' => '', 'autostart' => true, diff --git a/plugins/jetpack/modules/shortcodes/soundcloud.php b/plugins/jetpack/modules/shortcodes/soundcloud.php index 3295bf01..6e0b2cb1 100644 --- a/plugins/jetpack/modules/shortcodes/soundcloud.php +++ b/plugins/jetpack/modules/shortcodes/soundcloud.php @@ -1,7 +1,7 @@ 0 && $input_h > 0 ) { @@ -363,12 +366,10 @@ add_action( 'init', 'wpcom_youtube_embed_crazy_url_init' ); * * @param int get_option('embed_autourls') Option to automatically embed all plain text URLs. */ -if ( apply_filters( 'jetpack_comments_allow_oembed', get_option('embed_autourls') ) ) { +if ( ! is_admin() && apply_filters( 'jetpack_comments_allow_oembed', true ) ) { // We attach wp_kses_post to comment_text in default-filters.php with priority of 10 anyway, so the iframe gets filtered out. - if ( ! is_admin() ) { - // Higher priority because we need it before auto-link and autop get to it - add_filter( 'comment_text', 'youtube_link', 1 ); - } + // Higher priority because we need it before auto-link and autop get to it + add_filter( 'comment_text', 'youtube_link', 1 ); } /** diff --git a/plugins/jetpack/modules/site-icon.php b/plugins/jetpack/modules/site-icon.php index 72cf69f7..d9f0f47a 100644 --- a/plugins/jetpack/modules/site-icon.php +++ b/plugins/jetpack/modules/site-icon.php @@ -13,5 +13,3 @@ include dirname( __FILE__ ) . '/site-icon/jetpack-site-icon.php'; include dirname( __FILE__ ) . '/site-icon/site-icon-functions.php'; - -Jetpack_Sync::sync_options( __FILE__, 'jetpack_site_icon_url' ); diff --git a/plugins/jetpack/modules/site-icon/jetpack-site-icon.php b/plugins/jetpack/modules/site-icon/jetpack-site-icon.php index db5626f3..afc91b05 100644 --- a/plugins/jetpack/modules/site-icon/jetpack-site-icon.php +++ b/plugins/jetpack/modules/site-icon/jetpack-site-icon.php @@ -71,7 +71,7 @@ class Jetpack_Site_Icon { add_action( 'admin_print_styles-options-general.php', array( $this, 'add_general_options_styles' ) ); - // Add the favicon to the front end and backend if Core's site icon not used. + // Add the favicon to the front end and back end if Core's site icon not used. /** * As of WP 4.3 and JP 3.6, both are outputting the same icons so no need to fire these. * This is a temporary solution until Jetpack's module primary function is deprecated. diff --git a/plugins/jetpack/modules/sitemaps/sitemap-xsl.php b/plugins/jetpack/modules/sitemaps/sitemap-xsl.php index 906f11fb..eceb38ca 100644 --- a/plugins/jetpack/modules/sitemaps/sitemap-xsl.php +++ b/plugins/jetpack/modules/sitemaps/sitemap-xsl.php @@ -90,8 +90,36 @@ $xsl = '

    ' . esc_html( ent2ncr( __( 'XML Sitemap', 'jetpack' ) ) ) . '

    -

    ' . wp_kses( sprintf( ent2ncr( __( 'This is an XML Sitemap generated by Jetpack, meant to be consumed by search engines like Google or Bing.', 'jetpack' ), 'http://jetpack.com/', 'https://www.google.com', 'https://www.bing.com/' ), array( 'a' => array( 'href' => true, 'title' => true ) ) ) ) . '

    -

    ' . wp_kses( sprintf( ent2ncr( __( 'You can find more information on XML sitemaps at sitemaps.org', 'jetpack' ), 'http://sitemaps.org' ), array( 'a' => array( 'href' => true, 'title' => true ) ) ) ) . '

    +

    ' . wp_kses( + ent2ncr( + sprintf( + __( 'This is an XML Sitemap generated by Jetpack, meant to be consumed by search engines like Google or Bing.', 'jetpack' ), + 'http://jetpack.com/', + 'https://www.google.com', + 'https://www.bing.com/' + ) + ), + array( + 'a' => array( + 'href' => true, + 'title' => true + ) + ) + ) . '

    +

    ' . wp_kses( + ent2ncr( + sprintf( + __( 'You can find more information on XML sitemaps at sitemaps.org', 'jetpack' ), + 'http://sitemaps.org' + ) + ), + array( + 'a' => array( + 'href' => true, + 'title' => true + ) + ) + ) . '

    diff --git a/plugins/jetpack/modules/sso.php b/plugins/jetpack/modules/sso.php index 81b8f883..5b9c34c6 100644 --- a/plugins/jetpack/modules/sso.php +++ b/plugins/jetpack/modules/sso.php @@ -532,13 +532,13 @@ class Jetpack_SSO { - + - + @@ -795,7 +795,7 @@ class Jetpack_SSO { JetpackTracking::record_user_event( 'sso_user_logged_in', array( 'user_found_with' => $user_found_with, 'user_connected' => (bool) $is_user_connected, - 'user_role' => Jetpack::init()->translate_current_user_to_role() + 'user_role' => Jetpack::translate_current_user_to_role() ) ); if ( ! $is_user_connected ) { @@ -1010,7 +1010,7 @@ class Jetpack_SSO { $error = sprintf( wp_kses( __( - 'Two-Step Authentication is required to access this site. Please visit your Security Settings to configure Two-step Authentication for your account.', + 'Two-Step Authentication is required to access this site. Please visit your Security Settings to configure Two-step Authentication for your account.', 'jetpack' ), array( 'a' => array( 'href' => array() ) ) diff --git a/plugins/jetpack/modules/sso/jetpack-sso-login-rtl.css b/plugins/jetpack/modules/sso/jetpack-sso-login-rtl.css index 48cc5e4f..23993a37 100644 --- a/plugins/jetpack/modules/sso/jetpack-sso-login-rtl.css +++ b/plugins/jetpack/modules/sso/jetpack-sso-login-rtl.css @@ -177,3 +177,9 @@ .jetpack-sso-form-display #backtoblog { margin: 24px 0 0; } + +.jetpack-sso-clear:after { + content: ""; + display: table; + clear: both; +} diff --git a/plugins/jetpack/modules/sso/jetpack-sso-login-rtl.min.css b/plugins/jetpack/modules/sso/jetpack-sso-login-rtl.min.css index 0445451c..77c6aa07 100644 --- a/plugins/jetpack/modules/sso/jetpack-sso-login-rtl.min.css +++ b/plugins/jetpack/modules/sso/jetpack-sso-login-rtl.min.css @@ -1 +1 @@ -#loginform{position:relative!important;padding-bottom:92px}.jetpack-sso .message{margin-top:20px}.jetpack-sso #login .message:first-child,.jetpack-sso #login h1+.message{margin-top:0}.jetpack-sso-repositioned #loginform{padding-bottom:26px}#loginform #jetpack-sso-wrap,#loginform #jetpack-sso-wrap *{box-sizing:border-box}#jetpack-sso-wrap__action,#jetpack-sso-wrap__user{display:none}.jetpack-sso-form-display #jetpack-sso-wrap__action,.jetpack-sso-form-display #jetpack-sso-wrap__user{display:block}#jetpack-sso-wrap{position:absolute;bottom:20px;padding:0 24px;margin-right:-24px;margin-left:-24px;width:100%}.jetpack-sso-repositioned #jetpack-sso-wrap{position:relative;bottom:auto;padding:0;margin-top:16px;margin-right:0;margin-left:0}.jetpack-sso-form-display #jetpack-sso-wrap{position:relative;bottom:auto;padding:0;margin-top:0;margin-right:0;margin-left:0}#loginform #jetpack-sso-wrap p{color:#777;margin-bottom:16px}#jetpack-sso-wrap a{display:block;width:100%;text-align:center;text-decoration:none}#jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:none}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:block}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.default,.jetpack-sso-form-display #loginform>div,.jetpack-sso-form-display #loginform>p{display:none}.jetpack-sso-form-display #loginform #jetpack-sso-wrap{display:block}.jetpack-sso-form-display #loginform{padding:26px 24px}.jetpack-sso-or{margin-bottom:16px;position:relative;text-align:center}.jetpack-sso-or:before{background:#E5E5E5;content:'';height:1px;position:absolute;right:0;top:50%;width:100%}.jetpack-sso-or span{background:#fff;color:#777;position:relative;padding:0 8px;text-transform:uppercase}.jetpack-sso.button{height:36px;line-height:34px;float:none;margin-bottom:16px;position:relative;width:100%}.jetpack-sso.button>span{position:relative;padding-right:30px}.jetpack-sso.button .genericon-wordpress{position:absolute;right:0;top:-3px;font-size:24px}@media screen and (max-width:782px){.jetpack-sso.button{line-height:22px}}#jetpack-sso-wrap__user img{border-radius:50%;display:block;margin:0 auto 16px}#jetpack-sso-wrap__user h2{font-size:21px;font-weight:300;margin-bottom:16px;text-align:center}#jetpack-sso-wrap__user h2 span{font-weight:700}.jetpack-sso-wrap__reauth{margin-bottom:16px}.jetpack-sso-form-display #nav{display:none}.jetpack-sso-form-display #backtoblog{margin:24px 0 0} \ No newline at end of file +#loginform{position:relative!important;padding-bottom:92px}.jetpack-sso .message{margin-top:20px}.jetpack-sso #login .message:first-child,.jetpack-sso #login h1+.message{margin-top:0}.jetpack-sso-repositioned #loginform{padding-bottom:26px}#loginform #jetpack-sso-wrap,#loginform #jetpack-sso-wrap *{box-sizing:border-box}#jetpack-sso-wrap__action,#jetpack-sso-wrap__user{display:none}.jetpack-sso-form-display #jetpack-sso-wrap__action,.jetpack-sso-form-display #jetpack-sso-wrap__user{display:block}#jetpack-sso-wrap{position:absolute;bottom:20px;padding:0 24px;margin-right:-24px;margin-left:-24px;width:100%}.jetpack-sso-repositioned #jetpack-sso-wrap{position:relative;bottom:auto;padding:0;margin-top:16px;margin-right:0;margin-left:0}.jetpack-sso-form-display #jetpack-sso-wrap{position:relative;bottom:auto;padding:0;margin-top:0;margin-right:0;margin-left:0}#loginform #jetpack-sso-wrap p{color:#777;margin-bottom:16px}#jetpack-sso-wrap a{display:block;width:100%;text-align:center;text-decoration:none}#jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:none}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:block}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.default,.jetpack-sso-form-display #loginform>div,.jetpack-sso-form-display #loginform>p{display:none}.jetpack-sso-form-display #loginform #jetpack-sso-wrap{display:block}.jetpack-sso-form-display #loginform{padding:26px 24px}.jetpack-sso-or{margin-bottom:16px;position:relative;text-align:center}.jetpack-sso-or:before{background:#E5E5E5;content:'';height:1px;position:absolute;right:0;top:50%;width:100%}.jetpack-sso-or span{background:#fff;color:#777;position:relative;padding:0 8px;text-transform:uppercase}.jetpack-sso.button{height:36px;line-height:34px;float:none;margin-bottom:16px;position:relative;width:100%}.jetpack-sso.button>span{position:relative;padding-right:30px}.jetpack-sso.button .genericon-wordpress{position:absolute;right:0;top:-3px;font-size:24px}@media screen and (max-width:782px){.jetpack-sso.button{line-height:22px}}#jetpack-sso-wrap__user img{border-radius:50%;display:block;margin:0 auto 16px}#jetpack-sso-wrap__user h2{font-size:21px;font-weight:300;margin-bottom:16px;text-align:center}#jetpack-sso-wrap__user h2 span{font-weight:700}.jetpack-sso-wrap__reauth{margin-bottom:16px}.jetpack-sso-form-display #nav{display:none}.jetpack-sso-form-display #backtoblog{margin:24px 0 0}.jetpack-sso-clear:after{content:"";display:table;clear:both} \ No newline at end of file diff --git a/plugins/jetpack/modules/sso/jetpack-sso-login.css b/plugins/jetpack/modules/sso/jetpack-sso-login.css index f1da883f..e79cf1f4 100644 --- a/plugins/jetpack/modules/sso/jetpack-sso-login.css +++ b/plugins/jetpack/modules/sso/jetpack-sso-login.css @@ -177,3 +177,9 @@ .jetpack-sso-form-display #backtoblog { margin: 24px 0 0; } + +.jetpack-sso-clear:after { + content: ""; + display: table; + clear: both; +} diff --git a/plugins/jetpack/modules/sso/jetpack-sso-login.js b/plugins/jetpack/modules/sso/jetpack-sso-login.js index 9de8c2b9..53d1a0e8 100644 --- a/plugins/jetpack/modules/sso/jetpack-sso-login.js +++ b/plugins/jetpack/modules/sso/jetpack-sso-login.js @@ -6,7 +6,7 @@ jQuery( document ).ready( function( $ ) { userLogin = $( '#user_login' ), ssoWrap = $( '#jetpack-sso-wrap' ), loginForm = $( '#loginform' ), - overflow = $( '
    ' ); + overflow = $( '
    ' ); // The overflow div is a poor man's clearfloat. We reposition the remember me // checkbox and the submit button within that to clear the float on the diff --git a/plugins/jetpack/modules/sso/jetpack-sso-login.min.css b/plugins/jetpack/modules/sso/jetpack-sso-login.min.css index 311ade50..c82862e0 100644 --- a/plugins/jetpack/modules/sso/jetpack-sso-login.min.css +++ b/plugins/jetpack/modules/sso/jetpack-sso-login.min.css @@ -1 +1 @@ -#loginform{position:relative!important;padding-bottom:92px}.jetpack-sso .message{margin-top:20px}.jetpack-sso #login .message:first-child,.jetpack-sso #login h1+.message{margin-top:0}.jetpack-sso-repositioned #loginform{padding-bottom:26px}#loginform #jetpack-sso-wrap,#loginform #jetpack-sso-wrap *{box-sizing:border-box}#jetpack-sso-wrap__action,#jetpack-sso-wrap__user{display:none}.jetpack-sso-form-display #jetpack-sso-wrap__action,.jetpack-sso-form-display #jetpack-sso-wrap__user{display:block}#jetpack-sso-wrap{position:absolute;bottom:20px;padding:0 24px;margin-left:-24px;margin-right:-24px;width:100%}.jetpack-sso-repositioned #jetpack-sso-wrap{position:relative;bottom:auto;padding:0;margin-top:16px;margin-left:0;margin-right:0}.jetpack-sso-form-display #jetpack-sso-wrap{position:relative;bottom:auto;padding:0;margin-top:0;margin-left:0;margin-right:0}#loginform #jetpack-sso-wrap p{color:#777;margin-bottom:16px}#jetpack-sso-wrap a{display:block;width:100%;text-align:center;text-decoration:none}#jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:none}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:block}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.default,.jetpack-sso-form-display #loginform>div,.jetpack-sso-form-display #loginform>p{display:none}.jetpack-sso-form-display #loginform #jetpack-sso-wrap{display:block}.jetpack-sso-form-display #loginform{padding:26px 24px}.jetpack-sso-or{margin-bottom:16px;position:relative;text-align:center}.jetpack-sso-or:before{background:#E5E5E5;content:'';height:1px;position:absolute;left:0;top:50%;width:100%}.jetpack-sso-or span{background:#fff;color:#777;position:relative;padding:0 8px;text-transform:uppercase}.jetpack-sso.button{height:36px;line-height:34px;float:none;margin-bottom:16px;position:relative;width:100%}.jetpack-sso.button>span{position:relative;padding-left:30px}.jetpack-sso.button .genericon-wordpress{position:absolute;left:0;top:-3px;font-size:24px}@media screen and (max-width:782px){.jetpack-sso.button{line-height:22px}}#jetpack-sso-wrap__user img{border-radius:50%;display:block;margin:0 auto 16px}#jetpack-sso-wrap__user h2{font-size:21px;font-weight:300;margin-bottom:16px;text-align:center}#jetpack-sso-wrap__user h2 span{font-weight:700}.jetpack-sso-wrap__reauth{margin-bottom:16px}.jetpack-sso-form-display #nav{display:none}.jetpack-sso-form-display #backtoblog{margin:24px 0 0} \ No newline at end of file +#loginform{position:relative!important;padding-bottom:92px}.jetpack-sso .message{margin-top:20px}.jetpack-sso #login .message:first-child,.jetpack-sso #login h1+.message{margin-top:0}.jetpack-sso-repositioned #loginform{padding-bottom:26px}#loginform #jetpack-sso-wrap,#loginform #jetpack-sso-wrap *{box-sizing:border-box}#jetpack-sso-wrap__action,#jetpack-sso-wrap__user{display:none}.jetpack-sso-form-display #jetpack-sso-wrap__action,.jetpack-sso-form-display #jetpack-sso-wrap__user{display:block}#jetpack-sso-wrap{position:absolute;bottom:20px;padding:0 24px;margin-left:-24px;margin-right:-24px;width:100%}.jetpack-sso-repositioned #jetpack-sso-wrap{position:relative;bottom:auto;padding:0;margin-top:16px;margin-left:0;margin-right:0}.jetpack-sso-form-display #jetpack-sso-wrap{position:relative;bottom:auto;padding:0;margin-top:0;margin-left:0;margin-right:0}#loginform #jetpack-sso-wrap p{color:#777;margin-bottom:16px}#jetpack-sso-wrap a{display:block;width:100%;text-align:center;text-decoration:none}#jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:none}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:block}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.default,.jetpack-sso-form-display #loginform>div,.jetpack-sso-form-display #loginform>p{display:none}.jetpack-sso-form-display #loginform #jetpack-sso-wrap{display:block}.jetpack-sso-form-display #loginform{padding:26px 24px}.jetpack-sso-or{margin-bottom:16px;position:relative;text-align:center}.jetpack-sso-or:before{background:#E5E5E5;content:'';height:1px;position:absolute;left:0;top:50%;width:100%}.jetpack-sso-or span{background:#fff;color:#777;position:relative;padding:0 8px;text-transform:uppercase}.jetpack-sso.button{height:36px;line-height:34px;float:none;margin-bottom:16px;position:relative;width:100%}.jetpack-sso.button>span{position:relative;padding-left:30px}.jetpack-sso.button .genericon-wordpress{position:absolute;left:0;top:-3px;font-size:24px}@media screen and (max-width:782px){.jetpack-sso.button{line-height:22px}}#jetpack-sso-wrap__user img{border-radius:50%;display:block;margin:0 auto 16px}#jetpack-sso-wrap__user h2{font-size:21px;font-weight:300;margin-bottom:16px;text-align:center}#jetpack-sso-wrap__user h2 span{font-weight:700}.jetpack-sso-wrap__reauth{margin-bottom:16px}.jetpack-sso-form-display #nav{display:none}.jetpack-sso-form-display #backtoblog{margin:24px 0 0}.jetpack-sso-clear:after{content:"";display:table;clear:both} \ No newline at end of file diff --git a/plugins/jetpack/modules/stats.php b/plugins/jetpack/modules/stats.php index 2040b682..79ac290d 100644 --- a/plugins/jetpack/modules/stats.php +++ b/plugins/jetpack/modules/stats.php @@ -21,21 +21,6 @@ defined( 'STATS_DASHBOARD_SERVER' ) or define( 'STATS_DASHBOARD_SERVER', 'dashbo add_action( 'jetpack_modules_loaded', 'stats_load' ); -// Tell HQ about changed settings -Jetpack_Sync::sync_options( __FILE__, - 'stats_options', - 'home', - 'siteurl', - 'blogname', - 'blogdescription', - 'gmt_offset', - 'timezone_string', - 'page_on_front', - 'permalink_structure', - 'category_base', - 'tag_base' -); - function stats_load() { global $wp_roles; @@ -44,14 +29,6 @@ function stats_load() { Jetpack::module_configuration_head( __FILE__, 'stats_configuration_head' ); Jetpack::module_configuration_screen( __FILE__, 'stats_configuration_screen' ); - // Tell HQ about changed posts - $post_stati = get_post_stati( array( 'public' => true ) ); // All public post stati - $post_stati[] = 'private'; // Content from private stati will be redacted - Jetpack_Sync::sync_posts( __FILE__, array( - 'post_types' => get_post_types( array( 'public' => true ) ), // All public post types - 'post_stati' => $post_stati, - ) ); - // Generate the tracking code after wp() has queried for posts. add_action( 'template_redirect', 'stats_template_redirect', 1 ); diff --git a/plugins/jetpack/modules/subscriptions.php b/plugins/jetpack/modules/subscriptions.php index d34c8715..0c614fb8 100644 --- a/plugins/jetpack/modules/subscriptions.php +++ b/plugins/jetpack/modules/subscriptions.php @@ -15,24 +15,6 @@ add_action( 'jetpack_modules_loaded', 'jetpack_subscriptions_load' ); -Jetpack_Sync::sync_options( - __FILE__, - 'home', - 'blogname', - 'siteurl', - 'page_on_front', - 'permalink_structure', - 'category_base', - 'rss_use_excerpt', - 'subscription_options', - 'stb_enabled', - 'stc_enabled', - 'tag_base' -); - -Jetpack_Sync::sync_posts( __FILE__ ); -Jetpack_Sync::sync_comments( __FILE__ ); - function jetpack_subscriptions_load() { Jetpack::enable_module_configurable( __FILE__ ); Jetpack::module_configuration_load( __FILE__, 'jetpack_subscriptions_configuration_load' ); diff --git a/plugins/jetpack/modules/theme-tools/random-redirect.php b/plugins/jetpack/modules/theme-tools/random-redirect.php index a4f29d80..2e58bee6 100644 --- a/plugins/jetpack/modules/theme-tools/random-redirect.php +++ b/plugins/jetpack/modules/theme-tools/random-redirect.php @@ -1,7 +1,7 @@ get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = %s AND post_password = '' AND post_status = 'publish' %s ORDER BY RAND() LIMIT 1", $post_type, $random_author_query ) ); } - + $permalink = get_permalink( $random_id ); wp_safe_redirect( $permalink ); exit; diff --git a/plugins/jetpack/modules/theme-tools/site-breadcrumbs.php b/plugins/jetpack/modules/theme-tools/site-breadcrumbs.php index 3b783eea..7919d5df 100644 --- a/plugins/jetpack/modules/theme-tools/site-breadcrumbs.php +++ b/plugins/jetpack/modules/theme-tools/site-breadcrumbs.php @@ -2,7 +2,7 @@ /** * Plugin Name: Site Breadcrumbs * Plugin URI: http://wordpress.com - * Description: Quickly add breadcrumbs to the single view of a hierarchical post type + * Description: Quickly add breadcrumbs to the single view of a hierarchical post type or a hierarchical taxonomy. * Author: Automattic * Version: 1.0 * Author URI: http://wordpress.com @@ -10,28 +10,70 @@ */ function jetpack_breadcrumbs() { - if ( ! is_page() || is_front_page() ) { + $taxonomy = is_category() ? 'category' : get_query_var( 'taxonomy' ); + $is_taxonomy_hierarchical = is_taxonomy_hierarchical( $taxonomy ); + + $post_type = is_page() ? 'page' : get_query_var( 'post_type' ); + $is_post_type_hierarchical = is_post_type_hierarchical( $post_type ); + + if ( ! ( $is_post_type_hierarchical || $is_taxonomy_hierarchical ) || is_front_page() ) { return; } - global $post; + $breadcrumb = ''; - $ancestors = array_reverse( get_post_ancestors( $post->ID ) ); + if ( $is_post_type_hierarchical ) { + $post_id = get_queried_object_id(); + $ancestors = array_reverse( get_post_ancestors( $post_id ) ); + if ( $ancestors ) { + foreach ( $ancestors as $ancestor ) { + $breadcrumb .= '' . esc_html( get_the_title( $ancestor ) ) . ''; + } + } + $breadcrumb .= '' . esc_html( get_the_title( $post_id ) ) . ''; + } elseif ( $is_taxonomy_hierarchical ) { + $current = get_term( get_queried_object_id(), $taxonomy ); - $before = ''; + if ( is_wp_error( $current ) ) { + return; + } - $home = '' . __( 'Home', 'jetpack' ) . ''; + if ( $current->parent ) { + $breadcrumb = jetpack_get_term_parents( $current->parent, $taxonomy ); + } - $breadcrumb = ''; + $breadcrumb .= '' . esc_html( $current->name ) . ''; + } - if ( $ancestors ) { - foreach ( $ancestors as $ancestor ) { - $breadcrumb .= '' . esc_html( get_the_title( $ancestor ) ) . ''; - } + $home = '' . esc_html__( 'Home', 'jetpack' ) . ''; + + echo ''; +} + +/** + * Return the parents for a given taxonomy term ID. + * + * @param int $term Taxonomy term whose parents will be returned. + * @param string $taxonomy Taxonomy name that the term belongs to. + * @param array $visited Terms already added to prevent duplicates. + * + * @return string A list of links to the term parents. + */ +function jetpack_get_term_parents( $term, $taxonomy, $visited = array() ) { + $parent = get_term( $term, $taxonomy ); + + if ( is_wp_error( $parent ) ) { + return $parent; + } + + $chain = ''; + + if ( $parent->parent && ( $parent->parent != $parent->term_id ) && ! in_array( $parent->parent, $visited ) ) { + $visited[] = $parent->parent; + $chain .= jetpack_get_term_parents( $parent->parent, $taxonomy, $visited ); } - $breadcrumb .= '' . esc_html( get_the_title( $post->ID ) ) . ''; + $chain .= '' . $parent->name . ''; - echo $before . $home . $breadcrumb . $after; + return $chain; } diff --git a/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php b/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php index 3d89a071..0cc0b9cb 100644 --- a/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php +++ b/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php @@ -20,7 +20,7 @@ class Jetpack_Tiled_Gallery { public function tiles_enabled() { // Check the setting status - return '' != get_option( 'tiled_galleries' ); + return '' != Jetpack_Options::get_option_and_ensure_autoload( 'tiled_galleries', '' ); } public function set_atts( $atts ) { diff --git a/plugins/jetpack/modules/verification-tools/blog-verification-tools.php b/plugins/jetpack/modules/verification-tools/blog-verification-tools.php index 7320a8bf..e69e248e 100644 --- a/plugins/jetpack/modules/verification-tools/blog-verification-tools.php +++ b/plugins/jetpack/modules/verification-tools/blog-verification-tools.php @@ -31,7 +31,7 @@ function jetpack_verification_options_init() { add_action( 'admin_init', 'jetpack_verification_options_init' ); function jetpack_verification_print_meta() { - $verification_services_codes = get_option( 'verification_services_codes' ); + $verification_services_codes = Jetpack_Options::get_option_and_ensure_autoload( 'verification_services_codes', '0' ); if ( is_array( $verification_services_codes ) ) { $ver_output = "\n"; foreach ( jetpack_verification_services() as $name => $service ) { diff --git a/plugins/jetpack/modules/videopress/js/videopress-admin.js b/plugins/jetpack/modules/videopress/js/videopress-admin.js index 5daac4f4..76f26272 100644 --- a/plugins/jetpack/modules/videopress/js/videopress-admin.js +++ b/plugins/jetpack/modules/videopress/js/videopress-admin.js @@ -465,7 +465,7 @@ var VideoPressModal = new VideoPressModalView(); // Configuration screen behavior - $(document).on( 'ready', function() { + $(document).ready( function() { var $form = $( '#videopress-settings' ); // Not on a configuration screen diff --git a/plugins/jetpack/modules/widgets/contact-info.php b/plugins/jetpack/modules/widgets/contact-info.php index 552f9b91..fd5bef61 100644 --- a/plugins/jetpack/modules/widgets/contact-info.php +++ b/plugins/jetpack/modules/widgets/contact-info.php @@ -42,26 +42,10 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) { * Enqueue scripts and styles. */ public function enqueue_scripts() { - $google_url = 'https://maps.googleapis.com/maps/api/js'; - /** - * Set a Google Maps API Key. - * - * @since 4.1.0 - * - * @param string $key Google Maps API Key - */ - $key = apply_filters( 'jetpack_google_maps_api_key', null ); - - if ( ! empty( $key ) ) { - $google_url = add_query_arg( 'key', $key, $google_url ); - } - - wp_enqueue_script( 'jquery' ); - wp_enqueue_script( 'google-maps', esc_url( $google_url, null, null ) ); - wp_enqueue_script( 'contact-info-map-js', plugins_url( 'contact-info/contact-info-map.js', __FILE__ ), array( 'jquery', 'google-maps' ), 20150127 ); - wp_enqueue_style( 'contact-info-map-css', plugins_url( 'contact-info/contact-info-map.css', __FILE__ ), null, 20150127 ); + wp_enqueue_style( 'contact-info-map-css', plugins_url( 'contact-info/contact-info-map.css', __FILE__ ), null, 20160623 ); } + /** * Return an associative array of default values * @@ -75,7 +59,8 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) { 'address' => __( "3999 Mission Boulevard,\nSan Diego CA 92109", 'jetpack' ), 'phone' => _x( '1-202-555-1212', 'Example of a phone number', 'jetpack' ), 'hours' => __( "Lunch: 11am - 2pm \nDinner: M-Th 5pm - 11pm, Fri-Sat:5pm - 1am", 'jetpack' ), - 'showmap' => 1, + 'showmap' => 0, + 'apikey' => null, 'lat' => null, 'lon' => null ); @@ -111,12 +96,17 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) { $showmap = $instance['showmap']; + /** This action is documented in modules/widgets/contact-info.php */ if ( $showmap && $this->has_good_map( $instance ) ) { - - $lat = $instance['lat']; - $lon = $instance['lon']; - - echo $this->build_map( $lat, $lon ); + /** + * Set a Google Maps API Key. + * + * @since 4.1.0 + * + * @param string $api_key Google Maps API Key + */ + $api_key = apply_filters( 'jetpack_google_maps_api_key', $instance['apikey'] ); + echo $this->build_map( $instance['address'], $api_key ); } $map_link = $this->build_map_link( $instance['address'] ); @@ -173,6 +163,7 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) { $instance['address'] = wp_kses( $new_instance['address'], array() ); $instance['phone'] = wp_kses( $new_instance['phone'], array() ); $instance['hours'] = wp_kses( $new_instance['hours'], array() ); + $instance['apikey'] = wp_kses( isset( $new_instance['apikey'] ) ? $new_instance['apikey'] : $old_instance['apikey'], array() ); $instance['lat'] = isset( $old_instance['lat'] ) ? floatval( $old_instance['lat'] ) : 0; $instance['lon'] = isset( $old_instance['lon'] ) ? floatval( $old_instance['lon'] ) : 0; @@ -186,7 +177,7 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) { $address = $this->urlencode_address( $instance['address'] ); $path = "http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=" . $address; /** This action is documented in modules/widgets/contact-info.php */ - $key = apply_filters( 'jetpack_google_maps_api_key', null ); + $key = apply_filters( 'jetpack_google_maps_api_key', $instance['apikey'] ); if ( ! empty( $key ) ) { $path = add_query_arg( 'key', $key, $path ); @@ -239,6 +230,8 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) { */ function form( $instance ) { $instance = wp_parse_args( $instance, $this->defaults() ); + wp_enqueue_script( 'contact-info-admin', plugins_url( 'contact-info/contact-info-admin.js', __FILE__ ), array( 'jquery' ), 20160727 ); + ?>

    @@ -251,7 +244,7 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) { has_good_map( $instance ) ) { ?> - /> + />

    + + +

    @@ -298,19 +301,14 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) { * * @return string HTML of the map */ - function build_map( $lat, $lon ) { + function build_map( $address, $api_key = null ) { $this->enqueue_scripts(); + $src = add_query_arg( 'q', urlencode( $address ), 'https://www.google.com/maps/embed/v1/place' ); + if ( ! empty( $api_key ) ) { + $src = add_query_arg( 'key', $api_key, $src ); + } - $lat = esc_attr( $lat ); - $lon = esc_attr( $lon ); - $html = << - - -

    -EOT; - - return $html; + return ''; } /** diff --git a/plugins/jetpack/modules/widgets/contact-info/contact-info-admin.js b/plugins/jetpack/modules/widgets/contact-info/contact-info-admin.js new file mode 100644 index 00000000..e38f0dac --- /dev/null +++ b/plugins/jetpack/modules/widgets/contact-info/contact-info-admin.js @@ -0,0 +1,8 @@ +(function( $ ) { + $( document ).on( 'change', '.jp-contact-info-showmap', function() { + var $checkbox = $( this ), + isChecked = $checkbox.is( ':checked' ); + + $checkbox.closest( '.widget' ).find( '.jp-contact-info-apikey' ).toggle( isChecked ); + }); +})( window.jQuery ); diff --git a/plugins/jetpack/modules/widgets/contact-info/contact-info-map.css b/plugins/jetpack/modules/widgets/contact-info/contact-info-map.css index 47629e9b..7aa9e698 100644 --- a/plugins/jetpack/modules/widgets/contact-info/contact-info-map.css +++ b/plugins/jetpack/modules/widgets/contact-info/contact-info-map.css @@ -1,11 +1,4 @@ -.contact-info-map-canvas { - height: 216px; - margin: 0; - padding: 0; - overflow: hidden; -} - -/* Prevent Google maps controls from being hidden */ -.gmnoprint img { - max-width: none !important; -} +.contact-map { + max-width: 100%; + border: 0; +} \ No newline at end of file diff --git a/plugins/jetpack/modules/widgets/contact-info/contact-info-map.js b/plugins/jetpack/modules/widgets/contact-info/contact-info-map.js deleted file mode 100644 index c7036222..00000000 --- a/plugins/jetpack/modules/widgets/contact-info/contact-info-map.js +++ /dev/null @@ -1,41 +0,0 @@ -/* global google */ -/* jshint unused:false */ -jQuery( function( $ ) { - - function setupContactMaps( rootElement ) { - rootElement = $( rootElement || document.body ); - - rootElement.find( 'div.contact-map' ).each( function() { - - // get lat and lon from hidden input values - var lat = jQuery(this).find('.contact-info-map-lat').val(), - lon = jQuery(this).find('.contact-info-map-lon').val(), - lat_lon = new google.maps.LatLng( lat, lon ), - mapOptions = { - zoom: 16, - center: lat_lon, - mapTypeId: google.maps.MapTypeId.ROADMAP - }, - map = new google.maps.Map(jQuery(this).find('.contact-info-map-canvas')[0], mapOptions), - marker = new google.maps.Marker({ - map: map, - position: lat_lon - }); - - google.maps.event.addListenerOnce(map, 'mouseover', function() { - google.maps.event.trigger(map, 'resize'); - }); - - }); - } - - setupContactMaps(); - - if ( 'undefined' !== typeof wp && wp.customize && wp.customize.selectiveRefresh ) { - wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) { - if ( wp.isJetpackWidgetPlaced( placement, 'widget_contact_info' ) ) { - setupContactMaps( placement.container ); - } - } ); - } -} ); diff --git a/plugins/jetpack/modules/widgets/gravatar-profile.php b/plugins/jetpack/modules/widgets/gravatar-profile.php index 00e701e2..a82865ea 100644 --- a/plugins/jetpack/modules/widgets/gravatar-profile.php +++ b/plugins/jetpack/modules/widgets/gravatar-profile.php @@ -115,7 +115,7 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { defaults ); ?> diff --git a/plugins/jetpack/modules/widgets/top-posts.php b/plugins/jetpack/modules/widgets/top-posts.php index 0756a198..fa1222ff 100644 --- a/plugins/jetpack/modules/widgets/top-posts.php +++ b/plugins/jetpack/modules/widgets/top-posts.php @@ -246,7 +246,7 @@ class Jetpack_Top_Posts_Widget extends WP_Widget { $get_image_options = array( 'fallback_to_avatars' => true, /** This filter is documented in modules/stats.php */ - 'gravatar_default' => apply_filters( 'jetpack_static_url', set_url_scheme( 'http://en.wordpress.com/i/logo/white-gray-80.png' ) ), + 'gravatar_default' => apply_filters( 'jetpack_static_url', set_url_scheme( 'https://en.wordpress.com/i/logo/white-gray-80.png' ) ), ); if ( 'grid' == $display ) { $get_image_options['avatar_size'] = 200; diff --git a/plugins/jetpack/modules/widgets/top-posts/style.css b/plugins/jetpack/modules/widgets/top-posts/style.css index c301ee16..ceeae12b 100644 --- a/plugins/jetpack/modules/widgets/top-posts/style.css +++ b/plugins/jetpack/modules/widgets/top-posts/style.css @@ -28,7 +28,7 @@ margin: 0 2px 4px 0; } -.widget-grid-view-image:image:nth-child(even) { +.widget-grid-view-image:nth-child(even) { float: right; } diff --git a/plugins/jetpack/modules/widgets/twitter-timeline.php b/plugins/jetpack/modules/widgets/twitter-timeline.php index ef9ed60d..4cb663d2 100644 --- a/plugins/jetpack/modules/widgets/twitter-timeline.php +++ b/plugins/jetpack/modules/widgets/twitter-timeline.php @@ -2,7 +2,7 @@ /* * Based on Evolution Twitter Timeline - * (http://wordpress.org/extend/plugins/evolution-twitter-timeline/) + * (https://wordpress.org/extend/plugins/evolution-twitter-timeline/) * For details on Twitter Timelines see: * - https://twitter.com/settings/widgets * - https://dev.twitter.com/docs/embedded-timelines diff --git a/plugins/jetpack/readme.txt b/plugins/jetpack/readme.txt index b5e6c2d5..ef0d79ef 100644 --- a/plugins/jetpack/readme.txt +++ b/plugins/jetpack/readme.txt @@ -1,9 +1,9 @@ === Jetpack by WordPress.com === Contributors: automattic, adamkheckler, aduth, akirk, allendav, alternatekev, andy, annezazu, apeatling, azaozz, batmoo, barry, beaulebens, blobaugh, cainm, cena, cfinke, chaselivingston, chellycat, csonnek, danielbachhuber, davoraltman, daniloercoli, designsimply, dllh, drawmyface, dsmart, dzver, ebinnion, eliorivero, enej, eoigal, ethitter, gcorne, georgestephanis, gibrown, goldsounds, hew, hugobaeta, hypertextranch, iammattthomas, iandunn, jacobshere, jblz, jeherve, jenhooks, jenia, jgs, jkudish, jmdodd, Joen, johnjamesjacoby, jshreve, koke, kraftbj, lamdayap, lancewillett, lschuyler, macmanx, martinremy, matt, matveb, mattwiebe, maverick3x6, mcsf, mdawaffe, michael-arestad, migueluy, mikeyarce, mkaz, nancythanki, nickmomrik, obenland, pento, professor44, rachelsquirrel, rdcoll, ryancowles, richardmuscat, richardmtl, roccotripaldi, samhotchkiss, scarstocea, sdquirk, stefmattana, stephdau, tmoorewp, Viper007Bond, westi, yoavf, zinigor Tags: WordPress.com, jet pack, comments, contact, gallery, performance, sharing, security, shortcodes, stats, subscriptions, widgets -Stable tag: 4.1.1 -Requires at least: 4.4 -Tested up to: 4.5.3 +Stable tag: 4.2.2 +Requires at least: 4.5 +Tested up to: 4.6 Increase your traffic, view your stats, speed up your site, and protect yourself from hackers with Jetpack. @@ -75,6 +75,74 @@ There are opportunities for developers at all levels to contribute. [Learn more == Changelog == += 4.2.2 = + +* Release date: August 19th, 2016 + +**Bug Fixes:** + +* We fixed the code which displays the Facebook share count to accomodate Facebook's new data structure. +* We fixed an issue which caused PHP notices to get logged for users of the Twenty Fourteen theme. +* We fixed an issue with the Minileven mobile theme which was preventing it from loading. +* Improved Sync performance. +* Increase security by sanitizing a URL used in the SSO process. + += 4.2.1 = + +* Release date: August 17th, 2016 + +**Bug Fixes:** + +* We fixed a conflict between Jetpack and W3 Total Cache. +* We fixed some issues with Publicize and Custom Post Types. +* Very large Multisite networks with lots of users can now be synchronized with WordPress.com. +* We improved the synchronization process between your site and WordPress.com. + += 4.2 = + +* Release date: August 10th, 2016 + +**Performance Enhancements:** + +* We’ve improved Jetpack’s performance by making calls to the database more efficient; essentially, Jetpack is doing less on each page load, making things faster. #4281, #4316 +* We’ve ensured that every feature uses information that is up to date by completely refactoring the way information was synchronized between your site and WordPress.com. +* We've improved the way Jetpack queries for information about features, which results in less overall queries. + +**Exciting Feature and UI Improvements:** + +* We now track your visitor views of Carousel images in stats. +* You can now customize advanced typographic settings like ligatures in the Custom CSS editor with new support for the `font-feature-settings` property. +* We’ve improved the experience when you don’t actually have enough posts to Infinitely Scroll. +* Our Contact Info Widget allows you to enter a Google Maps API Key which is now required by Google if you want to display a map. + +**Security:** + +* We’re continuing our efforts to harden Jetpack security, by implementing the `hash_equals()` function to avoid timing attacks when comparing strings. We also improved security on CSVs exported from your contact form. + +**Slightly Less Exciting Feature Improvements:** + +* The Cartodb shortcode has been changed to match the new product name, Carto. +* The YouTube shortcode now uses the content width defined by the theme when available, even if an embed size was defined in an old version of WordPress. +* Breadcrumbs now support hierarchical post types and taxonomies. +* We’ve added the Portfolio Post Type to the WordPress.com REST API whitelist. +* There are a few new parameters for the Dailymotion shortcode. + +**Improved Compatibility:** + +* We now work well with WP Stagecoach staging sites, so you should not see any future impact on production sites. +* We had some PHP notices popping up in the WooCommerce plugin wizard screen, these are gone. + +**Bug Fixes:** + +* We stopped loading compatibility stylesheets on the default theme's singular views for Infinite Scroll. +* Debug tests forwarded through the contact form in the Jetpack Debug menu are now successfully sent to the support team. +* We’ve removed the PHP notices you might have seen when moderating comments. +* There are no longer PHP notices cropping up when publishing via Cron. +* We’ve fixed the official Sharing buttons so they now line up just right. +* The PHP warnings of Sitemaps stylesheets have been eliminated. +* We’ve done away with the warnings that appeared when Tonesque processes a file which claims to be one filetype, but is actually another. +* We’ve exterminated PHP notices that appeared when using Random Redirect, as well as when the author wasn't set. + = 4.1.1 = * Release date: July 7th, 2016 @@ -187,7 +255,7 @@ Bug Fixes: * We accidentally removed the ability for Open Graph to select images from slideshows, it’s up and running again. * There was an issue where Open Graph meta tags weren’t being set when your homepage is a “Static Front Page”, it’s working again. * In rare cases when developers were customizing Photon they were seeing a PHP notice when arguments were passed as a string rather than an array. This has been fixed. -* We’ve fixed an issue where Protect’s backup math form wasn’t showing on custom frontend login forms. +* We’ve fixed an issue where Protect’s backup math form wasn’t showing on custom front end login forms. * When setting up WooCommerce you might have seen a Related Posts notice which didn’t belong. We’ve eliminated them. * If you’ve been using our sharing tool with unofficial sharing buttons you might have noticed your sharing numbers were missing. They’re now back. * In unique situations where special characters were used in sitemap stylesheets an error would occur; that has been remedied. diff --git a/plugins/jetpack/sal/class.json-api-links.php b/plugins/jetpack/sal/class.json-api-links.php index b5278569..39c71067 100644 --- a/plugins/jetpack/sal/class.json-api-links.php +++ b/plugins/jetpack/sal/class.json-api-links.php @@ -7,11 +7,11 @@ class WPCOM_JSON_API_Links { private static $instance; public static function getInstance() { - if (null === static::$instance) { - static::$instance = new static(); + if ( null === self::$instance ) { + self::$instance = new self(); } - - return static::$instance; + + return self::$instance; } // protect these methods for singleton diff --git a/plugins/jetpack/sal/class.json-api-post-base.php b/plugins/jetpack/sal/class.json-api-post-base.php index 8a422e19..42bbc3c7 100644 --- a/plugins/jetpack/sal/class.json-api-post-base.php +++ b/plugins/jetpack/sal/class.json-api-post-base.php @@ -289,7 +289,6 @@ abstract class SAL_Post { $result['duration'] = (int) $metadata['duration']; } - /** This filter is documented in class.jetpack-sync.php */ return (object) apply_filters( 'get_attachment', $result ); } diff --git a/plugins/jetpack/sal/class.json-api-site-base.php b/plugins/jetpack/sal/class.json-api-site-base.php index c4f6afc8..0b58d8b1 100644 --- a/plugins/jetpack/sal/class.json-api-site-base.php +++ b/plugins/jetpack/sal/class.json-api-site-base.php @@ -395,7 +395,7 @@ abstract class SAL_Site { } function get_unmapped_url() { - return get_site_url( $this->blog_id ); + return get_site_url( get_current_blog_id() ); } function get_theme_slug() { diff --git a/plugins/jetpack/sal/class.json-api-site-jetpack-base.php b/plugins/jetpack/sal/class.json-api-site-jetpack-base.php index fdeec82c..10de82a3 100644 --- a/plugins/jetpack/sal/class.json-api-site-jetpack-base.php +++ b/plugins/jetpack/sal/class.json-api-site-jetpack-base.php @@ -10,12 +10,24 @@ abstract class Abstract_Jetpack_Site extends SAL_Site { abstract protected function get_theme_support( $feature_name ); - abstract protected function get_mock_option( $name ); - abstract protected function get_jetpack_version(); abstract protected function get_updates(); + abstract protected function main_network_site(); + + abstract protected function wp_version(); + + abstract protected function max_upload_size(); + + abstract protected function is_main_network(); + + abstract protected function is_multi_site(); + + abstract protected function is_version_controlled(); + + abstract protected function file_system_write_access(); + function before_render() { } @@ -35,9 +47,10 @@ abstract class Abstract_Jetpack_Site extends SAL_Site { } function after_render_options( &$options ) { + $options['jetpack_version'] = $this->get_jetpack_version(); - if ( $main_network_site = $this->get_mock_option( 'main_network_site' ) ) { + if ( $main_network_site = $this->main_network_site() ) { $options['main_network_site'] = (string) rtrim( $main_network_site, '/' ); } @@ -45,22 +58,22 @@ abstract class Abstract_Jetpack_Site extends SAL_Site { $options['active_modules'] = (array) array_values( $active_modules ); } - $options['software_version'] = (string) $this->get_mock_option( 'wp_version' ); - $options['max_upload_size'] = $this->get_mock_option( 'max_upload_size', false ); + $options['software_version'] = (string) $this->wp_version(); + $options['max_upload_size'] = $this->max_upload_size(); // Sites have to prove that they are not main_network site. // If the sync happends right then we should be able to see that we are not dealing with a network site - $options['is_multi_network'] = (bool) $this->get_mock_option( 'is_main_network', true ); - $options['is_multi_site'] = (bool) $this->get_mock_option( 'is_multi_site', true ); + $options['is_multi_network'] = (bool) $this->is_main_network(); + $options['is_multi_site'] = (bool) $this->is_multi_site(); $file_mod_disabled_reasons = array_keys( array_filter( array( 'automatic_updater_disabled' => (bool) $this->get_constant( 'AUTOMATIC_UPDATER_DISABLED' ), // WP AUTO UPDATE CORE defaults to minor, '1' if true and '0' if set to false. - 'wp_auto_update_core_disabled' => ! ( (bool) $this->get_constant( 'WP_AUTO_UPDATE_CORE' ) ), - 'is_version_controlled' => (bool) $this->get_mock_option( 'is_version_controlled' ), + 'wp_auto_update_core_disabled' => ! ( (bool) $this->get_constant( 'WP_AUTO_UPDATE_CORE' ) ), + 'is_version_controlled' => (bool) $this->is_version_controlled(), // By default we assume that site does have system write access if the value is not set yet. - 'has_no_file_system_write_access' => ! (bool) ( $this->get_mock_option( 'has_file_system_write_access' ) ), - 'disallow_file_mods' => (bool) $this->get_constant( 'DISALLOW_FILE_MODS' ), + 'has_no_file_system_write_access' => ! (bool) $this->file_system_write_access(), + 'disallow_file_mods' => (bool) $this->get_constant( 'DISALLOW_FILE_MODS' ), ) ) ); $options['file_mod_disabled'] = empty( $file_mod_disabled_reasons ) ? false : $file_mod_disabled_reasons; @@ -79,11 +92,11 @@ abstract class Abstract_Jetpack_Site extends SAL_Site { } function is_multisite() { - return (bool) $this->get_mock_option( 'is_multi_site' ); + return (bool) is_multisite(); } function is_single_user_site() { - return (bool) $this->get_mock_option( 'single_user_site' ); + return (bool) Jetpack::is_single_user_site(); } function featured_images_enabled() { diff --git a/plugins/jetpack/sal/class.json-api-site-jetpack.php b/plugins/jetpack/sal/class.json-api-site-jetpack.php index 32dd7a32..99a3fa96 100644 --- a/plugins/jetpack/sal/class.json-api-site-jetpack.php +++ b/plugins/jetpack/sal/class.json-api-site-jetpack.php @@ -6,10 +6,6 @@ require_once dirname( __FILE__ ) . '/class.json-api-post-jetpack.php'; // this code runs on Jetpack (.org) sites class Jetpack_Site extends Abstract_Jetpack_Site { - protected function get_mock_option( $name ) { - return get_option( 'jetpack_'.$name ); - } - protected function get_constant( $name ) { if ( defined( $name) ) { return constant( $name ); @@ -17,6 +13,35 @@ class Jetpack_Site extends Abstract_Jetpack_Site { return null; } + protected function main_network_site() { + return network_site_url(); + } + + protected function wp_version() { + global $wp_version; + return $wp_version; + } + + protected function max_upload_size() { + return wp_max_upload_size(); + } + + protected function is_main_network() { + return Jetpack::is_multi_network(); + } + + protected function is_multi_site() { + return is_multisite(); + } + + protected function is_version_controlled() { + return Jetpack_Sync_Functions::is_version_controlled(); + } + + protected function file_system_write_access() { + return Jetpack_Sync_Functions::file_system_write_access(); + } + protected function current_theme_supports( $feature_name ) { return current_theme_supports( $feature_name ); } diff --git a/plugins/jetpack/sync/class.jetpack-sync-actions.php b/plugins/jetpack/sync/class.jetpack-sync-actions.php new file mode 100644 index 00000000..65eff323 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-actions.php @@ -0,0 +1,285 @@ +post_type, Jetpack_Sync_Settings::get_setting( 'post_types_blacklist' ) ) ) { + return false; + } + + return $should_publicize; + } + + static function set_is_importing_true() { + Jetpack_Sync_Settings::set_importing( true ); + } + + static function send_data( $data, $codec_name, $sent_timestamp, $queue_id ) { + Jetpack::load_xml_rpc_client(); + + $url = add_query_arg( array( + 'sync' => '1', // add an extra parameter to the URL so we can tell it's a sync action + 'codec' => $codec_name, // send the name of the codec used to encode the data + 'timestamp' => $sent_timestamp, // send current server time so we can compensate for clock differences + 'queue' => $queue_id, // sync or full_sync + ), Jetpack::xmlrpc_api_url() ); + + $rpc = new Jetpack_IXR_Client( array( + 'url' => $url, + 'user_id' => JETPACK_MASTER_USER, + 'timeout' => 30, + ) ); + + $result = $rpc->query( 'jetpack.syncActions', $data ); + + if ( ! $result ) { + return $rpc->get_jetpack_error(); + } + + return $rpc->getResponse(); + } + + static function schedule_initial_sync() { + // we need this function call here because we have to run this function + // reeeeally early in init, before WP_CRON_LOCK_TIMEOUT is defined. + wp_functionality_constants(); + + if ( is_multisite() ) { + // stagger initial syncs for multisite blogs so they don't all pile on top of each other + $time_offset = ( rand() / getrandmax() ) * self::INITIAL_SYNC_MULTISITE_INTERVAL * get_blog_count(); + } else { + $time_offset = 1; + } + + self::schedule_full_sync( + array( + 'options' => true, + 'network_options' => true, + 'functions' => true, + 'constants' => true, + 'users' => 'initial' + ), + $time_offset + ); + } + + static function schedule_full_sync( $modules = null, $time_offset = 1 ) { + if ( ! self::sync_allowed() ) { + return false; + } + + if ( self::is_scheduled_full_sync() ) { + self::unschedule_all_full_syncs(); + } + + if ( $modules ) { + wp_schedule_single_event( time() + $time_offset, 'jetpack_sync_full', array( $modules ) ); + } else { + wp_schedule_single_event( time() + $time_offset, 'jetpack_sync_full' ); + } + + if ( $time_offset === 1 ) { + spawn_cron(); + } + + return true; + } + + static function unschedule_all_full_syncs() { + foreach ( _get_cron_array() as $timestamp => $cron ) { + if ( ! empty( $cron['jetpack_sync_full'] ) ) { + foreach( $cron['jetpack_sync_full'] as $key => $config ) { + wp_unschedule_event( $timestamp, 'jetpack_sync_full', $config['args'] ); + } + } + } + } + + static function is_scheduled_full_sync( $modules = null ) { + if ( is_null( $modules ) ) { + $crons = _get_cron_array(); + + foreach ( $crons as $timestamp => $cron ) { + if ( ! empty( $cron['jetpack_sync_full'] ) ) { + return true; + } + } + return false; + } + + return wp_next_scheduled( 'jetpack_sync_full', array( $modules ) ); + } + + static function do_full_sync( $modules = null ) { + if ( ! self::sync_allowed() ) { + return; + } + + self::initialize_listener(); + Jetpack_Sync_Modules::get_module( 'full-sync' )->start( $modules ); + self::do_cron_sync(); // immediately run a cron sync, which sends pending data + } + + static function minute_cron_schedule( $schedules ) { + if( ! isset( $schedules["1min"] ) ) { + $schedules["1min"] = array( + 'interval' => 60, + 'display' => __( 'Every minute' ) + ); + } + return $schedules; + } + + // try to send actions until we run out of things to send, + // or have to wait more than 15s before sending again, + // or we hit a lock or some other sending issue + static function do_cron_sync() { + if ( ! self::sync_allowed() ) { + return; + } + + self::initialize_sender(); + + // remove shutdown hook - no need to sync twice + if ( has_action( 'shutdown', array( self::$sender, 'do_sync' ) ) ) { + remove_action( 'shutdown', array( self::$sender, 'do_sync' ) ); + } + + do { + $next_sync_time = self::$sender->get_next_sync_time(); + + if ( $next_sync_time ) { + $delay = $next_sync_time - time() + 1; + if ( $delay > 15 ) { + break; + } elseif ( $delay > 0 ) { + sleep( $delay ); + } + } + + $result = self::$sender->do_sync(); + } while ( $result ); + } + + static function initialize_listener() { + require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php'; + self::$listener = Jetpack_Sync_Listener::get_instance(); + } + + static function initialize_sender() { + require_once dirname( __FILE__ ) . '/class.jetpack-sync-sender.php'; + self::$sender = Jetpack_Sync_Sender::get_instance(); + + // bind the sending process + add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 4 ); + } +} + +// Allow other plugins to add filters before we initialize the actions. +// Load the listeners if before modules get loaded so that we can capture version changes etc. +add_action( 'init', array( 'Jetpack_Sync_Actions', 'init' ), 90 ); + +// We need to define this here so that it's hooked before `updating_jetpack_version` is called +add_action( 'updating_jetpack_version', array( 'Jetpack_Sync_Actions', 'schedule_initial_sync' ), 10 ); diff --git a/plugins/jetpack/sync/class.jetpack-sync-defaults.php b/plugins/jetpack/sync/class.jetpack-sync-defaults.php new file mode 100644 index 00000000..f08a8268 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-defaults.php @@ -0,0 +1,276 @@ + 'wp_max_upload_size', + 'is_main_network' => array( 'Jetpack', 'is_multi_network' ), + 'is_multi_site' => 'is_multisite', + 'main_network_site' => array( 'Jetpack_Sync_Functions', 'main_network_site_url' ), + 'site_url' => array( 'Jetpack_Sync_Functions', 'site_url' ), + 'home_url' => array( 'Jetpack_Sync_Functions', 'home_url' ), + 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ), + 'updates' => array( 'Jetpack', 'get_updates' ), + 'has_file_system_write_access' => array( 'Jetpack_Sync_Functions', 'file_system_write_access' ), + 'is_version_controlled' => array( 'Jetpack_Sync_Functions', 'is_version_controlled' ), + 'taxonomies' => array( 'Jetpack_Sync_Functions', 'get_taxonomies' ), + 'post_types' => array( 'Jetpack_Sync_Functions', 'get_post_types' ), + 'post_type_features' => array( 'Jetpack_Sync_Functions', 'get_post_type_features' ), + 'rest_api_allowed_post_types' => array( 'Jetpack_Sync_Functions', 'rest_api_allowed_post_types' ), + 'rest_api_allowed_public_metadata' => array( 'Jetpack_Sync_Functions', 'rest_api_allowed_public_metadata' ), + 'sso_is_two_step_required' => array( 'Jetpack_SSO_Helpers', 'is_two_step_required' ), + 'sso_should_hide_login_form' => array( 'Jetpack_SSO_Helpers', 'should_hide_login_form' ), + 'sso_match_by_email' => array( 'Jetpack_SSO_Helpers', 'match_by_email' ), + 'sso_new_user_override' => array( 'Jetpack_SSO_Helpers', 'new_user_override' ), + 'sso_bypass_default_login_form' => array( 'Jetpack_SSO_Helpers', 'bypass_login_forward_wpcom' ), + 'wp_version' => array( 'Jetpack_Sync_Functions', 'wp_version' ), + 'get_plugins' => array( 'Jetpack_Sync_Functions', 'get_plugins' ), + 'active_modules' => array( 'Jetpack', 'get_active_modules' ), + ); + + static $blacklisted_post_types = array( + 'ai1ec_event', + 'snitch', + ); + + static $default_post_checksum_columns = array( + 'ID', + 'post_modified', + ); + + static $default_comment_checksum_columns = array( + 'comment_ID', + 'comment_content', + ); + + static $default_option_checksum_columns = array( + 'option_name', + 'option_value', + ); + + static $default_multisite_callable_whitelist = array( + 'network_name' => array( 'Jetpack', 'network_name' ), + 'network_allow_new_registrations' => array( 'Jetpack', 'network_allow_new_registrations' ), + 'network_add_new_users' => array( 'Jetpack', 'network_add_new_users' ), + 'network_site_upload_space' => array( 'Jetpack', 'network_site_upload_space' ), + 'network_upload_file_types' => array( 'Jetpack', 'network_upload_file_types' ), + 'network_enable_administration_menus' => array( 'Jetpack', 'network_enable_administration_menus' ), + ); + + + static $default_whitelist_meta_keys = array( + '_wp_attachment_metadata', + '_thumbnail_id', + '_wpas_mess', + '_wpas_skip_', + '_g_feedback_shortcode', + '_feedback_extra_fields', + '_feedback_akismet_values', + '_publicize_facebook_user', + '_wp_attachment_image_alt', + '_jetpack_post_thumbnail', + '_thumbnail_id', + '_wp_attachment_metadata', + '_wp_page_template', + '_publicize_twitter_user', + '_wp_trash_meta_comments_status', + ); + + static $default_blacklist_meta_keys = array( + 'post_views_count', + 'Views', + 'tve_leads_impressions', + 'views', + 'scc_share_count_crawldate', + 'wprss_last_update', + 'wprss_feed_is_updating', + 'snapFB', + 'syndication_item_hash', + 'phonenumber_spellings', + 'tmac_last_id', + 'opanda_imperessions', + 'administer_stats', + 'spec_ads_views', + 'snp_views', + 'mip_post_views_count', + 'esml_socialcount_LAST_UPDATED', + 'wprss_last_update_items', + ); + + // TODO: move this to server? - these are theme support values + // that should be synced as jetpack_current_theme_supports_foo option values + static $default_theme_support_whitelist = array( + 'post-thumbnails', + 'post-formats', + 'custom-header', + 'custom-background', + 'custom-logo', + 'menus', + 'automatic-feed-links', + 'editor-style', + 'widgets', + 'html5', + 'title-tag', + 'jetpack-social-menu', + 'jetpack-responsive-videos', + 'infinite-scroll', + 'site-logo', + ); + + static function is_whitelisted_option( $option ) { + foreach ( self::$default_options_whitelist as $whitelisted_option ) { + if ( $whitelisted_option[0] === '/' && preg_match( $whitelisted_option, $option ) ) { + return true; + } elseif ( $whitelisted_option === $option ) { + return true; + } + } + + return false; + } + + static $default_network_options_whitelist = array( + 'site_name', + 'jetpack_protect_key', + 'jetpack_protect_global_whitelist', + 'active_sitewide_plugins', + ); + static $default_taxonomy_whitelist = array(); + static $default_dequeue_max_bytes = 500000; // very conservative value, 1/2 MB + static $default_upload_max_bytes = 600000; // a little bigger than the upload limit to account for serialization + static $default_upload_max_rows = 500; + static $default_sync_wait_time = 10; // seconds, between syncs + static $default_sync_wait_threshold = 5; // only wait before next send if the current send took more than X seconds + static $default_max_queue_size = 1000; + static $default_max_queue_lag = 900; // 15 minutes + static $default_queue_max_writes_sec = 100; // 100 rows a second + static $default_post_types_blacklist = array(); + static $default_meta_blacklist = array(); + static $default_disable = 0; // completely disable sending data to wpcom + static $default_sync_callables_wait_time = MINUTE_IN_SECONDS; // seconds before sending callables again + static $default_sync_constants_wait_time = HOUR_IN_SECONDS; // seconds before sending constants again +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-functions.php b/plugins/jetpack/sync/class.jetpack-sync-functions.php new file mode 100644 index 00000000..9ce70f70 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-functions.php @@ -0,0 +1,157 @@ +get_modules(); + } + + public static function get_taxonomies() { + global $wp_taxonomies; + + return $wp_taxonomies; + } + + public static function get_post_types() { + global $wp_post_types; + + return $wp_post_types; + } + + public static function get_post_type_features() { + global $_wp_post_type_features; + + return $_wp_post_type_features; + } + + public static function rest_api_allowed_post_types() { + /** This filter is already documented in class.json-api-endpoints.php */ + return apply_filters( 'rest_api_allowed_post_types', array( 'post', 'page', 'revision' ) ); + } + + public static function rest_api_allowed_public_metadata() { + /** This filter is documented in json-endpoints/class.wpcom-json-api-post-endpoint.php */ + return apply_filters( 'rest_api_allowed_public_metadata', array() ); + } + + /** + * Finds out if a site is using a version control system. + * @return bool + **/ + public static function is_version_controlled() { + + if ( ! class_exists( 'WP_Automatic_Updater' ) ) { + require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); + } + $updater = new WP_Automatic_Updater(); + + return (bool) strval( $updater->is_vcs_checkout( $context = ABSPATH ) ); + } + + /** + * Returns true if the site has file write access false otherwise. + * @return bool + **/ + public static function file_system_write_access() { + if ( ! function_exists( 'get_filesystem_method' ) ) { + require_once( ABSPATH . 'wp-admin/includes/file.php' ); + } + + require_once( ABSPATH . 'wp-admin/includes/template.php' ); + + $filesystem_method = get_filesystem_method(); + if ( 'direct' === $filesystem_method ) { + return true; + } + + ob_start(); + $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() ); + ob_end_clean(); + if ( $filesystem_credentials_are_stored ) { + return true; + } + + return false; + } + + public static function home_url() { + return self::preserve_scheme( 'home', 'home_url', true ); + } + + public static function site_url() { + return self::preserve_scheme( 'siteurl', 'site_url', true ); + } + + public static function main_network_site_url() { + return self::preserve_scheme( 'siteurl', 'network_site_url', false ); + } + + public static function preserve_scheme( $option, $url_function, $normalize_www = false ) { + $previous_https_value = isset( $_SERVER['HTTPS'] ) ? $_SERVER['HTTPS'] : null; + $_SERVER['HTTPS'] = 'off'; + $url = call_user_func( $url_function ); + $option_url = get_option( $option ); + if ( $previous_https_value ) { + $_SERVER['HTTPS'] = $previous_https_value; + } else { + unset( $_SERVER['HTTPS'] ); + } + + if ( $option_url === $url ) { + return $url; + } + + // turn them both into parsed format + $option_url = parse_url( $option_url ); + $url = parse_url( $url ); + + if ( $normalize_www ) { + if ( $url['host'] === "www.{$option_url[ 'host' ]}" ) { + // remove www if not present in option URL + $url['host'] = $option_url['host']; + } + if ( $option_url['host'] === "www.{$url[ 'host' ]}" ) { + // add www if present in option URL + $url['host'] = $option_url['host']; + } + } + + if ( $url['host'] === $option_url['host'] ) { + $url['scheme'] = $option_url['scheme']; + // return set_url_scheme( $current_url, $option_url['scheme'] ); + } + + $normalized_url = "{$url['scheme']}://{$url['host']}"; + + if ( isset( $url['path'] ) ) { + $normalized_url .= "{$url['path']}"; + } + + if ( isset( $url['query'] ) ) { + $normalized_url .= "?{$url['query']}"; + } + + return $normalized_url; + } + + public static function get_plugins() { + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */ + return apply_filters( 'all_plugins', get_plugins() ); + } + + public static function wp_version() { + global $wp_version; + + return $wp_version; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-json-deflate-codec.php b/plugins/jetpack/sync/class.jetpack-sync-json-deflate-codec.php new file mode 100644 index 00000000..6ec966e9 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-json-deflate-codec.php @@ -0,0 +1,58 @@ +json_serialize( unserialize( serialize( $object ) ) ) ) ); + } + + public function decode( $input ) { + return $this->json_unserialize( gzinflate( base64_decode( $input ) ) ); + } + + // @see https://gist.github.com/muhqu/820694 + private function json_serialize( $any ) { + return json_encode( $this->json_wrap( $any ) ); + } + + private function json_unserialize( $str ) { + return $this->json_unwrap( json_decode( $str ) ); + } + + private function json_wrap( $any, $skip_assoc = false ) { + if ( ! $skip_assoc && is_array( $any ) && is_string( key( $any ) ) ) { + return (object) array( '_PHP_ASSOC' => $this->json_wrap( $any, true ) ); + } + if ( is_array( $any ) || is_object( $any ) ) { + foreach ( $any as &$v ) { + $v = $this->json_wrap( $v ); + } + } + + return $any; + } + + private function json_unwrap( $any, $skip_assoc = false ) { + if ( ! $skip_assoc && is_object( $any ) && isset( $any->_PHP_ASSOC ) && count( (array) $any ) == 1 ) { + return (array) $this->json_unwrap( $any->_PHP_ASSOC ); + } + if ( is_array( $any ) || is_object( $any ) ) { + foreach ( $any as &$v ) { + $v = $this->json_unwrap( $v ); + } + } + + return $any; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-listener.php b/plugins/jetpack/sync/class.jetpack-sync-listener.php new file mode 100644 index 00000000..1362084e --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-listener.php @@ -0,0 +1,207 @@ +:( + protected function __construct() { + $this->set_defaults(); + $this->init(); + } + + private function init() { + + $handler = array( $this, 'action_handler' ); + $full_sync_handler = array( $this, 'full_sync_action_handler' ); + + foreach ( Jetpack_Sync_Modules::get_modules() as $module ) { + $module->init_listeners( $handler ); + $module->init_full_sync_listeners( $full_sync_handler ); + } + + // Module Activation + add_action( 'jetpack_activate_module', $handler ); + add_action( 'jetpack_deactivate_module', $handler ); + + // Send periodic checksum + add_action( 'jetpack_sync_checksum', $handler ); + } + + function get_sync_queue() { + return $this->sync_queue; + } + + function get_full_sync_queue() { + return $this->full_sync_queue; + } + + function set_queue_size_limit( $limit ) { + $this->sync_queue_size_limit = $limit; + } + + function get_queue_size_limit() { + return $this->sync_queue_size_limit; + } + + function set_queue_lag_limit( $age ) { + $this->sync_queue_lag_limit = $age; + } + + function get_queue_lag_limit() { + return $this->sync_queue_lag_limit; + } + + function force_recheck_queue_limit() { + delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->sync_queue->id ); + delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->full_sync_queue->id ); + } + + // prevent adding items to the queue if it hasn't sent an item for 15 mins + // AND the queue is over 1000 items long (by default) + function can_add_to_queue( $queue ) { + if ( Jetpack_Sync_Settings::get_setting( 'disable' ) ) { + return false; + } + + $state_transient_name = self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $queue->id; + + $queue_state = get_transient( $state_transient_name ); + + if ( false === $queue_state ) { + $queue_state = array( $queue->size(), $queue->lag() ); + set_transient( $state_transient_name, $queue_state, self::QUEUE_STATE_CHECK_TIMEOUT ); + } + + list( $queue_size, $queue_age ) = $queue_state; + + return ( $queue_age < $this->sync_queue_lag_limit ) + || + ( ( $queue_size + 1 ) < $this->sync_queue_size_limit ); + } + + function full_sync_action_handler() { + $args = func_get_args(); + $this->enqueue_action( current_filter(), $args, $this->full_sync_queue ); + } + + function action_handler() { + $args = func_get_args(); + $this->enqueue_action( current_filter(), $args, $this->sync_queue ); + } + + // add many actions to the queue directly, without invoking them + function bulk_enqueue_full_sync_actions( $action_name, $args_array ) { + $queue = $this->get_full_sync_queue(); + + // periodically check the size of the queue, and disable adding to it if + // it exceeds some limit AND the oldest item exceeds the age limit (i.e. sending has stopped) + if ( ! $this->can_add_to_queue( $queue ) ) { + return; + } + + // if we add any items to the queue, we should try to ensure that our script + // can't be killed before they are sent + if ( function_exists( 'ignore_user_abort' ) ) { + ignore_user_abort( true ); + } + + $data_to_enqueue = array(); + $user_id = get_current_user_id(); + $currtime = microtime( true ); + $is_importing = Jetpack_Sync_Settings::is_importing(); + + foreach( $args_array as $args ) { + + /** + * Modify or reject the data within an action before it is enqueued locally. + * + * @since 4.2.0 + * + * @param array The action parameters + */ + $args = apply_filters( "jetpack_sync_before_enqueue_$action_name", $args ); + + // allow listeners to abort + if ( $args === false ) { + continue; + } + + $data_to_enqueue[] = array( + $action_name, + array( $args ), + $user_id, + $currtime, + $is_importing, + ); + } + + $queue->add_all( $data_to_enqueue ); + } + + function enqueue_action( $current_filter, $args, $queue ) { + /** + * Modify or reject the data within an action before it is enqueued locally. + * + * @since 4.2.0 + * + * @param array The action parameters + */ + $args = apply_filters( "jetpack_sync_before_enqueue_$current_filter", $args ); + + // allow listeners to abort + if ( $args === false ) { + return; + } + + // periodically check the size of the queue, and disable adding to it if + // it exceeds some limit AND the oldest item exceeds the age limit (i.e. sending has stopped) + if ( ! $this->can_add_to_queue( $queue ) ) { + return; + } + + // if we add any items to the queue, we should try to ensure that our script + // can't be killed before they are sent + if ( function_exists( 'ignore_user_abort' ) ) { + ignore_user_abort( true ); + } + + $queue->add( array( + $current_filter, + $args, + get_current_user_id(), + microtime( true ), + Jetpack_Sync_Settings::is_importing() + ) ); + } + + function set_defaults() { + $this->sync_queue = new Jetpack_Sync_Queue( 'sync' ); + $this->full_sync_queue = new Jetpack_Sync_Queue( 'full_sync' ); + $this->set_queue_size_limit( Jetpack_Sync_Settings::get_setting( 'max_queue_size' ) ); + $this->set_queue_lag_limit( Jetpack_Sync_Settings::get_setting( 'max_queue_lag' ) ); + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-attachments.php b/plugins/jetpack/sync/class.jetpack-sync-module-attachments.php new file mode 100644 index 00000000..4cee033e --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-attachments.php @@ -0,0 +1,28 @@ +callable_whitelist = array_merge( Jetpack_Sync_Defaults::$default_callable_whitelist, Jetpack_Sync_Defaults::$default_multisite_callable_whitelist ); + } else { + $this->callable_whitelist = Jetpack_Sync_Defaults::$default_callable_whitelist; + } + } + + public function init_listeners( $callable ) { + add_action( 'jetpack_sync_callable', $callable, 10, 2 ); + + // always send change to active modules right away + add_action( 'update_option_jetpack_active_modules', array( $this, 'unlock_sync_callable' ) ); + + // get_plugins and wp_version + // gets fired when new code gets installed, updates etc. + add_action( 'upgrader_process_complete', array( $this, 'unlock_sync_callable' ) ); + } + + public function init_full_sync_listeners( $callable ) { + add_action( 'jetpack_full_sync_callables', $callable ); + } + + public function init_before_send() { + add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_sync_callables' ) ); + + // full sync + add_filter( 'jetpack_sync_before_send_jetpack_full_sync_callables', array( $this, 'expand_callables' ) ); + } + + public function reset_data() { + delete_option( self::CALLABLES_CHECKSUM_OPTION_NAME ); + delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME ); + } + + function set_callable_whitelist( $callables ) { + $this->callable_whitelist = $callables; + } + + function get_callable_whitelist() { + return $this->callable_whitelist; + } + + public function get_all_callables() { + // get_all_callables should run as the master user always. + $current_user_id = get_current_user_id(); + wp_set_current_user( Jetpack_Options::get_option( 'master_user' ) ); + $callables = array_combine( + array_keys( $this->callable_whitelist ), + array_map( array( $this, 'get_callable' ), array_values( $this->callable_whitelist ) ) + ); + wp_set_current_user( $current_user_id ); + + return $callables; + } + + private function get_callable( $callable ) { + return call_user_func( $callable ); + } + + public function enqueue_full_sync_actions( $config ) { + /** + * Tells the client to sync all callables to the server + * + * @since 4.2.0 + * + * @param boolean Whether to expand callables (should always be true) + */ + do_action( 'jetpack_full_sync_callables', true ); + + return 1; // The number of actions enqueued + } + + public function estimate_full_sync_actions( $config ) { + return 1; + } + + public function get_full_sync_actions() { + return array( 'jetpack_full_sync_callables' ); + } + + public function unlock_sync_callable() { + delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME ); + } + + public function maybe_sync_callables() { + if ( ! is_admin() || Jetpack_Sync_Settings::is_doing_cron() ) { + return; + } + + if ( get_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME ) ) { + return; + } + + set_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME, microtime( true ), Jetpack_Sync_Defaults::$default_sync_callables_wait_time ); + + $callables = $this->get_all_callables(); + + if ( empty( $callables ) ) { + return; + } + + $callable_checksums = (array) get_option( self::CALLABLES_CHECKSUM_OPTION_NAME, array() ); + + // only send the callables that have changed + foreach ( $callables as $name => $value ) { + $checksum = $this->get_check_sum( $value ); + // explicitly not using Identical comparison as get_option returns a string + if ( ! $this->still_valid_checksum( $callable_checksums, $name, $checksum ) && ! is_null( $value ) ) { + /** + * Tells the client to sync a callable (aka function) to the server + * + * @since 4.2.0 + * + * @param string The name of the callable + * @param mixed The value of the callable + */ + do_action( 'jetpack_sync_callable', $name, $value ); + $callable_checksums[ $name ] = $checksum; + } else { + $callable_checksums[ $name ] = $checksum; + } + } + update_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callable_checksums ); + } + + public function expand_callables( $args ) { + if ( $args[0] ) { + return $this->get_all_callables(); + } + + return $args; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-comments.php b/plugins/jetpack/sync/class.jetpack-sync-module-comments.php new file mode 100644 index 00000000..3c93a8d6 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-comments.php @@ -0,0 +1,131 @@ +enqueue_all_ids_as_action( 'jetpack_full_sync_comments', $wpdb->comments, 'comment_ID', $this->get_where_sql( $config ) ); + } + + public function estimate_full_sync_actions( $config ) { + global $wpdb; + + $query = "SELECT count(*) FROM $wpdb->comments"; + + if ( $where_sql = $this->get_where_sql( $config ) ) { + $query .= ' WHERE ' . $where_sql; + } + + $count = $wpdb->get_var( $query ); + + return (int) ceil( $count / self::ARRAY_CHUNK_SIZE ); + } + + private function get_where_sql( $config ) { + if ( is_array( $config ) ) { + return 'comment_ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')'; + } + + return null; + } + + public function get_full_sync_actions() { + return array( 'jetpack_full_sync_comments' ); + } + + public function count_full_sync_actions( $action_names ) { + return $this->count_actions( $action_names, array( 'jetpack_full_sync_comments' ) ); + } + + function expand_wp_comment_status_change( $args ) { + return array( $args[0], $this->filter_comment( $args[1] ) ); + } + + function expand_wp_insert_comment( $args ) { + return array( $args[0], $this->filter_comment( $args[1] ) ); + } + + function filter_comment( $comment ) { + /** + * Filters whether to prevent sending comment data to .com + * + * Passing true to the filter will prevent the comment data from being sent + * to the WordPress.com. + * Instead we pass data that will still enable us to do a checksum against the + * Jetpacks data but will prevent us from displaying the data on in the API as well as + * other services. + * @since 4.2.0 + * + * @param boolean false prevent post data from bing synced to WordPress.com + * @param mixed $comment WP_COMMENT object + */ + if ( apply_filters( 'jetpack_sync_prevent_sending_comment_data', false, $comment ) ) { + $blocked_comment = new stdClass(); + $blocked_comment->comment_ID = $comment->comment_ID; + $blocked_comment->comment_date = $comment->comment_date; + $blocked_comment->comment_date_gmt = $comment->comment_date_gmt; + $blocked_comment->comment_approved = 'jetpack_sync_blocked'; + + return $blocked_comment; + } + + return $comment; + } + + public function expand_comment_ids( $args ) { + $comment_ids = $args[0]; + $comments = get_comments( array( + 'include_unapproved' => true, + 'comment__in' => $comment_ids, + ) ); + + return array( + $comments, + $this->get_metadata( $comment_ids, 'comment' ), + ); + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-constants.php b/plugins/jetpack/sync/class.jetpack-sync-module-constants.php new file mode 100644 index 00000000..4ad9bafa --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-constants.php @@ -0,0 +1,124 @@ +constants_whitelist = Jetpack_Sync_Defaults::$default_constants_whitelist; + } + + public function init_listeners( $callable ) { + add_action( 'jetpack_sync_constant', $callable, 10, 2 ); + } + + public function init_full_sync_listeners( $callable ) { + add_action( 'jetpack_full_sync_constants', $callable ); + } + + public function init_before_send() { + add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_sync_constants' ) ); + + // full sync + add_filter( 'jetpack_sync_before_send_jetpack_full_sync_constants', array( $this, 'expand_constants' ) ); + } + + public function reset_data() { + delete_option( self::CONSTANTS_CHECKSUM_OPTION_NAME ); + delete_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME ); + } + + function set_constants_whitelist( $constants ) { + $this->constants_whitelist = $constants; + } + + function get_constants_whitelist() { + return $this->constants_whitelist; + } + + function enqueue_full_sync_actions( $config ) { + /** + * Tells the client to sync all constants to the server + * + * @since 4.2.0 + * + * @param boolean Whether to expand constants (should always be true) + */ + do_action( 'jetpack_full_sync_constants', true ); + + return 1; + } + + function estimate_full_sync_actions( $config ) { + return 1; + } + + function get_full_sync_actions() { + return array( 'jetpack_full_sync_constants' ); + } + + function maybe_sync_constants() { + if ( get_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME ) ) { + return; + } + + set_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME, microtime( true ), Jetpack_Sync_Defaults::$default_sync_constants_wait_time ); + + $constants = $this->get_all_constants(); + if ( empty( $constants ) ) { + return; + } + + $constants_checksums = (array) get_option( self::CONSTANTS_CHECKSUM_OPTION_NAME, array() ); + + foreach ( $constants as $name => $value ) { + $checksum = $this->get_check_sum( $value ); + // explicitly not using Identical comparison as get_option returns a string + if ( ! $this->still_valid_checksum( $constants_checksums, $name, $checksum ) && ! is_null( $value ) ) { + /** + * Tells the client to sync a constant to the server + * + * @since 4.2.0 + * + * @param string The name of the constant + * @param mixed The value of the constant + */ + do_action( 'jetpack_sync_constant', $name, $value ); + $constants_checksums[ $name ] = $checksum; + } else { + $constants_checksums[ $name ] = $checksum; + } + } + update_option( self::CONSTANTS_CHECKSUM_OPTION_NAME, $constants_checksums ); + } + + // public so that we don't have to store an option for each constant + function get_all_constants() { + return array_combine( + $this->constants_whitelist, + array_map( array( $this, 'get_constant' ), $this->constants_whitelist ) + ); + } + + private function get_constant( $constant ) { + return ( defined( $constant ) ) ? + constant( $constant ) + : null; + } + + public function expand_constants( $args ) { + if ( $args[0] ) { + return $this->get_all_constants(); + } + + return $args; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-full-sync.php b/plugins/jetpack/sync/class.jetpack-sync-module-full-sync.php new file mode 100644 index 00000000..1f196d1e --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-full-sync.php @@ -0,0 +1,289 @@ +is_started() && ! $this->is_finished(); + + // remove all evidence of previous full sync items and status + $this->reset_data(); + + $this->enable_queue_rate_limit(); + + if ( $was_already_running ) { + /** + * Fires when a full sync is cancelled. + * + * @since 4.2.0 + */ + do_action( 'jetpack_full_sync_cancelled' ); + } + + /** + * Fires when a full sync begins. This action is serialized + * and sent to the server so that it knows a full sync is coming. + * + * @since 4.2.0 + */ + do_action( 'jetpack_full_sync_start', $modules ); + $this->update_status_option( 'started', time() ); + + // configure modules + if ( ! is_array( $modules ) ) { + $modules = array(); + } + + if ( isset( $modules['users'] ) && 'initial' === $modules['users'] ) { + $user_module = Jetpack_Sync_Modules::get_module( 'users' ); + $modules['users'] = $user_module->get_initial_sync_user_config(); + } + + // by default, all modules are fully enabled + if ( count( $modules ) === 0 ) { + $default_module_config = true; + } else { + $default_module_config = false; + } + + // set default configuration, calculate totals, and save configuration if totals > 0 + foreach ( Jetpack_Sync_Modules::get_modules() as $module ) { + $module_name = $module->name(); + if ( ! isset( $modules[ $module_name ] ) ) { + $modules[ $module_name ] = $default_module_config; + } + + // check if this module is enabled + if ( ! ( $module_config = $modules[ $module_name ] ) ) { + continue; + } + + $total_items = $module->estimate_full_sync_actions( $module_config ); + + if ( ! is_null( $total_items ) && $total_items > 0 ) { + $this->update_status_option( "{$module_name}_total", $total_items ); + $this->update_status_option( "{$module_name}_config", $module_config ); + } + } + + foreach ( Jetpack_Sync_Modules::get_modules() as $module ) { + $module_name = $module->name(); + $module_config = $modules[ $module_name ]; + + // check if this module is enabled + if ( ! $module_config ) { + continue; + } + + $items_enqueued = $module->enqueue_full_sync_actions( $module_config ); + + if ( ! is_null( $items_enqueued ) && $items_enqueued > 0 ) { + $this->update_status_option( "{$module_name}_queued", $items_enqueued ); + } + } + + $this->update_status_option( 'queue_finished', time() ); + + $store = new Jetpack_Sync_WP_Replicastore(); + + /** + * Fires when a full sync ends. This action is serialized + * and sent to the server with checksums so that we can confirm the + * sync was successful. + * + * @since 4.2.0 + */ + do_action( 'jetpack_full_sync_end', $store->checksum_all() ); + + $this->disable_queue_rate_limit(); + + return true; + } + + function update_sent_progress_action( $actions ) { + + // quick way to map to first items with an array of arrays + $actions_with_counts = array_count_values( array_map( 'reset', $actions ) ); + + if ( ! $this->is_started() || $this->is_finished() ) { + return; + } + + if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) { + $this->update_status_option( 'sent_started', time() ); + } + + foreach ( Jetpack_Sync_Modules::get_modules() as $module ) { + $module_actions = $module->get_full_sync_actions(); + $status_option_name = "{$module->name()}_sent"; + $items_sent = $this->get_status_option( $status_option_name, 0 ); + + foreach ( $module_actions as $module_action ) { + if ( isset( $actions_with_counts[ $module_action ] ) ) { + $items_sent += $actions_with_counts[ $module_action ]; + } + } + + if ( $items_sent > 0 ) { + $this->update_status_option( $status_option_name, $items_sent ); + } + } + + if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) { + $this->update_status_option( 'finished', time() ); + } + } + + public function is_started() { + return !! $this->get_status_option( 'started' ); + } + + public function is_finished() { + return !! $this->get_status_option( 'finished' ); + } + + public function get_status() { + $status = array( + 'started' => $this->get_status_option( 'started' ), + 'queue_finished' => $this->get_status_option( 'queue_finished' ), + 'sent_started' => $this->get_status_option( 'sent_started' ), + 'finished' => $this->get_status_option( 'finished' ), + 'sent' => array(), + 'queue' => array(), + 'config' => array(), + 'total' => array(), + ); + + foreach ( Jetpack_Sync_Modules::get_modules() as $module ) { + $name = $module->name(); + + if ( $total = $this->get_status_option( "{$name}_total" ) ) { + $status[ 'total' ][ $name ] = $total; + } + + if ( $queued = $this->get_status_option( "{$name}_queued" ) ) { + $status[ 'queue' ][ $name ] = $queued; + } + + if ( $sent = $this->get_status_option( "{$name}_sent" ) ) { + $status[ 'sent' ][ $name ] = $sent; + } + + if ( $config = $this->get_status_option( "{$name}_config" ) ) { + $status[ 'config' ][ $name ] = $config; + } + } + + return $status; + } + + public function clear_status() { + $prefix = self::STATUS_OPTION_PREFIX; + delete_option( "{$prefix}_started" ); + delete_option( "{$prefix}_queue_finished" ); + delete_option( "{$prefix}_sent_started" ); + delete_option( "{$prefix}_finished" ); + + foreach ( Jetpack_Sync_Modules::get_modules() as $module ) { + delete_option( "{$prefix}_{$module->name()}_total" ); + delete_option( "{$prefix}_{$module->name()}_queued" ); + delete_option( "{$prefix}_{$module->name()}_sent" ); + delete_option( "{$prefix}_{$module->name()}_config" ); + } + } + + public function reset_data() { + $this->clear_status(); + require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php'; + $listener = Jetpack_Sync_Listener::get_instance(); + $listener->get_full_sync_queue()->reset(); + } + + private function get_status_option( $option, $default = null ) { + $prefix = self::STATUS_OPTION_PREFIX; + + $value = get_option( "{$prefix}_{$option}", $default ); + + if ( ! $value ) { + // don't cast to int if we didn't find a value - we want to preserve null or false as sentinals + return $default; + } + + return is_numeric( $value ) ? intval( $value ) : $value; + } + + private function update_status_option( $name, $value ) { + $prefix = self::STATUS_OPTION_PREFIX; + update_option( "{$prefix}_{$name}", $value, false ); + } + + private function enable_queue_rate_limit() { + $this->queue_rate_limit = Jetpack_Sync_Settings::get_setting( 'queue_max_writes_sec' ); + $this->items_added_since_last_pause = 0; + $this->last_pause_time = microtime( true ); + + add_action( 'jpsq_item_added', array( $this, 'queue_item_added' ) ); + add_action( 'jpsq_items_added', array( $this, 'queue_items_added' ) ); + } + + private function disable_queue_rate_limit() { + remove_action( 'jpsq_item_added', array( $this, 'queue_item_added' ) ); + remove_action( 'jpsq_items_added', array( $this, 'queue_items_added' ) ); + } + + public function queue_item_added() { + $this->queue_items_added( 1 ); + } + + public function queue_items_added( $item_count ) { + // jpsq_item_added and jpsq_items_added both exec 1 db query, + // so we ignore $item_count and treat it as always 1 + $this->items_added_since_last_pause += 1; + + if ( $this->items_added_since_last_pause > $this->queue_rate_limit ) { + // sleep for the rest of the second + $sleep_til = $this->last_pause_time + 1.0; + $sleep_duration = $sleep_til - microtime( true ); + if ( $sleep_duration > 0.0 ) { + usleep( $sleep_duration * 1000000 ); + $this->last_pause_time = microtime( true ); + } + $this->items_added_since_last_pause = 0; + } + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-meta.php b/plugins/jetpack/sync/class.jetpack-sync-module-meta.php new file mode 100644 index 00000000..5fff16a3 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-meta.php @@ -0,0 +1,38 @@ +meta_types as $meta_type ) { + add_action( "added_{$meta_type}_meta", $callable, 10, 4 ); + add_action( "updated_{$meta_type}_meta", $callable, 10, 4 ); + add_action( "deleted_{$meta_type}_meta", $callable, 10, 4 ); + + add_filter( "jetpack_sync_before_enqueue_added_{$meta_type}_meta", $whitelist_handler ); + add_filter( "jetpack_sync_before_enqueue_updated_{$meta_type}_meta", $whitelist_handler ); + add_filter( "jetpack_sync_before_enqueue_deleted_{$meta_type}_meta", $whitelist_handler ); + } + } + + function filter_meta( $args ) { + if ( '_' === $args[2][0] && + ! in_array( $args[2], Jetpack_Sync_Defaults::$default_whitelist_meta_keys ) && + ! wp_startswith( $args[2], '_wpas_skip_' ) + ) { + return false; + } + + if ( in_array( $args[2], Jetpack_Sync_Settings::get_setting( 'meta_blacklist' ) ) ) { + return false; + } + + return $args; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-network-options.php b/plugins/jetpack/sync/class.jetpack-sync-module-network-options.php new file mode 100644 index 00000000..b677890f --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-network-options.php @@ -0,0 +1,112 @@ +network_options_whitelist = Jetpack_Sync_Defaults::$default_network_options_whitelist; + } + + function enqueue_full_sync_actions( $config ) { + if ( ! is_multisite() ) { + return 0; + } + + /** + * Tells the client to sync all options to the server + * + * @since 4.2.0 + * + * @param boolean Whether to expand options (should always be true) + */ + do_action( 'jetpack_full_sync_network_options', true ); + + return 1; // The number of actions enqueued + } + + function estimate_full_sync_actions( $config ) { + if ( ! is_multisite() ) { + return 0; + } + + return 1; + } + + function get_full_sync_actions() { + return array( 'jetpack_full_sync_network_options' ); + } + + function get_all_network_options() { + $options = array(); + foreach ( $this->network_options_whitelist as $option ) { + $options[ $option ] = get_site_option( $option ); + } + + return $options; + } + + function set_network_options_whitelist( $options ) { + $this->network_options_whitelist = $options; + } + + function get_network_options_whitelist() { + return $this->network_options_whitelist; + } + + // reject non-whitelisted network options + function whitelist_network_options( $args ) { + if ( ! $this->is_whitelisted_network_option( $args[0] ) ) { + return false; + } + + return $args; + } + + function is_whitelisted_network_option( $option ) { + return is_multisite() && in_array( $option, $this->network_options_whitelist ); + } + + public function expand_network_options( $args ) { + if ( $args[0] ) { + return $this->get_all_network_options(); + } + + return $args; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-options.php b/plugins/jetpack/sync/class.jetpack-sync-module-options.php new file mode 100644 index 00000000..a708d49d --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-options.php @@ -0,0 +1,141 @@ +update_options_whitelist(); + } + + function enqueue_full_sync_actions( $config ) { + /** + * Tells the client to sync all options to the server + * + * @since 4.2.0 + * + * @param boolean Whether to expand options (should always be true) + */ + do_action( 'jetpack_full_sync_options', true ); + + return 1; // The number of actions enqueued + } + + public function estimate_full_sync_actions( $config ) { + return 1; + } + + function get_full_sync_actions() { + return array( 'jetpack_full_sync_options' ); + } + + // Is public so that we don't have to store so much data all the options twice. + function get_all_options() { + $options = array(); + foreach ( $this->options_whitelist as $option ) { + $options[ $option ] = get_option( $option ); + } + + // add theme mods + $theme_mods_option = 'theme_mods_'.get_option( 'stylesheet' ); + $theme_mods_value = get_option( $theme_mods_option ); + $this->filter_theme_mods( $theme_mods_value ); + $options[ $theme_mods_option ] = $theme_mods_value; + + return $options; + } + + function update_options_whitelist() { + /** This filter is already documented in json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php */ + $this->options_whitelist = apply_filters( 'jetpack_options_whitelist', Jetpack_Sync_Defaults::$default_options_whitelist ); + } + + function set_options_whitelist( $options ) { + $this->options_whitelist = $options; + } + + function get_options_whitelist() { + return $this->options_whitelist; + } + + // reject non-whitelisted options + function whitelist_options( $args ) { + if ( ! $this->is_whitelisted_option( $args[0] ) ) { + return false; + } + + // filter our weird array( false ) value for theme_mods_* + if ( 'theme_mods_' === substr( $args[0], 0, 11 ) ) { + $this->filter_theme_mods( $args[1] ); + if ( isset( $args[2] ) ) { + $this->filter_theme_mods( $args[2] ); + } + } + + return $args; + } + + function is_whitelisted_option( $option ) { + return in_array( $option, $this->options_whitelist ) || 'theme_mods_' === substr( $option, 0, 11 ); + } + + private function filter_theme_mods( &$value ) { + if ( is_array( $value ) && isset( $value[0] ) ) { + unset( $value[0] ); + } + } + + function jetpack_sync_core_icon() { + if ( function_exists( 'get_site_icon_url' ) ) { + $url = get_site_icon_url(); + } else { + return; + } + + require_once( JETPACK__PLUGIN_DIR . 'modules/site-icon/site-icon-functions.php' ); + // If there's a core icon, maybe update the option. If not, fall back to Jetpack's. + if ( ! empty( $url ) && $url !== jetpack_site_icon_url() ) { + // This is the option that is synced with dotcom + Jetpack_Options::update_option( 'site_icon_url', $url ); + } else if ( empty( $url ) ) { + Jetpack_Options::delete_option( 'site_icon_url' ); + } + } + + public function expand_options( $args ) { + if ( $args[0] ) { + return $this->get_all_options(); + } + + return $args; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-plugins.php b/plugins/jetpack/sync/class.jetpack-sync-module-plugins.php new file mode 100644 index 00000000..e2b3dd8f --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-plugins.php @@ -0,0 +1,14 @@ +enqueue_all_ids_as_action( 'jetpack_full_sync_posts', $wpdb->posts, 'ID', $this->get_where_sql( $config ) ); + } + + public function estimate_full_sync_actions( $config ) { + global $wpdb; + + $query = "SELECT count(*) FROM $wpdb->posts WHERE " . $this->get_where_sql( $config ); + $count = $wpdb->get_var( $query ); + + return (int) ceil( $count / self::ARRAY_CHUNK_SIZE ); + } + + private function get_where_sql( $config ) { + $where_sql = Jetpack_Sync_Settings::get_blacklisted_post_types_sql(); + + // config is a list of post IDs to sync + if ( is_array( $config ) ) { + $where_sql .= ' AND ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')'; + } + + return $where_sql; + } + + function get_full_sync_actions() { + return array( 'jetpack_full_sync_posts' ); + } + + /** + * Process content before send + */ + + function expand_wp_insert_post( $args ) { + return array( $args[0], $this->filter_post_content_and_add_links( $args[1] ), $args[2] ); + } + + function filter_blacklisted_post_types( $args ) { + $post = $args[1]; + + if ( in_array( $post->post_type, Jetpack_Sync_Settings::get_setting( 'post_types_blacklist' ) ) ) { + return false; + } + + return $args; + } + + // Expands wp_insert_post to include filtered content + function filter_post_content_and_add_links( $post_object ) { + global $post; + $post = $post_object; + /** + * Filters whether to prevent sending post data to .com + * + * Passing true to the filter will prevent the post data from being sent + * to the WordPress.com. + * Instead we pass data that will still enable us to do a checksum against the + * Jetpacks data but will prevent us from displaying the data on in the API as well as + * other services. + * @since 4.2.0 + * + * @param boolean false prevent post data from being synced to WordPress.com + * @param mixed $post WP_POST object + */ + if ( apply_filters( 'jetpack_sync_prevent_sending_post_data', false, $post ) ) { + // We only send the bare necessary object to be able to create a checksum. + $blocked_post = new stdClass(); + $blocked_post->ID = $post->ID; + $blocked_post->post_modified = $post->post_modified; + $blocked_post->post_modified_gmt = $post->post_modified_gmt; + $blocked_post->post_status = 'jetpack_sync_blocked'; + + return $blocked_post; + } + + if ( 0 < strlen( $post->post_password ) ) { + $post->post_password = 'auto-' . wp_generate_password( 10, false ); + } + /** This filter is already documented in core. wp-includes/post-template.php */ + $post->post_content_filtered = apply_filters( 'the_content', $post->post_content ); + $post->post_excerpt_filtered = apply_filters( 'the_content', $post->post_excerpt ); + $post->permalink = get_permalink( $post->ID ); + $post->shortlink = wp_get_shortlink( $post->ID ); + $post->dont_email_post_to_subs = Jetpack::is_module_active( 'subscriptions' ) ? + get_post_meta( $post->ID, '_jetpack_dont_email_post_to_subs', true ) : + true; // Don't email subscription if the subscription module is not active. + + return $post; + } + + public function expand_post_ids( $args ) { + $post_ids = $args[0]; + + $posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) ); + $posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts ); + + return array( + $posts, + $this->get_metadata( $post_ids, 'post' ), + $this->get_term_relationships( $post_ids ), + ); + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-protect.php b/plugins/jetpack/sync/class.jetpack-sync-module-protect.php new file mode 100644 index 00000000..4a07c6b7 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-protect.php @@ -0,0 +1,16 @@ +get_col( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s", $taxonomy ) ); // Should we set a limit here? + // Request posts in groups of N for efficiency + $chunked_term_ids = array_chunk( $term_ids, self::ARRAY_CHUNK_SIZE ); + + // Send each chunk as an array of objects + foreach ( $chunked_term_ids as $chunk ) { + do_action( 'jetpack_full_sync_terms', $chunk, $taxonomy ); + $total_chunks_counter ++; + } + } + + return $total_chunks_counter; + } + + function estimate_full_sync_actions( $config ) { + // TODO - make this (and method above) more efficient for large numbers of terms or taxonomies + global $wpdb; + + $taxonomies = get_taxonomies(); + $total_chunks_counter = 0; + foreach ( $taxonomies as $taxonomy ) { + $total_ids = $wpdb->get_var( $wpdb->prepare( "SELECT count(term_id) FROM $wpdb->term_taxonomy WHERE taxonomy = %s", $taxonomy ) ); + $total_chunks_counter += (int) ceil( $total_ids / self::ARRAY_CHUNK_SIZE ); + } + + return $total_chunks_counter; + } + + function get_full_sync_actions() { + return array( 'jetpack_full_sync_terms' ); + } + + function save_term_handler( $term_id, $tt_id, $taxonomy ) { + if ( class_exists( 'WP_Term' ) ) { + $term_object = WP_Term::get_instance( $term_id, $taxonomy ); + } else { + $term_object = get_term_by( 'id', $term_id, $taxonomy ); + } + + /** + * Fires when the client needs to sync a new term + * + * @since 4.2.0 + * + * @param object the Term object + */ + do_action( 'jetpack_sync_save_term', $term_object ); + } + + function set_taxonomy_whitelist( $taxonomies ) { + $this->taxonomy_whitelist = $taxonomies; + } + + function set_defaults() { + $this->taxonomy_whitelist = Jetpack_Sync_Defaults::$default_taxonomy_whitelist; + } + + public function expand_term_ids( $args ) { + global $wp_version; + $term_ids = $args[0]; + $taxonomy = $args[1]; + // version 4.5 or higher + if ( version_compare( $wp_version, 4.5, '>=' ) ) { + $terms = get_terms( array( + 'taxonomy' => $taxonomy, + 'hide_empty' => false, + 'include' => $term_ids, + ) ); + } else { + $terms = get_terms( $taxonomy, array( + 'hide_empty' => false, + 'include' => $term_ids, + ) ); + } + + return $terms; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-themes.php b/plugins/jetpack/sync/class.jetpack-sync-module-themes.php new file mode 100644 index 00000000..16f0451d --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-themes.php @@ -0,0 +1,71 @@ +get_theme_support_info() ); + } + + public function enqueue_full_sync_actions( $config ) { + /** + * Tells the client to sync all theme data to the server + * + * @since 4.2.0 + * + * @param boolean Whether to expand theme data (should always be true) + */ + do_action( 'jetpack_full_sync_theme_data', true ); + return 1; // The number of actions enqueued + } + + public function estimate_full_sync_actions( $config ) { + return 1; + } + + public function init_before_send() { + add_filter( 'jetpack_sync_before_send_jetpack_full_sync_theme_data', array( $this, 'expand_theme_data' ) ); + } + + function get_full_sync_actions() { + return array( 'jetpack_full_sync_theme_data' ); + } + + function expand_theme_data() { + return array( $this->get_theme_support_info() ); + } + + private function get_theme_support_info() { + global $_wp_theme_features; + + $theme_support = array(); + + foreach ( Jetpack_Sync_Defaults::$default_theme_support_whitelist as $theme_feature ) { + $has_support = current_theme_supports( $theme_feature ); + if ( $has_support ) { + $theme_support[ $theme_feature ] = $_wp_theme_features[ $theme_feature ]; + } + } + + return $theme_support; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-updates.php b/plugins/jetpack/sync/class.jetpack-sync-module-updates.php new file mode 100644 index 00000000..f3f23774 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-updates.php @@ -0,0 +1,85 @@ + get_site_transient( 'update_core' ), + 'plugins' => get_site_transient( 'update_plugins' ), + 'themes' => get_site_transient( 'update_themes' ), + ); + } + + // removes unnecessary keys from synced updates data + function filter_update_keys( $args ) { + $updates = $args[0]; + + if ( isset( $updates->no_update ) ) { + unset( $updates->no_update ); + } + + return $args; + } + + function filter_upgrader_process_complete( $args ) { + array_shift( $args ); + + return $args; + } + + public function expand_updates( $args ) { + if ( $args[0] ) { + return $this->get_all_updates(); + } + + return $args; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module-users.php b/plugins/jetpack/sync/class.jetpack-sync-module-users.php new file mode 100644 index 00000000..2b6e1c09 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module-users.php @@ -0,0 +1,217 @@ +sanitize_user( $user ); + + return $this->add_to_user( $user ); + } + + public function sanitize_user( $user ) { + // this create a new user object and stops the passing of the object by reference. + $user = unserialize( serialize( $user ) ); + unset( $user->data->user_pass ); + + return $user; + } + + public function add_to_user( $user ) { + $user->allowed_mime_types = get_allowed_mime_types( $user ); + + return $user; + } + + public function expand_user( $args ) { + list( $user ) = $args; + + if ( $user ) { + return array( $this->add_to_user( $user ) ); + } + + return false; + } + + public function expand_login_username( $args ) { + list( $login, $user ) = $args; + $user = $this->sanitize_user( $user ); + + return array( $login, $user ); + } + + public function expand_logout_username( $args, $user_id ) { + $user = get_userdata( $user_id ); + $user = $this->sanitize_user( $user ); + $login = $user->data->user_login; + + return array( $login, $user ); + } + + function save_user_handler( $user_id, $old_user_data = null ) { + + // ensure we only sync users who are members of the current blog + if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) { + return; + } + + $user = $this->sanitize_user( get_user_by( 'id', $user_id ) ); + + // Older versions of WP don't pass the old_user_data in ->data + if ( isset( $old_user_data->data ) ) { + $old_user = $old_user_data->data; + } else { + $old_user = $old_user_data; + } + + if ( $old_user !== null ) { + unset( $old_user->user_pass ); + if ( serialize( $old_user ) === serialize( $user->data ) ) { + return; + } + } + /** + * Fires when the client needs to sync an updated user + * + * @since 4.2.0 + * + * @param object The WP_User object + */ + do_action( 'jetpack_sync_save_user', $user ); + } + + function save_user_role_handler( $user_id, $role, $old_roles = null ) { + $user = $this->sanitize_user( get_user_by( 'id', $user_id ) ); + + /** + * Fires when the client needs to sync an updated user + * + * @since 4.2.0 + * + * @param object The WP_User object + */ + do_action( 'jetpack_sync_save_user', $user ); + } + + function save_user_cap_handler( $meta_id, $user_id, $meta_key, $capabilities ) { + + // if a user is currently being removed as a member of this blog, we don't fire the event + if ( current_filter() === 'deleted_user_meta' + && + preg_match( '/capabilities|user_level/', $meta_key ) + && + ! is_user_member_of_blog( $user_id, get_current_blog_id() ) + ) { + return; + } + + $user = get_user_by( 'id', $user_id ); + if ( $meta_key === $user->cap_key ) { + /** + * Fires when the client needs to sync an updated user + * + * @since 4.2.0 + * + * @param object The Sanitized WP_User object + */ + do_action( 'jetpack_sync_save_user', $this->sanitize_user( $user ) ); + } + } + + public function enqueue_full_sync_actions( $config ) { + global $wpdb; + return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_users', $wpdb->usermeta, 'user_id', $this->get_where_sql( $config ) ); + } + + public function estimate_full_sync_actions( $config ) { + global $wpdb; + + $query = "SELECT count(*) FROM $wpdb->usermeta"; + + if ( $where_sql = $this->get_where_sql( $config ) ) { + $query .= ' WHERE ' . $where_sql; + } + + $count = $wpdb->get_var( $query ); + + return (int) ceil( $count / self::ARRAY_CHUNK_SIZE ); + } + + private function get_where_sql( $config ) { + global $wpdb; + + $query = "meta_key = '{$wpdb->prefix}capabilities'"; + + // config is a list of user IDs to sync + if ( is_array( $config ) ) { + $query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')'; + } + + return $query; + } + + function get_full_sync_actions() { + return array( 'jetpack_full_sync_users' ); + } + + function get_initial_sync_user_config() { + global $wpdb; + + $user_ids = $wpdb->get_col( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}user_level' AND meta_value > 0 LIMIT " . ( self::MAX_INITIAL_SYNC_USERS + 1 ) ); + + if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) { + return $user_ids; + } else { + return false; + } + } + + public function expand_users( $args ) { + $user_ids = $args[0]; + + return array_map( array( $this, 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) ); + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-module.php b/plugins/jetpack/sync/class.jetpack-sync-module.php new file mode 100644 index 00000000..bfcada54 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-module.php @@ -0,0 +1,107 @@ +get_col( "SELECT {$id_field} FROM {$table_name} WHERE {$where_sql} AND {$id_field} > {$previous_id} ORDER BY {$id_field} ASC LIMIT {$items_per_page}" ) ) { + // Request posts in groups of N for efficiency + $chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE ); + + $listener->bulk_enqueue_full_sync_actions( $action_name, $chunked_ids ); + + $chunk_count += count( $chunked_ids ); + $page += 1; + $previous_id = end( $ids ); + } + + return $chunk_count; + } + + protected function get_metadata( $ids, $meta_type ) { + global $wpdb; + $table = _get_meta_table( $meta_type ); + $id = $meta_type . '_id'; + if ( ! $table ) { + return array(); + } + + return array_map( + array( $this, 'unserialize_meta' ), + $wpdb->get_results( "SELECT $id, meta_key, meta_value, meta_id FROM $table WHERE $id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . ' )', OBJECT ) + ); + } + + protected function get_term_relationships( $ids ) { + global $wpdb; + + return $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . ' )', OBJECT ); + } + + public function unserialize_meta( $meta ) { + $meta->meta_value = maybe_unserialize( $meta->meta_value ); + return $meta; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-modules.php b/plugins/jetpack/sync/class.jetpack-sync-modules.php new file mode 100644 index 00000000..e197c20e --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-modules.php @@ -0,0 +1,90 @@ +set_defaults(); + } + } + + public static function get_module( $module_name ) { + foreach ( self::get_modules() as $module ) { + if ( $module->name() === $module_name ) { + return $module; + } + } + + return false; + } + + static function initialize_modules() { + /** + * Filters the list of class names of sync modules. + * If you add to this list, make sure any classes implement the + * Jetpack_Sync_Module interface. + * + * @since 4.2.0 + */ + $modules = apply_filters( 'jetpack_sync_modules', self::$default_sync_modules ); + + return array_map( array( 'Jetpack_Sync_Modules', 'initialize_module' ), $modules ); + } + + static function initialize_module( $module_name ) { + $module = new $module_name; + $module->set_defaults(); + + return $module; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-queue.php b/plugins/jetpack/sync/class.jetpack-sync-queue.php new file mode 100644 index 00000000..1f120bb8 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-queue.php @@ -0,0 +1,419 @@ +id = $id; + $this->items_with_ids = $items_with_ids; + } + + public function get_items() { + return array_combine( $this->get_item_ids(), $this->get_item_values() ); + } + + public function get_item_values() { + return Jetpack_Sync_Utils::get_item_values( $this->items_with_ids ); + } + + public function get_item_ids() { + return Jetpack_Sync_Utils::get_item_ids( $this->items_with_ids ); + } +} + +/** + * A persistent queue that can be flushed in increments of N items, + * and which blocks reads until checked-out buffers are checked in or + * closed. This uses raw SQL for two reasons: speed, and not triggering + * tons of added_option callbacks. + */ +class Jetpack_Sync_Queue { + public $id; + private $row_iterator; + + function __construct( $id ) { + $this->id = str_replace( '-', '_', $id ); // necessary to ensure we don't have ID collisions in the SQL + $this->row_iterator = 0; + } + + function add( $item ) { + global $wpdb; + $added = false; + // this basically tries to add the option until enough time has elapsed that + // it has a unique (microtime-based) option key + while ( ! $added ) { + $rows_added = $wpdb->query( $wpdb->prepare( + "INSERT INTO $wpdb->options (option_name, option_value,autoload) VALUES (%s, %s,%s)", + $this->get_next_data_row_option_name(), + serialize( $item ), + 'no' + ) ); + $added = ( 0 !== $rows_added ); + } + + do_action( 'jpsq_item_added' ); + } + + // Attempts to insert all the items in a single SQL query. May be subject to query size limits! + function add_all( $items ) { + global $wpdb; + $base_option_name = $this->get_next_data_row_option_name(); + + $query = "INSERT INTO $wpdb->options (option_name, option_value,autoload) VALUES "; + + $rows = array(); + + for ( $i = 0; $i < count( $items ); $i += 1 ) { + $option_name = esc_sql( $base_option_name . '-' . $i ); + $option_value = esc_sql( serialize( $items[ $i ] ) ); + $rows[] = "('$option_name', '$option_value', 'no')"; + } + + $rows_added = $wpdb->query( $query . join( ',', $rows ) ); + + if ( count( $items ) === $rows_added ) { + return new WP_Error( 'row_count_mismatch', "The number of rows inserted didn't match the size of the input array" ); + } + + do_action( 'jpsq_items_added', $rows_added ); + } + + // Peek at the front-most item on the queue without checking it out + function peek( $count = 1 ) { + $items = $this->fetch_items( $count ); + if ( $items ) { + return Jetpack_Sync_Utils::get_item_values( $items ); + } + + return array(); + } + + // lag is the difference in time between the age of the oldest item + // (aka first or frontmost item) and the current time + function lag() { + return self::get_lag( $this->id ); + } + + static function get_lag( $id ) { + global $wpdb; + + $first_item_name = $wpdb->get_var( $wpdb->prepare( + "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT 1", + "jpsq_{$id}-%" + ) ); + + if ( ! $first_item_name ) { + return 0; + } + + // break apart the item name to get the timestamp + $matches = null; + if ( preg_match( '/^jpsq_' . $id . '-(\d+\.\d+)-/', $first_item_name, $matches ) ) { + return microtime( true ) - floatval( $matches[1] ); + } else { + return 0; + } + } + + function reset() { + global $wpdb; + $this->delete_checkout_id(); + $wpdb->query( $wpdb->prepare( + "DELETE FROM $wpdb->options WHERE option_name LIKE %s", "jpsq_{$this->id}-%" + ) ); + } + + function size() { + global $wpdb; + + return (int) $wpdb->get_var( $wpdb->prepare( + "SELECT count(*) FROM $wpdb->options WHERE option_name LIKE %s", "jpsq_{$this->id}-%" + ) ); + } + + // we use this peculiar implementation because it's much faster than count(*) + function has_any_items() { + global $wpdb; + $value = $wpdb->get_var( $wpdb->prepare( + "SELECT exists( SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s )", "jpsq_{$this->id}-%" + ) ); + + return ( $value === '1' ); + } + + function checkout( $buffer_size ) { + if ( $this->get_checkout_id() ) { + return new WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' ); + } + + $buffer_id = uniqid(); + + $result = $this->set_checkout_id( $buffer_id ); + + if ( ! $result || is_wp_error( $result ) ) { + return $result; + } + + $items = $this->fetch_items( $buffer_size ); + + if ( count( $items ) === 0 ) { + return false; + } + + $buffer = new Jetpack_Sync_Queue_Buffer( $buffer_id, array_slice( $items, 0, $buffer_size ) ); + + return $buffer; + } + + // this checks out rows until it either empties the queue or hits a certain memory limit + // it loads the sizes from the DB first so that it doesn't accidentally + // load more data into memory than it needs to. + // The only way it will load more items than $max_size is if a single queue item + // exceeds the memory limit, but in that case it will send that item by itself. + function checkout_with_memory_limit( $max_memory, $max_buffer_size = 500 ) { + if ( $this->get_checkout_id() ) { + return new WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' ); + } + + $buffer_id = uniqid(); + + $result = $this->set_checkout_id( $buffer_id ); + + if ( ! $result || is_wp_error( $result ) ) { + return $result; + } + + // get the map of buffer_id -> memory_size + global $wpdb; + + $items_with_size = $wpdb->get_results( + $wpdb->prepare( + "SELECT option_name AS id, LENGTH(option_value) AS value_size FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d", + "jpsq_{$this->id}-%", + $max_buffer_size + ), + OBJECT + ); + + $total_memory = 0; + $item_ids = array(); + + foreach ( $items_with_size as $item_with_size ) { + $total_memory += $item_with_size->value_size; + + // if this is the first item and it exceeds memory, allow loop to continue + // we will exit on the next iteration instead + if ( $total_memory > $max_memory && count( $item_ids ) > 0 ) { + break; + } + $item_ids[] = $item_with_size->id; + } + + $items = $this->fetch_items_by_id( $item_ids ); + + if ( count( $items ) === 0 ) { + $this->delete_checkout_id(); + + return false; + } + + $buffer = new Jetpack_Sync_Queue_Buffer( $buffer_id, $items ); + + return $buffer; + } + + function checkin( $buffer ) { + $is_valid = $this->validate_checkout( $buffer ); + + if ( is_wp_error( $is_valid ) ) { + return $is_valid; + } + + $this->delete_checkout_id(); + + return true; + } + + function close( $buffer, $ids_to_remove = null ) { + $is_valid = $this->validate_checkout( $buffer ); + + if ( is_wp_error( $is_valid ) ) { + return $is_valid; + } + + $this->delete_checkout_id(); + + // by default clear all items in the buffer + if ( is_null( $ids_to_remove ) ) { + $ids_to_remove = $buffer->get_item_ids(); + } + + global $wpdb; + + if ( count( $ids_to_remove ) > 0 ) { + $sql = "DELETE FROM $wpdb->options WHERE option_name IN (" . implode( ', ', array_fill( 0, count( $ids_to_remove ), '%s' ) ) . ')'; + $query = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql ), $ids_to_remove ) ); + $wpdb->query( $query ); + } + + return true; + } + + function flush_all() { + $items = Jetpack_Sync_Utils::get_item_values( $this->fetch_items() ); + $this->reset(); + + return $items; + } + + function get_all() { + return $this->fetch_items(); + } + + // use with caution, this could allow multiple processes to delete + // and send from the queue at the same time + function force_checkin() { + $this->delete_checkout_id(); + } + + // used to lock checkouts from the queue. + // tries to wait up to $timeout seconds for the queue to be empty + function lock( $timeout = 30 ) { + $tries = 0; + + while ( $this->has_any_items() && $tries < $timeout ) { + sleep( 1 ); + $tries += 1; + } + + if ( $tries === 30 ) { + return new WP_Error( 'lock_timeout', 'Timeout waiting for sync queue to empty' ); + } + + if ( $this->get_checkout_id() ) { + return new WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' ); + } + + // hopefully this means we can acquire a checkout? + $result = $this->set_checkout_id( 'lock' ); + + if ( ! $result || is_wp_error( $result ) ) { + return $result; + } + + return true; + } + + function unlock() { + $this->delete_checkout_id(); + } + + private function get_checkout_id() { + return get_transient( $this->get_checkout_transient_name() ); + } + + private function set_checkout_id( $checkout_id ) { + return set_transient( $this->get_checkout_transient_name(), $checkout_id, 5 * 60 ); // 5 minute timeout + } + + private function delete_checkout_id() { + delete_transient( $this->get_checkout_transient_name() ); + } + + private function get_checkout_transient_name() { + return "jpsq_{$this->id}_checkout"; + } + + private function get_next_data_row_option_name() { + // this option is specifically chosen to, as much as possible, preserve time order + // and minimise the possibility of collisions between multiple processes working + // at the same time + // TODO: confirm we only need to support PHP 5.05+ (otherwise we'll need to emulate microtime as float, and avoid PHP_INT_MAX) + // @see: http://php.net/manual/en/function.microtime.php + $timestamp = sprintf( '%.6f', microtime( true ) ); + + // row iterator is used to avoid collisions where we're writing data waaay fast in a single process + if ( $this->row_iterator === PHP_INT_MAX ) { + $this->row_iterator = 0; + } else { + $this->row_iterator += 1; + } + + return 'jpsq_' . $this->id . '-' . $timestamp . '-' . getmypid() . '-' . $this->row_iterator; + } + + private function fetch_items( $limit = null ) { + global $wpdb; + + if ( $limit ) { + $query_sql = $wpdb->prepare( "SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d", "jpsq_{$this->id}-%", $limit ); + } else { + $query_sql = $wpdb->prepare( "SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC", "jpsq_{$this->id}-%" ); + } + + $items = $wpdb->get_results( $query_sql, OBJECT ); + foreach ( $items as $item ) { + $item->value = maybe_unserialize( $item->value ); + } + + return $items; + } + + private function fetch_items_by_id( $item_ids ) { + global $wpdb; + + if ( count( $item_ids ) > 0 ) { + $sql = "SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name IN (" . implode( ', ', array_fill( 0, count( $item_ids ), '%s' ) ) . ') ORDER BY option_name ASC'; + $query = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql ), $item_ids ) ); + $items = $wpdb->get_results( $query, OBJECT ); + foreach ( $items as $item ) { + $item->value = maybe_unserialize( $item->value ); + } + + return $items; + } else { + return array(); + } + } + + private function validate_checkout( $buffer ) { + if ( ! $buffer instanceof Jetpack_Sync_Queue_Buffer ) { + return new WP_Error( 'not_a_buffer', 'You must checkin an instance of Jetpack_Sync_Queue_Buffer' ); + } + + $checkout_id = $this->get_checkout_id(); + + if ( ! $checkout_id ) { + return new WP_Error( 'buffer_not_checked_out', 'There are no checked out buffers' ); + } + + if ( $checkout_id != $buffer->id ) { + return new WP_Error( 'buffer_mismatch', 'The buffer you checked in was not checked out' ); + } + + return true; + } +} + +class Jetpack_Sync_Utils { + + static function get_item_values( $items ) { + return array_map( array( __CLASS__, 'get_item_value' ), $items ); + } + + static function get_item_ids( $items ) { + return array_map( array( __CLASS__, 'get_item_id' ), $items ); + } + + static private function get_item_value( $item ) { + return $item->value; + } + + static private function get_item_id( $item ) { + return $item->id; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-sender.php b/plugins/jetpack/sync/class.jetpack-sync-sender.php new file mode 100644 index 00000000..3793af11 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-sender.php @@ -0,0 +1,321 @@ +:( + protected function __construct() { + $this->set_defaults(); + $this->init(); + } + + private function init() { + foreach ( Jetpack_Sync_Modules::get_modules() as $module ) { + $module->init_before_send(); + } + } + + public function get_next_sync_time() { + return (double) get_option( self::NEXT_SYNC_TIME_OPTION_NAME, 0 ); + } + + public function set_next_sync_time( $time ) { + return update_option( self::NEXT_SYNC_TIME_OPTION_NAME, $time, true ); + } + + public function do_sync() { + // don't sync if importing + if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) { + return false; + } + + // don't sync if we are throttled + if ( $this->get_next_sync_time() > microtime( true ) ) { + return false; + } + + $start_time = microtime( true ); + + $full_sync_result = $this->do_sync_for_queue( $this->full_sync_queue ); + $sync_result = $this->do_sync_for_queue( $this->sync_queue ); + + $exceeded_sync_wait_threshold = ( microtime( true ) - $start_time ) > (double) $this->get_sync_wait_threshold(); + + if ( is_wp_error( $full_sync_result ) || is_wp_error( $sync_result ) ) { + $this->set_next_sync_time( time() + self::WPCOM_ERROR_SYNC_DELAY ); + $full_sync_result = false; + $sync_result = false; + } elseif ( $exceeded_sync_wait_threshold ) { + // if we actually sent data and it took a while, wait before sending again + $this->set_next_sync_time( time() + $this->get_sync_wait_time() ); + } + + // we use OR here because if either one returns true then the caller should + // be allowed to call do_sync again, as there may be more items + return $full_sync_result || $sync_result; + } + + public function do_sync_for_queue( $queue ) { + + do_action( 'jetpack_sync_before_send_queue_' . $queue->id ); + + if ( $queue->size() === 0 ) { + return false; + } + + // now that we're sure we are about to sync, try to + // ignore user abort so we can avoid getting into a + // bad state + if ( function_exists( 'ignore_user_abort' ) ) { + ignore_user_abort( true ); + } + + $buffer = $queue->checkout_with_memory_limit( $this->dequeue_max_bytes, $this->upload_max_rows ); + + if ( ! $buffer ) { + // buffer has no items + return false; + } + + if ( is_wp_error( $buffer ) ) { + // another buffer is currently sending + return false; + } + + $upload_size = 0; + $items_to_send = array(); + $items = $buffer->get_items(); + + // set up current screen to avoid errors rendering content + require_once(ABSPATH . 'wp-admin/includes/class-wp-screen.php'); + require_once(ABSPATH . 'wp-admin/includes/screen.php'); + set_current_screen( 'sync' ); + + $skipped_items_ids = array(); + + // we estimate the total encoded size as we go by encoding each item individually + // this is expensive, but the only way to really know :/ + foreach ( $items as $key => $item ) { + // Suspending cache addition help prevent overloading in memory cache of large sites. + wp_suspend_cache_addition( true ); + /** + * Modify the data within an action before it is serialized and sent to the server + * For example, during full sync this expands Post ID's into full Post objects, + * so that we don't have to serialize the whole object into the queue. + * + * @since 4.2.0 + * + * @param array The action parameters + * @param int The ID of the user who triggered the action + */ + $item[1] = apply_filters( 'jetpack_sync_before_send_' . $item[0], $item[1], $item[2] ); + wp_suspend_cache_addition( false ); + if ( $item[1] === false ) { + $skipped_items_ids[] = $key; + continue; + } + + $encoded_item = $this->codec->encode( $item ); + + $upload_size += strlen( $encoded_item ); + + if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) { + break; + } + + $items_to_send[ $key ] = $encoded_item; + } + + /** + * Fires when data is ready to send to the server. + * Return false or WP_Error to abort the sync (e.g. if there's an error) + * The items will be automatically re-sent later + * + * @since 4.2.0 + * + * @param array $data The action buffer + * @param string $codec The codec name used to encode the data + * @param double $time The current time + * @param string $queue The queue used to send ('sync' or 'full_sync') + */ + $processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->codec->name(), microtime( true ), $queue->id ); + + if ( ! $processed_item_ids || is_wp_error( $processed_item_ids ) ) { + $checked_in_item_ids = $queue->checkin( $buffer ); + + if ( is_wp_error( $checked_in_item_ids ) ) { + error_log( 'Error checking in buffer: ' . $checked_in_item_ids->get_error_message() ); + $queue->force_checkin(); + } + + if ( is_wp_error( $processed_item_ids ) ) { + return $processed_item_ids; + } + + // returning a WP_Error is a sign to the caller that we should wait a while + // before syncing again + return new WP_Error( 'server_error' ); + + } else { + + // detect if the last item ID was an error + $had_wp_error = is_wp_error( end( $processed_item_ids ) ); + + if ( $had_wp_error ) { + $wp_error = array_pop( $processed_item_ids ); + } + + // also checkin any items that were skipped + if ( count( $skipped_items_ids ) > 0 ) { + $processed_item_ids = array_merge( $processed_item_ids, $skipped_items_ids ); + } + + $processed_items = array_intersect_key( $items, array_flip( $processed_item_ids ) ); + + /** + * Allows us to keep track of all the actions that have been sent. + * Allows us to calculate the progress of specific actions. + * + * @since 4.2.0 + * + * @param array $processed_actions The actions that we send successfully. + */ + do_action( 'jetpack_sync_processed_actions', $processed_items ); + + $queue->close( $buffer, $processed_item_ids ); + + // returning a WP_Error is a sign to the caller that we should wait a while + // before syncing again + if ( $had_wp_error ) { + return $wp_error; + } + } + + return true; + } + + function get_sync_queue() { + return $this->sync_queue; + } + + function get_full_sync_queue() { + return $this->full_sync_queue; + } + + function get_codec() { + return $this->codec; + } + + function send_checksum() { + require_once 'class.jetpack-sync-wp-replicastore.php'; + $store = new Jetpack_Sync_WP_Replicastore(); + do_action( 'jetpack_sync_checksum', $store->checksum_all() ); + } + + function reset_sync_queue() { + $this->sync_queue->reset(); + } + + function set_dequeue_max_bytes( $size ) { + $this->dequeue_max_bytes = $size; + } + + // in bytes + function set_upload_max_bytes( $max_bytes ) { + $this->upload_max_bytes = $max_bytes; + } + + // in rows + function set_upload_max_rows( $max_rows ) { + $this->upload_max_rows = $max_rows; + } + + // in seconds + function set_sync_wait_time( $seconds ) { + $this->sync_wait_time = $seconds; + } + + function get_sync_wait_time() { + return $this->sync_wait_time; + } + + // in seconds + function set_sync_wait_threshold( $seconds ) { + $this->sync_wait_threshold = $seconds; + } + + function get_sync_wait_threshold() { + return $this->sync_wait_threshold; + } + + function set_defaults() { + $this->sync_queue = new Jetpack_Sync_Queue( 'sync' ); + $this->full_sync_queue = new Jetpack_Sync_Queue( 'full_sync' ); + $this->codec = new Jetpack_Sync_JSON_Deflate_Codec(); + + // saved settings + Jetpack_Sync_Settings::set_importing( null ); + $settings = Jetpack_Sync_Settings::get_settings(); + $this->set_dequeue_max_bytes( $settings['dequeue_max_bytes'] ); + $this->set_upload_max_bytes( $settings['upload_max_bytes'] ); + $this->set_upload_max_rows( $settings['upload_max_rows'] ); + $this->set_sync_wait_time( $settings['sync_wait_time'] ); + $this->set_sync_wait_threshold( $settings['sync_wait_threshold'] ); + } + + function reset_data() { + $this->reset_sync_queue(); + + foreach ( Jetpack_Sync_Modules::get_modules() as $module ) { + $module->reset_data(); + } + + delete_option( self::SYNC_THROTTLE_OPTION_NAME ); + delete_option( self::NEXT_SYNC_TIME_OPTION_NAME ); + + Jetpack_Sync_Settings::reset_data(); + } + + function uninstall() { + // Lets delete all the other fun stuff like transient and option and the sync queue + $this->reset_data(); + + // delete the full sync status + delete_option( 'jetpack_full_sync_status' ); + + // clear the sync cron. + wp_clear_scheduled_hook( 'jetpack_sync_cron' ); + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-server.php b/plugins/jetpack/sync/class.jetpack-sync-server.php new file mode 100644 index 00000000..752aa666 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-server.php @@ -0,0 +1,106 @@ +:( + function __construct() { + $this->codec = new Jetpack_Sync_JSON_Deflate_Codec(); + } + + function set_codec( iJetpack_Sync_Codec $codec ) { + $this->codec = $codec; + } + + function attempt_request_lock( $blog_id, $expiry = self::BLOG_LOCK_TRANSIENT_EXPIRY ) { + $transient_name = $this->get_concurrent_request_transient_name( $blog_id ); + $locked_time = get_site_transient( $transient_name ); + if ( $locked_time ) { + return false; + } + set_site_transient( $transient_name, microtime( true ), $expiry ); + + return true; + } + + private function get_concurrent_request_transient_name( $blog_id ) { + return self::BLOG_LOCK_TRANSIENT_PREFIX . $blog_id; + } + + function remove_request_lock( $blog_id ) { + delete_site_transient( $this->get_concurrent_request_transient_name( $blog_id ) ); + } + + function receive( $data, $token = null, $sent_timestamp = null, $queue_id = null ) { + $start_time = microtime( true ); + if ( ! is_array( $data ) ) { + return new WP_Error( 'action_decoder_error', 'Events must be an array' ); + } + + if ( $token && ! $this->attempt_request_lock( $token->blog_id ) ) { + /** + * Fires when the server receives two concurrent requests from the same blog + * + * @since 4.2.0 + * + * @param token The token object of the misbehaving site + */ + do_action( 'jetpack_sync_multi_request_fail', $token ); + + return new WP_Error( 'concurrent_request_error', 'There is another request running for the same blog ID' ); + } + + $events = wp_unslash( array_map( array( $this->codec, 'decode' ), $data ) ); + $events_processed = array(); + + /** + * Fires when an array of actions are received from a remote Jetpack site + * + * @since 4.2.0 + * + * @param array Array of actions received from the remote site + */ + do_action( 'jetpack_sync_remote_actions', $events, $token ); + + foreach ( $events as $key => $event ) { + list( $action_name, $args, $user_id, $timestamp, $silent ) = $event; + + /** + * Fires when an action is received from a remote Jetpack site + * + * @since 4.2.0 + * + * @param string $action_name The name of the action executed on the remote site + * @param array $args The arguments passed to the action + * @param int $user_id The external_user_id who did the action + * @param bool $silent Whether the item was created via import + * @param double $timestamp Timestamp (in seconds) when the action occurred + * @param double $sent_timestamp Timestamp (in seconds) when the action was transmitted + * @param string $queue_id ID of the queue from which the event was sent (sync or full_sync) + * @param array $token The auth token used to invoke the API + */ + do_action( 'jetpack_sync_remote_action', $action_name, $args, $user_id, $silent, $timestamp, $sent_timestamp, $queue_id, $token ); + + $events_processed[] = $key; + + if ( microtime( true ) - $start_time > self::MAX_TIME_PER_REQUEST_IN_SECONDS ) { + break; + } + } + + if ( $token ) { + $this->remove_request_lock( $token->blog_id ); + } + + return $events_processed; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-settings.php b/plugins/jetpack/sync/class.jetpack-sync-settings.php new file mode 100644 index 00000000..5e5e39db --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-settings.php @@ -0,0 +1,130 @@ + true, + 'upload_max_bytes' => true, + 'upload_max_rows' => true, + 'sync_wait_time' => true, + 'sync_wait_threshold' => true, + 'max_queue_size' => true, + 'max_queue_lag' => true, + 'queue_max_writes_sec' => true, + 'post_types_blacklist' => true, + 'meta_blacklist' => true, + 'disable' => true, + ); + + static $is_importing; + static $is_doing_cron; + + static $settings_cache = array(); // some settings can be expensive to compute - let's cache them + + static function get_settings() { + $settings = array(); + foreach ( array_keys( self::$valid_settings ) as $setting ) { + $settings[ $setting ] = self::get_setting( $setting ); + } + + return $settings; + } + + // Fetches the setting. It saves it if the setting doesn't exist, so that it gets + // autoloaded on page load rather than re-queried every time. + static function get_setting( $setting ) { + if ( ! isset( self::$valid_settings[ $setting ] ) ) { + return false; + } + + if ( isset( self::$settings_cache[ $setting ] ) ) { + return self::$settings_cache[ $setting ]; + } + + $value = get_option( self::SETTINGS_OPTION_PREFIX . $setting ); + + if ( false === $value ) { + $default_name = "default_$setting"; // e.g. default_dequeue_max_bytes + $value = Jetpack_Sync_Defaults::$$default_name; + update_option( self::SETTINGS_OPTION_PREFIX . $setting, $value, true ); + } + + if ( is_numeric( $value ) ) { + $value = intval( $value ); + } + + // specifically for the post_types blacklist, we want to include the hardcoded settings + if ( $setting === 'post_types_blacklist' ) { + $value = array_unique( array_merge( $value, Jetpack_Sync_Defaults::$blacklisted_post_types ) ); + } + + // ditto for meta blacklist + if ( $setting === 'meta_blacklist' ) { + $value = array_unique( array_merge( $value, Jetpack_Sync_Defaults::$default_blacklist_meta_keys ) ); + } + + self::$settings_cache[ $setting ] = $value; + + return $value; + } + + static function update_settings( $new_settings ) { + $validated_settings = array_intersect_key( $new_settings, self::$valid_settings ); + foreach ( $validated_settings as $setting => $value ) { + update_option( self::SETTINGS_OPTION_PREFIX . $setting, $value, true ); + unset( self::$settings_cache[ $setting ] ); + + // if we set the disabled option to true, clear the queues + if ( 'disable' === $setting && !! $value ) { + require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php'; + $listener = Jetpack_Sync_Listener::get_instance(); + $listener->get_sync_queue()->reset(); + $listener->get_full_sync_queue()->reset(); + } + } + } + + // returns escapted SQL that can be injected into a WHERE clause + static function get_blacklisted_post_types_sql() { + return 'post_type NOT IN (\'' . join( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_types_blacklist' ) ) ) . '\')'; + } + + static function reset_data() { + $valid_settings = self::$valid_settings; + self::$settings_cache = array(); + foreach ( $valid_settings as $option => $value ) { + delete_option( self::SETTINGS_OPTION_PREFIX . $option ); + } + self::set_importing( null ); + self::set_doing_cron( null ); + } + + static function set_importing( $is_importing ) { + // set to NULL to revert to WP_IMPORTING, the standard behaviour + self::$is_importing = $is_importing; + } + + static function is_importing() { + if ( ! is_null( self::$is_importing ) ) { + return self::$is_importing; + } + + return defined( 'WP_IMPORTING' ) && WP_IMPORTING; + } + + static function set_doing_cron( $is_doing_cron ) { + // set to NULL to revert to WP_IMPORTING, the standard behaviour + self::$is_doing_cron = $is_doing_cron; + } + + static function is_doing_cron() { + if ( ! is_null( self::$is_doing_cron ) ) { + return self::$is_doing_cron; + } + + return defined( 'DOING_CRON' ) && DOING_CRON; + } +} diff --git a/plugins/jetpack/sync/class.jetpack-sync-users.php b/plugins/jetpack/sync/class.jetpack-sync-users.php new file mode 100644 index 00000000..69d4e3c5 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-users.php @@ -0,0 +1,81 @@ + array( 'id' ), + 'role' => 'administrator', + 'orderby' => 'id', + 'exclude' => array( $master_user_id ), + ) + ); + $new_master = false; + foreach ( $query->results as $result ) { + $found_user_id = absint( $result->id ); + if ( $found_user_id && Jetpack::is_user_connected( $found_user_id ) ) { + $new_master = $found_user_id; + break; + } + } + + if ( $new_master ) { + Jetpack_Options::update_option( 'master_user', $new_master ); + } + // else disconnect..? + } + } +} + +Jetpack_Sync_Users::init(); diff --git a/plugins/jetpack/sync/class.jetpack-sync-wp-replicastore.php b/plugins/jetpack/sync/class.jetpack-sync-wp-replicastore.php new file mode 100644 index 00000000..9b378870 --- /dev/null +++ b/plugins/jetpack/sync/class.jetpack-sync-wp-replicastore.php @@ -0,0 +1,702 @@ +query( "DELETE FROM $wpdb->posts" ); + $wpdb->query( "DELETE FROM $wpdb->comments" ); + + // also need to delete terms from cache + $term_ids = $wpdb->get_col( "SELECT term_id FROM $wpdb->terms" ); + foreach ( $term_ids as $term_id ) { + wp_cache_delete( $term_id, 'terms' ); + } + + $wpdb->query( "DELETE FROM $wpdb->terms" ); + + $wpdb->query( "DELETE FROM $wpdb->term_taxonomy" ); + $wpdb->query( "DELETE FROM $wpdb->term_relationships" ); + + // callables and constants + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'jetpack_%'" ); + $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key NOT LIKE '\_%'" ); + } + + function full_sync_start( $config ) { + $this->reset(); + } + + function full_sync_end( $checksum ) { + // noop right now + } + + public function post_count( $status = null, $min_id = null, $max_id = null ) { + global $wpdb; + + $where = ''; + + if ( $status ) { + $where = "post_status = '" . esc_sql( $status ) . "'"; + } else { + $where = '1=1'; + } + + if ( null != $min_id ) { + $where .= ' AND ID >= ' . intval( $min_id ); + } + + if ( null != $max_id ) { + $where .= ' AND ID <= ' . intval( $max_id ); + } + + return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE $where" ); + } + + // TODO: actually use max_id/min_id + public function get_posts( $status = null, $min_id = null, $max_id = null ) { + $args = array( 'orderby' => 'ID', 'posts_per_page' => -1 ); + + if ( $status ) { + $args['post_status'] = $status; + } else { + $args['post_status'] = 'any'; + } + + return get_posts( $args ); + } + + public function get_post( $id ) { + return get_post( $id ); + } + + public function upsert_post( $post, $silent = false ) { + global $wpdb; + + // reject the post if it's not a WP_Post + if ( ! $post instanceof WP_Post ) { + return; + } + + $post = $post->to_array(); + + // reject posts without an ID + if ( ! isset( $post['ID'] ) ) { + return; + } + + $now = current_time( 'mysql' ); + $now_gmt = get_gmt_from_date( $now ); + + $defaults = array( + 'ID' => 0, + 'post_author' => '0', + 'post_content' => '', + 'post_content_filtered' => '', + 'post_title' => '', + 'post_name' => '', + 'post_excerpt' => '', + 'post_status' => 'draft', + 'post_type' => 'post', + 'comment_status' => 'closed', + 'comment_count' => '0', + 'ping_status' => '', + 'post_password' => '', + 'to_ping' => '', + 'pinged' => '', + 'post_parent' => 0, + 'menu_order' => 0, + 'guid' => '', + 'post_date' => $now, + 'post_date_gmt' => $now_gmt, + 'post_modified' => $now, + 'post_modified_gmt' => $now_gmt, + ); + + $post = array_intersect_key( $post, $defaults ); + + $post = sanitize_post( $post, 'db' ); + + unset( $post['filter'] ); + + $exists = $wpdb->get_var( $wpdb->prepare( "SELECT EXISTS( SELECT 1 FROM $wpdb->posts WHERE ID = %d )", $post['ID'] ) ); + + if ( $exists ) { + $wpdb->update( $wpdb->posts, $post, array( 'ID' => $post['ID'] ) ); + } else { + $wpdb->insert( $wpdb->posts, $post ); + } + + clean_post_cache( $post['ID'] ); + } + + public function delete_post( $post_id ) { + wp_delete_post( $post_id, true ); + } + + public function posts_checksum( $min_id = null, $max_id = null ) { + global $wpdb; + return $this->table_checksum( $wpdb->posts, Jetpack_Sync_Defaults::$default_post_checksum_columns , 'ID', Jetpack_Sync_Settings::get_blacklisted_post_types_sql(), $min_id, $max_id ); + } + + public function comment_count( $status = null, $min_id = null, $max_id = null ) { + global $wpdb; + + $comment_approved = $this->comment_status_to_approval_value( $status ); + + if ( $comment_approved !== false ) { + $where = "comment_approved = '" . esc_sql( $comment_approved ) . "'"; + } else { + $where = '1=1'; + } + + if ( $min_id != null ) { + $where .= ' AND comment_ID >= ' . intval( $min_id ); + } + + if ( $max_id != null ) { + $where .= ' AND comment_ID <= ' . intval( $max_id ); + } + + return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE $where" ); + } + + private function comment_status_to_approval_value( $status ) { + switch ( $status ) { + case 'approve': + return '1'; + case 'hold': + return '0'; + case 'spam': + return 'spam'; + case 'trash': + return 'trash'; + case 'any': + return false; + case 'all': + return false; + default: + return false; + } + } + + // TODO: actually use max_id/min_id + public function get_comments( $status = null, $min_id = null, $max_id = null ) { + $args = array( 'orderby' => 'ID', 'status' => 'all' ); + + if ( $status ) { + $args['status'] = $status; + } + + return get_comments( $args ); + } + + public function get_comment( $id ) { + return WP_Comment::get_instance( $id ); + } + + public function upsert_comment( $comment ) { + global $wpdb, $wp_version; + + if ( version_compare( $wp_version, '4.4', '<' ) ) { + $comment = (array) $comment; + } else { + // WP 4.4 introduced the WP_Comment Class + $comment = $comment->to_array(); + } + + // filter by fields on comment table + $comment_fields_whitelist = array( + 'comment_ID', + 'comment_post_ID', + 'comment_author', + 'comment_author_email', + 'comment_author_url', + 'comment_author_IP', + 'comment_date', + 'comment_date_gmt', + 'comment_content', + 'comment_karma', + 'comment_approved', + 'comment_agent', + 'comment_type', + 'comment_parent', + 'user_id', + ); + + foreach ( $comment as $key => $value ) { + if ( ! in_array( $key, $comment_fields_whitelist ) ) { + unset( $comment[ $key ] ); + } + } + + $exists = $wpdb->get_var( + $wpdb->prepare( + "SELECT EXISTS( SELECT 1 FROM $wpdb->comments WHERE comment_ID = %d )", + $comment['comment_ID'] + ) + ); + + if ( $exists ) { + $wpdb->update( $wpdb->comments, $comment, array( 'comment_ID' => $comment['comment_ID'] ) ); + } else { + $wpdb->insert( $wpdb->comments, $comment ); + } + + wp_update_comment_count( $comment['comment_post_ID'] ); + } + + public function trash_comment( $comment_id ) { + wp_delete_comment( $comment_id ); + } + + public function delete_comment( $comment_id ) { + wp_delete_comment( $comment_id, true ); + } + + public function spam_comment( $comment_id ) { + wp_spam_comment( $comment_id ); + } + + public function trashed_post_comments( $post_id, $statuses ) { + wp_trash_post_comments( $post_id ); + } + + public function untrashed_post_comments( $post_id ) { + wp_untrash_post_comments( $post_id ); + } + + public function comments_checksum( $min_id = null, $max_id = null ) { + global $wpdb; + return $this->table_checksum( $wpdb->comments, Jetpack_Sync_Defaults::$default_comment_checksum_columns, 'comment_ID', "comment_approved <> 'spam'", $min_id, $max_id ); + } + + public function options_checksum() { + global $wpdb; + + $options_whitelist = "'" . implode( "', '", Jetpack_Sync_Defaults::$default_options_whitelist ) . "'"; + $where_sql = "option_name IN ( $options_whitelist )"; + + return $this->table_checksum( $wpdb->options, Jetpack_Sync_Defaults::$default_option_checksum_columns, null, $where_sql, null, null ); + } + + + public function update_option( $option, $value ) { + return update_option( $option, $value ); + } + + public function get_option( $option, $default = false ) { + return get_option( $option, $default ); + } + + public function delete_option( $option ) { + return delete_option( $option ); + } + + public function set_theme_support( $theme_support ) { + // noop + } + + public function current_theme_supports( $feature ) { + return current_theme_supports( $feature ); + } + + public function get_metadata( $type, $object_id, $meta_key = '', $single = false ) { + return get_metadata( $type, $object_id, $meta_key, $single ); + } + + /** + * + * Stores remote meta key/values alongside an ID mapping key + * + * @param $type + * @param $object_id + * @param $meta_key + * @param $meta_value + * @param $meta_id + * + * @return bool + */ + public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id ) { + + $table = _get_meta_table( $type ); + if ( ! $table ) { + return false; + } + + global $wpdb; + + $exists = $wpdb->get_var( $wpdb->prepare( + "SELECT EXISTS( SELECT 1 FROM $table WHERE meta_id = %d )", + $meta_id + ) ); + + if ( $exists ) { + $wpdb->update( $table, array( + 'meta_key' => $meta_key, + 'meta_value' => serialize( $meta_value ), + ), array( 'meta_id' => $meta_id ) ); + } else { + $object_id_field = $type . '_id'; + $wpdb->insert( $table, array( + 'meta_id' => $meta_id, + $object_id_field => $object_id, + 'meta_key' => $meta_key, + 'meta_value' => serialize( $meta_value ), + ) ); + } + + wp_cache_delete( $object_id, $type . '_meta' ); + + return true; + } + + public function delete_metadata( $type, $object_id, $meta_ids ) { + global $wpdb; + + $table = _get_meta_table( $type ); + if ( ! $table ) { + return false; + } + + foreach ( $meta_ids as $meta_id ) { + $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE meta_id = %d", $meta_id ) ); + } + + // if we don't have an object ID what do we do - invalidate ALL meta? + if ( $object_id ) { + wp_cache_delete( $object_id, $type . '_meta' ); + } + } + + // constants + public function get_constant( $constant ) { + $value = get_option( 'jetpack_constant_' . $constant ); + + if ( $value ) { + return $value; + } + + return null; + } + + public function set_constant( $constant, $value ) { + update_option( 'jetpack_constant_' . $constant, $value ); + } + + public function get_updates( $type ) { + $all_updates = get_option( 'jetpack_updates', array() ); + + if ( isset( $all_updates[ $type ] ) ) { + return $all_updates[ $type ]; + } else { + return null; + } + } + + public function set_updates( $type, $updates ) { + $all_updates = get_option( 'jetpack_updates', array() ); + $all_updates[ $type ] = $updates; + update_option( 'jetpack_updates', $all_updates ); + } + + // functions + public function get_callable( $name ) { + $value = get_option( 'jetpack_' . $name ); + + if ( $value ) { + return $value; + } + + return null; + } + + public function set_callable( $name, $value ) { + update_option( 'jetpack_' . $name, $value ); + } + + // network options + public function get_site_option( $option ) { + return get_option( 'jetpack_network_' . $option ); + } + + public function update_site_option( $option, $value ) { + return update_option( 'jetpack_network_' . $option, $value ); + } + + public function delete_site_option( $option ) { + return delete_option( 'jetpack_network_' . $option ); + } + + // terms + // terms + public function get_terms( $taxonomy ) { + return get_terms( $taxonomy ); + } + + public function get_term( $taxonomy, $term_id, $is_term_id = true ) { + $t = $this->ensure_taxonomy( $taxonomy ); + if ( ! $t || is_wp_error( $t ) ) { + return $t; + } + + return get_term( $term_id, $taxonomy ); + } + + private function ensure_taxonomy( $taxonomy ) { + if ( ! taxonomy_exists( $taxonomy ) ) { + // try re-registering synced taxonomies + $taxonomies = $this->get_callable( 'taxonomies' ); + if ( ! isset( $taxonomies[ $taxonomy ] ) ) { + // doesn't exist, or somehow hasn't been synced + return new WP_Error( 'invalid_taxonomy', "The taxonomy '$taxonomy' doesn't exist" ); + } + $t = $taxonomies[ $taxonomy ]; + + return register_taxonomy( + $taxonomy, + $t->object_type, + (array) $t + ); + } + + return true; + } + + public function get_the_terms( $object_id, $taxonomy ) { + return get_the_terms( $object_id, $taxonomy ); + } + + public function update_term( $term_object ) { + $taxonomy = $term_object->taxonomy; + global $wpdb; + $exists = $wpdb->get_var( $wpdb->prepare( + "SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )", + $term_object->term_id + ) ); + if ( ! $exists ) { + $term_object = sanitize_term( clone( $term_object ), $taxonomy, 'db' ); + $term = array( + 'term_id' => $term_object->term_id, + 'name' => $term_object->name, + 'slug' => $term_object->slug, + 'term_group' => $term_object->term_group, + ); + $term_taxonomy = array( + 'term_taxonomy_id' => $term_object->term_taxonomy_id, + 'term_id' => $term_object->term_id, + 'taxonomy' => $term_object->taxonomy, + 'description' => $term_object->description, + 'parent' => (int) $term_object->parent, + 'count' => (int) $term_object->count, + ); + $wpdb->insert( $wpdb->terms, $term ); + $wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy ); + + return true; + } + + return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object ); + } + + public function delete_term( $term_id, $taxonomy ) { + return wp_delete_term( $term_id, $taxonomy ); + } + + public function update_object_terms( $object_id, $taxonomy, $terms, $append ) { + wp_set_object_terms( $object_id, $terms, $taxonomy, $append ); + } + + public function delete_object_terms( $object_id, $tt_ids ) { + global $wpdb; + + if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) { + $taxonomies = array(); + foreach ( $tt_ids as $tt_id ) { + $term = get_term_by( 'term_taxonomy_id', $tt_id ); + $taxonomies[ $term->taxonomy ][] = $tt_id; + } + $in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'"; + + /** + * Fires immediately before an object-term relationship is deleted. + * + * @since 2.9.0 + * + * @param int $object_id Object ID. + * @param array $tt_ids An array of term taxonomy IDs. + */ + do_action( 'delete_term_relationships', $object_id, $tt_ids ); + $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) ); + foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) { + wp_cache_delete( $object_id, $taxonomy . '_relationships' ); + /** + * Fires immediately after an object-term relationship is deleted. + * + * @since 2.9.0 + * + * @param int $object_id Object ID. + * @param array $tt_ids An array of term taxonomy IDs. + */ + do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids ); + wp_update_term_count( $taxonomy_tt_ids, $taxonomy ); + } + + return (bool) $deleted; + } + + return false; + } + + // users + public function user_count() { + + } + + public function get_user( $user_id ) { + return WP_User::get_instance( $user_id ); + } + + public function upsert_user( $user ) { + $this->invalid_call(); + } + + public function delete_user( $user_id ) { + $this->invalid_call(); + } + + public function get_allowed_mime_types( $user_id ) { + + } + + public function checksum_all() { + return array( + 'posts' => $this->posts_checksum(), + 'comments' => $this->comments_checksum() + ); + } + + function checksum_histogram( $object_type, $buckets, $start_id = null, $end_id = null, $columns = null ) { + global $wpdb; + + $wpdb->queries = array(); + + switch( $object_type ) { + case "posts": + $object_count = $this->post_count( null, $start_id, $end_id ); + $object_table = $wpdb->posts; + $id_field = 'ID'; + if ( empty( $columns ) ) { + $columns = Jetpack_Sync_Defaults::$default_post_checksum_columns; + } + break; + case "comments": + $object_count = $this->comment_count( null, $start_id, $end_id ); + $object_table = $wpdb->comments; + $id_field = 'comment_ID'; + if ( empty( $columns ) ) { + $columns = Jetpack_Sync_Defaults::$default_comment_checksum_columns; + } + break; + default: + return false; + } + + $bucket_size = intval( ceil( $object_count / $buckets ) ); + $previous_max_id = 0; + $histogram = array(); + + $where = '1=1'; + + if ( $start_id ) { + $where .= " AND $id_field >= " . intval( $start_id ); + } + + if ( $end_id ) { + $where .= " AND $id_field <= " . intval( $end_id ); + } + + do { + list( $first_id, $last_id ) = $wpdb->get_row( + "SELECT MIN($id_field) as min_id, MAX($id_field) as max_id FROM ( SELECT $id_field FROM $object_table WHERE $where AND $id_field > $previous_max_id ORDER BY $id_field ASC LIMIT $bucket_size ) as ids", + ARRAY_N + ); + + // get the checksum value + $value = $this->table_checksum( $object_table, $columns, $id_field, '1=1', $first_id, $last_id ); + + if ( is_wp_error( $value ) ) { + return $value; + } + + if ( $first_id === null || $last_id === null ) { + break; + } elseif ( $first_id === $last_id ) { + $histogram[ $first_id ] = $value; + } else { + $histogram[ "{$first_id}-{$last_id}" ] = $value; + } + + $previous_max_id = $last_id; + } while ( true ); + + return $histogram; + } + + private function table_checksum( $table, $columns, $id_column, $where_sql = '1=1', $min_id = null, $max_id = null ) { + global $wpdb; + + // sanitize to just valid MySQL column names + $sanitized_columns = preg_grep ( '/^[0-9,a-z,A-Z$_]+$/i', $columns ); + $columns_sql = implode( ',', array_map( array( $this, 'strip_non_ascii_sql' ), $sanitized_columns ) ); + + if ( $min_id !== null ) { + $min_id = intval( $min_id ); + $where_sql .= " AND $id_column >= $min_id"; + } + + if ( $max_id !== null ) { + $max_id = intval( $max_id ); + $where_sql .= " AND $id_column <= $max_id"; + } + + $query = <<get_var( $query ); + + if ( $wpdb->last_error ) { + return new WP_Error( 'database_error', $wpdb->last_error ); + } + + return $result; + + } + + /** + * Wraps a column name in SQL which strips non-ASCII chars. + * This helps normalize data to avoid checksum differences caused by + * badly encoded data in the DB + */ + function strip_non_ascii_sql( $column_name ) { + return "REPLACE( CONVERT( $column_name USING ascii ), '?', '' )"; + } + + private function invalid_call() { + $backtrace = debug_backtrace(); + $caller = $backtrace[1]['function']; + throw new Exception( "This function $caller is not supported on the WP Replicastore" ); + } +} diff --git a/plugins/jetpack/sync/interface.jetpack-sync-codec.php b/plugins/jetpack/sync/interface.jetpack-sync-codec.php new file mode 100644 index 00000000..1405d90c --- /dev/null +++ b/plugins/jetpack/sync/interface.jetpack-sync-codec.php @@ -0,0 +1,14 @@ +uninstall(); \ No newline at end of file diff --git a/plugins/jetpack/views/admin/my-jetpack-page.php b/plugins/jetpack/views/admin/my-jetpack-page.php index 9fac14b4..61cb4710 100644 --- a/plugins/jetpack/views/admin/my-jetpack-page.php +++ b/plugins/jetpack/views/admin/my-jetpack-page.php @@ -106,6 +106,7 @@ ID ) && $user->caps['administrator'] ) { if ( $user->ID == Jetpack_Options::get_option( 'master_user' ) ) { -- cgit v1.2.3-65-gdbad