Completed
Push — update/package-tracks-move-mor... ( d28e64...dfcceb )
by
unknown
127:50 queued 121:01
created

class.jetpack.php (3 issues)

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

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...
7153
			delete_user_meta( $user_id, 'jetpack_tracks_anon_id' );
7154
			if ( ! headers_sent() ) {
7155
				setcookie( 'tk_ai', 'expired', time() - 1000 );
7156
			}
7157
		}
7158
7159
		$wpcom_user_data = Jetpack::get_connected_user_data( $user_id );
7160
		update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'] );
7161
7162
		self::record_user_event( 'wpa_user_linked', array() );
0 ignored issues
show
The method record_user_event() does not seem to exist on object<Jetpack>.

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...
7163
	}
7164
7165
	/* Failed login attempts */
7166
	function track_failed_login_attempts( $login ) {
7167
		require_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
7168
		self::record_user_event(
0 ignored issues
show
The method record_user_event() does not seem to exist on object<Jetpack>.

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...
7169
			'failed_login',
7170
			array(
7171
				'origin_ip' => jetpack_protect_get_ip(),
7172
				'login'     => $login,
7173
			)
7174
		);
7175
	}
7176
7177
	function track_jetpack_usage() {
7178
		if ( ! self::jetpack_tos_agreed() ) {
7179
			return;
7180
		}
7181
7182
		// For tracking stuff via js/ajax
7183
		add_action( 'admin_enqueue_scripts', array( $this->tracking, 'enqueue_tracks_scripts' ) );
7184
7185
		add_action( 'jetpack_activate_module', array( $this, 'track_activate_module' ), 1, 1 );
7186
		add_action( 'jetpack_deactivate_module', array( $this, 'track_deactivate_module' ), 1, 1 );
7187
		add_action( 'jetpack_user_authorized', array( $this, 'track_user_linked' ) );
7188
		add_action( 'wp_login_failed', array( $this, 'track_failed_login_attempts' ) );
7189
	}
7190
7191
7192
}
7193