PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 12.9.3
Jetpack – WP Security, Backup, Speed, & Growth v12.9.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 in Jetpack – WP Security, Backup, Speed, & Growth 12.9.3, at class.jetpack.php

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