PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 13.4
Jetpack – WP Security, Backup, Speed, & Growth v13.4
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.4, at class.jetpack.php

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