PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 13.5
Jetpack – WP Security, Backup, Speed, & Growth v13.5
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / class.jetpack.php

class.jetpack.php in Jetpack – WP Security, Backup, Speed, & Growth 13.5, at class.jetpack.php

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