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

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

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