PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 10.3
Jetpack – WP Security, Backup, Speed, & Growth v10.3
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / class.jetpack.php
class.jetpack.php
6,815 lines 222.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Automattic\Jetpack\Assets;
4 use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
5 use Automattic\Jetpack\Config;
6 use Automattic\Jetpack\Connection\Client;
7 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
8 use Automattic\Jetpack\Connection\Nonce_Handler;
9 use Automattic\Jetpack\Connection\Plugin_Storage as Connection_Plugin_Storage;
10 use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
11 use Automattic\Jetpack\Connection\Secrets;
12 use Automattic\Jetpack\Connection\Tokens;
13 use Automattic\Jetpack\Connection\Utils as Connection_Utils;
14 use Automattic\Jetpack\Connection\Webhooks as Connection_Webhooks;
15 use Automattic\Jetpack\Constants;
16 use Automattic\Jetpack\Device_Detection\User_Agent_Info;
17 use Automattic\Jetpack\Identity_Crisis;
18 use Automattic\Jetpack\Licensing;
19 use Automattic\Jetpack\Partner;
20 use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
21 use Automattic\Jetpack\Redirect;
22 use Automattic\Jetpack\Status;
23 use Automattic\Jetpack\Sync\Health;
24 use Automattic\Jetpack\Sync\Sender;
25 use Automattic\Jetpack\Terms_Of_Service;
26 use Automattic\Jetpack\Tracking;
27
28 /*
29 Options:
30 jetpack_options (array)
31 An array of options.
32 @see Jetpack_Options::get_option_names()
33
34 jetpack_register (string)
35 Temporary verification secrets.
36
37 jetpack_activated (int)
38 1: the plugin was activated normally
39 2: the plugin was activated on this site because of a network-wide activation
40 3: the plugin was auto-installed
41 4: the plugin was manually disconnected (but is still installed)
42
43 jetpack_active_modules (array)
44 Array of active module slugs.
45
46 jetpack_do_activate (bool)
47 Flag for "activating" the plugin on sites where the activation hook never fired (auto-installs)
48 */
49
50 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.media.php';
51
52 class Jetpack {
53 public $xmlrpc_server = null;
54
55 /**
56 * @var array The handles of styles that are concatenated into jetpack.css.
57 *
58 * When making changes to that list, you must also update concat_list in tools/builder/frontend-css.js.
59 */
60 public $concatenated_style_handles = array(
61 'jetpack-carousel-swiper-css',
62 'jetpack-carousel',
63 'grunion.css',
64 'the-neverending-homepage',
65 'jetpack_likes',
66 'jetpack_related-posts',
67 'sharedaddy',
68 'jetpack-slideshow',
69 'presentations',
70 'quiz',
71 'jetpack-subscriptions',
72 'jetpack-responsive-videos-style',
73 'jetpack-social-menu',
74 'tiled-gallery',
75 'jetpack_display_posts_widget',
76 'gravatar-profile-widget',
77 'goodreads-widget',
78 'jetpack_social_media_icons_widget',
79 'jetpack-top-posts-widget',
80 'jetpack_image_widget',
81 'jetpack-my-community-widget',
82 'jetpack-authors-widget',
83 'wordads',
84 'eu-cookie-law-style',
85 'flickr-widget-style',
86 'jetpack-search-widget',
87 'jetpack-simple-payments-widget-style',
88 'jetpack-widget-social-icons-styles',
89 'wpcom_instagram_widget',
90 );
91
92 /**
93 * Contains all assets that have had their URL rewritten to minified versions.
94 *
95 * @var array
96 */
97 static $min_assets = array();
98
99 public $plugins_to_deactivate = array(
100 'stats' => array( 'stats/stats.php', 'WordPress.com Stats' ),
101 'shortlinks' => array( 'stats/stats.php', 'WordPress.com Stats' ),
102 'sharedaddy' => array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
103 'twitter-widget' => array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
104 'contact-form' => array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
105 'contact-form' => array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
106 'custom-css' => array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
107 'random-redirect' => array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
108 'videopress' => array( 'video/video.php', 'VideoPress' ),
109 'widget-visibility' => array( 'jetpack-widget-visibility/widget-visibility.php', 'Jetpack Widget Visibility' ),
110 'widget-visibility' => array( 'widget-visibility-without-jetpack/widget-visibility-without-jetpack.php', 'Widget Visibility Without Jetpack' ),
111 'sharedaddy' => array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
112 'gravatar-hovercards' => array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
113 'latex' => array( 'wp-latex/wp-latex.php', 'WP LaTeX' ),
114 );
115
116 /**
117 * Map of roles we care about, and their corresponding minimum capabilities.
118 *
119 * @deprecated 7.6 Use Automattic\Jetpack\Roles::$capability_translations instead.
120 *
121 * @access public
122 * @static
123 *
124 * @var array
125 */
126 public static $capability_translations = array(
127 'administrator' => 'manage_options',
128 'editor' => 'edit_others_posts',
129 'author' => 'publish_posts',
130 'contributor' => 'edit_posts',
131 'subscriber' => 'read',
132 );
133
134 /**
135 * Map of modules that have conflicts with plugins and should not be auto-activated
136 * if the plugins are active. Used by filter_default_modules
137 *
138 * Plugin Authors: If you'd like to prevent a single module from auto-activating,
139 * change `module-slug` and add this to your plugin:
140 *
141 * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
142 * function my_jetpack_get_default_modules( $modules ) {
143 * return array_diff( $modules, array( 'module-slug' ) );
144 * }
145 *
146 * @var array
147 */
148 private $conflicting_plugins = array(
149 'comments' => array(
150 'Intense Debate' => 'intensedebate/intensedebate.php',
151 'Disqus' => 'disqus-comment-system/disqus.php',
152 'Livefyre' => 'livefyre-comments/livefyre.php',
153 'Comments Evolved for WordPress' => 'gplus-comments/comments-evolved.php',
154 'Google+ Comments' => 'google-plus-comments/google-plus-comments.php',
155 'WP-SpamShield Anti-Spam' => 'wp-spamshield/wp-spamshield.php',
156 ),
157 'comment-likes' => array(
158 'Epoch' => 'epoch/plugincore.php',
159 ),
160 'contact-form' => array(
161 'Contact Form 7' => 'contact-form-7/wp-contact-form-7.php',
162 'Gravity Forms' => 'gravityforms/gravityforms.php',
163 'Contact Form Plugin' => 'contact-form-plugin/contact_form.php',
164 'Easy Contact Forms' => 'easy-contact-forms/easy-contact-forms.php',
165 'Fast Secure Contact Form' => 'si-contact-form/si-contact-form.php',
166 'Ninja Forms' => 'ninja-forms/ninja-forms.php',
167 ),
168 'latex' => array(
169 'LaTeX for WordPress' => 'latex/latex.php',
170 'Youngwhans Simple Latex' => 'youngwhans-simple-latex/yw-latex.php',
171 'Easy WP LaTeX' => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
172 'MathJax-LaTeX' => 'mathjax-latex/mathjax-latex.php',
173 'Enable Latex' => 'enable-latex/enable-latex.php',
174 'WP QuickLaTeX' => 'wp-quicklatex/wp-quicklatex.php',
175 ),
176 'protect' => array(
177 'Limit Login Attempts' => 'limit-login-attempts/limit-login-attempts.php',
178 'Captcha' => 'captcha/captcha.php',
179 'Brute Force Login Protection' => 'brute-force-login-protection/brute-force-login-protection.php',
180 'Login Security Solution' => 'login-security-solution/login-security-solution.php',
181 'WPSecureOps Brute Force Protect' => 'wpsecureops-bruteforce-protect/wpsecureops-bruteforce-protect.php',
182 'BulletProof Security' => 'bulletproof-security/bulletproof-security.php',
183 'SiteGuard WP Plugin' => 'siteguard/siteguard.php',
184 'Security-protection' => 'security-protection/security-protection.php',
185 'Login Security' => 'login-security/login-security.php',
186 'Botnet Attack Blocker' => 'botnet-attack-blocker/botnet-attack-blocker.php',
187 'Wordfence Security' => 'wordfence/wordfence.php',
188 'All In One WP Security & Firewall' => 'all-in-one-wp-security-and-firewall/wp-security.php',
189 'iThemes Security' => 'better-wp-security/better-wp-security.php',
190 ),
191 'random-redirect' => array(
192 'Random Redirect 2' => 'random-redirect-2/random-redirect.php',
193 ),
194 'related-posts' => array(
195 'YARPP' => 'yet-another-related-posts-plugin/yarpp.php',
196 'WordPress Related Posts' => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
197 'nrelate Related Content' => 'nrelate-related-content/nrelate-related.php',
198 'Contextual Related Posts' => 'contextual-related-posts/contextual-related-posts.php',
199 'Related Posts for WordPress' => 'microkids-related-posts/microkids-related-posts.php',
200 'outbrain' => 'outbrain/outbrain.php',
201 'Shareaholic' => 'shareaholic/shareaholic.php',
202 'Sexybookmarks' => 'sexybookmarks/shareaholic.php',
203 ),
204 'sharedaddy' => array(
205 'AddThis' => 'addthis/addthis_social_widget.php',
206 'Add To Any' => 'add-to-any/add-to-any.php',
207 'ShareThis' => 'share-this/sharethis.php',
208 'Shareaholic' => 'shareaholic/shareaholic.php',
209 ),
210 'seo-tools' => array(
211 'WordPress SEO by Yoast' => 'wordpress-seo/wp-seo.php',
212 'WordPress SEO Premium by Yoast' => 'wordpress-seo-premium/wp-seo-premium.php',
213 'All in One SEO Pack' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
214 'All in One SEO Pack Pro' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
215 'The SEO Framework' => 'autodescription/autodescription.php',
216 'Rank Math' => 'seo-by-rank-math/rank-math.php',
217 'Slim SEO' => 'slim-seo/slim-seo.php',
218 ),
219 'verification-tools' => array(
220 'WordPress SEO by Yoast' => 'wordpress-seo/wp-seo.php',
221 'WordPress SEO Premium by Yoast' => 'wordpress-seo-premium/wp-seo-premium.php',
222 'All in One SEO Pack' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
223 'All in One SEO Pack Pro' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
224 'The SEO Framework' => 'autodescription/autodescription.php',
225 'Rank Math' => 'seo-by-rank-math/rank-math.php',
226 'Slim SEO' => 'slim-seo/slim-seo.php',
227 ),
228 'widget-visibility' => array(
229 'Widget Logic' => 'widget-logic/widget_logic.php',
230 'Dynamic Widgets' => 'dynamic-widgets/dynamic-widgets.php',
231 ),
232 'sitemaps' => array(
233 'Google XML Sitemaps' => 'google-sitemap-generator/sitemap.php',
234 'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
235 'Google XML Sitemaps for qTranslate' => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php',
236 'XML Sitemap & Google News feeds' => 'xml-sitemap-feed/xml-sitemap.php',
237 'Google Sitemap by BestWebSoft' => 'google-sitemap-plugin/google-sitemap-plugin.php',
238 'WordPress SEO by Yoast' => 'wordpress-seo/wp-seo.php',
239 'WordPress SEO Premium by Yoast' => 'wordpress-seo-premium/wp-seo-premium.php',
240 'All in One SEO Pack' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
241 'All in One SEO Pack Pro' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
242 'The SEO Framework' => 'autodescription/autodescription.php',
243 'Sitemap' => 'sitemap/sitemap.php',
244 'Simple Wp Sitemap' => 'simple-wp-sitemap/simple-wp-sitemap.php',
245 'Simple Sitemap' => 'simple-sitemap/simple-sitemap.php',
246 'XML Sitemaps' => 'xml-sitemaps/xml-sitemaps.php',
247 'MSM Sitemaps' => 'msm-sitemap/msm-sitemap.php',
248 'Rank Math' => 'seo-by-rank-math/rank-math.php',
249 'Slim SEO' => 'slim-seo/slim-seo.php',
250 ),
251 'lazy-images' => array(
252 'Lazy Load' => 'lazy-load/lazy-load.php',
253 'BJ Lazy Load' => 'bj-lazy-load/bj-lazy-load.php',
254 'Lazy Load by WP Rocket' => 'rocket-lazy-load/rocket-lazy-load.php',
255 ),
256 );
257
258 /**
259 * Plugins for which we turn off our Facebook OG Tags implementation.
260 *
261 * Note: All in One SEO Pack, All in one SEO Pack Pro, WordPress SEO by Yoast, and WordPress SEO Premium by Yoast automatically deactivate
262 * Jetpack's Open Graph tags via filter when their Social Meta modules are active.
263 *
264 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
265 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
266 */
267 private $open_graph_conflicting_plugins = array(
268 '2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
269 // 2 Click Social Media Buttons
270 'add-link-to-facebook/add-link-to-facebook.php', // Add Link to Facebook
271 'add-meta-tags/add-meta-tags.php', // Add Meta Tags
272 'complete-open-graph/complete-open-graph.php', // Complete Open Graph
273 'easy-facebook-share-thumbnails/esft.php', // Easy Facebook Share Thumbnail
274 'heateor-open-graph-meta-tags/heateor-open-graph-meta-tags.php',
275 // Open Graph Meta Tags by Heateor
276 'facebook/facebook.php', // Facebook (official plugin)
277 'facebook-awd/AWD_facebook.php', // Facebook AWD All in one
278 'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
279 // Facebook Featured Image & OG Meta Tags
280 'facebook-meta-tags/facebook-metatags.php', // Facebook Meta Tags
281 'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
282 // Facebook Open Graph Meta Tags for WordPress
283 'facebook-revised-open-graph-meta-tag/index.php', // Facebook Revised Open Graph Meta Tag
284 'facebook-thumb-fixer/_facebook-thumb-fixer.php', // Facebook Thumb Fixer
285 'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
286 // Fedmich's Facebook Open Graph Meta
287 'network-publisher/networkpub.php', // Network Publisher
288 'nextgen-facebook/nextgen-facebook.php', // NextGEN Facebook OG
289 'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php',
290 // NextScripts SNAP
291 'og-tags/og-tags.php', // OG Tags
292 'opengraph/opengraph.php', // Open Graph
293 'open-graph-protocol-framework/open-graph-protocol-framework.php',
294 // Open Graph Protocol Framework
295 'seo-facebook-comments/seofacebook.php', // SEO Facebook Comments
296 'seo-ultimate/seo-ultimate.php', // SEO Ultimate
297 'sexybookmarks/sexy-bookmarks.php', // Shareaholic
298 'shareaholic/sexy-bookmarks.php', // Shareaholic
299 'sharepress/sharepress.php', // SharePress
300 'simple-facebook-connect/sfc.php', // Simple Facebook Connect
301 'social-discussions/social-discussions.php', // Social Discussions
302 'social-sharing-toolkit/social_sharing_toolkit.php', // Social Sharing Toolkit
303 'socialize/socialize.php', // Socialize
304 'squirrly-seo/squirrly.php', // SEO by SQUIRRLY™
305 'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
306 // Tweet, Like, Google +1 and Share
307 'wordbooker/wordbooker.php', // Wordbooker
308 'wpsso/wpsso.php', // WordPress Social Sharing Optimization
309 'wp-caregiver/wp-caregiver.php', // WP Caregiver
310 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
311 // WP Facebook Like Send & Open Graph Meta
312 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol
313 'wp-ogp/wp-ogp.php', // WP-OGP
314 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin
315 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button
316 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox
317 'seo-by-rank-math/rank-math.php', // Rank Math.
318 'slim-seo/slim-seo.php', // Slim SEO
319 );
320
321 /**
322 * Plugins for which we turn off our Twitter Cards Tags implementation.
323 */
324 private $twitter_cards_conflicting_plugins = array(
325 // 'twitter/twitter.php', // The official one handles this on its own.
326 // https://github.com/twitter/wordpress/blob/master/src/Twitter/WordPress/Cards/Compatibility.php
327 'eewee-twitter-card/index.php', // Eewee Twitter Card
328 'ig-twitter-cards/ig-twitter-cards.php', // IG:Twitter Cards
329 'jm-twitter-cards/jm-twitter-cards.php', // JM Twitter Cards
330 'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',
331 // Pure Web Brilliant's Social Graph Twitter Cards Extension
332 'twitter-cards/twitter-cards.php', // Twitter Cards
333 'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta
334 'wp-to-twitter/wp-to-twitter.php', // WP to Twitter
335 'wp-twitter-cards/twitter_cards.php', // WP Twitter Cards
336 'seo-by-rank-math/rank-math.php', // Rank Math.
337 'slim-seo/slim-seo.php', // Slim SEO
338 );
339
340 /**
341 * Message to display in admin_notice
342 *
343 * @var string
344 */
345 public $message = '';
346
347 /**
348 * Error to display in admin_notice
349 *
350 * @var string
351 */
352 public $error = '';
353
354 /**
355 * Modules that need more privacy description.
356 *
357 * @var string
358 */
359 public $privacy_checks = '';
360
361 /**
362 * Stats to record once the page loads
363 *
364 * @var array
365 */
366 public $stats = array();
367
368 /**
369 * Jetpack_Sync object
370 */
371 public $sync;
372
373 /**
374 * Verified data for JSON authorization request
375 */
376 public $json_api_authorization_request = array();
377
378 /**
379 * @var Automattic\Jetpack\Connection\Manager
380 */
381 protected $connection_manager;
382
383 /**
384 * @var string Transient key used to prevent multiple simultaneous plugin upgrades
385 */
386 public static $plugin_upgrade_lock_key = 'jetpack_upgrade_lock';
387
388 /**
389 * Holds an instance of Automattic\Jetpack\A8c_Mc_Stats
390 *
391 * @var Automattic\Jetpack\A8c_Mc_Stats
392 */
393 public $a8c_mc_stats_instance;
394
395 /**
396 * Constant for login redirect key.
397 *
398 * @var string
399 * @since 8.4.0
400 */
401 public static $jetpack_redirect_login = 'jetpack_connect_login_redirect';
402
403 /**
404 * Holds the singleton instance of this class
405 *
406 * @since 2.3.3
407 * @var Jetpack
408 */
409 static $instance = false;
410
411 /**
412 * Singleton
413 *
414 * @static
415 */
416 public static function init() {
417 if ( ! self::$instance ) {
418 self::$instance = new Jetpack();
419 add_action( 'plugins_loaded', array( self::$instance, 'plugin_upgrade' ) );
420 add_action( 'jetpack_idc_disconnect', array( __CLASS__, 'on_idc_disconnect' ) );
421 }
422
423 return self::$instance;
424 }
425
426 /**
427 * Must never be called statically
428 */
429 function plugin_upgrade() {
430 if ( self::is_connection_ready() ) {
431 list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
432 if ( JETPACK__VERSION != $version ) {
433 // Prevent multiple upgrades at once - only a single process should trigger
434 // an upgrade to avoid stampedes
435 if ( false !== get_transient( self::$plugin_upgrade_lock_key ) ) {
436 return;
437 }
438
439 // Set a short lock to prevent multiple instances of the upgrade
440 set_transient( self::$plugin_upgrade_lock_key, 1, 10 );
441
442 // check which active modules actually exist and remove others from active_modules list
443 $unfiltered_modules = self::get_active_modules();
444 $modules = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) );
445 if ( array_diff( $unfiltered_modules, $modules ) ) {
446 self::update_active_modules( $modules );
447 }
448
449 add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
450
451 // Upgrade to 4.3.0
452 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
453 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
454 }
455
456 // Make sure Markdown for posts gets turned back on
457 if ( ! get_option( 'wpcom_publish_posts_with_markdown' ) ) {
458 update_option( 'wpcom_publish_posts_with_markdown', true );
459 }
460
461 /*
462 * Minileven deprecation. 8.3.0.
463 * Only delete options if not using
464 * the replacement standalone Minileven plugin.
465 */
466 if (
467 ! self::is_plugin_active( 'minileven-master/minileven.php' )
468 && ! self::is_plugin_active( 'minileven/minileven.php' )
469 ) {
470 if ( get_option( 'wp_mobile_custom_css' ) ) {
471 delete_option( 'wp_mobile_custom_css' );
472 }
473 if ( get_option( 'wp_mobile_excerpt' ) ) {
474 delete_option( 'wp_mobile_excerpt' );
475 }
476 if ( get_option( 'wp_mobile_featured_images' ) ) {
477 delete_option( 'wp_mobile_featured_images' );
478 }
479 if ( get_option( 'wp_mobile_app_promos' ) ) {
480 delete_option( 'wp_mobile_app_promos' );
481 }
482 }
483
484 // Upgrade to 8.4.0.
485 if ( Jetpack_Options::get_option( 'ab_connect_banner_green_bar' ) ) {
486 Jetpack_Options::delete_option( 'ab_connect_banner_green_bar' );
487 }
488
489 // Update to 8.8.x (WordPress 5.5 Compatibility).
490 if ( Jetpack_Options::get_option( 'autoupdate_plugins' ) ) {
491 $updated = update_site_option(
492 'auto_update_plugins',
493 array_unique(
494 array_merge(
495 (array) Jetpack_Options::get_option( 'autoupdate_plugins', array() ),
496 (array) get_site_option( 'auto_update_plugins', array() )
497 )
498 )
499 );
500
501 if ( $updated ) {
502 Jetpack_Options::delete_option( 'autoupdate_plugins' );
503 } // Should we have some type of fallback if something fails here?
504 }
505
506 if ( did_action( 'wp_loaded' ) ) {
507 self::upgrade_on_load();
508 } else {
509 add_action(
510 'wp_loaded',
511 array( __CLASS__, 'upgrade_on_load' )
512 );
513 }
514 }
515 }
516 }
517
518 /**
519 * Runs upgrade routines that need to have modules loaded.
520 */
521 static function upgrade_on_load() {
522
523 // Not attempting any upgrades if jetpack_modules_loaded did not fire.
524 // This can happen in case Jetpack has been just upgraded and is
525 // being initialized late during the page load. In this case we wait
526 // until the next proper admin page load with Jetpack active.
527 if ( ! did_action( 'jetpack_modules_loaded' ) ) {
528 delete_transient( self::$plugin_upgrade_lock_key );
529
530 return;
531 }
532
533 self::maybe_set_version_option();
534
535 if ( method_exists( 'Jetpack_Widget_Conditions', 'migrate_post_type_rules' ) ) {
536 Jetpack_Widget_Conditions::migrate_post_type_rules();
537 }
538
539 if (
540 class_exists( 'Jetpack_Sitemap_Manager' )
541 && version_compare( JETPACK__VERSION, '5.3', '>=' )
542 ) {
543 do_action( 'jetpack_sitemaps_purge_data' );
544 }
545
546 // Delete old stats cache
547 delete_option( 'jetpack_restapi_stats_cache' );
548
549 delete_transient( self::$plugin_upgrade_lock_key );
550 }
551
552 /**
553 * Saves all the currently active modules to options.
554 * Also fires Action hooks for each newly activated and deactivated module.
555 *
556 * @param $modules Array Array of active modules to be saved in options.
557 *
558 * @return $success bool true for success, false for failure.
559 */
560 static function update_active_modules( $modules ) {
561 $current_modules = Jetpack_Options::get_option( 'active_modules', array() );
562 $active_modules = self::get_active_modules();
563 $new_active_modules = array_diff( $modules, $current_modules );
564 $new_inactive_modules = array_diff( $active_modules, $modules );
565 $new_current_modules = array_diff( array_merge( $current_modules, $new_active_modules ), $new_inactive_modules );
566 $reindexed_modules = array_values( $new_current_modules );
567 $success = Jetpack_Options::update_option( 'active_modules', array_unique( $reindexed_modules ) );
568
569 foreach ( $new_active_modules as $module ) {
570 /**
571 * Fires when a specific module is activated.
572 *
573 * @since 1.9.0
574 *
575 * @param string $module Module slug.
576 * @param boolean $success whether the module was activated. @since 4.2
577 */
578 do_action( 'jetpack_activate_module', $module, $success );
579 /**
580 * Fires when a module is activated.
581 * The dynamic part of the filter, $module, is the module slug.
582 *
583 * @since 1.9.0
584 *
585 * @param string $module Module slug.
586 */
587 do_action( "jetpack_activate_module_$module", $module );
588 }
589
590 foreach ( $new_inactive_modules as $module ) {
591 /**
592 * Fired after a module has been deactivated.
593 *
594 * @since 4.2.0
595 *
596 * @param string $module Module slug.
597 * @param boolean $success whether the module was deactivated.
598 */
599 do_action( 'jetpack_deactivate_module', $module, $success );
600 /**
601 * Fires when a module is deactivated.
602 * The dynamic part of the filter, $module, is the module slug.
603 *
604 * @since 1.9.0
605 *
606 * @param string $module Module slug.
607 */
608 do_action( "jetpack_deactivate_module_$module", $module );
609 }
610
611 return $success;
612 }
613
614 static function delete_active_modules() {
615 self::update_active_modules( array() );
616 }
617
618 /**
619 * Adds a hook to plugins_loaded at a priority that's currently the earliest
620 * available.
621 */
622 public function add_configure_hook() {
623 global $wp_filter;
624
625 $current_priority = has_filter( 'plugins_loaded', array( $this, 'configure' ) );
626 if ( false !== $current_priority ) {
627 remove_action( 'plugins_loaded', array( $this, 'configure' ), $current_priority );
628 }
629
630 $taken_priorities = array_map( 'intval', array_keys( $wp_filter['plugins_loaded']->callbacks ) );
631 sort( $taken_priorities );
632
633 $first_priority = array_shift( $taken_priorities );
634
635 if ( defined( 'PHP_INT_MAX' ) && $first_priority <= - PHP_INT_MAX ) {
636 $new_priority = - PHP_INT_MAX;
637 } else {
638 $new_priority = $first_priority - 1;
639 }
640
641 add_action( 'plugins_loaded', array( $this, 'configure' ), $new_priority );
642 }
643
644 /**
645 * Constructor. Initializes WordPress hooks
646 */
647 private function __construct() {
648 /*
649 * Check for and alert any deprecated hooks
650 */
651 add_action( 'init', array( $this, 'deprecated_hooks' ) );
652
653 // Note how this runs at an earlier plugin_loaded hook intentionally to accomodate for other plugins.
654 add_action( 'plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
655 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
656 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
657 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
658
659 add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
660
661 add_filter(
662 'jetpack_signature_check_token',
663 array( __CLASS__, 'verify_onboarding_token' ),
664 10,
665 3
666 );
667
668 /**
669 * Prepare Gutenberg Editor functionality
670 */
671 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
672 add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'init' ) );
673 add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
674 add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_block_editor_extensions' ), 9 );
675 add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
676
677 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
678
679 // Unlink user before deleting the user from WP.com.
680 add_action( 'deleted_user', array( $this, 'disconnect_user' ), 10, 1 );
681 add_action( 'remove_user_from_blog', array( $this, 'disconnect_user' ), 10, 1 );
682
683 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
684
685 add_filter( 'login_url', array( $this, 'login_url' ), 10, 2 );
686 add_action( 'login_init', array( $this, 'login_init' ) );
687
688 // Set up the REST authentication hooks.
689 Connection_Rest_Authentication::init();
690
691 add_action( 'admin_init', array( $this, 'admin_init' ) );
692 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
693
694 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
695
696 // Filter the dashboard meta box order to swap the new one in in place of the old one.
697 add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) );
698
699 // WordPress dashboard widget.
700 require_once JETPACK__PLUGIN_DIR . 'class-jetpack-stats-dashboard-widget.php';
701 add_action( 'wp_dashboard_setup', array( new Jetpack_Stats_Dashboard_Widget(), 'init' ) );
702
703 // Returns HTTPS support status.
704 add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) );
705
706 add_action( 'wp_ajax_jetpack_connection_banner', array( $this, 'jetpack_connection_banner_callback' ) );
707
708 add_action( 'wp_ajax_jetpack_recommendations_banner', array( 'Jetpack_Recommendations_Banner', 'ajax_callback' ) );
709
710 add_action( 'wp_loaded', array( $this, 'register_assets' ) );
711
712 /**
713 * These actions run checks to load additional files.
714 * They check for external files or plugins, so they need to run as late as possible.
715 */
716 add_action( 'wp_head', array( $this, 'check_open_graph' ), 1 );
717 add_action( 'web_stories_story_head', array( $this, 'check_open_graph' ), 1 );
718 add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ), 999 );
719 add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
720
721 add_filter( 'plugins_url', array( 'Jetpack', 'maybe_min_asset' ), 1, 3 );
722 add_action( 'style_loader_src', array( 'Jetpack', 'set_suffix_on_min' ), 10, 2 );
723 add_filter( 'style_loader_tag', array( 'Jetpack', 'maybe_inline_style' ), 10, 2 );
724
725 add_filter( 'profile_update', array( 'Jetpack', 'user_meta_cleanup' ) );
726
727 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
728 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
729
730 require_once JETPACK__PLUGIN_DIR . 'class-jetpack-pre-connection-jitms.php';
731 $jetpack_jitm_messages = ( new Jetpack_Pre_Connection_JITMs() );
732 add_filter( 'jetpack_pre_connection_jitms', array( $jetpack_jitm_messages, 'add_pre_connection_jitms' ) );
733
734 /*
735 * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
736 * We should make sure to only do this for front end links.
737 */
738 if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
739 add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
740 add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
741
742 /*
743 * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
744 * so they point moderation links on emails to Calypso.
745 */
746 jetpack_require_lib( 'functions.wp-notify' );
747 add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
748 add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
749 }
750
751 add_action(
752 'plugins_loaded',
753 function () {
754 if ( User_Agent_Info::is_mobile_app() ) {
755 add_filter( 'get_edit_post_link', '__return_empty_string' );
756 }
757 }
758 );
759
760 // Update the site's Jetpack plan and products from API on heartbeats.
761 add_action( 'jetpack_heartbeat', array( 'Jetpack_Plan', 'refresh_from_wpcom' ) );
762
763 /**
764 * This is the hack to concatenate all css files into one.
765 * For description and reasoning see the implode_frontend_css method.
766 *
767 * Super late priority so we catch all the registered styles.
768 */
769 if ( ! is_admin() ) {
770 add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first
771 add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`
772 }
773
774 // Actually push the stats on shutdown.
775 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
776 add_action( 'shutdown', array( $this, 'push_stats' ) );
777 }
778
779 // After a successful connection.
780 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
781 add_action( 'jetpack_site_registered', array( $this, 'handle_unique_registrations_stats' ) );
782
783 // Actions for Manager::authorize().
784 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
785 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
786 add_action( 'jetpack_authorize_ending_authorized', array( $this, 'authorize_ending_authorized' ) );
787
788 add_action( 'jetpack_client_authorize_error', array( Jetpack_Client_Server::class, 'client_authorize_error' ) );
789 add_filter( 'jetpack_client_authorize_already_authorized_url', array( Jetpack_Client_Server::class, 'client_authorize_already_authorized_url' ) );
790 add_action( 'jetpack_client_authorize_processing', array( Jetpack_Client_Server::class, 'client_authorize_processing' ) );
791 add_filter( 'jetpack_client_authorize_fallback_url', array( Jetpack_Client_Server::class, 'client_authorize_fallback_url' ) );
792
793 // Filters for the Manager::get_token() urls and request body.
794 add_filter( 'jetpack_token_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
795 add_filter( 'jetpack_token_request_body', array( __CLASS__, 'filter_token_request_body' ) );
796
797 // Filter for the `jetpack/v4/connection/data` API response.
798 add_filter( 'jetpack_current_user_connection_data', array( __CLASS__, 'filter_jetpack_current_user_connection_data' ) );
799
800 // Actions for successful reconnect.
801 add_action( 'jetpack_reconnection_completed', array( $this, 'reconnection_completed' ) );
802
803 // Actions for successful disconnect.
804 add_action( 'jetpack_site_disconnected', array( $this, 'jetpack_site_disconnected' ) );
805
806 // Actions for licensing.
807 Licensing::instance()->initialize();
808
809 // Filters for Sync Callables.
810 add_filter( 'jetpack_sync_callable_whitelist', array( $this, 'filter_sync_callable_whitelist' ), 10, 1 );
811 add_filter( 'jetpack_sync_multisite_callable_whitelist', array( $this, 'filter_sync_multisite_callable_whitelist' ), 10, 1 );
812
813 // Make resources use static domain when possible.
814 add_filter( 'jetpack_static_url', array( 'Automattic\\Jetpack\\Assets', 'staticize_subdomain' ) );
815
816 // Validate the domain names in Jetpack development versions.
817 add_action( 'jetpack_pre_register', array( get_called_class(), 'registration_check_domains' ) );
818 }
819
820 /**
821 * Before everything else starts getting initalized, we need to initialize Jetpack using the
822 * Config object.
823 */
824 public function configure() {
825 $config = new Config();
826
827 foreach (
828 array(
829 'sync',
830 'jitm',
831 'identity_crisis',
832 )
833 as $feature
834 ) {
835 $config->ensure( $feature );
836 }
837
838 $config->ensure(
839 'connection',
840 array(
841 'slug' => 'jetpack',
842 'name' => 'Jetpack',
843 )
844 );
845
846 if ( ! $this->connection_manager ) {
847 $this->connection_manager = new Connection_Manager( 'jetpack' );
848
849 /**
850 * Filter to activate Jetpack Connection UI.
851 * INTERNAL USE ONLY.
852 *
853 * @since 9.5.0
854 *
855 * @param bool false Whether to activate the Connection UI.
856 */
857 if ( apply_filters( 'jetpack_connection_ui_active', false ) ) {
858 Automattic\Jetpack\ConnectionUI\Admin::init();
859 }
860 }
861
862 /*
863 * Load things that should only be in Network Admin.
864 *
865 * For now blow away everything else until a more full
866 * understanding of what is needed at the network level is
867 * available
868 */
869 if ( is_multisite() ) {
870 $network = Jetpack_Network::init();
871 $network->set_connection( $this->connection_manager );
872 }
873
874 if ( self::is_connection_ready() ) {
875 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
876
877 Jetpack_Heartbeat::init();
878 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
879 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
880 Jetpack_Search_Performance_Logger::init();
881 }
882 }
883
884 // Initialize remote file upload request handlers.
885 $this->add_remote_request_handlers();
886
887 /*
888 * Enable enhanced handling of previewing sites in Calypso
889 */
890 if ( self::is_connection_ready() ) {
891 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
892 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
893 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
894 add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
895 }
896
897 if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
898 add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
899 } else {
900 /**
901 * Initialize tracking right after the user agrees to the terms of service.
902 */
903 add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
904 }
905 }
906
907 /**
908 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
909 * initialization code.
910 *
911 * @action plugins_loaded
912 */
913 public function late_initialization() {
914 add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
915
916 Partner::init();
917
918 /**
919 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
920 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
921 *
922 * @since 8.1.0
923 *
924 * @param Jetpack $jetpack the main plugin class object.
925 */
926 do_action( 'jetpack_loaded', $this );
927
928 add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 4 );
929 }
930
931 /**
932 * This is ported over from the manage module, which has been deprecated and baked in here.
933 *
934 * @param $domains
935 */
936 function add_wpcom_to_allowed_redirect_hosts( $domains ) {
937 add_filter( 'allowed_redirect_hosts', array( $this, 'allow_wpcom_domain' ) );
938 }
939
940 /**
941 * Return $domains, with 'wordpress.com' appended.
942 * This is ported over from the manage module, which has been deprecated and baked in here.
943 *
944 * @param $domains
945 * @return array
946 */
947 function allow_wpcom_domain( $domains ) {
948 if ( empty( $domains ) ) {
949 $domains = array();
950 }
951 $domains[] = 'wordpress.com';
952 return array_unique( $domains );
953 }
954
955 function point_edit_post_links_to_calypso( $default_url, $post_id ) {
956 $post = get_post( $post_id );
957
958 if ( empty( $post ) ) {
959 return $default_url;
960 }
961
962 $post_type = $post->post_type;
963
964 // Mapping the allowed CPTs on WordPress.com to corresponding paths in Calypso.
965 // https://en.support.wordpress.com/custom-post-types/
966 $allowed_post_types = array(
967 'post',
968 'page',
969 'jetpack-portfolio',
970 'jetpack-testimonial',
971 );
972
973 if ( ! in_array( $post_type, $allowed_post_types, true ) ) {
974 return $default_url;
975 }
976
977 return Redirect::get_url(
978 'calypso-edit-' . $post_type,
979 array(
980 'path' => $post_id,
981 )
982 );
983 }
984
985 function point_edit_comment_links_to_calypso( $url ) {
986 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
987 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
988
989 return Redirect::get_url(
990 'calypso-edit-comment',
991 array(
992 'path' => $query_args['amp;c'],
993 )
994 );
995
996 }
997
998 /**
999 * Extend Sync callables with Jetpack Plugin functions.
1000 *
1001 * @param array $callables list of callables.
1002 *
1003 * @return array list of callables.
1004 */
1005 public function filter_sync_callable_whitelist( $callables ) {
1006
1007 // Jetpack Functions.
1008 $jetpack_callables = array(
1009 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1010 'updates' => array( 'Jetpack', 'get_updates' ),
1011 'active_modules' => array( 'Jetpack', 'get_active_modules' ),
1012 'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins.
1013 );
1014 $callables = array_merge( $callables, $jetpack_callables );
1015
1016 // Jetpack_SSO_Helpers.
1017 if ( include_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' ) {
1018 $sso_helpers = array(
1019 'sso_is_two_step_required' => array( 'Jetpack_SSO_Helpers', 'is_two_step_required' ),
1020 'sso_should_hide_login_form' => array( 'Jetpack_SSO_Helpers', 'should_hide_login_form' ),
1021 'sso_match_by_email' => array( 'Jetpack_SSO_Helpers', 'match_by_email' ),
1022 'sso_new_user_override' => array( 'Jetpack_SSO_Helpers', 'new_user_override' ),
1023 'sso_bypass_default_login_form' => array( 'Jetpack_SSO_Helpers', 'bypass_login_forward_wpcom' ),
1024 );
1025 $callables = array_merge( $callables, $sso_helpers );
1026 }
1027
1028 return $callables;
1029 }
1030
1031 /**
1032 * Extend Sync multisite callables with Jetpack Plugin functions.
1033 *
1034 * @param array $callables list of callables.
1035 *
1036 * @return array list of callables.
1037 */
1038 public function filter_sync_multisite_callable_whitelist( $callables ) {
1039
1040 // Jetpack Funtions.
1041 $jetpack_multisite_callables = array(
1042 'network_name' => array( 'Jetpack', 'network_name' ),
1043 'network_allow_new_registrations' => array( 'Jetpack', 'network_allow_new_registrations' ),
1044 'network_add_new_users' => array( 'Jetpack', 'network_add_new_users' ),
1045 'network_site_upload_space' => array( 'Jetpack', 'network_site_upload_space' ),
1046 'network_upload_file_types' => array( 'Jetpack', 'network_upload_file_types' ),
1047 'network_enable_administration_menus' => array( 'Jetpack', 'network_enable_administration_menus' ),
1048 );
1049 $callables = array_merge( $callables, $jetpack_multisite_callables );
1050
1051 return $callables;
1052 }
1053
1054 /**
1055 * Deprecated
1056 * Please use Automattic\Jetpack\JITMS\JITM::jetpack_track_last_sync_callback instead.
1057 *
1058 * @param array $params The action parameters.
1059 *
1060 * @deprecated since 9.8.
1061 */
1062 function jetpack_track_last_sync_callback( $params ) {
1063 _deprecated_function( __METHOD__, 'jetpack-9.8', '\Automattic\Jetpack\JITMS\JITM->jetpack_track_last_sync_callback' );
1064 return Automattic\Jetpack\JITMS\JITM::get_instance()->jetpack_track_last_sync_callback( $params );
1065 }
1066
1067 function jetpack_connection_banner_callback() {
1068 check_ajax_referer( 'jp-connection-banner-nonce', 'nonce' );
1069
1070 // Disable the banner dismiss functionality if the pre-connection prompt helpers filter is set.
1071 if (
1072 isset( $_REQUEST['dismissBanner'] ) &&
1073 ! Jetpack_Connection_Banner::force_display()
1074 ) {
1075 Jetpack_Options::update_option( 'dismissed_connection_banner', 1 );
1076 wp_send_json_success();
1077 }
1078
1079 wp_die();
1080 }
1081
1082 /**
1083 * If there are any stats that need to be pushed, but haven't been, push them now.
1084 */
1085 function push_stats() {
1086 if ( ! empty( $this->stats ) ) {
1087 $this->do_stats( 'server_side' );
1088 }
1089 }
1090
1091 /**
1092 * Sets the Jetpack custom capabilities.
1093 *
1094 * @param string[] $caps Array of the user's capabilities.
1095 * @param string $cap Capability name.
1096 * @param int $user_id The user ID.
1097 * @param array $args Adds the context to the cap. Typically the object ID.
1098 */
1099 public function jetpack_custom_caps( $caps, $cap, $user_id, $args ) {
1100 switch ( $cap ) {
1101 case 'jetpack_manage_modules':
1102 case 'jetpack_activate_modules':
1103 case 'jetpack_deactivate_modules':
1104 $caps = array( 'manage_options' );
1105 break;
1106 case 'jetpack_configure_modules':
1107 $caps = array( 'manage_options' );
1108 break;
1109 case 'jetpack_manage_autoupdates':
1110 $caps = array(
1111 'manage_options',
1112 'update_plugins',
1113 );
1114 break;
1115 case 'jetpack_network_admin_page':
1116 case 'jetpack_network_settings_page':
1117 $caps = array( 'manage_network_plugins' );
1118 break;
1119 case 'jetpack_network_sites_page':
1120 $caps = array( 'manage_sites' );
1121 break;
1122 case 'jetpack_admin_page':
1123 $is_offline_mode = ( new Status() )->is_offline_mode();
1124 if ( $is_offline_mode ) {
1125 $caps = array( 'manage_options' );
1126 break;
1127 } else {
1128 $caps = array( 'read' );
1129 }
1130 break;
1131 }
1132 return $caps;
1133 }
1134
1135 /**
1136 * Register assets for use in various modules and the Jetpack admin page.
1137 *
1138 * @uses wp_script_is, wp_register_script, plugins_url
1139 * @action wp_loaded
1140 * @return null
1141 */
1142 public function register_assets() {
1143 if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) ) {
1144 wp_register_script(
1145 'jetpack-gallery-settings',
1146 Assets::get_file_url_for_environment( '_inc/build/gallery-settings.min.js', '_inc/gallery-settings.js' ),
1147 array( 'media-views' ),
1148 '20121225'
1149 );
1150 }
1151
1152 if ( ! wp_script_is( 'jetpack-twitter-timeline', 'registered' ) ) {
1153 wp_register_script(
1154 'jetpack-twitter-timeline',
1155 Assets::get_file_url_for_environment( '_inc/build/twitter-timeline.min.js', '_inc/twitter-timeline.js' ),
1156 array( 'jquery' ),
1157 '4.0.0',
1158 true
1159 );
1160 }
1161
1162 if ( ! wp_script_is( 'jetpack-facebook-embed', 'registered' ) ) {
1163 wp_register_script(
1164 'jetpack-facebook-embed',
1165 Assets::get_file_url_for_environment( '_inc/build/facebook-embed.min.js', '_inc/facebook-embed.js' ),
1166 array(),
1167 null,
1168 true
1169 );
1170
1171 /** This filter is documented in modules/sharedaddy/sharing-sources.php */
1172 $fb_app_id = apply_filters( 'jetpack_sharing_facebook_app_id', '249643311490' );
1173 if ( ! is_numeric( $fb_app_id ) ) {
1174 $fb_app_id = '';
1175 }
1176 wp_localize_script(
1177 'jetpack-facebook-embed',
1178 'jpfbembed',
1179 array(
1180 'appid' => $fb_app_id,
1181 'locale' => $this->get_locale(),
1182 )
1183 );
1184 }
1185
1186 /**
1187 * As jetpack_register_genericons is by default fired off a hook,
1188 * the hook may have already fired by this point.
1189 * So, let's just trigger it manually.
1190 */
1191 require_once JETPACK__PLUGIN_DIR . '_inc/genericons.php';
1192 jetpack_register_genericons();
1193
1194 /**
1195 * Register the social logos
1196 */
1197 require_once JETPACK__PLUGIN_DIR . '_inc/social-logos.php';
1198 jetpack_register_social_logos();
1199
1200 if ( ! wp_style_is( 'jetpack-icons', 'registered' ) ) {
1201 wp_register_style( 'jetpack-icons', plugins_url( 'css/jetpack-icons.min.css', JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
1202 }
1203 }
1204
1205 /**
1206 * Guess locale from language code.
1207 *
1208 * @param string $lang Language code.
1209 * @return string|bool
1210 */
1211 function guess_locale_from_lang( $lang ) {
1212 if ( 'en' === $lang || 'en_US' === $lang || ! $lang ) {
1213 return 'en_US';
1214 }
1215
1216 if ( ! class_exists( 'GP_Locales' ) ) {
1217 if ( ! defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || ! file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
1218 return false;
1219 }
1220
1221 require JETPACK__GLOTPRESS_LOCALES_PATH;
1222 }
1223
1224 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1225 // WP.com: get_locale() returns 'it'
1226 $locale = GP_Locales::by_slug( $lang );
1227 } else {
1228 // Jetpack: get_locale() returns 'it_IT';
1229 $locale = GP_Locales::by_field( 'facebook_locale', $lang );
1230 }
1231
1232 if ( ! $locale ) {
1233 return false;
1234 }
1235
1236 if ( empty( $locale->facebook_locale ) ) {
1237 if ( empty( $locale->wp_locale ) ) {
1238 return false;
1239 } else {
1240 // Facebook SDK is smart enough to fall back to en_US if a
1241 // locale isn't supported. Since supported Facebook locales
1242 // can fall out of sync, we'll attempt to use the known
1243 // wp_locale value and rely on said fallback.
1244 return $locale->wp_locale;
1245 }
1246 }
1247
1248 return $locale->facebook_locale;
1249 }
1250
1251 /**
1252 * Get the locale.
1253 *
1254 * @return string|bool
1255 */
1256 function get_locale() {
1257 $locale = $this->guess_locale_from_lang( get_locale() );
1258
1259 if ( ! $locale ) {
1260 $locale = 'en_US';
1261 }
1262
1263 return $locale;
1264 }
1265
1266 /**
1267 * Return the network_site_url so that .com knows what network this site is a part of.
1268 *
1269 * @param bool $option
1270 * @return string
1271 */
1272 public function jetpack_main_network_site_option( $option ) {
1273 return network_site_url();
1274 }
1275 /**
1276 * Network Name.
1277 */
1278 static function network_name( $option = null ) {
1279 global $current_site;
1280 return $current_site->site_name;
1281 }
1282 /**
1283 * Does the network allow new user and site registrations.
1284 *
1285 * @return string
1286 */
1287 static function network_allow_new_registrations( $option = null ) {
1288 return ( in_array( get_site_option( 'registration' ), array( 'none', 'user', 'blog', 'all' ) ) ? get_site_option( 'registration' ) : 'none' );
1289 }
1290 /**
1291 * Does the network allow admins to add new users.
1292 *
1293 * @return boolian
1294 */
1295 static function network_add_new_users( $option = null ) {
1296 return (bool) get_site_option( 'add_new_users' );
1297 }
1298 /**
1299 * File upload psace left per site in MB.
1300 * -1 means NO LIMIT.
1301 *
1302 * @return number
1303 */
1304 static function network_site_upload_space( $option = null ) {
1305 // value in MB
1306 return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
1307 }
1308
1309 /**
1310 * Network allowed file types.
1311 *
1312 * @return string
1313 */
1314 static function network_upload_file_types( $option = null ) {
1315 return get_site_option( 'upload_filetypes', 'jpg jpeg png gif' );
1316 }
1317
1318 /**
1319 * Maximum file upload size set by the network.
1320 *
1321 * @return number
1322 */
1323 static function network_max_upload_file_size( $option = null ) {
1324 // value in KB
1325 return get_site_option( 'fileupload_maxk', 300 );
1326 }
1327
1328 /**
1329 * Lets us know if a site allows admins to manage the network.
1330 *
1331 * @return array
1332 */
1333 static function network_enable_administration_menus( $option = null ) {
1334 return get_site_option( 'menu_items' );
1335 }
1336
1337 /**
1338 * If a user has been promoted to or demoted from admin, we need to clear the
1339 * jetpack_other_linked_admins transient.
1340 *
1341 * @since 4.3.2
1342 * @since 4.4.0 $old_roles is null by default and if it's not passed, the transient is cleared.
1343 *
1344 * @param int $user_id The user ID whose role changed.
1345 * @param string $role The new role.
1346 * @param array $old_roles An array of the user's previous roles.
1347 */
1348 function maybe_clear_other_linked_admins_transient( $user_id, $role, $old_roles = null ) {
1349 if ( 'administrator' == $role
1350 || ( is_array( $old_roles ) && in_array( 'administrator', $old_roles ) )
1351 || is_null( $old_roles )
1352 ) {
1353 delete_transient( 'jetpack_other_linked_admins' );
1354 }
1355 }
1356
1357 /**
1358 * Checks to see if there are any other users available to become primary
1359 * Users must both:
1360 * - Be linked to wpcom
1361 * - Be an admin
1362 *
1363 * @return mixed False if no other users are linked, Int if there are.
1364 */
1365 static function get_other_linked_admins() {
1366 $other_linked_users = get_transient( 'jetpack_other_linked_admins' );
1367
1368 if ( false === $other_linked_users ) {
1369 $admins = get_users( array( 'role' => 'administrator' ) );
1370 if ( count( $admins ) > 1 ) {
1371 $available = array();
1372 foreach ( $admins as $admin ) {
1373 if ( self::connection()->is_user_connected( $admin->ID ) ) {
1374 $available[] = $admin->ID;
1375 }
1376 }
1377
1378 $count_connected_admins = count( $available );
1379 if ( count( $available ) > 1 ) {
1380 $other_linked_users = $count_connected_admins;
1381 } else {
1382 $other_linked_users = 0;
1383 }
1384 } else {
1385 $other_linked_users = 0;
1386 }
1387
1388 set_transient( 'jetpack_other_linked_admins', $other_linked_users, HOUR_IN_SECONDS );
1389 }
1390
1391 return ( 0 === $other_linked_users ) ? false : $other_linked_users;
1392 }
1393
1394 /**
1395 * Return whether we are dealing with a multi network setup or not.
1396 * The reason we are type casting this is because we want to avoid the situation where
1397 * the result is false since when is_main_network_option return false it cases
1398 * the rest the get_option( 'jetpack_is_multi_network' ); to return the value that is set in the
1399 * database which could be set to anything as opposed to what this function returns.
1400 *
1401 * @param bool $option
1402 *
1403 * @return boolean
1404 */
1405 public function is_main_network_option( $option ) {
1406 // return '1' or ''
1407 return (string) (bool) self::is_multi_network();
1408 }
1409
1410 /**
1411 * Return true if we are with multi-site or multi-network false if we are dealing with single site.
1412 *
1413 * @param string $option
1414 * @return boolean
1415 */
1416 public function is_multisite( $option ) {
1417 return (string) (bool) is_multisite();
1418 }
1419
1420 /**
1421 * Implemented since there is no core is multi network function
1422 * Right now there is no way to tell if we which network is the dominant network on the system
1423 *
1424 * @since 3.3
1425 * @return boolean
1426 */
1427 public static function is_multi_network() {
1428 global $wpdb;
1429
1430 // if we don't have a multi site setup no need to do any more
1431 if ( ! is_multisite() ) {
1432 return false;
1433 }
1434
1435 $num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
1436 if ( $num_sites > 1 ) {
1437 return true;
1438 } else {
1439 return false;
1440 }
1441 }
1442
1443 /**
1444 * Get back if the current site is single user site.
1445 *
1446 * @return bool
1447 */
1448 public static function is_single_user_site() {
1449 global $wpdb;
1450
1451 if ( false === ( $some_users = get_transient( 'jetpack_is_single_user' ) ) ) {
1452 $some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
1453 set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
1454 }
1455 return 1 === (int) $some_users;
1456 }
1457
1458 /**
1459 * Returns true if the site has file write access false otherwise.
1460 *
1461 * @return string ( '1' | '0' )
1462 **/
1463 public static function file_system_write_access() {
1464 if ( ! function_exists( 'get_filesystem_method' ) ) {
1465 require_once ABSPATH . 'wp-admin/includes/file.php';
1466 }
1467
1468 require_once ABSPATH . 'wp-admin/includes/template.php';
1469
1470 $filesystem_method = get_filesystem_method();
1471 if ( $filesystem_method === 'direct' ) {
1472 return 1;
1473 }
1474
1475 ob_start();
1476 $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1477 ob_end_clean();
1478 if ( $filesystem_credentials_are_stored ) {
1479 return 1;
1480 }
1481 return 0;
1482 }
1483
1484 // phpcs:disable WordPress.WP.CapitalPDangit.Misspelled
1485 /**
1486 * jetpack_updates is saved in the following schema:
1487 *
1488 * array (
1489 * 'plugins' => (int) Number of plugin updates available.
1490 * 'themes' => (int) Number of theme updates available.
1491 * 'wordpress' => (int) Number of WordPress core updates available.
1492 * 'translations' => (int) Number of translation updates available.
1493 * 'total' => (int) Total of all available updates.
1494 * 'wp_update_version' => (string) The latest available version of WordPress, only present if a WordPress update is needed.
1495 * )
1496 *
1497 * @return array
1498 */
1499 public static function get_updates() {
1500 $update_data = wp_get_update_data();
1501
1502 // Stores the individual update counts as well as the total count.
1503 if ( isset( $update_data['counts'] ) ) {
1504 $updates = $update_data['counts'];
1505 }
1506
1507 // If we need to update WordPress core, let's find the latest version number.
1508 if ( ! empty( $updates['wordpress'] ) ) {
1509 $cur = get_preferred_from_update_core();
1510 if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
1511 $updates['wp_update_version'] = $cur->current;
1512 }
1513 }
1514 return isset( $updates ) ? $updates : array();
1515 }
1516 // phpcs:enable
1517
1518 public static function get_update_details() {
1519 $update_details = array(
1520 'update_core' => get_site_transient( 'update_core' ),
1521 'update_plugins' => get_site_transient( 'update_plugins' ),
1522 'update_themes' => get_site_transient( 'update_themes' ),
1523 );
1524 return $update_details;
1525 }
1526
1527 /**
1528 * Is Jetpack active?
1529 * The method only checks if there's an existing token for the master user. It doesn't validate the token.
1530 *
1531 * This method is deprecated since 9.6.0. Please use one of the methods provided by the Manager class in the Connection package,
1532 * or Jetpack::is_connection_ready if you want to know when the Jetpack plugin starts considering the connection ready to be used.
1533 *
1534 * Since this method has a wide spread use, we decided not to throw any deprecation warnings for now.
1535 *
1536 * @deprecated 9.6.0
1537 *
1538 * @return bool
1539 */
1540 public static function is_active() {
1541 return self::connection()->is_active();
1542 }
1543
1544 /**
1545 * Returns true if the current site is connected to WordPress.com and has the minimum requirements to enable Jetpack UI
1546 *
1547 * This method was introduced just before the release of the possibility to use Jetpack without a user connection, while
1548 * it was available only when no_user_testing_mode was enabled. In the near future, this will return is_connected for all
1549 * users and this option will be available by default for everybody.
1550 *
1551 * @since 9.6.0
1552 * @since 9.7.0 returns is_connected in all cases and adds filter to the returned value
1553 *
1554 * @return bool is the site connection ready to be used?
1555 */
1556 public static function is_connection_ready() {
1557 $is_connected = false;
1558
1559 if ( method_exists( self::connection(), 'is_connected' ) ) {
1560 $is_connected = self::connection()->is_connected();
1561 } elseif ( method_exists( self::connection(), 'is_registered' ) ) {
1562 $is_connected = self::connection()->is_registered();
1563 }
1564
1565 /**
1566 * Allows filtering whether the connection is ready to be used. If true, this will enable the Jetpack UI and modules
1567 *
1568 * Modules will be enabled depending on the connection status and if the module requires a connection or user connection.
1569 *
1570 * @since 9.7.0
1571 *
1572 * @param bool $is_connection_ready Is the connection ready?
1573 * @param Automattic\Jetpack\Connection\Manager $connection_manager Instance of the Manager class, can be used to check the connection status.
1574 */
1575 return apply_filters( 'jetpack_is_connection_ready', $is_connected, self::connection() );
1576 }
1577
1578 /**
1579 * Deprecated: Is Jetpack in development (offline) mode?
1580 *
1581 * This static method is being left here intentionally without the use of _deprecated_function(), as other plugins
1582 * and themes still use it, and we do not want to flood them with notices.
1583 *
1584 * Please use Automattic\Jetpack\Status()->is_offline_mode() instead.
1585 *
1586 * @deprecated since 8.0.
1587 */
1588 public static function is_development_mode() {
1589 _deprecated_function( __METHOD__, 'jetpack-8.0', '\Automattic\Jetpack\Status->is_offline_mode' );
1590 return ( new Status() )->is_offline_mode();
1591 }
1592
1593 /**
1594 * Whether the site is currently onboarding or not.
1595 * A site is considered as being onboarded if it currently has an onboarding token.
1596 *
1597 * @since 5.8
1598 *
1599 * @access public
1600 * @static
1601 *
1602 * @return bool True if the site is currently onboarding, false otherwise
1603 */
1604 public static function is_onboarding() {
1605 return Jetpack_Options::get_option( 'onboarding' ) !== false;
1606 }
1607
1608 /**
1609 * Determines reason for Jetpack offline mode.
1610 */
1611 public static function development_mode_trigger_text() {
1612 $status = new Status();
1613
1614 if ( ! $status->is_offline_mode() ) {
1615 return __( 'Jetpack is not in Offline Mode.', 'jetpack' );
1616 }
1617
1618 if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
1619 $notice = __( 'The JETPACK_DEV_DEBUG constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1620 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1621 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1622 } elseif ( $status->is_local_site() ) {
1623 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1624 /** This filter is documented in packages/status/src/class-status.php */
1625 } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1626 $notice = __( 'The jetpack_development_mode filter is set to true.', 'jetpack' );
1627 } else {
1628 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1629 }
1630
1631 return $notice;
1632
1633 }
1634 /**
1635 * Get Jetpack offline mode notice text and notice class.
1636 *
1637 * Mirrors the checks made in Automattic\Jetpack\Status->is_offline_mode
1638 */
1639 public static function show_development_mode_notice() {
1640 if ( ( new Status() )->is_offline_mode() ) {
1641 $notice = sprintf(
1642 /* translators: %s is a URL */
1643 __( 'In <a href="%s" target="_blank">Offline Mode</a>:', 'jetpack' ),
1644 Redirect::get_url( 'jetpack-support-development-mode' )
1645 );
1646
1647 $notice .= ' ' . self::development_mode_trigger_text();
1648
1649 echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1650 }
1651
1652 // Throw up a notice if using a development version and as for feedback.
1653 if ( self::is_development_version() ) {
1654 /* translators: %s is a URL */
1655 $notice = sprintf( __( 'You are currently running a development version of Jetpack. <a href="%s" target="_blank">Submit your feedback</a>', 'jetpack' ), Redirect::get_url( 'jetpack-contact-support-beta-group' ) );
1656
1657 echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1658 }
1659 // Throw up a notice if using staging mode
1660 if ( ( new Status() )->is_staging_site() ) {
1661 /* translators: %s is a URL */
1662 $notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), Redirect::get_url( 'jetpack-support-staging-sites' ) );
1663
1664 echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1665 }
1666 }
1667
1668 /**
1669 * Whether Jetpack's version maps to a public release, or a development version.
1670 */
1671 public static function is_development_version() {
1672 /**
1673 * Allows filtering whether this is a development version of Jetpack.
1674 *
1675 * This filter is especially useful for tests.
1676 *
1677 * @since 4.3.0
1678 *
1679 * @param bool $development_version Is this a develoment version of Jetpack?
1680 */
1681 return (bool) apply_filters(
1682 'jetpack_development_version',
1683 ! preg_match( '/^\d+(\.\d+)+$/', Constants::get_constant( 'JETPACK__VERSION' ) )
1684 );
1685 }
1686
1687 /**
1688 * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1689 */
1690 public static function is_user_connected( $user_id = false ) {
1691 _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Manager\\is_user_connected' );
1692 return self::connection()->is_user_connected( $user_id );
1693 }
1694
1695 /**
1696 * Get the wpcom user data of the current|specified connected user.
1697 */
1698 public static function get_connected_user_data( $user_id = null ) {
1699 _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Manager\\get_connected_user_data' );
1700 return self::connection()->get_connected_user_data( $user_id );
1701 }
1702
1703 /**
1704 * Get the wpcom email of the current|specified connected user.
1705 */
1706 public static function get_connected_user_email( $user_id = null ) {
1707 if ( ! $user_id ) {
1708 $user_id = get_current_user_id();
1709 }
1710
1711 $xml = new Jetpack_IXR_Client(
1712 array(
1713 'user_id' => $user_id,
1714 )
1715 );
1716 $xml->query( 'wpcom.getUserEmail' );
1717 if ( ! $xml->isError() ) {
1718 return $xml->getResponse();
1719 }
1720 return false;
1721 }
1722
1723 /**
1724 * Get the wpcom email of the master user.
1725 */
1726 public static function get_master_user_email() {
1727 $master_user_id = Jetpack_Options::get_option( 'master_user' );
1728 if ( $master_user_id ) {
1729 return self::get_connected_user_email( $master_user_id );
1730 }
1731 return '';
1732 }
1733
1734 /**
1735 * Gets current user IP address.
1736 *
1737 * @param bool $check_all_headers Check all headers? Default is `false`.
1738 *
1739 * @return string Current user IP address.
1740 */
1741 public static function current_user_ip( $check_all_headers = false ) {
1742 if ( $check_all_headers ) {
1743 foreach ( array(
1744 'HTTP_CF_CONNECTING_IP',
1745 'HTTP_CLIENT_IP',
1746 'HTTP_X_FORWARDED_FOR',
1747 'HTTP_X_FORWARDED',
1748 'HTTP_X_CLUSTER_CLIENT_IP',
1749 'HTTP_FORWARDED_FOR',
1750 'HTTP_FORWARDED',
1751 'HTTP_VIA',
1752 ) as $key ) {
1753 if ( ! empty( $_SERVER[ $key ] ) ) {
1754 return $_SERVER[ $key ];
1755 }
1756 }
1757 }
1758
1759 return ! empty( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
1760 }
1761
1762 /**
1763 * Loads the currently active modules.
1764 */
1765 public static function load_modules() {
1766 $is_offline_mode = ( new Status() )->is_offline_mode();
1767 if (
1768 ! self::is_connection_ready()
1769 && ! $is_offline_mode
1770 && ! self::is_onboarding()
1771 && (
1772 ! is_multisite()
1773 || ! get_site_option( 'jetpack_protect_active' )
1774 )
1775 ) {
1776 return;
1777 }
1778
1779 $version = Jetpack_Options::get_option( 'version' );
1780 if ( ! $version ) {
1781 $version = $old_version = JETPACK__VERSION . ':' . time();
1782 /** This action is documented in class.jetpack.php */
1783 do_action( 'updating_jetpack_version', $version, false );
1784 Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1785 }
1786 list( $version ) = explode( ':', $version );
1787
1788 $modules = array_filter( self::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1789
1790 $modules_data = array();
1791
1792 // Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1793 if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1794 $updated_modules = array();
1795 foreach ( $modules as $module ) {
1796 $modules_data[ $module ] = self::get_module( $module );
1797 if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1798 continue;
1799 }
1800
1801 if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1802 continue;
1803 }
1804
1805 $updated_modules[] = $module;
1806 }
1807
1808 $modules = array_diff( $modules, $updated_modules );
1809 }
1810
1811 $is_site_connection = false;
1812
1813 if ( method_exists( self::connection(), 'is_site_connection' ) ) {
1814 $is_site_connection = self::connection()->is_site_connection();
1815 }
1816
1817 foreach ( $modules as $index => $module ) {
1818 // If we're in offline/site-connection mode, disable modules requiring a connection/user connection.
1819 if ( $is_offline_mode || $is_site_connection ) {
1820 // Prime the pump if we need to
1821 if ( empty( $modules_data[ $module ] ) ) {
1822 $modules_data[ $module ] = self::get_module( $module );
1823 }
1824 // If the module requires a connection, but we're in local mode, don't include it.
1825 if ( $is_offline_mode && $modules_data[ $module ]['requires_connection'] ) {
1826 continue;
1827 }
1828
1829 if ( $is_site_connection && $modules_data[ $module ]['requires_user_connection'] ) {
1830 continue;
1831 }
1832 }
1833
1834 if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1835 continue;
1836 }
1837
1838 if ( ! include_once self::get_module_path( $module ) ) {
1839 unset( $modules[ $index ] );
1840 self::update_active_modules( array_values( $modules ) );
1841 continue;
1842 }
1843
1844 /**
1845 * Fires when a specific module is loaded.
1846 * The dynamic part of the hook, $module, is the module slug.
1847 *
1848 * @since 1.1.0
1849 */
1850 do_action( 'jetpack_module_loaded_' . $module );
1851 }
1852
1853 /**
1854 * Fires when all the modules are loaded.
1855 *
1856 * @since 1.1.0
1857 */
1858 do_action( 'jetpack_modules_loaded' );
1859
1860 // Load module-specific code that is needed even when a module isn't active. Loaded here because code contained therein may need actions such as setup_theme.
1861 require_once JETPACK__PLUGIN_DIR . 'modules/module-extras.php';
1862 }
1863
1864 /**
1865 * Check if Jetpack's REST API compat file should be included
1866 *
1867 * @action plugins_loaded
1868 * @return null
1869 */
1870 public function check_rest_api_compat() {
1871 /**
1872 * Filters the list of REST API compat files to be included.
1873 *
1874 * @since 2.2.5
1875 *
1876 * @param array $args Array of REST API compat files to include.
1877 */
1878 $_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1879
1880 foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include ) {
1881 require_once $_jetpack_rest_api_compat_include;
1882 }
1883 }
1884
1885 /**
1886 * Gets all plugins currently active in values, regardless of whether they're
1887 * traditionally activated or network activated.
1888 *
1889 * @todo Store the result in core's object cache maybe?
1890 */
1891 public static function get_active_plugins() {
1892 $active_plugins = (array) get_option( 'active_plugins', array() );
1893
1894 if ( is_multisite() ) {
1895 // Due to legacy code, active_sitewide_plugins stores them in the keys,
1896 // whereas active_plugins stores them in the values.
1897 $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1898 if ( $network_plugins ) {
1899 $active_plugins = array_merge( $active_plugins, $network_plugins );
1900 }
1901 }
1902
1903 sort( $active_plugins );
1904
1905 return array_unique( $active_plugins );
1906 }
1907
1908 /**
1909 * Gets and parses additional plugin data to send with the heartbeat data
1910 *
1911 * @since 3.8.1
1912 *
1913 * @return array Array of plugin data
1914 */
1915 public static function get_parsed_plugin_data() {
1916 if ( ! function_exists( 'get_plugins' ) ) {
1917 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1918 }
1919 /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
1920 $all_plugins = apply_filters( 'all_plugins', get_plugins() );
1921 $active_plugins = self::get_active_plugins();
1922
1923 $plugins = array();
1924 foreach ( $all_plugins as $path => $plugin_data ) {
1925 $plugins[ $path ] = array(
1926 'is_active' => in_array( $path, $active_plugins ),
1927 'file' => $path,
1928 'name' => $plugin_data['Name'],
1929 'version' => $plugin_data['Version'],
1930 'author' => $plugin_data['Author'],
1931 );
1932 }
1933
1934 return $plugins;
1935 }
1936
1937 /**
1938 * Gets and parses theme data to send with the heartbeat data
1939 *
1940 * @since 3.8.1
1941 *
1942 * @return array Array of theme data
1943 */
1944 public static function get_parsed_theme_data() {
1945 $all_themes = wp_get_themes( array( 'allowed' => true ) );
1946 $header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
1947
1948 $themes = array();
1949 foreach ( $all_themes as $slug => $theme_data ) {
1950 $theme_headers = array();
1951 foreach ( $header_keys as $header_key ) {
1952 $theme_headers[ $header_key ] = $theme_data->get( $header_key );
1953 }
1954
1955 $themes[ $slug ] = array(
1956 'is_active_theme' => $slug == wp_get_theme()->get_template(),
1957 'slug' => $slug,
1958 'theme_root' => $theme_data->get_theme_root_uri(),
1959 'parent' => $theme_data->parent(),
1960 'headers' => $theme_headers,
1961 );
1962 }
1963
1964 return $themes;
1965 }
1966
1967 /**
1968 * Checks whether a specific plugin is active.
1969 *
1970 * We don't want to store these in a static variable, in case
1971 * there are switch_to_blog() calls involved.
1972 */
1973 public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
1974 return in_array( $plugin, self::get_active_plugins() );
1975 }
1976
1977 /**
1978 * Check if Jetpack's Open Graph tags should be used.
1979 * If certain plugins are active, Jetpack's og tags are suppressed.
1980 *
1981 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1982 * @action plugins_loaded
1983 * @return null
1984 */
1985 public function check_open_graph() {
1986 if ( in_array( 'publicize', self::get_active_modules() ) || in_array( 'sharedaddy', self::get_active_modules() ) ) {
1987 add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
1988 }
1989
1990 $active_plugins = self::get_active_plugins();
1991
1992 if ( ! empty( $active_plugins ) ) {
1993 foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
1994 if ( in_array( $plugin, $active_plugins ) ) {
1995 add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
1996 break;
1997 }
1998 }
1999 }
2000
2001 /**
2002 * Allow the addition of Open Graph Meta Tags to all pages.
2003 *
2004 * @since 2.0.3
2005 *
2006 * @param bool false Should Open Graph Meta tags be added. Default to false.
2007 */
2008 if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
2009 require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
2010 }
2011 }
2012
2013 /**
2014 * Check if Jetpack's Twitter tags should be used.
2015 * If certain plugins are active, Jetpack's twitter tags are suppressed.
2016 *
2017 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
2018 * @action plugins_loaded
2019 * @return null
2020 */
2021 public function check_twitter_tags() {
2022
2023 $active_plugins = self::get_active_plugins();
2024
2025 if ( ! empty( $active_plugins ) ) {
2026 foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
2027 if ( in_array( $plugin, $active_plugins ) ) {
2028 add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
2029 break;
2030 }
2031 }
2032 }
2033
2034 /**
2035 * Allow Twitter Card Meta tags to be disabled.
2036 *
2037 * @since 2.6.0
2038 *
2039 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2040 */
2041 if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2042 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2043 }
2044 }
2045
2046 /* Jetpack Options API */
2047
2048 public static function get_option_names( $type = 'compact' ) {
2049 return Jetpack_Options::get_option_names( $type );
2050 }
2051
2052 /**
2053 * Returns the requested option. Looks in jetpack_options or jetpack_$name as appropriate.
2054 *
2055 * @param string $name Option name
2056 * @param mixed $default (optional)
2057 */
2058 public static function get_option( $name, $default = false ) {
2059 return Jetpack_Options::get_option( $name, $default );
2060 }
2061
2062 /**
2063 * Returns an array of all PHP files in the specified absolute path.
2064 * Equivalent to glob( "$absolute_path/*.php" ).
2065 *
2066 * @param string $absolute_path The absolute path of the directory to search.
2067 * @return array Array of absolute paths to the PHP files.
2068 */
2069 public static function glob_php( $absolute_path ) {
2070 if ( function_exists( 'glob' ) ) {
2071 return glob( "$absolute_path/*.php" );
2072 }
2073
2074 $absolute_path = untrailingslashit( $absolute_path );
2075 $files = array();
2076 if ( ! $dir = @opendir( $absolute_path ) ) {
2077 return $files;
2078 }
2079
2080 while ( false !== $file = readdir( $dir ) ) {
2081 if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
2082 continue;
2083 }
2084
2085 $file = "$absolute_path/$file";
2086
2087 if ( ! is_file( $file ) ) {
2088 continue;
2089 }
2090
2091 $files[] = $file;
2092 }
2093
2094 closedir( $dir );
2095
2096 return $files;
2097 }
2098
2099 public static function activate_new_modules( $redirect = false ) {
2100 if ( ! self::is_connection_ready() && ! ( new Status() )->is_offline_mode() ) {
2101 return;
2102 }
2103
2104 $jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
2105 if ( ! $jetpack_old_version ) {
2106 $jetpack_old_version = $version = $old_version = '1.1:' . time();
2107 /** This action is documented in class.jetpack.php */
2108 do_action( 'updating_jetpack_version', $version, false );
2109 Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2110 }
2111
2112 list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
2113
2114 if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
2115 return;
2116 }
2117
2118 $active_modules = self::get_active_modules();
2119 $reactivate_modules = array();
2120 foreach ( $active_modules as $active_module ) {
2121 $module = self::get_module( $active_module );
2122 if ( ! isset( $module['changed'] ) ) {
2123 continue;
2124 }
2125
2126 if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
2127 continue;
2128 }
2129
2130 $reactivate_modules[] = $active_module;
2131 self::deactivate_module( $active_module );
2132 }
2133
2134 $new_version = JETPACK__VERSION . ':' . time();
2135 /** This action is documented in class.jetpack.php */
2136 do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
2137 Jetpack_Options::update_options(
2138 array(
2139 'version' => $new_version,
2140 'old_version' => $jetpack_old_version,
2141 )
2142 );
2143
2144 self::state( 'message', 'modules_activated' );
2145
2146 self::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules, $redirect );
2147
2148 if ( $redirect ) {
2149 $page = 'jetpack'; // make sure we redirect to either settings or the jetpack page
2150 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ) ) ) {
2151 $page = $_GET['page'];
2152 }
2153
2154 wp_safe_redirect( self::admin_url( 'page=' . $page ) );
2155 exit;
2156 }
2157 }
2158
2159 /**
2160 * List available Jetpack modules. Simply lists .php files in /modules/.
2161 * Make sure to tuck away module "library" files in a sub-directory.
2162 *
2163 * @param bool|string $min_version Only return modules introduced in this version or later. Default is false, do not filter.
2164 * @param bool|string $max_version Only return modules introduced before this version. Default is false, do not filter.
2165 * @param bool|null $requires_connection Pass a boolean value to only return modules that require (or do not require) a connection.
2166 * @param bool|null $requires_user_connection Pass a boolean value to only return modules that require (or do not require) a user connection.
2167 *
2168 * @return array $modules Array of module slugs
2169 */
2170 public static function get_available_modules( $min_version = false, $max_version = false, $requires_connection = null, $requires_user_connection = null ) {
2171 static $modules = null;
2172
2173 if ( ! isset( $modules ) ) {
2174 $available_modules_option = Jetpack_Options::get_option( 'available_modules', array() );
2175 // Use the cache if we're on the front-end and it's available...
2176 if ( ! is_admin() && ! empty( $available_modules_option[ JETPACK__VERSION ] ) ) {
2177 $modules = $available_modules_option[ JETPACK__VERSION ];
2178 } else {
2179 $files = self::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
2180
2181 $modules = array();
2182
2183 foreach ( $files as $file ) {
2184 $slug = self::get_module_slug( $file );
2185 $headers = self::get_module( $slug );
2186
2187 if ( ! $headers ) {
2188 continue;
2189 }
2190
2191 $modules[ $slug ] = $headers['introduced'];
2192 }
2193
2194 Jetpack_Options::update_option(
2195 'available_modules',
2196 array(
2197 JETPACK__VERSION => $modules,
2198 )
2199 );
2200 }
2201 }
2202
2203 /**
2204 * Filters the array of modules available to be activated.
2205 *
2206 * @since 2.4.0
2207 *
2208 * @param array $modules Array of available modules.
2209 * @param string $min_version Minimum version number required to use modules.
2210 * @param string $max_version Maximum version number required to use modules.
2211 * @param bool|null $requires_connection Value of the Requires Connection filter.
2212 * @param bool|null $requires_user_connection Value of the Requires User Connection filter.
2213 */
2214 $mods = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version, $requires_connection, $requires_user_connection );
2215
2216 if ( ! $min_version && ! $max_version && is_null( $requires_connection ) && is_null( $requires_user_connection ) ) {
2217 return array_keys( $mods );
2218 }
2219
2220 $r = array();
2221 foreach ( $mods as $slug => $introduced ) {
2222 if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
2223 continue;
2224 }
2225
2226 if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
2227 continue;
2228 }
2229
2230 $mod_details = self::get_module( $slug );
2231
2232 if ( null !== $requires_connection && (bool) $requires_connection !== $mod_details['requires_connection'] ) {
2233 continue;
2234 }
2235
2236 if ( null !== $requires_user_connection && (bool) $requires_user_connection !== $mod_details['requires_user_connection'] ) {
2237 continue;
2238 }
2239
2240 $r[] = $slug;
2241 }
2242
2243 return $r;
2244 }
2245
2246 /**
2247 * Get default modules loaded on activation.
2248 *
2249 * @param bool|string $min_version Onlu return modules introduced in this version or later. Default is false, do not filter.
2250 * @param bool|string $max_version Only return modules introduced before this version. Default is false, do not filter.
2251 * @param bool|null $requires_connection Pass a boolean value to only return modules that require (or do not require) a connection.
2252 * @param bool|null $requires_user_connection Pass a boolean value to only return modules that require (or do not require) a user connection.
2253 *
2254 * @return array $modules Array of module slugs
2255 */
2256 public static function get_default_modules( $min_version = false, $max_version = false, $requires_connection = null, $requires_user_connection = null ) {
2257 $return = array();
2258
2259 foreach ( self::get_available_modules( $min_version, $max_version, $requires_connection, $requires_user_connection ) as $module ) {
2260 $module_data = self::get_module( $module );
2261
2262 switch ( strtolower( $module_data['auto_activate'] ) ) {
2263 case 'yes':
2264 $return[] = $module;
2265 break;
2266 case 'public':
2267 if ( Jetpack_Options::get_option( 'public' ) ) {
2268 $return[] = $module;
2269 }
2270 break;
2271 case 'no':
2272 default:
2273 break;
2274 }
2275 }
2276 /**
2277 * Filters the array of default modules.
2278 *
2279 * @since 2.5.0
2280 *
2281 * @param array $return Array of default modules.
2282 * @param string $min_version Minimum version number required to use modules.
2283 * @param string $max_version Maximum version number required to use modules.
2284 * @param bool|null $requires_connection Value of the Requires Connection filter.
2285 * @param bool|null $requires_user_connection Value of the Requires User Connection filter.
2286 */
2287 return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version, $requires_connection, $requires_user_connection );
2288 }
2289
2290 /**
2291 * Checks activated modules during auto-activation to determine
2292 * if any of those modules are being deprecated. If so, close
2293 * them out, and add any replacement modules.
2294 *
2295 * Runs at priority 99 by default.
2296 *
2297 * This is run late, so that it can still activate a module if
2298 * the new module is a replacement for another that the user
2299 * currently has active, even if something at the normal priority
2300 * would kibosh everything.
2301 *
2302 * @since 2.6
2303 * @uses jetpack_get_default_modules filter
2304 * @param array $modules
2305 * @return array
2306 */
2307 function handle_deprecated_modules( $modules ) {
2308 $deprecated_modules = array(
2309 'debug' => null, // Closed out and moved to the debugger library.
2310 'wpcc' => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2311 'gplus-authorship' => null, // Closed out in 3.2 -- Google dropped support.
2312 'minileven' => null, // Closed out in 8.3 -- Responsive themes are common now, and so is AMP.
2313 );
2314
2315 // Don't activate SSO if they never completed activating WPCC.
2316 if ( self::is_module_active( 'wpcc' ) ) {
2317 $wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2318 if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2319 $deprecated_modules['wpcc'] = null;
2320 }
2321 }
2322
2323 foreach ( $deprecated_modules as $module => $replacement ) {
2324 if ( self::is_module_active( $module ) ) {
2325 self::deactivate_module( $module );
2326 if ( $replacement ) {
2327 $modules[] = $replacement;
2328 }
2329 }
2330 }
2331
2332 return array_unique( $modules );
2333 }
2334
2335 /**
2336 * Checks activated plugins during auto-activation to determine
2337 * if any of those plugins are in the list with a corresponding module
2338 * that is not compatible with the plugin. The module will not be allowed
2339 * to auto-activate.
2340 *
2341 * @since 2.6
2342 * @uses jetpack_get_default_modules filter
2343 * @param array $modules
2344 * @return array
2345 */
2346 function filter_default_modules( $modules ) {
2347
2348 $active_plugins = self::get_active_plugins();
2349
2350 if ( ! empty( $active_plugins ) ) {
2351
2352 // For each module we'd like to auto-activate...
2353 foreach ( $modules as $key => $module ) {
2354 // If there are potential conflicts for it...
2355 if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2356 // For each potential conflict...
2357 foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
2358 // If that conflicting plugin is active...
2359 if ( in_array( $plugin, $active_plugins ) ) {
2360 // Remove that item from being auto-activated.
2361 unset( $modules[ $key ] );
2362 }
2363 }
2364 }
2365 }
2366 }
2367
2368 return $modules;
2369 }
2370
2371 /**
2372 * Extract a module's slug from its full path.
2373 */
2374 public static function get_module_slug( $file ) {
2375 return str_replace( '.php', '', basename( $file ) );
2376 }
2377
2378 /**
2379 * Generate a module's path from its slug.
2380 */
2381 public static function get_module_path( $slug ) {
2382 /**
2383 * Filters the path of a modules.
2384 *
2385 * @since 7.4.0
2386 *
2387 * @param array $return The absolute path to a module's root php file
2388 * @param string $slug The module slug
2389 */
2390 return apply_filters( 'jetpack_get_module_path', JETPACK__PLUGIN_DIR . "modules/$slug.php", $slug );
2391 }
2392
2393 /**
2394 * Load module data from module file. Headers differ from WordPress
2395 * plugin headers to avoid them being identified as standalone
2396 * plugins on the WordPress plugins page.
2397 *
2398 * @param string $module The module slug.
2399 */
2400 public static function get_module( $module ) {
2401 static $modules_details;
2402
2403 if ( jetpack_has_no_module_info( $module ) ) {
2404 return false;
2405 }
2406
2407 $file = self::get_module_path( self::get_module_slug( $module ) );
2408
2409 if ( isset( $modules_details[ $module ] ) ) {
2410 $mod = $modules_details[ $module ];
2411 } else {
2412 $mod = jetpack_get_module_info( $module );
2413
2414 if ( null === $mod ) {
2415 // Try to get the module info from the file as a fallback.
2416 $mod = self::get_file_data( $file, jetpack_get_all_module_header_names() );
2417
2418 if ( empty( $mod['name'] ) ) {
2419 // No info for this module.
2420 return false;
2421 }
2422 }
2423
2424 $mod['sort'] = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
2425 $mod['recommendation_order'] = empty( $mod['recommendation_order'] ) ? 20 : (int) $mod['recommendation_order'];
2426 $mod['deactivate'] = empty( $mod['deactivate'] );
2427 $mod['free'] = empty( $mod['free'] );
2428 $mod['requires_connection'] = ( ! empty( $mod['requires_connection'] ) && 'No' === $mod['requires_connection'] ) ? false : true;
2429 $mod['requires_user_connection'] = ( empty( $mod['requires_user_connection'] ) || 'No' === $mod['requires_user_connection'] ) ? false : true;
2430
2431 if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ), true ) ) {
2432 $mod['auto_activate'] = 'No';
2433 } else {
2434 $mod['auto_activate'] = (string) $mod['auto_activate'];
2435 }
2436
2437 if ( $mod['module_tags'] ) {
2438 $mod['module_tags'] = explode( ',', $mod['module_tags'] );
2439 $mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
2440 $mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
2441 } else {
2442 $mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
2443 }
2444
2445 if ( $mod['plan_classes'] ) {
2446 $mod['plan_classes'] = explode( ',', $mod['plan_classes'] );
2447 $mod['plan_classes'] = array_map( 'strtolower', array_map( 'trim', $mod['plan_classes'] ) );
2448 } else {
2449 $mod['plan_classes'] = array( 'free' );
2450 }
2451
2452 if ( $mod['feature'] ) {
2453 $mod['feature'] = explode( ',', $mod['feature'] );
2454 $mod['feature'] = array_map( 'trim', $mod['feature'] );
2455 } else {
2456 $mod['feature'] = array( self::translate_module_tag( 'Other' ) );
2457 }
2458
2459 $modules_details[ $module ] = $mod;
2460
2461 }
2462
2463 /**
2464 * Filters the feature array on a module.
2465 *
2466 * This filter allows you to control where each module is filtered: Recommended,
2467 * and the default "Other" listing.
2468 *
2469 * @since 3.5.0
2470 *
2471 * @param array $mod['feature'] The areas to feature this module:
2472 * 'Recommended' shows on the main Jetpack admin screen.
2473 * 'Other' should be the default if no other value is in the array.
2474 * @param string $module The slug of the module, e.g. sharedaddy.
2475 * @param array $mod All the currently assembled module data.
2476 */
2477 $mod['feature'] = apply_filters( 'jetpack_module_feature', $mod['feature'], $module, $mod );
2478
2479 /**
2480 * Filter the returned data about a module.
2481 *
2482 * This filter allows overriding any info about Jetpack modules. It is dangerous,
2483 * so please be careful.
2484 *
2485 * @since 3.6.0
2486 *
2487 * @param array $mod The details of the requested module.
2488 * @param string $module The slug of the module, e.g. sharedaddy
2489 * @param string $file The path to the module source file.
2490 */
2491 return apply_filters( 'jetpack_get_module', $mod, $module, $file );
2492 }
2493
2494 /**
2495 * Like core's get_file_data implementation, but caches the result.
2496 */
2497 public static function get_file_data( $file, $headers ) {
2498 // Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated
2499 $file_name = basename( $file );
2500
2501 $cache_key = 'jetpack_file_data_' . JETPACK__VERSION;
2502
2503 $file_data_option = get_transient( $cache_key );
2504
2505 if ( ! is_array( $file_data_option ) ) {
2506 delete_transient( $cache_key );
2507 $file_data_option = false;
2508 }
2509
2510 if ( false === $file_data_option ) {
2511 $file_data_option = array();
2512 }
2513
2514 $key = md5( $file_name . serialize( $headers ) );
2515 $refresh_cache = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 );
2516
2517 // If we don't need to refresh the cache, and already have the value, short-circuit!
2518 if ( ! $refresh_cache && isset( $file_data_option[ $key ] ) ) {
2519 return $file_data_option[ $key ];
2520 }
2521
2522 $data = get_file_data( $file, $headers );
2523
2524 $file_data_option[ $key ] = $data;
2525
2526 set_transient( $cache_key, $file_data_option, 29 * DAY_IN_SECONDS );
2527
2528 return $data;
2529 }
2530
2531 /**
2532 * Return translated module tag.
2533 *
2534 * @param string $tag Tag as it appears in each module heading.
2535 *
2536 * @return mixed
2537 */
2538 public static function translate_module_tag( $tag ) {
2539 return jetpack_get_module_i18n_tag( $tag );
2540 }
2541
2542 /**
2543 * Return module name translation. Uses matching string created in modules/module-headings.php.
2544 *
2545 * @since 3.9.2
2546 *
2547 * @param array $modules
2548 *
2549 * @return string|void
2550 */
2551 public static function get_translated_modules( $modules ) {
2552 foreach ( $modules as $index => $module ) {
2553 $i18n_module = jetpack_get_module_i18n( $module['module'] );
2554 if ( isset( $module['name'] ) ) {
2555 $modules[ $index ]['name'] = $i18n_module['name'];
2556 }
2557 if ( isset( $module['description'] ) ) {
2558 $modules[ $index ]['description'] = $i18n_module['description'];
2559 $modules[ $index ]['short_description'] = $i18n_module['description'];
2560 }
2561 }
2562 return $modules;
2563 }
2564
2565 /**
2566 * Get a list of activated modules as an array of module slugs.
2567 */
2568 public static function get_active_modules() {
2569 $active = Jetpack_Options::get_option( 'active_modules' );
2570
2571 if ( ! is_array( $active ) ) {
2572 $active = array();
2573 }
2574
2575 if ( class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ) ) {
2576 $active[] = 'vaultpress';
2577 } else {
2578 $active = array_diff( $active, array( 'vaultpress' ) );
2579 }
2580
2581 // If protect is active on the main site of a multisite, it should be active on all sites.
2582 if ( ! in_array( 'protect', $active ) && is_multisite() && get_site_option( 'jetpack_protect_active' ) ) {
2583 $active[] = 'protect';
2584 }
2585
2586 /**
2587 * Allow filtering of the active modules.
2588 *
2589 * Gives theme and plugin developers the power to alter the modules that
2590 * are activated on the fly.
2591 *
2592 * @since 5.8.0
2593 *
2594 * @param array $active Array of active module slugs.
2595 */
2596 $active = apply_filters( 'jetpack_active_modules', $active );
2597
2598 return array_unique( $active );
2599 }
2600
2601 /**
2602 * Check whether or not a Jetpack module is active.
2603 *
2604 * @param string $module The slug of a Jetpack module.
2605 * @return bool
2606 *
2607 * @static
2608 */
2609 public static function is_module_active( $module ) {
2610 return in_array( $module, self::get_active_modules() );
2611 }
2612
2613 public static function is_module( $module ) {
2614 return ! empty( $module ) && ! validate_file( $module, self::get_available_modules() );
2615 }
2616
2617 /**
2618 * Catches PHP errors. Must be used in conjunction with output buffering.
2619 *
2620 * @param bool $catch True to start catching, False to stop.
2621 *
2622 * @static
2623 */
2624 public static function catch_errors( $catch ) {
2625 static $display_errors, $error_reporting;
2626
2627 if ( $catch ) {
2628 $display_errors = @ini_set( 'display_errors', 1 );
2629 $error_reporting = @error_reporting( E_ALL );
2630 add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2631 } else {
2632 @ini_set( 'display_errors', $display_errors );
2633 @error_reporting( $error_reporting );
2634 remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2635 }
2636 }
2637
2638 /**
2639 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2640 */
2641 public static function catch_errors_on_shutdown() {
2642 self::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2643 }
2644
2645 /**
2646 * Rewrite any string to make paths easier to read.
2647 *
2648 * Rewrites ABSPATH (eg `/home/jetpack/wordpress/`) to ABSPATH, and if WP_CONTENT_DIR
2649 * is located outside of ABSPATH, rewrites that to WP_CONTENT_DIR.
2650 *
2651 * @param $string
2652 * @return mixed
2653 */
2654 public static function alias_directories( $string ) {
2655 // ABSPATH has a trailing slash.
2656 $string = str_replace( ABSPATH, 'ABSPATH/', $string );
2657 // WP_CONTENT_DIR does not have a trailing slash.
2658 $string = str_replace( WP_CONTENT_DIR, 'WP_CONTENT_DIR', $string );
2659
2660 return $string;
2661 }
2662
2663 public static function activate_default_modules(
2664 $min_version = false,
2665 $max_version = false,
2666 $other_modules = array(),
2667 $redirect = null,
2668 $send_state_messages = null,
2669 $requires_connection = null,
2670 $requires_user_connection = null
2671 ) {
2672 $jetpack = self::init();
2673
2674 if ( is_null( $redirect ) ) {
2675 if (
2676 ( defined( 'REST_REQUEST' ) && REST_REQUEST )
2677 ||
2678 ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
2679 ||
2680 ( defined( 'WP_CLI' ) && WP_CLI )
2681 ||
2682 ( defined( 'DOING_CRON' ) && DOING_CRON )
2683 ||
2684 ( defined( 'DOING_AJAX' ) && DOING_AJAX )
2685 ) {
2686 $redirect = false;
2687 } elseif ( is_admin() ) {
2688 $redirect = true;
2689 } else {
2690 $redirect = false;
2691 }
2692 }
2693
2694 if ( is_null( $send_state_messages ) ) {
2695 $send_state_messages = current_user_can( 'jetpack_activate_modules' );
2696 }
2697
2698 $modules = self::get_default_modules( $min_version, $max_version, $requires_connection, $requires_user_connection );
2699 $modules = array_merge( $other_modules, $modules );
2700
2701 // Look for standalone plugins and disable if active.
2702
2703 $to_deactivate = array();
2704 foreach ( $modules as $module ) {
2705 if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2706 $to_deactivate[ $module ] = $jetpack->plugins_to_deactivate[ $module ];
2707 }
2708 }
2709
2710 $deactivated = array();
2711 foreach ( $to_deactivate as $module => $deactivate_me ) {
2712 list( $probable_file, $probable_title ) = $deactivate_me;
2713 if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2714 $deactivated[] = $module;
2715 }
2716 }
2717
2718 if ( $deactivated ) {
2719 if ( $send_state_messages ) {
2720 self::state( 'deactivated_plugins', join( ',', $deactivated ) );
2721 }
2722
2723 if ( $redirect ) {
2724 $url = add_query_arg(
2725 array(
2726 'action' => 'activate_default_modules',
2727 '_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2728 ),
2729 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2730 );
2731 wp_safe_redirect( $url );
2732 exit;
2733 }
2734 }
2735
2736 /**
2737 * Fires before default modules are activated.
2738 *
2739 * @since 1.9.0
2740 *
2741 * @param string $min_version Minimum version number required to use modules.
2742 * @param string $max_version Maximum version number required to use modules.
2743 * @param array $other_modules Array of other modules to activate alongside the default modules.
2744 * @param bool|null $requires_connection Value of the Requires Connection filter.
2745 * @param bool|null $requires_user_connection Value of the Requires User Connection filter.
2746 */
2747 do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules, $requires_connection, $requires_user_connection );
2748
2749 // Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
2750 if ( $send_state_messages ) {
2751 self::restate();
2752 self::catch_errors( true );
2753 }
2754
2755 $active = self::get_active_modules();
2756
2757 foreach ( $modules as $module ) {
2758 if ( did_action( "jetpack_module_loaded_$module" ) ) {
2759 $active[] = $module;
2760 self::update_active_modules( $active );
2761 continue;
2762 }
2763
2764 if ( $send_state_messages && in_array( $module, $active ) ) {
2765 $module_info = self::get_module( $module );
2766 if ( ! $module_info['deactivate'] ) {
2767 $state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2768 if ( $active_state = self::state( $state ) ) {
2769 $active_state = explode( ',', $active_state );
2770 } else {
2771 $active_state = array();
2772 }
2773 $active_state[] = $module;
2774 self::state( $state, implode( ',', $active_state ) );
2775 }
2776 continue;
2777 }
2778
2779 $file = self::get_module_path( $module );
2780 if ( ! file_exists( $file ) ) {
2781 continue;
2782 }
2783
2784 // we'll override this later if the plugin can be included without fatal error
2785 if ( $redirect ) {
2786 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
2787 }
2788
2789 if ( $send_state_messages ) {
2790 self::state( 'error', 'module_activation_failed' );
2791 self::state( 'module', $module );
2792 }
2793
2794 ob_start();
2795 require_once $file;
2796
2797 $active[] = $module;
2798
2799 if ( $send_state_messages ) {
2800
2801 $state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2802 if ( $active_state = self::state( $state ) ) {
2803 $active_state = explode( ',', $active_state );
2804 } else {
2805 $active_state = array();
2806 }
2807 $active_state[] = $module;
2808 self::state( $state, implode( ',', $active_state ) );
2809 }
2810
2811 self::update_active_modules( $active );
2812
2813 ob_end_clean();
2814 }
2815
2816 if ( $send_state_messages ) {
2817 self::state( 'error', false );
2818 self::state( 'module', false );
2819 }
2820
2821 self::catch_errors( false );
2822 /**
2823 * Fires when default modules are activated.
2824 *
2825 * @since 1.9.0
2826 *
2827 * @param string $min_version Minimum version number required to use modules.
2828 * @param string $max_version Maximum version number required to use modules.
2829 * @param array $other_modules Array of other modules to activate alongside the default modules.
2830 * @param bool|null $requires_connection Value of the Requires Connection filter.
2831 * @param bool|null $requires_user_connection Value of the Requires User Connection filter.
2832 */
2833 do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules, $requires_connection, $requires_user_connection );
2834 }
2835
2836 public static function activate_module( $module, $exit = true, $redirect = true ) {
2837 /**
2838 * Fires before a module is activated.
2839 *
2840 * @since 2.6.0
2841 *
2842 * @param string $module Module slug.
2843 * @param bool $exit Should we exit after the module has been activated. Default to true.
2844 * @param bool $redirect Should the user be redirected after module activation? Default to true.
2845 */
2846 do_action( 'jetpack_pre_activate_module', $module, $exit, $redirect );
2847
2848 $jetpack = self::init();
2849
2850 if ( ! strlen( $module ) ) {
2851 return false;
2852 }
2853
2854 if ( ! self::is_module( $module ) ) {
2855 return false;
2856 }
2857
2858 // If it's already active, then don't do it again
2859 $active = self::get_active_modules();
2860 foreach ( $active as $act ) {
2861 if ( $act == $module ) {
2862 return true;
2863 }
2864 }
2865
2866 $module_data = self::get_module( $module );
2867
2868 $is_offline_mode = ( new Status() )->is_offline_mode();
2869 if ( ! self::is_connection_ready() ) {
2870 if ( ! $is_offline_mode && ! self::is_onboarding() ) {
2871 return false;
2872 }
2873
2874 // If we're not connected but in offline mode, make sure the module doesn't require a connection.
2875 if ( $is_offline_mode && $module_data['requires_connection'] ) {
2876 return false;
2877 }
2878 }
2879
2880 // Check and see if the old plugin is active
2881 if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2882 // Deactivate the old plugin
2883 if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[ $module ][0], $jetpack->plugins_to_deactivate[ $module ][1] ) ) {
2884 // If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
2885 // We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
2886 self::state( 'deactivated_plugins', $module );
2887 wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
2888 exit;
2889 }
2890 }
2891
2892 // Protect won't work with mis-configured IPs
2893 if ( 'protect' === $module ) {
2894 include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
2895 if ( ! jetpack_protect_get_ip() ) {
2896 self::state( 'message', 'protect_misconfigured_ip' );
2897 return false;
2898 }
2899 }
2900
2901 if ( ! Jetpack_Plan::supports( $module ) ) {
2902 return false;
2903 }
2904
2905 // Check the file for fatal errors, a la wp-admin/plugins.php::activate
2906 self::state( 'module', $module );
2907 self::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
2908
2909 self::catch_errors( true );
2910 ob_start();
2911 require self::get_module_path( $module );
2912 /** This action is documented in class.jetpack.php */
2913 do_action( 'jetpack_activate_module', $module );
2914 $active[] = $module;
2915 self::update_active_modules( $active );
2916
2917 self::state( 'error', false ); // the override
2918 ob_end_clean();
2919 self::catch_errors( false );
2920
2921 if ( $redirect ) {
2922 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
2923 }
2924 if ( $exit ) {
2925 exit;
2926 }
2927 return true;
2928 }
2929
2930 public static function deactivate_module( $module ) {
2931 /**
2932 * Fires when a module is deactivated.
2933 *
2934 * @since 1.9.0
2935 *
2936 * @param string $module Module slug.
2937 */
2938 do_action( 'jetpack_pre_deactivate_module', $module );
2939
2940 $jetpack = self::init();
2941
2942 $active = self::get_active_modules();
2943 $new = array_filter( array_diff( $active, (array) $module ) );
2944
2945 return self::update_active_modules( $new );
2946 }
2947
2948 public static function enable_module_configurable( $module ) {
2949 $module = self::get_module_slug( $module );
2950 add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
2951 }
2952
2953 /**
2954 * Composes a module configure URL. It uses Jetpack settings search as default value
2955 * It is possible to redefine resulting URL by using "jetpack_module_configuration_url_$module" filter
2956 *
2957 * @param string $module Module slug
2958 * @return string $url module configuration URL
2959 */
2960 public static function module_configuration_url( $module ) {
2961 $module = self::get_module_slug( $module );
2962 $default_url = self::admin_url() . "#/settings?term=$module";
2963 /**
2964 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2965 *
2966 * @since 6.9.0
2967 *
2968 * @param string $default_url Default url, which redirects to jetpack settings page.
2969 */
2970 $url = apply_filters( 'jetpack_module_configuration_url_' . $module, $default_url );
2971
2972 return $url;
2973 }
2974
2975 /* Installation */
2976 public static function bail_on_activation( $message, $deactivate = true ) {
2977 ?>
2978 <!doctype html>
2979 <html>
2980 <head>
2981 <meta charset="<?php bloginfo( 'charset' ); ?>">
2982 <style>
2983 * {
2984 text-align: center;
2985 margin: 0;
2986 padding: 0;
2987 font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
2988 }
2989 p {
2990 margin-top: 1em;
2991 font-size: 18px;
2992 }
2993 </style>
2994 <body>
2995 <p><?php echo esc_html( $message ); ?></p>
2996 </body>
2997 </html>
2998 <?php
2999 if ( $deactivate ) {
3000 $plugins = get_option( 'active_plugins' );
3001 $jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
3002 $update = false;
3003 foreach ( $plugins as $i => $plugin ) {
3004 if ( $plugin === $jetpack ) {
3005 $plugins[ $i ] = false;
3006 $update = true;
3007 }
3008 }
3009
3010 if ( $update ) {
3011 update_option( 'active_plugins', array_filter( $plugins ) );
3012 }
3013 }
3014 exit;
3015 }
3016
3017 /**
3018 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
3019 *
3020 * @static
3021 */
3022 public static function plugin_activation( $network_wide ) {
3023 Jetpack_Options::update_option( 'activated', 1 );
3024
3025 if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
3026 self::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
3027 }
3028
3029 if ( $network_wide ) {
3030 self::state( 'network_nag', true );
3031 }
3032
3033 // For firing one-off events (notices) immediately after activation
3034 set_transient( 'activated_jetpack', true, 0.1 * MINUTE_IN_SECONDS );
3035
3036 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
3037
3038 Health::on_jetpack_activated();
3039
3040 self::plugin_initialize();
3041 }
3042
3043 public static function get_activation_source( $referer_url ) {
3044
3045 if ( defined( 'WP_CLI' ) && WP_CLI ) {
3046 return array( 'wp-cli', null );
3047 }
3048
3049 $referer = wp_parse_url( $referer_url );
3050
3051 $source_type = 'unknown';
3052 $source_query = null;
3053
3054 if ( ! is_array( $referer ) || ! isset( $referer['path'] ) ) {
3055 return array( $source_type, $source_query );
3056 }
3057
3058 $plugins_path = wp_parse_url( admin_url( 'plugins.php' ), PHP_URL_PATH );
3059 $plugins_install_path = wp_parse_url( admin_url( 'plugin-install.php' ), PHP_URL_PATH );// /wp-admin/plugin-install.php
3060
3061 if ( isset( $referer['query'] ) ) {
3062 parse_str( $referer['query'], $query_parts );
3063 } else {
3064 $query_parts = array();
3065 }
3066
3067 if ( $plugins_path === $referer['path'] ) {
3068 $source_type = 'list';
3069 } elseif ( $plugins_install_path === $referer['path'] ) {
3070 $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
3071 switch ( $tab ) {
3072 case 'popular':
3073 $source_type = 'popular';
3074 break;
3075 case 'recommended':
3076 $source_type = 'recommended';
3077 break;
3078 case 'favorites':
3079 $source_type = 'favorites';
3080 break;
3081 case 'search':
3082 $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
3083 $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
3084 break;
3085 default:
3086 $source_type = 'featured';
3087 }
3088 }
3089
3090 return array( $source_type, $source_query );
3091 }
3092
3093 /**
3094 * Runs before bumping version numbers up to a new version
3095 *
3096 * @param string $version Version:timestamp.
3097 * @param string $old_version Old Version:timestamp or false if not set yet.
3098 */
3099 public static function do_version_bump( $version, $old_version ) {
3100 if ( $old_version ) { // For existing Jetpack installations.
3101 add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3102
3103 // If a front end page is visited after the update, the 'wp' action will fire.
3104 add_action( 'wp', 'Jetpack::set_update_modal_display' );
3105
3106 // If an admin page is visited after the update, the 'current_screen' action will fire.
3107 add_action( 'current_screen', 'Jetpack::set_update_modal_display' );
3108 }
3109 }
3110
3111 /**
3112 * Sets the display_update_modal state.
3113 */
3114 public static function set_update_modal_display() {
3115 self::state( 'display_update_modal', true );
3116
3117 }
3118
3119 /**
3120 * Enqueues the block library styles.
3121 *
3122 * @param string $hook The current admin page.
3123 */
3124 public static function enqueue_block_style( $hook ) {
3125 if ( 'toplevel_page_jetpack' === $hook ) {
3126 wp_enqueue_style( 'wp-block-library' );
3127 }
3128 }
3129
3130 /**
3131 * Sets the internal version number and activation state.
3132 *
3133 * @static
3134 */
3135 public static function plugin_initialize() {
3136 if ( ! Jetpack_Options::get_option( 'activated' ) ) {
3137 Jetpack_Options::update_option( 'activated', 2 );
3138 }
3139
3140 if ( ! Jetpack_Options::get_option( 'version' ) ) {
3141 $version = $old_version = JETPACK__VERSION . ':' . time();
3142 /** This action is documented in class.jetpack.php */
3143 do_action( 'updating_jetpack_version', $version, false );
3144 Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
3145 }
3146
3147 self::load_modules();
3148
3149 Jetpack_Options::delete_option( 'do_activate' );
3150 Jetpack_Options::delete_option( 'dismissed_connection_banner' );
3151 }
3152
3153 /**
3154 * Removes all connection options
3155 *
3156 * @static
3157 */
3158 public static function plugin_deactivation() {
3159 require_once ABSPATH . '/wp-admin/includes/plugin.php';
3160 $tracking = new Tracking();
3161 $tracking->record_user_event( 'deactivate_plugin', array() );
3162 if ( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
3163 Jetpack_Network::init()->deactivate();
3164 } else {
3165 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
3166 self::disconnect();
3167 // Jetpack_Heartbeat::init()->deactivate();
3168 }
3169 }
3170
3171 /**
3172 * Set activated option to 4 on jetpack_idc_disconnect action.
3173 */
3174 public static function on_idc_disconnect() {
3175 \Jetpack_Options::update_option( 'activated', 4 );
3176 }
3177
3178 /**
3179 * Disconnects from the Jetpack servers.
3180 * Forgets all connection details and tells the Jetpack servers to do the same.
3181 *
3182 * @static
3183 */
3184 public static function disconnect() {
3185
3186 $connection = self::connection();
3187
3188 // If the site is in an IDC because sync is not allowed,
3189 // let's make sure to not disconnect the production site.
3190 $connection->disconnect_site( ! Identity_Crisis::validate_sync_error_idc_option() );
3191 }
3192
3193 /**
3194 * Happens after a successfull disconnection.
3195 *
3196 * @static
3197 */
3198 public static function jetpack_site_disconnected() {
3199 Identity_Crisis::clear_all_idc_options();
3200
3201 // Delete all the sync related data. Since it could be taking up space.
3202 Sender::get_instance()->uninstall();
3203
3204 /**
3205 * Filters whether the Jetpack activated state should be updated after disconnecting.
3206 *
3207 * @since 10.0.0
3208 *
3209 * @param bool $update_activated_state Whether activated state should be updated after disconnecting, defaults to true.
3210 */
3211 $update_activated_state = apply_filters( 'jetpack_update_activated_state_on_disconnect', true );
3212
3213 if ( $update_activated_state ) {
3214 Jetpack_Options::update_option( 'activated', 4 );
3215 }
3216 }
3217
3218 /**
3219 * Disconnects the user
3220 *
3221 * @param int $user_id The user ID to disconnect.
3222 */
3223 public function disconnect_user( $user_id ) {
3224 $this->connection_manager->disconnect_user( $user_id );
3225 }
3226
3227 /**
3228 * Attempts Jetpack registration. If it fail, a state flag is set: @see ::admin_page_load()
3229 *
3230 * @deprecated since Jetpack 9.7.0
3231 * @see Automattic\Jetpack\Connection\Manager::try_registration()
3232 *
3233 * @return bool|WP_Error
3234 */
3235 public static function try_registration() {
3236 _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Manager::try_registration' );
3237 return static::connection()->try_registration();
3238 }
3239
3240 /**
3241 * Checking the domain names in beta versions.
3242 * If this is a development version, before attempting to connect, let's make sure that the domains are viable.
3243 *
3244 * @param null|\WP_Error $error The domain validation error, or `null` if everything's fine.
3245 *
3246 * @return null|\WP_Error The domain validation error, or `null` if everything's fine.
3247 */
3248 public static function registration_check_domains( $error ) {
3249 if ( static::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
3250 $domains_to_check = array_unique(
3251 array(
3252 'siteurl' => wp_parse_url( get_site_url(), PHP_URL_HOST ),
3253 'homeurl' => wp_parse_url( get_home_url(), PHP_URL_HOST ),
3254 )
3255 );
3256 foreach ( $domains_to_check as $domain ) {
3257 $result = static::connection()->is_usable_domain( $domain );
3258 if ( is_wp_error( $result ) ) {
3259 return $result;
3260 }
3261 }
3262 }
3263
3264 return $error;
3265 }
3266
3267 /**
3268 * Tracking an internal event log. Try not to put too much chaff in here.
3269 *
3270 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
3271 */
3272 public static function log( $code, $data = null ) {
3273 // only grab the latest 200 entries
3274 $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3275
3276 // Append our event to the log
3277 $log_entry = array(
3278 'time' => time(),
3279 'user_id' => get_current_user_id(),
3280 'blog_id' => Jetpack_Options::get_option( 'id' ),
3281 'code' => $code,
3282 );
3283 // Don't bother storing it unless we've got some.
3284 if ( ! is_null( $data ) ) {
3285 $log_entry['data'] = $data;
3286 }
3287 $log[] = $log_entry;
3288
3289 // Try add_option first, to make sure it's not autoloaded.
3290 // @todo: Add an add_option method to Jetpack_Options
3291 if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
3292 Jetpack_Options::update_option( 'log', $log );
3293 }
3294
3295 /**
3296 * Fires when Jetpack logs an internal event.
3297 *
3298 * @since 3.0.0
3299 *
3300 * @param array $log_entry {
3301 * Array of details about the log entry.
3302 *
3303 * @param string time Time of the event.
3304 * @param int user_id ID of the user who trigerred the event.
3305 * @param int blog_id Jetpack Blog ID.
3306 * @param string code Unique name for the event.
3307 * @param string data Data about the event.
3308 * }
3309 */
3310 do_action( 'jetpack_log_entry', $log_entry );
3311 }
3312
3313 /**
3314 * Get the internal event log.
3315 *
3316 * @param $event (string) - only return the specific log events
3317 * @param $num (int) - get specific number of latest results, limited to 200
3318 *
3319 * @return array of log events || WP_Error for invalid params
3320 */
3321 public static function get_log( $event = false, $num = false ) {
3322 if ( $event && ! is_string( $event ) ) {
3323 return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
3324 }
3325
3326 if ( $num && ! is_numeric( $num ) ) {
3327 return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
3328 }
3329
3330 $entire_log = Jetpack_Options::get_option( 'log', array() );
3331
3332 // If nothing set - act as it did before, otherwise let's start customizing the output
3333 if ( ! $num && ! $event ) {
3334 return $entire_log;
3335 } else {
3336 $entire_log = array_reverse( $entire_log );
3337 }
3338
3339 $custom_log_output = array();
3340
3341 if ( $event ) {
3342 foreach ( $entire_log as $log_event ) {
3343 if ( $event == $log_event['code'] ) {
3344 $custom_log_output[] = $log_event;
3345 }
3346 }
3347 } else {
3348 $custom_log_output = $entire_log;
3349 }
3350
3351 if ( $num ) {
3352 $custom_log_output = array_slice( $custom_log_output, 0, $num );
3353 }
3354
3355 return $custom_log_output;
3356 }
3357
3358 /**
3359 * Log modification of important settings.
3360 */
3361 public static function log_settings_change( $option, $old_value, $value ) {
3362 switch ( $option ) {
3363 case 'jetpack_sync_non_public_post_stati':
3364 self::log( $option, $value );
3365 break;
3366 }
3367 }
3368
3369 /**
3370 * Return stat data for WPCOM sync
3371 */
3372 public static function get_stat_data( $encode = true, $extended = true ) {
3373 $data = Jetpack_Heartbeat::generate_stats_array();
3374
3375 if ( $extended ) {
3376 $additional_data = self::get_additional_stat_data();
3377 $data = array_merge( $data, $additional_data );
3378 }
3379
3380 if ( $encode ) {
3381 return json_encode( $data );
3382 }
3383
3384 return $data;
3385 }
3386
3387 /**
3388 * Get additional stat data to sync to WPCOM
3389 */
3390 public static function get_additional_stat_data( $prefix = '' ) {
3391 $return[ "{$prefix}themes" ] = self::get_parsed_theme_data();
3392 $return[ "{$prefix}plugins-extra" ] = self::get_parsed_plugin_data();
3393 $return[ "{$prefix}users" ] = (int) self::get_site_user_count();
3394 $return[ "{$prefix}site-count" ] = 0;
3395
3396 if ( function_exists( 'get_blog_count' ) ) {
3397 $return[ "{$prefix}site-count" ] = get_blog_count();
3398 }
3399 return $return;
3400 }
3401
3402 private static function get_site_user_count() {
3403 global $wpdb;
3404
3405 if ( function_exists( 'wp_is_large_network' ) ) {
3406 if ( wp_is_large_network( 'users' ) ) {
3407 return -1; // Not a real value but should tell us that we are dealing with a large network.
3408 }
3409 }
3410 if ( false === ( $user_count = get_transient( 'jetpack_site_user_count' ) ) ) {
3411 // It wasn't there, so regenerate the data and save the transient
3412 $user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities'" );
3413 set_transient( 'jetpack_site_user_count', $user_count, DAY_IN_SECONDS );
3414 }
3415 return $user_count;
3416 }
3417
3418 /* Admin Pages */
3419
3420 function admin_init() {
3421 // If the plugin is not connected, display a connect message.
3422 if (
3423 // the plugin was auto-activated and needs its candy
3424 Jetpack_Options::get_option_and_ensure_autoload( 'do_activate', '0' )
3425 ||
3426 // the plugin is active, but was never activated. Probably came from a site-wide network activation
3427 ! Jetpack_Options::get_option( 'activated' )
3428 ) {
3429 self::plugin_initialize();
3430 }
3431
3432 $is_offline_mode = ( new Status() )->is_offline_mode();
3433 $fallback_no_verify_ssl_certs = Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' );
3434 /** Already documented in automattic/jetpack-connection::src/class-client.php */
3435 $client_verify_ssl_certs = apply_filters( 'jetpack_client_verify_ssl_certs', false );
3436
3437 if ( ! $is_offline_mode ) {
3438 Jetpack_Connection_Banner::init();
3439 }
3440
3441 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3442 // Upgrade: 1.1 -> 1.1.1
3443 // Check and see if host can verify the Jetpack servers' SSL certificate
3444 $args = array();
3445 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3446 }
3447
3448 Jetpack_Recommendations_Banner::init();
3449
3450 if ( current_user_can( 'manage_options' ) && ! self::permit_ssl() ) {
3451 add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3452 }
3453
3454 add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3455 add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3456 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3457
3458 if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3459 add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3460 }
3461
3462 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3463
3464 if ( self::is_connection_ready() || $is_offline_mode ) {
3465 // Artificially throw errors in certain specific cases during plugin activation.
3466 add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3467 }
3468
3469 // Add custom column in wp-admin/users.php to show whether user is linked.
3470 add_filter( 'manage_users_columns', array( $this, 'jetpack_icon_user_connected' ) );
3471 add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_user_connected_icon' ), 10, 3 );
3472 add_action( 'admin_print_styles', array( $this, 'jetpack_user_col_style' ) );
3473 }
3474
3475 function admin_body_class( $admin_body_class = '' ) {
3476 $classes = explode( ' ', trim( $admin_body_class ) );
3477
3478 $classes[] = self::is_connection_ready() ? 'jetpack-connected' : 'jetpack-disconnected';
3479
3480 $admin_body_class = implode( ' ', array_unique( $classes ) );
3481 return " $admin_body_class ";
3482 }
3483
3484 static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3485 return $admin_body_class . ' jetpack-pagestyles ';
3486 }
3487
3488 /**
3489 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3490 * This function artificially throws errors for such cases (per a specific list).
3491 *
3492 * @param string $plugin The activated plugin.
3493 */
3494 function throw_error_on_activate_plugin( $plugin ) {
3495 $active_modules = self::get_active_modules();
3496
3497 // The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3498 if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3499 $throw = false;
3500
3501 // Try and make sure it really was the stats plugin
3502 if ( ! class_exists( 'ReflectionFunction' ) ) {
3503 if ( 'stats.php' == basename( $plugin ) ) {
3504 $throw = true;
3505 }
3506 } else {
3507 $reflection = new ReflectionFunction( 'stats_get_api_key' );
3508 if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3509 $throw = true;
3510 }
3511 }
3512
3513 if ( $throw ) {
3514 trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3515 }
3516 }
3517 }
3518
3519 function intercept_plugin_error_scrape_init() {
3520 add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3521 }
3522
3523 function intercept_plugin_error_scrape( $action, $result ) {
3524 if ( ! $result ) {
3525 return;
3526 }
3527
3528 foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3529 if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3530 self::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3531 }
3532 }
3533 }
3534
3535 /**
3536 * Register the remote file upload request handlers, if needed.
3537 *
3538 * @access public
3539 */
3540 public function add_remote_request_handlers() {
3541 // Remote file uploads are allowed only via AJAX requests.
3542 if ( ! is_admin() || ! Constants::get_constant( 'DOING_AJAX' ) ) {
3543 return;
3544 }
3545
3546 // Remote file uploads are allowed only for a set of specific AJAX actions.
3547 $remote_request_actions = array(
3548 'jetpack_upload_file',
3549 'jetpack_update_file',
3550 );
3551
3552 // phpcs:ignore WordPress.Security.NonceVerification
3553 if ( ! isset( $_POST['action'] ) || ! in_array( $_POST['action'], $remote_request_actions, true ) ) {
3554 return;
3555 }
3556
3557 // Require Jetpack authentication for the remote file upload AJAX requests.
3558 if ( ! $this->connection_manager ) {
3559 $this->connection_manager = new Connection_Manager();
3560 }
3561
3562 $this->connection_manager->require_jetpack_authentication();
3563
3564 // Register the remote file upload AJAX handlers.
3565 foreach ( $remote_request_actions as $action ) {
3566 add_action( "wp_ajax_nopriv_{$action}", array( $this, 'remote_request_handlers' ) );
3567 }
3568 }
3569
3570 /**
3571 * Handler for Jetpack remote file uploads.
3572 *
3573 * @access public
3574 */
3575 public function remote_request_handlers() {
3576 $action = current_filter();
3577
3578 switch ( current_filter() ) {
3579 case 'wp_ajax_nopriv_jetpack_upload_file':
3580 $response = $this->upload_handler();
3581 break;
3582
3583 case 'wp_ajax_nopriv_jetpack_update_file':
3584 $response = $this->upload_handler( true );
3585 break;
3586 default:
3587 $response = new WP_Error( 'unknown_handler', 'Unknown Handler', 400 );
3588 break;
3589 }
3590
3591 if ( ! $response ) {
3592 $response = new WP_Error( 'unknown_error', 'Unknown Error', 400 );
3593 }
3594
3595 if ( is_wp_error( $response ) ) {
3596 $status_code = $response->get_error_data();
3597 $error = $response->get_error_code();
3598 $error_description = $response->get_error_message();
3599
3600 if ( ! is_int( $status_code ) ) {
3601 $status_code = 400;
3602 }
3603
3604 status_header( $status_code );
3605 die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3606 }
3607
3608 status_header( 200 );
3609 if ( true === $response ) {
3610 exit;
3611 }
3612
3613 die( json_encode( (object) $response ) );
3614 }
3615
3616 /**
3617 * Uploads a file gotten from the global $_FILES.
3618 * If `$update_media_item` is true and `post_id` is defined
3619 * the attachment file of the media item (gotten through of the post_id)
3620 * will be updated instead of add a new one.
3621 *
3622 * @param boolean $update_media_item - update media attachment
3623 * @return array - An array describing the uploadind files process
3624 */
3625 function upload_handler( $update_media_item = false ) {
3626 if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3627 return new WP_Error( 405, get_status_header_desc( 405 ), 405 );
3628 }
3629
3630 $user = wp_authenticate( '', '' );
3631 if ( ! $user || is_wp_error( $user ) ) {
3632 return new WP_Error( 403, get_status_header_desc( 403 ), 403 );
3633 }
3634
3635 wp_set_current_user( $user->ID );
3636
3637 if ( ! current_user_can( 'upload_files' ) ) {
3638 return new WP_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3639 }
3640
3641 if ( empty( $_FILES ) ) {
3642 return new WP_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3643 }
3644
3645 foreach ( array_keys( $_FILES ) as $files_key ) {
3646 if ( ! isset( $_POST[ "_jetpack_file_hmac_{$files_key}" ] ) ) {
3647 return new WP_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3648 }
3649 }
3650
3651 $media_keys = array_keys( $_FILES['media'] );
3652
3653 $token = ( new Tokens() )->get_access_token( get_current_user_id() );
3654 if ( ! $token || is_wp_error( $token ) ) {
3655 return new WP_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3656 }
3657
3658 $uploaded_files = array();
3659 $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3660 unset( $GLOBALS['post'] );
3661 foreach ( $_FILES['media']['name'] as $index => $name ) {
3662 $file = array();
3663 foreach ( $media_keys as $media_key ) {
3664 $file[ $media_key ] = $_FILES['media'][ $media_key ][ $index ];
3665 }
3666
3667 list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][ $index ] );
3668
3669 $hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3670 if ( $hmac_provided !== $hmac_file ) {
3671 $uploaded_files[ $index ] = (object) array(
3672 'error' => 'invalid_hmac',
3673 'error_description' => 'The corresponding HMAC for this file does not match',
3674 );
3675 continue;
3676 }
3677
3678 $_FILES['.jetpack.upload.'] = $file;
3679 $post_id = isset( $_POST['post_id'][ $index ] ) ? absint( $_POST['post_id'][ $index ] ) : 0;
3680 if ( ! current_user_can( 'edit_post', $post_id ) ) {
3681 $post_id = 0;
3682 }
3683
3684 if ( $update_media_item ) {
3685 if ( ! isset( $post_id ) || $post_id === 0 ) {
3686 return new WP_Error( 'invalid_input', 'Media ID must be defined.', 400 );
3687 }
3688
3689 $media_array = $_FILES['media'];
3690
3691 $file_array['name'] = $media_array['name'][0];
3692 $file_array['type'] = $media_array['type'][0];
3693 $file_array['tmp_name'] = $media_array['tmp_name'][0];
3694 $file_array['error'] = $media_array['error'][0];
3695 $file_array['size'] = $media_array['size'][0];
3696
3697 $edited_media_item = Jetpack_Media::edit_media_file( $post_id, $file_array );
3698
3699 if ( is_wp_error( $edited_media_item ) ) {
3700 return $edited_media_item;
3701 }
3702
3703 $response = (object) array(
3704 'id' => (string) $post_id,
3705 'file' => (string) $edited_media_item->post_title,
3706 'url' => (string) wp_get_attachment_url( $post_id ),
3707 'type' => (string) $edited_media_item->post_mime_type,
3708 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3709 );
3710
3711 return (array) array( $response );
3712 }
3713
3714 $attachment_id = media_handle_upload(
3715 '.jetpack.upload.',
3716 $post_id,
3717 array(),
3718 array(
3719 'action' => 'jetpack_upload_file',
3720 )
3721 );
3722
3723 if ( ! $attachment_id ) {
3724 $uploaded_files[ $index ] = (object) array(
3725 'error' => 'unknown',
3726 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site',
3727 );
3728 } elseif ( is_wp_error( $attachment_id ) ) {
3729 $uploaded_files[ $index ] = (object) array(
3730 'error' => 'attachment_' . $attachment_id->get_error_code(),
3731 'error_description' => $attachment_id->get_error_message(),
3732 );
3733 } else {
3734 $attachment = get_post( $attachment_id );
3735 $uploaded_files[ $index ] = (object) array(
3736 'id' => (string) $attachment_id,
3737 'file' => $attachment->post_title,
3738 'url' => wp_get_attachment_url( $attachment_id ),
3739 'type' => $attachment->post_mime_type,
3740 'meta' => wp_get_attachment_metadata( $attachment_id ),
3741 );
3742 }
3743 }
3744 if ( ! is_null( $global_post ) ) {
3745 $GLOBALS['post'] = $global_post;
3746 }
3747
3748 return $uploaded_files;
3749 }
3750
3751 /**
3752 * Add help to the Jetpack page
3753 *
3754 * @since Jetpack (1.2.3)
3755 * @return false if not the Jetpack page
3756 */
3757 function admin_help() {
3758 $current_screen = get_current_screen();
3759
3760 // Overview
3761 $current_screen->add_help_tab(
3762 array(
3763 'id' => 'home',
3764 'title' => __( 'Home', 'jetpack' ),
3765 'content' =>
3766 '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3767 '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3768 '<p>' . __( 'On this page, you are able to view the modules available within Jetpack, learn more about them, and activate or deactivate them as needed.', 'jetpack' ) . '</p>',
3769 )
3770 );
3771
3772 // Screen Content
3773 if ( current_user_can( 'manage_options' ) ) {
3774 $current_screen->add_help_tab(
3775 array(
3776 'id' => 'settings',
3777 'title' => __( 'Settings', 'jetpack' ),
3778 'content' =>
3779 '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3780 '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3781 '<ol>' .
3782 '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3783 '<li>' . __( 'Using the checkboxes next to each module, you can select multiple modules to toggle via the Bulk Actions menu at the top of the list.', 'jetpack' ) . '</li>' .
3784 '</ol>' .
3785 '<p>' . __( 'Using the tools on the right, you can search for specific modules, filter by module categories or which are active, or change the sorting order.', 'jetpack' ) . '</p>',
3786 )
3787 );
3788 }
3789
3790 // Help Sidebar
3791 $support_url = Redirect::get_url( 'jetpack-support' );
3792 $faq_url = Redirect::get_url( 'jetpack-faq' );
3793 $current_screen->set_help_sidebar(
3794 '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3795 '<p><a href="' . $faq_url . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3796 '<p><a href="' . $support_url . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3797 '<p><a href="' . self::admin_url( array( 'page' => 'jetpack-debugger' ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3798 );
3799 }
3800
3801 function admin_menu_css() {
3802 wp_enqueue_style( 'jetpack-icons' );
3803 }
3804
3805 function admin_menu_order() {
3806 return true;
3807 }
3808
3809 function jetpack_menu_order( $menu_order ) {
3810 $jp_menu_order = array();
3811
3812 foreach ( $menu_order as $index => $item ) {
3813 if ( $item != 'jetpack' ) {
3814 $jp_menu_order[] = $item;
3815 }
3816
3817 if ( $index == 0 ) {
3818 $jp_menu_order[] = 'jetpack';
3819 }
3820 }
3821
3822 return $jp_menu_order;
3823 }
3824
3825 function admin_banner_styles() {
3826 $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3827
3828 if ( ! wp_style_is( 'jetpack-dops-style' ) ) {
3829 wp_register_style(
3830 'jetpack-dops-style',
3831 plugins_url( '_inc/build/admin.css', JETPACK__PLUGIN_FILE ),
3832 array(),
3833 JETPACK__VERSION
3834 );
3835 }
3836
3837 wp_enqueue_style(
3838 'jetpack',
3839 plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ),
3840 array( 'jetpack-dops-style' ),
3841 JETPACK__VERSION . '-20121016'
3842 );
3843 wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3844 wp_style_add_data( 'jetpack', 'suffix', $min );
3845 }
3846
3847 function plugin_action_links( $actions ) {
3848
3849 $jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', self::admin_url( 'page=jetpack' ), __( 'My Jetpack', 'jetpack' ) ) );
3850
3851 if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3852 return array_merge(
3853 $jetpack_home,
3854 array( 'settings' => sprintf( '<a href="%s">%s</a>', self::admin_url( 'page=jetpack#/settings' ), __( 'Settings', 'jetpack' ) ) ),
3855 array( 'support' => sprintf( '<a href="%s">%s</a>', self::admin_url( 'page=jetpack-debugger ' ), __( 'Support', 'jetpack' ) ) ),
3856 $actions
3857 );
3858 }
3859
3860 return array_merge( $jetpack_home, $actions );
3861 }
3862
3863 /**
3864 * Adds the deactivation warning modal if there are other active plugins using the connection
3865 *
3866 * @param string $hook The current admin page.
3867 *
3868 * @return void
3869 */
3870 public function deactivate_dialog( $hook ) {
3871 if (
3872 'plugins.php' === $hook
3873 && self::is_connection_ready()
3874 ) {
3875
3876 $active_plugins_using_connection = Connection_Plugin_Storage::get_all();
3877
3878 if ( count( $active_plugins_using_connection ) > 1 ) {
3879
3880 add_thickbox();
3881
3882 // Register jp-tracks-functions dependency.
3883 Tracking::register_tracks_functions_scripts();
3884
3885 wp_enqueue_script(
3886 'jetpack-deactivate-dialog-js',
3887 Assets::get_file_url_for_environment(
3888 '_inc/build/jetpack-deactivate-dialog.min.js',
3889 '_inc/jetpack-deactivate-dialog.js'
3890 ),
3891 array( 'jquery', 'jp-tracks-functions' ),
3892 JETPACK__VERSION,
3893 true
3894 );
3895
3896 wp_localize_script(
3897 'jetpack-deactivate-dialog-js',
3898 'deactivate_dialog',
3899 array(
3900 'title' => __( 'Deactivate Jetpack', 'jetpack' ),
3901 'deactivate_label' => __( 'Disconnect and Deactivate', 'jetpack' ),
3902 'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
3903 )
3904 );
3905
3906 add_action( 'admin_footer', array( $this, 'deactivate_dialog_content' ) );
3907
3908 wp_enqueue_style( 'jetpack-deactivate-dialog', plugins_url( 'css/jetpack-deactivate-dialog.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
3909 }
3910 }
3911 }
3912
3913 /**
3914 * Outputs the content of the deactivation modal
3915 *
3916 * @return void
3917 */
3918 public function deactivate_dialog_content() {
3919 $active_plugins_using_connection = Connection_Plugin_Storage::get_all();
3920 unset( $active_plugins_using_connection['jetpack'] );
3921 $this->load_view( 'admin/deactivation-dialog.php', $active_plugins_using_connection );
3922 }
3923
3924 /**
3925 * Filters the login URL to include the registration flow in case the user isn't logged in.
3926 *
3927 * @param string $login_url The wp-login URL.
3928 * @param string $redirect URL to redirect users after logging in.
3929 * @since Jetpack 8.4
3930 * @return string
3931 */
3932 public function login_url( $login_url, $redirect ) {
3933 parse_str( wp_parse_url( $redirect, PHP_URL_QUERY ), $redirect_parts );
3934 if ( ! empty( $redirect_parts[ self::$jetpack_redirect_login ] ) ) {
3935 $login_url = add_query_arg( self::$jetpack_redirect_login, 'true', $login_url );
3936 }
3937 return $login_url;
3938 }
3939
3940 /**
3941 * Redirects non-authenticated users to authenticate with Calypso if redirect flag is set.
3942 *
3943 * @since Jetpack 8.4
3944 */
3945 public function login_init() {
3946 // phpcs:ignore WordPress.Security.NonceVerification
3947 if ( ! empty( $_GET[ self::$jetpack_redirect_login ] ) ) {
3948 add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_environments' ) );
3949 wp_safe_redirect(
3950 add_query_arg(
3951 array(
3952 'forceInstall' => 1,
3953 'url' => rawurlencode( get_site_url() ),
3954 ),
3955 // @todo provide way to go to specific calypso env.
3956 self::get_calypso_host() . 'jetpack/connect'
3957 )
3958 );
3959 exit;
3960 }
3961 }
3962
3963 /*
3964 * Registration flow:
3965 * 1 - ::admin_page_load() action=register
3966 * 2 - ::try_registration()
3967 * 3 - ::register()
3968 * - Creates jetpack_register option containing two secrets and a timestamp
3969 * - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3970 * siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3971 * - That request to jetpack.wordpress.com does not immediately respond. It first makes a request BACK to this site's
3972 * xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
3973 * - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
3974 * - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
3975 * jetpack_id, jetpack_secret, jetpack_public
3976 * - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
3977 * 4 - redirect to https://wordpress.com/start/jetpack-connect
3978 * 5 - user logs in with WP.com account
3979 * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
3980 * - Manager::authorize()
3981 * - Manager::get_token()
3982 * - GET https://jetpack.wordpress.com/jetpack.token/1/ with
3983 * client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
3984 * - which responds with access_token, token_type, scope
3985 * - Manager::authorize() stores jetpack_options: user_token => access_token.$user_id
3986 * - Jetpack::activate_default_modules()
3987 * - Deactivates deprecated plugins
3988 * - Activates all default modules
3989 * - Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
3990 * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
3991 * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
3992 * Done!
3993 */
3994
3995 /**
3996 * Handles the page load events for the Jetpack admin page
3997 */
3998 function admin_page_load() {
3999 $error = false;
4000
4001 // Make sure we have the right body class to hook stylings for subpages off of.
4002 add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ), 20 );
4003
4004 if ( ! empty( $_GET['jetpack_restate'] ) ) {
4005 // Should only be used in intermediate redirects to preserve state across redirects
4006 self::restate();
4007 }
4008
4009 if ( isset( $_GET['connect_url_redirect'] ) ) {
4010 // @todo: Add validation against a known allowed list.
4011 $from = ! empty( $_GET['from'] ) ? $_GET['from'] : 'iframe';
4012 // User clicked in the iframe to link their accounts
4013 if ( ! self::connection()->is_user_connected() ) {
4014 $redirect = ! empty( $_GET['redirect_after_auth'] ) ? $_GET['redirect_after_auth'] : false;
4015
4016 add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_environments' ) );
4017 $connect_url = $this->build_connect_url( true, $redirect, $from );
4018 remove_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_environments' ) );
4019
4020 if ( isset( $_GET['notes_iframe'] ) ) {
4021 $connect_url .= '&notes_iframe';
4022 }
4023 wp_redirect( $connect_url );
4024 exit;
4025 } else {
4026 if ( ! isset( $_GET['calypso_env'] ) ) {
4027 self::state( 'message', 'already_authorized' );
4028 wp_safe_redirect( self::admin_url() );
4029 exit;
4030 } else {
4031 $connect_url = $this->build_connect_url( true, false, $from );
4032 $connect_url .= '&already_authorized=true';
4033 wp_redirect( $connect_url );
4034 exit;
4035 }
4036 }
4037 }
4038
4039 if ( isset( $_GET['action'] ) ) {
4040 switch ( $_GET['action'] ) {
4041 case 'authorize_redirect':
4042 self::log( 'authorize_redirect' );
4043
4044 add_filter(
4045 'allowed_redirect_hosts',
4046 function ( $domains ) {
4047 $domains[] = 'jetpack.com';
4048 $domains[] = 'jetpack.wordpress.com';
4049 $domains[] = 'wordpress.com';
4050 $domains[] = wp_parse_url( static::get_calypso_host(), PHP_URL_HOST ); // May differ from `wordpress.com`.
4051 return array_unique( $domains );
4052 }
4053 );
4054
4055 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
4056 $dest_url = empty( $_GET['dest_url'] ) ? null : $_GET['dest_url'];
4057
4058 if ( ! $dest_url || ( 0 === stripos( $dest_url, 'https://jetpack.com/' ) && 0 === stripos( $dest_url, 'https://wordpress.com/' ) ) ) {
4059 // The destination URL is missing or invalid, nothing to do here.
4060 exit;
4061 }
4062
4063 if ( static::connection()->is_connected() && static::connection()->is_user_connected() ) {
4064 // The user is either already connected, or finished the connection process.
4065 wp_safe_redirect( $dest_url );
4066 exit;
4067 } elseif ( ! empty( $_GET['done'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
4068 // The user decided not to proceed with setting up the connection.
4069 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4070 exit;
4071 }
4072
4073 $redirect_args = array(
4074 'page' => 'jetpack',
4075 'action' => 'authorize_redirect',
4076 'dest_url' => rawurlencode( $dest_url ),
4077 'done' => '1',
4078 );
4079
4080 if ( ! empty( $_GET['from'] ) && 'jetpack_site_only_checkout' === $_GET['from'] ) {
4081 $redirect_args['from'] = 'jetpack_site_only_checkout';
4082 }
4083
4084 wp_safe_redirect( static::build_authorize_url( self::admin_url( $redirect_args ) ) );
4085 exit;
4086 case 'authorize':
4087 _doing_it_wrong( __METHOD__, 'The `page=jetpack&action=authorize` webhook is deprecated. Use `handler=jetpack-connection-webhooks&action=authorize` instead', 'Jetpack 9.5.0' );
4088 ( new Connection_Webhooks( $this->connection_manager ) )->handle_authorize();
4089 exit;
4090 case 'register':
4091 if ( ! current_user_can( 'jetpack_connect' ) ) {
4092 $error = 'cheatin';
4093 break;
4094 }
4095 check_admin_referer( 'jetpack-register' );
4096 self::log( 'register' );
4097 self::maybe_set_version_option();
4098 $from = isset( $_GET['from'] ) ? $_GET['from'] : false;
4099 if ( $from ) {
4100 static::connection()->add_register_request_param( 'from', (string) $from );
4101 }
4102 $registered = static::connection()->try_registration();
4103 if ( is_wp_error( $registered ) ) {
4104 $error = $registered->get_error_code();
4105 self::state( 'error', $error );
4106 self::state( 'error', $registered->get_error_message() );
4107
4108 /**
4109 * Jetpack registration Error.
4110 *
4111 * @since 7.5.0
4112 *
4113 * @param string|int $error The error code.
4114 * @param \WP_Error $registered The error object.
4115 */
4116 do_action( 'jetpack_connection_register_fail', $error, $registered );
4117 break;
4118 }
4119
4120 $redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : false;
4121
4122 /**
4123 * Jetpack registration Success.
4124 *
4125 * @since 7.5.0
4126 *
4127 * @param string $from 'from' GET parameter;
4128 */
4129 do_action( 'jetpack_connection_register_success', $from );
4130
4131 $url = $this->build_connect_url( true, $redirect, $from );
4132
4133 if ( ! empty( $_GET['onboarding'] ) ) {
4134 $url = add_query_arg( 'onboarding', $_GET['onboarding'], $url );
4135 }
4136
4137 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
4138 $url = add_query_arg( 'auth_approved', 'true', $url );
4139 }
4140
4141 wp_redirect( $url );
4142 exit;
4143 case 'activate':
4144 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
4145 $error = 'cheatin';
4146 break;
4147 }
4148
4149 $module = stripslashes( $_GET['module'] );
4150 check_admin_referer( "jetpack_activate-$module" );
4151 self::log( 'activate', $module );
4152 if ( ! self::activate_module( $module ) ) {
4153 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
4154 }
4155 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
4156 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4157 exit;
4158 case 'activate_default_modules':
4159 check_admin_referer( 'activate_default_modules' );
4160 self::log( 'activate_default_modules' );
4161 self::restate();
4162 $min_version = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
4163 $max_version = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
4164 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
4165 self::activate_default_modules( $min_version, $max_version, $other_modules );
4166 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4167 exit;
4168 case 'disconnect':
4169 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
4170 $error = 'cheatin';
4171 break;
4172 }
4173
4174 check_admin_referer( 'jetpack-disconnect' );
4175 self::log( 'disconnect' );
4176 self::disconnect();
4177 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
4178 exit;
4179 case 'reconnect':
4180 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
4181 $error = 'cheatin';
4182 break;
4183 }
4184
4185 check_admin_referer( 'jetpack-reconnect' );
4186 self::log( 'reconnect' );
4187 self::disconnect();
4188 wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
4189 exit;
4190 case 'deactivate':
4191 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
4192 $error = 'cheatin';
4193 break;
4194 }
4195
4196 $modules = stripslashes( $_GET['module'] );
4197 check_admin_referer( "jetpack_deactivate-$modules" );
4198 foreach ( explode( ',', $modules ) as $module ) {
4199 self::log( 'deactivate', $module );
4200 self::deactivate_module( $module );
4201 self::state( 'message', 'module_deactivated' );
4202 }
4203 self::state( 'module', $modules );
4204 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4205 exit;
4206 case 'unlink':
4207 $redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
4208 check_admin_referer( 'jetpack-unlink' );
4209 self::log( 'unlink' );
4210 $this->connection_manager->disconnect_user();
4211 self::state( 'message', 'unlinked' );
4212 if ( 'sub-unlink' == $redirect ) {
4213 wp_safe_redirect( admin_url() );
4214 } else {
4215 wp_safe_redirect( self::admin_url( array( 'page' => $redirect ) ) );
4216 }
4217 exit;
4218 case 'onboard':
4219 if ( ! current_user_can( 'manage_options' ) ) {
4220 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4221 } else {
4222 self::create_onboarding_token();
4223 $url = $this->build_connect_url( true );
4224
4225 if ( false !== ( $token = Jetpack_Options::get_option( 'onboarding' ) ) ) {
4226 $url = add_query_arg( 'onboarding', $token, $url );
4227 }
4228
4229 $calypso_env = $this->get_calypso_env();
4230 if ( ! empty( $calypso_env ) ) {
4231 $url = add_query_arg( 'calypso_env', $calypso_env, $url );
4232 }
4233
4234 wp_redirect( $url );
4235 exit;
4236 }
4237 exit;
4238 default:
4239 /**
4240 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
4241 *
4242 * @since 2.6.0
4243 *
4244 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
4245 */
4246 do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
4247 }
4248 }
4249
4250 if ( ! $error = $error ? $error : self::state( 'error' ) ) {
4251 self::activate_new_modules( true );
4252 }
4253
4254 $message_code = self::state( 'message' );
4255 if ( self::state( 'optin-manage' ) ) {
4256 $activated_manage = $message_code;
4257 $message_code = 'jetpack-manage';
4258 }
4259
4260 switch ( $message_code ) {
4261 case 'jetpack-manage':
4262 $sites_url = esc_url( Redirect::get_url( 'calypso-sites' ) );
4263 // translators: %s is the URL to the "Sites" panel on wordpress.com.
4264 $this->message = '<strong>' . sprintf( __( 'You are all set! Your site can now be managed from <a href="%s" target="_blank">wordpress.com/sites</a>.', 'jetpack' ), $sites_url ) . '</strong>';
4265 if ( $activated_manage ) {
4266 $this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack' ) . '</strong>';
4267 }
4268 break;
4269
4270 }
4271
4272 $deactivated_plugins = self::state( 'deactivated_plugins' );
4273
4274 if ( ! empty( $deactivated_plugins ) ) {
4275 $deactivated_plugins = explode( ',', $deactivated_plugins );
4276 $deactivated_titles = array();
4277 foreach ( $deactivated_plugins as $deactivated_plugin ) {
4278 if ( ! isset( $this->plugins_to_deactivate[ $deactivated_plugin ] ) ) {
4279 continue;
4280 }
4281
4282 $deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[ $deactivated_plugin ][1] ) . '</strong>';
4283 }
4284
4285 if ( $deactivated_titles ) {
4286 if ( $this->message ) {
4287 $this->message .= "<br /><br />\n";
4288 }
4289
4290 $this->message .= wp_sprintf(
4291 _n(
4292 'Jetpack contains the most recent version of the old %l plugin.',
4293 'Jetpack contains the most recent versions of the old %l plugins.',
4294 count( $deactivated_titles ),
4295 'jetpack'
4296 ),
4297 $deactivated_titles
4298 );
4299
4300 $this->message .= "<br />\n";
4301
4302 $this->message .= _n(
4303 'The old version has been deactivated and can be removed from your site.',
4304 'The old versions have been deactivated and can be removed from your site.',
4305 count( $deactivated_titles ),
4306 'jetpack'
4307 );
4308 }
4309 }
4310
4311 $this->privacy_checks = self::state( 'privacy_checks' );
4312
4313 if ( $this->message || $this->error || $this->privacy_checks ) {
4314 add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
4315 }
4316
4317 add_filter( 'jetpack_short_module_description', 'wptexturize' );
4318 }
4319
4320 function admin_notices() {
4321
4322 if ( $this->error ) {
4323 ?>
4324 <div id="message" class="jetpack-message jetpack-err">
4325 <div class="squeezer">
4326 <h2>
4327 <?php
4328 echo wp_kses(
4329 $this->error,
4330 array(
4331 'a' => array( 'href' => array() ),
4332 'small' => true,
4333 'code' => true,
4334 'strong' => true,
4335 'br' => true,
4336 'b' => true,
4337 )
4338 );
4339 ?>
4340 </h2>
4341 <?php if ( $desc = self::state( 'error_description' ) ) : ?>
4342 <p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
4343 <?php endif; ?>
4344 </div>
4345 </div>
4346 <?php
4347 }
4348
4349 if ( $this->message ) {
4350 ?>
4351 <div id="message" class="jetpack-message">
4352 <div class="squeezer">
4353 <h2>
4354 <?php
4355 echo wp_kses(
4356 $this->message,
4357 array(
4358 'strong' => array(),
4359 'a' => array( 'href' => true ),
4360 'br' => true,
4361 )
4362 );
4363 ?>
4364 </h2>
4365 </div>
4366 </div>
4367 <?php
4368 }
4369
4370 if ( $this->privacy_checks ) :
4371 $module_names = $module_slugs = array();
4372
4373 $privacy_checks = explode( ',', $this->privacy_checks );
4374 $privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
4375 foreach ( $privacy_checks as $module_slug ) {
4376 $module = self::get_module( $module_slug );
4377 if ( ! $module ) {
4378 continue;
4379 }
4380
4381 $module_slugs[] = $module_slug;
4382 $module_names[] = "<strong>{$module['name']}</strong>";
4383 }
4384
4385 $module_slugs = join( ',', $module_slugs );
4386 ?>
4387 <div id="message" class="jetpack-message jetpack-err">
4388 <div class="squeezer">
4389 <h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
4390 <p>
4391 <?php
4392 echo wp_kses(
4393 wptexturize(
4394 wp_sprintf(
4395 _nx(
4396 "Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
4397 "Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
4398 count( $privacy_checks ),
4399 '%l = list of Jetpack module/feature names',
4400 'jetpack'
4401 ),
4402 $module_names
4403 )
4404 ),
4405 array( 'strong' => true )
4406 );
4407
4408 echo "\n<br />\n";
4409
4410 echo wp_kses(
4411 sprintf(
4412 _nx(
4413 'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
4414 'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
4415 count( $privacy_checks ),
4416 '%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
4417 'jetpack'
4418 ),
4419 wp_nonce_url(
4420 self::admin_url(
4421 array(
4422 'page' => 'jetpack',
4423 'action' => 'deactivate',
4424 'module' => urlencode( $module_slugs ),
4425 )
4426 ),
4427 "jetpack_deactivate-$module_slugs"
4428 ),
4429 esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
4430 ),
4431 array(
4432 'a' => array(
4433 'href' => true,
4434 'title' => true,
4435 ),
4436 )
4437 );
4438 ?>
4439 </p>
4440 </div>
4441 </div>
4442 <?php
4443 endif;
4444 }
4445
4446 /**
4447 * We can't always respond to a signed XML-RPC request with a
4448 * helpful error message. In some circumstances, doing so could
4449 * leak information.
4450 *
4451 * Instead, track that the error occurred via a Jetpack_Option,
4452 * and send that data back in the heartbeat.
4453 * All this does is increment a number, but it's enough to find
4454 * trends.
4455 *
4456 * @param WP_Error $xmlrpc_error The error produced during
4457 * signature validation.
4458 */
4459 function track_xmlrpc_error( $xmlrpc_error ) {
4460 $code = is_wp_error( $xmlrpc_error )
4461 ? $xmlrpc_error->get_error_code()
4462 : 'should-not-happen';
4463
4464 $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4465 if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4466 // No need to update the option if we already have
4467 // this code stored.
4468 return;
4469 }
4470 $xmlrpc_errors[ $code ] = true;
4471
4472 Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4473 }
4474
4475 /**
4476 * Initialize the jetpack stats instance only when needed
4477 *
4478 * @return void
4479 */
4480 private function initialize_stats() {
4481 if ( is_null( $this->a8c_mc_stats_instance ) ) {
4482 $this->a8c_mc_stats_instance = new Automattic\Jetpack\A8c_Mc_Stats();
4483 }
4484 }
4485
4486 /**
4487 * Record a stat for later output. This will only currently output in the admin_footer.
4488 */
4489 function stat( $group, $detail ) {
4490 $this->initialize_stats();
4491 $this->a8c_mc_stats_instance->add( $group, $detail );
4492
4493 // Keep a local copy for backward compatibility (there are some direct checks on this).
4494 $this->stats = $this->a8c_mc_stats_instance->get_current_stats();
4495 }
4496
4497 /**
4498 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
4499 */
4500 function do_stats( $method = '' ) {
4501 $this->initialize_stats();
4502 if ( 'server_side' === $method ) {
4503 $this->a8c_mc_stats_instance->do_server_side_stats();
4504 } else {
4505 $this->a8c_mc_stats_instance->do_stats();
4506 }
4507
4508 // Keep a local copy for backward compatibility (there are some direct checks on this).
4509 $this->stats = array();
4510 }
4511
4512 /**
4513 * Runs stats code for a one-off, server-side.
4514 *
4515 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
4516 *
4517 * @return bool If it worked.
4518 */
4519 static function do_server_side_stat( $args ) {
4520 $url = self::build_stats_url( $args );
4521 $a8c_mc_stats_instance = new Automattic\Jetpack\A8c_Mc_Stats();
4522 return $a8c_mc_stats_instance->do_server_side_stat( $url );
4523 }
4524
4525 /**
4526 * Builds the stats url.
4527 *
4528 * @param $args array|string The arguments to append to the URL.
4529 *
4530 * @return string The URL to be pinged.
4531 */
4532 static function build_stats_url( $args ) {
4533
4534 $a8c_mc_stats_instance = new Automattic\Jetpack\A8c_Mc_Stats();
4535 return $a8c_mc_stats_instance->build_stats_url( $args );
4536
4537 }
4538
4539 /**
4540 * Builds a URL to the Jetpack connection auth page
4541 *
4542 * @since 3.9.5
4543 *
4544 * @param bool $raw If true, URL will not be escaped.
4545 * @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
4546 * If string, will be a custom redirect.
4547 * @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
4548 * @param bool $register If true, will generate a register URL regardless of the existing token, since 4.9.0
4549 *
4550 * @return string Connect URL
4551 */
4552 function build_connect_url( $raw = false, $redirect = false, $from = false, $register = false ) {
4553 $site_id = Jetpack_Options::get_option( 'id' );
4554 $blog_token = ( new Tokens() )->get_access_token();
4555
4556 if ( $register || ! $blog_token || ! $site_id ) {
4557 $url = self::nonce_url_no_esc( self::admin_url( 'action=register' ), 'jetpack-register' );
4558
4559 if ( ! empty( $redirect ) ) {
4560 $url = add_query_arg(
4561 'redirect',
4562 urlencode( wp_validate_redirect( esc_url_raw( $redirect ) ) ),
4563 $url
4564 );
4565 }
4566
4567 if ( is_network_admin() ) {
4568 $url = add_query_arg( 'is_multisite', network_admin_url( 'admin.php?page=jetpack-settings' ), $url );
4569 }
4570
4571 $calypso_env = self::get_calypso_env();
4572
4573 if ( ! empty( $calypso_env ) ) {
4574 $url = add_query_arg( 'calypso_env', $calypso_env, $url );
4575 }
4576 } else {
4577
4578 // Let's check the existing blog token to see if we need to re-register. We only check once per minute
4579 // because otherwise this logic can get us in to a loop.
4580 $last_connect_url_check = (int) Jetpack_Options::get_raw_option( 'jetpack_last_connect_url_check' );
4581 if ( ! $last_connect_url_check || ( time() - $last_connect_url_check ) > MINUTE_IN_SECONDS ) {
4582 Jetpack_Options::update_raw_option( 'jetpack_last_connect_url_check', time() );
4583
4584 $response = Client::wpcom_json_api_request_as_blog(
4585 sprintf( '/sites/%d', $site_id ) . '?force=wpcom',
4586 '1.1'
4587 );
4588
4589 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
4590
4591 // Generating a register URL instead to refresh the existing token
4592 return $this->build_connect_url( $raw, $redirect, $from, true );
4593 }
4594 }
4595
4596 $url = $this->build_authorize_url( $redirect );
4597 }
4598
4599 if ( $from ) {
4600 $url = add_query_arg( 'from', $from, $url );
4601 }
4602
4603 $url = $raw ? esc_url_raw( $url ) : esc_url( $url );
4604 /**
4605 * Filter the URL used when connecting a user to a WordPress.com account.
4606 *
4607 * @since 8.1.0
4608 *
4609 * @param string $url Connection URL.
4610 * @param bool $raw If true, URL will not be escaped.
4611 */
4612 return apply_filters( 'jetpack_build_connection_url', $url, $raw );
4613 }
4614
4615 public static function build_authorize_url( $redirect = false, $iframe = false ) {
4616
4617 add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4618 add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4619
4620 if ( $iframe ) {
4621 add_filter( 'jetpack_use_iframe_authorization_flow', '__return_true' );
4622 }
4623
4624 $c8n = self::connection();
4625 $url = $c8n->get_authorization_url( wp_get_current_user(), $redirect );
4626
4627 remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4628 remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4629
4630 if ( $iframe ) {
4631 remove_filter( 'jetpack_use_iframe_authorization_flow', '__return_true' );
4632 }
4633
4634 /**
4635 * Filter the URL used when authorizing a user to a WordPress.com account.
4636 *
4637 * @since 8.9.0
4638 *
4639 * @param string $url Connection URL.
4640 */
4641 return apply_filters( 'jetpack_build_authorize_url', $url );
4642 }
4643
4644 /**
4645 * Filters the connection URL parameter array.
4646 *
4647 * @param array $args default URL parameters used by the package.
4648 * @return array the modified URL arguments array.
4649 */
4650 public static function filter_connect_request_body( $args ) {
4651 if (
4652 Constants::is_defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4653 && include_once Constants::get_constant( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4654 ) {
4655 $gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
4656 $args['locale'] = isset( $gp_locale ) && isset( $gp_locale->slug )
4657 ? $gp_locale->slug
4658 : '';
4659 }
4660
4661 $tracking = new Tracking();
4662 $tracks_identity = $tracking->tracks_get_identity( $args['state'] );
4663
4664 $args = array_merge(
4665 $args,
4666 array(
4667 '_ui' => $tracks_identity['_ui'],
4668 '_ut' => $tracks_identity['_ut'],
4669 )
4670 );
4671
4672 $calypso_env = self::get_calypso_env();
4673
4674 if ( ! empty( $calypso_env ) ) {
4675 $args['calypso_env'] = $calypso_env;
4676 }
4677
4678 return $args;
4679 }
4680
4681 /**
4682 * Filters the `jetpack/v4/connection/data` API response of the Connection package in order to
4683 * add Jetpack-the-plugin related permissions.
4684 *
4685 * @since 10.0
4686 *
4687 * @param array $current_user_connection_data An array containing the current user connection data.
4688 * @return array
4689 */
4690 public static function filter_jetpack_current_user_connection_data( $current_user_connection_data ) {
4691 $jetpack_permissions = array(
4692 'admin_page' => current_user_can( 'jetpack_admin_page' ),
4693 'manage_modules' => current_user_can( 'jetpack_manage_modules' ),
4694 'network_admin' => current_user_can( 'jetpack_network_admin_page' ),
4695 'network_sites_page' => current_user_can( 'jetpack_network_sites_page' ),
4696 'edit_posts' => current_user_can( 'edit_posts' ),
4697 'publish_posts' => current_user_can( 'publish_posts' ),
4698 'manage_options' => current_user_can( 'manage_options' ),
4699 'view_stats' => current_user_can( 'view_stats' ),
4700 'manage_plugins' => current_user_can( 'install_plugins' )
4701 && current_user_can( 'activate_plugins' )
4702 && current_user_can( 'update_plugins' )
4703 && current_user_can( 'delete_plugins' ),
4704 );
4705
4706 if ( isset( $current_user_connection_data['permissions'] ) &&
4707 is_array( $current_user_connection_data['permissions'] ) ) {
4708 $current_user_connection_data['permissions'] = array_merge( $current_user_connection_data['permissions'], $jetpack_permissions );
4709 } else {
4710 $current_user_connection_data['permissions'] = $jetpack_permissions;
4711 }
4712
4713 return $current_user_connection_data;
4714 }
4715
4716 /**
4717 * Filters the URL that will process the connection data. It can be different from the URL
4718 * that we send the user to after everything is done.
4719 *
4720 * @param String $processing_url the default redirect URL used by the package.
4721 * @return String the modified URL.
4722 *
4723 * @deprecated since Jetpack 9.5.0
4724 */
4725 public static function filter_connect_processing_url( $processing_url ) {
4726 _deprecated_function( __METHOD__, 'jetpack-9.5' );
4727
4728 $processing_url = admin_url( 'admin.php?page=jetpack' ); // Making PHPCS happy.
4729 return $processing_url;
4730 }
4731
4732 /**
4733 * Filters the redirection URL that is used for connect requests. The redirect
4734 * URL should return the user back to the Jetpack console.
4735 *
4736 * @param String $redirect the default redirect URL used by the package.
4737 * @return String the modified URL.
4738 */
4739 public static function filter_connect_redirect_url( $redirect ) {
4740 $jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
4741 $redirect = $redirect
4742 ? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
4743 : $jetpack_admin_page;
4744
4745 if ( isset( $_REQUEST['is_multisite'] ) ) {
4746 $redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4747 }
4748
4749 return $redirect;
4750 }
4751
4752 /**
4753 * This action fires at the beginning of the Manager::authorize method.
4754 */
4755 public static function authorize_starting() {
4756 $jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' );
4757 // Checking if site has been active/connected previously before recording unique connection.
4758 if ( ! $jetpack_unique_connection ) {
4759 // jetpack_unique_connection option has never been set.
4760 $jetpack_unique_connection = array(
4761 'connected' => 0,
4762 'disconnected' => 0,
4763 'version' => '3.6.1',
4764 );
4765
4766 update_option( 'jetpack_unique_connection', $jetpack_unique_connection );
4767
4768 // Track unique connection.
4769 $jetpack = self::init();
4770
4771 $jetpack->stat( 'connections', 'unique-connection' );
4772 $jetpack->do_stats( 'server_side' );
4773 }
4774
4775 // Increment number of times connected.
4776 $jetpack_unique_connection['connected'] += 1;
4777 Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
4778 }
4779
4780 /**
4781 * This action fires when the site is registered (connected at a site level).
4782 */
4783 public function handle_unique_registrations_stats() {
4784 $jetpack_unique_registrations = Jetpack_Options::get_option( 'unique_registrations' );
4785 // Checking if site has been registered previously before recording unique connection.
4786 if ( ! $jetpack_unique_registrations ) {
4787
4788 $jetpack_unique_registrations = 0;
4789
4790 $this->stat( 'connections', 'unique-registrations' );
4791 $this->do_stats( 'server_side' );
4792 }
4793
4794 // Increment number of times connected.
4795 $jetpack_unique_registrations ++;
4796 Jetpack_Options::update_option( 'unique_registrations', $jetpack_unique_registrations );
4797 }
4798
4799 /**
4800 * This action fires at the end of the Manager::authorize method when a secondary user is
4801 * linked.
4802 */
4803 public static function authorize_ending_linked() {
4804 // Don't activate anything since we are just connecting a user.
4805 self::state( 'message', 'linked' );
4806 }
4807
4808 /**
4809 * This action fires at the end of the Manager::authorize method when the master user is
4810 * authorized.
4811 *
4812 * @param array $data The request data.
4813 */
4814 public static function authorize_ending_authorized( $data ) {
4815 // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4816 self::invalidate_onboarding_token();
4817
4818 // If redirect_uri is SSO, ensure SSO module is enabled.
4819 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4820
4821 /** This filter is documented in class.jetpack-cli.php */
4822 $jetpack_start_enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
4823
4824 $activate_sso = (
4825 isset( $redirect_options['action'] ) &&
4826 'jetpack-sso' === $redirect_options['action'] &&
4827 $jetpack_start_enable_sso
4828 );
4829
4830 $do_redirect_on_error = ( 'client' === $data['auth_type'] );
4831
4832 self::handle_post_authorization_actions( $activate_sso, $do_redirect_on_error );
4833 }
4834
4835 /**
4836 * Fires on the jetpack_site_registered hook and acitvates default modules
4837 */
4838 public static function activate_default_modules_on_site_register() {
4839 $active_modules = Jetpack_Options::get_option( 'active_modules' );
4840 if ( $active_modules ) {
4841 self::delete_active_modules();
4842
4843 // If there was previously activated modules (a reconnection), re-activate them all including those that require a user, and do not re-activate those that have been deactivated.
4844 self::activate_default_modules( 999, 1, $active_modules, false );
4845 } else {
4846 // On a fresh new connection, at this point we activate only modules that do not require a user connection.
4847 self::activate_default_modules( false, false, array(), false, null, null, false );
4848 }
4849
4850 // Since this is a fresh connection, be sure to clear out IDC options.
4851 Identity_Crisis::clear_all_idc_options();
4852 }
4853
4854 /**
4855 * This action fires at the end of the REST_Connector connection_reconnect method when the
4856 * reconnect process is completed.
4857 * Note that this currently only happens when we don't need the user to re-authorize
4858 * their WP.com account, eg in cases where we are restoring a connection with
4859 * unhealthy blog token.
4860 */
4861 public static function reconnection_completed() {
4862 self::state( 'message', 'reconnection_completed' );
4863 }
4864
4865 public static function apply_activation_source_to_args( &$args ) {
4866 list( $activation_source_name, $activation_source_keyword ) = get_option( 'jetpack_activation_source' );
4867
4868 if ( $activation_source_name ) {
4869 $args['_as'] = urlencode( $activation_source_name );
4870 }
4871
4872 if ( $activation_source_keyword ) {
4873 $args['_ak'] = urlencode( $activation_source_keyword );
4874 }
4875 }
4876
4877 function build_reconnect_url( $raw = false ) {
4878 $url = wp_nonce_url( self::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4879 return $raw ? $url : esc_url( $url );
4880 }
4881
4882 public static function admin_url( $args = null ) {
4883 $args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
4884 $url = add_query_arg( $args, admin_url( 'admin.php' ) );
4885 return $url;
4886 }
4887
4888 public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4889 $actionurl = str_replace( '&amp;', '&', $actionurl );
4890 return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4891 }
4892
4893 function dismiss_jetpack_notice() {
4894
4895 if ( ! isset( $_GET['jetpack-notice'] ) ) {
4896 return;
4897 }
4898
4899 switch ( $_GET['jetpack-notice'] ) {
4900 case 'dismiss':
4901 if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4902
4903 require_once ABSPATH . 'wp-admin/includes/plugin.php';
4904 deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4905 wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4906 }
4907 break;
4908 }
4909 }
4910
4911 public static function sort_modules( $a, $b ) {
4912 if ( $a['sort'] == $b['sort'] ) {
4913 return 0;
4914 }
4915
4916 return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
4917 }
4918
4919 function ajax_recheck_ssl() {
4920 check_ajax_referer( 'recheck-ssl', 'ajax-nonce' );
4921 $result = self::permit_ssl( true );
4922 wp_send_json(
4923 array(
4924 'enabled' => $result,
4925 'message' => get_transient( 'jetpack_https_test_message' ),
4926 )
4927 );
4928 }
4929
4930 /* Client API */
4931
4932 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4933 // Default to a blog token.
4934 $token_type = 'blog';
4935
4936 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
4937 if ( isset( $request_data ) || ! empty( $_GET['onboarding'] ) ) {
4938 if ( ! empty( $_GET['onboarding'] ) ) {
4939 $jpo = $_GET;
4940 } else {
4941 $jpo = json_decode( $request_data, true );
4942 }
4943
4944 $jpo_token = ! empty( $jpo['onboarding']['token'] ) ? $jpo['onboarding']['token'] : null;
4945 $jpo_user = ! empty( $jpo['onboarding']['jpUser'] ) ? $jpo['onboarding']['jpUser'] : null;
4946
4947 if (
4948 isset( $jpo_user )
4949 && isset( $jpo_token )
4950 && is_email( $jpo_user )
4951 && ctype_alnum( $jpo_token )
4952 && isset( $_GET['rest_route'] )
4953 && self::validate_onboarding_token_action(
4954 $jpo_token,
4955 $_GET['rest_route']
4956 )
4957 ) {
4958 $jp_user = get_user_by( 'email', $jpo_user );
4959 if ( is_a( $jp_user, 'WP_User' ) ) {
4960 wp_set_current_user( $jp_user->ID );
4961 $user_can = is_multisite()
4962 ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
4963 : current_user_can( 'manage_options' );
4964 if ( $user_can ) {
4965 $token_type = 'user';
4966 $token->external_user_id = $jp_user->ID;
4967 }
4968 }
4969 }
4970
4971 $token_data['type'] = $token_type;
4972 $token_data['user_id'] = $token->external_user_id;
4973 }
4974
4975 return $token_data;
4976 }
4977
4978 /**
4979 * Create a random secret for validating onboarding payload
4980 *
4981 * @return string Secret token
4982 */
4983 public static function create_onboarding_token() {
4984 if ( false === ( $token = Jetpack_Options::get_option( 'onboarding' ) ) ) {
4985 $token = wp_generate_password( 32, false );
4986 Jetpack_Options::update_option( 'onboarding', $token );
4987 }
4988
4989 return $token;
4990 }
4991
4992 /**
4993 * Remove the onboarding token
4994 *
4995 * @return bool True on success, false on failure
4996 */
4997 public static function invalidate_onboarding_token() {
4998 return Jetpack_Options::delete_option( 'onboarding' );
4999 }
5000
5001 /**
5002 * Validate an onboarding token for a specific action
5003 *
5004 * @return boolean True if token/action pair is accepted, false if not
5005 */
5006 public static function validate_onboarding_token_action( $token, $action ) {
5007 // Compare tokens, bail if tokens do not match
5008 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
5009 return false;
5010 }
5011
5012 // List of valid actions we can take
5013 $valid_actions = array(
5014 '/jetpack/v4/settings',
5015 );
5016
5017 // Only allow valid actions.
5018 if ( ! in_array( $action, $valid_actions ) ) {
5019 return false;
5020 }
5021
5022 return true;
5023 }
5024
5025 /**
5026 * Checks to see if the URL is using SSL to connect with Jetpack
5027 *
5028 * @since 2.3.3
5029 * @return boolean
5030 */
5031 public static function permit_ssl( $force_recheck = false ) {
5032 // Do some fancy tests to see if ssl is being supported
5033 if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
5034 $message = '';
5035 if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
5036 $ssl = 0;
5037 } else {
5038 $ssl = 1;
5039
5040 if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
5041 $ssl = 0;
5042 $message = __( 'WordPress reports no SSL support', 'jetpack' );
5043 } else {
5044 $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
5045 if ( is_wp_error( $response ) ) {
5046 $ssl = 0;
5047 $message = __( 'WordPress reports no SSL support', 'jetpack' );
5048 } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
5049 $ssl = 0;
5050 $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
5051 }
5052 }
5053 }
5054 set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
5055 set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5056 }
5057
5058 return (bool) $ssl;
5059 }
5060
5061 /*
5062 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
5063 */
5064 public function alert_auto_ssl_fail() {
5065 if ( ! current_user_can( 'manage_options' ) ) {
5066 return;
5067 }
5068
5069 $ajax_nonce = wp_create_nonce( 'recheck-ssl' );
5070 ?>
5071
5072 <div id="jetpack-ssl-warning" class="error jp-identity-crisis">
5073 <div class="jp-banner__content">
5074 <h2><?php _e( 'Outbound HTTPS not working', 'jetpack' ); ?></h2>
5075 <p><?php _e( 'Your site could not connect to WordPress.com via HTTPS. This could be due to any number of reasons, including faulty SSL certificates, misconfigured or missing SSL libraries, or network issues.', 'jetpack' ); ?></p>
5076 <p>
5077 <?php _e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
5078 <a href="#" id="jetpack-recheck-ssl-button"><?php _e( 'Try again', 'jetpack' ); ?></a>
5079 <span id="jetpack-recheck-ssl-output"><?php echo get_transient( 'jetpack_https_test_message' ); ?></span>
5080 </p>
5081 <p>
5082 <?php
5083 printf(
5084 __( 'For more help, try our <a href="%1$s">connection debugger</a> or <a href="%2$s" target="_blank">troubleshooting tips</a>.', 'jetpack' ),
5085 esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ),
5086 esc_url( Redirect::get_url( 'jetpack-support-getting-started-troubleshooting-tips' ) )
5087 );
5088 ?>
5089 </p>
5090 </div>
5091 </div>
5092 <style>
5093 #jetpack-recheck-ssl-output { margin-left: 5px; color: red; }
5094 </style>
5095 <script type="text/javascript">
5096 jQuery( document ).ready( function( $ ) {
5097 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
5098 var $this = $( this );
5099 $this.html( <?php echo json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5100 $( '#jetpack-recheck-ssl-output' ).html( '' );
5101 e.preventDefault();
5102 var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': '<?php echo $ajax_nonce; ?>' };
5103 $.post( ajaxurl, data )
5104 .done( function( response ) {
5105 if ( response.enabled ) {
5106 $( '#jetpack-ssl-warning' ).hide();
5107 } else {
5108 this.html( <?php echo json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5109 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
5110 }
5111 }.bind( $this ) );
5112 } );
5113 } );
5114 </script>
5115
5116 <?php
5117 }
5118
5119 /**
5120 * Returns the connection manager object.
5121 *
5122 * @return Automattic\Jetpack\Connection\Manager
5123 */
5124 public static function connection() {
5125 $jetpack = static::init();
5126
5127 // If the connection manager hasn't been instantiated, do that now.
5128 if ( ! $jetpack->connection_manager ) {
5129 $jetpack->connection_manager = new Connection_Manager( 'jetpack' );
5130 }
5131
5132 return $jetpack->connection_manager;
5133 }
5134
5135 /**
5136 * Creates two secret tokens and the end of life timestamp for them.
5137 *
5138 * Note these tokens are unique per call, NOT static per site for connecting.
5139 *
5140 * @deprecated 9.5 Use Automattic\Jetpack\Connection\Secrets->generate() instead.
5141 *
5142 * @since 2.6
5143 * @param String $action The action name.
5144 * @param Integer $user_id The user identifier.
5145 * @param Integer $exp Expiration time in seconds.
5146 * @return array
5147 */
5148 public static function generate_secrets( $action, $user_id = false, $exp = 600 ) {
5149 _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Secrets->generate' );
5150 return self::connection()->generate_secrets( $action, $user_id, $exp );
5151 }
5152
5153 public static function get_secrets( $action, $user_id ) {
5154 $secrets = ( new Secrets() )->get( $action, $user_id );
5155
5156 if ( Secrets::SECRETS_MISSING === $secrets ) {
5157 return new WP_Error( 'verify_secrets_missing', 'Verification secrets not found' );
5158 }
5159
5160 if ( Secrets::SECRETS_EXPIRED === $secrets ) {
5161 return new WP_Error( 'verify_secrets_expired', 'Verification took too long' );
5162 }
5163
5164 return $secrets;
5165 }
5166
5167 /**
5168 * @deprecated since Jetpack 9.7.0
5169 * @see Automattic\Jetpack\Connection\Manager::try_registration()
5170 *
5171 * @return bool|WP_Error
5172 */
5173 public static function register() {
5174 _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Manager::try_registration' );
5175 return static::connection()->try_registration( false );
5176 }
5177
5178 /**
5179 * Filters the registration request body to include tracking properties.
5180 *
5181 * @deprecated since Jetpack 9.7.0
5182 * @see Automattic\Jetpack\Connection\Utils::filter_register_request_body()
5183 *
5184 * @param array $properties
5185 * @return array amended properties.
5186 */
5187 public static function filter_register_request_body( $properties ) {
5188 _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Utils::filter_register_request_body' );
5189 return Connection_Utils::filter_register_request_body( $properties );
5190 }
5191
5192 /**
5193 * Filters the token request body to include tracking properties.
5194 *
5195 * @param array $properties
5196 * @return array amended properties.
5197 */
5198 public static function filter_token_request_body( $properties ) {
5199 $tracking = new Tracking();
5200 $tracks_identity = $tracking->tracks_get_identity( get_current_user_id() );
5201
5202 return array_merge(
5203 $properties,
5204 array(
5205 '_ui' => $tracks_identity['_ui'],
5206 '_ut' => $tracks_identity['_ut'],
5207 )
5208 );
5209 }
5210
5211 /**
5212 * If the db version is showing something other that what we've got now, bump it to current.
5213 *
5214 * @return bool: True if the option was incorrect and updated, false if nothing happened.
5215 */
5216 public static function maybe_set_version_option() {
5217 list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
5218 if ( JETPACK__VERSION != $version ) {
5219 Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
5220
5221 if ( version_compare( JETPACK__VERSION, $version, '>' ) ) {
5222 /** This action is documented in class.jetpack.php */
5223 do_action( 'updating_jetpack_version', JETPACK__VERSION, $version );
5224 }
5225
5226 return true;
5227 }
5228 return false;
5229 }
5230
5231 /* Client Server API */
5232
5233 /**
5234 * Loads the Jetpack XML-RPC client.
5235 * No longer necessary, as the XML-RPC client will be automagically loaded.
5236 *
5237 * Note: we cannot remove this function yet as it is used in this plugin:
5238 * https://wordpress.org/plugins/jetpack-subscription-form/
5239 *
5240 * @deprecated since 7.7.0
5241 */
5242 public static function load_xml_rpc_client() {
5243 _deprecated_function( __METHOD__, 'jetpack-7.7' );
5244 }
5245
5246 /**
5247 * State is passed via cookies from one request to the next, but never to subsequent requests.
5248 * SET: state( $key, $value );
5249 * GET: $value = state( $key );
5250 *
5251 * @param string $key
5252 * @param string $value
5253 * @param bool $restate private
5254 */
5255 public static function state( $key = null, $value = null, $restate = false ) {
5256 static $state = array();
5257 static $path, $domain;
5258 if ( ! isset( $path ) ) {
5259 require_once ABSPATH . 'wp-admin/includes/plugin.php';
5260 $admin_url = self::admin_url();
5261 $bits = wp_parse_url( $admin_url );
5262
5263 if ( is_array( $bits ) ) {
5264 $path = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
5265 $domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
5266 } else {
5267 $path = $domain = null;
5268 }
5269 }
5270
5271 // Extract state from cookies and delete cookies
5272 if ( isset( $_COOKIE['jetpackState'] ) && is_array( $_COOKIE['jetpackState'] ) ) {
5273 $yum = wp_unslash( $_COOKIE['jetpackState'] );
5274 unset( $_COOKIE['jetpackState'] );
5275 foreach ( $yum as $k => $v ) {
5276 if ( strlen( $v ) ) {
5277 $state[ $k ] = $v;
5278 }
5279 setcookie( "jetpackState[$k]", false, 0, $path, $domain );
5280 }
5281 }
5282
5283 if ( $restate ) {
5284 foreach ( $state as $k => $v ) {
5285 setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
5286 }
5287 return;
5288 }
5289
5290 // Get a state variable.
5291 if ( isset( $key ) && ! isset( $value ) ) {
5292 if ( array_key_exists( $key, $state ) ) {
5293 return $state[ $key ];
5294 }
5295 return null;
5296 }
5297
5298 // Set a state variable.
5299 if ( isset( $key ) && isset( $value ) ) {
5300 if ( is_array( $value ) && isset( $value[0] ) ) {
5301 $value = $value[0];
5302 }
5303 $state[ $key ] = $value;
5304 if ( ! headers_sent() ) {
5305 if ( self::should_set_cookie( $key ) ) {
5306 setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
5307 }
5308 }
5309 }
5310 }
5311
5312 public static function restate() {
5313 self::state( null, null, true );
5314 }
5315
5316 /**
5317 * Determines whether the jetpackState[$key] value should be added to the
5318 * cookie.
5319 *
5320 * @param string $key The state key.
5321 *
5322 * @return boolean Whether the value should be added to the cookie.
5323 */
5324 public static function should_set_cookie( $key ) {
5325 global $current_screen;
5326 $page = isset( $current_screen->base ) ? $current_screen->base : null;
5327
5328 if ( 'toplevel_page_jetpack' === $page && 'display_update_modal' === $key ) {
5329 return false;
5330 }
5331
5332 return true;
5333 }
5334
5335 public static function check_privacy( $file ) {
5336 static $is_site_publicly_accessible = null;
5337
5338 if ( is_null( $is_site_publicly_accessible ) ) {
5339 $is_site_publicly_accessible = false;
5340
5341 $rpc = new Jetpack_IXR_Client();
5342
5343 $success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
5344 if ( $success ) {
5345 $response = $rpc->getResponse();
5346 if ( $response ) {
5347 $is_site_publicly_accessible = true;
5348 }
5349 }
5350
5351 Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
5352 }
5353
5354 if ( $is_site_publicly_accessible ) {
5355 return;
5356 }
5357
5358 $module_slug = self::get_module_slug( $file );
5359
5360 $privacy_checks = self::state( 'privacy_checks' );
5361 if ( ! $privacy_checks ) {
5362 $privacy_checks = $module_slug;
5363 } else {
5364 $privacy_checks .= ",$module_slug";
5365 }
5366
5367 self::state( 'privacy_checks', $privacy_checks );
5368 }
5369
5370 /**
5371 * Serve a WordPress.com static resource via a randomized wp.com subdomain.
5372 *
5373 * @deprecated 9.3.0 Use Assets::staticize_subdomain.
5374 *
5375 * @param string $url WordPress.com static resource URL.
5376 */
5377 public static function staticize_subdomain( $url ) {
5378 _deprecated_function( __METHOD__, 'jetpack-9.3.0', 'Automattic\Jetpack\Assets::staticize_subdomain' );
5379 return Assets::staticize_subdomain( $url );
5380 }
5381
5382 /* JSON API Authorization */
5383
5384 /**
5385 * Handles the login action for Authorizing the JSON API
5386 */
5387 function login_form_json_api_authorization() {
5388 $this->verify_json_api_authorization_request();
5389
5390 add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
5391
5392 add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
5393 add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5394 add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
5395 }
5396
5397 // Make sure the login form is POSTed to the signed URL so we can reverify the request
5398 function post_login_form_to_signed_url( $url, $path, $scheme ) {
5399 if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
5400 return $url;
5401 }
5402
5403 $parsed_url = wp_parse_url( $url );
5404 $url = strtok( $url, '?' );
5405 $url = "$url?{$_SERVER['QUERY_STRING']}";
5406 if ( ! empty( $parsed_url['query'] ) ) {
5407 $url .= "&{$parsed_url['query']}";
5408 }
5409
5410 return $url;
5411 }
5412
5413 // Make sure the POSTed request is handled by the same action
5414 function preserve_action_in_login_form_for_json_api_authorization() {
5415 echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
5416 echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) . "' />\n";
5417 }
5418
5419 // If someone logs in to approve API access, store the Access Code in usermeta
5420 function store_json_api_authorization_token( $user_login, $user ) {
5421 add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5422 add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
5423 $token = wp_generate_password( 32, false );
5424 update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5425 }
5426
5427 // Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
5428 function allow_wpcom_public_api_domain( $domains ) {
5429 $domains[] = 'public-api.wordpress.com';
5430 return $domains;
5431 }
5432
5433 static function is_redirect_encoded( $redirect_url ) {
5434 return preg_match( '/https?%3A%2F%2F/i', $redirect_url ) > 0;
5435 }
5436
5437 // Add all wordpress.com environments to the safe redirect allowed list.
5438 function allow_wpcom_environments( $domains ) {
5439 $domains[] = 'wordpress.com';
5440 $domains[] = 'wpcalypso.wordpress.com';
5441 $domains[] = 'horizon.wordpress.com';
5442 $domains[] = 'calypso.localhost';
5443 return $domains;
5444 }
5445
5446 // Add the Access Code details to the public-api.wordpress.com redirect
5447 function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5448 return add_query_arg(
5449 urlencode_deep(
5450 array(
5451 'jetpack-code' => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5452 'jetpack-user-id' => (int) $user->ID,
5453 'jetpack-state' => $this->json_api_authorization_request['state'],
5454 )
5455 ),
5456 $redirect_to
5457 );
5458 }
5459
5460 /**
5461 * Verifies the request by checking the signature
5462 *
5463 * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
5464 * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
5465 *
5466 * @param null|array $environment
5467 */
5468 function verify_json_api_authorization_request( $environment = null ) {
5469 $environment = is_null( $environment )
5470 ? $_REQUEST
5471 : $environment;
5472
5473 list( $env_token,, $env_user_id ) = explode( ':', $environment['token'] );
5474 $token = ( new Tokens() )->get_access_token( $env_user_id, $env_token );
5475 if ( ! $token || empty( $token->secret ) ) {
5476 wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack' ) );
5477 }
5478
5479 $die_error = __( 'Someone may be trying to trick you into giving them access to your site. Or it could be you just encountered a bug :). Either way, please close this window.', 'jetpack' );
5480
5481 // Host has encoded the request URL, probably as a result of a bad http => https redirect
5482 if ( self::is_redirect_encoded( $_GET['redirect_to'] ) ) {
5483 /**
5484 * Jetpack authorisation request Error.
5485 *
5486 * @since 7.5.0
5487 */
5488 do_action( 'jetpack_verify_api_authorization_request_error_double_encode' );
5489 $die_error = sprintf(
5490 /* translators: %s is a URL */
5491 __( 'Your site is incorrectly double-encoding redirects from http to https. This is preventing Jetpack from authenticating your connection. Please visit our <a href="%s">support page</a> for details about how to resolve this.', 'jetpack' ),
5492 Redirect::get_url( 'jetpack-support-double-encoding' )
5493 );
5494 }
5495
5496 $jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5497
5498 if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
5499 $signature = $jetpack_signature->sign_request(
5500 $environment['token'],
5501 $environment['timestamp'],
5502 $environment['nonce'],
5503 '',
5504 'GET',
5505 $environment['jetpack_json_api_original_query'],
5506 null,
5507 true
5508 );
5509 } else {
5510 $signature = $jetpack_signature->sign_current_request(
5511 array(
5512 'body' => null,
5513 'method' => 'GET',
5514 )
5515 );
5516 }
5517
5518 if ( ! $signature ) {
5519 wp_die( $die_error );
5520 } elseif ( is_wp_error( $signature ) ) {
5521 wp_die( $die_error );
5522 } elseif ( ! hash_equals( $signature, $environment['signature'] ) ) {
5523 if ( is_ssl() ) {
5524 // If we signed an HTTP request on the Jetpack Servers, but got redirected to HTTPS by the local blog, check the HTTP signature as well
5525 $signature = $jetpack_signature->sign_current_request(
5526 array(
5527 'scheme' => 'http',
5528 'body' => null,
5529 'method' => 'GET',
5530 )
5531 );
5532 if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
5533 wp_die( $die_error );
5534 }
5535 } else {
5536 wp_die( $die_error );
5537 }
5538 }
5539
5540 $timestamp = (int) $environment['timestamp'];
5541 $nonce = stripslashes( (string) $environment['nonce'] );
5542
5543 if ( ! $this->connection_manager ) {
5544 $this->connection_manager = new Connection_Manager();
5545 }
5546
5547 if ( ! ( new Nonce_Handler() )->add( $timestamp, $nonce ) ) {
5548 // De-nonce the nonce, at least for 5 minutes.
5549 // We have to reuse this nonce at least once (used the first time when the initial request is made, used a second time when the login form is POSTed)
5550 $old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5551 if ( $old_nonce_time < time() - 300 ) {
5552 wp_die( esc_html__( 'The authorization process expired. Please go back and try again.', 'jetpack' ) );
5553 }
5554 }
5555
5556 $data = json_decode( base64_decode( stripslashes( $environment['data'] ) ) );
5557 $data_filters = array(
5558 'state' => 'opaque',
5559 'client_id' => 'int',
5560 'client_title' => 'string',
5561 'client_image' => 'url',
5562 );
5563
5564 foreach ( $data_filters as $key => $sanitation ) {
5565 if ( ! isset( $data->$key ) ) {
5566 wp_die( $die_error );
5567 }
5568
5569 switch ( $sanitation ) {
5570 case 'int':
5571 $this->json_api_authorization_request[ $key ] = (int) $data->$key;
5572 break;
5573 case 'opaque':
5574 $this->json_api_authorization_request[ $key ] = (string) $data->$key;
5575 break;
5576 case 'string':
5577 $this->json_api_authorization_request[ $key ] = wp_kses( (string) $data->$key, array() );
5578 break;
5579 case 'url':
5580 $this->json_api_authorization_request[ $key ] = esc_url_raw( (string) $data->$key );
5581 break;
5582 }
5583 }
5584
5585 if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5586 wp_die( $die_error );
5587 }
5588 }
5589
5590 function login_message_json_api_authorization( $message ) {
5591 return '<p class="message">' . sprintf(
5592 /* translators: Name/image of the client requesting authorization */
5593 esc_html__( '%s wants to access your site’s data. Log in to authorize that access.', 'jetpack' ),
5594 '<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5595 ) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5596 }
5597
5598 /**
5599 * Get $content_width, but with a <s>twist</s> filter.
5600 */
5601 public static function get_content_width() {
5602 $content_width = ( isset( $GLOBALS['content_width'] ) && is_numeric( $GLOBALS['content_width'] ) )
5603 ? $GLOBALS['content_width']
5604 : false;
5605 /**
5606 * Filter the Content Width value.
5607 *
5608 * @since 2.2.3
5609 *
5610 * @param string $content_width Content Width value.
5611 */
5612 return apply_filters( 'jetpack_content_width', $content_width );
5613 }
5614
5615 /**
5616 * Pings the WordPress.com Mirror Site for the specified options.
5617 *
5618 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
5619 *
5620 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
5621 */
5622 public function get_cloud_site_options( $option_names ) {
5623 $option_names = array_filter( (array) $option_names, 'is_string' );
5624
5625 $xml = new Jetpack_IXR_Client();
5626 $xml->query( 'jetpack.fetchSiteOptions', $option_names );
5627 if ( $xml->isError() ) {
5628 return array(
5629 'error_code' => $xml->getErrorCode(),
5630 'error_msg' => $xml->getErrorMessage(),
5631 );
5632 }
5633 $cloud_site_options = $xml->getResponse();
5634
5635 return $cloud_site_options;
5636 }
5637
5638 /**
5639 * Checks if the site is currently in an identity crisis.
5640 *
5641 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5642 */
5643 public static function check_identity_crisis() {
5644 if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5645 return false;
5646 }
5647
5648 return Jetpack_Options::get_option( 'sync_error_idc' );
5649 }
5650
5651 /**
5652 * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
5653 *
5654 * @since 4.4.0
5655 * @since 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC
5656 *
5657 * @return bool
5658 */
5659 public static function validate_sync_error_idc_option() {
5660 _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::validate_sync_error_idc_option' );
5661 return Identity_Crisis::validate_sync_error_idc_option();
5662 }
5663
5664 /**
5665 * Normalizes a url by doing three things:
5666 * - Strips protocol
5667 * - Strips www
5668 * - Adds a trailing slash
5669 *
5670 * @since 4.4.0
5671 * @param string $url
5672 * @return WP_Error|string
5673 */
5674 public static function normalize_url_protocol_agnostic( $url ) {
5675 $parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
5676 if ( ! $parsed_url || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
5677 return new WP_Error( 'cannot_parse_url', sprintf( esc_html__( 'Cannot parse URL %s', 'jetpack' ), $url ) );
5678 }
5679
5680 // Strip www and protocols
5681 $url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
5682 return $url;
5683 }
5684
5685 /**
5686 * Gets the value that is to be saved in the jetpack_sync_error_idc option.
5687 *
5688 * @since 4.4.0
5689 * @since 5.4.0 Add transient since home/siteurl retrieved directly from DB
5690 *
5691 * @param array $response
5692 * @return array Array of the local urls, wpcom urls, and error code
5693 */
5694 public static function get_sync_error_idc_option( $response = array() ) {
5695 _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::get_sync_error_idc_option' );
5696 return Identity_Crisis::get_sync_error_idc_option( $response );
5697 }
5698
5699 /**
5700 * Returns the value of the jetpack_sync_idc_optin filter, or constant.
5701 * If set to true, the site will be put into staging mode.
5702 *
5703 * @since 4.3.2
5704 * @return bool
5705 */
5706 public static function sync_idc_optin() {
5707 _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::sync_idc_optin' );
5708 return Identity_Crisis::sync_idc_optin();
5709 }
5710
5711 /**
5712 * Maybe Use a .min.css stylesheet, maybe not.
5713 *
5714 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
5715 */
5716 public static function maybe_min_asset( $url, $path, $plugin ) {
5717 // Short out on things trying to find actual paths.
5718 if ( ! $path || empty( $plugin ) ) {
5719 return $url;
5720 }
5721
5722 $path = ltrim( $path, '/' );
5723
5724 // Strip out the abspath.
5725 $base = dirname( plugin_basename( $plugin ) );
5726
5727 // Short out on non-Jetpack assets.
5728 if ( 'jetpack/' !== substr( $base, 0, 8 ) ) {
5729 return $url;
5730 }
5731
5732 // File name parsing.
5733 $file = "{$base}/{$path}";
5734 $full_path = JETPACK__PLUGIN_DIR . substr( $file, 8 );
5735 $file_name = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
5736 $file_name_parts_r = array_reverse( explode( '.', $file_name ) );
5737 $extension = array_shift( $file_name_parts_r );
5738
5739 if ( in_array( strtolower( $extension ), array( 'css', 'js' ) ) ) {
5740 // Already pointing at the minified version.
5741 if ( 'min' === $file_name_parts_r[0] ) {
5742 return $url;
5743 }
5744
5745 $min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
5746 if ( file_exists( $min_full_path ) ) {
5747 $url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
5748 // If it's a CSS file, stash it so we can set the .min suffix for rtl-ing.
5749 if ( 'css' === $extension ) {
5750 $key = str_replace( JETPACK__PLUGIN_DIR, 'jetpack/', $min_full_path );
5751 self::$min_assets[ $key ] = $path;
5752 }
5753 }
5754 }
5755
5756 return $url;
5757 }
5758
5759 /**
5760 * If the asset is minified, let's flag .min as the suffix.
5761 *
5762 * Attached to `style_loader_src` filter.
5763 *
5764 * @param string $tag The tag that would link to the external asset.
5765 * @param string $handle The registered handle of the script in question.
5766 * @param string $href The url of the asset in question.
5767 */
5768 public static function set_suffix_on_min( $src, $handle ) {
5769 if ( false === strpos( $src, '.min.css' ) ) {
5770 return $src;
5771 }
5772
5773 if ( ! empty( self::$min_assets ) ) {
5774 foreach ( self::$min_assets as $file => $path ) {
5775 if ( false !== strpos( $src, $file ) ) {
5776 wp_style_add_data( $handle, 'suffix', '.min' );
5777 return $src;
5778 }
5779 }
5780 }
5781
5782 return $src;
5783 }
5784
5785 /**
5786 * Maybe inlines a stylesheet.
5787 *
5788 * If you'd like to inline a stylesheet instead of printing a link to it,
5789 * wp_style_add_data( 'handle', 'jetpack-inline', true );
5790 *
5791 * Attached to `style_loader_tag` filter.
5792 *
5793 * @param string $tag The tag that would link to the external asset.
5794 * @param string $handle The registered handle of the script in question.
5795 *
5796 * @return string
5797 */
5798 public static function maybe_inline_style( $tag, $handle ) {
5799 global $wp_styles;
5800 $item = $wp_styles->registered[ $handle ];
5801
5802 if ( ! isset( $item->extra['jetpack-inline'] ) || ! $item->extra['jetpack-inline'] ) {
5803 return $tag;
5804 }
5805
5806 if ( preg_match( '# href=\'([^\']+)\' #i', $tag, $matches ) ) {
5807 $href = $matches[1];
5808 // Strip off query string
5809 if ( $pos = strpos( $href, '?' ) ) {
5810 $href = substr( $href, 0, $pos );
5811 }
5812 // Strip off fragment
5813 if ( $pos = strpos( $href, '#' ) ) {
5814 $href = substr( $href, 0, $pos );
5815 }
5816 } else {
5817 return $tag;
5818 }
5819
5820 $plugins_dir = plugin_dir_url( JETPACK__PLUGIN_FILE );
5821 if ( $plugins_dir !== substr( $href, 0, strlen( $plugins_dir ) ) ) {
5822 return $tag;
5823 }
5824
5825 // If this stylesheet has a RTL version, and the RTL version replaces normal...
5826 if ( isset( $item->extra['rtl'] ) && 'replace' === $item->extra['rtl'] && is_rtl() ) {
5827 // And this isn't the pass that actually deals with the RTL version...
5828 if ( false === strpos( $tag, " id='$handle-rtl-css' " ) ) {
5829 // Short out, as the RTL version will deal with it in a moment.
5830 return $tag;
5831 }
5832 }
5833
5834 $file = JETPACK__PLUGIN_DIR . substr( $href, strlen( $plugins_dir ) );
5835 $css = self::absolutize_css_urls( file_get_contents( $file ), $href );
5836 if ( $css ) {
5837 $tag = "<!-- Inline {$item->handle} -->\r\n";
5838 if ( empty( $item->extra['after'] ) ) {
5839 wp_add_inline_style( $handle, $css );
5840 } else {
5841 array_unshift( $item->extra['after'], $css );
5842 wp_style_add_data( $handle, 'after', $item->extra['after'] );
5843 }
5844 }
5845
5846 return $tag;
5847 }
5848
5849 /**
5850 * Loads a view file from the views
5851 *
5852 * Data passed in with the $data parameter will be available in the
5853 * template file as $data['value']
5854 *
5855 * @param string $template - Template file to load
5856 * @param array $data - Any data to pass along to the template
5857 * @return boolean - If template file was found
5858 **/
5859 public function load_view( $template, $data = array() ) {
5860 $views_dir = JETPACK__PLUGIN_DIR . 'views/';
5861
5862 if ( file_exists( $views_dir . $template ) ) {
5863 require_once $views_dir . $template;
5864 return true;
5865 }
5866
5867 error_log( "Jetpack: Unable to find view file $views_dir$template" );
5868 return false;
5869 }
5870
5871 /**
5872 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5873 */
5874 public function deprecated_hooks() {
5875 $filter_deprecated_list = array(
5876 'jetpack_bail_on_shortcode' => array(
5877 'replacement' => 'jetpack_shortcodes_to_include',
5878 'version' => 'jetpack-3.1.0',
5879 ),
5880 'wpl_sharing_2014_1' => array(
5881 'replacement' => null,
5882 'version' => 'jetpack-3.6.0',
5883 ),
5884 'jetpack-tools-to-include' => array(
5885 'replacement' => 'jetpack_tools_to_include',
5886 'version' => 'jetpack-3.9.0',
5887 ),
5888 'jetpack_identity_crisis_options_to_check' => array(
5889 'replacement' => null,
5890 'version' => 'jetpack-4.0.0',
5891 ),
5892 'update_option_jetpack_single_user_site' => array(
5893 'replacement' => null,
5894 'version' => 'jetpack-4.3.0',
5895 ),
5896 'audio_player_default_colors' => array(
5897 'replacement' => null,
5898 'version' => 'jetpack-4.3.0',
5899 ),
5900 'add_option_jetpack_featured_images_enabled' => array(
5901 'replacement' => null,
5902 'version' => 'jetpack-4.3.0',
5903 ),
5904 'add_option_jetpack_update_details' => array(
5905 'replacement' => null,
5906 'version' => 'jetpack-4.3.0',
5907 ),
5908 'add_option_jetpack_updates' => array(
5909 'replacement' => null,
5910 'version' => 'jetpack-4.3.0',
5911 ),
5912 'add_option_jetpack_network_name' => array(
5913 'replacement' => null,
5914 'version' => 'jetpack-4.3.0',
5915 ),
5916 'add_option_jetpack_network_allow_new_registrations' => array(
5917 'replacement' => null,
5918 'version' => 'jetpack-4.3.0',
5919 ),
5920 'add_option_jetpack_network_add_new_users' => array(
5921 'replacement' => null,
5922 'version' => 'jetpack-4.3.0',
5923 ),
5924 'add_option_jetpack_network_site_upload_space' => array(
5925 'replacement' => null,
5926 'version' => 'jetpack-4.3.0',
5927 ),
5928 'add_option_jetpack_network_upload_file_types' => array(
5929 'replacement' => null,
5930 'version' => 'jetpack-4.3.0',
5931 ),
5932 'add_option_jetpack_network_enable_administration_menus' => array(
5933 'replacement' => null,
5934 'version' => 'jetpack-4.3.0',
5935 ),
5936 'add_option_jetpack_is_multi_site' => array(
5937 'replacement' => null,
5938 'version' => 'jetpack-4.3.0',
5939 ),
5940 'add_option_jetpack_is_main_network' => array(
5941 'replacement' => null,
5942 'version' => 'jetpack-4.3.0',
5943 ),
5944 'add_option_jetpack_main_network_site' => array(
5945 'replacement' => null,
5946 'version' => 'jetpack-4.3.0',
5947 ),
5948 'jetpack_sync_all_registered_options' => array(
5949 'replacement' => null,
5950 'version' => 'jetpack-4.3.0',
5951 ),
5952 'jetpack_has_identity_crisis' => array(
5953 'replacement' => 'jetpack_sync_error_idc_validation',
5954 'version' => 'jetpack-4.4.0',
5955 ),
5956 'jetpack_is_post_mailable' => array(
5957 'replacement' => null,
5958 'version' => 'jetpack-4.4.0',
5959 ),
5960 'jetpack_seo_site_host' => array(
5961 'replacement' => null,
5962 'version' => 'jetpack-5.1.0',
5963 ),
5964 'jetpack_installed_plugin' => array(
5965 'replacement' => 'jetpack_plugin_installed',
5966 'version' => 'jetpack-6.0.0',
5967 ),
5968 'jetpack_holiday_snow_option_name' => array(
5969 'replacement' => null,
5970 'version' => 'jetpack-6.0.0',
5971 ),
5972 'jetpack_holiday_chance_of_snow' => array(
5973 'replacement' => null,
5974 'version' => 'jetpack-6.0.0',
5975 ),
5976 'jetpack_holiday_snow_js_url' => array(
5977 'replacement' => null,
5978 'version' => 'jetpack-6.0.0',
5979 ),
5980 'jetpack_is_holiday_snow_season' => array(
5981 'replacement' => null,
5982 'version' => 'jetpack-6.0.0',
5983 ),
5984 'jetpack_holiday_snow_option_updated' => array(
5985 'replacement' => null,
5986 'version' => 'jetpack-6.0.0',
5987 ),
5988 'jetpack_holiday_snowing' => array(
5989 'replacement' => null,
5990 'version' => 'jetpack-6.0.0',
5991 ),
5992 'jetpack_sso_auth_cookie_expirtation' => array(
5993 'replacement' => 'jetpack_sso_auth_cookie_expiration',
5994 'version' => 'jetpack-6.1.0',
5995 ),
5996 'jetpack_cache_plans' => array(
5997 'replacement' => null,
5998 'version' => 'jetpack-6.1.0',
5999 ),
6000
6001 'jetpack_lazy_images_skip_image_with_atttributes' => array(
6002 'replacement' => 'jetpack_lazy_images_skip_image_with_attributes',
6003 'version' => 'jetpack-6.5.0',
6004 ),
6005 'jetpack_enable_site_verification' => array(
6006 'replacement' => null,
6007 'version' => 'jetpack-6.5.0',
6008 ),
6009 'can_display_jetpack_manage_notice' => array(
6010 'replacement' => null,
6011 'version' => 'jetpack-7.3.0',
6012 ),
6013 'atd_http_post_timeout' => array(
6014 'replacement' => null,
6015 'version' => 'jetpack-7.3.0',
6016 ),
6017 'atd_service_domain' => array(
6018 'replacement' => null,
6019 'version' => 'jetpack-7.3.0',
6020 ),
6021 'atd_load_scripts' => array(
6022 'replacement' => null,
6023 'version' => 'jetpack-7.3.0',
6024 ),
6025 'jetpack_widget_authors_exclude' => array(
6026 'replacement' => 'jetpack_widget_authors_params',
6027 'version' => 'jetpack-7.7.0',
6028 ),
6029 // Removed in Jetpack 7.9.0
6030 'jetpack_pwa_manifest' => array(
6031 'replacement' => null,
6032 'version' => 'jetpack-7.9.0',
6033 ),
6034 'jetpack_pwa_background_color' => array(
6035 'replacement' => null,
6036 'version' => 'jetpack-7.9.0',
6037 ),
6038 'jetpack_check_mobile' => array(
6039 'replacement' => null,
6040 'version' => 'jetpack-8.3.0',
6041 ),
6042 'jetpack_mobile_stylesheet' => array(
6043 'replacement' => null,
6044 'version' => 'jetpack-8.3.0',
6045 ),
6046 'jetpack_mobile_template' => array(
6047 'replacement' => null,
6048 'version' => 'jetpack-8.3.0',
6049 ),
6050 'jetpack_mobile_theme_menu' => array(
6051 'replacement' => null,
6052 'version' => 'jetpack-8.3.0',
6053 ),
6054 'minileven_show_featured_images' => array(
6055 'replacement' => null,
6056 'version' => 'jetpack-8.3.0',
6057 ),
6058 'minileven_attachment_size' => array(
6059 'replacement' => null,
6060 'version' => 'jetpack-8.3.0',
6061 ),
6062 'instagram_cache_oembed_api_response_body' => array(
6063 'replacement' => null,
6064 'version' => 'jetpack-9.1.0',
6065 ),
6066 'jetpack_can_make_outbound_https' => array(
6067 'replacement' => null,
6068 'version' => 'jetpack-9.1.0',
6069 ),
6070 );
6071
6072 foreach ( $filter_deprecated_list as $tag => $args ) {
6073 if ( has_filter( $tag ) ) {
6074 apply_filters_deprecated( $tag, array( null ), $args['version'], $args['replacement'] );
6075 }
6076 }
6077
6078 $action_deprecated_list = array(
6079 'jetpack_updated_theme' => array(
6080 'replacement' => 'jetpack_updated_themes',
6081 'version' => 'jetpack-6.2.0',
6082 ),
6083 'atd_http_post_error' => array(
6084 'replacement' => null,
6085 'version' => 'jetpack-7.3.0',
6086 ),
6087 'mobile_reject_mobile' => array(
6088 'replacement' => null,
6089 'version' => 'jetpack-8.3.0',
6090 ),
6091 'mobile_force_mobile' => array(
6092 'replacement' => null,
6093 'version' => 'jetpack-8.3.0',
6094 ),
6095 'mobile_app_promo_download' => array(
6096 'replacement' => null,
6097 'version' => 'jetpack-8.3.0',
6098 ),
6099 'mobile_setup' => array(
6100 'replacement' => null,
6101 'version' => 'jetpack-8.3.0',
6102 ),
6103 'jetpack_mobile_footer_before' => array(
6104 'replacement' => null,
6105 'version' => 'jetpack-8.3.0',
6106 ),
6107 'wp_mobile_theme_footer' => array(
6108 'replacement' => null,
6109 'version' => 'jetpack-8.3.0',
6110 ),
6111 'minileven_credits' => array(
6112 'replacement' => null,
6113 'version' => 'jetpack-8.3.0',
6114 ),
6115 'jetpack_mobile_header_before' => array(
6116 'replacement' => null,
6117 'version' => 'jetpack-8.3.0',
6118 ),
6119 'jetpack_mobile_header_after' => array(
6120 'replacement' => null,
6121 'version' => 'jetpack-8.3.0',
6122 ),
6123 );
6124
6125 foreach ( $action_deprecated_list as $tag => $args ) {
6126 if ( has_action( $tag ) ) {
6127 do_action_deprecated( $tag, array(), $args['version'], $args['replacement'] );
6128 }
6129 }
6130 }
6131
6132 /**
6133 * Converts any url in a stylesheet, to the correct absolute url.
6134 *
6135 * Considerations:
6136 * - Normal, relative URLs `feh.png`
6137 * - Data URLs `data:image/gif;base64,eh129ehiuehjdhsa==`
6138 * - Schema-agnostic URLs `//domain.com/feh.png`
6139 * - Absolute URLs `http://domain.com/feh.png`
6140 * - Domain root relative URLs `/feh.png`
6141 *
6142 * @param $css string: The raw CSS -- should be read in directly from the file.
6143 * @param $css_file_url : The URL that the file can be accessed at, for calculating paths from.
6144 *
6145 * @return mixed|string
6146 */
6147 public static function absolutize_css_urls( $css, $css_file_url ) {
6148 $pattern = '#url\((?P<path>[^)]*)\)#i';
6149 $css_dir = dirname( $css_file_url );
6150 $p = wp_parse_url( $css_dir );
6151 $domain = sprintf(
6152 '%1$s//%2$s%3$s%4$s',
6153 isset( $p['scheme'] ) ? "{$p['scheme']}:" : '',
6154 isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
6155 $p['host'],
6156 isset( $p['port'] ) ? ":{$p['port']}" : ''
6157 );
6158
6159 if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
6160 $find = $replace = array();
6161 foreach ( $matches as $match ) {
6162 $url = trim( $match['path'], "'\" \t" );
6163
6164 // If this is a data url, we don't want to mess with it.
6165 if ( 'data:' === substr( $url, 0, 5 ) ) {
6166 continue;
6167 }
6168
6169 // If this is an absolute or protocol-agnostic url,
6170 // we don't want to mess with it.
6171 if ( preg_match( '#^(https?:)?//#i', $url ) ) {
6172 continue;
6173 }
6174
6175 switch ( substr( $url, 0, 1 ) ) {
6176 case '/':
6177 $absolute = $domain . $url;
6178 break;
6179 default:
6180 $absolute = $css_dir . '/' . $url;
6181 }
6182
6183 $find[] = $match[0];
6184 $replace[] = sprintf( 'url("%s")', $absolute );
6185 }
6186 $css = str_replace( $find, $replace, $css );
6187 }
6188
6189 return $css;
6190 }
6191
6192 /**
6193 * This methods removes all of the registered css files on the front end
6194 * from Jetpack in favor of using a single file. In effect "imploding"
6195 * all the files into one file.
6196 *
6197 * Pros:
6198 * - Uses only ONE css asset connection instead of 15
6199 * - Saves a minimum of 56k
6200 * - Reduces server load
6201 * - Reduces time to first painted byte
6202 *
6203 * Cons:
6204 * - Loads css for ALL modules. However all selectors are prefixed so it
6205 * should not cause any issues with themes.
6206 * - Plugins/themes dequeuing styles no longer do anything. See
6207 * jetpack_implode_frontend_css filter for a workaround
6208 *
6209 * For some situations developers may wish to disable css imploding and
6210 * instead operate in legacy mode where each file loads seperately and
6211 * can be edited individually or dequeued. This can be accomplished with
6212 * the following line:
6213 *
6214 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6215 *
6216 * @since 3.2
6217 **/
6218 public function implode_frontend_css( $travis_test = false ) {
6219 $do_implode = true;
6220 if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6221 $do_implode = false;
6222 }
6223
6224 // Do not implode CSS when the page loads via the AMP plugin.
6225 if ( Jetpack_AMP_Support::is_amp_request() ) {
6226 $do_implode = false;
6227 }
6228
6229 /**
6230 * Allow CSS to be concatenated into a single jetpack.css file.
6231 *
6232 * @since 3.2.0
6233 *
6234 * @param bool $do_implode Should CSS be concatenated? Default to true.
6235 */
6236 $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6237
6238 // Do not use the imploded file when default behavior was altered through the filter
6239 if ( ! $do_implode ) {
6240 return;
6241 }
6242
6243 // We do not want to use the imploded file in dev mode, or if not connected
6244 if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
6245 if ( ! $travis_test ) {
6246 return;
6247 }
6248 }
6249
6250 // Do not use the imploded file if sharing css was dequeued via the sharing settings screen
6251 if ( get_option( 'sharedaddy_disable_resources' ) ) {
6252 return;
6253 }
6254
6255 /*
6256 * Now we assume Jetpack is connected and able to serve the single
6257 * file.
6258 *
6259 * In the future there will be a check here to serve the file locally
6260 * or potentially from the Jetpack CDN
6261 *
6262 * For now:
6263 * - Enqueue a single imploded css file
6264 * - Zero out the style_loader_tag for the bundled ones
6265 * - Be happy, drink scotch
6266 */
6267
6268 add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6269
6270 $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6271
6272 wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6273 wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6274 }
6275
6276 function concat_remove_style_loader_tag( $tag, $handle ) {
6277 if ( in_array( $handle, $this->concatenated_style_handles ) ) {
6278 $tag = '';
6279 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6280 $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6281 }
6282 }
6283
6284 return $tag;
6285 }
6286
6287 /*
6288 * Check the heartbeat data
6289 *
6290 * Organizes the heartbeat data by severity. For example, if the site
6291 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
6292 *
6293 * Data will be added to "caution" array, if it either:
6294 * - Out of date Jetpack version
6295 * - Out of date WP version
6296 * - Out of date PHP version
6297 *
6298 * $return array $filtered_data
6299 */
6300 public static function jetpack_check_heartbeat_data() {
6301 $raw_data = Jetpack_Heartbeat::generate_stats_array();
6302
6303 $good = array();
6304 $caution = array();
6305 $bad = array();
6306
6307 foreach ( $raw_data as $stat => $value ) {
6308
6309 // Check jetpack version
6310 if ( 'version' == $stat ) {
6311 if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
6312 $caution[ $stat ] = $value . ' - min supported is ' . JETPACK__VERSION;
6313 continue;
6314 }
6315 }
6316
6317 // Check WP version
6318 if ( 'wp-version' == $stat ) {
6319 if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
6320 $caution[ $stat ] = $value . ' - min supported is ' . JETPACK__MINIMUM_WP_VERSION;
6321 continue;
6322 }
6323 }
6324
6325 // Check PHP version
6326 if ( 'php-version' == $stat ) {
6327 if ( version_compare( PHP_VERSION, JETPACK__MINIMUM_PHP_VERSION, '<' ) ) {
6328 $caution[ $stat ] = $value . ' - min supported is ' . JETPACK__MINIMUM_PHP_VERSION;
6329 continue;
6330 }
6331 }
6332
6333 // Check ID crisis
6334 if ( 'identitycrisis' == $stat ) {
6335 if ( 'yes' == $value ) {
6336 $bad[ $stat ] = $value;
6337 continue;
6338 }
6339 }
6340
6341 // The rest are good :)
6342 $good[ $stat ] = $value;
6343 }
6344
6345 $filtered_data = array(
6346 'good' => $good,
6347 'caution' => $caution,
6348 'bad' => $bad,
6349 );
6350
6351 return $filtered_data;
6352 }
6353
6354 /*
6355 * This method is used to organize all options that can be reset
6356 * without disconnecting Jetpack.
6357 *
6358 * It is used in class.jetpack-cli.php to reset options
6359 *
6360 * @since 5.4.0 Logic moved to Jetpack_Options class. Method left in Jetpack class for backwards compat.
6361 *
6362 * @return array of options to delete.
6363 */
6364 public static function get_jetpack_options_for_reset() {
6365 return Jetpack_Options::get_options_for_reset();
6366 }
6367
6368 /*
6369 * Strip http:// or https:// from a url, replaces forward slash with ::,
6370 * so we can bring them directly to their site in calypso.
6371 *
6372 * @deprecated 9.2.0 Use Automattic\Jetpack\Status::get_site_suffix
6373 *
6374 * @param string | url
6375 * @return string | url without the guff
6376 */
6377 public static function build_raw_urls( $url ) {
6378 _deprecated_function( __METHOD__, 'jetpack-9.2.0', 'Automattic\Jetpack\Status::get_site_suffix' );
6379
6380 return ( new Status() )->get_site_suffix( $url );
6381 }
6382
6383 /**
6384 * @param mixed $result Value for the user's option
6385 * @return mixed
6386 */
6387 function get_user_option_meta_box_order_dashboard( $sorted ) {
6388 if ( ! is_array( $sorted ) ) {
6389 return $sorted;
6390 }
6391
6392 foreach ( $sorted as $box_context => $ids ) {
6393 if ( false === strpos( $ids, 'dashboard_stats' ) ) {
6394 // If the old id isn't anywhere in the ids, don't bother exploding and fail out.
6395 continue;
6396 }
6397
6398 $ids_array = explode( ',', $ids );
6399 $key = array_search( 'dashboard_stats', $ids_array );
6400
6401 if ( false !== $key ) {
6402 // If we've found that exact value in the option (and not `google_dashboard_stats` for example)
6403 $ids_array[ $key ] = 'jetpack_summary_widget';
6404 $sorted[ $box_context ] = implode( ',', $ids_array );
6405 // We've found it, stop searching, and just return.
6406 break;
6407 }
6408 }
6409
6410 return $sorted;
6411 }
6412
6413 /*
6414 * Adds a "blank" column in the user admin table to display indication of user connection.
6415 */
6416 function jetpack_icon_user_connected( $columns ) {
6417 $columns['user_jetpack'] = '';
6418 return $columns;
6419 }
6420
6421 /*
6422 * Show Jetpack icon if the user is linked.
6423 */
6424 function jetpack_show_user_connected_icon( $val, $col, $user_id ) {
6425 if ( 'user_jetpack' === $col && self::connection()->is_user_connected( $user_id ) ) {
6426 $jetpack_logo = new Jetpack_Logo();
6427 $emblem_html = sprintf(
6428 '<a title="%1$s" class="jp-emblem-user-admin">%2$s</a>',
6429 esc_attr__( 'This user is linked and ready to fly with Jetpack.', 'jetpack' ),
6430 $jetpack_logo->get_jp_emblem()
6431 );
6432 return $emblem_html;
6433 }
6434
6435 return $val;
6436 }
6437
6438 /*
6439 * Style the Jetpack user column
6440 */
6441 function jetpack_user_col_style() {
6442 global $current_screen;
6443 if ( ! empty( $current_screen->base ) && 'users' == $current_screen->base ) {
6444 ?>
6445 <style>
6446 .fixed .column-user_jetpack {
6447 width: 21px;
6448 }
6449 .jp-emblem-user-admin svg {
6450 width: 20px;
6451 height: 20px;
6452 }
6453 .jp-emblem-user-admin path {
6454 fill: #00BE28;
6455 }
6456 </style>
6457 <?php
6458 }
6459 }
6460
6461 /**
6462 * Checks if Akismet is active and working.
6463 *
6464 * We dropped support for Akismet 3.0 with Jetpack 6.1.1 while introducing a check for an Akismet valid key
6465 * that implied usage of methods present since more recent version.
6466 * See https://github.com/Automattic/jetpack/pull/9585
6467 *
6468 * @since 5.1.0
6469 *
6470 * @return bool True = Akismet available. False = Aksimet not available.
6471 */
6472 public static function is_akismet_active() {
6473 static $status = null;
6474
6475 if ( ! is_null( $status ) ) {
6476 return $status;
6477 }
6478
6479 // Check if a modern version of Akismet is active.
6480 if ( ! method_exists( 'Akismet', 'http_post' ) ) {
6481 $status = false;
6482 return $status;
6483 }
6484
6485 // Make sure there is a key known to Akismet at all before verifying key.
6486 $akismet_key = Akismet::get_api_key();
6487 if ( ! $akismet_key ) {
6488 $status = false;
6489 return $status;
6490 }
6491
6492 // Possible values: valid, invalid, failure via Akismet. false if no status is cached.
6493 $akismet_key_state = get_transient( 'jetpack_akismet_key_is_valid' );
6494
6495 // Do not used the cache result in wp-admin or REST API requests if the key isn't valid, in case someone is actively renewing, etc.
6496 $recheck = ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) && 'valid' !== $akismet_key_state;
6497 // We cache the result of the Akismet key verification for ten minutes.
6498 if ( ! $akismet_key_state || $recheck ) {
6499 $akismet_key_state = Akismet::verify_key( $akismet_key );
6500 set_transient( 'jetpack_akismet_key_is_valid', $akismet_key_state, 10 * MINUTE_IN_SECONDS );
6501 }
6502
6503 $status = 'valid' === $akismet_key_state;
6504
6505 return $status;
6506 }
6507
6508 /**
6509 * Given a minified path, and a non-minified path, will return
6510 * a minified or non-minified file URL based on whether SCRIPT_DEBUG is set and truthy.
6511 *
6512 * Both `$min_base` and `$non_min_base` are expected to be relative to the
6513 * root Jetpack directory.
6514 *
6515 * @since 5.6.0
6516 *
6517 * @param string $min_path
6518 * @param string $non_min_path
6519 * @return string The URL to the file
6520 */
6521 public static function get_file_url_for_environment( $min_path, $non_min_path ) {
6522 return Assets::get_file_url_for_environment( $min_path, $non_min_path );
6523 }
6524
6525 /**
6526 * Checks for whether Jetpack Backup is enabled.
6527 * Will return true if the state of Backup is anything except "unavailable".
6528 *
6529 * @return bool|int|mixed
6530 */
6531 public static function is_rewind_enabled() {
6532 // Rewind is a paid feature, therefore requires a user-level connection.
6533 if ( ! static::connection()->has_connected_owner() ) {
6534 return false;
6535 }
6536
6537 $rewind_enabled = get_transient( 'jetpack_rewind_enabled' );
6538 if ( false === $rewind_enabled ) {
6539 jetpack_require_lib( 'class.core-rest-api-endpoints' );
6540 $rewind_data = (array) Jetpack_Core_Json_Api_Endpoints::rewind_data();
6541 $rewind_enabled = ( ! is_wp_error( $rewind_data )
6542 && ! empty( $rewind_data['state'] )
6543 && 'active' === $rewind_data['state'] )
6544 ? 1
6545 : 0;
6546
6547 set_transient( 'jetpack_rewind_enabled', $rewind_enabled, 10 * MINUTE_IN_SECONDS );
6548 }
6549 return $rewind_enabled;
6550 }
6551
6552 /**
6553 * Return Calypso environment value; used for developing Jetpack and pairing
6554 * it with different Calypso enrionments, such as localhost.
6555 *
6556 * @since 7.4.0
6557 *
6558 * @return string Calypso environment
6559 */
6560 public static function get_calypso_env() {
6561 if ( isset( $_GET['calypso_env'] ) ) {
6562 return sanitize_key( $_GET['calypso_env'] );
6563 }
6564
6565 if ( getenv( 'CALYPSO_ENV' ) ) {
6566 return sanitize_key( getenv( 'CALYPSO_ENV' ) );
6567 }
6568
6569 if ( defined( 'CALYPSO_ENV' ) && CALYPSO_ENV ) {
6570 return sanitize_key( CALYPSO_ENV );
6571 }
6572
6573 return '';
6574 }
6575
6576 /**
6577 * Returns the hostname with protocol for Calypso.
6578 * Used for developing Jetpack with Calypso.
6579 *
6580 * @since 8.4.0
6581 *
6582 * @return string Calypso host.
6583 */
6584 public static function get_calypso_host() {
6585 $calypso_env = self::get_calypso_env();
6586 switch ( $calypso_env ) {
6587 case 'development':
6588 return 'http://calypso.localhost:3000/';
6589 case 'wpcalypso':
6590 return 'https://wpcalypso.wordpress.com/';
6591 case 'horizon':
6592 return 'https://horizon.wordpress.com/';
6593 default:
6594 return 'https://wordpress.com/';
6595 }
6596 }
6597
6598 /**
6599 * Handles activating default modules as well general cleanup for the new connection.
6600 *
6601 * @param boolean $activate_sso Whether to activate the SSO module when activating default modules.
6602 * @param boolean $redirect_on_activation_error Whether to redirect on activation error.
6603 * @param boolean $send_state_messages Whether to send state messages.
6604 * @return void
6605 */
6606 public static function handle_post_authorization_actions(
6607 $activate_sso = false,
6608 $redirect_on_activation_error = false,
6609 $send_state_messages = true
6610 ) {
6611 $other_modules = $activate_sso
6612 ? array( 'sso' )
6613 : array();
6614
6615 if ( Jetpack_Options::get_option( 'active_modules_initialized' ) ) {
6616 $active_modules = Jetpack_Options::get_option( 'active_modules' );
6617 self::delete_active_modules();
6618
6619 self::activate_default_modules( 999, 1, array_merge( $active_modules, $other_modules ), $redirect_on_activation_error, $send_state_messages );
6620 } else {
6621 // Default modules that don't require a user were already activated on site_register.
6622 // This time let's activate only those that require a user, this assures we don't reactivate manually deactivated modules while the site was connected only at a site level.
6623 self::activate_default_modules( false, false, $other_modules, $redirect_on_activation_error, $send_state_messages, null, true );
6624 Jetpack_Options::update_option( 'active_modules_initialized', true );
6625 }
6626
6627 // Since this is a fresh connection, be sure to clear out IDC options
6628 Identity_Crisis::clear_all_idc_options();
6629
6630 if ( $send_state_messages ) {
6631 self::state( 'message', 'authorized' );
6632 }
6633 }
6634
6635 /**
6636 * Returns a boolean for whether backups UI should be displayed or not.
6637 *
6638 * @return bool Should backups UI be displayed?
6639 */
6640 public static function show_backups_ui() {
6641 /**
6642 * Whether UI for backups should be displayed.
6643 *
6644 * @since 6.5.0
6645 *
6646 * @param bool $show_backups Should UI for backups be displayed? True by default.
6647 */
6648 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6649 }
6650
6651 /**
6652 * Clean leftoveruser meta.
6653 *
6654 * Delete Jetpack-related user meta when it is no longer needed.
6655 *
6656 * @since 7.3.0
6657 *
6658 * @param int $user_id User ID being updated.
6659 */
6660 public static function user_meta_cleanup( $user_id ) {
6661 $meta_keys = array(
6662 // AtD removed from Jetpack 7.3
6663 'AtD_options',
6664 'AtD_check_when',
6665 'AtD_guess_lang',
6666 'AtD_ignored_phrases',
6667 );
6668
6669 foreach ( $meta_keys as $meta_key ) {
6670 if ( get_user_meta( $user_id, $meta_key ) ) {
6671 delete_user_meta( $user_id, $meta_key );
6672 }
6673 }
6674 }
6675
6676 /**
6677 * Checks if a Jetpack site is both active and not in offline mode.
6678 *
6679 * This is a DRY function to avoid repeating `Jetpack::is_connection_ready && ! Automattic\Jetpack\Status->is_offline_mode`.
6680 *
6681 * @since 8.8.0
6682 *
6683 * @return bool True if Jetpack is active and not in offline mode.
6684 */
6685 public static function is_active_and_not_offline_mode() {
6686 if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() ) {
6687 return false;
6688 }
6689 return true;
6690 }
6691
6692 /**
6693 * Returns the list of products that we have available for purchase.
6694 *
6695 * This method will not take current purchases or upgrades into account
6696 * but is instead a static list of products Jetpack offers with some
6697 * corresponding sales text/materials.
6698 */
6699 public static function get_products_for_purchase() {
6700 $products = array();
6701
6702 $products['backup'] = array(
6703 'title' => __( 'Jetpack Backup', 'jetpack' ),
6704 'slug' => 'jetpack_backup_daily',
6705 'description' => __( 'Never lose a word, image, page, or time worrying about your site with automated backups & one-click restores.', 'jetpack' ),
6706 'show_promotion' => true,
6707 'discount_percent' => 40,
6708 'included_in_plans' => array( 'security' ),
6709 'features' => array(
6710 _x( 'Automated daily backups (off-site)', 'Backup Product Feature', 'jetpack' ),
6711 _x( 'One-click restores', 'Backup Product Feature', 'jetpack' ),
6712 _x( 'Unlimited backup storage', 'Backup Product Feature', 'jetpack' ),
6713 ),
6714 );
6715
6716 $products['scan'] = array(
6717 'title' => __( 'Jetpack Scan', 'jetpack' ),
6718 'slug' => 'jetpack_scan',
6719 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
6720 'show_promotion' => true,
6721 'discount_percent' => 40,
6722 'included_in_plans' => array( 'security' ),
6723 'features' => array(
6724 _x( 'Automated daily scanning', 'Scan Product Feature', 'jetpack' ),
6725 _x( 'One-click fixes for most issues', 'Scan Product Feature', 'jetpack' ),
6726 _x( 'Instant email notifications', 'Scan Product Feature', 'jetpack' ),
6727 ),
6728 );
6729
6730 $products['search'] = array(
6731 'title' => __( 'Jetpack Site Search', 'jetpack' ),
6732 'slug' => 'jetpack_search',
6733 'description' => __( 'Help your site visitors find answers instantly so they keep reading and buying. Great for sites with a lot of content.', 'jetpack' ),
6734 'show_promotion' => true,
6735 'discount_percent' => 40,
6736 'included_in_plans' => array(),
6737 'features' => array(
6738 _x( 'Instant search and indexing', 'Search Product Feature', 'jetpack' ),
6739 _x( 'Powerful filtering', 'Search Product Feature', 'jetpack' ),
6740 _x( 'Supports 29 languages', 'Search Product Feature', 'jetpack' ),
6741 _x( 'Spelling correction', 'Search Product Feature', 'jetpack' ),
6742 ),
6743 );
6744
6745 $products['akismet'] = array(
6746 'title' => __( 'Jetpack Anti-Spam', 'jetpack' ),
6747 'slug' => 'jetpack_anti_spam',
6748 'description' => __( 'Save time and get better responses by automatically blocking spam from your comments and forms.', 'jetpack' ),
6749 'show_promotion' => true,
6750 'discount_percent' => 40,
6751 'included_in_plans' => array( 'security' ),
6752 'features' => array(
6753 _x( 'Comment and form spam protection', 'Anti-Spam Product Feature', 'jetpack' ),
6754 _x( 'Powered by Akismet', 'Anti-Spam Product Feature', 'jetpack' ),
6755 _x( 'Block spam without CAPTCHAs', 'Anti-Spam Product Feature', 'jetpack' ),
6756 _x( 'Advanced stats', 'Anti-Spam Product Feature', 'jetpack' ),
6757 ),
6758 );
6759
6760 $products['security'] = array(
6761 'title' => __( 'Security Bundle', 'jetpack' ),
6762 'slug' => 'jetpack_security_daily',
6763 'description' => __( 'Get all security products including backups, site scanning, and anti-spam.', 'jetpack' ),
6764 'show_promotion' => true,
6765 'discount_percent' => 40,
6766 'included_in_plans' => array(),
6767 'features' => array(
6768 _x( 'All Backup Features ', 'Security Daily Plan Feature', 'jetpack' ),
6769 _x( 'Automated real-time malware scan', 'Security Daily Plan Feature', 'jetpack' ),
6770 _x( 'One-click fixes for most threats', 'Security Daily Plan Feature', 'jetpack' ),
6771 _x( 'Comment & form spam protection', 'Security Daily Plan Feature', 'jetpack' ),
6772 ),
6773 );
6774
6775 $products['videopress'] = array(
6776 'title' => __( 'Jetpack VideoPress', 'jetpack' ),
6777 'slug' => 'jetpack_videopress',
6778 'description' => __( 'High-quality, ad-free video built specifically for WordPress.', 'jetpack' ),
6779 'show_promotion' => true,
6780 'discount_percent' => 40,
6781 'included_in_plans' => array(),
6782 'features' => array(
6783 _x( '1TB of storage', 'VideoPress Product Feature', 'jetpack' ),
6784 _x( 'Built into WordPress editor', 'VideoPress Product Feature', 'jetpack' ),
6785 _x( 'Ad-free and brandable player', 'VideoPress Product Feature', 'jetpack' ),
6786 _x( 'Unlimited users', 'VideoPress Product Feature', 'jetpack' ),
6787 ),
6788 );
6789
6790 return $products;
6791 }
6792
6793 /**
6794 * Determine if the current user is allowed to make Jetpack purchases without
6795 * a WordPress.com account
6796 *
6797 * @return boolean True if the user can make purchases, false if not
6798 */
6799 public static function current_user_can_purchase() {
6800
6801 // The site must be site-connected to Jetpack (no users connected).
6802 if ( ! self::connection()->is_site_connection() ) {
6803 return false;
6804 }
6805
6806 // Make sure only administrators can make purchases.
6807 if ( ! current_user_can( 'manage_options' ) ) {
6808 return false;
6809 }
6810
6811 return true;
6812 }
6813
6814 }
6815