Completed
Push — branch-8.2 ( 34ca4d...ba6750 )
by
unknown
12:41 queued 05:38
created

class.jetpack.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
use Automattic\Jetpack\Assets;
3
use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
4
use Automattic\Jetpack\Config;
5
use Automattic\Jetpack\Connection\Client;
6
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
7
use Automattic\Jetpack\Connection\Utils as Connection_Utils;
8
use Automattic\Jetpack\Constants;
9
use Automattic\Jetpack\Partner;
10
use Automattic\Jetpack\Roles;
11
use Automattic\Jetpack\Status;
12
use Automattic\Jetpack\Sync\Functions;
13
use Automattic\Jetpack\Sync\Sender;
14
use Automattic\Jetpack\Sync\Users;
15
use Automattic\Jetpack\Terms_Of_Service;
16
use Automattic\Jetpack\Tracking;
17
use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
18
19
/*
20
Options:
21
jetpack_options (array)
22
	An array of options.
23
	@see Jetpack_Options::get_option_names()
24
25
jetpack_register (string)
26
	Temporary verification secrets.
27
28
jetpack_activated (int)
29
	1: the plugin was activated normally
30
	2: the plugin was activated on this site because of a network-wide activation
31
	3: the plugin was auto-installed
32
	4: the plugin was manually disconnected (but is still installed)
33
34
jetpack_active_modules (array)
35
	Array of active module slugs.
36
37
jetpack_do_activate (bool)
38
	Flag for "activating" the plugin on sites where the activation hook never fired (auto-installs)
39
*/
40
41
require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.media.php';
42
43
class Jetpack {
44
	public $xmlrpc_server = null;
45
46
	private $rest_authentication_status = null;
47
48
	public $HTTP_RAW_POST_DATA = null; // copy of $GLOBALS['HTTP_RAW_POST_DATA']
49
50
	/**
51
	 * @var array The handles of styles that are concatenated into jetpack.css.
52
	 *
53
	 * When making changes to that list, you must also update concat_list in tools/builder/frontend-css.js.
54
	 */
55
	public $concatenated_style_handles = array(
56
		'jetpack-carousel',
57
		'grunion.css',
58
		'the-neverending-homepage',
59
		'jetpack_likes',
60
		'jetpack_related-posts',
61
		'sharedaddy',
62
		'jetpack-slideshow',
63
		'presentations',
64
		'quiz',
65
		'jetpack-subscriptions',
66
		'jetpack-responsive-videos-style',
67
		'jetpack-social-menu',
68
		'tiled-gallery',
69
		'jetpack_display_posts_widget',
70
		'gravatar-profile-widget',
71
		'goodreads-widget',
72
		'jetpack_social_media_icons_widget',
73
		'jetpack-top-posts-widget',
74
		'jetpack_image_widget',
75
		'jetpack-my-community-widget',
76
		'jetpack-authors-widget',
77
		'wordads',
78
		'eu-cookie-law-style',
79
		'flickr-widget-style',
80
		'jetpack-search-widget',
81
		'jetpack-simple-payments-widget-style',
82
		'jetpack-widget-social-icons-styles',
83
	);
84
85
	/**
86
	 * The handles of scripts that can be loaded asynchronously.
87
	 *
88
	 * @var array
89
	 */
90
	public $async_script_handles = array(
91
		'woocommerce-analytics',
92
	);
93
94
	/**
95
	 * Contains all assets that have had their URL rewritten to minified versions.
96
	 *
97
	 * @var array
98
	 */
99
	static $min_assets = array();
100
101
	public $plugins_to_deactivate = array(
102
		'stats'               => array( 'stats/stats.php', 'WordPress.com Stats' ),
103
		'shortlinks'          => array( 'stats/stats.php', 'WordPress.com Stats' ),
104
		'sharedaddy'          => array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
105
		'twitter-widget'      => array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
106
		'contact-form'        => array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
107
		'contact-form'        => array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
108
		'custom-css'          => array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
109
		'random-redirect'     => array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
110
		'videopress'          => array( 'video/video.php', 'VideoPress' ),
111
		'widget-visibility'   => array( 'jetpack-widget-visibility/widget-visibility.php', 'Jetpack Widget Visibility' ),
112
		'widget-visibility'   => array( 'widget-visibility-without-jetpack/widget-visibility-without-jetpack.php', 'Widget Visibility Without Jetpack' ),
113
		'sharedaddy'          => array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
114
		'gravatar-hovercards' => array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
115
		'latex'               => array( 'wp-latex/wp-latex.php', 'WP LaTeX' ),
116
	);
117
118
	/**
119
	 * Map of roles we care about, and their corresponding minimum capabilities.
120
	 *
121
	 * @deprecated 7.6 Use Automattic\Jetpack\Roles::$capability_translations instead.
122
	 *
123
	 * @access public
124
	 * @static
125
	 *
126
	 * @var array
127
	 */
128
	public static $capability_translations = array(
129
		'administrator' => 'manage_options',
130
		'editor'        => 'edit_others_posts',
131
		'author'        => 'publish_posts',
132
		'contributor'   => 'edit_posts',
133
		'subscriber'    => 'read',
134
	);
135
136
	/**
137
	 * Map of modules that have conflicts with plugins and should not be auto-activated
138
	 * if the plugins are active.  Used by filter_default_modules
139
	 *
140
	 * Plugin Authors: If you'd like to prevent a single module from auto-activating,
141
	 * change `module-slug` and add this to your plugin:
142
	 *
143
	 * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
144
	 * function my_jetpack_get_default_modules( $modules ) {
145
	 *     return array_diff( $modules, array( 'module-slug' ) );
146
	 * }
147
	 *
148
	 * @var array
149
	 */
150
	private $conflicting_plugins = array(
151
		'comments'           => array(
152
			'Intense Debate'                 => 'intensedebate/intensedebate.php',
153
			'Disqus'                         => 'disqus-comment-system/disqus.php',
154
			'Livefyre'                       => 'livefyre-comments/livefyre.php',
155
			'Comments Evolved for WordPress' => 'gplus-comments/comments-evolved.php',
156
			'Google+ Comments'               => 'google-plus-comments/google-plus-comments.php',
157
			'WP-SpamShield Anti-Spam'        => 'wp-spamshield/wp-spamshield.php',
158
		),
159
		'comment-likes'      => array(
160
			'Epoch' => 'epoch/plugincore.php',
161
		),
162
		'contact-form'       => array(
163
			'Contact Form 7'           => 'contact-form-7/wp-contact-form-7.php',
164
			'Gravity Forms'            => 'gravityforms/gravityforms.php',
165
			'Contact Form Plugin'      => 'contact-form-plugin/contact_form.php',
166
			'Easy Contact Forms'       => 'easy-contact-forms/easy-contact-forms.php',
167
			'Fast Secure Contact Form' => 'si-contact-form/si-contact-form.php',
168
			'Ninja Forms'              => 'ninja-forms/ninja-forms.php',
169
		),
170
		'minileven'          => array(
171
			'WPtouch' => 'wptouch/wptouch.php',
172
		),
173
		'latex'              => array(
174
			'LaTeX for WordPress'     => 'latex/latex.php',
175
			'Youngwhans Simple Latex' => 'youngwhans-simple-latex/yw-latex.php',
176
			'Easy WP LaTeX'           => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
177
			'MathJax-LaTeX'           => 'mathjax-latex/mathjax-latex.php',
178
			'Enable Latex'            => 'enable-latex/enable-latex.php',
179
			'WP QuickLaTeX'           => 'wp-quicklatex/wp-quicklatex.php',
180
		),
181
		'protect'            => array(
182
			'Limit Login Attempts'              => 'limit-login-attempts/limit-login-attempts.php',
183
			'Captcha'                           => 'captcha/captcha.php',
184
			'Brute Force Login Protection'      => 'brute-force-login-protection/brute-force-login-protection.php',
185
			'Login Security Solution'           => 'login-security-solution/login-security-solution.php',
186
			'WPSecureOps Brute Force Protect'   => 'wpsecureops-bruteforce-protect/wpsecureops-bruteforce-protect.php',
187
			'BulletProof Security'              => 'bulletproof-security/bulletproof-security.php',
188
			'SiteGuard WP Plugin'               => 'siteguard/siteguard.php',
189
			'Security-protection'               => 'security-protection/security-protection.php',
190
			'Login Security'                    => 'login-security/login-security.php',
191
			'Botnet Attack Blocker'             => 'botnet-attack-blocker/botnet-attack-blocker.php',
192
			'Wordfence Security'                => 'wordfence/wordfence.php',
193
			'All In One WP Security & Firewall' => 'all-in-one-wp-security-and-firewall/wp-security.php',
194
			'iThemes Security'                  => 'better-wp-security/better-wp-security.php',
195
		),
196
		'random-redirect'    => array(
197
			'Random Redirect 2' => 'random-redirect-2/random-redirect.php',
198
		),
199
		'related-posts'      => array(
200
			'YARPP'                       => 'yet-another-related-posts-plugin/yarpp.php',
201
			'WordPress Related Posts'     => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
202
			'nrelate Related Content'     => 'nrelate-related-content/nrelate-related.php',
203
			'Contextual Related Posts'    => 'contextual-related-posts/contextual-related-posts.php',
204
			'Related Posts for WordPress' => 'microkids-related-posts/microkids-related-posts.php',
205
			'outbrain'                    => 'outbrain/outbrain.php',
206
			'Shareaholic'                 => 'shareaholic/shareaholic.php',
207
			'Sexybookmarks'               => 'sexybookmarks/shareaholic.php',
208
		),
209
		'sharedaddy'         => array(
210
			'AddThis'     => 'addthis/addthis_social_widget.php',
211
			'Add To Any'  => 'add-to-any/add-to-any.php',
212
			'ShareThis'   => 'share-this/sharethis.php',
213
			'Shareaholic' => 'shareaholic/shareaholic.php',
214
		),
215
		'seo-tools'          => array(
216
			'WordPress SEO by Yoast'         => 'wordpress-seo/wp-seo.php',
217
			'WordPress SEO Premium by Yoast' => 'wordpress-seo-premium/wp-seo-premium.php',
218
			'All in One SEO Pack'            => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
219
			'All in One SEO Pack Pro'        => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
220
			'The SEO Framework'              => 'autodescription/autodescription.php',
221
		),
222
		'verification-tools' => array(
223
			'WordPress SEO by Yoast'         => 'wordpress-seo/wp-seo.php',
224
			'WordPress SEO Premium by Yoast' => 'wordpress-seo-premium/wp-seo-premium.php',
225
			'All in One SEO Pack'            => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
226
			'All in One SEO Pack Pro'        => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
227
			'The SEO Framework'              => 'autodescription/autodescription.php',
228
		),
229
		'widget-visibility'  => array(
230
			'Widget Logic'    => 'widget-logic/widget_logic.php',
231
			'Dynamic Widgets' => 'dynamic-widgets/dynamic-widgets.php',
232
		),
233
		'sitemaps'           => array(
234
			'Google XML Sitemaps'                  => 'google-sitemap-generator/sitemap.php',
235
			'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
236
			'Google XML Sitemaps for qTranslate'   => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php',
237
			'XML Sitemap & Google News feeds'      => 'xml-sitemap-feed/xml-sitemap.php',
238
			'Google Sitemap by BestWebSoft'        => 'google-sitemap-plugin/google-sitemap-plugin.php',
239
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
240
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
241
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
242
			'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
243
			'The SEO Framework'                    => 'autodescription/autodescription.php',
244
			'Sitemap'                              => 'sitemap/sitemap.php',
245
			'Simple Wp Sitemap'                    => 'simple-wp-sitemap/simple-wp-sitemap.php',
246
			'Simple Sitemap'                       => 'simple-sitemap/simple-sitemap.php',
247
			'XML Sitemaps'                         => 'xml-sitemaps/xml-sitemaps.php',
248
			'MSM Sitemaps'                         => 'msm-sitemap/msm-sitemap.php',
249
		),
250
		'lazy-images'        => array(
251
			'Lazy Load'              => 'lazy-load/lazy-load.php',
252
			'BJ Lazy Load'           => 'bj-lazy-load/bj-lazy-load.php',
253
			'Lazy Load by WP Rocket' => 'rocket-lazy-load/rocket-lazy-load.php',
254
		),
255
	);
256
257
	/**
258
	 * Plugins for which we turn off our Facebook OG Tags implementation.
259
	 *
260
	 * Note: All in One SEO Pack, All in one SEO Pack Pro, WordPress SEO by Yoast, and WordPress SEO Premium by Yoast automatically deactivate
261
	 * Jetpack's Open Graph tags via filter when their Social Meta modules are active.
262
	 *
263
	 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
264
	 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
265
	 */
266
	private $open_graph_conflicting_plugins = array(
267
		'2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
268
		// 2 Click Social Media Buttons
269
		'add-link-to-facebook/add-link-to-facebook.php',         // Add Link to Facebook
270
		'add-meta-tags/add-meta-tags.php',                       // Add Meta Tags
271
		'complete-open-graph/complete-open-graph.php',           // Complete Open Graph
272
		'easy-facebook-share-thumbnails/esft.php',               // Easy Facebook Share Thumbnail
273
		'heateor-open-graph-meta-tags/heateor-open-graph-meta-tags.php',
274
		// Open Graph Meta Tags by Heateor
275
		'facebook/facebook.php',                                 // Facebook (official plugin)
276
		'facebook-awd/AWD_facebook.php',                         // Facebook AWD All in one
277
		'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
278
		// Facebook Featured Image & OG Meta Tags
279
		'facebook-meta-tags/facebook-metatags.php',              // Facebook Meta Tags
280
		'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
281
		// Facebook Open Graph Meta Tags for WordPress
282
		'facebook-revised-open-graph-meta-tag/index.php',        // Facebook Revised Open Graph Meta Tag
283
		'facebook-thumb-fixer/_facebook-thumb-fixer.php',        // Facebook Thumb Fixer
284
		'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
285
		// Fedmich's Facebook Open Graph Meta
286
		'network-publisher/networkpub.php',                      // Network Publisher
287
		'nextgen-facebook/nextgen-facebook.php',                 // NextGEN Facebook OG
288
		'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php',
289
		// NextScripts SNAP
290
		'og-tags/og-tags.php',                                   // OG Tags
291
		'opengraph/opengraph.php',                               // Open Graph
292
		'open-graph-protocol-framework/open-graph-protocol-framework.php',
293
		// Open Graph Protocol Framework
294
		'seo-facebook-comments/seofacebook.php',                 // SEO Facebook Comments
295
		'seo-ultimate/seo-ultimate.php',                         // SEO Ultimate
296
		'sexybookmarks/sexy-bookmarks.php',                      // Shareaholic
297
		'shareaholic/sexy-bookmarks.php',                        // Shareaholic
298
		'sharepress/sharepress.php',                             // SharePress
299
		'simple-facebook-connect/sfc.php',                       // Simple Facebook Connect
300
		'social-discussions/social-discussions.php',             // Social Discussions
301
		'social-sharing-toolkit/social_sharing_toolkit.php',     // Social Sharing Toolkit
302
		'socialize/socialize.php',                               // Socialize
303
		'squirrly-seo/squirrly.php',                             // SEO by SQUIRRLY™
304
		'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
305
		// Tweet, Like, Google +1 and Share
306
		'wordbooker/wordbooker.php',                             // Wordbooker
307
		'wpsso/wpsso.php',                                       // WordPress Social Sharing Optimization
308
		'wp-caregiver/wp-caregiver.php',                         // WP Caregiver
309
		'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
310
		// WP Facebook Like Send & Open Graph Meta
311
		'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',   // WP Facebook Open Graph protocol
312
		'wp-ogp/wp-ogp.php',                                     // WP-OGP
313
		'zoltonorg-social-plugin/zosp.php',                      // Zolton.org Social Plugin
314
		'wp-fb-share-like-button/wp_fb_share-like_widget.php',   // WP Facebook Like Button
315
		'open-graph-metabox/open-graph-metabox.php',              // Open Graph Metabox
316
	);
317
318
	/**
319
	 * Plugins for which we turn off our Twitter Cards Tags implementation.
320
	 */
321
	private $twitter_cards_conflicting_plugins = array(
322
		// 'twitter/twitter.php',                       // The official one handles this on its own.
323
		// https://github.com/twitter/wordpress/blob/master/src/Twitter/WordPress/Cards/Compatibility.php
324
			'eewee-twitter-card/index.php',              // Eewee Twitter Card
325
		'ig-twitter-cards/ig-twitter-cards.php',     // IG:Twitter Cards
326
		'jm-twitter-cards/jm-twitter-cards.php',     // JM Twitter Cards
327
		'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',
328
		// Pure Web Brilliant's Social Graph Twitter Cards Extension
329
		'twitter-cards/twitter-cards.php',           // Twitter Cards
330
		'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta
331
		'wp-to-twitter/wp-to-twitter.php',           // WP to Twitter
332
		'wp-twitter-cards/twitter_cards.php',        // WP Twitter Cards
333
	);
334
335
	/**
336
	 * Message to display in admin_notice
337
	 *
338
	 * @var string
339
	 */
340
	public $message = '';
341
342
	/**
343
	 * Error to display in admin_notice
344
	 *
345
	 * @var string
346
	 */
347
	public $error = '';
348
349
	/**
350
	 * Modules that need more privacy description.
351
	 *
352
	 * @var string
353
	 */
354
	public $privacy_checks = '';
355
356
	/**
357
	 * Stats to record once the page loads
358
	 *
359
	 * @var array
360
	 */
361
	public $stats = array();
362
363
	/**
364
	 * Jetpack_Sync object
365
	 */
366
	public $sync;
367
368
	/**
369
	 * Verified data for JSON authorization request
370
	 */
371
	public $json_api_authorization_request = array();
372
373
	/**
374
	 * @var Automattic\Jetpack\Connection\Manager
375
	 */
376
	protected $connection_manager;
377
378
	/**
379
	 * @var string Transient key used to prevent multiple simultaneous plugin upgrades
380
	 */
381
	public static $plugin_upgrade_lock_key = 'jetpack_upgrade_lock';
382
383
	/**
384
	 * Holds the singleton instance of this class
385
	 *
386
	 * @since 2.3.3
387
	 * @var Jetpack
388
	 */
389
	static $instance = false;
390
391
	/**
392
	 * Singleton
393
	 *
394
	 * @static
395
	 */
396
	public static function init() {
397
		if ( ! self::$instance ) {
398
			self::$instance = new Jetpack();
399
			add_action( 'plugins_loaded', array( self::$instance, 'plugin_upgrade' ) );
400
		}
401
402
		return self::$instance;
403
	}
404
405
	/**
406
	 * Must never be called statically
407
	 */
408
	function plugin_upgrade() {
409
		if ( self::is_active() ) {
410
			list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
411
			if ( JETPACK__VERSION != $version ) {
412
				// Prevent multiple upgrades at once - only a single process should trigger
413
				// an upgrade to avoid stampedes
414
				if ( false !== get_transient( self::$plugin_upgrade_lock_key ) ) {
415
					return;
416
				}
417
418
				// Set a short lock to prevent multiple instances of the upgrade
419
				set_transient( self::$plugin_upgrade_lock_key, 1, 10 );
420
421
				// check which active modules actually exist and remove others from active_modules list
422
				$unfiltered_modules = self::get_active_modules();
423
				$modules            = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) );
424
				if ( array_diff( $unfiltered_modules, $modules ) ) {
425
					self::update_active_modules( $modules );
426
				}
427
428
				add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
429
430
				// Upgrade to 4.3.0
431
				if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
432
					Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
433
				}
434
435
				// Make sure Markdown for posts gets turned back on
436
				if ( ! get_option( 'wpcom_publish_posts_with_markdown' ) ) {
437
					update_option( 'wpcom_publish_posts_with_markdown', true );
438
				}
439
440
				if ( did_action( 'wp_loaded' ) ) {
441
					self::upgrade_on_load();
442
				} else {
443
					add_action(
444
						'wp_loaded',
445
						array( __CLASS__, 'upgrade_on_load' )
446
					);
447
				}
448
			}
449
		}
450
	}
451
452
	/**
453
	 * Runs upgrade routines that need to have modules loaded.
454
	 */
455
	static function upgrade_on_load() {
456
457
		// Not attempting any upgrades if jetpack_modules_loaded did not fire.
458
		// This can happen in case Jetpack has been just upgraded and is
459
		// being initialized late during the page load. In this case we wait
460
		// until the next proper admin page load with Jetpack active.
461
		if ( ! did_action( 'jetpack_modules_loaded' ) ) {
462
			delete_transient( self::$plugin_upgrade_lock_key );
463
464
			return;
465
		}
466
467
		self::maybe_set_version_option();
468
469
		if ( method_exists( 'Jetpack_Widget_Conditions', 'migrate_post_type_rules' ) ) {
470
			Jetpack_Widget_Conditions::migrate_post_type_rules();
471
		}
472
473
		if (
474
			class_exists( 'Jetpack_Sitemap_Manager' )
475
			&& version_compare( JETPACK__VERSION, '5.3', '>=' )
476
		) {
477
			do_action( 'jetpack_sitemaps_purge_data' );
478
		}
479
480
		// Delete old stats cache
481
		delete_option( 'jetpack_restapi_stats_cache' );
482
483
		delete_transient( self::$plugin_upgrade_lock_key );
484
	}
485
486
	/**
487
	 * Saves all the currently active modules to options.
488
	 * Also fires Action hooks for each newly activated and deactivated module.
489
	 *
490
	 * @param $modules Array Array of active modules to be saved in options.
491
	 *
492
	 * @return $success bool true for success, false for failure.
493
	 */
494
	static function update_active_modules( $modules ) {
495
		$current_modules      = Jetpack_Options::get_option( 'active_modules', array() );
496
		$active_modules       = self::get_active_modules();
497
		$new_active_modules   = array_diff( $modules, $current_modules );
498
		$new_inactive_modules = array_diff( $active_modules, $modules );
499
		$new_current_modules  = array_diff( array_merge( $current_modules, $new_active_modules ), $new_inactive_modules );
500
		$reindexed_modules    = array_values( $new_current_modules );
501
		$success              = Jetpack_Options::update_option( 'active_modules', array_unique( $reindexed_modules ) );
502
503
		foreach ( $new_active_modules as $module ) {
504
			/**
505
			 * Fires when a specific module is activated.
506
			 *
507
			 * @since 1.9.0
508
			 *
509
			 * @param string $module Module slug.
510
			 * @param boolean $success whether the module was activated. @since 4.2
511
			 */
512
			do_action( 'jetpack_activate_module', $module, $success );
513
			/**
514
			 * Fires when a module is activated.
515
			 * The dynamic part of the filter, $module, is the module slug.
516
			 *
517
			 * @since 1.9.0
518
			 *
519
			 * @param string $module Module slug.
520
			 */
521
			do_action( "jetpack_activate_module_$module", $module );
522
		}
523
524
		foreach ( $new_inactive_modules as $module ) {
525
			/**
526
			 * Fired after a module has been deactivated.
527
			 *
528
			 * @since 4.2.0
529
			 *
530
			 * @param string $module Module slug.
531
			 * @param boolean $success whether the module was deactivated.
532
			 */
533
			do_action( 'jetpack_deactivate_module', $module, $success );
534
			/**
535
			 * Fires when a module is deactivated.
536
			 * The dynamic part of the filter, $module, is the module slug.
537
			 *
538
			 * @since 1.9.0
539
			 *
540
			 * @param string $module Module slug.
541
			 */
542
			do_action( "jetpack_deactivate_module_$module", $module );
543
		}
544
545
		return $success;
546
	}
547
548
	static function delete_active_modules() {
549
		self::update_active_modules( array() );
550
	}
551
552
	/**
553
	 * Adds a hook to plugins_loaded at a priority that's currently the earliest
554
	 * available.
555
	 */
556
	public function add_configure_hook() {
557
		global $wp_filter;
558
559
		$current_priority = has_filter( 'plugins_loaded', array( $this, 'configure' ) );
560
		if ( false !== $current_priority ) {
561
			remove_action( 'plugins_loaded', array( $this, 'configure' ), $current_priority );
562
		}
563
564
		$taken_priorities = array_map( 'intval', array_keys( $wp_filter['plugins_loaded']->callbacks ) );
565
		sort( $taken_priorities );
566
567
		$first_priority = array_shift( $taken_priorities );
568
569
		if ( defined( 'PHP_INT_MAX' ) && $first_priority <= - PHP_INT_MAX ) {
570
			trigger_error( // phpcs:ignore
571
				/* translators: plugins_loaded is a filter name in WordPress, no need to translate. */
572
				__( 'A plugin on your site is using the plugins_loaded filter with a priority that is too high. Jetpack does not support this, you may experience problems.', 'jetpack' ), // phpcs:ignore
573
				E_USER_NOTICE
574
			);
575
			$new_priority = - PHP_INT_MAX;
576
		} else {
577
			$new_priority = $first_priority - 1;
578
		}
579
580
		add_action( 'plugins_loaded', array( $this, 'configure' ), $new_priority );
581
	}
582
583
	/**
584
	 * Constructor.  Initializes WordPress hooks
585
	 */
586
	private function __construct() {
587
		/*
588
		 * Check for and alert any deprecated hooks
589
		 */
590
		add_action( 'init', array( $this, 'deprecated_hooks' ) );
591
592
		// Note how this runs at an earlier plugin_loaded hook intentionally to accomodate for other plugins.
593
		add_action( 'plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
594
		add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
595
596
		add_filter(
597
			'jetpack_connection_secret_generator',
598
			function( $callable ) {
599
				return function() {
600
					return wp_generate_password( 32, false );
601
				};
602
			}
603
		);
604
605
		add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
606
607
		add_filter(
608
			'jetpack_signature_check_token',
609
			array( __CLASS__, 'verify_onboarding_token' ),
610
			10,
611
			3
612
		);
613
614
		/**
615
		 * Prepare Gutenberg Editor functionality
616
		 */
617
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
618
		add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'init' ) );
619
		add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
620
		add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
621
622
		add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
623
624
		// Unlink user before deleting the user from WP.com.
625
		add_action( 'deleted_user', array( 'Automattic\\Jetpack\\Connection\\Manager', 'disconnect_user' ), 10, 1 );
626
		add_action( 'remove_user_from_blog', array( 'Automattic\\Jetpack\\Connection\\Manager', 'disconnect_user' ), 10, 1 );
627
628
		add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
629
630
		add_filter( 'determine_current_user', array( $this, 'wp_rest_authenticate' ) );
631
		add_filter( 'rest_authentication_errors', array( $this, 'wp_rest_authentication_errors' ) );
632
633
		add_action( 'admin_init', array( $this, 'admin_init' ) );
634
		add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
635
636
		add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
637
638
		add_action( 'wp_dashboard_setup', array( $this, 'wp_dashboard_setup' ) );
639
		// Filter the dashboard meta box order to swap the new one in in place of the old one.
640
		add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) );
641
642
		// returns HTTPS support status
643
		add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) );
644
645
		add_action( 'wp_ajax_jetpack_connection_banner', array( $this, 'jetpack_connection_banner_callback' ) );
646
647
		add_action( 'wp_loaded', array( $this, 'register_assets' ) );
648
649
		add_action( 'plugins_loaded', array( $this, 'extra_oembed_providers' ), 100 );
650
651
		/**
652
		 * These actions run checks to load additional files.
653
		 * They check for external files or plugins, so they need to run as late as possible.
654
		 */
655
		add_action( 'wp_head', array( $this, 'check_open_graph' ), 1 );
656
		add_action( 'amp_story_head', array( $this, 'check_open_graph' ), 1 );
657
		add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ), 999 );
658
		add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
659
660
		add_filter( 'plugins_url', array( 'Jetpack', 'maybe_min_asset' ), 1, 3 );
661
		add_action( 'style_loader_src', array( 'Jetpack', 'set_suffix_on_min' ), 10, 2 );
662
		add_filter( 'style_loader_tag', array( 'Jetpack', 'maybe_inline_style' ), 10, 2 );
663
664
		add_filter( 'profile_update', array( 'Jetpack', 'user_meta_cleanup' ) );
665
666
		add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
667
		add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
668
669
		// A filter to control all just in time messages
670
		add_filter( 'jetpack_just_in_time_msgs', array( $this, 'is_active_and_not_development_mode' ), 9 );
671
672
		add_filter( 'jetpack_just_in_time_msg_cache', '__return_true', 9 );
673
674
		// Hide edit post link if mobile app.
675
		if ( Jetpack_User_Agent_Info::is_mobile_app() ) {
676
			add_filter( 'edit_post_link', '__return_empty_string' );
677
		}
678
679
		// Update the Jetpack plan from API on heartbeats
680
		add_action( 'jetpack_heartbeat', array( 'Jetpack_Plan', 'refresh_from_wpcom' ) );
681
682
		/**
683
		 * This is the hack to concatenate all css files into one.
684
		 * For description and reasoning see the implode_frontend_css method
685
		 *
686
		 * Super late priority so we catch all the registered styles
687
		 */
688
		if ( ! is_admin() ) {
689
			add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first
690
			add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`
691
		}
692
693
		/**
694
		 * These are sync actions that we need to keep track of for jitms
695
		 */
696
		add_filter( 'jetpack_sync_before_send_updated_option', array( $this, 'jetpack_track_last_sync_callback' ), 99 );
697
698
		// Actually push the stats on shutdown.
699
		if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
700
			add_action( 'shutdown', array( $this, 'push_stats' ) );
701
		}
702
703
		/*
704
		 * Load some scripts asynchronously.
705
		 */
706
		add_action( 'script_loader_tag', array( $this, 'script_add_async' ), 10, 3 );
707
708
		// Actions for Manager::authorize().
709
		add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
710
		add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
711
		add_action( 'jetpack_authorize_ending_authorized', array( $this, 'authorize_ending_authorized' ) );
712
713
		// Filters for the Manager::get_token() urls and request body.
714
		add_filter( 'jetpack_token_processing_url', array( __CLASS__, 'filter_connect_processing_url' ) );
715
		add_filter( 'jetpack_token_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
716
		add_filter( 'jetpack_token_request_body', array( __CLASS__, 'filter_token_request_body' ) );
717
	}
718
719
	/**
720
	 * Before everything else starts getting initalized, we need to initialize Jetpack using the
721
	 * Config object.
722
	 */
723
	public function configure() {
724
		$config = new Config();
725
726
		foreach (
727
			array(
728
				'connection',
729
				'sync',
730
				'tracking',
731
				'tos',
732
			)
733
			as $feature
734
		) {
735
			$config->ensure( $feature );
736
		}
737
738
		if ( is_admin() ) {
739
			$config->ensure( 'jitm' );
740
		}
741
742
		$this->connection_manager = new Connection_Manager();
743
744
		/*
745
		 * Load things that should only be in Network Admin.
746
		 *
747
		 * For now blow away everything else until a more full
748
		 * understanding of what is needed at the network level is
749
		 * available
750
		 */
751
		if ( is_multisite() ) {
752
			$network = Jetpack_Network::init();
753
			$network->set_connection( $this->connection_manager );
754
		}
755
756
		if ( $this->connection_manager->is_active() ) {
757
			add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
758
759
			Jetpack_Heartbeat::init();
760
			if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
761
				require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
762
				Jetpack_Search_Performance_Logger::init();
763
			}
764
		}
765
766
		// Initialize remote file upload request handlers.
767
		$this->add_remote_request_handlers();
768
769
		/*
770
		 * Enable enhanced handling of previewing sites in Calypso
771
		 */
772
		if ( self::is_active() ) {
773
			require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
774
			add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
775
			require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
776
			add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
777
		}
778
779
		/*
780
		 * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
781
		 * We should make sure to only do this for front end links.
782
		 */
783
		if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
784
			add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
785
			add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
786
787
			/*
788
			 * We'll override wp_notify_postauthor and wp_notify_moderator pluggable functions
789
			 * so they point moderation links on emails to Calypso.
790
			 */
791
			jetpack_require_lib( 'functions.wp-notify' );
792
		}
793
794
	}
795
796
	/**
797
	 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
798
	 * initialization code.
799
	 *
800
	 * @action plugins_loaded
801
	 */
802
	public function late_initialization() {
803
		add_action( 'plugins_loaded', array( 'Jetpack', 'plugin_textdomain' ), 99 );
804
		add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
805
806
		Partner::init();
807
808
		/**
809
		 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
810
		 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
811
		 *
812
		 * @since 8.1.0
813
		 *
814
		 * @param Jetpack $jetpack the main plugin class object.
815
		 */
816
		do_action( 'jetpack_loaded', $this );
817
818
		add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 4 );
819
	}
820
821
	/**
822
	 * Sets up the XMLRPC request handlers.
823
	 *
824
	 * @deprecated since 7.7.0
825
	 * @see Automattic\Jetpack\Connection\Manager::setup_xmlrpc_handlers()
826
	 *
827
	 * @param Array                 $request_params Incoming request parameters.
828
	 * @param Boolean               $is_active      Whether the connection is currently active.
829
	 * @param Boolean               $is_signed      Whether the signature check has been successful.
830
	 * @param Jetpack_XMLRPC_Server $xmlrpc_server  (optional) An instance of the server to use instead of instantiating a new one.
831
	 */
832
	public function setup_xmlrpc_handlers(
833
		$request_params,
834
		$is_active,
835
		$is_signed,
836
		Jetpack_XMLRPC_Server $xmlrpc_server = null
837
	) {
838
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::setup_xmlrpc_handlers' );
839
		return $this->connection_manager->setup_xmlrpc_handlers(
840
			$request_params,
841
			$is_active,
842
			$is_signed,
843
			$xmlrpc_server
844
		);
845
	}
846
847
	/**
848
	 * Initialize REST API registration connector.
849
	 *
850
	 * @deprecated since 7.7.0
851
	 * @see Automattic\Jetpack\Connection\Manager::initialize_rest_api_registration_connector()
852
	 */
853
	public function initialize_rest_api_registration_connector() {
854
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::initialize_rest_api_registration_connector' );
855
		$this->connection_manager->initialize_rest_api_registration_connector();
856
	}
857
858
	/**
859
	 * This is ported over from the manage module, which has been deprecated and baked in here.
860
	 *
861
	 * @param $domains
862
	 */
863
	function add_wpcom_to_allowed_redirect_hosts( $domains ) {
864
		add_filter( 'allowed_redirect_hosts', array( $this, 'allow_wpcom_domain' ) );
865
	}
866
867
	/**
868
	 * Return $domains, with 'wordpress.com' appended.
869
	 * This is ported over from the manage module, which has been deprecated and baked in here.
870
	 *
871
	 * @param $domains
872
	 * @return array
873
	 */
874
	function allow_wpcom_domain( $domains ) {
875
		if ( empty( $domains ) ) {
876
			$domains = array();
877
		}
878
		$domains[] = 'wordpress.com';
879
		return array_unique( $domains );
880
	}
881
882
	function point_edit_post_links_to_calypso( $default_url, $post_id ) {
883
		$post = get_post( $post_id );
884
885
		if ( empty( $post ) ) {
886
			return $default_url;
887
		}
888
889
		$post_type = $post->post_type;
890
891
		// Mapping the allowed CPTs on WordPress.com to corresponding paths in Calypso.
892
		// https://en.support.wordpress.com/custom-post-types/
893
		$allowed_post_types = array(
894
			'post'                => 'post',
895
			'page'                => 'page',
896
			'jetpack-portfolio'   => 'edit/jetpack-portfolio',
897
			'jetpack-testimonial' => 'edit/jetpack-testimonial',
898
		);
899
900
		if ( ! in_array( $post_type, array_keys( $allowed_post_types ) ) ) {
901
			return $default_url;
902
		}
903
904
		$path_prefix = $allowed_post_types[ $post_type ];
905
906
		$site_slug = self::build_raw_urls( get_home_url() );
907
908
		return esc_url( sprintf( 'https://wordpress.com/%s/%s/%d', $path_prefix, $site_slug, $post_id ) );
909
	}
910
911
	function point_edit_comment_links_to_calypso( $url ) {
912
		// Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
913
		wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
914
		return esc_url(
915
			sprintf(
916
				'https://wordpress.com/comment/%s/%d',
917
				self::build_raw_urls( get_home_url() ),
918
				$query_args['amp;c']
919
			)
920
		);
921
	}
922
923
	function jetpack_track_last_sync_callback( $params ) {
924
		/**
925
		 * Filter to turn off jitm caching
926
		 *
927
		 * @since 5.4.0
928
		 *
929
		 * @param bool false Whether to cache just in time messages
930
		 */
931
		if ( ! apply_filters( 'jetpack_just_in_time_msg_cache', false ) ) {
932
			return $params;
933
		}
934
935
		if ( is_array( $params ) && isset( $params[0] ) ) {
936
			$option = $params[0];
937
			if ( 'active_plugins' === $option ) {
938
				// use the cache if we can, but not terribly important if it gets evicted
939
				set_transient( 'jetpack_last_plugin_sync', time(), HOUR_IN_SECONDS );
940
			}
941
		}
942
943
		return $params;
944
	}
945
946
	function jetpack_connection_banner_callback() {
947
		check_ajax_referer( 'jp-connection-banner-nonce', 'nonce' );
948
949
		if ( isset( $_REQUEST['dismissBanner'] ) ) {
950
			Jetpack_Options::update_option( 'dismissed_connection_banner', 1 );
951
			wp_send_json_success();
952
		}
953
954
		wp_die();
955
	}
956
957
	/**
958
	 * Removes all XML-RPC methods that are not `jetpack.*`.
959
	 * Only used in our alternate XML-RPC endpoint, where we want to
960
	 * ensure that Core and other plugins' methods are not exposed.
961
	 *
962
	 * @deprecated since 7.7.0
963
	 * @see Automattic\Jetpack\Connection\Manager::remove_non_jetpack_xmlrpc_methods()
964
	 *
965
	 * @param array $methods A list of registered WordPress XMLRPC methods.
966
	 * @return array Filtered $methods
967
	 */
968
	public function remove_non_jetpack_xmlrpc_methods( $methods ) {
969
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::remove_non_jetpack_xmlrpc_methods' );
970
		return $this->connection_manager->remove_non_jetpack_xmlrpc_methods( $methods );
971
	}
972
973
	/**
974
	 * Since a lot of hosts use a hammer approach to "protecting" WordPress sites,
975
	 * and just blanket block all requests to /xmlrpc.php, or apply other overly-sensitive
976
	 * security/firewall policies, we provide our own alternate XML RPC API endpoint
977
	 * which is accessible via a different URI. Most of the below is copied directly
978
	 * from /xmlrpc.php so that we're replicating it as closely as possible.
979
	 *
980
	 * @deprecated since 7.7.0
981
	 * @see Automattic\Jetpack\Connection\Manager::alternate_xmlrpc()
982
	 */
983
	public function alternate_xmlrpc() {
984
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::alternate_xmlrpc' );
985
		$this->connection_manager->alternate_xmlrpc();
986
	}
987
988
	/**
989
	 * The callback for the JITM ajax requests.
990
	 *
991
	 * @deprecated since 7.9.0
992
	 */
993
	function jetpack_jitm_ajax_callback() {
994
		_deprecated_function( __METHOD__, 'jetpack-7.9' );
995
	}
996
997
	/**
998
	 * If there are any stats that need to be pushed, but haven't been, push them now.
999
	 */
1000
	function push_stats() {
1001
		if ( ! empty( $this->stats ) ) {
1002
			$this->do_stats( 'server_side' );
1003
		}
1004
	}
1005
1006
	function jetpack_custom_caps( $caps, $cap, $user_id, $args ) {
1007
		$is_development_mode = ( new Status() )->is_development_mode();
1008
		switch ( $cap ) {
1009
			case 'jetpack_connect':
1010
			case 'jetpack_reconnect':
1011
				if ( $is_development_mode ) {
1012
					$caps = array( 'do_not_allow' );
1013
					break;
1014
				}
1015
				/**
1016
				 * Pass through. If it's not development mode, these should match disconnect.
1017
				 * Let users disconnect if it's development mode, just in case things glitch.
1018
				 */
1019
			case 'jetpack_disconnect':
1020
				/**
1021
				 * In multisite, can individual site admins manage their own connection?
1022
				 *
1023
				 * Ideally, this should be extracted out to a separate filter in the Jetpack_Network class.
1024
				 */
1025
				if ( is_multisite() && ! is_super_admin() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
1026
					if ( ! Jetpack_Network::init()->get_option( 'sub-site-connection-override' ) ) {
1027
						/**
1028
						 * We need to update the option name -- it's terribly unclear which
1029
						 * direction the override goes.
1030
						 *
1031
						 * @todo: Update the option name to `sub-sites-can-manage-own-connections`
1032
						 */
1033
						$caps = array( 'do_not_allow' );
1034
						break;
1035
					}
1036
				}
1037
1038
				$caps = array( 'manage_options' );
1039
				break;
1040
			case 'jetpack_manage_modules':
1041
			case 'jetpack_activate_modules':
1042
			case 'jetpack_deactivate_modules':
1043
				$caps = array( 'manage_options' );
1044
				break;
1045
			case 'jetpack_configure_modules':
1046
				$caps = array( 'manage_options' );
1047
				break;
1048
			case 'jetpack_manage_autoupdates':
1049
				$caps = array(
1050
					'manage_options',
1051
					'update_plugins',
1052
				);
1053
				break;
1054
			case 'jetpack_network_admin_page':
1055
			case 'jetpack_network_settings_page':
1056
				$caps = array( 'manage_network_plugins' );
1057
				break;
1058
			case 'jetpack_network_sites_page':
1059
				$caps = array( 'manage_sites' );
1060
				break;
1061
			case 'jetpack_admin_page':
1062
				if ( $is_development_mode ) {
1063
					$caps = array( 'manage_options' );
1064
					break;
1065
				} else {
1066
					$caps = array( 'read' );
1067
				}
1068
				break;
1069
			case 'jetpack_connect_user':
1070
				if ( $is_development_mode ) {
1071
					$caps = array( 'do_not_allow' );
1072
					break;
1073
				}
1074
				$caps = array( 'read' );
1075
				break;
1076
		}
1077
		return $caps;
1078
	}
1079
1080
	/**
1081
	 * Require a Jetpack authentication.
1082
	 *
1083
	 * @deprecated since 7.7.0
1084
	 * @see Automattic\Jetpack\Connection\Manager::require_jetpack_authentication()
1085
	 */
1086
	public function require_jetpack_authentication() {
1087
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::require_jetpack_authentication' );
1088
		$this->connection_manager->require_jetpack_authentication();
1089
	}
1090
1091
	/**
1092
	 * Load language files
1093
	 *
1094
	 * @action plugins_loaded
1095
	 */
1096
	public static function plugin_textdomain() {
1097
		// Note to self, the third argument must not be hardcoded, to account for relocated folders.
1098
		load_plugin_textdomain( 'jetpack', false, dirname( plugin_basename( JETPACK__PLUGIN_FILE ) ) . '/languages/' );
1099
	}
1100
1101
	/**
1102
	 * Register assets for use in various modules and the Jetpack admin page.
1103
	 *
1104
	 * @uses wp_script_is, wp_register_script, plugins_url
1105
	 * @action wp_loaded
1106
	 * @return null
1107
	 */
1108
	public function register_assets() {
1109
		if ( ! wp_script_is( 'spin', 'registered' ) ) {
1110
			wp_register_script(
1111
				'spin',
1112
				Assets::get_file_url_for_environment( '_inc/build/spin.min.js', '_inc/spin.js' ),
1113
				false,
1114
				'1.3'
1115
			);
1116
		}
1117
1118 View Code Duplication
		if ( ! wp_script_is( 'jquery.spin', 'registered' ) ) {
1119
			wp_register_script(
1120
				'jquery.spin',
1121
				Assets::get_file_url_for_environment( '_inc/build/jquery.spin.min.js', '_inc/jquery.spin.js' ),
1122
				array( 'jquery', 'spin' ),
1123
				'1.3'
1124
			);
1125
		}
1126
1127 View Code Duplication
		if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) ) {
1128
			wp_register_script(
1129
				'jetpack-gallery-settings',
1130
				Assets::get_file_url_for_environment( '_inc/build/gallery-settings.min.js', '_inc/gallery-settings.js' ),
1131
				array( 'media-views' ),
1132
				'20121225'
1133
			);
1134
		}
1135
1136 View Code Duplication
		if ( ! wp_script_is( 'jetpack-twitter-timeline', 'registered' ) ) {
1137
			wp_register_script(
1138
				'jetpack-twitter-timeline',
1139
				Assets::get_file_url_for_environment( '_inc/build/twitter-timeline.min.js', '_inc/twitter-timeline.js' ),
1140
				array( 'jquery' ),
1141
				'4.0.0',
1142
				true
1143
			);
1144
		}
1145
1146
		if ( ! wp_script_is( 'jetpack-facebook-embed', 'registered' ) ) {
1147
			wp_register_script(
1148
				'jetpack-facebook-embed',
1149
				Assets::get_file_url_for_environment( '_inc/build/facebook-embed.min.js', '_inc/facebook-embed.js' ),
1150
				array( 'jquery' ),
1151
				null,
1152
				true
1153
			);
1154
1155
			/** This filter is documented in modules/sharedaddy/sharing-sources.php */
1156
			$fb_app_id = apply_filters( 'jetpack_sharing_facebook_app_id', '249643311490' );
1157
			if ( ! is_numeric( $fb_app_id ) ) {
1158
				$fb_app_id = '';
1159
			}
1160
			wp_localize_script(
1161
				'jetpack-facebook-embed',
1162
				'jpfbembed',
1163
				array(
1164
					'appid'  => $fb_app_id,
1165
					'locale' => $this->get_locale(),
1166
				)
1167
			);
1168
		}
1169
1170
		/**
1171
		 * As jetpack_register_genericons is by default fired off a hook,
1172
		 * the hook may have already fired by this point.
1173
		 * So, let's just trigger it manually.
1174
		 */
1175
		require_once JETPACK__PLUGIN_DIR . '_inc/genericons.php';
1176
		jetpack_register_genericons();
1177
1178
		/**
1179
		 * Register the social logos
1180
		 */
1181
		require_once JETPACK__PLUGIN_DIR . '_inc/social-logos.php';
1182
		jetpack_register_social_logos();
1183
1184 View Code Duplication
		if ( ! wp_style_is( 'jetpack-icons', 'registered' ) ) {
1185
			wp_register_style( 'jetpack-icons', plugins_url( 'css/jetpack-icons.min.css', JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
1186
		}
1187
	}
1188
1189
	/**
1190
	 * Guess locale from language code.
1191
	 *
1192
	 * @param string $lang Language code.
1193
	 * @return string|bool
1194
	 */
1195 View Code Duplication
	function guess_locale_from_lang( $lang ) {
1196
		if ( 'en' === $lang || 'en_US' === $lang || ! $lang ) {
1197
			return 'en_US';
1198
		}
1199
1200
		if ( ! class_exists( 'GP_Locales' ) ) {
1201
			if ( ! defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || ! file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
1202
				return false;
1203
			}
1204
1205
			require JETPACK__GLOTPRESS_LOCALES_PATH;
1206
		}
1207
1208
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1209
			// WP.com: get_locale() returns 'it'
1210
			$locale = GP_Locales::by_slug( $lang );
1211
		} else {
1212
			// Jetpack: get_locale() returns 'it_IT';
1213
			$locale = GP_Locales::by_field( 'facebook_locale', $lang );
1214
		}
1215
1216
		if ( ! $locale ) {
1217
			return false;
1218
		}
1219
1220
		if ( empty( $locale->facebook_locale ) ) {
1221
			if ( empty( $locale->wp_locale ) ) {
1222
				return false;
1223
			} else {
1224
				// Facebook SDK is smart enough to fall back to en_US if a
1225
				// locale isn't supported. Since supported Facebook locales
1226
				// can fall out of sync, we'll attempt to use the known
1227
				// wp_locale value and rely on said fallback.
1228
				return $locale->wp_locale;
1229
			}
1230
		}
1231
1232
		return $locale->facebook_locale;
1233
	}
1234
1235
	/**
1236
	 * Get the locale.
1237
	 *
1238
	 * @return string|bool
1239
	 */
1240
	function get_locale() {
1241
		$locale = $this->guess_locale_from_lang( get_locale() );
1242
1243
		if ( ! $locale ) {
1244
			$locale = 'en_US';
1245
		}
1246
1247
		return $locale;
1248
	}
1249
1250
	/**
1251
	 * Return the network_site_url so that .com knows what network this site is a part of.
1252
	 *
1253
	 * @param  bool $option
1254
	 * @return string
1255
	 */
1256
	public function jetpack_main_network_site_option( $option ) {
1257
		return network_site_url();
1258
	}
1259
	/**
1260
	 * Network Name.
1261
	 */
1262
	static function network_name( $option = null ) {
1263
		global $current_site;
1264
		return $current_site->site_name;
1265
	}
1266
	/**
1267
	 * Does the network allow new user and site registrations.
1268
	 *
1269
	 * @return string
1270
	 */
1271
	static function network_allow_new_registrations( $option = null ) {
1272
		return ( in_array( get_site_option( 'registration' ), array( 'none', 'user', 'blog', 'all' ) ) ? get_site_option( 'registration' ) : 'none' );
1273
	}
1274
	/**
1275
	 * Does the network allow admins to add new users.
1276
	 *
1277
	 * @return boolian
1278
	 */
1279
	static function network_add_new_users( $option = null ) {
1280
		return (bool) get_site_option( 'add_new_users' );
1281
	}
1282
	/**
1283
	 * File upload psace left per site in MB.
1284
	 *  -1 means NO LIMIT.
1285
	 *
1286
	 * @return number
1287
	 */
1288
	static function network_site_upload_space( $option = null ) {
1289
		// value in MB
1290
		return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
1291
	}
1292
1293
	/**
1294
	 * Network allowed file types.
1295
	 *
1296
	 * @return string
1297
	 */
1298
	static function network_upload_file_types( $option = null ) {
1299
		return get_site_option( 'upload_filetypes', 'jpg jpeg png gif' );
1300
	}
1301
1302
	/**
1303
	 * Maximum file upload size set by the network.
1304
	 *
1305
	 * @return number
1306
	 */
1307
	static function network_max_upload_file_size( $option = null ) {
1308
		// value in KB
1309
		return get_site_option( 'fileupload_maxk', 300 );
1310
	}
1311
1312
	/**
1313
	 * Lets us know if a site allows admins to manage the network.
1314
	 *
1315
	 * @return array
1316
	 */
1317
	static function network_enable_administration_menus( $option = null ) {
1318
		return get_site_option( 'menu_items' );
1319
	}
1320
1321
	/**
1322
	 * If a user has been promoted to or demoted from admin, we need to clear the
1323
	 * jetpack_other_linked_admins transient.
1324
	 *
1325
	 * @since 4.3.2
1326
	 * @since 4.4.0  $old_roles is null by default and if it's not passed, the transient is cleared.
1327
	 *
1328
	 * @param int    $user_id   The user ID whose role changed.
1329
	 * @param string $role      The new role.
1330
	 * @param array  $old_roles An array of the user's previous roles.
1331
	 */
1332
	function maybe_clear_other_linked_admins_transient( $user_id, $role, $old_roles = null ) {
1333
		if ( 'administrator' == $role
1334
			|| ( is_array( $old_roles ) && in_array( 'administrator', $old_roles ) )
1335
			|| is_null( $old_roles )
1336
		) {
1337
			delete_transient( 'jetpack_other_linked_admins' );
1338
		}
1339
	}
1340
1341
	/**
1342
	 * Checks to see if there are any other users available to become primary
1343
	 * Users must both:
1344
	 * - Be linked to wpcom
1345
	 * - Be an admin
1346
	 *
1347
	 * @return mixed False if no other users are linked, Int if there are.
1348
	 */
1349
	static function get_other_linked_admins() {
1350
		$other_linked_users = get_transient( 'jetpack_other_linked_admins' );
1351
1352
		if ( false === $other_linked_users ) {
1353
			$admins = get_users( array( 'role' => 'administrator' ) );
1354
			if ( count( $admins ) > 1 ) {
1355
				$available = array();
1356
				foreach ( $admins as $admin ) {
1357
					if ( self::is_user_connected( $admin->ID ) ) {
1358
						$available[] = $admin->ID;
1359
					}
1360
				}
1361
1362
				$count_connected_admins = count( $available );
1363
				if ( count( $available ) > 1 ) {
1364
					$other_linked_users = $count_connected_admins;
1365
				} else {
1366
					$other_linked_users = 0;
1367
				}
1368
			} else {
1369
				$other_linked_users = 0;
1370
			}
1371
1372
			set_transient( 'jetpack_other_linked_admins', $other_linked_users, HOUR_IN_SECONDS );
1373
		}
1374
1375
		return ( 0 === $other_linked_users ) ? false : $other_linked_users;
1376
	}
1377
1378
	/**
1379
	 * Return whether we are dealing with a multi network setup or not.
1380
	 * The reason we are type casting this is because we want to avoid the situation where
1381
	 * the result is false since when is_main_network_option return false it cases
1382
	 * the rest the get_option( 'jetpack_is_multi_network' ); to return the value that is set in the
1383
	 * database which could be set to anything as opposed to what this function returns.
1384
	 *
1385
	 * @param  bool $option
1386
	 *
1387
	 * @return boolean
1388
	 */
1389
	public function is_main_network_option( $option ) {
1390
		// return '1' or ''
1391
		return (string) (bool) self::is_multi_network();
1392
	}
1393
1394
	/**
1395
	 * Return true if we are with multi-site or multi-network false if we are dealing with single site.
1396
	 *
1397
	 * @param  string $option
1398
	 * @return boolean
1399
	 */
1400
	public function is_multisite( $option ) {
1401
		return (string) (bool) is_multisite();
1402
	}
1403
1404
	/**
1405
	 * Implemented since there is no core is multi network function
1406
	 * Right now there is no way to tell if we which network is the dominant network on the system
1407
	 *
1408
	 * @since  3.3
1409
	 * @return boolean
1410
	 */
1411 View Code Duplication
	public static function is_multi_network() {
1412
		global  $wpdb;
1413
1414
		// if we don't have a multi site setup no need to do any more
1415
		if ( ! is_multisite() ) {
1416
			return false;
1417
		}
1418
1419
		$num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
1420
		if ( $num_sites > 1 ) {
1421
			return true;
1422
		} else {
1423
			return false;
1424
		}
1425
	}
1426
1427
	/**
1428
	 * Trigger an update to the main_network_site when we update the siteurl of a site.
1429
	 *
1430
	 * @return null
1431
	 */
1432
	function update_jetpack_main_network_site_option() {
1433
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1434
	}
1435
	/**
1436
	 * Triggered after a user updates the network settings via Network Settings Admin Page
1437
	 */
1438
	function update_jetpack_network_settings() {
1439
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1440
		// Only sync this info for the main network site.
1441
	}
1442
1443
	/**
1444
	 * Get back if the current site is single user site.
1445
	 *
1446
	 * @return bool
1447
	 */
1448 View Code Duplication
	public static function is_single_user_site() {
1449
		global $wpdb;
1450
1451
		if ( false === ( $some_users = get_transient( 'jetpack_is_single_user' ) ) ) {
1452
			$some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
1453
			set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
1454
		}
1455
		return 1 === (int) $some_users;
1456
	}
1457
1458
	/**
1459
	 * Returns true if the site has file write access false otherwise.
1460
	 *
1461
	 * @return string ( '1' | '0' )
1462
	 **/
1463
	public static function file_system_write_access() {
1464
		if ( ! function_exists( 'get_filesystem_method' ) ) {
1465
			require_once ABSPATH . 'wp-admin/includes/file.php';
1466
		}
1467
1468
		require_once ABSPATH . 'wp-admin/includes/template.php';
1469
1470
		$filesystem_method = get_filesystem_method();
1471
		if ( $filesystem_method === 'direct' ) {
1472
			return 1;
1473
		}
1474
1475
		ob_start();
1476
		$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1477
		ob_end_clean();
1478
		if ( $filesystem_credentials_are_stored ) {
1479
			return 1;
1480
		}
1481
		return 0;
1482
	}
1483
1484
	/**
1485
	 * Finds out if a site is using a version control system.
1486
	 *
1487
	 * @return string ( '1' | '0' )
1488
	 **/
1489
	public static function is_version_controlled() {
1490
		_deprecated_function( __METHOD__, 'jetpack-4.2', 'Functions::is_version_controlled' );
1491
		return (string) (int) Functions::is_version_controlled();
1492
	}
1493
1494
	/**
1495
	 * Determines whether the current theme supports featured images or not.
1496
	 *
1497
	 * @return string ( '1' | '0' )
1498
	 */
1499
	public static function featured_images_enabled() {
1500
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1501
		return current_theme_supports( 'post-thumbnails' ) ? '1' : '0';
1502
	}
1503
1504
	/**
1505
	 * Wrapper for core's get_avatar_url().  This one is deprecated.
1506
	 *
1507
	 * @deprecated 4.7 use get_avatar_url instead.
1508
	 * @param int|string|object $id_or_email A user ID,  email address, or comment object
1509
	 * @param int               $size Size of the avatar image
1510
	 * @param string            $default URL to a default image to use if no avatar is available
1511
	 * @param bool              $force_display Whether to force it to return an avatar even if show_avatars is disabled
1512
	 *
1513
	 * @return array
1514
	 */
1515
	public static function get_avatar_url( $id_or_email, $size = 96, $default = '', $force_display = false ) {
1516
		_deprecated_function( __METHOD__, 'jetpack-4.7', 'get_avatar_url' );
1517
		return get_avatar_url(
1518
			$id_or_email,
1519
			array(
1520
				'size'          => $size,
1521
				'default'       => $default,
1522
				'force_default' => $force_display,
1523
			)
1524
		);
1525
	}
1526
1527
	/**
1528
	 * jetpack_updates is saved in the following schema:
1529
	 *
1530
	 * array (
1531
	 *      'plugins'                       => (int) Number of plugin updates available.
1532
	 *      'themes'                        => (int) Number of theme updates available.
1533
	 *      'wordpress'                     => (int) Number of WordPress core updates available. // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled
1534
	 *      'translations'                  => (int) Number of translation updates available.
1535
	 *      'total'                         => (int) Total of all available updates.
1536
	 *      'wp_update_version'             => (string) The latest available version of WordPress, only present if a WordPress update is needed.
1537
	 * )
1538
	 *
1539
	 * @return array
1540
	 */
1541
	public static function get_updates() {
1542
		$update_data = wp_get_update_data();
1543
1544
		// Stores the individual update counts as well as the total count.
1545
		if ( isset( $update_data['counts'] ) ) {
1546
			$updates = $update_data['counts'];
1547
		}
1548
1549
		// If we need to update WordPress core, let's find the latest version number.
1550 View Code Duplication
		if ( ! empty( $updates['wordpress'] ) ) {
1551
			$cur = get_preferred_from_update_core();
1552
			if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
1553
				$updates['wp_update_version'] = $cur->current;
1554
			}
1555
		}
1556
		return isset( $updates ) ? $updates : array();
1557
	}
1558
1559
	public static function get_update_details() {
1560
		$update_details = array(
1561
			'update_core'    => get_site_transient( 'update_core' ),
1562
			'update_plugins' => get_site_transient( 'update_plugins' ),
1563
			'update_themes'  => get_site_transient( 'update_themes' ),
1564
		);
1565
		return $update_details;
1566
	}
1567
1568
	public static function refresh_update_data() {
1569
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1570
1571
	}
1572
1573
	public static function refresh_theme_data() {
1574
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1575
	}
1576
1577
	/**
1578
	 * Is Jetpack active?
1579
	 */
1580
	public static function is_active() {
1581
		return (bool) Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1582
	}
1583
1584
	/**
1585
	 * Make an API call to WordPress.com for plan status
1586
	 *
1587
	 * @deprecated 7.2.0 Use Jetpack_Plan::refresh_from_wpcom.
1588
	 *
1589
	 * @return bool True if plan is updated, false if no update
1590
	 */
1591
	public static function refresh_active_plan_from_wpcom() {
1592
		_deprecated_function( __METHOD__, 'jetpack-7.2.0', 'Jetpack_Plan::refresh_from_wpcom' );
1593
		return Jetpack_Plan::refresh_from_wpcom();
1594
	}
1595
1596
	/**
1597
	 * Get the plan that this Jetpack site is currently using
1598
	 *
1599
	 * @deprecated 7.2.0 Use Jetpack_Plan::get.
1600
	 * @return array Active Jetpack plan details.
1601
	 */
1602
	public static function get_active_plan() {
1603
		_deprecated_function( __METHOD__, 'jetpack-7.2.0', 'Jetpack_Plan::get' );
1604
		return Jetpack_Plan::get();
1605
	}
1606
1607
	/**
1608
	 * Determine whether the active plan supports a particular feature
1609
	 *
1610
	 * @deprecated 7.2.0 Use Jetpack_Plan::supports.
1611
	 * @return bool True if plan supports feature, false if not.
1612
	 */
1613
	public static function active_plan_supports( $feature ) {
1614
		_deprecated_function( __METHOD__, 'jetpack-7.2.0', 'Jetpack_Plan::supports' );
1615
		return Jetpack_Plan::supports( $feature );
1616
	}
1617
1618
	/**
1619
	 * Deprecated: Is Jetpack in development (offline) mode?
1620
	 *
1621
	 * This static method is being left here intentionally without the use of _deprecated_function(), as other plugins
1622
	 * and themes still use it, and we do not want to flood them with notices.
1623
	 *
1624
	 * Please use Automattic\Jetpack\Status()->is_development_mode() instead.
1625
	 *
1626
	 * @deprecated since 8.0.
1627
	 */
1628
	public static function is_development_mode() {
1629
		return ( new Status() )->is_development_mode();
1630
	}
1631
1632
	/**
1633
	 * Whether the site is currently onboarding or not.
1634
	 * A site is considered as being onboarded if it currently has an onboarding token.
1635
	 *
1636
	 * @since 5.8
1637
	 *
1638
	 * @access public
1639
	 * @static
1640
	 *
1641
	 * @return bool True if the site is currently onboarding, false otherwise
1642
	 */
1643
	public static function is_onboarding() {
1644
		return Jetpack_Options::get_option( 'onboarding' ) !== false;
1645
	}
1646
1647
	/**
1648
	 * Determines reason for Jetpack development mode.
1649
	 */
1650
	public static function development_mode_trigger_text() {
1651
		if ( ! ( new Status() )->is_development_mode() ) {
1652
			return __( 'Jetpack is not in Development Mode.', 'jetpack' );
1653
		}
1654
1655
		if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
1656
			$notice = __( 'The JETPACK_DEV_DEBUG constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1657
		} elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1658
			$notice = __( 'The site URL lacking a dot (e.g. http://localhost).', 'jetpack' );
1659
		} else {
1660
			$notice = __( 'The jetpack_development_mode filter is set to true.', 'jetpack' );
1661
		}
1662
1663
		return $notice;
1664
1665
	}
1666
	/**
1667
	 * Get Jetpack development mode notice text and notice class.
1668
	 *
1669
	 * Mirrors the checks made in Automattic\Jetpack\Status->is_development_mode
1670
	 */
1671
	public static function show_development_mode_notice() {
1672 View Code Duplication
		if ( ( new Status() )->is_development_mode() ) {
1673
			$notice = sprintf(
1674
				/* translators: %s is a URL */
1675
				__( 'In <a href="%s" target="_blank">Development Mode</a>:', 'jetpack' ),
1676
				'https://jetpack.com/support/development-mode/'
1677
			);
1678
1679
			$notice .= ' ' . self::development_mode_trigger_text();
1680
1681
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1682
		}
1683
1684
		// Throw up a notice if using a development version and as for feedback.
1685
		if ( self::is_development_version() ) {
1686
			/* translators: %s is a URL */
1687
			$notice = sprintf( __( 'You are currently running a development version of Jetpack. <a href="%s" target="_blank">Submit your feedback</a>', 'jetpack' ), 'https://jetpack.com/contact-support/beta-group/' );
1688
1689
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1690
		}
1691
		// Throw up a notice if using staging mode
1692
		if ( ( new Status() )->is_staging_site() ) {
1693
			/* translators: %s is a URL */
1694
			$notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), 'https://jetpack.com/support/staging-sites/' );
1695
1696
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1697
		}
1698
	}
1699
1700
	/**
1701
	 * Whether Jetpack's version maps to a public release, or a development version.
1702
	 */
1703
	public static function is_development_version() {
1704
		/**
1705
		 * Allows filtering whether this is a development version of Jetpack.
1706
		 *
1707
		 * This filter is especially useful for tests.
1708
		 *
1709
		 * @since 4.3.0
1710
		 *
1711
		 * @param bool $development_version Is this a develoment version of Jetpack?
1712
		 */
1713
		return (bool) apply_filters(
1714
			'jetpack_development_version',
1715
			! preg_match( '/^\d+(\.\d+)+$/', Constants::get_constant( 'JETPACK__VERSION' ) )
1716
		);
1717
	}
1718
1719
	/**
1720
	 * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1721
	 */
1722
	public static function is_user_connected( $user_id = false ) {
1723
		return self::connection()->is_user_connected( $user_id );
1724
	}
1725
1726
	/**
1727
	 * Get the wpcom user data of the current|specified connected user.
1728
	 */
1729 View Code Duplication
	public static function get_connected_user_data( $user_id = null ) {
1730
		// TODO: remove in favor of Connection_Manager->get_connected_user_data
1731
		if ( ! $user_id ) {
1732
			$user_id = get_current_user_id();
1733
		}
1734
1735
		$transient_key = "jetpack_connected_user_data_$user_id";
1736
1737
		if ( $cached_user_data = get_transient( $transient_key ) ) {
1738
			return $cached_user_data;
1739
		}
1740
1741
		$xml = new Jetpack_IXR_Client(
1742
			array(
1743
				'user_id' => $user_id,
1744
			)
1745
		);
1746
		$xml->query( 'wpcom.getUser' );
1747
		if ( ! $xml->isError() ) {
1748
			$user_data = $xml->getResponse();
1749
			set_transient( $transient_key, $xml->getResponse(), DAY_IN_SECONDS );
1750
			return $user_data;
1751
		}
1752
1753
		return false;
1754
	}
1755
1756
	/**
1757
	 * Get the wpcom email of the current|specified connected user.
1758
	 */
1759 View Code Duplication
	public static function get_connected_user_email( $user_id = null ) {
1760
		if ( ! $user_id ) {
1761
			$user_id = get_current_user_id();
1762
		}
1763
1764
		$xml = new Jetpack_IXR_Client(
1765
			array(
1766
				'user_id' => $user_id,
1767
			)
1768
		);
1769
		$xml->query( 'wpcom.getUserEmail' );
1770
		if ( ! $xml->isError() ) {
1771
			return $xml->getResponse();
1772
		}
1773
		return false;
1774
	}
1775
1776
	/**
1777
	 * Get the wpcom email of the master user.
1778
	 */
1779
	public static function get_master_user_email() {
1780
		$master_user_id = Jetpack_Options::get_option( 'master_user' );
1781
		if ( $master_user_id ) {
1782
			return self::get_connected_user_email( $master_user_id );
1783
		}
1784
		return '';
1785
	}
1786
1787
	/**
1788
	 * Whether the current user is the connection owner.
1789
	 *
1790
	 * @deprecated since 7.7
1791
	 *
1792
	 * @return bool Whether the current user is the connection owner.
1793
	 */
1794
	public function current_user_is_connection_owner() {
1795
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::is_connection_owner' );
1796
		return self::connection()->is_connection_owner();
1797
	}
1798
1799
	/**
1800
	 * Gets current user IP address.
1801
	 *
1802
	 * @param  bool $check_all_headers Check all headers? Default is `false`.
1803
	 *
1804
	 * @return string                  Current user IP address.
1805
	 */
1806
	public static function current_user_ip( $check_all_headers = false ) {
1807
		if ( $check_all_headers ) {
1808
			foreach ( array(
1809
				'HTTP_CF_CONNECTING_IP',
1810
				'HTTP_CLIENT_IP',
1811
				'HTTP_X_FORWARDED_FOR',
1812
				'HTTP_X_FORWARDED',
1813
				'HTTP_X_CLUSTER_CLIENT_IP',
1814
				'HTTP_FORWARDED_FOR',
1815
				'HTTP_FORWARDED',
1816
				'HTTP_VIA',
1817
			) as $key ) {
1818
				if ( ! empty( $_SERVER[ $key ] ) ) {
1819
					return $_SERVER[ $key ];
1820
				}
1821
			}
1822
		}
1823
1824
		return ! empty( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
1825
	}
1826
1827
	/**
1828
	 * Add any extra oEmbed providers that we know about and use on wpcom for feature parity.
1829
	 */
1830
	function extra_oembed_providers() {
1831
		// Cloudup: https://dev.cloudup.com/#oembed
1832
		wp_oembed_add_provider( 'https://cloudup.com/*', 'https://cloudup.com/oembed' );
1833
		wp_oembed_add_provider( 'https://me.sh/*', 'https://me.sh/oembed?format=json' );
1834
		wp_oembed_add_provider( '#https?://(www\.)?gfycat\.com/.*#i', 'https://api.gfycat.com/v1/oembed', true );
1835
		wp_oembed_add_provider( '#https?://[^.]+\.(wistia\.com|wi\.st)/(medias|embed)/.*#', 'https://fast.wistia.com/oembed', true );
1836
		wp_oembed_add_provider( '#https?://sketchfab\.com/.*#i', 'https://sketchfab.com/oembed', true );
1837
		wp_oembed_add_provider( '#https?://(www\.)?icloud\.com/keynote/.*#i', 'https://iwmb.icloud.com/iwmb/oembed', true );
1838
		wp_oembed_add_provider( 'https://song.link/*', 'https://song.link/oembed', false );
1839
	}
1840
1841
	/**
1842
	 * Synchronize connected user role changes
1843
	 */
1844
	function user_role_change( $user_id ) {
1845
		_deprecated_function( __METHOD__, 'jetpack-4.2', 'Users::user_role_change()' );
1846
		Users::user_role_change( $user_id );
1847
	}
1848
1849
	/**
1850
	 * Loads the currently active modules.
1851
	 */
1852
	public static function load_modules() {
1853
		$is_development_mode = ( new Status() )->is_development_mode();
1854
		if (
1855
			! self::is_active()
1856
			&& ! $is_development_mode
1857
			&& ! self::is_onboarding()
1858
			&& (
1859
				! is_multisite()
1860
				|| ! get_site_option( 'jetpack_protect_active' )
1861
			)
1862
		) {
1863
			return;
1864
		}
1865
1866
		$version = Jetpack_Options::get_option( 'version' );
1867 View Code Duplication
		if ( ! $version ) {
1868
			$version = $old_version = JETPACK__VERSION . ':' . time();
1869
			/** This action is documented in class.jetpack.php */
1870
			do_action( 'updating_jetpack_version', $version, false );
1871
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1872
		}
1873
		list( $version ) = explode( ':', $version );
1874
1875
		$modules = array_filter( self::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1876
1877
		$modules_data = array();
1878
1879
		// Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1880
		if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1881
			$updated_modules = array();
1882
			foreach ( $modules as $module ) {
1883
				$modules_data[ $module ] = self::get_module( $module );
1884
				if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1885
					continue;
1886
				}
1887
1888
				if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1889
					continue;
1890
				}
1891
1892
				$updated_modules[] = $module;
1893
			}
1894
1895
			$modules = array_diff( $modules, $updated_modules );
1896
		}
1897
1898
		foreach ( $modules as $index => $module ) {
1899
			// If we're in dev mode, disable modules requiring a connection
1900
			if ( $is_development_mode ) {
1901
				// Prime the pump if we need to
1902
				if ( empty( $modules_data[ $module ] ) ) {
1903
					$modules_data[ $module ] = self::get_module( $module );
1904
				}
1905
				// If the module requires a connection, but we're in local mode, don't include it.
1906
				if ( $modules_data[ $module ]['requires_connection'] ) {
1907
					continue;
1908
				}
1909
			}
1910
1911
			if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1912
				continue;
1913
			}
1914
1915
			if ( ! include_once self::get_module_path( $module ) ) {
1916
				unset( $modules[ $index ] );
1917
				self::update_active_modules( array_values( $modules ) );
1918
				continue;
1919
			}
1920
1921
			/**
1922
			 * Fires when a specific module is loaded.
1923
			 * The dynamic part of the hook, $module, is the module slug.
1924
			 *
1925
			 * @since 1.1.0
1926
			 */
1927
			do_action( 'jetpack_module_loaded_' . $module );
1928
		}
1929
1930
		/**
1931
		 * Fires when all the modules are loaded.
1932
		 *
1933
		 * @since 1.1.0
1934
		 */
1935
		do_action( 'jetpack_modules_loaded' );
1936
1937
		// 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.
1938
		require_once JETPACK__PLUGIN_DIR . 'modules/module-extras.php';
1939
	}
1940
1941
	/**
1942
	 * Check if Jetpack's REST API compat file should be included
1943
	 *
1944
	 * @action plugins_loaded
1945
	 * @return null
1946
	 */
1947
	public function check_rest_api_compat() {
1948
		/**
1949
		 * Filters the list of REST API compat files to be included.
1950
		 *
1951
		 * @since 2.2.5
1952
		 *
1953
		 * @param array $args Array of REST API compat files to include.
1954
		 */
1955
		$_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1956
1957
		if ( function_exists( 'bbpress' ) ) {
1958
			$_jetpack_rest_api_compat_includes[] = JETPACK__PLUGIN_DIR . 'class.jetpack-bbpress-json-api-compat.php';
1959
		}
1960
1961
		foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include ) {
1962
			require_once $_jetpack_rest_api_compat_include;
1963
		}
1964
	}
1965
1966
	/**
1967
	 * Gets all plugins currently active in values, regardless of whether they're
1968
	 * traditionally activated or network activated.
1969
	 *
1970
	 * @todo Store the result in core's object cache maybe?
1971
	 */
1972
	public static function get_active_plugins() {
1973
		$active_plugins = (array) get_option( 'active_plugins', array() );
1974
1975
		if ( is_multisite() ) {
1976
			// Due to legacy code, active_sitewide_plugins stores them in the keys,
1977
			// whereas active_plugins stores them in the values.
1978
			$network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1979
			if ( $network_plugins ) {
1980
				$active_plugins = array_merge( $active_plugins, $network_plugins );
1981
			}
1982
		}
1983
1984
		sort( $active_plugins );
1985
1986
		return array_unique( $active_plugins );
1987
	}
1988
1989
	/**
1990
	 * Gets and parses additional plugin data to send with the heartbeat data
1991
	 *
1992
	 * @since 3.8.1
1993
	 *
1994
	 * @return array Array of plugin data
1995
	 */
1996
	public static function get_parsed_plugin_data() {
1997
		if ( ! function_exists( 'get_plugins' ) ) {
1998
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
1999
		}
2000
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
2001
		$all_plugins    = apply_filters( 'all_plugins', get_plugins() );
2002
		$active_plugins = self::get_active_plugins();
2003
2004
		$plugins = array();
2005
		foreach ( $all_plugins as $path => $plugin_data ) {
2006
			$plugins[ $path ] = array(
2007
				'is_active' => in_array( $path, $active_plugins ),
2008
				'file'      => $path,
2009
				'name'      => $plugin_data['Name'],
2010
				'version'   => $plugin_data['Version'],
2011
				'author'    => $plugin_data['Author'],
2012
			);
2013
		}
2014
2015
		return $plugins;
2016
	}
2017
2018
	/**
2019
	 * Gets and parses theme data to send with the heartbeat data
2020
	 *
2021
	 * @since 3.8.1
2022
	 *
2023
	 * @return array Array of theme data
2024
	 */
2025
	public static function get_parsed_theme_data() {
2026
		$all_themes  = wp_get_themes( array( 'allowed' => true ) );
2027
		$header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
2028
2029
		$themes = array();
2030
		foreach ( $all_themes as $slug => $theme_data ) {
2031
			$theme_headers = array();
2032
			foreach ( $header_keys as $header_key ) {
2033
				$theme_headers[ $header_key ] = $theme_data->get( $header_key );
2034
			}
2035
2036
			$themes[ $slug ] = array(
2037
				'is_active_theme' => $slug == wp_get_theme()->get_template(),
2038
				'slug'            => $slug,
2039
				'theme_root'      => $theme_data->get_theme_root_uri(),
2040
				'parent'          => $theme_data->parent(),
2041
				'headers'         => $theme_headers,
2042
			);
2043
		}
2044
2045
		return $themes;
2046
	}
2047
2048
	/**
2049
	 * Checks whether a specific plugin is active.
2050
	 *
2051
	 * We don't want to store these in a static variable, in case
2052
	 * there are switch_to_blog() calls involved.
2053
	 */
2054
	public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
2055
		return in_array( $plugin, self::get_active_plugins() );
2056
	}
2057
2058
	/**
2059
	 * Check if Jetpack's Open Graph tags should be used.
2060
	 * If certain plugins are active, Jetpack's og tags are suppressed.
2061
	 *
2062
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
2063
	 * @action plugins_loaded
2064
	 * @return null
2065
	 */
2066
	public function check_open_graph() {
2067
		if ( in_array( 'publicize', self::get_active_modules() ) || in_array( 'sharedaddy', self::get_active_modules() ) ) {
2068
			add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
2069
		}
2070
2071
		$active_plugins = self::get_active_plugins();
2072
2073
		if ( ! empty( $active_plugins ) ) {
2074
			foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
2075
				if ( in_array( $plugin, $active_plugins ) ) {
2076
					add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
2077
					break;
2078
				}
2079
			}
2080
		}
2081
2082
		/**
2083
		 * Allow the addition of Open Graph Meta Tags to all pages.
2084
		 *
2085
		 * @since 2.0.3
2086
		 *
2087
		 * @param bool false Should Open Graph Meta tags be added. Default to false.
2088
		 */
2089
		if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
2090
			require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
2091
		}
2092
	}
2093
2094
	/**
2095
	 * Check if Jetpack's Twitter tags should be used.
2096
	 * If certain plugins are active, Jetpack's twitter tags are suppressed.
2097
	 *
2098
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
2099
	 * @action plugins_loaded
2100
	 * @return null
2101
	 */
2102
	public function check_twitter_tags() {
2103
2104
		$active_plugins = self::get_active_plugins();
2105
2106
		if ( ! empty( $active_plugins ) ) {
2107
			foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
2108
				if ( in_array( $plugin, $active_plugins ) ) {
2109
					add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
2110
					break;
2111
				}
2112
			}
2113
		}
2114
2115
		/**
2116
		 * Allow Twitter Card Meta tags to be disabled.
2117
		 *
2118
		 * @since 2.6.0
2119
		 *
2120
		 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2121
		 */
2122
		if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2123
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2124
		}
2125
	}
2126
2127
	/**
2128
	 * Allows plugins to submit security reports.
2129
	 *
2130
	 * @param string $type         Report type (login_form, backup, file_scanning, spam)
2131
	 * @param string $plugin_file  Plugin __FILE__, so that we can pull plugin data
2132
	 * @param array  $args         See definitions above
2133
	 */
2134
	public static function submit_security_report( $type = '', $plugin_file = '', $args = array() ) {
2135
		_deprecated_function( __FUNCTION__, 'jetpack-4.2', null );
2136
	}
2137
2138
	/* Jetpack Options API */
2139
2140
	public static function get_option_names( $type = 'compact' ) {
2141
		return Jetpack_Options::get_option_names( $type );
2142
	}
2143
2144
	/**
2145
	 * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
2146
	 *
2147
	 * @param string $name    Option name
2148
	 * @param mixed  $default (optional)
2149
	 */
2150
	public static function get_option( $name, $default = false ) {
2151
		return Jetpack_Options::get_option( $name, $default );
2152
	}
2153
2154
	/**
2155
	 * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
2156
	 *
2157
	 * @deprecated 3.4 use Jetpack_Options::update_option() instead.
2158
	 * @param string $name  Option name
2159
	 * @param mixed  $value Option value
2160
	 */
2161
	public static function update_option( $name, $value ) {
2162
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_option()' );
2163
		return Jetpack_Options::update_option( $name, $value );
2164
	}
2165
2166
	/**
2167
	 * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
2168
	 *
2169
	 * @deprecated 3.4 use Jetpack_Options::update_options() instead.
2170
	 * @param array $array array( option name => option value, ... )
2171
	 */
2172
	public static function update_options( $array ) {
2173
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_options()' );
2174
		return Jetpack_Options::update_options( $array );
2175
	}
2176
2177
	/**
2178
	 * Deletes the given option.  May be passed multiple option names as an array.
2179
	 * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
2180
	 *
2181
	 * @deprecated 3.4 use Jetpack_Options::delete_option() instead.
2182
	 * @param string|array $names
2183
	 */
2184
	public static function delete_option( $names ) {
2185
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::delete_option()' );
2186
		return Jetpack_Options::delete_option( $names );
2187
	}
2188
2189
	/**
2190
	 * @deprecated 8.0 Use Automattic\Jetpack\Connection\Utils::update_user_token() instead.
2191
	 *
2192
	 * Enters a user token into the user_tokens option
2193
	 *
2194
	 * @param int    $user_id The user id.
2195
	 * @param string $token The user token.
2196
	 * @param bool   $is_master_user Whether the user is the master user.
2197
	 * @return bool
2198
	 */
2199
	public static function update_user_token( $user_id, $token, $is_master_user ) {
2200
		_deprecated_function( __METHOD__, 'jetpack-8.0', 'Automattic\\Jetpack\\Connection\\Utils::update_user_token' );
2201
		return Connection_Utils::update_user_token( $user_id, $token, $is_master_user );
2202
	}
2203
2204
	/**
2205
	 * Returns an array of all PHP files in the specified absolute path.
2206
	 * Equivalent to glob( "$absolute_path/*.php" ).
2207
	 *
2208
	 * @param string $absolute_path The absolute path of the directory to search.
2209
	 * @return array Array of absolute paths to the PHP files.
2210
	 */
2211
	public static function glob_php( $absolute_path ) {
2212
		if ( function_exists( 'glob' ) ) {
2213
			return glob( "$absolute_path/*.php" );
2214
		}
2215
2216
		$absolute_path = untrailingslashit( $absolute_path );
2217
		$files         = array();
2218
		if ( ! $dir = @opendir( $absolute_path ) ) {
2219
			return $files;
2220
		}
2221
2222
		while ( false !== $file = readdir( $dir ) ) {
2223
			if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
2224
				continue;
2225
			}
2226
2227
			$file = "$absolute_path/$file";
2228
2229
			if ( ! is_file( $file ) ) {
2230
				continue;
2231
			}
2232
2233
			$files[] = $file;
2234
		}
2235
2236
		closedir( $dir );
2237
2238
		return $files;
2239
	}
2240
2241
	public static function activate_new_modules( $redirect = false ) {
2242
		if ( ! self::is_active() && ! ( new Status() )->is_development_mode() ) {
2243
			return;
2244
		}
2245
2246
		$jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
2247 View Code Duplication
		if ( ! $jetpack_old_version ) {
2248
			$jetpack_old_version = $version = $old_version = '1.1:' . time();
2249
			/** This action is documented in class.jetpack.php */
2250
			do_action( 'updating_jetpack_version', $version, false );
2251
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2252
		}
2253
2254
		list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
2255
2256
		if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
2257
			return;
2258
		}
2259
2260
		$active_modules     = self::get_active_modules();
2261
		$reactivate_modules = array();
2262
		foreach ( $active_modules as $active_module ) {
2263
			$module = self::get_module( $active_module );
2264
			if ( ! isset( $module['changed'] ) ) {
2265
				continue;
2266
			}
2267
2268
			if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
2269
				continue;
2270
			}
2271
2272
			$reactivate_modules[] = $active_module;
2273
			self::deactivate_module( $active_module );
2274
		}
2275
2276
		$new_version = JETPACK__VERSION . ':' . time();
2277
		/** This action is documented in class.jetpack.php */
2278
		do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
2279
		Jetpack_Options::update_options(
2280
			array(
2281
				'version'     => $new_version,
2282
				'old_version' => $jetpack_old_version,
2283
			)
2284
		);
2285
2286
		self::state( 'message', 'modules_activated' );
2287
		self::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules, $redirect );
2288
2289
		if ( $redirect ) {
2290
			$page = 'jetpack'; // make sure we redirect to either settings or the jetpack page
2291
			if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ) ) ) {
2292
				$page = $_GET['page'];
2293
			}
2294
2295
			wp_safe_redirect( self::admin_url( 'page=' . $page ) );
2296
			exit;
2297
		}
2298
	}
2299
2300
	/**
2301
	 * List available Jetpack modules. Simply lists .php files in /modules/.
2302
	 * Make sure to tuck away module "library" files in a sub-directory.
2303
	 */
2304
	public static function get_available_modules( $min_version = false, $max_version = false ) {
2305
		static $modules = null;
2306
2307
		if ( ! isset( $modules ) ) {
2308
			$available_modules_option = Jetpack_Options::get_option( 'available_modules', array() );
2309
			// Use the cache if we're on the front-end and it's available...
2310
			if ( ! is_admin() && ! empty( $available_modules_option[ JETPACK__VERSION ] ) ) {
2311
				$modules = $available_modules_option[ JETPACK__VERSION ];
2312
			} else {
2313
				$files = self::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
2314
2315
				$modules = array();
2316
2317
				foreach ( $files as $file ) {
2318
					if ( ! $headers = self::get_module( $file ) ) {
2319
						continue;
2320
					}
2321
2322
					$modules[ self::get_module_slug( $file ) ] = $headers['introduced'];
2323
				}
2324
2325
				Jetpack_Options::update_option(
2326
					'available_modules',
2327
					array(
2328
						JETPACK__VERSION => $modules,
2329
					)
2330
				);
2331
			}
2332
		}
2333
2334
		/**
2335
		 * Filters the array of modules available to be activated.
2336
		 *
2337
		 * @since 2.4.0
2338
		 *
2339
		 * @param array $modules Array of available modules.
2340
		 * @param string $min_version Minimum version number required to use modules.
2341
		 * @param string $max_version Maximum version number required to use modules.
2342
		 */
2343
		$mods = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version );
2344
2345
		if ( ! $min_version && ! $max_version ) {
2346
			return array_keys( $mods );
2347
		}
2348
2349
		$r = array();
2350
		foreach ( $mods as $slug => $introduced ) {
2351
			if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
2352
				continue;
2353
			}
2354
2355
			if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
2356
				continue;
2357
			}
2358
2359
			$r[] = $slug;
2360
		}
2361
2362
		return $r;
2363
	}
2364
2365
	/**
2366
	 * Default modules loaded on activation.
2367
	 */
2368
	public static function get_default_modules( $min_version = false, $max_version = false ) {
2369
		$return = array();
2370
2371
		foreach ( self::get_available_modules( $min_version, $max_version ) as $module ) {
2372
			$module_data = self::get_module( $module );
2373
2374
			switch ( strtolower( $module_data['auto_activate'] ) ) {
2375
				case 'yes':
2376
					$return[] = $module;
2377
					break;
2378
				case 'public':
2379
					if ( Jetpack_Options::get_option( 'public' ) ) {
2380
						$return[] = $module;
2381
					}
2382
					break;
2383
				case 'no':
2384
				default:
2385
					break;
2386
			}
2387
		}
2388
		/**
2389
		 * Filters the array of default modules.
2390
		 *
2391
		 * @since 2.5.0
2392
		 *
2393
		 * @param array $return Array of default modules.
2394
		 * @param string $min_version Minimum version number required to use modules.
2395
		 * @param string $max_version Maximum version number required to use modules.
2396
		 */
2397
		return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version );
2398
	}
2399
2400
	/**
2401
	 * Checks activated modules during auto-activation to determine
2402
	 * if any of those modules are being deprecated.  If so, close
2403
	 * them out, and add any replacement modules.
2404
	 *
2405
	 * Runs at priority 99 by default.
2406
	 *
2407
	 * This is run late, so that it can still activate a module if
2408
	 * the new module is a replacement for another that the user
2409
	 * currently has active, even if something at the normal priority
2410
	 * would kibosh everything.
2411
	 *
2412
	 * @since 2.6
2413
	 * @uses jetpack_get_default_modules filter
2414
	 * @param array $modules
2415
	 * @return array
2416
	 */
2417
	function handle_deprecated_modules( $modules ) {
2418
		$deprecated_modules = array(
2419
			'debug'            => null,  // Closed out and moved to the debugger library.
2420
			'wpcc'             => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2421
			'gplus-authorship' => null,  // Closed out in 3.2 -- Google dropped support.
2422
		);
2423
2424
		// Don't activate SSO if they never completed activating WPCC.
2425
		if ( self::is_module_active( 'wpcc' ) ) {
2426
			$wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2427
			if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2428
				$deprecated_modules['wpcc'] = null;
2429
			}
2430
		}
2431
2432
		foreach ( $deprecated_modules as $module => $replacement ) {
2433
			if ( self::is_module_active( $module ) ) {
2434
				self::deactivate_module( $module );
2435
				if ( $replacement ) {
2436
					$modules[] = $replacement;
2437
				}
2438
			}
2439
		}
2440
2441
		return array_unique( $modules );
2442
	}
2443
2444
	/**
2445
	 * Checks activated plugins during auto-activation to determine
2446
	 * if any of those plugins are in the list with a corresponding module
2447
	 * that is not compatible with the plugin. The module will not be allowed
2448
	 * to auto-activate.
2449
	 *
2450
	 * @since 2.6
2451
	 * @uses jetpack_get_default_modules filter
2452
	 * @param array $modules
2453
	 * @return array
2454
	 */
2455
	function filter_default_modules( $modules ) {
2456
2457
		$active_plugins = self::get_active_plugins();
2458
2459
		if ( ! empty( $active_plugins ) ) {
2460
2461
			// For each module we'd like to auto-activate...
2462
			foreach ( $modules as $key => $module ) {
2463
				// If there are potential conflicts for it...
2464
				if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2465
					// For each potential conflict...
2466
					foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
2467
						// If that conflicting plugin is active...
2468
						if ( in_array( $plugin, $active_plugins ) ) {
2469
							// Remove that item from being auto-activated.
2470
							unset( $modules[ $key ] );
2471
						}
2472
					}
2473
				}
2474
			}
2475
		}
2476
2477
		return $modules;
2478
	}
2479
2480
	/**
2481
	 * Extract a module's slug from its full path.
2482
	 */
2483
	public static function get_module_slug( $file ) {
2484
		return str_replace( '.php', '', basename( $file ) );
2485
	}
2486
2487
	/**
2488
	 * Generate a module's path from its slug.
2489
	 */
2490
	public static function get_module_path( $slug ) {
2491
		/**
2492
		 * Filters the path of a modules.
2493
		 *
2494
		 * @since 7.4.0
2495
		 *
2496
		 * @param array $return The absolute path to a module's root php file
2497
		 * @param string $slug The module slug
2498
		 */
2499
		return apply_filters( 'jetpack_get_module_path', JETPACK__PLUGIN_DIR . "modules/$slug.php", $slug );
2500
	}
2501
2502
	/**
2503
	 * Load module data from module file. Headers differ from WordPress
2504
	 * plugin headers to avoid them being identified as standalone
2505
	 * plugins on the WordPress plugins page.
2506
	 */
2507
	public static function get_module( $module ) {
2508
		$headers = array(
2509
			'name'                      => 'Module Name',
2510
			'description'               => 'Module Description',
2511
			'sort'                      => 'Sort Order',
2512
			'recommendation_order'      => 'Recommendation Order',
2513
			'introduced'                => 'First Introduced',
2514
			'changed'                   => 'Major Changes In',
2515
			'deactivate'                => 'Deactivate',
2516
			'free'                      => 'Free',
2517
			'requires_connection'       => 'Requires Connection',
2518
			'auto_activate'             => 'Auto Activate',
2519
			'module_tags'               => 'Module Tags',
2520
			'feature'                   => 'Feature',
2521
			'additional_search_queries' => 'Additional Search Queries',
2522
			'plan_classes'              => 'Plans',
2523
		);
2524
2525
		$file = self::get_module_path( self::get_module_slug( $module ) );
2526
2527
		$mod = self::get_file_data( $file, $headers );
2528
		if ( empty( $mod['name'] ) ) {
2529
			return false;
2530
		}
2531
2532
		$mod['sort']                 = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
2533
		$mod['recommendation_order'] = empty( $mod['recommendation_order'] ) ? 20 : (int) $mod['recommendation_order'];
2534
		$mod['deactivate']           = empty( $mod['deactivate'] );
2535
		$mod['free']                 = empty( $mod['free'] );
2536
		$mod['requires_connection']  = ( ! empty( $mod['requires_connection'] ) && 'No' == $mod['requires_connection'] ) ? false : true;
2537
2538
		if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ) ) ) {
2539
			$mod['auto_activate'] = 'No';
2540
		} else {
2541
			$mod['auto_activate'] = (string) $mod['auto_activate'];
2542
		}
2543
2544
		if ( $mod['module_tags'] ) {
2545
			$mod['module_tags'] = explode( ',', $mod['module_tags'] );
2546
			$mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
2547
			$mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
2548
		} else {
2549
			$mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
2550
		}
2551
2552 View Code Duplication
		if ( $mod['plan_classes'] ) {
2553
			$mod['plan_classes'] = explode( ',', $mod['plan_classes'] );
2554
			$mod['plan_classes'] = array_map( 'strtolower', array_map( 'trim', $mod['plan_classes'] ) );
2555
		} else {
2556
			$mod['plan_classes'] = array( 'free' );
2557
		}
2558
2559 View Code Duplication
		if ( $mod['feature'] ) {
2560
			$mod['feature'] = explode( ',', $mod['feature'] );
2561
			$mod['feature'] = array_map( 'trim', $mod['feature'] );
2562
		} else {
2563
			$mod['feature'] = array( self::translate_module_tag( 'Other' ) );
2564
		}
2565
2566
		/**
2567
		 * Filters the feature array on a module.
2568
		 *
2569
		 * This filter allows you to control where each module is filtered: Recommended,
2570
		 * and the default "Other" listing.
2571
		 *
2572
		 * @since 3.5.0
2573
		 *
2574
		 * @param array   $mod['feature'] The areas to feature this module:
2575
		 *     'Recommended' shows on the main Jetpack admin screen.
2576
		 *     'Other' should be the default if no other value is in the array.
2577
		 * @param string  $module The slug of the module, e.g. sharedaddy.
2578
		 * @param array   $mod All the currently assembled module data.
2579
		 */
2580
		$mod['feature'] = apply_filters( 'jetpack_module_feature', $mod['feature'], $module, $mod );
2581
2582
		/**
2583
		 * Filter the returned data about a module.
2584
		 *
2585
		 * This filter allows overriding any info about Jetpack modules. It is dangerous,
2586
		 * so please be careful.
2587
		 *
2588
		 * @since 3.6.0
2589
		 *
2590
		 * @param array   $mod    The details of the requested module.
2591
		 * @param string  $module The slug of the module, e.g. sharedaddy
2592
		 * @param string  $file   The path to the module source file.
2593
		 */
2594
		return apply_filters( 'jetpack_get_module', $mod, $module, $file );
2595
	}
2596
2597
	/**
2598
	 * Like core's get_file_data implementation, but caches the result.
2599
	 */
2600
	public static function get_file_data( $file, $headers ) {
2601
		// Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated
2602
		$file_name = basename( $file );
2603
2604
		$cache_key = 'jetpack_file_data_' . JETPACK__VERSION;
2605
2606
		$file_data_option = get_transient( $cache_key );
2607
2608
		if ( ! is_array( $file_data_option ) ) {
2609
			delete_transient( $cache_key );
2610
			$file_data_option = false;
2611
		}
2612
2613
		if ( false === $file_data_option ) {
2614
			$file_data_option = array();
2615
		}
2616
2617
		$key           = md5( $file_name . serialize( $headers ) );
2618
		$refresh_cache = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 );
2619
2620
		// If we don't need to refresh the cache, and already have the value, short-circuit!
2621
		if ( ! $refresh_cache && isset( $file_data_option[ $key ] ) ) {
2622
			return $file_data_option[ $key ];
2623
		}
2624
2625
		$data = get_file_data( $file, $headers );
2626
2627
		$file_data_option[ $key ] = $data;
2628
2629
		set_transient( $cache_key, $file_data_option, 29 * DAY_IN_SECONDS );
2630
2631
		return $data;
2632
	}
2633
2634
2635
	/**
2636
	 * Return translated module tag.
2637
	 *
2638
	 * @param string $tag Tag as it appears in each module heading.
2639
	 *
2640
	 * @return mixed
2641
	 */
2642
	public static function translate_module_tag( $tag ) {
2643
		return jetpack_get_module_i18n_tag( $tag );
2644
	}
2645
2646
	/**
2647
	 * Get i18n strings as a JSON-encoded string
2648
	 *
2649
	 * @return string The locale as JSON
2650
	 */
2651
	public static function get_i18n_data_json() {
2652
2653
		// WordPress 5.0 uses md5 hashes of file paths to associate translation
2654
		// JSON files with the file they should be included for. This is an md5
2655
		// of '_inc/build/admin.js'.
2656
		$path_md5 = '1bac79e646a8bf4081a5011ab72d5807';
2657
2658
		$i18n_json =
2659
				   JETPACK__PLUGIN_DIR
2660
				   . 'languages/json/jetpack-'
2661
				   . get_user_locale()
2662
				   . '-'
2663
				   . $path_md5
2664
				   . '.json';
2665
2666
		if ( is_file( $i18n_json ) && is_readable( $i18n_json ) ) {
2667
			$locale_data = @file_get_contents( $i18n_json );
2668
			if ( $locale_data ) {
2669
				return $locale_data;
2670
			}
2671
		}
2672
2673
		// Return valid empty Jed locale
2674
		return '{ "locale_data": { "messages": { "": {} } } }';
2675
	}
2676
2677
	/**
2678
	 * Add locale data setup to wp-i18n
2679
	 *
2680
	 * Any Jetpack script that depends on wp-i18n should use this method to set up the locale.
2681
	 *
2682
	 * The locale setup depends on an adding inline script. This is error-prone and could easily
2683
	 * result in multiple additions of the same script when exactly 0 or 1 is desireable.
2684
	 *
2685
	 * This method provides a safe way to request the setup multiple times but add the script at
2686
	 * most once.
2687
	 *
2688
	 * @since 6.7.0
2689
	 *
2690
	 * @return void
2691
	 */
2692
	public static function setup_wp_i18n_locale_data() {
2693
		static $script_added = false;
2694
		if ( ! $script_added ) {
2695
			$script_added = true;
2696
			wp_add_inline_script(
2697
				'wp-i18n',
2698
				'wp.i18n.setLocaleData( ' . self::get_i18n_data_json() . ', \'jetpack\' );'
2699
			);
2700
		}
2701
	}
2702
2703
	/**
2704
	 * Return module name translation. Uses matching string created in modules/module-headings.php.
2705
	 *
2706
	 * @since 3.9.2
2707
	 *
2708
	 * @param array $modules
2709
	 *
2710
	 * @return string|void
2711
	 */
2712
	public static function get_translated_modules( $modules ) {
2713
		foreach ( $modules as $index => $module ) {
2714
			$i18n_module = jetpack_get_module_i18n( $module['module'] );
2715
			if ( isset( $module['name'] ) ) {
2716
				$modules[ $index ]['name'] = $i18n_module['name'];
2717
			}
2718
			if ( isset( $module['description'] ) ) {
2719
				$modules[ $index ]['description']       = $i18n_module['description'];
2720
				$modules[ $index ]['short_description'] = $i18n_module['description'];
2721
			}
2722
		}
2723
		return $modules;
2724
	}
2725
2726
	/**
2727
	 * Get a list of activated modules as an array of module slugs.
2728
	 */
2729
	public static function get_active_modules() {
2730
		$active = Jetpack_Options::get_option( 'active_modules' );
2731
2732
		if ( ! is_array( $active ) ) {
2733
			$active = array();
2734
		}
2735
2736
		if ( class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ) ) {
2737
			$active[] = 'vaultpress';
2738
		} else {
2739
			$active = array_diff( $active, array( 'vaultpress' ) );
2740
		}
2741
2742
		// If protect is active on the main site of a multisite, it should be active on all sites.
2743
		if ( ! in_array( 'protect', $active ) && is_multisite() && get_site_option( 'jetpack_protect_active' ) ) {
2744
			$active[] = 'protect';
2745
		}
2746
2747
		/**
2748
		 * Allow filtering of the active modules.
2749
		 *
2750
		 * Gives theme and plugin developers the power to alter the modules that
2751
		 * are activated on the fly.
2752
		 *
2753
		 * @since 5.8.0
2754
		 *
2755
		 * @param array $active Array of active module slugs.
2756
		 */
2757
		$active = apply_filters( 'jetpack_active_modules', $active );
2758
2759
		return array_unique( $active );
2760
	}
2761
2762
	/**
2763
	 * Check whether or not a Jetpack module is active.
2764
	 *
2765
	 * @param string $module The slug of a Jetpack module.
2766
	 * @return bool
2767
	 *
2768
	 * @static
2769
	 */
2770
	public static function is_module_active( $module ) {
2771
		return in_array( $module, self::get_active_modules() );
2772
	}
2773
2774
	public static function is_module( $module ) {
2775
		return ! empty( $module ) && ! validate_file( $module, self::get_available_modules() );
2776
	}
2777
2778
	/**
2779
	 * Catches PHP errors.  Must be used in conjunction with output buffering.
2780
	 *
2781
	 * @param bool $catch True to start catching, False to stop.
2782
	 *
2783
	 * @static
2784
	 */
2785
	public static function catch_errors( $catch ) {
2786
		static $display_errors, $error_reporting;
2787
2788
		if ( $catch ) {
2789
			$display_errors  = @ini_set( 'display_errors', 1 );
2790
			$error_reporting = @error_reporting( E_ALL );
2791
			add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2792
		} else {
2793
			@ini_set( 'display_errors', $display_errors );
2794
			@error_reporting( $error_reporting );
2795
			remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2796
		}
2797
	}
2798
2799
	/**
2800
	 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2801
	 */
2802
	public static function catch_errors_on_shutdown() {
2803
		self::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2804
	}
2805
2806
	/**
2807
	 * Rewrite any string to make paths easier to read.
2808
	 *
2809
	 * Rewrites ABSPATH (eg `/home/jetpack/wordpress/`) to ABSPATH, and if WP_CONTENT_DIR
2810
	 * is located outside of ABSPATH, rewrites that to WP_CONTENT_DIR.
2811
	 *
2812
	 * @param $string
2813
	 * @return mixed
2814
	 */
2815
	public static function alias_directories( $string ) {
2816
		// ABSPATH has a trailing slash.
2817
		$string = str_replace( ABSPATH, 'ABSPATH/', $string );
2818
		// WP_CONTENT_DIR does not have a trailing slash.
2819
		$string = str_replace( WP_CONTENT_DIR, 'WP_CONTENT_DIR', $string );
2820
2821
		return $string;
2822
	}
2823
2824
	public static function activate_default_modules(
2825
		$min_version = false,
2826
		$max_version = false,
2827
		$other_modules = array(),
2828
		$redirect = null,
2829
		$send_state_messages = null
2830
	) {
2831
		$jetpack = self::init();
2832
2833
		if ( is_null( $redirect ) ) {
2834
			if (
2835
				( defined( 'REST_REQUEST' ) && REST_REQUEST )
2836
			||
2837
				( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
2838
			||
2839
				( defined( 'WP_CLI' ) && WP_CLI )
2840
			||
2841
				( defined( 'DOING_CRON' ) && DOING_CRON )
2842
			||
2843
				( defined( 'DOING_AJAX' ) && DOING_AJAX )
2844
			) {
2845
				$redirect = false;
2846
			} elseif ( is_admin() ) {
2847
				$redirect = true;
2848
			} else {
2849
				$redirect = false;
2850
			}
2851
		}
2852
2853
		if ( is_null( $send_state_messages ) ) {
2854
			$send_state_messages = current_user_can( 'jetpack_activate_modules' );
2855
		}
2856
2857
		$modules = self::get_default_modules( $min_version, $max_version );
2858
		$modules = array_merge( $other_modules, $modules );
2859
2860
		// Look for standalone plugins and disable if active.
2861
2862
		$to_deactivate = array();
2863
		foreach ( $modules as $module ) {
2864
			if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2865
				$to_deactivate[ $module ] = $jetpack->plugins_to_deactivate[ $module ];
2866
			}
2867
		}
2868
2869
		$deactivated = array();
2870
		foreach ( $to_deactivate as $module => $deactivate_me ) {
2871
			list( $probable_file, $probable_title ) = $deactivate_me;
2872
			if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2873
				$deactivated[] = $module;
2874
			}
2875
		}
2876
2877
		if ( $deactivated ) {
2878
			if ( $send_state_messages ) {
2879
				self::state( 'deactivated_plugins', join( ',', $deactivated ) );
2880
			}
2881
2882
			if ( $redirect ) {
2883
				$url = add_query_arg(
2884
					array(
2885
						'action'   => 'activate_default_modules',
2886
						'_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2887
					),
2888
					add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2889
				);
2890
				wp_safe_redirect( $url );
2891
				exit;
2892
			}
2893
		}
2894
2895
		/**
2896
		 * Fires before default modules are activated.
2897
		 *
2898
		 * @since 1.9.0
2899
		 *
2900
		 * @param string $min_version Minimum version number required to use modules.
2901
		 * @param string $max_version Maximum version number required to use modules.
2902
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2903
		 */
2904
		do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
2905
2906
		// Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
2907
		if ( $send_state_messages ) {
2908
			self::restate();
2909
			self::catch_errors( true );
2910
		}
2911
2912
		$active = self::get_active_modules();
2913
2914
		foreach ( $modules as $module ) {
2915
			if ( did_action( "jetpack_module_loaded_$module" ) ) {
2916
				$active[] = $module;
2917
				self::update_active_modules( $active );
2918
				continue;
2919
			}
2920
2921
			if ( $send_state_messages && in_array( $module, $active ) ) {
2922
				$module_info = self::get_module( $module );
2923 View Code Duplication
				if ( ! $module_info['deactivate'] ) {
2924
					$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2925
					if ( $active_state = self::state( $state ) ) {
2926
						$active_state = explode( ',', $active_state );
2927
					} else {
2928
						$active_state = array();
2929
					}
2930
					$active_state[] = $module;
2931
					self::state( $state, implode( ',', $active_state ) );
2932
				}
2933
				continue;
2934
			}
2935
2936
			$file = self::get_module_path( $module );
2937
			if ( ! file_exists( $file ) ) {
2938
				continue;
2939
			}
2940
2941
			// we'll override this later if the plugin can be included without fatal error
2942
			if ( $redirect ) {
2943
				wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
2944
			}
2945
2946
			if ( $send_state_messages ) {
2947
				self::state( 'error', 'module_activation_failed' );
2948
				self::state( 'module', $module );
2949
			}
2950
2951
			ob_start();
2952
			require_once $file;
2953
2954
			$active[] = $module;
2955
2956 View Code Duplication
			if ( $send_state_messages ) {
2957
2958
				$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2959
				if ( $active_state = self::state( $state ) ) {
2960
					$active_state = explode( ',', $active_state );
2961
				} else {
2962
					$active_state = array();
2963
				}
2964
				$active_state[] = $module;
2965
				self::state( $state, implode( ',', $active_state ) );
2966
			}
2967
2968
			self::update_active_modules( $active );
2969
2970
			ob_end_clean();
2971
		}
2972
2973
		if ( $send_state_messages ) {
2974
			self::state( 'error', false );
2975
			self::state( 'module', false );
2976
		}
2977
2978
		self::catch_errors( false );
2979
		/**
2980
		 * Fires when default modules are activated.
2981
		 *
2982
		 * @since 1.9.0
2983
		 *
2984
		 * @param string $min_version Minimum version number required to use modules.
2985
		 * @param string $max_version Maximum version number required to use modules.
2986
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2987
		 */
2988
		do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
2989
	}
2990
2991
	public static function activate_module( $module, $exit = true, $redirect = true ) {
2992
		/**
2993
		 * Fires before a module is activated.
2994
		 *
2995
		 * @since 2.6.0
2996
		 *
2997
		 * @param string $module Module slug.
2998
		 * @param bool $exit Should we exit after the module has been activated. Default to true.
2999
		 * @param bool $redirect Should the user be redirected after module activation? Default to true.
3000
		 */
3001
		do_action( 'jetpack_pre_activate_module', $module, $exit, $redirect );
3002
3003
		$jetpack = self::init();
3004
3005
		if ( ! strlen( $module ) ) {
3006
			return false;
3007
		}
3008
3009
		if ( ! self::is_module( $module ) ) {
3010
			return false;
3011
		}
3012
3013
		// If it's already active, then don't do it again
3014
		$active = self::get_active_modules();
3015
		foreach ( $active as $act ) {
3016
			if ( $act == $module ) {
3017
				return true;
3018
			}
3019
		}
3020
3021
		$module_data = self::get_module( $module );
3022
3023
		$is_development_mode = ( new Status() )->is_development_mode();
3024
		if ( ! self::is_active() ) {
3025
			if ( ! $is_development_mode && ! self::is_onboarding() ) {
3026
				return false;
3027
			}
3028
3029
			// If we're not connected but in development mode, make sure the module doesn't require a connection
3030
			if ( $is_development_mode && $module_data['requires_connection'] ) {
3031
				return false;
3032
			}
3033
		}
3034
3035
		// Check and see if the old plugin is active
3036
		if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
3037
			// Deactivate the old plugin
3038
			if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[ $module ][0], $jetpack->plugins_to_deactivate[ $module ][1] ) ) {
3039
				// If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
3040
				// We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
3041
				self::state( 'deactivated_plugins', $module );
3042
				wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
3043
				exit;
3044
			}
3045
		}
3046
3047
		// Protect won't work with mis-configured IPs
3048
		if ( 'protect' === $module ) {
3049
			include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
3050
			if ( ! jetpack_protect_get_ip() ) {
3051
				self::state( 'message', 'protect_misconfigured_ip' );
3052
				return false;
3053
			}
3054
		}
3055
3056
		if ( ! Jetpack_Plan::supports( $module ) ) {
3057
			return false;
3058
		}
3059
3060
		// Check the file for fatal errors, a la wp-admin/plugins.php::activate
3061
		self::state( 'module', $module );
3062
		self::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
3063
3064
		self::catch_errors( true );
3065
		ob_start();
3066
		require self::get_module_path( $module );
3067
		/** This action is documented in class.jetpack.php */
3068
		do_action( 'jetpack_activate_module', $module );
3069
		$active[] = $module;
3070
		self::update_active_modules( $active );
3071
3072
		self::state( 'error', false ); // the override
3073
		ob_end_clean();
3074
		self::catch_errors( false );
3075
3076
		if ( $redirect ) {
3077
			wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3078
		}
3079
		if ( $exit ) {
3080
			exit;
3081
		}
3082
		return true;
3083
	}
3084
3085
	function activate_module_actions( $module ) {
3086
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
3087
	}
3088
3089
	public static function deactivate_module( $module ) {
3090
		/**
3091
		 * Fires when a module is deactivated.
3092
		 *
3093
		 * @since 1.9.0
3094
		 *
3095
		 * @param string $module Module slug.
3096
		 */
3097
		do_action( 'jetpack_pre_deactivate_module', $module );
3098
3099
		$jetpack = self::init();
3100
3101
		$active = self::get_active_modules();
3102
		$new    = array_filter( array_diff( $active, (array) $module ) );
3103
3104
		return self::update_active_modules( $new );
3105
	}
3106
3107
	public static function enable_module_configurable( $module ) {
3108
		$module = self::get_module_slug( $module );
3109
		add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
3110
	}
3111
3112
	/**
3113
	 * Composes a module configure URL. It uses Jetpack settings search as default value
3114
	 * It is possible to redefine resulting URL by using "jetpack_module_configuration_url_$module" filter
3115
	 *
3116
	 * @param string $module Module slug
3117
	 * @return string $url module configuration URL
3118
	 */
3119
	public static function module_configuration_url( $module ) {
3120
		$module      = self::get_module_slug( $module );
3121
		$default_url = self::admin_url() . "#/settings?term=$module";
3122
		/**
3123
		 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
3124
		 *
3125
		 * @since 6.9.0
3126
		 *
3127
		 * @param string $default_url Default url, which redirects to jetpack settings page.
3128
		 */
3129
		$url = apply_filters( 'jetpack_module_configuration_url_' . $module, $default_url );
3130
3131
		return $url;
3132
	}
3133
3134
	/* Installation */
3135
	public static function bail_on_activation( $message, $deactivate = true ) {
3136
		?>
3137
<!doctype html>
3138
<html>
3139
<head>
3140
<meta charset="<?php bloginfo( 'charset' ); ?>">
3141
<style>
3142
* {
3143
	text-align: center;
3144
	margin: 0;
3145
	padding: 0;
3146
	font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
3147
}
3148
p {
3149
	margin-top: 1em;
3150
	font-size: 18px;
3151
}
3152
</style>
3153
<body>
3154
<p><?php echo esc_html( $message ); ?></p>
3155
</body>
3156
</html>
3157
		<?php
3158
		if ( $deactivate ) {
3159
			$plugins = get_option( 'active_plugins' );
3160
			$jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
3161
			$update  = false;
3162
			foreach ( $plugins as $i => $plugin ) {
3163
				if ( $plugin === $jetpack ) {
3164
					$plugins[ $i ] = false;
3165
					$update        = true;
3166
				}
3167
			}
3168
3169
			if ( $update ) {
3170
				update_option( 'active_plugins', array_filter( $plugins ) );
3171
			}
3172
		}
3173
		exit;
3174
	}
3175
3176
	/**
3177
	 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
3178
	 *
3179
	 * @static
3180
	 */
3181
	public static function plugin_activation( $network_wide ) {
3182
		Jetpack_Options::update_option( 'activated', 1 );
3183
3184
		if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
3185
			self::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
3186
		}
3187
3188
		if ( $network_wide ) {
3189
			self::state( 'network_nag', true );
3190
		}
3191
3192
		// For firing one-off events (notices) immediately after activation
3193
		set_transient( 'activated_jetpack', true, .1 * MINUTE_IN_SECONDS );
3194
3195
		update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
3196
3197
		self::plugin_initialize();
3198
	}
3199
3200
	public static function get_activation_source( $referer_url ) {
3201
3202
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
3203
			return array( 'wp-cli', null );
3204
		}
3205
3206
		$referer = wp_parse_url( $referer_url );
3207
3208
		$source_type  = 'unknown';
3209
		$source_query = null;
3210
3211
		if ( ! is_array( $referer ) ) {
3212
			return array( $source_type, $source_query );
3213
		}
3214
3215
		$plugins_path         = wp_parse_url( admin_url( 'plugins.php' ), PHP_URL_PATH );
3216
		$plugins_install_path = wp_parse_url( admin_url( 'plugin-install.php' ), PHP_URL_PATH );// /wp-admin/plugin-install.php
3217
3218
		if ( isset( $referer['query'] ) ) {
3219
			parse_str( $referer['query'], $query_parts );
3220
		} else {
3221
			$query_parts = array();
3222
		}
3223
3224
		if ( $plugins_path === $referer['path'] ) {
3225
			$source_type = 'list';
3226
		} elseif ( $plugins_install_path === $referer['path'] ) {
3227
			$tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
3228
			switch ( $tab ) {
3229
				case 'popular':
3230
					$source_type = 'popular';
3231
					break;
3232
				case 'recommended':
3233
					$source_type = 'recommended';
3234
					break;
3235
				case 'favorites':
3236
					$source_type = 'favorites';
3237
					break;
3238
				case 'search':
3239
					$source_type  = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
3240
					$source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
3241
					break;
3242
				default:
3243
					$source_type = 'featured';
3244
			}
3245
		}
3246
3247
		return array( $source_type, $source_query );
3248
	}
3249
3250
	/**
3251
	 * Runs before bumping version numbers up to a new version
3252
	 *
3253
	 * @param  string $version    Version:timestamp
3254
	 * @param  string $old_version Old Version:timestamp or false if not set yet.
3255
	 * @return null              [description]
3256
	 */
3257
	public static function do_version_bump( $version, $old_version ) {
3258
		if ( ! $old_version ) { // For new sites
3259
			// There used to be stuff here, but this seems like it might  be useful to someone in the future...
3260
		}
3261
	}
3262
3263
	/**
3264
	 * Sets the internal version number and activation state.
3265
	 *
3266
	 * @static
3267
	 */
3268
	public static function plugin_initialize() {
3269
		if ( ! Jetpack_Options::get_option( 'activated' ) ) {
3270
			Jetpack_Options::update_option( 'activated', 2 );
3271
		}
3272
3273 View Code Duplication
		if ( ! Jetpack_Options::get_option( 'version' ) ) {
3274
			$version = $old_version = JETPACK__VERSION . ':' . time();
3275
			/** This action is documented in class.jetpack.php */
3276
			do_action( 'updating_jetpack_version', $version, false );
3277
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
3278
		}
3279
3280
		self::load_modules();
3281
3282
		Jetpack_Options::delete_option( 'do_activate' );
3283
		Jetpack_Options::delete_option( 'dismissed_connection_banner' );
3284
	}
3285
3286
	/**
3287
	 * Removes all connection options
3288
	 *
3289
	 * @static
3290
	 */
3291
	public static function plugin_deactivation() {
3292
		require_once ABSPATH . '/wp-admin/includes/plugin.php';
3293
		if ( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
3294
			Jetpack_Network::init()->deactivate();
3295
		} else {
3296
			self::disconnect( false );
3297
			// Jetpack_Heartbeat::init()->deactivate();
3298
		}
3299
	}
3300
3301
	/**
3302
	 * Disconnects from the Jetpack servers.
3303
	 * Forgets all connection details and tells the Jetpack servers to do the same.
3304
	 *
3305
	 * @static
3306
	 */
3307
	public static function disconnect( $update_activated_state = true ) {
3308
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
3309
		$connection = self::connection();
3310
		$connection->clean_nonces( true );
3311
3312
		// If the site is in an IDC because sync is not allowed,
3313
		// let's make sure to not disconnect the production site.
3314
		if ( ! self::validate_sync_error_idc_option() ) {
3315
			$tracking = new Tracking();
3316
			$tracking->record_user_event( 'disconnect_site', array() );
3317
3318
			$connection->disconnect_site_wpcom();
3319
		}
3320
3321
		$connection->delete_all_connection_tokens();
3322
		Jetpack_IDC::clear_all_idc_options();
3323
3324
		if ( $update_activated_state ) {
3325
			Jetpack_Options::update_option( 'activated', 4 );
3326
		}
3327
3328
		if ( $jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' ) ) {
3329
			// Check then record unique disconnection if site has never been disconnected previously
3330
			if ( - 1 == $jetpack_unique_connection['disconnected'] ) {
3331
				$jetpack_unique_connection['disconnected'] = 1;
3332
			} else {
3333
				if ( 0 == $jetpack_unique_connection['disconnected'] ) {
3334
					// track unique disconnect
3335
					$jetpack = self::init();
3336
3337
					$jetpack->stat( 'connections', 'unique-disconnect' );
3338
					$jetpack->do_stats( 'server_side' );
3339
				}
3340
				// increment number of times disconnected
3341
				$jetpack_unique_connection['disconnected'] += 1;
3342
			}
3343
3344
			Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
3345
		}
3346
3347
		// Delete all the sync related data. Since it could be taking up space.
3348
		Sender::get_instance()->uninstall();
3349
3350
		// Disable the Heartbeat cron
3351
		Jetpack_Heartbeat::init()->deactivate();
3352
	}
3353
3354
	/**
3355
	 * Unlinks the current user from the linked WordPress.com user.
3356
	 *
3357
	 * @deprecated since 7.7
3358
	 * @see Automattic\Jetpack\Connection\Manager::disconnect_user()
3359
	 *
3360
	 * @param Integer $user_id the user identifier.
3361
	 * @return Boolean Whether the disconnection of the user was successful.
3362
	 */
3363
	public static function unlink_user( $user_id = null ) {
3364
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::disconnect_user' );
3365
		return Connection_Manager::disconnect_user( $user_id );
3366
	}
3367
3368
	/**
3369
	 * Attempts Jetpack registration.  If it fail, a state flag is set: @see ::admin_page_load()
3370
	 */
3371
	public static function try_registration() {
3372
		$terms_of_service = new Terms_Of_Service();
3373
		// The user has agreed to the TOS at some point by now.
3374
		$terms_of_service->agree();
3375
3376
		// Let's get some testing in beta versions and such.
3377
		if ( self::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
3378
			// Before attempting to connect, let's make sure that the domains are viable.
3379
			$domains_to_check = array_unique(
3380
				array(
3381
					'siteurl' => wp_parse_url( get_site_url(), PHP_URL_HOST ),
3382
					'homeurl' => wp_parse_url( get_home_url(), PHP_URL_HOST ),
3383
				)
3384
			);
3385
			foreach ( $domains_to_check as $domain ) {
3386
				$result = self::connection()->is_usable_domain( $domain );
3387
				if ( is_wp_error( $result ) ) {
3388
					return $result;
3389
				}
3390
			}
3391
		}
3392
3393
		$result = self::register();
3394
3395
		// If there was an error with registration and the site was not registered, record this so we can show a message.
3396
		if ( ! $result || is_wp_error( $result ) ) {
3397
			return $result;
3398
		} else {
3399
			return true;
3400
		}
3401
	}
3402
3403
	/**
3404
	 * Tracking an internal event log. Try not to put too much chaff in here.
3405
	 *
3406
	 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
3407
	 */
3408
	public static function log( $code, $data = null ) {
3409
		// only grab the latest 200 entries
3410
		$log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3411
3412
		// Append our event to the log
3413
		$log_entry = array(
3414
			'time'    => time(),
3415
			'user_id' => get_current_user_id(),
3416
			'blog_id' => Jetpack_Options::get_option( 'id' ),
3417
			'code'    => $code,
3418
		);
3419
		// Don't bother storing it unless we've got some.
3420
		if ( ! is_null( $data ) ) {
3421
			$log_entry['data'] = $data;
3422
		}
3423
		$log[] = $log_entry;
3424
3425
		// Try add_option first, to make sure it's not autoloaded.
3426
		// @todo: Add an add_option method to Jetpack_Options
3427
		if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
3428
			Jetpack_Options::update_option( 'log', $log );
3429
		}
3430
3431
		/**
3432
		 * Fires when Jetpack logs an internal event.
3433
		 *
3434
		 * @since 3.0.0
3435
		 *
3436
		 * @param array $log_entry {
3437
		 *  Array of details about the log entry.
3438
		 *
3439
		 *  @param string time Time of the event.
3440
		 *  @param int user_id ID of the user who trigerred the event.
3441
		 *  @param int blog_id Jetpack Blog ID.
3442
		 *  @param string code Unique name for the event.
3443
		 *  @param string data Data about the event.
3444
		 * }
3445
		 */
3446
		do_action( 'jetpack_log_entry', $log_entry );
3447
	}
3448
3449
	/**
3450
	 * Get the internal event log.
3451
	 *
3452
	 * @param $event (string) - only return the specific log events
3453
	 * @param $num   (int)    - get specific number of latest results, limited to 200
3454
	 *
3455
	 * @return array of log events || WP_Error for invalid params
3456
	 */
3457
	public static function get_log( $event = false, $num = false ) {
3458
		if ( $event && ! is_string( $event ) ) {
3459
			return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
3460
		}
3461
3462
		if ( $num && ! is_numeric( $num ) ) {
3463
			return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
3464
		}
3465
3466
		$entire_log = Jetpack_Options::get_option( 'log', array() );
3467
3468
		// If nothing set - act as it did before, otherwise let's start customizing the output
3469
		if ( ! $num && ! $event ) {
3470
			return $entire_log;
3471
		} else {
3472
			$entire_log = array_reverse( $entire_log );
3473
		}
3474
3475
		$custom_log_output = array();
3476
3477
		if ( $event ) {
3478
			foreach ( $entire_log as $log_event ) {
3479
				if ( $event == $log_event['code'] ) {
3480
					$custom_log_output[] = $log_event;
3481
				}
3482
			}
3483
		} else {
3484
			$custom_log_output = $entire_log;
3485
		}
3486
3487
		if ( $num ) {
3488
			$custom_log_output = array_slice( $custom_log_output, 0, $num );
3489
		}
3490
3491
		return $custom_log_output;
3492
	}
3493
3494
	/**
3495
	 * Log modification of important settings.
3496
	 */
3497
	public static function log_settings_change( $option, $old_value, $value ) {
3498
		switch ( $option ) {
3499
			case 'jetpack_sync_non_public_post_stati':
3500
				self::log( $option, $value );
3501
				break;
3502
		}
3503
	}
3504
3505
	/**
3506
	 * Return stat data for WPCOM sync
3507
	 */
3508
	public static function get_stat_data( $encode = true, $extended = true ) {
3509
		$data = Jetpack_Heartbeat::generate_stats_array();
3510
3511
		if ( $extended ) {
3512
			$additional_data = self::get_additional_stat_data();
3513
			$data            = array_merge( $data, $additional_data );
3514
		}
3515
3516
		if ( $encode ) {
3517
			return json_encode( $data );
3518
		}
3519
3520
		return $data;
3521
	}
3522
3523
	/**
3524
	 * Get additional stat data to sync to WPCOM
3525
	 */
3526
	public static function get_additional_stat_data( $prefix = '' ) {
3527
		$return[ "{$prefix}themes" ]        = self::get_parsed_theme_data();
3528
		$return[ "{$prefix}plugins-extra" ] = self::get_parsed_plugin_data();
3529
		$return[ "{$prefix}users" ]         = (int) self::get_site_user_count();
3530
		$return[ "{$prefix}site-count" ]    = 0;
3531
3532
		if ( function_exists( 'get_blog_count' ) ) {
3533
			$return[ "{$prefix}site-count" ] = get_blog_count();
3534
		}
3535
		return $return;
3536
	}
3537
3538
	private static function get_site_user_count() {
3539
		global $wpdb;
3540
3541
		if ( function_exists( 'wp_is_large_network' ) ) {
3542
			if ( wp_is_large_network( 'users' ) ) {
3543
				return -1; // Not a real value but should tell us that we are dealing with a large network.
3544
			}
3545
		}
3546
		if ( false === ( $user_count = get_transient( 'jetpack_site_user_count' ) ) ) {
3547
			// It wasn't there, so regenerate the data and save the transient
3548
			$user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities'" );
3549
			set_transient( 'jetpack_site_user_count', $user_count, DAY_IN_SECONDS );
3550
		}
3551
		return $user_count;
3552
	}
3553
3554
	/* Admin Pages */
3555
3556
	function admin_init() {
3557
		// If the plugin is not connected, display a connect message.
3558
		if (
3559
			// the plugin was auto-activated and needs its candy
3560
			Jetpack_Options::get_option_and_ensure_autoload( 'do_activate', '0' )
3561
		||
3562
			// the plugin is active, but was never activated.  Probably came from a site-wide network activation
3563
			! Jetpack_Options::get_option( 'activated' )
3564
		) {
3565
			self::plugin_initialize();
3566
		}
3567
3568
		$is_development_mode = ( new Status() )->is_development_mode();
3569
		if ( ! self::is_active() && ! $is_development_mode ) {
3570
			Jetpack_Connection_Banner::init();
3571
		} elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
3572
			// Upgrade: 1.1 -> 1.1.1
3573
			// Check and see if host can verify the Jetpack servers' SSL certificate
3574
			$args       = array();
3575
			$connection = self::connection();
3576
			Client::_wp_remote_request(
3577
				Connection_Utils::fix_url_for_bad_hosts( $connection->api_url( 'test' ) ),
3578
				$args,
3579
				true
3580
			);
3581
		}
3582
3583
		if ( current_user_can( 'manage_options' ) && 'AUTO' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
3584
			add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3585
		}
3586
3587
		add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3588
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3589
		add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3590
3591
		if ( self::is_active() || $is_development_mode ) {
3592
			// Artificially throw errors in certain whitelisted cases during plugin activation
3593
			add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3594
		}
3595
3596
		// Add custom column in wp-admin/users.php to show whether user is linked.
3597
		add_filter( 'manage_users_columns', array( $this, 'jetpack_icon_user_connected' ) );
3598
		add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_user_connected_icon' ), 10, 3 );
3599
		add_action( 'admin_print_styles', array( $this, 'jetpack_user_col_style' ) );
3600
	}
3601
3602
	function admin_body_class( $admin_body_class = '' ) {
3603
		$classes = explode( ' ', trim( $admin_body_class ) );
3604
3605
		$classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
3606
3607
		$admin_body_class = implode( ' ', array_unique( $classes ) );
3608
		return " $admin_body_class ";
3609
	}
3610
3611
	static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3612
		return $admin_body_class . ' jetpack-pagestyles ';
3613
	}
3614
3615
	/**
3616
	 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3617
	 * This function artificially throws errors for such cases (whitelisted).
3618
	 *
3619
	 * @param string $plugin The activated plugin.
3620
	 */
3621
	function throw_error_on_activate_plugin( $plugin ) {
3622
		$active_modules = self::get_active_modules();
3623
3624
		// The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3625
		if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3626
			$throw = false;
3627
3628
			// Try and make sure it really was the stats plugin
3629
			if ( ! class_exists( 'ReflectionFunction' ) ) {
3630
				if ( 'stats.php' == basename( $plugin ) ) {
3631
					$throw = true;
3632
				}
3633
			} else {
3634
				$reflection = new ReflectionFunction( 'stats_get_api_key' );
3635
				if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3636
					$throw = true;
3637
				}
3638
			}
3639
3640
			if ( $throw ) {
3641
				trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3642
			}
3643
		}
3644
	}
3645
3646
	function intercept_plugin_error_scrape_init() {
3647
		add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3648
	}
3649
3650
	function intercept_plugin_error_scrape( $action, $result ) {
3651
		if ( ! $result ) {
3652
			return;
3653
		}
3654
3655
		foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3656
			if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3657
				self::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3658
			}
3659
		}
3660
	}
3661
3662
	/**
3663
	 * Register the remote file upload request handlers, if needed.
3664
	 *
3665
	 * @access public
3666
	 */
3667
	public function add_remote_request_handlers() {
3668
		// Remote file uploads are allowed only via AJAX requests.
3669
		if ( ! is_admin() || ! Constants::get_constant( 'DOING_AJAX' ) ) {
3670
			return;
3671
		}
3672
3673
		// Remote file uploads are allowed only for a set of specific AJAX actions.
3674
		$remote_request_actions = array(
3675
			'jetpack_upload_file',
3676
			'jetpack_update_file',
3677
		);
3678
3679
		// phpcs:ignore WordPress.Security.NonceVerification
3680
		if ( ! isset( $_POST['action'] ) || ! in_array( $_POST['action'], $remote_request_actions, true ) ) {
3681
			return;
3682
		}
3683
3684
		// Require Jetpack authentication for the remote file upload AJAX requests.
3685
		$this->connection_manager->require_jetpack_authentication();
3686
3687
		// Register the remote file upload AJAX handlers.
3688
		foreach ( $remote_request_actions as $action ) {
3689
			add_action( "wp_ajax_nopriv_{$action}", array( $this, 'remote_request_handlers' ) );
3690
		}
3691
	}
3692
3693
	/**
3694
	 * Handler for Jetpack remote file uploads.
3695
	 *
3696
	 * @access public
3697
	 */
3698
	public function remote_request_handlers() {
3699
		$action = current_filter();
3700
3701
		switch ( current_filter() ) {
3702
			case 'wp_ajax_nopriv_jetpack_upload_file':
3703
				$response = $this->upload_handler();
3704
				break;
3705
3706
			case 'wp_ajax_nopriv_jetpack_update_file':
3707
				$response = $this->upload_handler( true );
3708
				break;
3709
			default:
3710
				$response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
3711
				break;
3712
		}
3713
3714
		if ( ! $response ) {
3715
			$response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
3716
		}
3717
3718
		if ( is_wp_error( $response ) ) {
3719
			$status_code       = $response->get_error_data();
3720
			$error             = $response->get_error_code();
3721
			$error_description = $response->get_error_message();
3722
3723
			if ( ! is_int( $status_code ) ) {
3724
				$status_code = 400;
3725
			}
3726
3727
			status_header( $status_code );
3728
			die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3729
		}
3730
3731
		status_header( 200 );
3732
		if ( true === $response ) {
3733
			exit;
3734
		}
3735
3736
		die( json_encode( (object) $response ) );
3737
	}
3738
3739
	/**
3740
	 * Uploads a file gotten from the global $_FILES.
3741
	 * If `$update_media_item` is true and `post_id` is defined
3742
	 * the attachment file of the media item (gotten through of the post_id)
3743
	 * will be updated instead of add a new one.
3744
	 *
3745
	 * @param  boolean $update_media_item - update media attachment
3746
	 * @return array - An array describing the uploadind files process
3747
	 */
3748
	function upload_handler( $update_media_item = false ) {
3749
		if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3750
			return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
3751
		}
3752
3753
		$user = wp_authenticate( '', '' );
3754
		if ( ! $user || is_wp_error( $user ) ) {
3755
			return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
3756
		}
3757
3758
		wp_set_current_user( $user->ID );
3759
3760
		if ( ! current_user_can( 'upload_files' ) ) {
3761
			return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3762
		}
3763
3764
		if ( empty( $_FILES ) ) {
3765
			return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3766
		}
3767
3768
		foreach ( array_keys( $_FILES ) as $files_key ) {
3769
			if ( ! isset( $_POST[ "_jetpack_file_hmac_{$files_key}" ] ) ) {
3770
				return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3771
			}
3772
		}
3773
3774
		$media_keys = array_keys( $_FILES['media'] );
3775
3776
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
3777
		if ( ! $token || is_wp_error( $token ) ) {
3778
			return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3779
		}
3780
3781
		$uploaded_files = array();
3782
		$global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3783
		unset( $GLOBALS['post'] );
3784
		foreach ( $_FILES['media']['name'] as $index => $name ) {
3785
			$file = array();
3786
			foreach ( $media_keys as $media_key ) {
3787
				$file[ $media_key ] = $_FILES['media'][ $media_key ][ $index ];
3788
			}
3789
3790
			list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][ $index ] );
3791
3792
			$hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3793
			if ( $hmac_provided !== $hmac_file ) {
3794
				$uploaded_files[ $index ] = (object) array(
3795
					'error'             => 'invalid_hmac',
3796
					'error_description' => 'The corresponding HMAC for this file does not match',
3797
				);
3798
				continue;
3799
			}
3800
3801
			$_FILES['.jetpack.upload.'] = $file;
3802
			$post_id                    = isset( $_POST['post_id'][ $index ] ) ? absint( $_POST['post_id'][ $index ] ) : 0;
3803
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
3804
				$post_id = 0;
3805
			}
3806
3807
			if ( $update_media_item ) {
3808
				if ( ! isset( $post_id ) || $post_id === 0 ) {
3809
					return new Jetpack_Error( 'invalid_input', 'Media ID must be defined.', 400 );
3810
				}
3811
3812
				$media_array = $_FILES['media'];
3813
3814
				$file_array['name']     = $media_array['name'][0];
3815
				$file_array['type']     = $media_array['type'][0];
3816
				$file_array['tmp_name'] = $media_array['tmp_name'][0];
3817
				$file_array['error']    = $media_array['error'][0];
3818
				$file_array['size']     = $media_array['size'][0];
3819
3820
				$edited_media_item = Jetpack_Media::edit_media_file( $post_id, $file_array );
3821
3822
				if ( is_wp_error( $edited_media_item ) ) {
3823
					return $edited_media_item;
3824
				}
3825
3826
				$response = (object) array(
3827
					'id'   => (string) $post_id,
3828
					'file' => (string) $edited_media_item->post_title,
3829
					'url'  => (string) wp_get_attachment_url( $post_id ),
3830
					'type' => (string) $edited_media_item->post_mime_type,
3831
					'meta' => (array) wp_get_attachment_metadata( $post_id ),
3832
				);
3833
3834
				return (array) array( $response );
3835
			}
3836
3837
			$attachment_id = media_handle_upload(
3838
				'.jetpack.upload.',
3839
				$post_id,
3840
				array(),
3841
				array(
3842
					'action' => 'jetpack_upload_file',
3843
				)
3844
			);
3845
3846
			if ( ! $attachment_id ) {
3847
				$uploaded_files[ $index ] = (object) array(
3848
					'error'             => 'unknown',
3849
					'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site',
3850
				);
3851
			} elseif ( is_wp_error( $attachment_id ) ) {
3852
				$uploaded_files[ $index ] = (object) array(
3853
					'error'             => 'attachment_' . $attachment_id->get_error_code(),
3854
					'error_description' => $attachment_id->get_error_message(),
3855
				);
3856
			} else {
3857
				$attachment               = get_post( $attachment_id );
3858
				$uploaded_files[ $index ] = (object) array(
3859
					'id'   => (string) $attachment_id,
3860
					'file' => $attachment->post_title,
3861
					'url'  => wp_get_attachment_url( $attachment_id ),
3862
					'type' => $attachment->post_mime_type,
3863
					'meta' => wp_get_attachment_metadata( $attachment_id ),
3864
				);
3865
				// Zip files uploads are not supported unless they are done for installation purposed
3866
				// lets delete them in case something goes wrong in this whole process
3867
				if ( 'application/zip' === $attachment->post_mime_type ) {
3868
					// Schedule a cleanup for 2 hours from now in case of failed install.
3869
					wp_schedule_single_event( time() + 2 * HOUR_IN_SECONDS, 'upgrader_scheduled_cleanup', array( $attachment_id ) );
3870
				}
3871
			}
3872
		}
3873
		if ( ! is_null( $global_post ) ) {
3874
			$GLOBALS['post'] = $global_post;
3875
		}
3876
3877
		return $uploaded_files;
3878
	}
3879
3880
	/**
3881
	 * Add help to the Jetpack page
3882
	 *
3883
	 * @since Jetpack (1.2.3)
3884
	 * @return false if not the Jetpack page
3885
	 */
3886
	function admin_help() {
3887
		$current_screen = get_current_screen();
3888
3889
		// Overview
3890
		$current_screen->add_help_tab(
3891
			array(
3892
				'id'      => 'home',
3893
				'title'   => __( 'Home', 'jetpack' ),
3894
				'content' =>
3895
					'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3896
					'<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3897
					'<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>',
3898
			)
3899
		);
3900
3901
		// Screen Content
3902
		if ( current_user_can( 'manage_options' ) ) {
3903
			$current_screen->add_help_tab(
3904
				array(
3905
					'id'      => 'settings',
3906
					'title'   => __( 'Settings', 'jetpack' ),
3907
					'content' =>
3908
						'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3909
						'<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3910
						'<ol>' .
3911
							'<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3912
							'<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>' .
3913
						'</ol>' .
3914
						'<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>',
3915
				)
3916
			);
3917
		}
3918
3919
		// Help Sidebar
3920
		$current_screen->set_help_sidebar(
3921
			'<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3922
			'<p><a href="https://jetpack.com/faq/" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3923
			'<p><a href="https://jetpack.com/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3924
			'<p><a href="' . self::admin_url( array( 'page' => 'jetpack-debugger' ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3925
		);
3926
	}
3927
3928
	function admin_menu_css() {
3929
		wp_enqueue_style( 'jetpack-icons' );
3930
	}
3931
3932
	function admin_menu_order() {
3933
		return true;
3934
	}
3935
3936 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
3937
		$jp_menu_order = array();
3938
3939
		foreach ( $menu_order as $index => $item ) {
3940
			if ( $item != 'jetpack' ) {
3941
				$jp_menu_order[] = $item;
3942
			}
3943
3944
			if ( $index == 0 ) {
3945
				$jp_menu_order[] = 'jetpack';
3946
			}
3947
		}
3948
3949
		return $jp_menu_order;
3950
	}
3951
3952
	function admin_banner_styles() {
3953
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3954
3955 View Code Duplication
		if ( ! wp_style_is( 'jetpack-dops-style' ) ) {
3956
			wp_register_style(
3957
				'jetpack-dops-style',
3958
				plugins_url( '_inc/build/admin.css', JETPACK__PLUGIN_FILE ),
3959
				array(),
3960
				JETPACK__VERSION
3961
			);
3962
		}
3963
3964
		wp_enqueue_style(
3965
			'jetpack',
3966
			plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ),
3967
			array( 'jetpack-dops-style' ),
3968
			JETPACK__VERSION . '-20121016'
3969
		);
3970
		wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3971
		wp_style_add_data( 'jetpack', 'suffix', $min );
3972
	}
3973
3974
	function plugin_action_links( $actions ) {
3975
3976
		$jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', self::admin_url( 'page=jetpack' ), 'Jetpack' ) );
3977
3978
		if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_active() || ( new Status() )->is_development_mode() ) ) {
3979
			return array_merge(
3980
				$jetpack_home,
3981
				array( 'settings' => sprintf( '<a href="%s">%s</a>', self::admin_url( 'page=jetpack#/settings' ), __( 'Settings', 'jetpack' ) ) ),
3982
				array( 'support' => sprintf( '<a href="%s">%s</a>', self::admin_url( 'page=jetpack-debugger ' ), __( 'Support', 'jetpack' ) ) ),
3983
				$actions
3984
			);
3985
		}
3986
3987
		return array_merge( $jetpack_home, $actions );
3988
	}
3989
3990
	/*
3991
	 * Registration flow:
3992
	 * 1 - ::admin_page_load() action=register
3993
	 * 2 - ::try_registration()
3994
	 * 3 - ::register()
3995
	 *     - Creates jetpack_register option containing two secrets and a timestamp
3996
	 *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3997
	 *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3998
	 *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
3999
	 *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
4000
	 *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
4001
	 *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
4002
	 *       jetpack_id, jetpack_secret, jetpack_public
4003
	 *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
4004
	 * 4 - redirect to https://wordpress.com/start/jetpack-connect
4005
	 * 5 - user logs in with WP.com account
4006
	 * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
4007
	 *		- Manager::authorize()
4008
	 *		- Manager::get_token()
4009
	 *		- GET https://jetpack.wordpress.com/jetpack.token/1/ with
4010
	 *        client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
4011
	 *			- which responds with access_token, token_type, scope
4012
	 *		- Manager::authorize() stores jetpack_options: user_token => access_token.$user_id
4013
	 *		- Jetpack::activate_default_modules()
4014
	 *     		- Deactivates deprecated plugins
4015
	 *     		- Activates all default modules
4016
	 *		- Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
4017
	 * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
4018
	 * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
4019
	 *     Done!
4020
	 */
4021
4022
	/**
4023
	 * Handles the page load events for the Jetpack admin page
4024
	 */
4025
	function admin_page_load() {
4026
		$error = false;
4027
4028
		// Make sure we have the right body class to hook stylings for subpages off of.
4029
		add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ), 20 );
4030
4031
		if ( ! empty( $_GET['jetpack_restate'] ) ) {
4032
			// Should only be used in intermediate redirects to preserve state across redirects
4033
			self::restate();
4034
		}
4035
4036
		if ( isset( $_GET['connect_url_redirect'] ) ) {
4037
			// @todo: Add validation against a known whitelist
4038
			$from = ! empty( $_GET['from'] ) ? $_GET['from'] : 'iframe';
4039
			// User clicked in the iframe to link their accounts
4040
			if ( ! self::is_user_connected() ) {
4041
				$redirect = ! empty( $_GET['redirect_after_auth'] ) ? $_GET['redirect_after_auth'] : false;
4042
4043
				add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_environments' ) );
4044
				$connect_url = $this->build_connect_url( true, $redirect, $from );
4045
				remove_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_environments' ) );
4046
4047
				if ( isset( $_GET['notes_iframe'] ) ) {
4048
					$connect_url .= '&notes_iframe';
4049
				}
4050
				wp_redirect( $connect_url );
4051
				exit;
4052
			} else {
4053
				if ( ! isset( $_GET['calypso_env'] ) ) {
4054
					self::state( 'message', 'already_authorized' );
4055
					wp_safe_redirect( self::admin_url() );
4056
					exit;
4057
				} else {
4058
					$connect_url  = $this->build_connect_url( true, false, $from );
4059
					$connect_url .= '&already_authorized=true';
4060
					wp_redirect( $connect_url );
4061
					exit;
4062
				}
4063
			}
4064
		}
4065
4066
		if ( isset( $_GET['action'] ) ) {
4067
			switch ( $_GET['action'] ) {
4068
				case 'authorize':
4069
					if ( self::is_active() && self::is_user_connected() ) {
4070
						self::state( 'message', 'already_authorized' );
4071
						wp_safe_redirect( self::admin_url() );
4072
						exit;
4073
					}
4074
					self::log( 'authorize' );
4075
					$client_server = new Jetpack_Client_Server();
4076
					$client_server->client_authorize();
4077
					exit;
4078
				case 'register':
4079
					if ( ! current_user_can( 'jetpack_connect' ) ) {
4080
						$error = 'cheatin';
4081
						break;
4082
					}
4083
					check_admin_referer( 'jetpack-register' );
4084
					self::log( 'register' );
4085
					self::maybe_set_version_option();
4086
					$registered = self::try_registration();
4087 View Code Duplication
					if ( is_wp_error( $registered ) ) {
4088
						$error = $registered->get_error_code();
0 ignored issues
show
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
4089
						self::state( 'error', $error );
4090
						self::state( 'error', $registered->get_error_message() );
4091
4092
						/**
4093
						 * Jetpack registration Error.
4094
						 *
4095
						 * @since 7.5.0
4096
						 *
4097
						 * @param string|int $error The error code.
4098
						 * @param \WP_Error $registered The error object.
4099
						 */
4100
						do_action( 'jetpack_connection_register_fail', $error, $registered );
4101
						break;
4102
					}
4103
4104
					$from     = isset( $_GET['from'] ) ? $_GET['from'] : false;
4105
					$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : false;
4106
4107
					/**
4108
					 * Jetpack registration Success.
4109
					 *
4110
					 * @since 7.5.0
4111
					 *
4112
					 * @param string $from 'from' GET parameter;
4113
					 */
4114
					do_action( 'jetpack_connection_register_success', $from );
4115
4116
					$url = $this->build_connect_url( true, $redirect, $from );
4117
4118
					if ( ! empty( $_GET['onboarding'] ) ) {
4119
						$url = add_query_arg( 'onboarding', $_GET['onboarding'], $url );
4120
					}
4121
4122
					if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
4123
						$url = add_query_arg( 'auth_approved', 'true', $url );
4124
					}
4125
4126
					wp_redirect( $url );
4127
					exit;
4128
				case 'activate':
4129
					if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
4130
						$error = 'cheatin';
4131
						break;
4132
					}
4133
4134
					$module = stripslashes( $_GET['module'] );
4135
					check_admin_referer( "jetpack_activate-$module" );
4136
					self::log( 'activate', $module );
4137
					if ( ! self::activate_module( $module ) ) {
4138
						self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
4139
					}
4140
					// The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
4141
					wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4142
					exit;
4143
				case 'activate_default_modules':
4144
					check_admin_referer( 'activate_default_modules' );
4145
					self::log( 'activate_default_modules' );
4146
					self::restate();
4147
					$min_version   = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
4148
					$max_version   = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
4149
					$other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
4150
					self::activate_default_modules( $min_version, $max_version, $other_modules );
4151
					wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4152
					exit;
4153
				case 'disconnect':
4154
					if ( ! current_user_can( 'jetpack_disconnect' ) ) {
4155
						$error = 'cheatin';
4156
						break;
4157
					}
4158
4159
					check_admin_referer( 'jetpack-disconnect' );
4160
					self::log( 'disconnect' );
4161
					self::disconnect();
4162
					wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
4163
					exit;
4164
				case 'reconnect':
4165
					if ( ! current_user_can( 'jetpack_reconnect' ) ) {
4166
						$error = 'cheatin';
4167
						break;
4168
					}
4169
4170
					check_admin_referer( 'jetpack-reconnect' );
4171
					self::log( 'reconnect' );
4172
					$this->disconnect();
4173
					wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
4174
					exit;
4175 View Code Duplication
				case 'deactivate':
4176
					if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
4177
						$error = 'cheatin';
4178
						break;
4179
					}
4180
4181
					$modules = stripslashes( $_GET['module'] );
4182
					check_admin_referer( "jetpack_deactivate-$modules" );
4183
					foreach ( explode( ',', $modules ) as $module ) {
4184
						self::log( 'deactivate', $module );
4185
						self::deactivate_module( $module );
4186
						self::state( 'message', 'module_deactivated' );
4187
					}
4188
					self::state( 'module', $modules );
4189
					wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4190
					exit;
4191
				case 'unlink':
4192
					$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
4193
					check_admin_referer( 'jetpack-unlink' );
4194
					self::log( 'unlink' );
4195
					Connection_Manager::disconnect_user();
4196
					self::state( 'message', 'unlinked' );
4197
					if ( 'sub-unlink' == $redirect ) {
4198
						wp_safe_redirect( admin_url() );
4199
					} else {
4200
						wp_safe_redirect( self::admin_url( array( 'page' => $redirect ) ) );
4201
					}
4202
					exit;
4203
				case 'onboard':
4204
					if ( ! current_user_can( 'manage_options' ) ) {
4205
						wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4206
					} else {
4207
						self::create_onboarding_token();
4208
						$url = $this->build_connect_url( true );
4209
4210
						if ( false !== ( $token = Jetpack_Options::get_option( 'onboarding' ) ) ) {
4211
							$url = add_query_arg( 'onboarding', $token, $url );
4212
						}
4213
4214
						$calypso_env = $this->get_calypso_env();
4215
						if ( ! empty( $calypso_env ) ) {
4216
							$url = add_query_arg( 'calypso_env', $calypso_env, $url );
4217
						}
4218
4219
						wp_redirect( $url );
4220
						exit;
4221
					}
4222
					exit;
4223
				default:
4224
					/**
4225
					 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
4226
					 *
4227
					 * @since 2.6.0
4228
					 *
4229
					 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
4230
					 */
4231
					do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
4232
			}
4233
		}
4234
4235
		if ( ! $error = $error ? $error : self::state( 'error' ) ) {
4236
			self::activate_new_modules( true );
4237
		}
4238
4239
		$message_code = self::state( 'message' );
4240
		if ( self::state( 'optin-manage' ) ) {
4241
			$activated_manage = $message_code;
4242
			$message_code     = 'jetpack-manage';
4243
		}
4244
4245
		switch ( $message_code ) {
4246
			case 'jetpack-manage':
4247
				$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' ), 'https://wordpress.com/sites' ) . '</strong>';
4248
				if ( $activated_manage ) {
4249
					$this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack' ) . '</strong>';
4250
				}
4251
				break;
4252
4253
		}
4254
4255
		$deactivated_plugins = self::state( 'deactivated_plugins' );
4256
4257
		if ( ! empty( $deactivated_plugins ) ) {
4258
			$deactivated_plugins = explode( ',', $deactivated_plugins );
4259
			$deactivated_titles  = array();
4260
			foreach ( $deactivated_plugins as $deactivated_plugin ) {
4261
				if ( ! isset( $this->plugins_to_deactivate[ $deactivated_plugin ] ) ) {
4262
					continue;
4263
				}
4264
4265
				$deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[ $deactivated_plugin ][1] ) . '</strong>';
4266
			}
4267
4268
			if ( $deactivated_titles ) {
4269
				if ( $this->message ) {
4270
					$this->message .= "<br /><br />\n";
4271
				}
4272
4273
				$this->message .= wp_sprintf(
4274
					_n(
4275
						'Jetpack contains the most recent version of the old %l plugin.',
4276
						'Jetpack contains the most recent versions of the old %l plugins.',
4277
						count( $deactivated_titles ),
4278
						'jetpack'
4279
					),
4280
					$deactivated_titles
4281
				);
4282
4283
				$this->message .= "<br />\n";
4284
4285
				$this->message .= _n(
4286
					'The old version has been deactivated and can be removed from your site.',
4287
					'The old versions have been deactivated and can be removed from your site.',
4288
					count( $deactivated_titles ),
4289
					'jetpack'
4290
				);
4291
			}
4292
		}
4293
4294
		$this->privacy_checks = self::state( 'privacy_checks' );
4295
4296
		if ( $this->message || $this->error || $this->privacy_checks ) {
4297
			add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
4298
		}
4299
4300
		add_filter( 'jetpack_short_module_description', 'wptexturize' );
4301
	}
4302
4303
	function admin_notices() {
4304
4305
		if ( $this->error ) {
4306
			?>
4307
<div id="message" class="jetpack-message jetpack-err">
4308
	<div class="squeezer">
4309
		<h2>
4310
			<?php
4311
			echo wp_kses(
4312
				$this->error,
4313
				array(
4314
					'a'      => array( 'href' => array() ),
4315
					'small'  => true,
4316
					'code'   => true,
4317
					'strong' => true,
4318
					'br'     => true,
4319
					'b'      => true,
4320
				)
4321
			);
4322
			?>
4323
			</h2>
4324
			<?php	if ( $desc = self::state( 'error_description' ) ) : ?>
4325
		<p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
4326
<?php	endif; ?>
4327
	</div>
4328
</div>
4329
			<?php
4330
		}
4331
4332
		if ( $this->message ) {
4333
			?>
4334
<div id="message" class="jetpack-message">
4335
	<div class="squeezer">
4336
		<h2>
4337
			<?php
4338
			echo wp_kses(
4339
				$this->message,
4340
				array(
4341
					'strong' => array(),
4342
					'a'      => array( 'href' => true ),
4343
					'br'     => true,
4344
				)
4345
			);
4346
			?>
4347
			</h2>
4348
	</div>
4349
</div>
4350
			<?php
4351
		}
4352
4353
		if ( $this->privacy_checks ) :
4354
			$module_names = $module_slugs = array();
4355
4356
			$privacy_checks = explode( ',', $this->privacy_checks );
4357
			$privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
4358
			foreach ( $privacy_checks as $module_slug ) {
4359
				$module = self::get_module( $module_slug );
4360
				if ( ! $module ) {
4361
					continue;
4362
				}
4363
4364
				$module_slugs[] = $module_slug;
4365
				$module_names[] = "<strong>{$module['name']}</strong>";
4366
			}
4367
4368
			$module_slugs = join( ',', $module_slugs );
4369
			?>
4370
<div id="message" class="jetpack-message jetpack-err">
4371
	<div class="squeezer">
4372
		<h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
4373
		<p>
4374
			<?php
4375
			echo wp_kses(
4376
				wptexturize(
4377
					wp_sprintf(
4378
						_nx(
4379
							"Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
4380
							"Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
4381
							count( $privacy_checks ),
4382
							'%l = list of Jetpack module/feature names',
4383
							'jetpack'
4384
						),
4385
						$module_names
4386
					)
4387
				),
4388
				array( 'strong' => true )
4389
			);
4390
4391
			echo "\n<br />\n";
4392
4393
			echo wp_kses(
4394
				sprintf(
4395
					_nx(
4396
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
4397
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
4398
						count( $privacy_checks ),
4399
						'%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
4400
						'jetpack'
4401
					),
4402
					wp_nonce_url(
4403
						self::admin_url(
4404
							array(
4405
								'page'   => 'jetpack',
4406
								'action' => 'deactivate',
4407
								'module' => urlencode( $module_slugs ),
4408
							)
4409
						),
4410
						"jetpack_deactivate-$module_slugs"
4411
					),
4412
					esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
4413
				),
4414
				array(
4415
					'a' => array(
4416
						'href'  => true,
4417
						'title' => true,
4418
					),
4419
				)
4420
			);
4421
			?>
4422
		</p>
4423
	</div>
4424
</div>
4425
			<?php
4426
endif;
4427
	}
4428
4429
	/**
4430
	 * We can't always respond to a signed XML-RPC request with a
4431
	 * helpful error message. In some circumstances, doing so could
4432
	 * leak information.
4433
	 *
4434
	 * Instead, track that the error occurred via a Jetpack_Option,
4435
	 * and send that data back in the heartbeat.
4436
	 * All this does is increment a number, but it's enough to find
4437
	 * trends.
4438
	 *
4439
	 * @param WP_Error $xmlrpc_error The error produced during
4440
	 *                               signature validation.
4441
	 */
4442
	function track_xmlrpc_error( $xmlrpc_error ) {
4443
		$code = is_wp_error( $xmlrpc_error )
4444
			? $xmlrpc_error->get_error_code()
4445
			: 'should-not-happen';
4446
4447
		$xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4448
		if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4449
			// No need to update the option if we already have
4450
			// this code stored.
4451
			return;
4452
		}
4453
		$xmlrpc_errors[ $code ] = true;
4454
4455
		Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4456
	}
4457
4458
	/**
4459
	 * Record a stat for later output.  This will only currently output in the admin_footer.
4460
	 */
4461
	function stat( $group, $detail ) {
4462
		if ( ! isset( $this->stats[ $group ] ) ) {
4463
			$this->stats[ $group ] = array();
4464
		}
4465
		$this->stats[ $group ][] = $detail;
4466
	}
4467
4468
	/**
4469
	 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
4470
	 */
4471
	function do_stats( $method = '' ) {
4472
		if ( is_array( $this->stats ) && count( $this->stats ) ) {
4473
			foreach ( $this->stats as $group => $stats ) {
4474
				if ( is_array( $stats ) && count( $stats ) ) {
4475
					$args = array( "x_jetpack-{$group}" => implode( ',', $stats ) );
4476
					if ( 'server_side' === $method ) {
4477
						self::do_server_side_stat( $args );
4478
					} else {
4479
						echo '<img src="' . esc_url( self::build_stats_url( $args ) ) . '" width="1" height="1" style="display:none;" />';
4480
					}
4481
				}
4482
				unset( $this->stats[ $group ] );
4483
			}
4484
		}
4485
	}
4486
4487
	/**
4488
	 * Runs stats code for a one-off, server-side.
4489
	 *
4490
	 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
4491
	 *
4492
	 * @return bool If it worked.
4493
	 */
4494
	static function do_server_side_stat( $args ) {
4495
		$response = wp_remote_get( esc_url_raw( self::build_stats_url( $args ) ) );
4496
		if ( is_wp_error( $response ) ) {
4497
			return false;
4498
		}
4499
4500
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
4501
			return false;
4502
		}
4503
4504
		return true;
4505
	}
4506
4507
	/**
4508
	 * Builds the stats url.
4509
	 *
4510
	 * @param $args array|string The arguments to append to the URL.
4511
	 *
4512
	 * @return string The URL to be pinged.
4513
	 */
4514
	static function build_stats_url( $args ) {
4515
		$defaults = array(
4516
			'v'    => 'wpcom2',
4517
			'rand' => md5( mt_rand( 0, 999 ) . time() ),
4518
		);
4519
		$args     = wp_parse_args( $args, $defaults );
4520
		/**
4521
		 * Filter the URL used as the Stats tracking pixel.
4522
		 *
4523
		 * @since 2.3.2
4524
		 *
4525
		 * @param string $url Base URL used as the Stats tracking pixel.
4526
		 */
4527
		$base_url = apply_filters(
4528
			'jetpack_stats_base_url',
4529
			'https://pixel.wp.com/g.gif'
4530
		);
4531
		$url      = add_query_arg( $args, $base_url );
4532
		return $url;
4533
	}
4534
4535
	/**
4536
	 * Get the role of the current user.
4537
	 *
4538
	 * @deprecated 7.6 Use Automattic\Jetpack\Roles::translate_current_user_to_role() instead.
4539
	 *
4540
	 * @access public
4541
	 * @static
4542
	 *
4543
	 * @return string|boolean Current user's role, false if not enough capabilities for any of the roles.
4544
	 */
4545
	public static function translate_current_user_to_role() {
4546
		_deprecated_function( __METHOD__, 'jetpack-7.6.0' );
4547
4548
		$roles = new Roles();
4549
		return $roles->translate_current_user_to_role();
4550
	}
4551
4552
	/**
4553
	 * Get the role of a particular user.
4554
	 *
4555
	 * @deprecated 7.6 Use Automattic\Jetpack\Roles::translate_user_to_role() instead.
4556
	 *
4557
	 * @access public
4558
	 * @static
4559
	 *
4560
	 * @param \WP_User $user User object.
4561
	 * @return string|boolean User's role, false if not enough capabilities for any of the roles.
4562
	 */
4563
	public static function translate_user_to_role( $user ) {
4564
		_deprecated_function( __METHOD__, 'jetpack-7.6.0' );
4565
4566
		$roles = new Roles();
4567
		return $roles->translate_user_to_role( $user );
4568
	}
4569
4570
	/**
4571
	 * Get the minimum capability for a role.
4572
	 *
4573
	 * @deprecated 7.6 Use Automattic\Jetpack\Roles::translate_role_to_cap() instead.
4574
	 *
4575
	 * @access public
4576
	 * @static
4577
	 *
4578
	 * @param string $role Role name.
4579
	 * @return string|boolean Capability, false if role isn't mapped to any capabilities.
4580
	 */
4581
	public static function translate_role_to_cap( $role ) {
4582
		_deprecated_function( __METHOD__, 'jetpack-7.6.0' );
4583
4584
		$roles = new Roles();
4585
		return $roles->translate_role_to_cap( $role );
4586
	}
4587
4588
	/**
4589
	 * Sign a user role with the master access token.
4590
	 * If not specified, will default to the current user.
4591
	 *
4592
	 * @deprecated since 7.7
4593
	 * @see Automattic\Jetpack\Connection\Manager::sign_role()
4594
	 *
4595
	 * @access public
4596
	 * @static
4597
	 *
4598
	 * @param string $role    User role.
4599
	 * @param int    $user_id ID of the user.
4600
	 * @return string Signed user role.
4601
	 */
4602
	public static function sign_role( $role, $user_id = null ) {
4603
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::sign_role' );
4604
		return self::connection()->sign_role( $role, $user_id );
4605
	}
4606
4607
	/**
4608
	 * Builds a URL to the Jetpack connection auth page
4609
	 *
4610
	 * @since 3.9.5
4611
	 *
4612
	 * @param bool        $raw If true, URL will not be escaped.
4613
	 * @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
4614
	 *                              If string, will be a custom redirect.
4615
	 * @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
4616
	 * @param bool        $register If true, will generate a register URL regardless of the existing token, since 4.9.0
4617
	 *
4618
	 * @return string Connect URL
4619
	 */
4620
	function build_connect_url( $raw = false, $redirect = false, $from = false, $register = false ) {
4621
		$site_id    = Jetpack_Options::get_option( 'id' );
4622
		$blog_token = Jetpack_Data::get_access_token();
4623
4624
		if ( $register || ! $blog_token || ! $site_id ) {
4625
			$url = self::nonce_url_no_esc( self::admin_url( 'action=register' ), 'jetpack-register' );
4626
4627
			if ( ! empty( $redirect ) ) {
4628
				$url = add_query_arg(
4629
					'redirect',
4630
					urlencode( wp_validate_redirect( esc_url_raw( $redirect ) ) ),
4631
					$url
4632
				);
4633
			}
4634
4635
			if ( is_network_admin() ) {
4636
				$url = add_query_arg( 'is_multisite', network_admin_url( 'admin.php?page=jetpack-settings' ), $url );
4637
			}
4638
4639
			$calypso_env = self::get_calypso_env();
4640
4641
			if ( ! empty( $calypso_env ) ) {
4642
				$url = add_query_arg( 'calypso_env', $calypso_env, $url );
4643
			}
4644
		} else {
4645
4646
			// Let's check the existing blog token to see if we need to re-register. We only check once per minute
4647
			// because otherwise this logic can get us in to a loop.
4648
			$last_connect_url_check = intval( Jetpack_Options::get_raw_option( 'jetpack_last_connect_url_check' ) );
4649
			if ( ! $last_connect_url_check || ( time() - $last_connect_url_check ) > MINUTE_IN_SECONDS ) {
4650
				Jetpack_Options::update_raw_option( 'jetpack_last_connect_url_check', time() );
4651
4652
				$response = Client::wpcom_json_api_request_as_blog(
4653
					sprintf( '/sites/%d', $site_id ) . '?force=wpcom',
4654
					'1.1'
4655
				);
4656
4657
				if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
4658
4659
					// Generating a register URL instead to refresh the existing token
4660
					return $this->build_connect_url( $raw, $redirect, $from, true );
4661
				}
4662
			}
4663
4664
			$url = $this->build_authorize_url( $redirect );
4665
		}
4666
4667
		if ( $from ) {
4668
			$url = add_query_arg( 'from', $from, $url );
4669
		}
4670
4671
		$url = $raw ? esc_url_raw( $url ) : esc_url( $url );
4672
		/**
4673
		 * Filter the URL used when connecting a user to a WordPress.com account.
4674
		 *
4675
		 * @since 8.1.0
4676
		 *
4677
		 * @param string $url Connection URL.
4678
		 * @param bool   $raw If true, URL will not be escaped.
4679
		 */
4680
		return apply_filters( 'jetpack_build_connection_url', $url, $raw );
4681
	}
4682
4683
	public static function build_authorize_url( $redirect = false, $iframe = false ) {
4684
4685
		add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4686
		add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4687
		add_filter( 'jetpack_connect_processing_url', array( __CLASS__, 'filter_connect_processing_url' ) );
4688
4689
		if ( $iframe ) {
4690
			add_filter( 'jetpack_api_url', array( __CLASS__, 'filter_connect_api_iframe_url' ), 10, 2 );
4691
		}
4692
4693
		$c8n = self::connection();
4694
		$url = $c8n->get_authorization_url( wp_get_current_user(), $redirect );
4695
4696
		remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4697
		remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4698
		remove_filter( 'jetpack_connect_processing_url', array( __CLASS__, 'filter_connect_processing_url' ) );
4699
4700
		if ( $iframe ) {
4701
			remove_filter( 'jetpack_api_url', array( __CLASS__, 'filter_connect_api_iframe_url' ) );
4702
		}
4703
4704
		return $url;
4705
	}
4706
4707
	/**
4708
	 * Filters the connection URL parameter array.
4709
	 *
4710
	 * @param Array $args default URL parameters used by the package.
4711
	 * @return Array the modified URL arguments array.
4712
	 */
4713
	public static function filter_connect_request_body( $args ) {
4714
		if (
4715
			Constants::is_defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4716
			&& include_once Constants::get_constant( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4717
		) {
4718
			$gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
4719
			$args['locale'] = isset( $gp_locale ) && isset( $gp_locale->slug )
4720
				? $gp_locale->slug
4721
				: '';
4722
		}
4723
4724
		$tracking        = new Tracking();
4725
		$tracks_identity = $tracking->tracks_get_identity( $args['state'] );
4726
4727
		$args = array_merge(
4728
			$args,
4729
			array(
4730
				'_ui' => $tracks_identity['_ui'],
4731
				'_ut' => $tracks_identity['_ut'],
4732
			)
4733
		);
4734
4735
		$calypso_env = self::get_calypso_env();
4736
4737
		if ( ! empty( $calypso_env ) ) {
4738
			$args['calypso_env'] = $calypso_env;
4739
		}
4740
4741
		return $args;
4742
	}
4743
4744
	/**
4745
	 * Filters the URL that will process the connection data. It can be different from the URL
4746
	 * that we send the user to after everything is done.
4747
	 *
4748
	 * @param String $processing_url the default redirect URL used by the package.
4749
	 * @return String the modified URL.
4750
	 */
4751
	public static function filter_connect_processing_url( $processing_url ) {
4752
		$processing_url = admin_url( 'admin.php?page=jetpack' ); // Making PHPCS happy.
4753
		return $processing_url;
4754
	}
4755
4756
	/**
4757
	 * Filters the redirection URL that is used for connect requests. The redirect
4758
	 * URL should return the user back to the Jetpack console.
4759
	 *
4760
	 * @param String $redirect the default redirect URL used by the package.
4761
	 * @return String the modified URL.
4762
	 */
4763
	public static function filter_connect_redirect_url( $redirect ) {
4764
		$jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
4765
		$redirect           = $redirect
4766
			? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
4767
			: $jetpack_admin_page;
4768
4769
		if ( isset( $_REQUEST['is_multisite'] ) ) {
4770
			$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4771
		}
4772
4773
		return $redirect;
4774
	}
4775
4776
	/**
4777
	 * Filters the API URL that is used for connect requests. The method
4778
	 * intercepts only the authorize URL and replaces it with another if needed.
4779
	 *
4780
	 * @param String $api_url the default redirect API URL used by the package.
4781
	 * @param String $relative_url the path of the URL that's being used.
4782
	 * @return String the modified URL.
4783
	 */
4784
	public static function filter_connect_api_iframe_url( $api_url, $relative_url ) {
4785
4786
		// Short-circuit on anything that is not related to connect requests.
4787
		if ( 'authorize' !== $relative_url ) {
4788
			return $api_url;
4789
		}
4790
4791
		$c8n = self::connection();
4792
4793
		return $c8n->api_url( 'authorize_iframe' );
4794
	}
4795
4796
	/**
4797
	 * This action fires at the beginning of the Manager::authorize method.
4798
	 */
4799
	public static function authorize_starting() {
4800
		$jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' );
4801
		// Checking if site has been active/connected previously before recording unique connection.
4802
		if ( ! $jetpack_unique_connection ) {
4803
			// jetpack_unique_connection option has never been set.
4804
			$jetpack_unique_connection = array(
4805
				'connected'    => 0,
4806
				'disconnected' => 0,
4807
				'version'      => '3.6.1',
4808
			);
4809
4810
			update_option( 'jetpack_unique_connection', $jetpack_unique_connection );
4811
4812
			// Track unique connection.
4813
			$jetpack = self::init();
4814
4815
			$jetpack->stat( 'connections', 'unique-connection' );
4816
			$jetpack->do_stats( 'server_side' );
4817
		}
4818
4819
		// Increment number of times connected.
4820
		$jetpack_unique_connection['connected'] += 1;
4821
		Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
4822
	}
4823
4824
	/**
4825
	 * This action fires at the end of the Manager::authorize method when a secondary user is
4826
	 * linked.
4827
	 */
4828
	public static function authorize_ending_linked() {
4829
		// Don't activate anything since we are just connecting a user.
4830
		self::state( 'message', 'linked' );
4831
	}
4832
4833
	/**
4834
	 * This action fires at the end of the Manager::authorize method when the master user is
4835
	 * authorized.
4836
	 *
4837
	 * @param array $data The request data.
4838
	 */
4839
	public static function authorize_ending_authorized( $data ) {
4840
		// If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4841
		self::invalidate_onboarding_token();
4842
4843
		// If redirect_uri is SSO, ensure SSO module is enabled.
4844
		parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4845
4846
		/** This filter is documented in class.jetpack-cli.php */
4847
		$jetpack_start_enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
4848
4849
		$activate_sso = (
4850
			isset( $redirect_options['action'] ) &&
4851
			'jetpack-sso' === $redirect_options['action'] &&
4852
			$jetpack_start_enable_sso
4853
		);
4854
4855
		$do_redirect_on_error = ( 'client' === $data['auth_type'] );
4856
4857
		self::handle_post_authorization_actions( $activate_sso, $do_redirect_on_error );
4858
	}
4859
4860
	/**
4861
	 * Get our assumed site creation date.
4862
	 * Calculated based on the earlier date of either:
4863
	 * - Earliest admin user registration date.
4864
	 * - Earliest date of post of any post type.
4865
	 *
4866
	 * @since 7.2.0
4867
	 * @deprecated since 7.8.0
4868
	 *
4869
	 * @return string Assumed site creation date and time.
4870
	 */
4871
	public static function get_assumed_site_creation_date() {
4872
		_deprecated_function( __METHOD__, 'jetpack-7.8', 'Automattic\\Jetpack\\Connection\\Manager' );
4873
		return self::connection()->get_assumed_site_creation_date();
4874
	}
4875
4876 View Code Duplication
	public static function apply_activation_source_to_args( &$args ) {
4877
		list( $activation_source_name, $activation_source_keyword ) = get_option( 'jetpack_activation_source' );
4878
4879
		if ( $activation_source_name ) {
4880
			$args['_as'] = urlencode( $activation_source_name );
4881
		}
4882
4883
		if ( $activation_source_keyword ) {
4884
			$args['_ak'] = urlencode( $activation_source_keyword );
4885
		}
4886
	}
4887
4888
	function build_reconnect_url( $raw = false ) {
4889
		$url = wp_nonce_url( self::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4890
		return $raw ? $url : esc_url( $url );
4891
	}
4892
4893
	public static function admin_url( $args = null ) {
4894
		$args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
4895
		$url  = add_query_arg( $args, admin_url( 'admin.php' ) );
4896
		return $url;
4897
	}
4898
4899
	public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4900
		$actionurl = str_replace( '&amp;', '&', $actionurl );
4901
		return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4902
	}
4903
4904
	function dismiss_jetpack_notice() {
4905
4906
		if ( ! isset( $_GET['jetpack-notice'] ) ) {
4907
			return;
4908
		}
4909
4910
		switch ( $_GET['jetpack-notice'] ) {
4911
			case 'dismiss':
4912
				if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4913
4914
					require_once ABSPATH . 'wp-admin/includes/plugin.php';
4915
					deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4916
					wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4917
				}
4918
				break;
4919
		}
4920
	}
4921
4922
	public static function sort_modules( $a, $b ) {
4923
		if ( $a['sort'] == $b['sort'] ) {
4924
			return 0;
4925
		}
4926
4927
		return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
4928
	}
4929
4930
	function ajax_recheck_ssl() {
4931
		check_ajax_referer( 'recheck-ssl', 'ajax-nonce' );
4932
		$result = self::permit_ssl( true );
4933
		wp_send_json(
4934
			array(
4935
				'enabled' => $result,
4936
				'message' => get_transient( 'jetpack_https_test_message' ),
4937
			)
4938
		);
4939
	}
4940
4941
	/* Client API */
4942
4943
	/**
4944
	 * Returns the requested Jetpack API URL
4945
	 *
4946
	 * @deprecated since 7.7
4947
	 * @return string
4948
	 */
4949
	public static function api_url( $relative_url ) {
4950
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::api_url' );
4951
		$connection = self::connection();
4952
		return $connection->api_url( $relative_url );
4953
	}
4954
4955
	/**
4956
	 * @deprecated 8.0 Use Automattic\Jetpack\Connection\Utils::fix_url_for_bad_hosts() instead.
4957
	 *
4958
	 * Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
4959
	 */
4960
	public static function fix_url_for_bad_hosts( $url ) {
4961
		_deprecated_function( __METHOD__, 'jetpack-8.0', 'Automattic\\Jetpack\\Connection\\Utils::fix_url_for_bad_hosts' );
4962
		return Connection_Utils::fix_url_for_bad_hosts( $url );
4963
	}
4964
4965
	public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4966
		// Default to a blog token.
4967
		$token_type = 'blog';
4968
4969
		// Let's see if this is onboarding. In such case, use user token type and the provided user id.
4970
		if ( isset( $request_data ) || ! empty( $_GET['onboarding'] ) ) {
4971
			if ( ! empty( $_GET['onboarding'] ) ) {
4972
				$jpo = $_GET;
4973
			} else {
4974
				$jpo = json_decode( $request_data, true );
4975
			}
4976
4977
			$jpo_token = ! empty( $jpo['onboarding']['token'] ) ? $jpo['onboarding']['token'] : null;
4978
			$jpo_user  = ! empty( $jpo['onboarding']['jpUser'] ) ? $jpo['onboarding']['jpUser'] : null;
4979
4980
			if (
4981
				isset( $jpo_user )
4982
				&& isset( $jpo_token )
4983
				&& is_email( $jpo_user )
4984
				&& ctype_alnum( $jpo_token )
4985
				&& isset( $_GET['rest_route'] )
4986
				&& self::validate_onboarding_token_action(
4987
					$jpo_token,
4988
					$_GET['rest_route']
4989
				)
4990
			) {
4991
				$jp_user = get_user_by( 'email', $jpo_user );
4992
				if ( is_a( $jp_user, 'WP_User' ) ) {
4993
					wp_set_current_user( $jp_user->ID );
4994
					$user_can = is_multisite()
4995
						? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
4996
						: current_user_can( 'manage_options' );
4997
					if ( $user_can ) {
4998
						$token_type              = 'user';
4999
						$token->external_user_id = $jp_user->ID;
5000
					}
5001
				}
5002
			}
5003
5004
			$token_data['type']    = $token_type;
5005
			$token_data['user_id'] = $token->external_user_id;
5006
		}
5007
5008
		return $token_data;
5009
	}
5010
5011
	/**
5012
	 * Create a random secret for validating onboarding payload
5013
	 *
5014
	 * @return string Secret token
5015
	 */
5016
	public static function create_onboarding_token() {
5017
		if ( false === ( $token = Jetpack_Options::get_option( 'onboarding' ) ) ) {
5018
			$token = wp_generate_password( 32, false );
5019
			Jetpack_Options::update_option( 'onboarding', $token );
5020
		}
5021
5022
		return $token;
5023
	}
5024
5025
	/**
5026
	 * Remove the onboarding token
5027
	 *
5028
	 * @return bool True on success, false on failure
5029
	 */
5030
	public static function invalidate_onboarding_token() {
5031
		return Jetpack_Options::delete_option( 'onboarding' );
5032
	}
5033
5034
	/**
5035
	 * Validate an onboarding token for a specific action
5036
	 *
5037
	 * @return boolean True if token/action pair is accepted, false if not
5038
	 */
5039
	public static function validate_onboarding_token_action( $token, $action ) {
5040
		// Compare tokens, bail if tokens do not match
5041
		if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
5042
			return false;
5043
		}
5044
5045
		// List of valid actions we can take
5046
		$valid_actions = array(
5047
			'/jetpack/v4/settings',
5048
		);
5049
5050
		// Whitelist the action
5051
		if ( ! in_array( $action, $valid_actions ) ) {
5052
			return false;
5053
		}
5054
5055
		return true;
5056
	}
5057
5058
	/**
5059
	 * Checks to see if the URL is using SSL to connect with Jetpack
5060
	 *
5061
	 * @since 2.3.3
5062
	 * @return boolean
5063
	 */
5064
	public static function permit_ssl( $force_recheck = false ) {
5065
		// Do some fancy tests to see if ssl is being supported
5066
		if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
5067
			$message = '';
5068
			if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
5069
				$ssl = 0;
5070
			} else {
5071
				switch ( JETPACK_CLIENT__HTTPS ) {
5072
					case 'NEVER':
5073
						$ssl     = 0;
5074
						$message = __( 'JETPACK_CLIENT__HTTPS is set to NEVER', 'jetpack' );
5075
						break;
5076
					case 'ALWAYS':
5077
					case 'AUTO':
5078
					default:
5079
						$ssl = 1;
5080
						break;
5081
				}
5082
5083
				// If it's not 'NEVER', test to see
5084
				if ( $ssl ) {
5085
					if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
5086
						$ssl     = 0;
5087
						$message = __( 'WordPress reports no SSL support', 'jetpack' );
5088
					} else {
5089
						$response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
5090
						if ( is_wp_error( $response ) ) {
5091
							$ssl     = 0;
5092
							$message = __( 'WordPress reports no SSL support', 'jetpack' );
5093
						} elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
5094
							$ssl     = 0;
5095
							$message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
5096
						}
5097
					}
5098
				}
5099
			}
5100
			set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
5101
			set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5102
		}
5103
5104
		return (bool) $ssl;
5105
	}
5106
5107
	/*
5108
	 * Displays an admin_notice, alerting the user to their JETPACK_CLIENT__HTTPS constant being 'AUTO' but SSL isn't working.
5109
	 */
5110
	public function alert_auto_ssl_fail() {
5111
		if ( ! current_user_can( 'manage_options' ) ) {
5112
			return;
5113
		}
5114
5115
		$ajax_nonce = wp_create_nonce( 'recheck-ssl' );
5116
		?>
5117
5118
		<div id="jetpack-ssl-warning" class="error jp-identity-crisis">
5119
			<div class="jp-banner__content">
5120
				<h2><?php _e( 'Outbound HTTPS not working', 'jetpack' ); ?></h2>
5121
				<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>
5122
				<p>
5123
					<?php _e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
5124
					<a href="#" id="jetpack-recheck-ssl-button"><?php _e( 'Try again', 'jetpack' ); ?></a>
5125
					<span id="jetpack-recheck-ssl-output"><?php echo get_transient( 'jetpack_https_test_message' ); ?></span>
5126
				</p>
5127
				<p>
5128
					<?php
5129
					printf(
5130
						__( 'For more help, try our <a href="%1$s">connection debugger</a> or <a href="%2$s" target="_blank">troubleshooting tips</a>.', 'jetpack' ),
5131
						esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ),
5132
						esc_url( 'https://jetpack.com/support/getting-started-with-jetpack/troubleshooting-tips/' )
5133
					);
5134
					?>
5135
				</p>
5136
			</div>
5137
		</div>
5138
		<style>
5139
			#jetpack-recheck-ssl-output { margin-left: 5px; color: red; }
5140
		</style>
5141
		<script type="text/javascript">
5142
			jQuery( document ).ready( function( $ ) {
5143
				$( '#jetpack-recheck-ssl-button' ).click( function( e ) {
5144
					var $this = $( this );
5145
					$this.html( <?php echo json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5146
					$( '#jetpack-recheck-ssl-output' ).html( '' );
5147
					e.preventDefault();
5148
					var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': '<?php echo $ajax_nonce; ?>' };
5149
					$.post( ajaxurl, data )
5150
					  .done( function( response ) {
5151
						  if ( response.enabled ) {
5152
							  $( '#jetpack-ssl-warning' ).hide();
5153
						  } else {
5154
							  this.html( <?php echo json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5155
							  $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
5156
						  }
5157
					  }.bind( $this ) );
5158
				} );
5159
			} );
5160
		</script>
5161
5162
		<?php
5163
	}
5164
5165
	/**
5166
	 * Returns the Jetpack XML-RPC API
5167
	 *
5168
	 * @deprecated 8.0 Use Connection_Manager instead.
5169
	 * @return string
5170
	 */
5171
	public static function xmlrpc_api_url() {
5172
		_deprecated_function( __METHOD__, 'jetpack-8.0', 'Automattic\\Jetpack\\Connection\\Manager::xmlrpc_api_url()' );
5173
		return self::connection()->xmlrpc_api_url();
5174
	}
5175
5176
	/**
5177
	 * Returns the connection manager object.
5178
	 *
5179
	 * @return Automattic\Jetpack\Connection\Manager
5180
	 */
5181
	public static function connection() {
5182
		return self::init()->connection_manager;
5183
	}
5184
5185
	/**
5186
	 * Creates two secret tokens and the end of life timestamp for them.
5187
	 *
5188
	 * Note these tokens are unique per call, NOT static per site for connecting.
5189
	 *
5190
	 * @since 2.6
5191
	 * @param String  $action  The action name.
5192
	 * @param Integer $user_id The user identifier.
5193
	 * @param Integer $exp     Expiration time in seconds.
5194
	 * @return array
5195
	 */
5196
	public static function generate_secrets( $action, $user_id = false, $exp = 600 ) {
5197
		return self::connection()->generate_secrets( $action, $user_id, $exp );
5198
	}
5199
5200
	public static function get_secrets( $action, $user_id ) {
5201
		$secrets = self::connection()->get_secrets( $action, $user_id );
5202
5203
		if ( Connection_Manager::SECRETS_MISSING === $secrets ) {
5204
			return new WP_Error( 'verify_secrets_missing', 'Verification secrets not found' );
5205
		}
5206
5207
		if ( Connection_Manager::SECRETS_EXPIRED === $secrets ) {
5208
			return new WP_Error( 'verify_secrets_expired', 'Verification took too long' );
5209
		}
5210
5211
		return $secrets;
5212
	}
5213
5214
	/**
5215
	 * @deprecated 7.5 Use Connection_Manager instead.
5216
	 *
5217
	 * @param $action
5218
	 * @param $user_id
5219
	 */
5220
	public static function delete_secrets( $action, $user_id ) {
5221
		return self::connection()->delete_secrets( $action, $user_id );
5222
	}
5223
5224
	/**
5225
	 * Builds the timeout limit for queries talking with the wpcom servers.
5226
	 *
5227
	 * Based on local php max_execution_time in php.ini
5228
	 *
5229
	 * @since 2.6
5230
	 * @return int
5231
	 * @deprecated
5232
	 **/
5233
	public function get_remote_query_timeout_limit() {
5234
		_deprecated_function( __METHOD__, 'jetpack-5.4' );
5235
		return self::get_max_execution_time();
5236
	}
5237
5238
	/**
5239
	 * Builds the timeout limit for queries talking with the wpcom servers.
5240
	 *
5241
	 * Based on local php max_execution_time in php.ini
5242
	 *
5243
	 * @since 5.4
5244
	 * @return int
5245
	 **/
5246
	public static function get_max_execution_time() {
5247
		$timeout = (int) ini_get( 'max_execution_time' );
5248
5249
		// Ensure exec time set in php.ini
5250
		if ( ! $timeout ) {
5251
			$timeout = 30;
5252
		}
5253
		return $timeout;
5254
	}
5255
5256
	/**
5257
	 * Sets a minimum request timeout, and returns the current timeout
5258
	 *
5259
	 * @since 5.4
5260
	 **/
5261 View Code Duplication
	public static function set_min_time_limit( $min_timeout ) {
5262
		$timeout = self::get_max_execution_time();
5263
		if ( $timeout < $min_timeout ) {
5264
			$timeout = $min_timeout;
5265
			set_time_limit( $timeout );
5266
		}
5267
		return $timeout;
5268
	}
5269
5270
	/**
5271
	 * Takes the response from the Jetpack register new site endpoint and
5272
	 * verifies it worked properly.
5273
	 *
5274
	 * @since 2.6
5275
	 * @deprecated since 7.7.0
5276
	 * @see Automattic\Jetpack\Connection\Manager::validate_remote_register_response()
5277
	 **/
5278
	public function validate_remote_register_response() {
5279
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::validate_remote_register_response' );
5280
	}
5281
5282
	/**
5283
	 * @return bool|WP_Error
5284
	 */
5285
	public static function register() {
5286
		$tracking = new Tracking();
5287
		$tracking->record_user_event( 'jpc_register_begin' );
5288
5289
		add_filter( 'jetpack_register_request_body', array( __CLASS__, 'filter_register_request_body' ) );
5290
5291
		$connection   = self::connection();
5292
		$registration = $connection->register();
5293
5294
		remove_filter( 'jetpack_register_request_body', array( __CLASS__, 'filter_register_request_body' ) );
5295
5296
		if ( ! $registration || is_wp_error( $registration ) ) {
5297
			return $registration;
5298
		}
5299
5300
		return true;
5301
	}
5302
5303
	/**
5304
	 * Filters the registration request body to include tracking properties.
5305
	 *
5306
	 * @param Array $properties
5307
	 * @return Array amended properties.
5308
	 */
5309 View Code Duplication
	public static function filter_register_request_body( $properties ) {
5310
		$tracking        = new Tracking();
5311
		$tracks_identity = $tracking->tracks_get_identity( get_current_user_id() );
5312
5313
		return array_merge(
5314
			$properties,
5315
			array(
5316
				'_ui' => $tracks_identity['_ui'],
5317
				'_ut' => $tracks_identity['_ut'],
5318
			)
5319
		);
5320
	}
5321
5322
	/**
5323
	 * Filters the token request body to include tracking properties.
5324
	 *
5325
	 * @param Array $properties
5326
	 * @return Array amended properties.
5327
	 */
5328 View Code Duplication
	public static function filter_token_request_body( $properties ) {
5329
		$tracking        = new Tracking();
5330
		$tracks_identity = $tracking->tracks_get_identity( get_current_user_id() );
5331
5332
		return array_merge(
5333
			$properties,
5334
			array(
5335
				'_ui' => $tracks_identity['_ui'],
5336
				'_ut' => $tracks_identity['_ut'],
5337
			)
5338
		);
5339
	}
5340
5341
	/**
5342
	 * If the db version is showing something other that what we've got now, bump it to current.
5343
	 *
5344
	 * @return bool: True if the option was incorrect and updated, false if nothing happened.
5345
	 */
5346
	public static function maybe_set_version_option() {
5347
		list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
5348
		if ( JETPACK__VERSION != $version ) {
5349
			Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
5350
5351
			if ( version_compare( JETPACK__VERSION, $version, '>' ) ) {
5352
				/** This action is documented in class.jetpack.php */
5353
				do_action( 'updating_jetpack_version', JETPACK__VERSION, $version );
5354
			}
5355
5356
			return true;
5357
		}
5358
		return false;
5359
	}
5360
5361
	/* Client Server API */
5362
5363
	/**
5364
	 * Loads the Jetpack XML-RPC client.
5365
	 * No longer necessary, as the XML-RPC client will be automagically loaded.
5366
	 *
5367
	 * @deprecated since 7.7.0
5368
	 */
5369
	public static function load_xml_rpc_client() {
5370
		_deprecated_function( __METHOD__, 'jetpack-7.7' );
5371
	}
5372
5373
	/**
5374
	 * Resets the saved authentication state in between testing requests.
5375
	 */
5376
	public function reset_saved_auth_state() {
5377
		$this->rest_authentication_status = null;
5378
		$this->connection_manager->reset_saved_auth_state();
5379
	}
5380
5381
	/**
5382
	 * Verifies the signature of the current request.
5383
	 *
5384
	 * @deprecated since 7.7.0
5385
	 * @see Automattic\Jetpack\Connection\Manager::verify_xml_rpc_signature()
5386
	 *
5387
	 * @return false|array
5388
	 */
5389
	public function verify_xml_rpc_signature() {
5390
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::verify_xml_rpc_signature' );
5391
		return self::connection()->verify_xml_rpc_signature();
5392
	}
5393
5394
	/**
5395
	 * Verifies the signature of the current request.
5396
	 *
5397
	 * This function has side effects and should not be used. Instead,
5398
	 * use the memoized version `->verify_xml_rpc_signature()`.
5399
	 *
5400
	 * @deprecated since 7.7.0
5401
	 * @see Automattic\Jetpack\Connection\Manager::internal_verify_xml_rpc_signature()
5402
	 * @internal
5403
	 */
5404
	private function internal_verify_xml_rpc_signature() {
5405
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::internal_verify_xml_rpc_signature' );
5406
	}
5407
5408
	/**
5409
	 * Authenticates XML-RPC and other requests from the Jetpack Server.
5410
	 *
5411
	 * @deprecated since 7.7.0
5412
	 * @see Automattic\Jetpack\Connection\Manager::authenticate_jetpack()
5413
	 *
5414
	 * @param \WP_User|mixed $user     User object if authenticated.
5415
	 * @param string         $username Username.
5416
	 * @param string         $password Password string.
5417
	 * @return \WP_User|mixed Authenticated user or error.
5418
	 */
5419
	public function authenticate_jetpack( $user, $username, $password ) {
5420
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::authenticate_jetpack' );
5421
		return $this->connection_manager->authenticate_jetpack( $user, $username, $password );
5422
	}
5423
5424
	// Authenticates requests from Jetpack server to WP REST API endpoints.
5425
	// Uses the existing XMLRPC request signing implementation.
5426
	function wp_rest_authenticate( $user ) {
5427
		if ( ! empty( $user ) ) {
5428
			// Another authentication method is in effect.
5429
			return $user;
5430
		}
5431
5432
		if ( ! isset( $_GET['_for'] ) || $_GET['_for'] !== 'jetpack' ) {
5433
			// Nothing to do for this authentication method.
5434
			return null;
5435
		}
5436
5437
		if ( ! isset( $_GET['token'] ) && ! isset( $_GET['signature'] ) ) {
5438
			// Nothing to do for this authentication method.
5439
			return null;
5440
		}
5441
5442
		// Ensure that we always have the request body available.  At this
5443
		// point, the WP REST API code to determine the request body has not
5444
		// run yet.  That code may try to read from 'php://input' later, but
5445
		// this can only be done once per request in PHP versions prior to 5.6.
5446
		// So we will go ahead and perform this read now if needed, and save
5447
		// the request body where both the Jetpack signature verification code
5448
		// and the WP REST API code can see it.
5449
		if ( ! isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ) {
5450
			$GLOBALS['HTTP_RAW_POST_DATA'] = file_get_contents( 'php://input' );
5451
		}
5452
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
5453
5454
		// Only support specific request parameters that have been tested and
5455
		// are known to work with signature verification.  A different method
5456
		// can be passed to the WP REST API via the '?_method=' parameter if
5457
		// needed.
5458
		if ( $_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
5459
			$this->rest_authentication_status = new WP_Error(
5460
				'rest_invalid_request',
5461
				__( 'This request method is not supported.', 'jetpack' ),
5462
				array( 'status' => 400 )
5463
			);
5464
			return null;
5465
		}
5466
		if ( $_SERVER['REQUEST_METHOD'] !== 'POST' && ! empty( $this->HTTP_RAW_POST_DATA ) ) {
5467
			$this->rest_authentication_status = new WP_Error(
5468
				'rest_invalid_request',
5469
				__( 'This request method does not support body parameters.', 'jetpack' ),
5470
				array( 'status' => 400 )
5471
			);
5472
			return null;
5473
		}
5474
5475
		$verified = $this->connection_manager->verify_xml_rpc_signature();
5476
5477
		if (
5478
			$verified &&
5479
			isset( $verified['type'] ) &&
5480
			'user' === $verified['type'] &&
5481
			! empty( $verified['user_id'] )
5482
		) {
5483
			// Authentication successful.
5484
			$this->rest_authentication_status = true;
5485
			return $verified['user_id'];
5486
		}
5487
5488
		// Something else went wrong.  Probably a signature error.
5489
		$this->rest_authentication_status = new WP_Error(
5490
			'rest_invalid_signature',
5491
			__( 'The request is not signed correctly.', 'jetpack' ),
5492
			array( 'status' => 400 )
5493
		);
5494
		return null;
5495
	}
5496
5497
	/**
5498
	 * Report authentication status to the WP REST API.
5499
	 *
5500
	 * @param  WP_Error|mixed $result Error from another authentication handler, null if we should handle it, or another value if not
5501
	 * @return WP_Error|boolean|null {@see WP_JSON_Server::check_authentication}
5502
	 */
5503
	public function wp_rest_authentication_errors( $value ) {
5504
		if ( $value !== null ) {
5505
			return $value;
5506
		}
5507
		return $this->rest_authentication_status;
5508
	}
5509
5510
	/**
5511
	 * Add our nonce to this request.
5512
	 *
5513
	 * @deprecated since 7.7.0
5514
	 * @see Automattic\Jetpack\Connection\Manager::add_nonce()
5515
	 *
5516
	 * @param int    $timestamp Timestamp of the request.
5517
	 * @param string $nonce     Nonce string.
5518
	 */
5519
	public function add_nonce( $timestamp, $nonce ) {
5520
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::add_nonce' );
5521
		return $this->connection_manager->add_nonce( $timestamp, $nonce );
5522
	}
5523
5524
	/**
5525
	 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths since it is passed by reference to various methods.
5526
	 * Capture it here so we can verify the signature later.
5527
	 *
5528
	 * @deprecated since 7.7.0
5529
	 * @see Automattic\Jetpack\Connection\Manager::xmlrpc_methods()
5530
	 *
5531
	 * @param array $methods XMLRPC methods.
5532
	 * @return array XMLRPC methods, with the $HTTP_RAW_POST_DATA one.
5533
	 */
5534
	public function xmlrpc_methods( $methods ) {
5535
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::xmlrpc_methods' );
5536
		return $this->connection_manager->xmlrpc_methods( $methods );
5537
	}
5538
5539
	/**
5540
	 * Register additional public XMLRPC methods.
5541
	 *
5542
	 * @deprecated since 7.7.0
5543
	 * @see Automattic\Jetpack\Connection\Manager::public_xmlrpc_methods()
5544
	 *
5545
	 * @param array $methods Public XMLRPC methods.
5546
	 * @return array Public XMLRPC methods, with the getOptions one.
5547
	 */
5548
	public function public_xmlrpc_methods( $methods ) {
5549
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::public_xmlrpc_methods' );
5550
		return $this->connection_manager->public_xmlrpc_methods( $methods );
5551
	}
5552
5553
	/**
5554
	 * Handles a getOptions XMLRPC method call.
5555
	 *
5556
	 * @deprecated since 7.7.0
5557
	 * @see Automattic\Jetpack\Connection\Manager::jetpack_getOptions()
5558
	 *
5559
	 * @param array $args method call arguments.
5560
	 * @return array an amended XMLRPC server options array.
5561
	 */
5562
	public function jetpack_getOptions( $args ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
5563
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::jetpack_getOptions' );
5564
		return $this->connection_manager->jetpack_getOptions( $args );
5565
	}
5566
5567
	/**
5568
	 * Adds Jetpack-specific options to the output of the XMLRPC options method.
5569
	 *
5570
	 * @deprecated since 7.7.0
5571
	 * @see Automattic\Jetpack\Connection\Manager::xmlrpc_options()
5572
	 *
5573
	 * @param array $options Standard Core options.
5574
	 * @return array Amended options.
5575
	 */
5576
	public function xmlrpc_options( $options ) {
5577
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::xmlrpc_options' );
5578
		return $this->connection_manager->xmlrpc_options( $options );
5579
	}
5580
5581
	/**
5582
	 * Cleans nonces that were saved when calling ::add_nonce.
5583
	 *
5584
	 * @deprecated since 7.7.0
5585
	 * @see Automattic\Jetpack\Connection\Manager::clean_nonces()
5586
	 *
5587
	 * @param bool $all whether to clean even non-expired nonces.
5588
	 */
5589
	public static function clean_nonces( $all = false ) {
5590
		_deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::clean_nonces' );
5591
		return self::connection()->clean_nonces( $all );
5592
	}
5593
5594
	/**
5595
	 * State is passed via cookies from one request to the next, but never to subsequent requests.
5596
	 * SET: state( $key, $value );
5597
	 * GET: $value = state( $key );
5598
	 *
5599
	 * @param string $key
5600
	 * @param string $value
5601
	 * @param bool   $restate private
5602
	 */
5603
	public static function state( $key = null, $value = null, $restate = false ) {
5604
		static $state = array();
5605
		static $path, $domain;
5606
		if ( ! isset( $path ) ) {
5607
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
5608
			$admin_url = self::admin_url();
5609
			$bits      = wp_parse_url( $admin_url );
5610
5611
			if ( is_array( $bits ) ) {
5612
				$path   = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
5613
				$domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
5614
			} else {
5615
				$path = $domain = null;
5616
			}
5617
		}
5618
5619
		// Extract state from cookies and delete cookies
5620
		if ( isset( $_COOKIE['jetpackState'] ) && is_array( $_COOKIE['jetpackState'] ) ) {
5621
			$yum = $_COOKIE['jetpackState'];
5622
			unset( $_COOKIE['jetpackState'] );
5623
			foreach ( $yum as $k => $v ) {
5624
				if ( strlen( $v ) ) {
5625
					$state[ $k ] = $v;
5626
				}
5627
				setcookie( "jetpackState[$k]", false, 0, $path, $domain );
5628
			}
5629
		}
5630
5631
		if ( $restate ) {
5632
			foreach ( $state as $k => $v ) {
5633
				setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
5634
			}
5635
			return;
5636
		}
5637
5638
		// Get a state variable
5639
		if ( isset( $key ) && ! isset( $value ) ) {
5640
			if ( array_key_exists( $key, $state ) ) {
5641
				return $state[ $key ];
5642
			}
5643
			return null;
5644
		}
5645
5646
		// Set a state variable
5647
		if ( isset( $key ) && isset( $value ) ) {
5648
			if ( is_array( $value ) && isset( $value[0] ) ) {
5649
				$value = $value[0];
5650
			}
5651
			$state[ $key ] = $value;
5652
			if ( ! headers_sent() ) {
5653
				setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
5654
			}
5655
		}
5656
	}
5657
5658
	public static function restate() {
5659
		self::state( null, null, true );
5660
	}
5661
5662
	public static function check_privacy( $file ) {
5663
		static $is_site_publicly_accessible = null;
5664
5665
		if ( is_null( $is_site_publicly_accessible ) ) {
5666
			$is_site_publicly_accessible = false;
5667
5668
			$rpc = new Jetpack_IXR_Client();
5669
5670
			$success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
5671
			if ( $success ) {
5672
				$response = $rpc->getResponse();
5673
				if ( $response ) {
5674
					$is_site_publicly_accessible = true;
5675
				}
5676
			}
5677
5678
			Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
5679
		}
5680
5681
		if ( $is_site_publicly_accessible ) {
5682
			return;
5683
		}
5684
5685
		$module_slug = self::get_module_slug( $file );
5686
5687
		$privacy_checks = self::state( 'privacy_checks' );
5688
		if ( ! $privacy_checks ) {
5689
			$privacy_checks = $module_slug;
5690
		} else {
5691
			$privacy_checks .= ",$module_slug";
5692
		}
5693
5694
		self::state( 'privacy_checks', $privacy_checks );
5695
	}
5696
5697
	/**
5698
	 * Helper method for multicall XMLRPC.
5699
	 *
5700
	 * @param ...$args Args for the async_call.
5701
	 */
5702
	public static function xmlrpc_async_call( ...$args ) {
5703
		global $blog_id;
5704
		static $clients = array();
5705
5706
		$client_blog_id = is_multisite() ? $blog_id : 0;
5707
5708
		if ( ! isset( $clients[ $client_blog_id ] ) ) {
5709
			$clients[ $client_blog_id ] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => JETPACK_MASTER_USER ) );
5710
			if ( function_exists( 'ignore_user_abort' ) ) {
5711
				ignore_user_abort( true );
5712
			}
5713
			add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
5714
		}
5715
5716
		if ( ! empty( $args[0] ) ) {
5717
			call_user_func_array( array( $clients[ $client_blog_id ], 'addCall' ), $args );
5718
		} elseif ( is_multisite() ) {
5719
			foreach ( $clients as $client_blog_id => $client ) {
5720
				if ( ! $client_blog_id || empty( $client->calls ) ) {
5721
					continue;
5722
				}
5723
5724
				$switch_success = switch_to_blog( $client_blog_id, true );
5725
				if ( ! $switch_success ) {
5726
					continue;
5727
				}
5728
5729
				flush();
5730
				$client->query();
5731
5732
				restore_current_blog();
5733
			}
5734
		} else {
5735
			if ( isset( $clients[0] ) && ! empty( $clients[0]->calls ) ) {
5736
				flush();
5737
				$clients[0]->query();
5738
			}
5739
		}
5740
	}
5741
5742
	public static function staticize_subdomain( $url ) {
5743
5744
		// Extract hostname from URL
5745
		$host = wp_parse_url( $url, PHP_URL_HOST );
5746
5747
		// Explode hostname on '.'
5748
		$exploded_host = explode( '.', $host );
5749
5750
		// Retrieve the name and TLD
5751
		if ( count( $exploded_host ) > 1 ) {
5752
			$name = $exploded_host[ count( $exploded_host ) - 2 ];
5753
			$tld  = $exploded_host[ count( $exploded_host ) - 1 ];
5754
			// Rebuild domain excluding subdomains
5755
			$domain = $name . '.' . $tld;
5756
		} else {
5757
			$domain = $host;
5758
		}
5759
		// Array of Automattic domains
5760
		$domain_whitelist = array( 'wordpress.com', 'wp.com' );
5761
5762
		// Return $url if not an Automattic domain
5763
		if ( ! in_array( $domain, $domain_whitelist ) ) {
5764
			return $url;
5765
		}
5766
5767
		if ( is_ssl() ) {
5768
			return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
5769
		}
5770
5771
		srand( crc32( basename( $url ) ) );
5772
		$static_counter = rand( 0, 2 );
5773
		srand(); // this resets everything that relies on this, like array_rand() and shuffle()
5774
5775
		return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
5776
	}
5777
5778
	/* JSON API Authorization */
5779
5780
	/**
5781
	 * Handles the login action for Authorizing the JSON API
5782
	 */
5783
	function login_form_json_api_authorization() {
5784
		$this->verify_json_api_authorization_request();
5785
5786
		add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
5787
5788
		add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
5789
		add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5790
		add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
5791
	}
5792
5793
	// Make sure the login form is POSTed to the signed URL so we can reverify the request
5794
	function post_login_form_to_signed_url( $url, $path, $scheme ) {
5795
		if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
5796
			return $url;
5797
		}
5798
5799
		$parsed_url = wp_parse_url( $url );
5800
		$url        = strtok( $url, '?' );
5801
		$url        = "$url?{$_SERVER['QUERY_STRING']}";
5802
		if ( ! empty( $parsed_url['query'] ) ) {
5803
			$url .= "&{$parsed_url['query']}";
5804
		}
5805
5806
		return $url;
5807
	}
5808
5809
	// Make sure the POSTed request is handled by the same action
5810
	function preserve_action_in_login_form_for_json_api_authorization() {
5811
		echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
5812
		echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) . "' />\n";
5813
	}
5814
5815
	// If someone logs in to approve API access, store the Access Code in usermeta
5816
	function store_json_api_authorization_token( $user_login, $user ) {
5817
		add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5818
		add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
5819
		$token = wp_generate_password( 32, false );
5820
		update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5821
	}
5822
5823
	// Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
5824
	function allow_wpcom_public_api_domain( $domains ) {
5825
		$domains[] = 'public-api.wordpress.com';
5826
		return $domains;
5827
	}
5828
5829
	static function is_redirect_encoded( $redirect_url ) {
5830
		return preg_match( '/https?%3A%2F%2F/i', $redirect_url ) > 0;
5831
	}
5832
5833
	// Add all wordpress.com environments to the safe redirect whitelist
5834
	function allow_wpcom_environments( $domains ) {
5835
		$domains[] = 'wordpress.com';
5836
		$domains[] = 'wpcalypso.wordpress.com';
5837
		$domains[] = 'horizon.wordpress.com';
5838
		$domains[] = 'calypso.localhost';
5839
		return $domains;
5840
	}
5841
5842
	// Add the Access Code details to the public-api.wordpress.com redirect
5843
	function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5844
		return add_query_arg(
5845
			urlencode_deep(
5846
				array(
5847
					'jetpack-code'    => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5848
					'jetpack-user-id' => (int) $user->ID,
5849
					'jetpack-state'   => $this->json_api_authorization_request['state'],
5850
				)
5851
			),
5852
			$redirect_to
5853
		);
5854
	}
5855
5856
5857
	/**
5858
	 * Verifies the request by checking the signature
5859
	 *
5860
	 * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
5861
	 * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
5862
	 *
5863
	 * @param null|array $environment
5864
	 */
5865
	function verify_json_api_authorization_request( $environment = null ) {
5866
		$environment = is_null( $environment )
5867
			? $_REQUEST
5868
			: $environment;
5869
5870
		list( $envToken, $envVersion, $envUserId ) = explode( ':', $environment['token'] );
5871
		$token                                     = Jetpack_Data::get_access_token( $envUserId, $envToken );
5872
		if ( ! $token || empty( $token->secret ) ) {
5873
			wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack' ) );
5874
		}
5875
5876
		$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' );
5877
5878
		// Host has encoded the request URL, probably as a result of a bad http => https redirect
5879
		if ( self::is_redirect_encoded( $_GET['redirect_to'] ) ) {
5880
			/**
5881
			 * Jetpack authorisation request Error.
5882
			 *
5883
			 * @since 7.5.0
5884
			 */
5885
			do_action( 'jetpack_verify_api_authorization_request_error_double_encode' );
5886
			$die_error = sprintf(
5887
				/* translators: %s is a URL */
5888
				__( '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' ),
5889
				'https://jetpack.com/support/double-encoding/'
5890
			);
5891
		}
5892
5893
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5894
5895
		if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
5896
			$signature = $jetpack_signature->sign_request(
5897
				$environment['token'],
5898
				$environment['timestamp'],
5899
				$environment['nonce'],
5900
				'',
5901
				'GET',
5902
				$environment['jetpack_json_api_original_query'],
5903
				null,
5904
				true
5905
			);
5906
		} else {
5907
			$signature = $jetpack_signature->sign_current_request(
5908
				array(
5909
					'body'   => null,
5910
					'method' => 'GET',
5911
				)
5912
			);
5913
		}
5914
5915
		if ( ! $signature ) {
5916
			wp_die( $die_error );
5917
		} elseif ( is_wp_error( $signature ) ) {
5918
			wp_die( $die_error );
5919
		} elseif ( ! hash_equals( $signature, $environment['signature'] ) ) {
5920
			if ( is_ssl() ) {
5921
				// 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
5922
				$signature = $jetpack_signature->sign_current_request(
5923
					array(
5924
						'scheme' => 'http',
5925
						'body'   => null,
5926
						'method' => 'GET',
5927
					)
5928
				);
5929
				if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
5930
					wp_die( $die_error );
5931
				}
5932
			} else {
5933
				wp_die( $die_error );
5934
			}
5935
		}
5936
5937
		$timestamp = (int) $environment['timestamp'];
5938
		$nonce     = stripslashes( (string) $environment['nonce'] );
5939
5940
		if ( ! $this->connection_manager->add_nonce( $timestamp, $nonce ) ) {
5941
			// De-nonce the nonce, at least for 5 minutes.
5942
			// 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)
5943
			$old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5944
			if ( $old_nonce_time < time() - 300 ) {
5945
				wp_die( __( 'The authorization process expired.  Please go back and try again.', 'jetpack' ) );
5946
			}
5947
		}
5948
5949
		$data         = json_decode( base64_decode( stripslashes( $environment['data'] ) ) );
5950
		$data_filters = array(
5951
			'state'        => 'opaque',
5952
			'client_id'    => 'int',
5953
			'client_title' => 'string',
5954
			'client_image' => 'url',
5955
		);
5956
5957
		foreach ( $data_filters as $key => $sanitation ) {
5958
			if ( ! isset( $data->$key ) ) {
5959
				wp_die( $die_error );
5960
			}
5961
5962
			switch ( $sanitation ) {
5963
				case 'int':
5964
					$this->json_api_authorization_request[ $key ] = (int) $data->$key;
5965
					break;
5966
				case 'opaque':
5967
					$this->json_api_authorization_request[ $key ] = (string) $data->$key;
5968
					break;
5969
				case 'string':
5970
					$this->json_api_authorization_request[ $key ] = wp_kses( (string) $data->$key, array() );
5971
					break;
5972
				case 'url':
5973
					$this->json_api_authorization_request[ $key ] = esc_url_raw( (string) $data->$key );
5974
					break;
5975
			}
5976
		}
5977
5978
		if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5979
			wp_die( $die_error );
5980
		}
5981
	}
5982
5983
	function login_message_json_api_authorization( $message ) {
5984
		return '<p class="message">' . sprintf(
5985
			esc_html__( '%s wants to access your site&#8217;s data.  Log in to authorize that access.', 'jetpack' ),
5986
			'<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5987
		) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5988
	}
5989
5990
	/**
5991
	 * Get $content_width, but with a <s>twist</s> filter.
5992
	 */
5993
	public static function get_content_width() {
5994
		$content_width = ( isset( $GLOBALS['content_width'] ) && is_numeric( $GLOBALS['content_width'] ) )
5995
			? $GLOBALS['content_width']
5996
			: false;
5997
		/**
5998
		 * Filter the Content Width value.
5999
		 *
6000
		 * @since 2.2.3
6001
		 *
6002
		 * @param string $content_width Content Width value.
6003
		 */
6004
		return apply_filters( 'jetpack_content_width', $content_width );
6005
	}
6006
6007
	/**
6008
	 * Pings the WordPress.com Mirror Site for the specified options.
6009
	 *
6010
	 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
6011
	 *
6012
	 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
6013
	 */
6014
	public function get_cloud_site_options( $option_names ) {
6015
		$option_names = array_filter( (array) $option_names, 'is_string' );
6016
6017
		$xml = new Jetpack_IXR_Client( array( 'user_id' => JETPACK_MASTER_USER ) );
6018
		$xml->query( 'jetpack.fetchSiteOptions', $option_names );
6019
		if ( $xml->isError() ) {
6020
			return array(
6021
				'error_code' => $xml->getErrorCode(),
6022
				'error_msg'  => $xml->getErrorMessage(),
6023
			);
6024
		}
6025
		$cloud_site_options = $xml->getResponse();
6026
6027
		return $cloud_site_options;
6028
	}
6029
6030
	/**
6031
	 * Checks if the site is currently in an identity crisis.
6032
	 *
6033
	 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
6034
	 */
6035
	public static function check_identity_crisis() {
6036
		if ( ! self::is_active() || ( new Status() )->is_development_mode() || ! self::validate_sync_error_idc_option() ) {
6037
			return false;
6038
		}
6039
6040
		return Jetpack_Options::get_option( 'sync_error_idc' );
6041
	}
6042
6043
	/**
6044
	 * Checks whether the home and siteurl specifically are whitelisted
6045
	 * Written so that we don't have re-check $key and $value params every time
6046
	 * we want to check if this site is whitelisted, for example in footer.php
6047
	 *
6048
	 * @since  3.8.0
6049
	 * @return bool True = already whitelisted False = not whitelisted
6050
	 */
6051
	public static function is_staging_site() {
6052
		_deprecated_function( 'Jetpack::is_staging_site', 'jetpack-8.1', '/Automattic/Jetpack/Status->is_staging_site' );
6053
		return ( new Status() )->is_staging_site();
6054
	}
6055
6056
	/**
6057
	 * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
6058
	 *
6059
	 * @since 4.4.0
6060
	 * @since 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC
6061
	 *
6062
	 * @return bool
6063
	 */
6064
	public static function validate_sync_error_idc_option() {
6065
		$is_valid = false;
6066
6067
		$idc_allowed = get_transient( 'jetpack_idc_allowed' );
6068
		if ( false === $idc_allowed ) {
6069
			$response = wp_remote_get( 'https://jetpack.com/is-idc-allowed/' );
6070
			if ( 200 === (int) wp_remote_retrieve_response_code( $response ) ) {
6071
				$json               = json_decode( wp_remote_retrieve_body( $response ) );
6072
				$idc_allowed        = isset( $json, $json->result ) && $json->result ? '1' : '0';
6073
				$transient_duration = HOUR_IN_SECONDS;
6074
			} else {
6075
				// If the request failed for some reason, then assume IDC is allowed and set shorter transient.
6076
				$idc_allowed        = '1';
6077
				$transient_duration = 5 * MINUTE_IN_SECONDS;
6078
			}
6079
6080
			set_transient( 'jetpack_idc_allowed', $idc_allowed, $transient_duration );
6081
		}
6082
6083
		// Is the site opted in and does the stored sync_error_idc option match what we now generate?
6084
		$sync_error = Jetpack_Options::get_option( 'sync_error_idc' );
6085
		if ( $idc_allowed && $sync_error && self::sync_idc_optin() ) {
6086
			$local_options = self::get_sync_error_idc_option();
6087
			// Ensure all values are set.
6088
			if ( isset( $sync_error['home'] ) && isset ( $local_options['home'] ) && isset( $sync_error['siteurl'] ) && isset( $local_options['siteurl'] ) ) {
6089
				if ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) {
6090
					$is_valid = true;
6091
				}
6092
			}
6093
6094
		}
6095
6096
		/**
6097
		 * Filters whether the sync_error_idc option is valid.
6098
		 *
6099
		 * @since 4.4.0
6100
		 *
6101
		 * @param bool $is_valid If the sync_error_idc is valid or not.
6102
		 */
6103
		$is_valid = (bool) apply_filters( 'jetpack_sync_error_idc_validation', $is_valid );
6104
6105
		if ( ! $idc_allowed || ( ! $is_valid && $sync_error ) ) {
6106
			// Since the option exists, and did not validate, delete it
6107
			Jetpack_Options::delete_option( 'sync_error_idc' );
6108
		}
6109
6110
		return $is_valid;
6111
	}
6112
6113
	/**
6114
	 * Normalizes a url by doing three things:
6115
	 *  - Strips protocol
6116
	 *  - Strips www
6117
	 *  - Adds a trailing slash
6118
	 *
6119
	 * @since 4.4.0
6120
	 * @param string $url
6121
	 * @return WP_Error|string
6122
	 */
6123
	public static function normalize_url_protocol_agnostic( $url ) {
6124
		$parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
6125
		if ( ! $parsed_url || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
6126
			return new WP_Error( 'cannot_parse_url', sprintf( esc_html__( 'Cannot parse URL %s', 'jetpack' ), $url ) );
6127
		}
6128
6129
		// Strip www and protocols
6130
		$url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
6131
		return $url;
6132
	}
6133
6134
	/**
6135
	 * Gets the value that is to be saved in the jetpack_sync_error_idc option.
6136
	 *
6137
	 * @since 4.4.0
6138
	 * @since 5.4.0 Add transient since home/siteurl retrieved directly from DB
6139
	 *
6140
	 * @param array $response
6141
	 * @return array Array of the local urls, wpcom urls, and error code
6142
	 */
6143
	public static function get_sync_error_idc_option( $response = array() ) {
6144
		// Since the local options will hit the database directly, store the values
6145
		// in a transient to allow for autoloading and caching on subsequent views.
6146
		$local_options = get_transient( 'jetpack_idc_local' );
6147
		if ( false === $local_options ) {
6148
			$local_options = array(
6149
				'home'    => Functions::home_url(),
6150
				'siteurl' => Functions::site_url(),
6151
			);
6152
			set_transient( 'jetpack_idc_local', $local_options, MINUTE_IN_SECONDS );
6153
		}
6154
6155
		$options = array_merge( $local_options, $response );
6156
6157
		$returned_values = array();
6158
		foreach ( $options as $key => $option ) {
6159
			if ( 'error_code' === $key ) {
6160
				$returned_values[ $key ] = $option;
6161
				continue;
6162
			}
6163
6164
			if ( is_wp_error( $normalized_url = self::normalize_url_protocol_agnostic( $option ) ) ) {
6165
				continue;
6166
			}
6167
6168
			$returned_values[ $key ] = $normalized_url;
6169
		}
6170
6171
		set_transient( 'jetpack_idc_option', $returned_values, MINUTE_IN_SECONDS );
6172
6173
		return $returned_values;
6174
	}
6175
6176
	/**
6177
	 * Returns the value of the jetpack_sync_idc_optin filter, or constant.
6178
	 * If set to true, the site will be put into staging mode.
6179
	 *
6180
	 * @since 4.3.2
6181
	 * @return bool
6182
	 */
6183
	public static function sync_idc_optin() {
6184
		if ( Constants::is_defined( 'JETPACK_SYNC_IDC_OPTIN' ) ) {
6185
			$default = Constants::get_constant( 'JETPACK_SYNC_IDC_OPTIN' );
6186
		} else {
6187
			$default = ! Constants::is_defined( 'SUNRISE' ) && ! is_multisite();
6188
		}
6189
6190
		/**
6191
		 * Allows sites to optin to IDC mitigation which blocks the site from syncing to WordPress.com when the home
6192
		 * URL or site URL do not match what WordPress.com expects. The default value is either false, or the value of
6193
		 * JETPACK_SYNC_IDC_OPTIN constant if set.
6194
		 *
6195
		 * @since 4.3.2
6196
		 *
6197
		 * @param bool $default Whether the site is opted in to IDC mitigation.
6198
		 */
6199
		return (bool) apply_filters( 'jetpack_sync_idc_optin', $default );
6200
	}
6201
6202
	/**
6203
	 * Maybe Use a .min.css stylesheet, maybe not.
6204
	 *
6205
	 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
6206
	 */
6207
	public static function maybe_min_asset( $url, $path, $plugin ) {
6208
		// Short out on things trying to find actual paths.
6209
		if ( ! $path || empty( $plugin ) ) {
6210
			return $url;
6211
		}
6212
6213
		$path = ltrim( $path, '/' );
6214
6215
		// Strip out the abspath.
6216
		$base = dirname( plugin_basename( $plugin ) );
6217
6218
		// Short out on non-Jetpack assets.
6219
		if ( 'jetpack/' !== substr( $base, 0, 8 ) ) {
6220
			return $url;
6221
		}
6222
6223
		// File name parsing.
6224
		$file              = "{$base}/{$path}";
6225
		$full_path         = JETPACK__PLUGIN_DIR . substr( $file, 8 );
6226
		$file_name         = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
6227
		$file_name_parts_r = array_reverse( explode( '.', $file_name ) );
6228
		$extension         = array_shift( $file_name_parts_r );
6229
6230
		if ( in_array( strtolower( $extension ), array( 'css', 'js' ) ) ) {
6231
			// Already pointing at the minified version.
6232
			if ( 'min' === $file_name_parts_r[0] ) {
6233
				return $url;
6234
			}
6235
6236
			$min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
6237
			if ( file_exists( $min_full_path ) ) {
6238
				$url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
6239
				// If it's a CSS file, stash it so we can set the .min suffix for rtl-ing.
6240
				if ( 'css' === $extension ) {
6241
					$key                      = str_replace( JETPACK__PLUGIN_DIR, 'jetpack/', $min_full_path );
6242
					self::$min_assets[ $key ] = $path;
6243
				}
6244
			}
6245
		}
6246
6247
		return $url;
6248
	}
6249
6250
	/**
6251
	 * If the asset is minified, let's flag .min as the suffix.
6252
	 *
6253
	 * Attached to `style_loader_src` filter.
6254
	 *
6255
	 * @param string $tag The tag that would link to the external asset.
6256
	 * @param string $handle The registered handle of the script in question.
6257
	 * @param string $href The url of the asset in question.
6258
	 */
6259
	public static function set_suffix_on_min( $src, $handle ) {
6260
		if ( false === strpos( $src, '.min.css' ) ) {
6261
			return $src;
6262
		}
6263
6264
		if ( ! empty( self::$min_assets ) ) {
6265
			foreach ( self::$min_assets as $file => $path ) {
6266
				if ( false !== strpos( $src, $file ) ) {
6267
					wp_style_add_data( $handle, 'suffix', '.min' );
6268
					return $src;
6269
				}
6270
			}
6271
		}
6272
6273
		return $src;
6274
	}
6275
6276
	/**
6277
	 * Maybe inlines a stylesheet.
6278
	 *
6279
	 * If you'd like to inline a stylesheet instead of printing a link to it,
6280
	 * wp_style_add_data( 'handle', 'jetpack-inline', true );
6281
	 *
6282
	 * Attached to `style_loader_tag` filter.
6283
	 *
6284
	 * @param string $tag The tag that would link to the external asset.
6285
	 * @param string $handle The registered handle of the script in question.
6286
	 *
6287
	 * @return string
6288
	 */
6289
	public static function maybe_inline_style( $tag, $handle ) {
6290
		global $wp_styles;
6291
		$item = $wp_styles->registered[ $handle ];
6292
6293
		if ( ! isset( $item->extra['jetpack-inline'] ) || ! $item->extra['jetpack-inline'] ) {
6294
			return $tag;
6295
		}
6296
6297
		if ( preg_match( '# href=\'([^\']+)\' #i', $tag, $matches ) ) {
6298
			$href = $matches[1];
6299
			// Strip off query string
6300
			if ( $pos = strpos( $href, '?' ) ) {
6301
				$href = substr( $href, 0, $pos );
6302
			}
6303
			// Strip off fragment
6304
			if ( $pos = strpos( $href, '#' ) ) {
6305
				$href = substr( $href, 0, $pos );
6306
			}
6307
		} else {
6308
			return $tag;
6309
		}
6310
6311
		$plugins_dir = plugin_dir_url( JETPACK__PLUGIN_FILE );
6312
		if ( $plugins_dir !== substr( $href, 0, strlen( $plugins_dir ) ) ) {
6313
			return $tag;
6314
		}
6315
6316
		// If this stylesheet has a RTL version, and the RTL version replaces normal...
6317
		if ( isset( $item->extra['rtl'] ) && 'replace' === $item->extra['rtl'] && is_rtl() ) {
6318
			// And this isn't the pass that actually deals with the RTL version...
6319
			if ( false === strpos( $tag, " id='$handle-rtl-css' " ) ) {
6320
				// Short out, as the RTL version will deal with it in a moment.
6321
				return $tag;
6322
			}
6323
		}
6324
6325
		$file = JETPACK__PLUGIN_DIR . substr( $href, strlen( $plugins_dir ) );
6326
		$css  = self::absolutize_css_urls( file_get_contents( $file ), $href );
6327
		if ( $css ) {
6328
			$tag = "<!-- Inline {$item->handle} -->\r\n";
6329
			if ( empty( $item->extra['after'] ) ) {
6330
				wp_add_inline_style( $handle, $css );
6331
			} else {
6332
				array_unshift( $item->extra['after'], $css );
6333
				wp_style_add_data( $handle, 'after', $item->extra['after'] );
6334
			}
6335
		}
6336
6337
		return $tag;
6338
	}
6339
6340
	/**
6341
	 * Loads a view file from the views
6342
	 *
6343
	 * Data passed in with the $data parameter will be available in the
6344
	 * template file as $data['value']
6345
	 *
6346
	 * @param string $template - Template file to load
6347
	 * @param array  $data - Any data to pass along to the template
6348
	 * @return boolean - If template file was found
6349
	 **/
6350
	public function load_view( $template, $data = array() ) {
6351
		$views_dir = JETPACK__PLUGIN_DIR . 'views/';
6352
6353
		if ( file_exists( $views_dir . $template ) ) {
6354
			require_once $views_dir . $template;
6355
			return true;
6356
		}
6357
6358
		error_log( "Jetpack: Unable to find view file $views_dir$template" );
6359
		return false;
6360
	}
6361
6362
	/**
6363
	 * Throws warnings for deprecated hooks to be removed from Jetpack
6364
	 */
6365
	public function deprecated_hooks() {
6366
		global $wp_filter;
6367
6368
		/*
6369
		 * Format:
6370
		 * deprecated_filter_name => replacement_name
6371
		 *
6372
		 * If there is no replacement, use null for replacement_name
6373
		 */
6374
		$deprecated_list = array(
6375
			'jetpack_bail_on_shortcode'                    => 'jetpack_shortcodes_to_include',
6376
			'wpl_sharing_2014_1'                           => null,
6377
			'jetpack-tools-to-include'                     => 'jetpack_tools_to_include',
6378
			'jetpack_identity_crisis_options_to_check'     => null,
6379
			'update_option_jetpack_single_user_site'       => null,
6380
			'audio_player_default_colors'                  => null,
6381
			'add_option_jetpack_featured_images_enabled'   => null,
6382
			'add_option_jetpack_update_details'            => null,
6383
			'add_option_jetpack_updates'                   => null,
6384
			'add_option_jetpack_network_name'              => null,
6385
			'add_option_jetpack_network_allow_new_registrations' => null,
6386
			'add_option_jetpack_network_add_new_users'     => null,
6387
			'add_option_jetpack_network_site_upload_space' => null,
6388
			'add_option_jetpack_network_upload_file_types' => null,
6389
			'add_option_jetpack_network_enable_administration_menus' => null,
6390
			'add_option_jetpack_is_multi_site'             => null,
6391
			'add_option_jetpack_is_main_network'           => null,
6392
			'add_option_jetpack_main_network_site'         => null,
6393
			'jetpack_sync_all_registered_options'          => null,
6394
			'jetpack_has_identity_crisis'                  => 'jetpack_sync_error_idc_validation',
6395
			'jetpack_is_post_mailable'                     => null,
6396
			'jetpack_seo_site_host'                        => null,
6397
			'jetpack_installed_plugin'                     => 'jetpack_plugin_installed',
6398
			'jetpack_holiday_snow_option_name'             => null,
6399
			'jetpack_holiday_chance_of_snow'               => null,
6400
			'jetpack_holiday_snow_js_url'                  => null,
6401
			'jetpack_is_holiday_snow_season'               => null,
6402
			'jetpack_holiday_snow_option_updated'          => null,
6403
			'jetpack_holiday_snowing'                      => null,
6404
			'jetpack_sso_auth_cookie_expirtation'          => 'jetpack_sso_auth_cookie_expiration',
6405
			'jetpack_cache_plans'                          => null,
6406
			'jetpack_updated_theme'                        => 'jetpack_updated_themes',
6407
			'jetpack_lazy_images_skip_image_with_atttributes' => 'jetpack_lazy_images_skip_image_with_attributes',
6408
			'jetpack_enable_site_verification'             => null,
6409
			'can_display_jetpack_manage_notice'            => null,
6410
			// Removed in Jetpack 7.3.0
6411
			'atd_load_scripts'                             => null,
6412
			'atd_http_post_timeout'                        => null,
6413
			'atd_http_post_error'                          => null,
6414
			'atd_service_domain'                           => null,
6415
			'jetpack_widget_authors_exclude'               => 'jetpack_widget_authors_params',
6416
			// Removed in Jetpack 7.9.0
6417
			'jetpack_pwa_manifest'                         => null,
6418
			'jetpack_pwa_background_color'                 => null,
6419
		);
6420
6421
		// This is a silly loop depth. Better way?
6422
		foreach ( $deprecated_list as $hook => $hook_alt ) {
6423
			if ( has_action( $hook ) ) {
6424
				foreach ( $wp_filter[ $hook ] as $func => $values ) {
6425
					foreach ( $values as $hooked ) {
6426
						if ( is_callable( $hooked['function'] ) ) {
6427
							$function_name = 'an anonymous function';
6428
						} else {
6429
							$function_name = $hooked['function'];
6430
						}
6431
						_deprecated_function( $hook . ' used for ' . $function_name, null, $hook_alt );
6432
					}
6433
				}
6434
			}
6435
		}
6436
	}
6437
6438
	/**
6439
	 * Converts any url in a stylesheet, to the correct absolute url.
6440
	 *
6441
	 * Considerations:
6442
	 *  - Normal, relative URLs     `feh.png`
6443
	 *  - Data URLs                 `data:image/gif;base64,eh129ehiuehjdhsa==`
6444
	 *  - Schema-agnostic URLs      `//domain.com/feh.png`
6445
	 *  - Absolute URLs             `http://domain.com/feh.png`
6446
	 *  - Domain root relative URLs `/feh.png`
6447
	 *
6448
	 * @param $css string: The raw CSS -- should be read in directly from the file.
6449
	 * @param $css_file_url : The URL that the file can be accessed at, for calculating paths from.
6450
	 *
6451
	 * @return mixed|string
6452
	 */
6453
	public static function absolutize_css_urls( $css, $css_file_url ) {
6454
		$pattern = '#url\((?P<path>[^)]*)\)#i';
6455
		$css_dir = dirname( $css_file_url );
6456
		$p       = wp_parse_url( $css_dir );
6457
		$domain  = sprintf(
6458
			'%1$s//%2$s%3$s%4$s',
6459
			isset( $p['scheme'] ) ? "{$p['scheme']}:" : '',
6460
			isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
6461
			$p['host'],
6462
			isset( $p['port'] ) ? ":{$p['port']}" : ''
6463
		);
6464
6465
		if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
6466
			$find = $replace = array();
6467
			foreach ( $matches as $match ) {
6468
				$url = trim( $match['path'], "'\" \t" );
6469
6470
				// If this is a data url, we don't want to mess with it.
6471
				if ( 'data:' === substr( $url, 0, 5 ) ) {
6472
					continue;
6473
				}
6474
6475
				// If this is an absolute or protocol-agnostic url,
6476
				// we don't want to mess with it.
6477
				if ( preg_match( '#^(https?:)?//#i', $url ) ) {
6478
					continue;
6479
				}
6480
6481
				switch ( substr( $url, 0, 1 ) ) {
6482
					case '/':
6483
						$absolute = $domain . $url;
6484
						break;
6485
					default:
6486
						$absolute = $css_dir . '/' . $url;
6487
				}
6488
6489
				$find[]    = $match[0];
6490
				$replace[] = sprintf( 'url("%s")', $absolute );
6491
			}
6492
			$css = str_replace( $find, $replace, $css );
6493
		}
6494
6495
		return $css;
6496
	}
6497
6498
	/**
6499
	 * This methods removes all of the registered css files on the front end
6500
	 * from Jetpack in favor of using a single file. In effect "imploding"
6501
	 * all the files into one file.
6502
	 *
6503
	 * Pros:
6504
	 * - Uses only ONE css asset connection instead of 15
6505
	 * - Saves a minimum of 56k
6506
	 * - Reduces server load
6507
	 * - Reduces time to first painted byte
6508
	 *
6509
	 * Cons:
6510
	 * - Loads css for ALL modules. However all selectors are prefixed so it
6511
	 *      should not cause any issues with themes.
6512
	 * - Plugins/themes dequeuing styles no longer do anything. See
6513
	 *      jetpack_implode_frontend_css filter for a workaround
6514
	 *
6515
	 * For some situations developers may wish to disable css imploding and
6516
	 * instead operate in legacy mode where each file loads seperately and
6517
	 * can be edited individually or dequeued. This can be accomplished with
6518
	 * the following line:
6519
	 *
6520
	 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6521
	 *
6522
	 * @since 3.2
6523
	 **/
6524
	public function implode_frontend_css( $travis_test = false ) {
6525
		$do_implode = true;
6526
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6527
			$do_implode = false;
6528
		}
6529
6530
		// Do not implode CSS when the page loads via the AMP plugin.
6531
		if ( Jetpack_AMP_Support::is_amp_request() ) {
6532
			$do_implode = false;
6533
		}
6534
6535
		/**
6536
		 * Allow CSS to be concatenated into a single jetpack.css file.
6537
		 *
6538
		 * @since 3.2.0
6539
		 *
6540
		 * @param bool $do_implode Should CSS be concatenated? Default to true.
6541
		 */
6542
		$do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6543
6544
		// Do not use the imploded file when default behavior was altered through the filter
6545
		if ( ! $do_implode ) {
6546
			return;
6547
		}
6548
6549
		// We do not want to use the imploded file in dev mode, or if not connected
6550
		if ( ( new Status() )->is_development_mode() || ! self::is_active() ) {
6551
			if ( ! $travis_test ) {
6552
				return;
6553
			}
6554
		}
6555
6556
		// Do not use the imploded file if sharing css was dequeued via the sharing settings screen
6557
		if ( get_option( 'sharedaddy_disable_resources' ) ) {
6558
			return;
6559
		}
6560
6561
		/*
6562
		 * Now we assume Jetpack is connected and able to serve the single
6563
		 * file.
6564
		 *
6565
		 * In the future there will be a check here to serve the file locally
6566
		 * or potentially from the Jetpack CDN
6567
		 *
6568
		 * For now:
6569
		 * - Enqueue a single imploded css file
6570
		 * - Zero out the style_loader_tag for the bundled ones
6571
		 * - Be happy, drink scotch
6572
		 */
6573
6574
		add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6575
6576
		$version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6577
6578
		wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6579
		wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6580
	}
6581
6582
	function concat_remove_style_loader_tag( $tag, $handle ) {
6583
		if ( in_array( $handle, $this->concatenated_style_handles ) ) {
6584
			$tag = '';
6585
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6586
				$tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6587
			}
6588
		}
6589
6590
		return $tag;
6591
	}
6592
6593
	/**
6594
	 * Add an async attribute to scripts that can be loaded asynchronously.
6595
	 * https://www.w3schools.com/tags/att_script_async.asp
6596
	 *
6597
	 * @since 7.7.0
6598
	 *
6599
	 * @param string $tag    The <script> tag for the enqueued script.
6600
	 * @param string $handle The script's registered handle.
6601
	 * @param string $src    The script's source URL.
6602
	 */
6603
	public function script_add_async( $tag, $handle, $src ) {
6604
		if ( in_array( $handle, $this->async_script_handles, true ) ) {
6605
			return preg_replace( '/^<script /i', '<script async ', $tag );
6606
		}
6607
6608
		return $tag;
6609
	}
6610
6611
	/*
6612
	 * Check the heartbeat data
6613
	 *
6614
	 * Organizes the heartbeat data by severity.  For example, if the site
6615
	 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
6616
	 *
6617
	 * Data will be added to "caution" array, if it either:
6618
	 *  - Out of date Jetpack version
6619
	 *  - Out of date WP version
6620
	 *  - Out of date PHP version
6621
	 *
6622
	 * $return array $filtered_data
6623
	 */
6624
	public static function jetpack_check_heartbeat_data() {
6625
		$raw_data = Jetpack_Heartbeat::generate_stats_array();
6626
6627
		$good    = array();
6628
		$caution = array();
6629
		$bad     = array();
6630
6631
		foreach ( $raw_data as $stat => $value ) {
6632
6633
			// Check jetpack version
6634
			if ( 'version' == $stat ) {
6635
				if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
6636
					$caution[ $stat ] = $value . ' - min supported is ' . JETPACK__VERSION;
6637
					continue;
6638
				}
6639
			}
6640
6641
			// Check WP version
6642
			if ( 'wp-version' == $stat ) {
6643
				if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
6644
					$caution[ $stat ] = $value . ' - min supported is ' . JETPACK__MINIMUM_WP_VERSION;
6645
					continue;
6646
				}
6647
			}
6648
6649
			// Check PHP version
6650
			if ( 'php-version' == $stat ) {
6651
				if ( version_compare( PHP_VERSION, JETPACK__MINIMUM_PHP_VERSION, '<' ) ) {
6652
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__MINIMUM_PHP_VERSION;
6653
					continue;
6654
				}
6655
			}
6656
6657
			// Check ID crisis
6658
			if ( 'identitycrisis' == $stat ) {
6659
				if ( 'yes' == $value ) {
6660
					$bad[ $stat ] = $value;
6661
					continue;
6662
				}
6663
			}
6664
6665
			// The rest are good :)
6666
			$good[ $stat ] = $value;
6667
		}
6668
6669
		$filtered_data = array(
6670
			'good'    => $good,
6671
			'caution' => $caution,
6672
			'bad'     => $bad,
6673
		);
6674
6675
		return $filtered_data;
6676
	}
6677
6678
6679
	/*
6680
	 * This method is used to organize all options that can be reset
6681
	 * without disconnecting Jetpack.
6682
	 *
6683
	 * It is used in class.jetpack-cli.php to reset options
6684
	 *
6685
	 * @since 5.4.0 Logic moved to Jetpack_Options class. Method left in Jetpack class for backwards compat.
6686
	 *
6687
	 * @return array of options to delete.
6688
	 */
6689
	public static function get_jetpack_options_for_reset() {
6690
		return Jetpack_Options::get_options_for_reset();
6691
	}
6692
6693
	/*
6694
	 * Strip http:// or https:// from a url, replaces forward slash with ::,
6695
	 * so we can bring them directly to their site in calypso.
6696
	 *
6697
	 * @param string | url
6698
	 * @return string | url without the guff
6699
	 */
6700
	public static function build_raw_urls( $url ) {
6701
		$strip_http = '/.*?:\/\//i';
6702
		$url        = preg_replace( $strip_http, '', $url );
6703
		$url        = str_replace( '/', '::', $url );
6704
		return $url;
6705
	}
6706
6707
	/**
6708
	 * Stores and prints out domains to prefetch for page speed optimization.
6709
	 *
6710
	 * @param mixed $new_urls
6711
	 */
6712
	public static function dns_prefetch( $new_urls = null ) {
6713
		static $prefetch_urls = array();
6714
		if ( empty( $new_urls ) && ! empty( $prefetch_urls ) ) {
6715
			echo "\r\n";
6716
			foreach ( $prefetch_urls as $this_prefetch_url ) {
6717
				printf( "<link rel='dns-prefetch' href='%s'/>\r\n", esc_attr( $this_prefetch_url ) );
6718
			}
6719
		} elseif ( ! empty( $new_urls ) ) {
6720
			if ( ! has_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) ) ) {
6721
				add_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) );
6722
			}
6723
			foreach ( (array) $new_urls as $this_new_url ) {
6724
				$prefetch_urls[] = strtolower( untrailingslashit( preg_replace( '#^https?://#i', '//', $this_new_url ) ) );
6725
			}
6726
			$prefetch_urls = array_unique( $prefetch_urls );
6727
		}
6728
	}
6729
6730
	public function wp_dashboard_setup() {
6731
		if ( self::is_active() ) {
6732
			add_action( 'jetpack_dashboard_widget', array( __CLASS__, 'dashboard_widget_footer' ), 999 );
6733
		}
6734
6735
		if ( has_action( 'jetpack_dashboard_widget' ) ) {
6736
			$jetpack_logo = new Jetpack_Logo();
6737
			$widget_title = sprintf(
6738
				wp_kses(
6739
					/* translators: Placeholder is a Jetpack logo. */
6740
					__( 'Stats <span>by %s</span>', 'jetpack' ),
6741
					array( 'span' => array() )
6742
				),
6743
				$jetpack_logo->get_jp_emblem( true )
6744
			);
6745
6746
			wp_add_dashboard_widget(
6747
				'jetpack_summary_widget',
6748
				$widget_title,
6749
				array( __CLASS__, 'dashboard_widget' )
6750
			);
6751
			wp_enqueue_style( 'jetpack-dashboard-widget', plugins_url( 'css/dashboard-widget.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
6752
			wp_style_add_data( 'jetpack-dashboard-widget', 'rtl', 'replace' );
6753
6754
			// If we're inactive and not in development mode, sort our box to the top.
6755
			if ( ! self::is_active() && ! ( new Status() )->is_development_mode() ) {
6756
				global $wp_meta_boxes;
6757
6758
				$dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
6759
				$ours      = array( 'jetpack_summary_widget' => $dashboard['jetpack_summary_widget'] );
6760
6761
				$wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard );
6762
			}
6763
		}
6764
	}
6765
6766
	/**
6767
	 * @param mixed $result Value for the user's option
6768
	 * @return mixed
6769
	 */
6770
	function get_user_option_meta_box_order_dashboard( $sorted ) {
6771
		if ( ! is_array( $sorted ) ) {
6772
			return $sorted;
6773
		}
6774
6775
		foreach ( $sorted as $box_context => $ids ) {
6776
			if ( false === strpos( $ids, 'dashboard_stats' ) ) {
6777
				// If the old id isn't anywhere in the ids, don't bother exploding and fail out.
6778
				continue;
6779
			}
6780
6781
			$ids_array = explode( ',', $ids );
6782
			$key       = array_search( 'dashboard_stats', $ids_array );
6783
6784
			if ( false !== $key ) {
6785
				// If we've found that exact value in the option (and not `google_dashboard_stats` for example)
6786
				$ids_array[ $key ]      = 'jetpack_summary_widget';
6787
				$sorted[ $box_context ] = implode( ',', $ids_array );
6788
				// We've found it, stop searching, and just return.
6789
				break;
6790
			}
6791
		}
6792
6793
		return $sorted;
6794
	}
6795
6796
	public static function dashboard_widget() {
6797
		/**
6798
		 * Fires when the dashboard is loaded.
6799
		 *
6800
		 * @since 3.4.0
6801
		 */
6802
		do_action( 'jetpack_dashboard_widget' );
6803
	}
6804
6805
	public static function dashboard_widget_footer() {
6806
		?>
6807
		<footer>
6808
6809
		<div class="protect">
6810
			<?php if ( self::is_module_active( 'protect' ) ) : ?>
6811
				<h3><?php echo number_format_i18n( get_site_option( 'jetpack_protect_blocked_attempts', 0 ) ); ?></h3>
6812
				<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>
6813
			<?php elseif ( current_user_can( 'jetpack_activate_modules' ) && ! ( new Status() )->is_development_mode() ) : ?>
6814
				<a href="
6815
				<?php
6816
				echo esc_url(
6817
					wp_nonce_url(
6818
						self::admin_url(
6819
							array(
6820
								'action' => 'activate',
6821
								'module' => 'protect',
6822
							)
6823
						),
6824
						'jetpack_activate-protect'
6825
					)
6826
				);
6827
				?>
6828
							" class="button button-jetpack" title="<?php esc_attr_e( 'Protect helps to keep you secure from brute-force login attacks.', 'jetpack' ); ?>">
6829
					<?php esc_html_e( 'Activate Protect', 'jetpack' ); ?>
6830
				</a>
6831
			<?php else : ?>
6832
				<?php esc_html_e( 'Protect is inactive.', 'jetpack' ); ?>
6833
			<?php endif; ?>
6834
		</div>
6835
6836
		<div class="akismet">
6837
			<?php if ( is_plugin_active( 'akismet/akismet.php' ) ) : ?>
6838
				<h3><?php echo number_format_i18n( get_option( 'akismet_spam_count', 0 ) ); ?></h3>
6839
				<p><?php echo esc_html_x( 'Spam comments blocked by Akismet.', '{#} Spam comments blocked by Akismet -- number is on a prior line, text is a caption.', 'jetpack' ); ?></p>
6840
			<?php elseif ( current_user_can( 'activate_plugins' ) && ! is_wp_error( validate_plugin( 'akismet/akismet.php' ) ) ) : ?>
6841
				<a href="
6842
				<?php
6843
				echo esc_url(
6844
					wp_nonce_url(
6845
						add_query_arg(
6846
							array(
6847
								'action' => 'activate',
6848
								'plugin' => 'akismet/akismet.php',
6849
							),
6850
							admin_url( 'plugins.php' )
6851
						),
6852
						'activate-plugin_akismet/akismet.php'
6853
					)
6854
				);
6855
				?>
6856
							" class="button button-jetpack">
6857
					<?php esc_html_e( 'Activate Akismet', 'jetpack' ); ?>
6858
				</a>
6859
			<?php else : ?>
6860
				<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( 'Akismet can help to keep your blog safe from spam!', 'jetpack' ); ?></a></p>
6861
			<?php endif; ?>
6862
		</div>
6863
6864
		</footer>
6865
		<?php
6866
	}
6867
6868
	/*
6869
	 * Adds a "blank" column in the user admin table to display indication of user connection.
6870
	 */
6871
	function jetpack_icon_user_connected( $columns ) {
6872
		$columns['user_jetpack'] = '';
6873
		return $columns;
6874
	}
6875
6876
	/*
6877
	 * Show Jetpack icon if the user is linked.
6878
	 */
6879
	function jetpack_show_user_connected_icon( $val, $col, $user_id ) {
6880
		if ( 'user_jetpack' == $col && self::is_user_connected( $user_id ) ) {
6881
			$jetpack_logo = new Jetpack_Logo();
6882
			$emblem_html  = sprintf(
6883
				'<a title="%1$s" class="jp-emblem-user-admin">%2$s</a>',
6884
				esc_attr__( 'This user is linked and ready to fly with Jetpack.', 'jetpack' ),
6885
				$jetpack_logo->get_jp_emblem()
6886
			);
6887
			return $emblem_html;
6888
		}
6889
6890
		return $val;
6891
	}
6892
6893
	/*
6894
	 * Style the Jetpack user column
6895
	 */
6896
	function jetpack_user_col_style() {
6897
		global $current_screen;
6898
		if ( ! empty( $current_screen->base ) && 'users' == $current_screen->base ) {
6899
			?>
6900
			<style>
6901
				.fixed .column-user_jetpack {
6902
					width: 21px;
6903
				}
6904
				.jp-emblem-user-admin svg {
6905
					width: 20px;
6906
					height: 20px;
6907
				}
6908
				.jp-emblem-user-admin path {
6909
					fill: #00BE28;
6910
				}
6911
			</style>
6912
			<?php
6913
		}
6914
	}
6915
6916
	/**
6917
	 * Checks if Akismet is active and working.
6918
	 *
6919
	 * We dropped support for Akismet 3.0 with Jetpack 6.1.1 while introducing a check for an Akismet valid key
6920
	 * that implied usage of methods present since more recent version.
6921
	 * See https://github.com/Automattic/jetpack/pull/9585
6922
	 *
6923
	 * @since  5.1.0
6924
	 *
6925
	 * @return bool True = Akismet available. False = Aksimet not available.
6926
	 */
6927
	public static function is_akismet_active() {
6928
		static $status = null;
6929
6930
		if ( ! is_null( $status ) ) {
6931
			return $status;
6932
		}
6933
6934
		// Check if a modern version of Akismet is active.
6935
		if ( ! method_exists( 'Akismet', 'http_post' ) ) {
6936
			$status = false;
6937
			return $status;
6938
		}
6939
6940
		// Make sure there is a key known to Akismet at all before verifying key.
6941
		$akismet_key = Akismet::get_api_key();
6942
		if ( ! $akismet_key ) {
6943
			$status = false;
6944
			return $status;
6945
		}
6946
6947
		// Possible values: valid, invalid, failure via Akismet. false if no status is cached.
6948
		$akismet_key_state = get_transient( 'jetpack_akismet_key_is_valid' );
6949
6950
		// 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.
6951
		$recheck = ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) && 'valid' !== $akismet_key_state;
6952
		// We cache the result of the Akismet key verification for ten minutes.
6953
		if ( ! $akismet_key_state || $recheck ) {
6954
			$akismet_key_state = Akismet::verify_key( $akismet_key );
6955
			set_transient( 'jetpack_akismet_key_is_valid', $akismet_key_state, 10 * MINUTE_IN_SECONDS );
6956
		}
6957
6958
		$status = 'valid' === $akismet_key_state;
6959
6960
		return $status;
6961
	}
6962
6963
	/**
6964
	 * @deprecated
6965
	 *
6966
	 * @see Automattic\Jetpack\Sync\Modules\Users::is_function_in_backtrace
6967
	 */
6968
	public static function is_function_in_backtrace() {
6969
		_deprecated_function( __METHOD__, 'jetpack-7.6.0' );
6970
	}
6971
6972
	/**
6973
	 * Given a minified path, and a non-minified path, will return
6974
	 * a minified or non-minified file URL based on whether SCRIPT_DEBUG is set and truthy.
6975
	 *
6976
	 * Both `$min_base` and `$non_min_base` are expected to be relative to the
6977
	 * root Jetpack directory.
6978
	 *
6979
	 * @since 5.6.0
6980
	 *
6981
	 * @param string $min_path
6982
	 * @param string $non_min_path
6983
	 * @return string The URL to the file
6984
	 */
6985
	public static function get_file_url_for_environment( $min_path, $non_min_path ) {
6986
		return Assets::get_file_url_for_environment( $min_path, $non_min_path );
6987
	}
6988
6989
	/**
6990
	 * Checks for whether Jetpack Backup & Scan is enabled.
6991
	 * Will return true if the state of Backup & Scan is anything except "unavailable".
6992
	 *
6993
	 * @return bool|int|mixed
6994
	 */
6995
	public static function is_rewind_enabled() {
6996
		if ( ! self::is_active() ) {
6997
			return false;
6998
		}
6999
7000
		$rewind_enabled = get_transient( 'jetpack_rewind_enabled' );
7001
		if ( false === $rewind_enabled ) {
7002
			jetpack_require_lib( 'class.core-rest-api-endpoints' );
7003
			$rewind_data    = (array) Jetpack_Core_Json_Api_Endpoints::rewind_data();
7004
			$rewind_enabled = ( ! is_wp_error( $rewind_data )
7005
				&& ! empty( $rewind_data['state'] )
7006
				&& 'active' === $rewind_data['state'] )
7007
				? 1
7008
				: 0;
7009
7010
			set_transient( 'jetpack_rewind_enabled', $rewind_enabled, 10 * MINUTE_IN_SECONDS );
7011
		}
7012
		return $rewind_enabled;
7013
	}
7014
7015
	/**
7016
	 * Return Calypso environment value; used for developing Jetpack and pairing
7017
	 * it with different Calypso enrionments, such as localhost.
7018
	 *
7019
	 * @since 7.4.0
7020
	 *
7021
	 * @return string Calypso environment
7022
	 */
7023
	public static function get_calypso_env() {
7024
		if ( isset( $_GET['calypso_env'] ) ) {
7025
			return sanitize_key( $_GET['calypso_env'] );
7026
		}
7027
7028
		if ( getenv( 'CALYPSO_ENV' ) ) {
7029
			return sanitize_key( getenv( 'CALYPSO_ENV' ) );
7030
		}
7031
7032
		if ( defined( 'CALYPSO_ENV' ) && CALYPSO_ENV ) {
7033
			return sanitize_key( CALYPSO_ENV );
7034
		}
7035
7036
		return '';
7037
	}
7038
7039
	/**
7040
	 * Checks whether or not TOS has been agreed upon.
7041
	 * Will return true if a user has clicked to register, or is already connected.
7042
	 */
7043
	public static function jetpack_tos_agreed() {
7044
		_deprecated_function( 'Jetpack::jetpack_tos_agreed', 'Jetpack 7.9.0', '\Automattic\Jetpack\Terms_Of_Service->has_agreed' );
7045
7046
		$terms_of_service = new Terms_Of_Service();
7047
		return $terms_of_service->has_agreed();
7048
7049
	}
7050
7051
	/**
7052
	 * Handles activating default modules as well general cleanup for the new connection.
7053
	 *
7054
	 * @param boolean $activate_sso                 Whether to activate the SSO module when activating default modules.
7055
	 * @param boolean $redirect_on_activation_error Whether to redirect on activation error.
7056
	 * @param boolean $send_state_messages          Whether to send state messages.
7057
	 * @return void
7058
	 */
7059
	public static function handle_post_authorization_actions(
7060
		$activate_sso = false,
7061
		$redirect_on_activation_error = false,
7062
		$send_state_messages = true
7063
	) {
7064
		$other_modules = $activate_sso
7065
			? array( 'sso' )
7066
			: array();
7067
7068
		if ( $active_modules = Jetpack_Options::get_option( 'active_modules' ) ) {
7069
			self::delete_active_modules();
7070
7071
			self::activate_default_modules( 999, 1, array_merge( $active_modules, $other_modules ), $redirect_on_activation_error, $send_state_messages );
7072
		} else {
7073
			self::activate_default_modules( false, false, $other_modules, $redirect_on_activation_error, $send_state_messages );
7074
		}
7075
7076
		// Since this is a fresh connection, be sure to clear out IDC options
7077
		Jetpack_IDC::clear_all_idc_options();
7078
		Jetpack_Options::delete_raw_option( 'jetpack_last_connect_url_check' );
7079
7080
		// Start nonce cleaner
7081
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
7082
		wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
7083
7084
		if ( $send_state_messages ) {
7085
			self::state( 'message', 'authorized' );
7086
		}
7087
	}
7088
7089
	/**
7090
	 * Returns a boolean for whether backups UI should be displayed or not.
7091
	 *
7092
	 * @return bool Should backups UI be displayed?
7093
	 */
7094
	public static function show_backups_ui() {
7095
		/**
7096
		 * Whether UI for backups should be displayed.
7097
		 *
7098
		 * @since 6.5.0
7099
		 *
7100
		 * @param bool $show_backups Should UI for backups be displayed? True by default.
7101
		 */
7102
		return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
7103
	}
7104
7105
	/*
7106
	 * Deprecated manage functions
7107
	 */
7108
	function prepare_manage_jetpack_notice() {
7109
		_deprecated_function( __METHOD__, 'jetpack-7.3' );
7110
	}
7111
	function manage_activate_screen() {
7112
		_deprecated_function( __METHOD__, 'jetpack-7.3' );
7113
	}
7114
	function admin_jetpack_manage_notice() {
7115
		_deprecated_function( __METHOD__, 'jetpack-7.3' );
7116
	}
7117
	function opt_out_jetpack_manage_url() {
7118
		_deprecated_function( __METHOD__, 'jetpack-7.3' );
7119
	}
7120
	function opt_in_jetpack_manage_url() {
7121
		_deprecated_function( __METHOD__, 'jetpack-7.3' );
7122
	}
7123
	function opt_in_jetpack_manage_notice() {
7124
		_deprecated_function( __METHOD__, 'jetpack-7.3' );
7125
	}
7126
	function can_display_jetpack_manage_notice() {
7127
		_deprecated_function( __METHOD__, 'jetpack-7.3' );
7128
	}
7129
7130
	/**
7131
	 * Clean leftoveruser meta.
7132
	 *
7133
	 * Delete Jetpack-related user meta when it is no longer needed.
7134
	 *
7135
	 * @since 7.3.0
7136
	 *
7137
	 * @param int $user_id User ID being updated.
7138
	 */
7139
	public static function user_meta_cleanup( $user_id ) {
7140
		$meta_keys = array(
7141
			// AtD removed from Jetpack 7.3
7142
			'AtD_options',
7143
			'AtD_check_when',
7144
			'AtD_guess_lang',
7145
			'AtD_ignored_phrases',
7146
		);
7147
7148
		foreach ( $meta_keys as $meta_key ) {
7149
			if ( get_user_meta( $user_id, $meta_key ) ) {
7150
				delete_user_meta( $user_id, $meta_key );
7151
			}
7152
		}
7153
	}
7154
7155
	/**
7156
	 * Checks if a Jetpack site is both active and not in development.
7157
	 *
7158
	 * This is a DRY function to avoid repeating `Jetpack::is_active && ! Automattic\Jetpack\Status->is_development_mode`.
7159
	 *
7160
	 * @return bool True if Jetpack is active and not in development.
7161
	 */
7162
	public static function is_active_and_not_development_mode() {
7163
		if ( ! self::is_active() || ( new Status() )->is_development_mode() ) {
7164
			return false;
7165
		}
7166
		return true;
7167
	}
7168
}
7169