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