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

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

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