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

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