Completed
Push — update/travis-dont-run-on-docs ( 177fb1...0da07d )
by
unknown
94:00 queued 82:36
created

class.jetpack.php (1 issue)

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
/*
4
Options:
5
jetpack_options (array)
6
	An array of options.
7
	@see Jetpack_Options::get_option_names()
8
9
jetpack_register (string)
10
	Temporary verification secrets.
11
12
jetpack_activated (int)
13
	1: the plugin was activated normally
14
	2: the plugin was activated on this site because of a network-wide activation
15
	3: the plugin was auto-installed
16
	4: the plugin was manually disconnected (but is still installed)
17
18
jetpack_active_modules (array)
19
	Array of active module slugs.
20
21
jetpack_do_activate (bool)
22
	Flag for "activating" the plugin on sites where the activation hook never fired (auto-installs)
23
*/
24
25
require_once( JETPACK__PLUGIN_DIR . '_inc/lib/class.media.php' );
26
27
class Jetpack {
28
	public $xmlrpc_server = null;
29
30
	private $xmlrpc_verification = null;
31
	private $rest_authentication_status = null;
32
33
	public $HTTP_RAW_POST_DATA = null; // copy of $GLOBALS['HTTP_RAW_POST_DATA']
34
35
	/**
36
	 * @var array The handles of styles that are concatenated into jetpack.css
37
	 */
38
	public $concatenated_style_handles = array(
39
		'jetpack-carousel',
40
		'grunion.css',
41
		'the-neverending-homepage',
42
		'jetpack_likes',
43
		'jetpack_related-posts',
44
		'sharedaddy',
45
		'jetpack-slideshow',
46
		'presentations',
47
		'jetpack-subscriptions',
48
		'jetpack-responsive-videos-style',
49
		'jetpack-social-menu',
50
		'tiled-gallery',
51
		'jetpack_display_posts_widget',
52
		'gravatar-profile-widget',
53
		'goodreads-widget',
54
		'jetpack_social_media_icons_widget',
55
		'jetpack-top-posts-widget',
56
		'jetpack_image_widget',
57
		'jetpack-my-community-widget',
58
		'wordads',
59
		'eu-cookie-law-style',
60
		'flickr-widget-style',
61
		'jetpack-search-widget',
62
		'jetpack-simple-payments-widget-style',
63
	);
64
65
	/**
66
	 * Contains all assets that have had their URL rewritten to minified versions.
67
	 *
68
	 * @var array
69
	 */
70
	static $min_assets = array();
71
72
	public $plugins_to_deactivate = array(
73
		'stats'               => array( 'stats/stats.php', 'WordPress.com Stats' ),
74
		'shortlinks'          => array( 'stats/stats.php', 'WordPress.com Stats' ),
75
		'sharedaddy'          => array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
76
		'twitter-widget'      => array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
77
		'after-the-deadline'  => array( 'after-the-deadline/after-the-deadline.php', 'After The Deadline' ),
78
		'contact-form'        => array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
79
		'contact-form'        => array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
80
		'custom-css'          => array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
81
		'random-redirect'     => array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
82
		'videopress'          => array( 'video/video.php', 'VideoPress' ),
83
		'widget-visibility'   => array( 'jetpack-widget-visibility/widget-visibility.php', 'Jetpack Widget Visibility' ),
84
		'widget-visibility'   => array( 'widget-visibility-without-jetpack/widget-visibility-without-jetpack.php', 'Widget Visibility Without Jetpack' ),
85
		'sharedaddy'          => array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
86
		'gravatar-hovercards' => array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
87
		'latex'               => array( 'wp-latex/wp-latex.php', 'WP LaTeX' )
88
	);
89
90
	static $capability_translations = array(
91
		'administrator' => 'manage_options',
92
		'editor'        => 'edit_others_posts',
93
		'author'        => 'publish_posts',
94
		'contributor'   => 'edit_posts',
95
		'subscriber'    => 'read',
96
	);
97
98
	/**
99
	 * Map of modules that have conflicts with plugins and should not be auto-activated
100
	 * if the plugins are active.  Used by filter_default_modules
101
	 *
102
	 * Plugin Authors: If you'd like to prevent a single module from auto-activating,
103
	 * change `module-slug` and add this to your plugin:
104
	 *
105
	 * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
106
	 * function my_jetpack_get_default_modules( $modules ) {
107
	 *     return array_diff( $modules, array( 'module-slug' ) );
108
	 * }
109
	 *
110
	 * @var array
111
	 */
112
	private $conflicting_plugins = array(
113
		'comments'          => array(
114
			'Intense Debate'                       => 'intensedebate/intensedebate.php',
115
			'Disqus'                               => 'disqus-comment-system/disqus.php',
116
			'Livefyre'                             => 'livefyre-comments/livefyre.php',
117
			'Comments Evolved for WordPress'       => 'gplus-comments/comments-evolved.php',
118
			'Google+ Comments'                     => 'google-plus-comments/google-plus-comments.php',
119
			'WP-SpamShield Anti-Spam'              => 'wp-spamshield/wp-spamshield.php',
120
		),
121
		'comment-likes' => array(
122
			'Epoch'                                => 'epoch/plugincore.php',
123
		),
124
		'contact-form'      => array(
125
			'Contact Form 7'                       => 'contact-form-7/wp-contact-form-7.php',
126
			'Gravity Forms'                        => 'gravityforms/gravityforms.php',
127
			'Contact Form Plugin'                  => 'contact-form-plugin/contact_form.php',
128
			'Easy Contact Forms'                   => 'easy-contact-forms/easy-contact-forms.php',
129
			'Fast Secure Contact Form'             => 'si-contact-form/si-contact-form.php',
130
			'Ninja Forms'                          => 'ninja-forms/ninja-forms.php',
131
		),
132
		'minileven'         => array(
133
			'WPtouch'                              => 'wptouch/wptouch.php',
134
		),
135
		'latex'             => array(
136
			'LaTeX for WordPress'                  => 'latex/latex.php',
137
			'Youngwhans Simple Latex'              => 'youngwhans-simple-latex/yw-latex.php',
138
			'Easy WP LaTeX'                        => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
139
			'MathJax-LaTeX'                        => 'mathjax-latex/mathjax-latex.php',
140
			'Enable Latex'                         => 'enable-latex/enable-latex.php',
141
			'WP QuickLaTeX'                        => 'wp-quicklatex/wp-quicklatex.php',
142
		),
143
		'protect'           => array(
144
			'Limit Login Attempts'                 => 'limit-login-attempts/limit-login-attempts.php',
145
			'Captcha'                              => 'captcha/captcha.php',
146
			'Brute Force Login Protection'         => 'brute-force-login-protection/brute-force-login-protection.php',
147
			'Login Security Solution'              => 'login-security-solution/login-security-solution.php',
148
			'WPSecureOps Brute Force Protect'      => 'wpsecureops-bruteforce-protect/wpsecureops-bruteforce-protect.php',
149
			'BulletProof Security'                 => 'bulletproof-security/bulletproof-security.php',
150
			'SiteGuard WP Plugin'                  => 'siteguard/siteguard.php',
151
			'Security-protection'                  => 'security-protection/security-protection.php',
152
			'Login Security'                       => 'login-security/login-security.php',
153
			'Botnet Attack Blocker'                => 'botnet-attack-blocker/botnet-attack-blocker.php',
154
			'Wordfence Security'                   => 'wordfence/wordfence.php',
155
			'All In One WP Security & Firewall'    => 'all-in-one-wp-security-and-firewall/wp-security.php',
156
			'iThemes Security'                     => 'better-wp-security/better-wp-security.php',
157
		),
158
		'random-redirect'   => array(
159
			'Random Redirect 2'                    => 'random-redirect-2/random-redirect.php',
160
		),
161
		'related-posts'     => array(
162
			'YARPP'                                => 'yet-another-related-posts-plugin/yarpp.php',
163
			'WordPress Related Posts'              => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
164
			'nrelate Related Content'              => 'nrelate-related-content/nrelate-related.php',
165
			'Contextual Related Posts'             => 'contextual-related-posts/contextual-related-posts.php',
166
			'Related Posts for WordPress'          => 'microkids-related-posts/microkids-related-posts.php',
167
			'outbrain'                             => 'outbrain/outbrain.php',
168
			'Shareaholic'                          => 'shareaholic/shareaholic.php',
169
			'Sexybookmarks'                        => 'sexybookmarks/shareaholic.php',
170
		),
171
		'sharedaddy'        => array(
172
			'AddThis'                              => 'addthis/addthis_social_widget.php',
173
			'Add To Any'                           => 'add-to-any/add-to-any.php',
174
			'ShareThis'                            => 'share-this/sharethis.php',
175
			'Shareaholic'                          => 'shareaholic/shareaholic.php',
176
		),
177
		'seo-tools' => array(
178
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
179
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
180
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
181
			'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
182
		),
183
		'verification-tools' => array(
184
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
185
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
186
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
187
			'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
188
		),
189
		'widget-visibility' => array(
190
			'Widget Logic'                         => 'widget-logic/widget_logic.php',
191
			'Dynamic Widgets'                      => 'dynamic-widgets/dynamic-widgets.php',
192
		),
193
		'sitemaps' => array(
194
			'Google XML Sitemaps'                  => 'google-sitemap-generator/sitemap.php',
195
			'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
196
			'Google XML Sitemaps for qTranslate'   => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php',
197
			'XML Sitemap & Google News feeds'      => 'xml-sitemap-feed/xml-sitemap.php',
198
			'Google Sitemap by BestWebSoft'        => 'google-sitemap-plugin/google-sitemap-plugin.php',
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
			'Sitemap'                              => 'sitemap/sitemap.php',
204
			'Simple Wp Sitemap'                    => 'simple-wp-sitemap/simple-wp-sitemap.php',
205
			'Simple Sitemap'                       => 'simple-sitemap/simple-sitemap.php',
206
			'XML Sitemaps'                         => 'xml-sitemaps/xml-sitemaps.php',
207
			'MSM Sitemaps'                         => 'msm-sitemap/msm-sitemap.php',
208
		),
209
		'lazy-images' => array(
210
			'Lazy Load'              => 'lazy-load/lazy-load.php',
211
			'BJ Lazy Load'           => 'bj-lazy-load/bj-lazy-load.php',
212
			'Lazy Load by WP Rocket' => 'rocket-lazy-load/rocket-lazy-load.php',
213
		),
214
	);
215
216
	/**
217
	 * Plugins for which we turn off our Facebook OG Tags implementation.
218
	 *
219
	 * Note: All in One SEO Pack, All in one SEO Pack Pro, WordPress SEO by Yoast, and WordPress SEO Premium by Yoast automatically deactivate
220
	 * Jetpack's Open Graph tags via filter when their Social Meta modules are active.
221
	 *
222
	 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
223
	 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
224
	 */
225
	private $open_graph_conflicting_plugins = array(
226
		'2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
227
		                                                         // 2 Click Social Media Buttons
228
		'add-link-to-facebook/add-link-to-facebook.php',         // Add Link to Facebook
229
		'add-meta-tags/add-meta-tags.php',                       // Add Meta Tags
230
		'autodescription/autodescription.php',                   // The SEO Framework
231
		'easy-facebook-share-thumbnails/esft.php',               // Easy Facebook Share Thumbnail
232
		'heateor-open-graph-meta-tags/heateor-open-graph-meta-tags.php',
233
		                                                         // Open Graph Meta Tags by Heateor
234
		'facebook/facebook.php',                                 // Facebook (official plugin)
235
		'facebook-awd/AWD_facebook.php',                         // Facebook AWD All in one
236
		'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
237
		                                                         // Facebook Featured Image & OG Meta Tags
238
		'facebook-meta-tags/facebook-metatags.php',              // Facebook Meta Tags
239
		'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
240
		                                                         // Facebook Open Graph Meta Tags for WordPress
241
		'facebook-revised-open-graph-meta-tag/index.php',        // Facebook Revised Open Graph Meta Tag
242
		'facebook-thumb-fixer/_facebook-thumb-fixer.php',        // Facebook Thumb Fixer
243
		'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
244
		                                                         // Fedmich's Facebook Open Graph Meta
245
		'network-publisher/networkpub.php',                      // Network Publisher
246
		'nextgen-facebook/nextgen-facebook.php',                 // NextGEN Facebook OG
247
		'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php',
248
		                                                         // NextScripts SNAP
249
		'og-tags/og-tags.php',                                   // OG Tags
250
		'opengraph/opengraph.php',                               // Open Graph
251
		'open-graph-protocol-framework/open-graph-protocol-framework.php',
252
		                                                         // Open Graph Protocol Framework
253
		'seo-facebook-comments/seofacebook.php',                 // SEO Facebook Comments
254
		'seo-ultimate/seo-ultimate.php',                         // SEO Ultimate
255
		'sexybookmarks/sexy-bookmarks.php',                      // Shareaholic
256
		'shareaholic/sexy-bookmarks.php',                        // Shareaholic
257
		'sharepress/sharepress.php',                             // SharePress
258
		'simple-facebook-connect/sfc.php',                       // Simple Facebook Connect
259
		'social-discussions/social-discussions.php',             // Social Discussions
260
		'social-sharing-toolkit/social_sharing_toolkit.php',     // Social Sharing Toolkit
261
		'socialize/socialize.php',                               // Socialize
262
		'squirrly-seo/squirrly.php',                             // SEO by SQUIRRLY™
263
		'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
264
		                                                         // Tweet, Like, Google +1 and Share
265
		'wordbooker/wordbooker.php',                             // Wordbooker
266
		'wpsso/wpsso.php',                                       // WordPress Social Sharing Optimization
267
		'wp-caregiver/wp-caregiver.php',                         // WP Caregiver
268
		'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
269
		                                                         // WP Facebook Like Send & Open Graph Meta
270
		'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',   // WP Facebook Open Graph protocol
271
		'wp-ogp/wp-ogp.php',                                     // WP-OGP
272
		'zoltonorg-social-plugin/zosp.php',                      // Zolton.org Social Plugin
273
		'wp-fb-share-like-button/wp_fb_share-like_widget.php',   // WP Facebook Like Button
274
		'open-graph-metabox/open-graph-metabox.php'              // Open Graph Metabox
275
	);
276
277
	/**
278
	 * Plugins for which we turn off our Twitter Cards Tags implementation.
279
	 */
280
	private $twitter_cards_conflicting_plugins = array(
281
	//	'twitter/twitter.php',                       // The official one handles this on its own.
282
	//	                                             // https://github.com/twitter/wordpress/blob/master/src/Twitter/WordPress/Cards/Compatibility.php
283
		'eewee-twitter-card/index.php',              // Eewee Twitter Card
284
		'ig-twitter-cards/ig-twitter-cards.php',     // IG:Twitter Cards
285
		'jm-twitter-cards/jm-twitter-cards.php',     // JM Twitter Cards
286
		'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',
287
		                                             // Pure Web Brilliant's Social Graph Twitter Cards Extension
288
		'twitter-cards/twitter-cards.php',           // Twitter Cards
289
		'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta
290
		'wp-twitter-cards/twitter_cards.php',        // WP Twitter Cards
291
	);
292
293
	/**
294
	 * Message to display in admin_notice
295
	 * @var string
296
	 */
297
	public $message = '';
298
299
	/**
300
	 * Error to display in admin_notice
301
	 * @var string
302
	 */
303
	public $error = '';
304
305
	/**
306
	 * Modules that need more privacy description.
307
	 * @var string
308
	 */
309
	public $privacy_checks = '';
310
311
	/**
312
	 * Stats to record once the page loads
313
	 *
314
	 * @var array
315
	 */
316
	public $stats = array();
317
318
	/**
319
	 * Jetpack_Sync object
320
	 */
321
	public $sync;
322
323
	/**
324
	 * Verified data for JSON authorization request
325
	 */
326
	public $json_api_authorization_request = array();
327
328
	/**
329
	 * @var string Transient key used to prevent multiple simultaneous plugin upgrades
330
	 */
331
	public static $plugin_upgrade_lock_key = 'jetpack_upgrade_lock';
332
333
	/**
334
	 * Holds the singleton instance of this class
335
	 * @since 2.3.3
336
	 * @var Jetpack
337
	 */
338
	static $instance = false;
339
340
	/**
341
	 * Singleton
342
	 * @static
343
	 */
344
	public static function init() {
345
		if ( ! self::$instance ) {
346
			self::$instance = new Jetpack;
347
348
			self::$instance->plugin_upgrade();
349
		}
350
351
		return self::$instance;
352
	}
353
354
	/**
355
	 * Must never be called statically
356
	 */
357
	function plugin_upgrade() {
358
		if ( Jetpack::is_active() ) {
359
			list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
360
			if ( JETPACK__VERSION != $version ) {
361
				// Prevent multiple upgrades at once - only a single process should trigger
362
				// an upgrade to avoid stampedes
363
				if ( false !== get_transient( self::$plugin_upgrade_lock_key ) ) {
364
					return;
365
				}
366
367
				// Set a short lock to prevent multiple instances of the upgrade
368
				set_transient( self::$plugin_upgrade_lock_key, 1, 10 );
369
370
				// check which active modules actually exist and remove others from active_modules list
371
				$unfiltered_modules = Jetpack::get_active_modules();
372
				$modules = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) );
373
				if ( array_diff( $unfiltered_modules, $modules ) ) {
374
					Jetpack::update_active_modules( $modules );
375
				}
376
377
				add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
378
379
				// Upgrade to 4.3.0
380
				if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
381
					Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
382
				}
383
384
				// Make sure Markdown for posts gets turned back on
385
				if ( ! get_option( 'wpcom_publish_posts_with_markdown' ) ) {
386
					update_option( 'wpcom_publish_posts_with_markdown', true );
387
				}
388
389
				if ( did_action( 'wp_loaded' ) ) {
390
					self::upgrade_on_load();
391
				} else {
392
					add_action(
393
						'wp_loaded',
394
						array( __CLASS__, 'upgrade_on_load' )
395
					);
396
				}
397
			}
398
		}
399
	}
400
401
	/**
402
	 * Runs upgrade routines that need to have modules loaded.
403
	 */
404
	static function upgrade_on_load() {
405
406
		// Not attempting any upgrades if jetpack_modules_loaded did not fire.
407
		// This can happen in case Jetpack has been just upgraded and is
408
		// being initialized late during the page load. In this case we wait
409
		// until the next proper admin page load with Jetpack active.
410
		if ( ! did_action( 'jetpack_modules_loaded' ) ) {
411
			delete_transient( self::$plugin_upgrade_lock_key );
412
413
			return;
414
		}
415
416
		Jetpack::maybe_set_version_option();
417
418
		if ( class_exists( 'Jetpack_Widget_Conditions' ) ) {
419
			Jetpack_Widget_Conditions::migrate_post_type_rules();
420
		}
421
422
		if (
423
			class_exists( 'Jetpack_Sitemap_Manager' )
424
			&& version_compare( JETPACK__VERSION, '5.3', '>=' )
425
		) {
426
			do_action( 'jetpack_sitemaps_purge_data' );
427
		}
428
429
		delete_transient( self::$plugin_upgrade_lock_key );
430
	}
431
432
	static function activate_manage( ) {
433
		if ( did_action( 'init' ) || current_filter() == 'init' ) {
434
			self::activate_module( 'manage', false, false );
435
		} else if ( !  has_action( 'init' , array( __CLASS__, 'activate_manage' ) ) ) {
436
			add_action( 'init', array( __CLASS__, 'activate_manage' ) );
437
		}
438
	}
439
440
	static function update_active_modules( $modules ) {
441
		$current_modules = Jetpack_Options::get_option( 'active_modules', array() );
442
443
		$success = Jetpack_Options::update_option( 'active_modules', array_unique( $modules ) );
444
445
		if ( is_array( $modules ) && is_array( $current_modules ) ) {
446
			$new_active_modules = array_diff( $modules, $current_modules );
447
			foreach( $new_active_modules as $module ) {
448
				/**
449
				 * Fires when a specific module is activated.
450
				 *
451
				 * @since 1.9.0
452
				 *
453
				 * @param string $module Module slug.
454
				 * @param boolean $success whether the module was activated. @since 4.2
455
				 */
456
				do_action( 'jetpack_activate_module', $module, $success );
457
458
				/**
459
				 * Fires when a module is activated.
460
				 * The dynamic part of the filter, $module, is the module slug.
461
				 *
462
				 * @since 1.9.0
463
				 *
464
				 * @param string $module Module slug.
465
				 */
466
				do_action( "jetpack_activate_module_$module", $module );
467
			}
468
469
			$new_deactive_modules = array_diff( $current_modules, $modules );
470
			foreach( $new_deactive_modules as $module ) {
471
				/**
472
				 * Fired after a module has been deactivated.
473
				 *
474
				 * @since 4.2.0
475
				 *
476
				 * @param string $module Module slug.
477
				 * @param boolean $success whether the module was deactivated.
478
				 */
479
				do_action( 'jetpack_deactivate_module', $module, $success );
480
				/**
481
				 * Fires when a module is deactivated.
482
				 * The dynamic part of the filter, $module, is the module slug.
483
				 *
484
				 * @since 1.9.0
485
				 *
486
				 * @param string $module Module slug.
487
				 */
488
				do_action( "jetpack_deactivate_module_$module", $module );
489
			}
490
		}
491
492
		return $success;
493
	}
494
495
	static function delete_active_modules() {
496
		self::update_active_modules( array() );
497
	}
498
499
	/**
500
	 * Constructor.  Initializes WordPress hooks
501
	 */
502
	private function __construct() {
503
		/*
504
		 * Check for and alert any deprecated hooks
505
		 */
506
		add_action( 'init', array( $this, 'deprecated_hooks' ) );
507
508
		/*
509
		 * Enable enhanced handling of previewing sites in Calypso
510
		 */
511
		if ( Jetpack::is_active() ) {
512
			require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
513
			add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
514
		}
515
516
		/*
517
		 * Load things that should only be in Network Admin.
518
		 *
519
		 * For now blow away everything else until a more full
520
		 * understanding of what is needed at the network level is
521
		 * available
522
		 */
523
		if( is_multisite() ) {
524
			Jetpack_Network::init();
525
		}
526
527
		add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
528
529
		// Unlink user before deleting the user from .com
530
		add_action( 'deleted_user', array( $this, 'unlink_user' ), 10, 1 );
531
		add_action( 'remove_user_from_blog', array( $this, 'unlink_user' ), 10, 1 );
532
533
		if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && isset( $_GET['for'] ) && 'jetpack' == $_GET['for'] ) {
534
			@ini_set( 'display_errors', false ); // Display errors can cause the XML to be not well formed.
535
536
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
537
			$this->xmlrpc_server = new Jetpack_XMLRPC_Server();
538
539
			$this->require_jetpack_authentication();
540
541
			if ( Jetpack::is_active() ) {
542
				// Hack to preserve $HTTP_RAW_POST_DATA
543
				add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
544
545
				$signed = $this->verify_xml_rpc_signature();
546 View Code Duplication
				if ( $signed && ! is_wp_error( $signed ) ) {
547
					// The actual API methods.
548
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'xmlrpc_methods' ) );
549
				} else {
550
					// The jetpack.authorize method should be available for unauthenticated users on a site with an
551
					// active Jetpack connection, so that additional users can link their account.
552
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'authorize_xmlrpc_methods' ) );
553
				}
554 View Code Duplication
			} else {
555
				// The bootstrap API methods.
556
				add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'bootstrap_xmlrpc_methods' ) );
557
				$signed = $this->verify_xml_rpc_signature();
558
				if ( $signed && ! is_wp_error( $signed ) ) {
559
					// the jetpack Provision method is available for blog-token-signed requests
560
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'provision_xmlrpc_methods' ) );
561
				}
562
			}
563
564
			// Now that no one can authenticate, and we're whitelisting all XML-RPC methods, force enable_xmlrpc on.
565
			add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
566
		} elseif (
567
			is_admin() &&
568
			isset( $_POST['action'] ) && (
569
				'jetpack_upload_file' == $_POST['action'] ||
570
				'jetpack_update_file' == $_POST['action']
571
			)
572
		) {
573
			$this->require_jetpack_authentication();
574
			$this->add_remote_request_handlers();
575
		} else {
576
			if ( Jetpack::is_active() ) {
577
				add_action( 'login_form_jetpack_json_api_authorization', array( &$this, 'login_form_json_api_authorization' ) );
578
				add_filter( 'xmlrpc_methods', array( $this, 'public_xmlrpc_methods' ) );
579
			}
580
		}
581
582
		if ( Jetpack::is_active() ) {
583
			Jetpack_Heartbeat::init();
584
			if ( Jetpack::is_module_active( 'stats' ) && Jetpack::is_module_active( 'search' ) ) {
585
				require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
586
				Jetpack_Search_Performance_Logger::init();
587
			}
588
		}
589
590
		add_filter( 'determine_current_user', array( $this, 'wp_rest_authenticate' ) );
591
		add_filter( 'rest_authentication_errors', array( $this, 'wp_rest_authentication_errors' ) );
592
593
		add_action( 'jetpack_clean_nonces', array( 'Jetpack', 'clean_nonces' ) );
594
		if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
595
			wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
596
		}
597
598
		add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ) );
599
600
		add_action( 'admin_init', array( $this, 'admin_init' ) );
601
		add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
602
603
		add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
604
605
		add_action( 'wp_dashboard_setup', array( $this, 'wp_dashboard_setup' ) );
606
		// Filter the dashboard meta box order to swap the new one in in place of the old one.
607
		add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) );
608
609
		// returns HTTPS support status
610
		add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) );
611
612
		// If any module option is updated before Jump Start is dismissed, hide Jump Start.
613
		add_action( 'update_option', array( $this, 'jumpstart_has_updated_module_option' ) );
614
615
		// JITM AJAX callback function
616
		add_action( 'wp_ajax_jitm_ajax',  array( $this, 'jetpack_jitm_ajax_callback' ) );
617
618
		// Universal ajax callback for all tracking events triggered via js
619
		add_action( 'wp_ajax_jetpack_tracks', array( $this, 'jetpack_admin_ajax_tracks_callback' ) );
620
621
		add_action( 'wp_ajax_jetpack_connection_banner', array( $this, 'jetpack_connection_banner_callback' ) );
622
623
		add_action( 'wp_loaded', array( $this, 'register_assets' ) );
624
		add_action( 'wp_enqueue_scripts', array( $this, 'devicepx' ) );
625
		add_action( 'customize_controls_enqueue_scripts', array( $this, 'devicepx' ) );
626
		add_action( 'admin_enqueue_scripts', array( $this, 'devicepx' ) );
627
628
		// gutenberg locale
629
		add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_gutenberg_locale' ) );
630
631
		add_action( 'plugins_loaded', array( $this, 'extra_oembed_providers' ), 100 );
632
633
		/**
634
		 * These actions run checks to load additional files.
635
		 * They check for external files or plugins, so they need to run as late as possible.
636
		 */
637
		add_action( 'wp_head', array( $this, 'check_open_graph' ),       1 );
638
		add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ),     999 );
639
		add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
640
641
		add_filter( 'plugins_url',      array( 'Jetpack', 'maybe_min_asset' ),     1, 3 );
642
		add_action( 'style_loader_src', array( 'Jetpack', 'set_suffix_on_min' ), 10, 2  );
643
		add_filter( 'style_loader_tag', array( 'Jetpack', 'maybe_inline_style' ), 10, 2 );
644
645
		add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 4 );
646
647
		add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
648
		add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
649
650
		// A filter to control all just in time messages
651
		add_filter( 'jetpack_just_in_time_msgs', '__return_true', 9 );
652
		add_filter( 'jetpack_just_in_time_msg_cache', '__return_true', 9);
653
654
		// If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
655
		// We should make sure to only do this for front end links.
656
		if ( Jetpack::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
657
			add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
658
			add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
659
660
			//we'll override wp_notify_postauthor and wp_notify_moderator pluggable functions
661
			//so they point moderation links on emails to Calypso
662
			jetpack_require_lib( 'functions.wp-notify' );
663
		}
664
665
		// Update the Jetpack plan from API on heartbeats
666
		add_action( 'jetpack_heartbeat', array( $this, 'refresh_active_plan_from_wpcom' ) );
667
668
		/**
669
		 * This is the hack to concatenate all css files into one.
670
		 * For description and reasoning see the implode_frontend_css method
671
		 *
672
		 * Super late priority so we catch all the registered styles
673
		 */
674
		if( !is_admin() ) {
675
			add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first
676
			add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`
677
		}
678
679
		/**
680
		 * These are sync actions that we need to keep track of for jitms
681
		 */
682
		add_filter( 'jetpack_sync_before_send_updated_option', array( $this, 'jetpack_track_last_sync_callback' ), 99 );
683
684
		// Actually push the stats on shutdown.
685
		if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
686
			add_action( 'shutdown', array( $this, 'push_stats' ) );
687
		}
688
	}
689
690
	function point_edit_post_links_to_calypso( $default_url, $post_id ) {
691
		$post = get_post( $post_id );
692
693
		if ( empty( $post ) ) {
694
			return $default_url;
695
		}
696
697
		$post_type = $post->post_type;
698
699
		// Mapping the allowed CPTs on WordPress.com to corresponding paths in Calypso.
700
		// https://en.support.wordpress.com/custom-post-types/
701
		$allowed_post_types = array(
702
			'post' => 'post',
703
			'page' => 'page',
704
			'jetpack-portfolio' => 'edit/jetpack-portfolio',
705
			'jetpack-testimonial' => 'edit/jetpack-testimonial',
706
		);
707
708
		if ( ! in_array( $post_type, array_keys( $allowed_post_types ) ) ) {
709
			return $default_url;
710
		}
711
712
		$path_prefix = $allowed_post_types[ $post_type ];
713
714
		$site_slug  = Jetpack::build_raw_urls( get_home_url() );
715
716
		return esc_url( sprintf( 'https://wordpress.com/%s/%s/%d', $path_prefix, $site_slug, $post_id ) );
717
	}
718
719
	function point_edit_comment_links_to_calypso( $url ) {
720
		// Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
721
		wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
722
		return esc_url( sprintf( 'https://wordpress.com/comment/%s/%d',
723
			Jetpack::build_raw_urls( get_home_url() ),
724
			$query_args['amp;c']
725
		) );
726
	}
727
728
	function jetpack_track_last_sync_callback( $params ) {
729
		/**
730
		 * Filter to turn off jitm caching
731
		 *
732
		 * @since 5.4.0
733
		 *
734
		 * @param bool false Whether to cache just in time messages
735
		 */
736
		if ( ! apply_filters( 'jetpack_just_in_time_msg_cache', false ) ) {
737
			return $params;
738
		}
739
740
		if ( is_array( $params ) && isset( $params[0] ) ) {
741
			$option = $params[0];
742
			if ( 'active_plugins' === $option ) {
743
				// use the cache if we can, but not terribly important if it gets evicted
744
				set_transient( 'jetpack_last_plugin_sync', time(), HOUR_IN_SECONDS );
745
			}
746
		}
747
748
		return $params;
749
	}
750
751
	function jetpack_connection_banner_callback() {
752
		check_ajax_referer( 'jp-connection-banner-nonce', 'nonce' );
753
754
		if ( isset( $_REQUEST['dismissBanner'] ) ) {
755
			Jetpack_Options::update_option( 'dismissed_connection_banner', 1 );
756
			wp_send_json_success();
757
		}
758
759
		wp_die();
760
	}
761
762
	function jetpack_admin_ajax_tracks_callback() {
763
		// Check for nonce
764
		if ( ! isset( $_REQUEST['tracksNonce'] ) || ! wp_verify_nonce( $_REQUEST['tracksNonce'], 'jp-tracks-ajax-nonce' ) ) {
765
			wp_die( 'Permissions check failed.' );
766
		}
767
768
		if ( ! isset( $_REQUEST['tracksEventName'] ) || ! isset( $_REQUEST['tracksEventType'] )  ) {
769
			wp_die( 'No valid event name or type.' );
770
		}
771
772
		$tracks_data = array();
773
		if ( 'click' === $_REQUEST['tracksEventType'] && isset( $_REQUEST['tracksEventProp'] ) ) {
774
			if ( is_array( $_REQUEST['tracksEventProp'] ) ) {
775
				$tracks_data = $_REQUEST['tracksEventProp'];
776
			} else {
777
				$tracks_data = array( 'clicked' => $_REQUEST['tracksEventProp'] );
778
			}
779
		}
780
781
		JetpackTracking::record_user_event( $_REQUEST['tracksEventName'], $tracks_data );
782
		wp_send_json_success();
783
		wp_die();
784
	}
785
786
	/**
787
	 * The callback for the JITM ajax requests.
788
	 */
789
	function jetpack_jitm_ajax_callback() {
790
		// Check for nonce
791
		if ( ! isset( $_REQUEST['jitmNonce'] ) || ! wp_verify_nonce( $_REQUEST['jitmNonce'], 'jetpack-jitm-nonce' ) ) {
792
			wp_die( 'Module activation failed due to lack of appropriate permissions' );
793
		}
794
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'activate' == $_REQUEST['jitmActionToTake'] ) {
795
			$module_slug = $_REQUEST['jitmModule'];
796
			Jetpack::log( 'activate', $module_slug );
797
			Jetpack::activate_module( $module_slug, false, false );
798
			Jetpack::state( 'message', 'no_message' );
799
800
			//A Jetpack module is being activated through a JITM, track it
801
			$this->stat( 'jitm', $module_slug.'-activated-' . JETPACK__VERSION );
802
			$this->do_stats( 'server_side' );
803
804
			wp_send_json_success();
805
		}
806
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'dismiss' == $_REQUEST['jitmActionToTake'] ) {
807
			// get the hide_jitm options array
808
			$jetpack_hide_jitm = Jetpack_Options::get_option( 'hide_jitm' );
809
			$module_slug = $_REQUEST['jitmModule'];
810
811
			if( ! $jetpack_hide_jitm ) {
812
				$jetpack_hide_jitm = array(
813
					$module_slug => 'hide'
814
				);
815
			} else {
816
				$jetpack_hide_jitm[$module_slug] = 'hide';
817
			}
818
819
			Jetpack_Options::update_option( 'hide_jitm', $jetpack_hide_jitm );
820
821
			//jitm is being dismissed forever, track it
822
			$this->stat( 'jitm', $module_slug.'-dismissed-' . JETPACK__VERSION );
823
			$this->do_stats( 'server_side' );
824
825
			wp_send_json_success();
826
		}
827 View Code Duplication
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'launch' == $_REQUEST['jitmActionToTake'] ) {
828
			$module_slug = $_REQUEST['jitmModule'];
829
830
			// User went to WordPress.com, track this
831
			$this->stat( 'jitm', $module_slug.'-wordpress-tools-' . JETPACK__VERSION );
832
			$this->do_stats( 'server_side' );
833
834
			wp_send_json_success();
835
		}
836 View Code Duplication
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'viewed' == $_REQUEST['jitmActionToTake'] ) {
837
			$track = $_REQUEST['jitmModule'];
838
839
			// User is viewing JITM, track it.
840
			$this->stat( 'jitm', $track . '-viewed-' . JETPACK__VERSION );
841
			$this->do_stats( 'server_side' );
842
843
			wp_send_json_success();
844
		}
845
	}
846
847
	/**
848
	 * If there are any stats that need to be pushed, but haven't been, push them now.
849
	 */
850
	function push_stats() {
851
		if ( ! empty( $this->stats ) ) {
852
			$this->do_stats( 'server_side' );
853
		}
854
	}
855
856
	function jetpack_custom_caps( $caps, $cap, $user_id, $args ) {
857
		switch( $cap ) {
858
			case 'jetpack_connect' :
859
			case 'jetpack_reconnect' :
860
				if ( Jetpack::is_development_mode() ) {
861
					$caps = array( 'do_not_allow' );
862
					break;
863
				}
864
				/**
865
				 * Pass through. If it's not development mode, these should match disconnect.
866
				 * Let users disconnect if it's development mode, just in case things glitch.
867
				 */
868
			case 'jetpack_disconnect' :
869
				/**
870
				 * In multisite, can individual site admins manage their own connection?
871
				 *
872
				 * Ideally, this should be extracted out to a separate filter in the Jetpack_Network class.
873
				 */
874
				if ( is_multisite() && ! is_super_admin() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
875
					if ( ! Jetpack_Network::init()->get_option( 'sub-site-connection-override' ) ) {
876
						/**
877
						 * We need to update the option name -- it's terribly unclear which
878
						 * direction the override goes.
879
						 *
880
						 * @todo: Update the option name to `sub-sites-can-manage-own-connections`
881
						 */
882
						$caps = array( 'do_not_allow' );
883
						break;
884
					}
885
				}
886
887
				$caps = array( 'manage_options' );
888
				break;
889
			case 'jetpack_manage_modules' :
890
			case 'jetpack_activate_modules' :
891
			case 'jetpack_deactivate_modules' :
892
				$caps = array( 'manage_options' );
893
				break;
894
			case 'jetpack_configure_modules' :
895
				$caps = array( 'manage_options' );
896
				break;
897
			case 'jetpack_network_admin_page':
898
			case 'jetpack_network_settings_page':
899
				$caps = array( 'manage_network_plugins' );
900
				break;
901
			case 'jetpack_network_sites_page':
902
				$caps = array( 'manage_sites' );
903
				break;
904
			case 'jetpack_admin_page' :
905
				if ( Jetpack::is_development_mode() ) {
906
					$caps = array( 'manage_options' );
907
					break;
908
				} else {
909
					$caps = array( 'read' );
910
				}
911
				break;
912
			case 'jetpack_connect_user' :
913
				if ( Jetpack::is_development_mode() ) {
914
					$caps = array( 'do_not_allow' );
915
					break;
916
				}
917
				$caps = array( 'read' );
918
				break;
919
		}
920
		return $caps;
921
	}
922
923
	function require_jetpack_authentication() {
924
		// Don't let anyone authenticate
925
		$_COOKIE = array();
926
		remove_all_filters( 'authenticate' );
927
		remove_all_actions( 'wp_login_failed' );
928
929
		if ( Jetpack::is_active() ) {
930
			// Allow Jetpack authentication
931
			add_filter( 'authenticate', array( $this, 'authenticate_jetpack' ), 10, 3 );
932
		}
933
	}
934
935
	/**
936
	 * Load language files
937
	 * @action plugins_loaded
938
	 */
939
	public static function plugin_textdomain() {
940
		// Note to self, the third argument must not be hardcoded, to account for relocated folders.
941
		load_plugin_textdomain( 'jetpack', false, dirname( plugin_basename( JETPACK__PLUGIN_FILE ) ) . '/languages/' );
942
	}
943
944
	/**
945
	 * Register assets for use in various modules and the Jetpack admin page.
946
	 *
947
	 * @uses wp_script_is, wp_register_script, plugins_url
948
	 * @action wp_loaded
949
	 * @return null
950
	 */
951
	public function register_assets() {
952
		if ( ! wp_script_is( 'spin', 'registered' ) ) {
953
			wp_register_script(
954
				'spin',
955
				self::get_file_url_for_environment( '_inc/build/spin.min.js', '_inc/spin.js' ),
956
				false,
957
				'1.3'
958
			);
959
		}
960
961
		if ( ! wp_script_is( 'jquery.spin', 'registered' ) ) {
962
			wp_register_script(
963
				'jquery.spin',
964
				self::get_file_url_for_environment( '_inc/build/jquery.spin.min.js', '_inc/jquery.spin.js' ),
965
				array( 'jquery', 'spin' ),
966
				'1.3'
967
			);
968
		}
969
970 View Code Duplication
		if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) ) {
971
			wp_register_script(
972
				'jetpack-gallery-settings',
973
				self::get_file_url_for_environment( '_inc/build/gallery-settings.min.js', '_inc/gallery-settings.js' ),
974
				array( 'media-views' ),
975
				'20121225'
976
			);
977
		}
978
979
		if ( ! wp_script_is( 'jetpack-twitter-timeline', 'registered' ) ) {
980
			wp_register_script(
981
				'jetpack-twitter-timeline',
982
				self::get_file_url_for_environment( '_inc/build/twitter-timeline.min.js', '_inc/twitter-timeline.js' ),
983
				array( 'jquery' ),
984
				'4.0.0',
985
				true
986
			);
987
		}
988
989
		if ( ! wp_script_is( 'jetpack-facebook-embed', 'registered' ) ) {
990
			wp_register_script(
991
				'jetpack-facebook-embed',
992
				self::get_file_url_for_environment( '_inc/build/facebook-embed.min.js', '_inc/facebook-embed.js' ),
993
				array( 'jquery' ),
994
				null,
995
				true
996
			);
997
998
			/** This filter is documented in modules/sharedaddy/sharing-sources.php */
999
			$fb_app_id = apply_filters( 'jetpack_sharing_facebook_app_id', '249643311490' );
1000
			if ( ! is_numeric( $fb_app_id ) ) {
1001
				$fb_app_id = '';
1002
			}
1003
			wp_localize_script(
1004
				'jetpack-facebook-embed',
1005
				'jpfbembed',
1006
				array(
1007
					'appid' => $fb_app_id,
1008
					'locale' => $this->get_locale(),
1009
				)
1010
			);
1011
		}
1012
1013
		/**
1014
		 * As jetpack_register_genericons is by default fired off a hook,
1015
		 * the hook may have already fired by this point.
1016
		 * So, let's just trigger it manually.
1017
		 */
1018
		require_once( JETPACK__PLUGIN_DIR . '_inc/genericons.php' );
1019
		jetpack_register_genericons();
1020
1021
		/**
1022
		 * Register the social logos
1023
		 */
1024
		require_once( JETPACK__PLUGIN_DIR . '_inc/social-logos.php' );
1025
		jetpack_register_social_logos();
1026
1027 View Code Duplication
		if ( ! wp_style_is( 'jetpack-icons', 'registered' ) )
1028
			wp_register_style( 'jetpack-icons', plugins_url( 'css/jetpack-icons.min.css', JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
1029
	}
1030
1031
	/**
1032
	 * Guess locale from language code.
1033
	 *
1034
	 * @param string $lang Language code.
1035
	 * @return string|bool
1036
	 */
1037 View Code Duplication
	function guess_locale_from_lang( $lang ) {
1038
		if ( 'en' === $lang || 'en_US' === $lang || ! $lang ) {
1039
			return 'en_US';
1040
		}
1041
1042
		if ( ! class_exists( 'GP_Locales' ) ) {
1043
			if ( ! defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || ! file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
1044
				return false;
1045
			}
1046
1047
			require JETPACK__GLOTPRESS_LOCALES_PATH;
1048
		}
1049
1050
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1051
			// WP.com: get_locale() returns 'it'
1052
			$locale = GP_Locales::by_slug( $lang );
1053
		} else {
1054
			// Jetpack: get_locale() returns 'it_IT';
1055
			$locale = GP_Locales::by_field( 'facebook_locale', $lang );
1056
		}
1057
1058
		if ( ! $locale ) {
1059
			return false;
1060
		}
1061
1062
		if ( empty( $locale->facebook_locale ) ) {
1063
			if ( empty( $locale->wp_locale ) ) {
1064
				return false;
1065
			} else {
1066
				// Facebook SDK is smart enough to fall back to en_US if a
1067
				// locale isn't supported. Since supported Facebook locales
1068
				// can fall out of sync, we'll attempt to use the known
1069
				// wp_locale value and rely on said fallback.
1070
				return $locale->wp_locale;
1071
			}
1072
		}
1073
1074
		return $locale->facebook_locale;
1075
	}
1076
1077
	/**
1078
	 * Get the locale.
1079
	 *
1080
	 * @return string|bool
1081
	 */
1082
	function get_locale() {
1083
		$locale = $this->guess_locale_from_lang( get_locale() );
1084
1085
		if ( ! $locale ) {
1086
			$locale = 'en_US';
1087
		}
1088
1089
		return $locale;
1090
	}
1091
1092
	/**
1093
	 * Device Pixels support
1094
	 * This improves the resolution of gravatars and wordpress.com uploads on hi-res and zoomed browsers.
1095
	 */
1096
	function devicepx() {
1097
		if ( Jetpack::is_active() && ! Jetpack_AMP_Support::is_amp_request() ) {
1098
			wp_enqueue_script( 'devicepx', 'https://s0.wp.com/wp-content/js/devicepx-jetpack.js', array(), gmdate( 'oW' ), true );
1099
		}
1100
	}
1101
1102
	/**
1103
	 * Return the network_site_url so that .com knows what network this site is a part of.
1104
	 * @param  bool $option
1105
	 * @return string
1106
	 */
1107
	public function jetpack_main_network_site_option( $option ) {
1108
		return network_site_url();
1109
	}
1110
	/**
1111
	 * Network Name.
1112
	 */
1113
	static function network_name( $option = null ) {
1114
		global $current_site;
1115
		return $current_site->site_name;
1116
	}
1117
	/**
1118
	 * Does the network allow new user and site registrations.
1119
	 * @return string
1120
	 */
1121
	static function network_allow_new_registrations( $option = null ) {
1122
		return ( in_array( get_site_option( 'registration' ), array('none', 'user', 'blog', 'all' ) ) ? get_site_option( 'registration') : 'none' );
1123
	}
1124
	/**
1125
	 * Does the network allow admins to add new users.
1126
	 * @return boolian
1127
	 */
1128
	static function network_add_new_users( $option = null ) {
1129
		return (bool) get_site_option( 'add_new_users' );
1130
	}
1131
	/**
1132
	 * File upload psace left per site in MB.
1133
	 *  -1 means NO LIMIT.
1134
	 * @return number
1135
	 */
1136
	static function network_site_upload_space( $option = null ) {
1137
		// value in MB
1138
		return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
1139
	}
1140
1141
	/**
1142
	 * Network allowed file types.
1143
	 * @return string
1144
	 */
1145
	static function network_upload_file_types( $option = null ) {
1146
		return get_site_option( 'upload_filetypes', 'jpg jpeg png gif' );
1147
	}
1148
1149
	/**
1150
	 * Maximum file upload size set by the network.
1151
	 * @return number
1152
	 */
1153
	static function network_max_upload_file_size( $option = null ) {
1154
		// value in KB
1155
		return get_site_option( 'fileupload_maxk', 300 );
1156
	}
1157
1158
	/**
1159
	 * Lets us know if a site allows admins to manage the network.
1160
	 * @return array
1161
	 */
1162
	static function network_enable_administration_menus( $option = null ) {
1163
		return get_site_option( 'menu_items' );
1164
	}
1165
1166
	/**
1167
	 * If a user has been promoted to or demoted from admin, we need to clear the
1168
	 * jetpack_other_linked_admins transient.
1169
	 *
1170
	 * @since 4.3.2
1171
	 * @since 4.4.0  $old_roles is null by default and if it's not passed, the transient is cleared.
1172
	 *
1173
	 * @param int    $user_id   The user ID whose role changed.
1174
	 * @param string $role      The new role.
1175
	 * @param array  $old_roles An array of the user's previous roles.
1176
	 */
1177
	function maybe_clear_other_linked_admins_transient( $user_id, $role, $old_roles = null ) {
1178
		if ( 'administrator' == $role
1179
			|| ( is_array( $old_roles ) && in_array( 'administrator', $old_roles ) )
1180
			|| is_null( $old_roles )
1181
		) {
1182
			delete_transient( 'jetpack_other_linked_admins' );
1183
		}
1184
	}
1185
1186
	/**
1187
	 * Checks to see if there are any other users available to become primary
1188
	 * Users must both:
1189
	 * - Be linked to wpcom
1190
	 * - Be an admin
1191
	 *
1192
	 * @return mixed False if no other users are linked, Int if there are.
1193
	 */
1194
	static function get_other_linked_admins() {
1195
		$other_linked_users = get_transient( 'jetpack_other_linked_admins' );
1196
1197
		if ( false === $other_linked_users ) {
1198
			$admins = get_users( array( 'role' => 'administrator' ) );
1199
			if ( count( $admins ) > 1 ) {
1200
				$available = array();
1201
				foreach ( $admins as $admin ) {
1202
					if ( Jetpack::is_user_connected( $admin->ID ) ) {
1203
						$available[] = $admin->ID;
1204
					}
1205
				}
1206
1207
				$count_connected_admins = count( $available );
1208
				if ( count( $available ) > 1 ) {
1209
					$other_linked_users = $count_connected_admins;
1210
				} else {
1211
					$other_linked_users = 0;
1212
				}
1213
			} else {
1214
				$other_linked_users = 0;
1215
			}
1216
1217
			set_transient( 'jetpack_other_linked_admins', $other_linked_users, HOUR_IN_SECONDS );
1218
		}
1219
1220
		return ( 0 === $other_linked_users ) ? false : $other_linked_users;
1221
	}
1222
1223
	/**
1224
	 * Return whether we are dealing with a multi network setup or not.
1225
	 * The reason we are type casting this is because we want to avoid the situation where
1226
	 * the result is false since when is_main_network_option return false it cases
1227
	 * the rest the get_option( 'jetpack_is_multi_network' ); to return the value that is set in the
1228
	 * database which could be set to anything as opposed to what this function returns.
1229
	 * @param  bool  $option
1230
	 *
1231
	 * @return boolean
1232
	 */
1233
	public function is_main_network_option( $option ) {
1234
		// return '1' or ''
1235
		return (string) (bool) Jetpack::is_multi_network();
1236
	}
1237
1238
	/**
1239
	 * Return true if we are with multi-site or multi-network false if we are dealing with single site.
1240
	 *
1241
	 * @param  string  $option
1242
	 * @return boolean
1243
	 */
1244
	public function is_multisite( $option ) {
1245
		return (string) (bool) is_multisite();
1246
	}
1247
1248
	/**
1249
	 * Implemented since there is no core is multi network function
1250
	 * Right now there is no way to tell if we which network is the dominant network on the system
1251
	 *
1252
	 * @since  3.3
1253
	 * @return boolean
1254
	 */
1255
	public static function is_multi_network() {
1256
		global  $wpdb;
1257
1258
		// if we don't have a multi site setup no need to do any more
1259
		if ( ! is_multisite() ) {
1260
			return false;
1261
		}
1262
1263
		$num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
1264
		if ( $num_sites > 1 ) {
1265
			return true;
1266
		} else {
1267
			return false;
1268
		}
1269
	}
1270
1271
	/**
1272
	 * Trigger an update to the main_network_site when we update the siteurl of a site.
1273
	 * @return null
1274
	 */
1275
	function update_jetpack_main_network_site_option() {
1276
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1277
	}
1278
	/**
1279
	 * Triggered after a user updates the network settings via Network Settings Admin Page
1280
	 *
1281
	 */
1282
	function update_jetpack_network_settings() {
1283
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1284
		// Only sync this info for the main network site.
1285
	}
1286
1287
	/**
1288
	 * Get back if the current site is single user site.
1289
	 *
1290
	 * @return bool
1291
	 */
1292
	public static function is_single_user_site() {
1293
		global $wpdb;
1294
1295 View Code Duplication
		if ( false === ( $some_users = get_transient( 'jetpack_is_single_user' ) ) ) {
1296
			$some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
1297
			set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
1298
		}
1299
		return 1 === (int) $some_users;
1300
	}
1301
1302
	/**
1303
	 * Returns true if the site has file write access false otherwise.
1304
	 * @return string ( '1' | '0' )
1305
	 **/
1306
	public static function file_system_write_access() {
1307
		if ( ! function_exists( 'get_filesystem_method' ) ) {
1308
			require_once( ABSPATH . 'wp-admin/includes/file.php' );
1309
		}
1310
1311
		require_once( ABSPATH . 'wp-admin/includes/template.php' );
1312
1313
		$filesystem_method = get_filesystem_method();
1314
		if ( $filesystem_method === 'direct' ) {
1315
			return 1;
1316
		}
1317
1318
		ob_start();
1319
		$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1320
		ob_end_clean();
1321
		if ( $filesystem_credentials_are_stored ) {
1322
			return 1;
1323
		}
1324
		return 0;
1325
	}
1326
1327
	/**
1328
	 * Finds out if a site is using a version control system.
1329
	 * @return string ( '1' | '0' )
1330
	 **/
1331
	public static function is_version_controlled() {
1332
		_deprecated_function( __METHOD__, 'jetpack-4.2', 'Jetpack_Sync_Functions::is_version_controlled' );
1333
		return (string) (int) Jetpack_Sync_Functions::is_version_controlled();
1334
	}
1335
1336
	/**
1337
	 * Determines whether the current theme supports featured images or not.
1338
	 * @return string ( '1' | '0' )
1339
	 */
1340
	public static function featured_images_enabled() {
1341
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1342
		return current_theme_supports( 'post-thumbnails' ) ? '1' : '0';
1343
	}
1344
1345
	/**
1346
	 * Wrapper for core's get_avatar_url().  This one is deprecated.
1347
	 *
1348
	 * @deprecated 4.7 use get_avatar_url instead.
1349
	 * @param int|string|object $id_or_email A user ID,  email address, or comment object
1350
	 * @param int $size Size of the avatar image
1351
	 * @param string $default URL to a default image to use if no avatar is available
1352
	 * @param bool $force_display Whether to force it to return an avatar even if show_avatars is disabled
1353
	 *
1354
	 * @return array
1355
	 */
1356
	public static function get_avatar_url( $id_or_email, $size = 96, $default = '', $force_display = false ) {
1357
		_deprecated_function( __METHOD__, 'jetpack-4.7', 'get_avatar_url' );
1358
		return get_avatar_url( $id_or_email, array(
1359
			'size' => $size,
1360
			'default' => $default,
1361
			'force_default' => $force_display,
1362
		) );
1363
	}
1364
1365
	/**
1366
	 * jetpack_updates is saved in the following schema:
1367
	 *
1368
	 * array (
1369
	 *      'plugins'                       => (int) Number of plugin updates available.
1370
	 *      'themes'                        => (int) Number of theme updates available.
1371
	 *      'wordpress'                     => (int) Number of WordPress core updates available.
1372
	 *      'translations'                  => (int) Number of translation updates available.
1373
	 *      'total'                         => (int) Total of all available updates.
1374
	 *      'wp_update_version'             => (string) The latest available version of WordPress, only present if a WordPress update is needed.
1375
	 * )
1376
	 * @return array
1377
	 */
1378
	public static function get_updates() {
1379
		$update_data = wp_get_update_data();
1380
1381
		// Stores the individual update counts as well as the total count.
1382
		if ( isset( $update_data['counts'] ) ) {
1383
			$updates = $update_data['counts'];
1384
		}
1385
1386
		// If we need to update WordPress core, let's find the latest version number.
1387 View Code Duplication
		if ( ! empty( $updates['wordpress'] ) ) {
1388
			$cur = get_preferred_from_update_core();
1389
			if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
1390
				$updates['wp_update_version'] = $cur->current;
1391
			}
1392
		}
1393
		return isset( $updates ) ? $updates : array();
1394
	}
1395
1396
	public static function get_update_details() {
1397
		$update_details = array(
1398
			'update_core' => get_site_transient( 'update_core' ),
1399
			'update_plugins' => get_site_transient( 'update_plugins' ),
1400
			'update_themes' => get_site_transient( 'update_themes' ),
1401
		);
1402
		return $update_details;
1403
	}
1404
1405
	public static function refresh_update_data() {
1406
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1407
1408
	}
1409
1410
	public static function refresh_theme_data() {
1411
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1412
	}
1413
1414
	/**
1415
	 * Is Jetpack active?
1416
	 */
1417
	public static function is_active() {
1418
		return (bool) Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1419
	}
1420
1421
	/**
1422
	 * Make an API call to WordPress.com for plan status
1423
	 *
1424
	 * @uses Jetpack_Options::get_option()
1425
	 * @uses Jetpack_Client::wpcom_json_api_request_as_blog()
1426
	 * @uses update_option()
1427
	 *
1428
	 * @access public
1429
	 * @static
1430
	 *
1431
	 * @return bool True if plan is updated, false if no update
1432
	 */
1433
	public static function refresh_active_plan_from_wpcom() {
1434
		// Make the API request
1435
		$request = sprintf( '/sites/%d', Jetpack_Options::get_option( 'id' ) );
1436
		$response = Jetpack_Client::wpcom_json_api_request_as_blog( $request, '1.1' );
1437
1438
		// Bail if there was an error or malformed response
1439
		if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
1440
			return false;
1441
		}
1442
1443
		// Decode the results
1444
		$results = json_decode( $response['body'], true );
1445
1446
		// Bail if there were no results or plan details returned
1447
		if ( ! is_array( $results ) || ! isset( $results['plan'] ) ) {
1448
			return false;
1449
		}
1450
1451
		// Store the option and return true if updated
1452
		return update_option( 'jetpack_active_plan', $results['plan'] );
1453
	}
1454
1455
	/**
1456
	 * Get the plan that this Jetpack site is currently using
1457
	 *
1458
	 * @uses get_option()
1459
	 *
1460
	 * @access public
1461
	 * @static
1462
	 *
1463
	 * @return array Active Jetpack plan details
1464
	 */
1465
	public static function get_active_plan() {
1466
		global $active_plan_cache;
1467
1468
		// this can be expensive to compute so we cache for the duration of a request
1469
		if ( is_array( $active_plan_cache ) && ! empty( $active_plan_cache ) ) {
1470
			return $active_plan_cache;
1471
		}
1472
1473
		$plan = get_option( 'jetpack_active_plan', array() );
1474
1475
		// Set the default options
1476
		$plan = wp_parse_args( $plan, array(
1477
			'product_slug' => 'jetpack_free',
1478
			'class'        => 'free',
1479
			'features'     => array(
1480
				'active' => array()
1481
			),
1482
		) );
1483
1484
		$supports = array();
1485
1486
		// Define what paid modules are supported by personal plans
1487
		$personal_plans = array(
1488
			'jetpack_personal',
1489
			'jetpack_personal_monthly',
1490
			'personal-bundle',
1491
		);
1492
1493
		if ( in_array( $plan['product_slug'], $personal_plans ) ) {
1494
			// special support value, not a module but a separate plugin
1495
			$supports[] = 'akismet';
1496
			$plan['class'] = 'personal';
1497
		}
1498
1499
		// Define what paid modules are supported by premium plans
1500
		$premium_plans = array(
1501
			'jetpack_premium',
1502
			'jetpack_premium_monthly',
1503
			'value_bundle',
1504
		);
1505
1506 View Code Duplication
		if ( in_array( $plan['product_slug'], $premium_plans ) ) {
1507
			$supports[] = 'akismet';
1508
			$supports[] = 'simple-payments';
1509
			$supports[] = 'vaultpress';
1510
			$plan['class'] = 'premium';
1511
		}
1512
1513
		// Define what paid modules are supported by professional plans
1514
		$business_plans = array(
1515
			'jetpack_business',
1516
			'jetpack_business_monthly',
1517
			'business-bundle',
1518
			'vip',
1519
		);
1520
1521 View Code Duplication
		if ( in_array( $plan['product_slug'], $business_plans ) ) {
1522
			$supports[] = 'akismet';
1523
			$supports[] = 'simple-payments';
1524
			$supports[] = 'vaultpress';
1525
			$plan['class'] = 'business';
1526
		}
1527
1528
		// get available features
1529
		foreach ( self::get_available_modules() as $module_slug ) {
1530
			$module = self::get_module( $module_slug );
1531
			if ( ! isset( $module ) || ! is_array( $module ) ) {
1532
				continue;
1533
			}
1534
			if ( in_array( 'free', $module['plan_classes'] ) || in_array( $plan['class'], $module['plan_classes'] ) ) {
1535
				$supports[] = $module_slug;
1536
			}
1537
		}
1538
1539
		$plan['supports'] = $supports;
1540
1541
		$active_plan_cache = $plan;
1542
1543
		return $plan;
1544
	}
1545
1546
	/**
1547
	 * Determine whether the active plan supports a particular feature
1548
	 *
1549
	 * @uses Jetpack::get_active_plan()
1550
	 *
1551
	 * @access public
1552
	 * @static
1553
	 *
1554
	 * @return bool True if plan supports feature, false if not
1555
	 */
1556
	public static function active_plan_supports( $feature ) {
1557
		$plan = Jetpack::get_active_plan();
1558
1559
		// Manually mapping WordPress.com features to Jetpack module slugs
1560
		foreach ( $plan['features']['active'] as $wpcom_feature ) {
1561
			switch ( $wpcom_feature ) {
1562
				case 'wordads-jetpack';
1563
1564
				// WordAds are supported for this site
1565
				if ( 'wordads' === $feature ) {
1566
					return true;
1567
				}
1568
				break;
1569
			}
1570
		}
1571
1572
		if (
1573
			in_array( $feature, $plan['supports'] )
1574
			|| in_array( $feature, $plan['features']['active'] )
1575
		) {
1576
			return true;
1577
		}
1578
1579
		return false;
1580
	}
1581
1582
	/**
1583
	 * Is Jetpack in development (offline) mode?
1584
	 */
1585
	public static function is_development_mode() {
1586
		$development_mode = false;
1587
1588
		if ( defined( 'JETPACK_DEV_DEBUG' ) ) {
1589
			$development_mode = JETPACK_DEV_DEBUG;
1590
		} elseif ( $site_url = site_url() ) {
1591
			$development_mode = false === strpos( $site_url, '.' );
1592
		}
1593
1594
		/**
1595
		 * Filters Jetpack's development mode.
1596
		 *
1597
		 * @see https://jetpack.com/support/development-mode/
1598
		 *
1599
		 * @since 2.2.1
1600
		 *
1601
		 * @param bool $development_mode Is Jetpack's development mode active.
1602
		 */
1603
		$development_mode = ( bool ) apply_filters( 'jetpack_development_mode', $development_mode );
1604
		return $development_mode;
1605
	}
1606
1607
	/**
1608
	 * Whether the site is currently onboarding or not.
1609
	 * A site is considered as being onboarded if it currently has an onboarding token.
1610
	 *
1611
	 * @since 5.8
1612
	 *
1613
	 * @access public
1614
	 * @static
1615
	 *
1616
	 * @return bool True if the site is currently onboarding, false otherwise
1617
	 */
1618
	public static function is_onboarding() {
1619
		return Jetpack_Options::get_option( 'onboarding' ) !== false;
1620
	}
1621
1622
	/**
1623
	* Get Jetpack development mode notice text and notice class.
1624
	*
1625
	* Mirrors the checks made in Jetpack::is_development_mode
1626
	*
1627
	*/
1628
	public static function show_development_mode_notice() {
1629
		if ( Jetpack::is_development_mode() ) {
1630
			if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
1631
				$notice = sprintf(
1632
					/* translators: %s is a URL */
1633
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the JETPACK_DEV_DEBUG constant being defined in wp-config.php or elsewhere.', 'jetpack' ),
1634
					'https://jetpack.com/support/development-mode/'
1635
				);
1636
			} elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1637
				$notice = sprintf(
1638
					/* translators: %s is a URL */
1639
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via site URL lacking a dot (e.g. http://localhost).', 'jetpack' ),
1640
					'https://jetpack.com/support/development-mode/'
1641
				);
1642
			} else {
1643
				$notice = sprintf(
1644
					/* translators: %s is a URL */
1645
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the jetpack_development_mode filter.', 'jetpack' ),
1646
					'https://jetpack.com/support/development-mode/'
1647
				);
1648
			}
1649
1650
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1651
		}
1652
1653
		// Throw up a notice if using a development version and as for feedback.
1654
		if ( Jetpack::is_development_version() ) {
1655
			/* translators: %s is a URL */
1656
			$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/' );
1657
1658
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1659
		}
1660
		// Throw up a notice if using staging mode
1661
		if ( Jetpack::is_staging_site() ) {
1662
			/* translators: %s is a URL */
1663
			$notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), 'https://jetpack.com/support/staging-sites/' );
1664
1665
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1666
		}
1667
	}
1668
1669
	/**
1670
	 * Whether Jetpack's version maps to a public release, or a development version.
1671
	 */
1672
	public static function is_development_version() {
1673
		/**
1674
		 * Allows filtering whether this is a development version of Jetpack.
1675
		 *
1676
		 * This filter is especially useful for tests.
1677
		 *
1678
		 * @since 4.3.0
1679
		 *
1680
		 * @param bool $development_version Is this a develoment version of Jetpack?
1681
		 */
1682
		return (bool) apply_filters(
1683
			'jetpack_development_version',
1684
			! preg_match( '/^\d+(\.\d+)+$/', Jetpack_Constants::get_constant( 'JETPACK__VERSION' ) )
1685
		);
1686
	}
1687
1688
	/**
1689
	 * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1690
	 */
1691
	public static function is_user_connected( $user_id = false ) {
1692
		$user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
1693
		if ( ! $user_id ) {
1694
			return false;
1695
		}
1696
1697
		return (bool) Jetpack_Data::get_access_token( $user_id );
1698
	}
1699
1700
	/**
1701
	 * Get the wpcom user data of the current|specified connected user.
1702
	 */
1703
	public static function get_connected_user_data( $user_id = null ) {
1704
		if ( ! $user_id ) {
1705
			$user_id = get_current_user_id();
1706
		}
1707
1708
		$transient_key = "jetpack_connected_user_data_$user_id";
1709
1710
		if ( $cached_user_data = get_transient( $transient_key ) ) {
1711
			return $cached_user_data;
1712
		}
1713
1714
		Jetpack::load_xml_rpc_client();
1715
		$xml = new Jetpack_IXR_Client( array(
1716
			'user_id' => $user_id,
1717
		) );
1718
		$xml->query( 'wpcom.getUser' );
1719
		if ( ! $xml->isError() ) {
1720
			$user_data = $xml->getResponse();
1721
			set_transient( $transient_key, $xml->getResponse(), DAY_IN_SECONDS );
1722
			return $user_data;
1723
		}
1724
1725
		return false;
1726
	}
1727
1728
	/**
1729
	 * Get the wpcom email of the current|specified connected user.
1730
	 */
1731 View Code Duplication
	public static function get_connected_user_email( $user_id = null ) {
1732
		if ( ! $user_id ) {
1733
			$user_id = get_current_user_id();
1734
		}
1735
		Jetpack::load_xml_rpc_client();
1736
		$xml = new Jetpack_IXR_Client( array(
1737
			'user_id' => $user_id,
1738
		) );
1739
		$xml->query( 'wpcom.getUserEmail' );
1740
		if ( ! $xml->isError() ) {
1741
			return $xml->getResponse();
1742
		}
1743
		return false;
1744
	}
1745
1746
	/**
1747
	 * Get the wpcom email of the master user.
1748
	 */
1749
	public static function get_master_user_email() {
1750
		$master_user_id = Jetpack_Options::get_option( 'master_user' );
1751
		if ( $master_user_id ) {
1752
			return self::get_connected_user_email( $master_user_id );
1753
		}
1754
		return '';
1755
	}
1756
1757
	function current_user_is_connection_owner() {
1758
		$user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1759
		return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && get_current_user_id() === $user_token->external_user_id;
1760
	}
1761
1762
	/**
1763
	 * Gets current user IP address.
1764
	 *
1765
	 * @param  bool $check_all_headers Check all headers? Default is `false`.
1766
	 *
1767
	 * @return string                  Current user IP address.
1768
	 */
1769
	public static function current_user_ip( $check_all_headers = false ) {
1770
		if ( $check_all_headers ) {
1771
			foreach ( array(
1772
				'HTTP_CF_CONNECTING_IP',
1773
				'HTTP_CLIENT_IP',
1774
				'HTTP_X_FORWARDED_FOR',
1775
				'HTTP_X_FORWARDED',
1776
				'HTTP_X_CLUSTER_CLIENT_IP',
1777
				'HTTP_FORWARDED_FOR',
1778
				'HTTP_FORWARDED',
1779
				'HTTP_VIA',
1780
			) as $key ) {
1781
				if ( ! empty( $_SERVER[ $key ] ) ) {
1782
					return $_SERVER[ $key ];
1783
				}
1784
			}
1785
		}
1786
1787
		return ! empty( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
1788
	}
1789
1790
	/**
1791
	 * Add any extra oEmbed providers that we know about and use on wpcom for feature parity.
1792
	 */
1793
	function extra_oembed_providers() {
1794
		// Cloudup: https://dev.cloudup.com/#oembed
1795
		wp_oembed_add_provider( 'https://cloudup.com/*' , 'https://cloudup.com/oembed' );
1796
		wp_oembed_add_provider( 'https://me.sh/*', 'https://me.sh/oembed?format=json' );
1797
		wp_oembed_add_provider( '#https?://(www\.)?gfycat\.com/.*#i', 'https://api.gfycat.com/v1/oembed', true );
1798
		wp_oembed_add_provider( '#https?://[^.]+\.(wistia\.com|wi\.st)/(medias|embed)/.*#', 'https://fast.wistia.com/oembed', true );
1799
		wp_oembed_add_provider( '#https?://sketchfab\.com/.*#i', 'https://sketchfab.com/oembed', true );
1800
		wp_oembed_add_provider( '#https?://(www\.)?icloud\.com/keynote/.*#i', 'https://iwmb.icloud.com/iwmb/oembed', true );
1801
	}
1802
1803
	/**
1804
	 * Synchronize connected user role changes
1805
	 */
1806
	function user_role_change( $user_id ) {
1807
		_deprecated_function( __METHOD__, 'jetpack-4.2', 'Jetpack_Sync_Users::user_role_change()' );
1808
		Jetpack_Sync_Users::user_role_change( $user_id );
1809
	}
1810
1811
	/**
1812
	 * Loads the currently active modules.
1813
	 */
1814
	public static function load_modules() {
1815
		if (
1816
			! self::is_active()
1817
			&& ! self::is_development_mode()
1818
			&& ! self::is_onboarding()
1819
			&& (
1820
				! is_multisite()
1821
				|| ! get_site_option( 'jetpack_protect_active' )
1822
			)
1823
		) {
1824
			return;
1825
		}
1826
1827
		$version = Jetpack_Options::get_option( 'version' );
1828 View Code Duplication
		if ( ! $version ) {
1829
			$version = $old_version = JETPACK__VERSION . ':' . time();
1830
			/** This action is documented in class.jetpack.php */
1831
			do_action( 'updating_jetpack_version', $version, false );
1832
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1833
		}
1834
		list( $version ) = explode( ':', $version );
1835
1836
		$modules = array_filter( Jetpack::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1837
1838
		$modules_data = array();
1839
1840
		// Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1841
		if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1842
			$updated_modules = array();
1843
			foreach ( $modules as $module ) {
1844
				$modules_data[ $module ] = Jetpack::get_module( $module );
1845
				if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1846
					continue;
1847
				}
1848
1849
				if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1850
					continue;
1851
				}
1852
1853
				$updated_modules[] = $module;
1854
			}
1855
1856
			$modules = array_diff( $modules, $updated_modules );
1857
		}
1858
1859
		$is_development_mode = Jetpack::is_development_mode();
1860
1861
		foreach ( $modules as $index => $module ) {
1862
			// If we're in dev mode, disable modules requiring a connection
1863
			if ( $is_development_mode ) {
1864
				// Prime the pump if we need to
1865
				if ( empty( $modules_data[ $module ] ) ) {
1866
					$modules_data[ $module ] = Jetpack::get_module( $module );
1867
				}
1868
				// If the module requires a connection, but we're in local mode, don't include it.
1869
				if ( $modules_data[ $module ]['requires_connection'] ) {
1870
					continue;
1871
				}
1872
			}
1873
1874
			if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1875
				continue;
1876
			}
1877
1878
			if ( ! include_once( Jetpack::get_module_path( $module ) ) ) {
1879
				unset( $modules[ $index ] );
1880
				self::update_active_modules( array_values( $modules ) );
1881
				continue;
1882
			}
1883
1884
			/**
1885
			 * Fires when a specific module is loaded.
1886
			 * The dynamic part of the hook, $module, is the module slug.
1887
			 *
1888
			 * @since 1.1.0
1889
			 */
1890
			do_action( 'jetpack_module_loaded_' . $module );
1891
		}
1892
1893
		/**
1894
		 * Fires when all the modules are loaded.
1895
		 *
1896
		 * @since 1.1.0
1897
		 */
1898
		do_action( 'jetpack_modules_loaded' );
1899
1900
		// 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.
1901
		require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' );
1902
	}
1903
1904
	/**
1905
	 * Check if Jetpack's REST API compat file should be included
1906
	 * @action plugins_loaded
1907
	 * @return null
1908
	 */
1909
	public function check_rest_api_compat() {
1910
		/**
1911
		 * Filters the list of REST API compat files to be included.
1912
		 *
1913
		 * @since 2.2.5
1914
		 *
1915
		 * @param array $args Array of REST API compat files to include.
1916
		 */
1917
		$_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1918
1919
		if ( function_exists( 'bbpress' ) )
1920
			$_jetpack_rest_api_compat_includes[] = JETPACK__PLUGIN_DIR . 'class.jetpack-bbpress-json-api-compat.php';
1921
1922
		foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include )
1923
			require_once $_jetpack_rest_api_compat_include;
1924
	}
1925
1926
	/**
1927
	 * Gets all plugins currently active in values, regardless of whether they're
1928
	 * traditionally activated or network activated.
1929
	 *
1930
	 * @todo Store the result in core's object cache maybe?
1931
	 */
1932
	public static function get_active_plugins() {
1933
		$active_plugins = (array) get_option( 'active_plugins', array() );
1934
1935
		if ( is_multisite() ) {
1936
			// Due to legacy code, active_sitewide_plugins stores them in the keys,
1937
			// whereas active_plugins stores them in the values.
1938
			$network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1939
			if ( $network_plugins ) {
1940
				$active_plugins = array_merge( $active_plugins, $network_plugins );
1941
			}
1942
		}
1943
1944
		sort( $active_plugins );
1945
1946
		return array_unique( $active_plugins );
1947
	}
1948
1949
	/**
1950
	 * Gets and parses additional plugin data to send with the heartbeat data
1951
	 *
1952
	 * @since 3.8.1
1953
	 *
1954
	 * @return array Array of plugin data
1955
	 */
1956
	public static function get_parsed_plugin_data() {
1957
		if ( ! function_exists( 'get_plugins' ) ) {
1958
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
1959
		}
1960
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
1961
		$all_plugins    = apply_filters( 'all_plugins', get_plugins() );
1962
		$active_plugins = Jetpack::get_active_plugins();
1963
1964
		$plugins = array();
1965
		foreach ( $all_plugins as $path => $plugin_data ) {
1966
			$plugins[ $path ] = array(
1967
					'is_active' => in_array( $path, $active_plugins ),
1968
					'file'      => $path,
1969
					'name'      => $plugin_data['Name'],
1970
					'version'   => $plugin_data['Version'],
1971
					'author'    => $plugin_data['Author'],
1972
			);
1973
		}
1974
1975
		return $plugins;
1976
	}
1977
1978
	/**
1979
	 * Gets and parses theme data to send with the heartbeat data
1980
	 *
1981
	 * @since 3.8.1
1982
	 *
1983
	 * @return array Array of theme data
1984
	 */
1985
	public static function get_parsed_theme_data() {
1986
		$all_themes = wp_get_themes( array( 'allowed' => true ) );
1987
		$header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
1988
1989
		$themes = array();
1990
		foreach ( $all_themes as $slug => $theme_data ) {
1991
			$theme_headers = array();
1992
			foreach ( $header_keys as $header_key ) {
1993
				$theme_headers[ $header_key ] = $theme_data->get( $header_key );
1994
			}
1995
1996
			$themes[ $slug ] = array(
1997
					'is_active_theme' => $slug == wp_get_theme()->get_template(),
1998
					'slug' => $slug,
1999
					'theme_root' => $theme_data->get_theme_root_uri(),
2000
					'parent' => $theme_data->parent(),
2001
					'headers' => $theme_headers
2002
			);
2003
		}
2004
2005
		return $themes;
2006
	}
2007
2008
	/**
2009
	 * Checks whether a specific plugin is active.
2010
	 *
2011
	 * We don't want to store these in a static variable, in case
2012
	 * there are switch_to_blog() calls involved.
2013
	 */
2014
	public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
2015
		return in_array( $plugin, self::get_active_plugins() );
2016
	}
2017
2018
	/**
2019
	 * Check if Jetpack's Open Graph tags should be used.
2020
	 * If certain plugins are active, Jetpack's og tags are suppressed.
2021
	 *
2022
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
2023
	 * @action plugins_loaded
2024
	 * @return null
2025
	 */
2026
	public function check_open_graph() {
2027
		if ( in_array( 'publicize', Jetpack::get_active_modules() ) || in_array( 'sharedaddy', Jetpack::get_active_modules() ) ) {
2028
			add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
2029
		}
2030
2031
		$active_plugins = self::get_active_plugins();
2032
2033
		if ( ! empty( $active_plugins ) ) {
2034
			foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
2035
				if ( in_array( $plugin, $active_plugins ) ) {
2036
					add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
2037
					break;
2038
				}
2039
			}
2040
		}
2041
2042
		/**
2043
		 * Allow the addition of Open Graph Meta Tags to all pages.
2044
		 *
2045
		 * @since 2.0.3
2046
		 *
2047
		 * @param bool false Should Open Graph Meta tags be added. Default to false.
2048
		 */
2049
		if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
2050
			require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
2051
		}
2052
	}
2053
2054
	/**
2055
	 * Check if Jetpack's Twitter tags should be used.
2056
	 * If certain plugins are active, Jetpack's twitter 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_twitter_tags() {
2063
2064
		$active_plugins = self::get_active_plugins();
2065
2066
		if ( ! empty( $active_plugins ) ) {
2067
			foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
2068
				if ( in_array( $plugin, $active_plugins ) ) {
2069
					add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
2070
					break;
2071
				}
2072
			}
2073
		}
2074
2075
		/**
2076
		 * Allow Twitter Card Meta tags to be disabled.
2077
		 *
2078
		 * @since 2.6.0
2079
		 *
2080
		 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2081
		 */
2082
		if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2083
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2084
		}
2085
	}
2086
2087
	/**
2088
	 * Allows plugins to submit security reports.
2089
 	 *
2090
	 * @param string  $type         Report type (login_form, backup, file_scanning, spam)
2091
	 * @param string  $plugin_file  Plugin __FILE__, so that we can pull plugin data
2092
	 * @param array   $args         See definitions above
2093
	 */
2094
	public static function submit_security_report( $type = '', $plugin_file = '', $args = array() ) {
2095
		_deprecated_function( __FUNCTION__, 'jetpack-4.2', null );
2096
	}
2097
2098
/* Jetpack Options API */
2099
2100
	public static function get_option_names( $type = 'compact' ) {
2101
		return Jetpack_Options::get_option_names( $type );
2102
	}
2103
2104
	/**
2105
	 * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
2106
 	 *
2107
	 * @param string $name    Option name
2108
	 * @param mixed  $default (optional)
2109
	 */
2110
	public static function get_option( $name, $default = false ) {
2111
		return Jetpack_Options::get_option( $name, $default );
2112
	}
2113
2114
	/**
2115
	 * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
2116
 	 *
2117
	 * @deprecated 3.4 use Jetpack_Options::update_option() instead.
2118
	 * @param string $name  Option name
2119
	 * @param mixed  $value Option value
2120
	 */
2121
	public static function update_option( $name, $value ) {
2122
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_option()' );
2123
		return Jetpack_Options::update_option( $name, $value );
2124
	}
2125
2126
	/**
2127
	 * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
2128
 	 *
2129
	 * @deprecated 3.4 use Jetpack_Options::update_options() instead.
2130
	 * @param array $array array( option name => option value, ... )
2131
	 */
2132
	public static function update_options( $array ) {
2133
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_options()' );
2134
		return Jetpack_Options::update_options( $array );
2135
	}
2136
2137
	/**
2138
	 * Deletes the given option.  May be passed multiple option names as an array.
2139
	 * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
2140
	 *
2141
	 * @deprecated 3.4 use Jetpack_Options::delete_option() instead.
2142
	 * @param string|array $names
2143
	 */
2144
	public static function delete_option( $names ) {
2145
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::delete_option()' );
2146
		return Jetpack_Options::delete_option( $names );
2147
	}
2148
2149
	/**
2150
	 * Enters a user token into the user_tokens option
2151
	 *
2152
	 * @param int $user_id
2153
	 * @param string $token
2154
	 * return bool
2155
	 */
2156
	public static function update_user_token( $user_id, $token, $is_master_user ) {
2157
		// not designed for concurrent updates
2158
		$user_tokens = Jetpack_Options::get_option( 'user_tokens' );
2159
		if ( ! is_array( $user_tokens ) )
2160
			$user_tokens = array();
2161
		$user_tokens[$user_id] = $token;
2162
		if ( $is_master_user ) {
2163
			$master_user = $user_id;
2164
			$options     = compact( 'user_tokens', 'master_user' );
2165
		} else {
2166
			$options = compact( 'user_tokens' );
2167
		}
2168
		return Jetpack_Options::update_options( $options );
2169
	}
2170
2171
	/**
2172
	 * Returns an array of all PHP files in the specified absolute path.
2173
	 * Equivalent to glob( "$absolute_path/*.php" ).
2174
	 *
2175
	 * @param string $absolute_path The absolute path of the directory to search.
2176
	 * @return array Array of absolute paths to the PHP files.
2177
	 */
2178
	public static function glob_php( $absolute_path ) {
2179
		if ( function_exists( 'glob' ) ) {
2180
			return glob( "$absolute_path/*.php" );
2181
		}
2182
2183
		$absolute_path = untrailingslashit( $absolute_path );
2184
		$files = array();
2185
		if ( ! $dir = @opendir( $absolute_path ) ) {
2186
			return $files;
2187
		}
2188
2189
		while ( false !== $file = readdir( $dir ) ) {
2190
			if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
2191
				continue;
2192
			}
2193
2194
			$file = "$absolute_path/$file";
2195
2196
			if ( ! is_file( $file ) ) {
2197
				continue;
2198
			}
2199
2200
			$files[] = $file;
2201
		}
2202
2203
		closedir( $dir );
2204
2205
		return $files;
2206
	}
2207
2208
	public static function activate_new_modules( $redirect = false ) {
2209
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
2210
			return;
2211
		}
2212
2213
		$jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
2214 View Code Duplication
		if ( ! $jetpack_old_version ) {
2215
			$jetpack_old_version = $version = $old_version = '1.1:' . time();
2216
			/** This action is documented in class.jetpack.php */
2217
			do_action( 'updating_jetpack_version', $version, false );
2218
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2219
		}
2220
2221
		list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
2222
2223
		if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
2224
			return;
2225
		}
2226
2227
		$active_modules     = Jetpack::get_active_modules();
2228
		$reactivate_modules = array();
2229
		foreach ( $active_modules as $active_module ) {
2230
			$module = Jetpack::get_module( $active_module );
2231
			if ( ! isset( $module['changed'] ) ) {
2232
				continue;
2233
			}
2234
2235
			if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
2236
				continue;
2237
			}
2238
2239
			$reactivate_modules[] = $active_module;
2240
			Jetpack::deactivate_module( $active_module );
2241
		}
2242
2243
		$new_version = JETPACK__VERSION . ':' . time();
2244
		/** This action is documented in class.jetpack.php */
2245
		do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
2246
		Jetpack_Options::update_options(
2247
			array(
2248
				'version'     => $new_version,
2249
				'old_version' => $jetpack_old_version,
2250
			)
2251
		);
2252
2253
		Jetpack::state( 'message', 'modules_activated' );
2254
		Jetpack::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules );
0 ignored issues
show
JETPACK__VERSION is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2255
2256
		if ( $redirect ) {
2257
			$page = 'jetpack'; // make sure we redirect to either settings or the jetpack page
2258
			if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ) ) ) {
2259
				$page = $_GET['page'];
2260
			}
2261
2262
			wp_safe_redirect( Jetpack::admin_url( 'page=' . $page ) );
2263
			exit;
2264
		}
2265
	}
2266
2267
	/**
2268
	 * List available Jetpack modules. Simply lists .php files in /modules/.
2269
	 * Make sure to tuck away module "library" files in a sub-directory.
2270
	 */
2271
	public static function get_available_modules( $min_version = false, $max_version = false ) {
2272
		static $modules = null;
2273
2274
		if ( ! isset( $modules ) ) {
2275
			$available_modules_option = Jetpack_Options::get_option( 'available_modules', array() );
2276
			// Use the cache if we're on the front-end and it's available...
2277
			if ( ! is_admin() && ! empty( $available_modules_option[ JETPACK__VERSION ] ) ) {
2278
				$modules = $available_modules_option[ JETPACK__VERSION ];
2279
			} else {
2280
				$files = Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
2281
2282
				$modules = array();
2283
2284
				foreach ( $files as $file ) {
2285
					if ( ! $headers = Jetpack::get_module( $file ) ) {
2286
						continue;
2287
					}
2288
2289
					$modules[ Jetpack::get_module_slug( $file ) ] = $headers['introduced'];
2290
				}
2291
2292
				Jetpack_Options::update_option( 'available_modules', array(
2293
					JETPACK__VERSION => $modules,
2294
				) );
2295
			}
2296
		}
2297
2298
		/**
2299
		 * Filters the array of modules available to be activated.
2300
		 *
2301
		 * @since 2.4.0
2302
		 *
2303
		 * @param array $modules Array of available modules.
2304
		 * @param string $min_version Minimum version number required to use modules.
2305
		 * @param string $max_version Maximum version number required to use modules.
2306
		 */
2307
		$mods = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version );
2308
2309
		if ( ! $min_version && ! $max_version ) {
2310
			return array_keys( $mods );
2311
		}
2312
2313
		$r = array();
2314
		foreach ( $mods as $slug => $introduced ) {
2315
			if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
2316
				continue;
2317
			}
2318
2319
			if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
2320
				continue;
2321
			}
2322
2323
			$r[] = $slug;
2324
		}
2325
2326
		return $r;
2327
	}
2328
2329
	/**
2330
	 * Default modules loaded on activation.
2331
	 */
2332
	public static function get_default_modules( $min_version = false, $max_version = false ) {
2333
		$return = array();
2334
2335
		foreach ( Jetpack::get_available_modules( $min_version, $max_version ) as $module ) {
2336
			$module_data = Jetpack::get_module( $module );
2337
2338
			switch ( strtolower( $module_data['auto_activate'] ) ) {
2339
				case 'yes' :
2340
					$return[] = $module;
2341
					break;
2342
				case 'public' :
2343
					if ( Jetpack_Options::get_option( 'public' ) ) {
2344
						$return[] = $module;
2345
					}
2346
					break;
2347
				case 'no' :
2348
				default :
2349
					break;
2350
			}
2351
		}
2352
		/**
2353
		 * Filters the array of default modules.
2354
		 *
2355
		 * @since 2.5.0
2356
		 *
2357
		 * @param array $return Array of default modules.
2358
		 * @param string $min_version Minimum version number required to use modules.
2359
		 * @param string $max_version Maximum version number required to use modules.
2360
		 */
2361
		return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version );
2362
	}
2363
2364
	/**
2365
	 * Checks activated modules during auto-activation to determine
2366
	 * if any of those modules are being deprecated.  If so, close
2367
	 * them out, and add any replacement modules.
2368
	 *
2369
	 * Runs at priority 99 by default.
2370
	 *
2371
	 * This is run late, so that it can still activate a module if
2372
	 * the new module is a replacement for another that the user
2373
	 * currently has active, even if something at the normal priority
2374
	 * would kibosh everything.
2375
	 *
2376
	 * @since 2.6
2377
	 * @uses jetpack_get_default_modules filter
2378
	 * @param array $modules
2379
	 * @return array
2380
	 */
2381
	function handle_deprecated_modules( $modules ) {
2382
		$deprecated_modules = array(
2383
			'debug'            => null,  // Closed out and moved to ./class.jetpack-debugger.php
2384
			'wpcc'             => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2385
			'gplus-authorship' => null,  // Closed out in 3.2 -- Google dropped support.
2386
		);
2387
2388
		// Don't activate SSO if they never completed activating WPCC.
2389
		if ( Jetpack::is_module_active( 'wpcc' ) ) {
2390
			$wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2391
			if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2392
				$deprecated_modules['wpcc'] = null;
2393
			}
2394
		}
2395
2396
		foreach ( $deprecated_modules as $module => $replacement ) {
2397
			if ( Jetpack::is_module_active( $module ) ) {
2398
				self::deactivate_module( $module );
2399
				if ( $replacement ) {
2400
					$modules[] = $replacement;
2401
				}
2402
			}
2403
		}
2404
2405
		return array_unique( $modules );
2406
	}
2407
2408
	/**
2409
	 * Checks activated plugins during auto-activation to determine
2410
	 * if any of those plugins are in the list with a corresponding module
2411
	 * that is not compatible with the plugin. The module will not be allowed
2412
	 * to auto-activate.
2413
	 *
2414
	 * @since 2.6
2415
	 * @uses jetpack_get_default_modules filter
2416
	 * @param array $modules
2417
	 * @return array
2418
	 */
2419
	function filter_default_modules( $modules ) {
2420
2421
		$active_plugins = self::get_active_plugins();
2422
2423
		if ( ! empty( $active_plugins ) ) {
2424
2425
			// For each module we'd like to auto-activate...
2426
			foreach ( $modules as $key => $module ) {
2427
				// If there are potential conflicts for it...
2428
				if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2429
					// For each potential conflict...
2430
					foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
2431
						// If that conflicting plugin is active...
2432
						if ( in_array( $plugin, $active_plugins ) ) {
2433
							// Remove that item from being auto-activated.
2434
							unset( $modules[ $key ] );
2435
						}
2436
					}
2437
				}
2438
			}
2439
		}
2440
2441
		return $modules;
2442
	}
2443
2444
	/**
2445
	 * Extract a module's slug from its full path.
2446
	 */
2447
	public static function get_module_slug( $file ) {
2448
		return str_replace( '.php', '', basename( $file ) );
2449
	}
2450
2451
	/**
2452
	 * Generate a module's path from its slug.
2453
	 */
2454
	public static function get_module_path( $slug ) {
2455
		return JETPACK__PLUGIN_DIR . "modules/$slug.php";
2456
	}
2457
2458
	/**
2459
	 * Load module data from module file. Headers differ from WordPress
2460
	 * plugin headers to avoid them being identified as standalone
2461
	 * plugins on the WordPress plugins page.
2462
	 */
2463
	public static function get_module( $module ) {
2464
		$headers = array(
2465
			'name'                      => 'Module Name',
2466
			'description'               => 'Module Description',
2467
			'jumpstart_desc'            => 'Jumpstart Description',
2468
			'sort'                      => 'Sort Order',
2469
			'recommendation_order'      => 'Recommendation Order',
2470
			'introduced'                => 'First Introduced',
2471
			'changed'                   => 'Major Changes In',
2472
			'deactivate'                => 'Deactivate',
2473
			'free'                      => 'Free',
2474
			'requires_connection'       => 'Requires Connection',
2475
			'auto_activate'             => 'Auto Activate',
2476
			'module_tags'               => 'Module Tags',
2477
			'feature'                   => 'Feature',
2478
			'additional_search_queries' => 'Additional Search Queries',
2479
			'plan_classes'              => 'Plans',
2480
		);
2481
2482
		$file = Jetpack::get_module_path( Jetpack::get_module_slug( $module ) );
2483
2484
		$mod = Jetpack::get_file_data( $file, $headers );
2485
		if ( empty( $mod['name'] ) ) {
2486
			return false;
2487
		}
2488
2489
		$mod['sort']                    = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
2490
		$mod['recommendation_order']    = empty( $mod['recommendation_order'] ) ? 20 : (int) $mod['recommendation_order'];
2491
		$mod['deactivate']              = empty( $mod['deactivate'] );
2492
		$mod['free']                    = empty( $mod['free'] );
2493
		$mod['requires_connection']     = ( ! empty( $mod['requires_connection'] ) && 'No' == $mod['requires_connection'] ) ? false : true;
2494
2495
		if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ) ) ) {
2496
			$mod['auto_activate'] = 'No';
2497
		} else {
2498
			$mod['auto_activate'] = (string) $mod['auto_activate'];
2499
		}
2500
2501
		if ( $mod['module_tags'] ) {
2502
			$mod['module_tags'] = explode( ',', $mod['module_tags'] );
2503
			$mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
2504
			$mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
2505
		} else {
2506
			$mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
2507
		}
2508
2509 View Code Duplication
		if ( $mod['plan_classes'] ) {
2510
			$mod['plan_classes'] = explode( ',', $mod['plan_classes'] );
2511
			$mod['plan_classes'] = array_map( 'strtolower', array_map( 'trim', $mod['plan_classes'] ) );
2512
		} else {
2513
			$mod['plan_classes'] = array( 'free' );
2514
		}
2515
2516 View Code Duplication
		if ( $mod['feature'] ) {
2517
			$mod['feature'] = explode( ',', $mod['feature'] );
2518
			$mod['feature'] = array_map( 'trim', $mod['feature'] );
2519
		} else {
2520
			$mod['feature'] = array( self::translate_module_tag( 'Other' ) );
2521
		}
2522
2523
		/**
2524
		 * Filters the feature array on a module.
2525
		 *
2526
		 * This filter allows you to control where each module is filtered: Recommended,
2527
		 * Jumpstart, and the default "Other" listing.
2528
		 *
2529
		 * @since 3.5.0
2530
		 *
2531
		 * @param array   $mod['feature'] The areas to feature this module:
2532
		 *     'Jumpstart' adds to the "Jumpstart" option to activate many modules at once.
2533
		 *     'Recommended' shows on the main Jetpack admin screen.
2534
		 *     'Other' should be the default if no other value is in the array.
2535
		 * @param string  $module The slug of the module, e.g. sharedaddy.
2536
		 * @param array   $mod All the currently assembled module data.
2537
		 */
2538
		$mod['feature'] = apply_filters( 'jetpack_module_feature', $mod['feature'], $module, $mod );
2539
2540
		/**
2541
		 * Filter the returned data about a module.
2542
		 *
2543
		 * This filter allows overriding any info about Jetpack modules. It is dangerous,
2544
		 * so please be careful.
2545
		 *
2546
		 * @since 3.6.0
2547
		 *
2548
		 * @param array   $mod    The details of the requested module.
2549
		 * @param string  $module The slug of the module, e.g. sharedaddy
2550
		 * @param string  $file   The path to the module source file.
2551
		 */
2552
		return apply_filters( 'jetpack_get_module', $mod, $module, $file );
2553
	}
2554
2555
	/**
2556
	 * Like core's get_file_data implementation, but caches the result.
2557
	 */
2558
	public static function get_file_data( $file, $headers ) {
2559
		//Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated
2560
		$file_name = basename( $file );
2561
2562
		$cache_key = 'jetpack_file_data_' . JETPACK__VERSION;
2563
2564
		$file_data_option = get_transient( $cache_key );
2565
2566
		if ( false === $file_data_option ) {
2567
			$file_data_option = array();
2568
		}
2569
2570
		$key           = md5( $file_name . serialize( $headers ) );
2571
		$refresh_cache = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 );
2572
2573
		// If we don't need to refresh the cache, and already have the value, short-circuit!
2574
		if ( ! $refresh_cache && isset( $file_data_option[ $key ] ) ) {
2575
			return $file_data_option[ $key ];
2576
		}
2577
2578
		$data = get_file_data( $file, $headers );
2579
2580
		$file_data_option[ $key ] = $data;
2581
2582
		set_transient( $cache_key, $file_data_option, 29 * DAY_IN_SECONDS );
2583
2584
		return $data;
2585
	}
2586
2587
2588
	/**
2589
	 * Return translated module tag.
2590
	 *
2591
	 * @param string $tag Tag as it appears in each module heading.
2592
	 *
2593
	 * @return mixed
2594
	 */
2595
	public static function translate_module_tag( $tag ) {
2596
		return jetpack_get_module_i18n_tag( $tag );
2597
	}
2598
2599
	/**
2600
	 * Get i18n strings as a JSON-encoded string
2601
	 *
2602
	 * @return string The locale as JSON
2603
	 */
2604
	public static function get_i18n_data_json() {
2605
		$i18n_json = JETPACK__PLUGIN_DIR . 'languages/json/jetpack-' . jetpack_get_user_locale() . '.json';
2606
2607
		if ( is_file( $i18n_json ) && is_readable( $i18n_json ) ) {
2608
			$locale_data = @file_get_contents( $i18n_json );
2609
			if ( $locale_data ) {
2610
				return $locale_data;
2611
			}
2612
		}
2613
2614
		// Return valid empty Jed locale
2615
		return json_encode( array(
2616
			'' => array(
2617
				'domain' => 'jetpack',
2618
				'lang'   => is_admin() ? get_user_locale() : get_locale(),
2619
			),
2620
		) );
2621
	}
2622
2623
	/**
2624
	 * Return module name translation. Uses matching string created in modules/module-headings.php.
2625
	 *
2626
	 * @since 3.9.2
2627
	 *
2628
	 * @param array $modules
2629
	 *
2630
	 * @return string|void
2631
	 */
2632
	public static function get_translated_modules( $modules ) {
2633
		foreach ( $modules as $index => $module ) {
2634
			$i18n_module = jetpack_get_module_i18n( $module['module'] );
2635
			if ( isset( $module['name'] ) ) {
2636
				$modules[ $index ]['name'] = $i18n_module['name'];
2637
			}
2638
			if ( isset( $module['description'] ) ) {
2639
				$modules[ $index ]['description'] = $i18n_module['description'];
2640
				$modules[ $index ]['short_description'] = $i18n_module['description'];
2641
			}
2642
		}
2643
		return $modules;
2644
	}
2645
2646
	/**
2647
	 * Get a list of activated modules as an array of module slugs.
2648
	 */
2649
	public static function get_active_modules() {
2650
		$active = Jetpack_Options::get_option( 'active_modules' );
2651
2652
		if ( ! is_array( $active ) ) {
2653
			$active = array();
2654
		}
2655
2656
		if ( class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ) ) {
2657
			$active[] = 'vaultpress';
2658
		} else {
2659
			$active = array_diff( $active, array( 'vaultpress' ) );
2660
		}
2661
2662
		//If protect is active on the main site of a multisite, it should be active on all sites.
2663
		if ( ! in_array( 'protect', $active ) && is_multisite() && get_site_option( 'jetpack_protect_active' ) ) {
2664
			$active[] = 'protect';
2665
		}
2666
2667
		/**
2668
		 * Allow filtering of the active modules.
2669
		 *
2670
		 * Gives theme and plugin developers the power to alter the modules that
2671
		 * are activated on the fly.
2672
		 *
2673
		 * @since 5.8.0
2674
		 *
2675
		 * @param array $active Array of active module slugs.
2676
		 */
2677
		$active = apply_filters( 'jetpack_active_modules', $active );
2678
2679
		return array_unique( $active );
2680
	}
2681
2682
	/**
2683
	 * Check whether or not a Jetpack module is active.
2684
	 *
2685
	 * @param string $module The slug of a Jetpack module.
2686
	 * @return bool
2687
	 *
2688
	 * @static
2689
	 */
2690
	public static function is_module_active( $module ) {
2691
		return in_array( $module, self::get_active_modules() );
2692
	}
2693
2694
	public static function is_module( $module ) {
2695
		return ! empty( $module ) && ! validate_file( $module, Jetpack::get_available_modules() );
2696
	}
2697
2698
	/**
2699
	 * Catches PHP errors.  Must be used in conjunction with output buffering.
2700
	 *
2701
	 * @param bool $catch True to start catching, False to stop.
2702
	 *
2703
	 * @static
2704
	 */
2705
	public static function catch_errors( $catch ) {
2706
		static $display_errors, $error_reporting;
2707
2708
		if ( $catch ) {
2709
			$display_errors  = @ini_set( 'display_errors', 1 );
2710
			$error_reporting = @error_reporting( E_ALL );
2711
			add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2712
		} else {
2713
			@ini_set( 'display_errors', $display_errors );
2714
			@error_reporting( $error_reporting );
2715
			remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2716
		}
2717
	}
2718
2719
	/**
2720
	 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2721
	 */
2722
	public static function catch_errors_on_shutdown() {
2723
		Jetpack::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2724
	}
2725
2726
	/**
2727
	 * Rewrite any string to make paths easier to read.
2728
	 *
2729
	 * Rewrites ABSPATH (eg `/home/jetpack/wordpress/`) to ABSPATH, and if WP_CONTENT_DIR
2730
	 * is located outside of ABSPATH, rewrites that to WP_CONTENT_DIR.
2731
	 *
2732
	 * @param $string
2733
	 * @return mixed
2734
	 */
2735
	public static function alias_directories( $string ) {
2736
		// ABSPATH has a trailing slash.
2737
		$string = str_replace( ABSPATH, 'ABSPATH/', $string );
2738
		// WP_CONTENT_DIR does not have a trailing slash.
2739
		$string = str_replace( WP_CONTENT_DIR, 'WP_CONTENT_DIR', $string );
2740
2741
		return $string;
2742
	}
2743
2744
	public static function activate_default_modules(
2745
		$min_version = false,
2746
		$max_version = false,
2747
		$other_modules = array(),
2748
		$redirect = true,
2749
		$send_state_messages = true
2750
	) {
2751
		$jetpack = Jetpack::init();
2752
2753
		$modules = Jetpack::get_default_modules( $min_version, $max_version );
2754
		$modules = array_merge( $other_modules, $modules );
2755
2756
		// Look for standalone plugins and disable if active.
2757
2758
		$to_deactivate = array();
2759
		foreach ( $modules as $module ) {
2760
			if ( isset( $jetpack->plugins_to_deactivate[$module] ) ) {
2761
				$to_deactivate[$module] = $jetpack->plugins_to_deactivate[$module];
2762
			}
2763
		}
2764
2765
		$deactivated = array();
2766
		foreach ( $to_deactivate as $module => $deactivate_me ) {
2767
			list( $probable_file, $probable_title ) = $deactivate_me;
2768
			if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2769
				$deactivated[] = $module;
2770
			}
2771
		}
2772
2773
		if ( $deactivated && $redirect ) {
2774
			Jetpack::state( 'deactivated_plugins', join( ',', $deactivated ) );
2775
2776
			$url = add_query_arg(
2777
				array(
2778
					'action'   => 'activate_default_modules',
2779
					'_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2780
				),
2781
				add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), Jetpack::admin_url( 'page=jetpack' ) )
2782
			);
2783
			wp_safe_redirect( $url );
2784
			exit;
2785
		}
2786
2787
		/**
2788
		 * Fires before default modules are activated.
2789
		 *
2790
		 * @since 1.9.0
2791
		 *
2792
		 * @param string $min_version Minimum version number required to use modules.
2793
		 * @param string $max_version Maximum version number required to use modules.
2794
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2795
		 */
2796
		do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
2797
2798
		// Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
2799
		Jetpack::restate();
2800
		Jetpack::catch_errors( true );
2801
2802
		$active = Jetpack::get_active_modules();
2803
2804
		foreach ( $modules as $module ) {
2805
			if ( did_action( "jetpack_module_loaded_$module" ) ) {
2806
				$active[] = $module;
2807
				self::update_active_modules( $active );
2808
				continue;
2809
			}
2810
2811
			if ( $send_state_messages && in_array( $module, $active ) ) {
2812
				$module_info = Jetpack::get_module( $module );
2813 View Code Duplication
				if ( ! $module_info['deactivate'] ) {
2814
					$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2815
					if ( $active_state = Jetpack::state( $state ) ) {
2816
						$active_state = explode( ',', $active_state );
2817
					} else {
2818
						$active_state = array();
2819
					}
2820
					$active_state[] = $module;
2821
					Jetpack::state( $state, implode( ',', $active_state ) );
2822
				}
2823
				continue;
2824
			}
2825
2826
			$file = Jetpack::get_module_path( $module );
2827
			if ( ! file_exists( $file ) ) {
2828
				continue;
2829
			}
2830
2831
			// we'll override this later if the plugin can be included without fatal error
2832
			if ( $redirect ) {
2833
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2834
			}
2835
2836
			if ( $send_state_messages ) {
2837
				Jetpack::state( 'error', 'module_activation_failed' );
2838
				Jetpack::state( 'module', $module );
2839
			}
2840
2841
			ob_start();
2842
			require_once $file;
2843
2844
			$active[] = $module;
2845
2846 View Code Duplication
			if ( $send_state_messages ) {
2847
2848
				$state    = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2849
				if ( $active_state = Jetpack::state( $state ) ) {
2850
					$active_state = explode( ',', $active_state );
2851
				} else {
2852
					$active_state = array();
2853
				}
2854
				$active_state[] = $module;
2855
				Jetpack::state( $state, implode( ',', $active_state ) );
2856
			}
2857
2858
			Jetpack::update_active_modules( $active );
2859
2860
			ob_end_clean();
2861
		}
2862
2863
		if ( $send_state_messages ) {
2864
			Jetpack::state( 'error', false );
2865
			Jetpack::state( 'module', false );
2866
		}
2867
2868
		Jetpack::catch_errors( false );
2869
		/**
2870
		 * Fires when default modules are activated.
2871
		 *
2872
		 * @since 1.9.0
2873
		 *
2874
		 * @param string $min_version Minimum version number required to use modules.
2875
		 * @param string $max_version Maximum version number required to use modules.
2876
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2877
		 */
2878
		do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
2879
	}
2880
2881
	public static function activate_module( $module, $exit = true, $redirect = true ) {
2882
		/**
2883
		 * Fires before a module is activated.
2884
		 *
2885
		 * @since 2.6.0
2886
		 *
2887
		 * @param string $module Module slug.
2888
		 * @param bool $exit Should we exit after the module has been activated. Default to true.
2889
		 * @param bool $redirect Should the user be redirected after module activation? Default to true.
2890
		 */
2891
		do_action( 'jetpack_pre_activate_module', $module, $exit, $redirect );
2892
2893
		$jetpack = Jetpack::init();
2894
2895
		if ( ! strlen( $module ) )
2896
			return false;
2897
2898
		if ( ! Jetpack::is_module( $module ) )
2899
			return false;
2900
2901
		// If it's already active, then don't do it again
2902
		$active = Jetpack::get_active_modules();
2903
		foreach ( $active as $act ) {
2904
			if ( $act == $module )
2905
				return true;
2906
		}
2907
2908
		$module_data = Jetpack::get_module( $module );
2909
2910
		if ( ! Jetpack::is_active() ) {
2911
			if ( ! Jetpack::is_development_mode() && ! Jetpack::is_onboarding() )
2912
				return false;
2913
2914
			// If we're not connected but in development mode, make sure the module doesn't require a connection
2915
			if ( Jetpack::is_development_mode() && $module_data['requires_connection'] )
2916
				return false;
2917
		}
2918
2919
		// Check and see if the old plugin is active
2920
		if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2921
			// Deactivate the old plugin
2922
			if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[ $module ][0], $jetpack->plugins_to_deactivate[ $module ][1] ) ) {
2923
				// If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
2924
				// We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
2925
				Jetpack::state( 'deactivated_plugins', $module );
2926
				wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
2927
				exit;
2928
			}
2929
		}
2930
2931
		// Protect won't work with mis-configured IPs
2932
		if ( 'protect' === $module ) {
2933
			include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
2934
			if ( ! jetpack_protect_get_ip() ) {
2935
				Jetpack::state( 'message', 'protect_misconfigured_ip' );
2936
				return false;
2937
			}
2938
		}
2939
2940
		if ( ! Jetpack::active_plan_supports( $module ) ) {
2941
			return false;
2942
		}
2943
2944
		// Check the file for fatal errors, a la wp-admin/plugins.php::activate
2945
		Jetpack::state( 'module', $module );
2946
		Jetpack::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
2947
2948
		Jetpack::catch_errors( true );
2949
		ob_start();
2950
		require Jetpack::get_module_path( $module );
2951
		/** This action is documented in class.jetpack.php */
2952
		do_action( 'jetpack_activate_module', $module );
2953
		$active[] = $module;
2954
		Jetpack::update_active_modules( $active );
2955
2956
		Jetpack::state( 'error', false ); // the override
2957
		ob_end_clean();
2958
		Jetpack::catch_errors( false );
2959
2960
		// A flag for Jump Start so it's not shown again. Only set if it hasn't been yet.
2961 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2962
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2963
2964
			//Jump start is being dismissed send data to MC Stats
2965
			$jetpack->stat( 'jumpstart', 'manual,'.$module );
2966
2967
			$jetpack->do_stats( 'server_side' );
2968
		}
2969
2970
		if ( $redirect ) {
2971
			wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2972
		}
2973
		if ( $exit ) {
2974
			exit;
2975
		}
2976
		return true;
2977
	}
2978
2979
	function activate_module_actions( $module ) {
2980
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
2981
	}
2982
2983
	public static function deactivate_module( $module ) {
2984
		/**
2985
		 * Fires when a module is deactivated.
2986
		 *
2987
		 * @since 1.9.0
2988
		 *
2989
		 * @param string $module Module slug.
2990
		 */
2991
		do_action( 'jetpack_pre_deactivate_module', $module );
2992
2993
		$jetpack = Jetpack::init();
2994
2995
		$active = Jetpack::get_active_modules();
2996
		$new    = array_filter( array_diff( $active, (array) $module ) );
2997
2998
		// A flag for Jump Start so it's not shown again.
2999 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
3000
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
3001
3002
			//Jump start is being dismissed send data to MC Stats
3003
			$jetpack->stat( 'jumpstart', 'manual,deactivated-'.$module );
3004
3005
			$jetpack->do_stats( 'server_side' );
3006
		}
3007
3008
		return self::update_active_modules( $new );
3009
	}
3010
3011
	public static function enable_module_configurable( $module ) {
3012
		$module = Jetpack::get_module_slug( $module );
3013
		add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
3014
	}
3015
3016
	public static function module_configuration_url( $module ) {
3017
		$module = Jetpack::get_module_slug( $module );
3018
		return Jetpack::admin_url( array( 'page' => 'jetpack', 'configure' => $module ) );
3019
	}
3020
3021
	public static function module_configuration_load( $module, $method ) {
3022
		$module = Jetpack::get_module_slug( $module );
3023
		add_action( 'jetpack_module_configuration_load_' . $module, $method );
3024
	}
3025
3026
	public static function module_configuration_head( $module, $method ) {
3027
		$module = Jetpack::get_module_slug( $module );
3028
		add_action( 'jetpack_module_configuration_head_' . $module, $method );
3029
	}
3030
3031
	public static function module_configuration_screen( $module, $method ) {
3032
		$module = Jetpack::get_module_slug( $module );
3033
		add_action( 'jetpack_module_configuration_screen_' . $module, $method );
3034
	}
3035
3036
	public static function module_configuration_activation_screen( $module, $method ) {
3037
		$module = Jetpack::get_module_slug( $module );
3038
		add_action( 'display_activate_module_setting_' . $module, $method );
3039
	}
3040
3041
/* Installation */
3042
3043
	public static function bail_on_activation( $message, $deactivate = true ) {
3044
?>
3045
<!doctype html>
3046
<html>
3047
<head>
3048
<meta charset="<?php bloginfo( 'charset' ); ?>">
3049
<style>
3050
* {
3051
	text-align: center;
3052
	margin: 0;
3053
	padding: 0;
3054
	font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
3055
}
3056
p {
3057
	margin-top: 1em;
3058
	font-size: 18px;
3059
}
3060
</style>
3061
<body>
3062
<p><?php echo esc_html( $message ); ?></p>
3063
</body>
3064
</html>
3065
<?php
3066
		if ( $deactivate ) {
3067
			$plugins = get_option( 'active_plugins' );
3068
			$jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
3069
			$update  = false;
3070
			foreach ( $plugins as $i => $plugin ) {
3071
				if ( $plugin === $jetpack ) {
3072
					$plugins[$i] = false;
3073
					$update = true;
3074
				}
3075
			}
3076
3077
			if ( $update ) {
3078
				update_option( 'active_plugins', array_filter( $plugins ) );
3079
			}
3080
		}
3081
		exit;
3082
	}
3083
3084
	/**
3085
	 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
3086
	 * @static
3087
	 */
3088
	public static function plugin_activation( $network_wide ) {
3089
		Jetpack_Options::update_option( 'activated', 1 );
3090
3091
		if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
3092
			Jetpack::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
3093
		}
3094
3095
		if ( $network_wide )
3096
			Jetpack::state( 'network_nag', true );
3097
3098
		// For firing one-off events (notices) immediately after activation
3099
		set_transient( 'activated_jetpack', true, .1 * MINUTE_IN_SECONDS );
3100
3101
		update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
3102
3103
		Jetpack::plugin_initialize();
3104
	}
3105
3106
	public static function get_activation_source( $referer_url ) {
3107
3108
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
3109
			return array( 'wp-cli', null );
3110
		}
3111
3112
		$referer = parse_url( $referer_url );
3113
3114
		$source_type = 'unknown';
3115
		$source_query = null;
3116
3117
		if ( ! is_array( $referer ) ) {
3118
			return array( $source_type, $source_query );
3119
		}
3120
3121
		$plugins_path = parse_url( admin_url( 'plugins.php' ), PHP_URL_PATH );
3122
		$plugins_install_path = parse_url( admin_url( 'plugin-install.php' ), PHP_URL_PATH );// /wp-admin/plugin-install.php
3123
3124
		if ( isset( $referer['query'] ) ) {
3125
			parse_str( $referer['query'], $query_parts );
3126
		} else {
3127
			$query_parts = array();
3128
		}
3129
3130
		if ( $plugins_path === $referer['path'] ) {
3131
			$source_type = 'list';
3132
		} elseif ( $plugins_install_path === $referer['path'] ) {
3133
			$tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
3134
			switch( $tab ) {
3135
				case 'popular':
3136
					$source_type = 'popular';
3137
					break;
3138
				case 'recommended':
3139
					$source_type = 'recommended';
3140
					break;
3141
				case 'favorites':
3142
					$source_type = 'favorites';
3143
					break;
3144
				case 'search':
3145
					$source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
3146
					$source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
3147
					break;
3148
				default:
3149
					$source_type = 'featured';
3150
			}
3151
		}
3152
3153
		return array( $source_type, $source_query );
3154
	}
3155
3156
	/**
3157
	 * Runs before bumping version numbers up to a new version
3158
	 * @param  string $version    Version:timestamp
3159
	 * @param  string $old_version Old Version:timestamp or false if not set yet.
3160
	 * @return null              [description]
3161
	 */
3162
	public static function do_version_bump( $version, $old_version ) {
3163
3164
		if ( ! $old_version ) { // For new sites
3165
			// Setting up jetpack manage
3166
			Jetpack::activate_manage();
3167
		}
3168
	}
3169
3170
	/**
3171
	 * Sets the internal version number and activation state.
3172
	 * @static
3173
	 */
3174
	public static function plugin_initialize() {
3175
		if ( ! Jetpack_Options::get_option( 'activated' ) ) {
3176
			Jetpack_Options::update_option( 'activated', 2 );
3177
		}
3178
3179 View Code Duplication
		if ( ! Jetpack_Options::get_option( 'version' ) ) {
3180
			$version = $old_version = JETPACK__VERSION . ':' . time();
3181
			/** This action is documented in class.jetpack.php */
3182
			do_action( 'updating_jetpack_version', $version, false );
3183
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
3184
		}
3185
3186
		Jetpack::load_modules();
3187
3188
		Jetpack_Options::delete_option( 'do_activate' );
3189
		Jetpack_Options::delete_option( 'dismissed_connection_banner' );
3190
	}
3191
3192
	/**
3193
	 * Removes all connection options
3194
	 * @static
3195
	 */
3196
	public static function plugin_deactivation( ) {
3197
		require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
3198
		if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
3199
			Jetpack_Network::init()->deactivate();
3200
		} else {
3201
			Jetpack::disconnect( false );
3202
			//Jetpack_Heartbeat::init()->deactivate();
3203
		}
3204
	}
3205
3206
	/**
3207
	 * Disconnects from the Jetpack servers.
3208
	 * Forgets all connection details and tells the Jetpack servers to do the same.
3209
	 * @static
3210
	 */
3211
	public static function disconnect( $update_activated_state = true ) {
3212
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
3213
		Jetpack::clean_nonces( true );
3214
3215
		// If the site is in an IDC because sync is not allowed,
3216
		// let's make sure to not disconnect the production site.
3217
		if ( ! self::validate_sync_error_idc_option() ) {
3218
			JetpackTracking::record_user_event( 'disconnect_site', array() );
3219
			Jetpack::load_xml_rpc_client();
3220
			$xml = new Jetpack_IXR_Client();
3221
			$xml->query( 'jetpack.deregister' );
3222
		}
3223
3224
		Jetpack_Options::delete_option(
3225
			array(
3226
				'blog_token',
3227
				'user_token',
3228
				'user_tokens',
3229
				'master_user',
3230
				'time_diff',
3231
				'fallback_no_verify_ssl_certs',
3232
			)
3233
		);
3234
3235
		Jetpack_IDC::clear_all_idc_options();
3236
		Jetpack_Options::delete_raw_option( 'jetpack_secrets' );
3237
3238
		if ( $update_activated_state ) {
3239
			Jetpack_Options::update_option( 'activated', 4 );
3240
		}
3241
3242
		if ( $jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' ) ) {
3243
			// Check then record unique disconnection if site has never been disconnected previously
3244
			if ( - 1 == $jetpack_unique_connection['disconnected'] ) {
3245
				$jetpack_unique_connection['disconnected'] = 1;
3246
			} else {
3247
				if ( 0 == $jetpack_unique_connection['disconnected'] ) {
3248
					//track unique disconnect
3249
					$jetpack = Jetpack::init();
3250
3251
					$jetpack->stat( 'connections', 'unique-disconnect' );
3252
					$jetpack->do_stats( 'server_side' );
3253
				}
3254
				// increment number of times disconnected
3255
				$jetpack_unique_connection['disconnected'] += 1;
3256
			}
3257
3258
			Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
3259
		}
3260
3261
		// Delete cached connected user data
3262
		$transient_key = "jetpack_connected_user_data_" . get_current_user_id();
3263
		delete_transient( $transient_key );
3264
3265
		// Delete all the sync related data. Since it could be taking up space.
3266
		require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php';
3267
		Jetpack_Sync_Sender::get_instance()->uninstall();
3268
3269
		// Disable the Heartbeat cron
3270
		Jetpack_Heartbeat::init()->deactivate();
3271
	}
3272
3273
	/**
3274
	 * Unlinks the current user from the linked WordPress.com user
3275
	 */
3276
	public static function unlink_user( $user_id = null ) {
3277
		if ( ! $tokens = Jetpack_Options::get_option( 'user_tokens' ) )
3278
			return false;
3279
3280
		$user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
3281
3282
		if ( Jetpack_Options::get_option( 'master_user' ) == $user_id )
3283
			return false;
3284
3285
		if ( ! isset( $tokens[ $user_id ] ) )
3286
			return false;
3287
3288
		Jetpack::load_xml_rpc_client();
3289
		$xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
3290
		$xml->query( 'jetpack.unlink_user', $user_id );
3291
3292
		unset( $tokens[ $user_id ] );
3293
3294
		Jetpack_Options::update_option( 'user_tokens', $tokens );
3295
3296
		/**
3297
		 * Fires after the current user has been unlinked from WordPress.com.
3298
		 *
3299
		 * @since 4.1.0
3300
		 *
3301
		 * @param int $user_id The current user's ID.
3302
		 */
3303
		do_action( 'jetpack_unlinked_user', $user_id );
3304
3305
		return true;
3306
	}
3307
3308
	/**
3309
	 * Attempts Jetpack registration.  If it fail, a state flag is set: @see ::admin_page_load()
3310
	 */
3311
	public static function try_registration() {
3312
		// The user has agreed to the TOS at some point by now.
3313
		Jetpack_Options::update_option( 'tos_agreed', true );
3314
3315
		// Let's get some testing in beta versions and such.
3316
		if ( self::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
3317
			// Before attempting to connect, let's make sure that the domains are viable.
3318
			$domains_to_check = array_unique( array(
3319
				'siteurl' => parse_url( get_site_url(), PHP_URL_HOST ),
3320
				'homeurl' => parse_url( get_home_url(), PHP_URL_HOST ),
3321
			) );
3322
			foreach ( $domains_to_check as $domain ) {
3323
				$result = Jetpack_Data::is_usable_domain( $domain );
3324
				if ( is_wp_error( $result ) ) {
3325
					return $result;
3326
				}
3327
			}
3328
		}
3329
3330
		$result = Jetpack::register();
3331
3332
		// If there was an error with registration and the site was not registered, record this so we can show a message.
3333
		if ( ! $result || is_wp_error( $result ) ) {
3334
			return $result;
3335
		} else {
3336
			return true;
3337
		}
3338
	}
3339
3340
	/**
3341
	 * Tracking an internal event log. Try not to put too much chaff in here.
3342
	 *
3343
	 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
3344
	 */
3345
	public static function log( $code, $data = null ) {
3346
		// only grab the latest 200 entries
3347
		$log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3348
3349
		// Append our event to the log
3350
		$log_entry = array(
3351
			'time'    => time(),
3352
			'user_id' => get_current_user_id(),
3353
			'blog_id' => Jetpack_Options::get_option( 'id' ),
3354
			'code'    => $code,
3355
		);
3356
		// Don't bother storing it unless we've got some.
3357
		if ( ! is_null( $data ) ) {
3358
			$log_entry['data'] = $data;
3359
		}
3360
		$log[] = $log_entry;
3361
3362
		// Try add_option first, to make sure it's not autoloaded.
3363
		// @todo: Add an add_option method to Jetpack_Options
3364
		if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
3365
			Jetpack_Options::update_option( 'log', $log );
3366
		}
3367
3368
		/**
3369
		 * Fires when Jetpack logs an internal event.
3370
		 *
3371
		 * @since 3.0.0
3372
		 *
3373
		 * @param array $log_entry {
3374
		 *	Array of details about the log entry.
3375
		 *
3376
		 *	@param string time Time of the event.
3377
		 *	@param int user_id ID of the user who trigerred the event.
3378
		 *	@param int blog_id Jetpack Blog ID.
3379
		 *	@param string code Unique name for the event.
3380
		 *	@param string data Data about the event.
3381
		 * }
3382
		 */
3383
		do_action( 'jetpack_log_entry', $log_entry );
3384
	}
3385
3386
	/**
3387
	 * Get the internal event log.
3388
	 *
3389
	 * @param $event (string) - only return the specific log events
3390
	 * @param $num   (int)    - get specific number of latest results, limited to 200
3391
	 *
3392
	 * @return array of log events || WP_Error for invalid params
3393
	 */
3394
	public static function get_log( $event = false, $num = false ) {
3395
		if ( $event && ! is_string( $event ) ) {
3396
			return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
3397
		}
3398
3399
		if ( $num && ! is_numeric( $num ) ) {
3400
			return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
3401
		}
3402
3403
		$entire_log = Jetpack_Options::get_option( 'log', array() );
3404
3405
		// If nothing set - act as it did before, otherwise let's start customizing the output
3406
		if ( ! $num && ! $event ) {
3407
			return $entire_log;
3408
		} else {
3409
			$entire_log = array_reverse( $entire_log );
3410
		}
3411
3412
		$custom_log_output = array();
3413
3414
		if ( $event ) {
3415
			foreach ( $entire_log as $log_event ) {
3416
				if ( $event == $log_event[ 'code' ] ) {
3417
					$custom_log_output[] = $log_event;
3418
				}
3419
			}
3420
		} else {
3421
			$custom_log_output = $entire_log;
3422
		}
3423
3424
		if ( $num ) {
3425
			$custom_log_output = array_slice( $custom_log_output, 0, $num );
3426
		}
3427
3428
		return $custom_log_output;
3429
	}
3430
3431
	/**
3432
	 * Log modification of important settings.
3433
	 */
3434
	public static function log_settings_change( $option, $old_value, $value ) {
3435
		switch( $option ) {
3436
			case 'jetpack_sync_non_public_post_stati':
3437
				self::log( $option, $value );
3438
				break;
3439
		}
3440
	}
3441
3442
	/**
3443
	 * Return stat data for WPCOM sync
3444
	 */
3445
	public static function get_stat_data( $encode = true, $extended = true ) {
3446
		$data = Jetpack_Heartbeat::generate_stats_array();
3447
3448
		if ( $extended ) {
3449
			$additional_data = self::get_additional_stat_data();
3450
			$data = array_merge( $data, $additional_data );
3451
		}
3452
3453
		if ( $encode ) {
3454
			return json_encode( $data );
3455
		}
3456
3457
		return $data;
3458
	}
3459
3460
	/**
3461
	 * Get additional stat data to sync to WPCOM
3462
	 */
3463
	public static function get_additional_stat_data( $prefix = '' ) {
3464
		$return["{$prefix}themes"]         = Jetpack::get_parsed_theme_data();
3465
		$return["{$prefix}plugins-extra"]  = Jetpack::get_parsed_plugin_data();
3466
		$return["{$prefix}users"]          = (int) Jetpack::get_site_user_count();
3467
		$return["{$prefix}site-count"]     = 0;
3468
3469
		if ( function_exists( 'get_blog_count' ) ) {
3470
			$return["{$prefix}site-count"] = get_blog_count();
3471
		}
3472
		return $return;
3473
	}
3474
3475
	private static function get_site_user_count() {
3476
		global $wpdb;
3477
3478
		if ( function_exists( 'wp_is_large_network' ) ) {
3479
			if ( wp_is_large_network( 'users' ) ) {
3480
				return -1; // Not a real value but should tell us that we are dealing with a large network.
3481
			}
3482
		}
3483 View Code Duplication
		if ( false === ( $user_count = get_transient( 'jetpack_site_user_count' ) ) ) {
3484
			// It wasn't there, so regenerate the data and save the transient
3485
			$user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities'" );
3486
			set_transient( 'jetpack_site_user_count', $user_count, DAY_IN_SECONDS );
3487
		}
3488
		return $user_count;
3489
	}
3490
3491
	/* Admin Pages */
3492
3493
	function admin_init() {
3494
		// If the plugin is not connected, display a connect message.
3495
		if (
3496
			// the plugin was auto-activated and needs its candy
3497
			Jetpack_Options::get_option_and_ensure_autoload( 'do_activate', '0' )
3498
		||
3499
			// the plugin is active, but was never activated.  Probably came from a site-wide network activation
3500
			! Jetpack_Options::get_option( 'activated' )
3501
		) {
3502
			Jetpack::plugin_initialize();
3503
		}
3504
3505
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
3506
			Jetpack_Connection_Banner::init();
3507
		} elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
3508
			// Upgrade: 1.1 -> 1.1.1
3509
			// Check and see if host can verify the Jetpack servers' SSL certificate
3510
			$args = array();
3511
			Jetpack_Client::_wp_remote_request(
3512
				Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'test' ) ),
3513
				$args,
3514
				true
3515
			);
3516
		} else if ( $this->can_display_jetpack_manage_notice() && ! Jetpack_Options::get_option( 'dismissed_manage_banner' ) ) {
3517
			// Show the notice on the Dashboard only for now
3518
			add_action( 'load-index.php', array( $this, 'prepare_manage_jetpack_notice' ) );
3519
		}
3520
3521
		if ( current_user_can( 'manage_options' ) && 'AUTO' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
3522
			add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3523
		}
3524
3525
		add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3526
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3527
		add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3528
3529
		if ( Jetpack::is_active() || Jetpack::is_development_mode() ) {
3530
			// Artificially throw errors in certain whitelisted cases during plugin activation
3531
			add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3532
		}
3533
3534
		// Jetpack Manage Activation Screen from .com
3535
		Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
3536
3537
		// Add custom column in wp-admin/users.php to show whether user is linked.
3538
		add_filter( 'manage_users_columns',       array( $this, 'jetpack_icon_user_connected' ) );
3539
		add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_user_connected_icon' ), 10, 3 );
3540
		add_action( 'admin_print_styles',         array( $this, 'jetpack_user_col_style' ) );
3541
	}
3542
3543
	function admin_body_class( $admin_body_class = '' ) {
3544
		$classes = explode( ' ', trim( $admin_body_class ) );
3545
3546
		$classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
3547
3548
		$admin_body_class = implode( ' ', array_unique( $classes ) );
3549
		return " $admin_body_class ";
3550
	}
3551
3552
	static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3553
		return $admin_body_class . ' jetpack-pagestyles ';
3554
	}
3555
3556
	/**
3557
	 * Call this function if you want the Big Jetpack Manage Notice to show up.
3558
	 *
3559
	 * @return null
3560
	 */
3561
	function prepare_manage_jetpack_notice() {
3562
3563
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3564
		add_action( 'admin_notices', array( $this, 'admin_jetpack_manage_notice' ) );
3565
	}
3566
3567
	function manage_activate_screen() {
3568
		include ( JETPACK__PLUGIN_DIR . 'modules/manage/activate-admin.php' );
3569
	}
3570
	/**
3571
	 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3572
	 * This function artificially throws errors for such cases (whitelisted).
3573
	 *
3574
	 * @param string $plugin The activated plugin.
3575
	 */
3576
	function throw_error_on_activate_plugin( $plugin ) {
3577
		$active_modules = Jetpack::get_active_modules();
3578
3579
		// The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3580
		if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3581
			$throw = false;
3582
3583
			// Try and make sure it really was the stats plugin
3584
			if ( ! class_exists( 'ReflectionFunction' ) ) {
3585
				if ( 'stats.php' == basename( $plugin ) ) {
3586
					$throw = true;
3587
				}
3588
			} else {
3589
				$reflection = new ReflectionFunction( 'stats_get_api_key' );
3590
				if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3591
					$throw = true;
3592
				}
3593
			}
3594
3595
			if ( $throw ) {
3596
				trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3597
			}
3598
		}
3599
	}
3600
3601
	function intercept_plugin_error_scrape_init() {
3602
		add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3603
	}
3604
3605
	function intercept_plugin_error_scrape( $action, $result ) {
3606
		if ( ! $result ) {
3607
			return;
3608
		}
3609
3610
		foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3611
			if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3612
				Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3613
			}
3614
		}
3615
	}
3616
3617
	function add_remote_request_handlers() {
3618
		add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
3619
		add_action( 'wp_ajax_nopriv_jetpack_update_file', array( $this, 'remote_request_handlers' ) );
3620
	}
3621
3622
	function remote_request_handlers() {
3623
		$action = current_filter();
3624
3625
		switch ( current_filter() ) {
3626
		case 'wp_ajax_nopriv_jetpack_upload_file' :
3627
			$response = $this->upload_handler();
3628
			break;
3629
3630
		case 'wp_ajax_nopriv_jetpack_update_file' :
3631
			$response = $this->upload_handler( true );
3632
			break;
3633
		default :
3634
			$response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
3635
			break;
3636
		}
3637
3638
		if ( ! $response ) {
3639
			$response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
3640
		}
3641
3642
		if ( is_wp_error( $response ) ) {
3643
			$status_code       = $response->get_error_data();
3644
			$error             = $response->get_error_code();
3645
			$error_description = $response->get_error_message();
3646
3647
			if ( ! is_int( $status_code ) ) {
3648
				$status_code = 400;
3649
			}
3650
3651
			status_header( $status_code );
3652
			die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3653
		}
3654
3655
		status_header( 200 );
3656
		if ( true === $response ) {
3657
			exit;
3658
		}
3659
3660
		die( json_encode( (object) $response ) );
3661
	}
3662
3663
	/**
3664
	 * Uploads a file gotten from the global $_FILES.
3665
	 * If `$update_media_item` is true and `post_id` is defined
3666
	 * the attachment file of the media item (gotten through of the post_id)
3667
	 * will be updated instead of add a new one.
3668
	 *
3669
	 * @param  boolean $update_media_item - update media attachment
3670
	 * @return array - An array describing the uploadind files process
3671
	 */
3672
	function upload_handler( $update_media_item = false ) {
3673
		if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3674
			return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
3675
		}
3676
3677
		$user = wp_authenticate( '', '' );
3678
		if ( ! $user || is_wp_error( $user ) ) {
3679
			return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
3680
		}
3681
3682
		wp_set_current_user( $user->ID );
3683
3684
		if ( ! current_user_can( 'upload_files' ) ) {
3685
			return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3686
		}
3687
3688
		if ( empty( $_FILES ) ) {
3689
			return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3690
		}
3691
3692
		foreach ( array_keys( $_FILES ) as $files_key ) {
3693
			if ( ! isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
3694
				return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3695
			}
3696
		}
3697
3698
		$media_keys = array_keys( $_FILES['media'] );
3699
3700
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
3701
		if ( ! $token || is_wp_error( $token ) ) {
3702
			return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3703
		}
3704
3705
		$uploaded_files = array();
3706
		$global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3707
		unset( $GLOBALS['post'] );
3708
		foreach ( $_FILES['media']['name'] as $index => $name ) {
3709
			$file = array();
3710
			foreach ( $media_keys as $media_key ) {
3711
				$file[$media_key] = $_FILES['media'][$media_key][$index];
3712
			}
3713
3714
			list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
3715
3716
			$hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3717
			if ( $hmac_provided !== $hmac_file ) {
3718
				$uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
3719
				continue;
3720
			}
3721
3722
			$_FILES['.jetpack.upload.'] = $file;
3723
			$post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
3724
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
3725
				$post_id = 0;
3726
			}
3727
3728
			if ( $update_media_item ) {
3729
				if ( ! isset( $post_id ) || $post_id === 0 ) {
3730
					return new Jetpack_Error( 'invalid_input', 'Media ID must be defined.', 400 );
3731
				}
3732
3733
				$media_array = $_FILES['media'];
3734
3735
				$file_array['name'] = $media_array['name'][0];
3736
				$file_array['type'] = $media_array['type'][0];
3737
				$file_array['tmp_name'] = $media_array['tmp_name'][0];
3738
				$file_array['error'] = $media_array['error'][0];
3739
				$file_array['size'] = $media_array['size'][0];
3740
3741
				$edited_media_item = Jetpack_Media::edit_media_file( $post_id, $file_array );
3742
3743
				if ( is_wp_error( $edited_media_item ) ) {
3744
					return $edited_media_item;
3745
				}
3746
3747
				$response = (object) array(
3748
					'id'   => (string) $post_id,
3749
					'file' => (string) $edited_media_item->post_title,
3750
					'url'  => (string) wp_get_attachment_url( $post_id ),
3751
					'type' => (string) $edited_media_item->post_mime_type,
3752
					'meta' => (array) wp_get_attachment_metadata( $post_id ),
3753
				);
3754
3755
				return (array) array( $response );
3756
			}
3757
3758
			$attachment_id = media_handle_upload(
3759
				'.jetpack.upload.',
3760
				$post_id,
3761
				array(),
3762
				array(
3763
					'action' => 'jetpack_upload_file',
3764
				)
3765
			);
3766
3767
			if ( ! $attachment_id ) {
3768
				$uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
3769
			} elseif ( is_wp_error( $attachment_id ) ) {
3770
				$uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
3771
			} else {
3772
				$attachment = get_post( $attachment_id );
3773
				$uploaded_files[$index] = (object) array(
3774
					'id'   => (string) $attachment_id,
3775
					'file' => $attachment->post_title,
3776
					'url'  => wp_get_attachment_url( $attachment_id ),
3777
					'type' => $attachment->post_mime_type,
3778
					'meta' => wp_get_attachment_metadata( $attachment_id ),
3779
				);
3780
				// Zip files uploads are not supported unless they are done for installation purposed
3781
				// lets delete them in case something goes wrong in this whole process
3782
				if ( 'application/zip' === $attachment->post_mime_type ) {
3783
					// Schedule a cleanup for 2 hours from now in case of failed install.
3784
					wp_schedule_single_event( time() + 2 * HOUR_IN_SECONDS, 'upgrader_scheduled_cleanup', array( $attachment_id ) );
3785
				}
3786
			}
3787
		}
3788
		if ( ! is_null( $global_post ) ) {
3789
			$GLOBALS['post'] = $global_post;
3790
		}
3791
3792
		return $uploaded_files;
3793
	}
3794
3795
	/**
3796
	 * Add help to the Jetpack page
3797
	 *
3798
	 * @since Jetpack (1.2.3)
3799
	 * @return false if not the Jetpack page
3800
	 */
3801
	function admin_help() {
3802
		$current_screen = get_current_screen();
3803
3804
		// Overview
3805
		$current_screen->add_help_tab(
3806
			array(
3807
				'id'		=> 'home',
3808
				'title'		=> __( 'Home', 'jetpack' ),
3809
				'content'	=>
3810
					'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3811
					'<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3812
					'<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>',
3813
			)
3814
		);
3815
3816
		// Screen Content
3817
		if ( current_user_can( 'manage_options' ) ) {
3818
			$current_screen->add_help_tab(
3819
				array(
3820
					'id'		=> 'settings',
3821
					'title'		=> __( 'Settings', 'jetpack' ),
3822
					'content'	=>
3823
						'<p><strong>' . __( 'Jetpack by WordPress.com',                                              'jetpack' ) . '</strong></p>' .
3824
						'<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3825
						'<ol>' .
3826
							'<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.',														'jetpack' ) . '</li>' .
3827
							'<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>' .
3828
						'</ol>' .
3829
						'<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>'
3830
				)
3831
			);
3832
		}
3833
3834
		// Help Sidebar
3835
		$current_screen->set_help_sidebar(
3836
			'<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3837
			'<p><a href="https://jetpack.com/faq/" target="_blank">'     . __( 'Jetpack FAQ',     'jetpack' ) . '</a></p>' .
3838
			'<p><a href="https://jetpack.com/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3839
			'<p><a href="' . Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) .'">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3840
		);
3841
	}
3842
3843
	function admin_menu_css() {
3844
		wp_enqueue_style( 'jetpack-icons' );
3845
	}
3846
3847
	function admin_menu_order() {
3848
		return true;
3849
	}
3850
3851
	function enqueue_gutenberg_locale() {
3852
		wp_add_inline_script(
3853
			'wp-i18n',
3854
			'wp.i18n.setLocaleData( ' . self::get_i18n_data_json() . ', \'jetpack\' );'
3855
		);
3856
	}
3857
3858 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
3859
		$jp_menu_order = array();
3860
3861
		foreach ( $menu_order as $index => $item ) {
3862
			if ( $item != 'jetpack' ) {
3863
				$jp_menu_order[] = $item;
3864
			}
3865
3866
			if ( $index == 0 ) {
3867
				$jp_menu_order[] = 'jetpack';
3868
			}
3869
		}
3870
3871
		return $jp_menu_order;
3872
	}
3873
3874
	function admin_head() {
3875 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) )
3876
			/** This action is documented in class.jetpack-admin-page.php */
3877
			do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
3878
	}
3879
3880
	function admin_banner_styles() {
3881
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3882
3883
		if ( ! wp_style_is( 'jetpack-dops-style' ) ) {
3884
			wp_register_style(
3885
				'jetpack-dops-style',
3886
				plugins_url( '_inc/build/admin.dops-style.css', JETPACK__PLUGIN_FILE ),
3887
				array(),
3888
				JETPACK__VERSION
3889
			);
3890
		}
3891
3892
		wp_enqueue_style(
3893
			'jetpack',
3894
			plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ),
3895
			array( 'jetpack-dops-style' ),
3896
			 JETPACK__VERSION . '-20121016'
3897
		);
3898
		wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3899
		wp_style_add_data( 'jetpack', 'suffix', $min );
3900
	}
3901
3902
	function plugin_action_links( $actions ) {
3903
3904
		$jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack' ), __( 'Jetpack', 'jetpack' ) ) );
3905
3906
		if( current_user_can( 'jetpack_manage_modules' ) && ( Jetpack::is_active() || Jetpack::is_development_mode() ) ) {
3907
			return array_merge(
3908
				$jetpack_home,
3909
				array( 'settings' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack#/settings' ), __( 'Settings', 'jetpack' ) ) ),
3910
				array( 'support' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack-debugger '), __( 'Support', 'jetpack' ) ) ),
3911
				$actions
3912
				);
3913
			}
3914
3915
		return array_merge( $jetpack_home, $actions );
3916
	}
3917
3918
	/**
3919
	 * This is the first banner
3920
	 * It should be visible only to user that can update the option
3921
	 * Are not connected
3922
	 *
3923
	 * @return null
3924
	 */
3925
	function admin_jetpack_manage_notice() {
3926
		$screen = get_current_screen();
3927
3928
		// Don't show the connect notice on the jetpack settings page.
3929
		if ( ! in_array( $screen->base, array( 'dashboard' ) ) || $screen->is_network || $screen->action ) {
3930
			return;
3931
		}
3932
3933
		$opt_out_url = $this->opt_out_jetpack_manage_url();
3934
		$opt_in_url  = $this->opt_in_jetpack_manage_url();
3935
		/**
3936
		 * I think it would be great to have different wordsing depending on where you are
3937
		 * for example if we show the notice on dashboard and a different one if we show it on Plugins screen
3938
		 * etc..
3939
		 */
3940
3941
		?>
3942
		<div id="message" class="updated jp-banner">
3943
				<a href="<?php echo esc_url( $opt_out_url ); ?>" class="notice-dismiss" title="<?php esc_attr_e( 'Dismiss this notice', 'jetpack' ); ?>"></a>
3944
				<div class="jp-banner__description-container">
3945
					<h2 class="jp-banner__header"><?php esc_html_e( 'Jetpack Centralized Site Management', 'jetpack' ); ?></h2>
3946
					<p class="jp-banner__description"><?php printf( __( 'Manage multiple Jetpack enabled sites from one single dashboard at wordpress.com. Allows all existing, connected Administrators to modify your site.', 'jetpack' ), 'https://jetpack.com/support/site-management' ); ?></p>
3947
					<p class="jp-banner__button-container">
3948
						<a href="<?php echo esc_url( $opt_in_url ); ?>" class="button button-primary" id="wpcom-connect"><?php _e( 'Activate Jetpack Manage', 'jetpack' ); ?></a>
3949
						<a href="https://jetpack.com/support/site-management" class="button" target="_blank" title="<?php esc_attr_e( 'Learn more about Jetpack Manage on Jetpack.com', 'jetpack' ); ?>"><?php _e( 'Learn more', 'jetpack' ); ?></a>
3950
					</p>
3951
				</div>
3952
		</div>
3953
		<?php
3954
	}
3955
3956
	/**
3957
	 * Returns the url that the user clicks to remove the notice for the big banner
3958
	 * @return string
3959
	 */
3960
	function opt_out_jetpack_manage_url() {
3961
		$referer = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
3962
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-out' . $referer ), 'jetpack_manage_banner_opt_out' );
3963
	}
3964
	/**
3965
	 * Returns the url that the user clicks to opt in to Jetpack Manage
3966
	 * @return string
3967
	 */
3968
	function opt_in_jetpack_manage_url() {
3969
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-in' ), 'jetpack_manage_banner_opt_in' );
3970
	}
3971
3972
	function opt_in_jetpack_manage_notice() {
3973
		?>
3974
		<div class="wrap">
3975
			<div id="message" class="jetpack-message is-opt-in">
3976
				<?php echo sprintf( __( '<p><a href="%1$s" title="Opt in to WordPress.com Site Management" >Activate Site Management</a> to manage multiple sites from our centralized dashboard at wordpress.com/sites. <a href="%2$s" target="_blank">Learn more</a>.</p><a href="%1$s" class="jp-button">Activate Now</a>', 'jetpack' ), $this->opt_in_jetpack_manage_url(), 'https://jetpack.com/support/site-management' ); ?>
3977
			</div>
3978
		</div>
3979
		<?php
3980
3981
	}
3982
	/**
3983
	 * Determines whether to show the notice of not true = display notice
3984
	 * @return bool
3985
	 */
3986
	function can_display_jetpack_manage_notice() {
3987
		// never display the notice to users that can't do anything about it anyways
3988
		if( ! current_user_can( 'jetpack_manage_modules' ) )
3989
			return false;
3990
3991
		// don't display if we are in development more
3992
		if( Jetpack::is_development_mode() ) {
3993
			return false;
3994
		}
3995
		// don't display if the site is private
3996
		if(  ! Jetpack_Options::get_option( 'public' ) )
3997
			return false;
3998
3999
		/**
4000
		 * Should the Jetpack Remote Site Management notice be displayed.
4001
		 *
4002
		 * @since 3.3.0
4003
		 *
4004
		 * @param bool ! self::is_module_active( 'manage' ) Is the Manage module inactive.
4005
		 */
4006
		return apply_filters( 'can_display_jetpack_manage_notice', ! self::is_module_active( 'manage' ) );
4007
	}
4008
4009
	/*
4010
	 * Registration flow:
4011
	 * 1 - ::admin_page_load() action=register
4012
	 * 2 - ::try_registration()
4013
	 * 3 - ::register()
4014
	 *     - Creates jetpack_register option containing two secrets and a timestamp
4015
	 *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
4016
	 *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
4017
	 *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
4018
	 *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
4019
	 *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
4020
	 *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
4021
	 *       jetpack_id, jetpack_secret, jetpack_public
4022
	 *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
4023
	 * 4 - redirect to https://wordpress.com/start/jetpack-connect
4024
	 * 5 - user logs in with WP.com account
4025
	 * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
4026
	 *		- Jetpack_Client_Server::authorize()
4027
	 *		- Jetpack_Client_Server::get_token()
4028
	 *		- GET https://jetpack.wordpress.com/jetpack.token/1/ with
4029
	 *        client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
4030
	 *			- which responds with access_token, token_type, scope
4031
	 *		- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
4032
	 *		- Jetpack::activate_default_modules()
4033
	 *     		- Deactivates deprecated plugins
4034
	 *     		- Activates all default modules
4035
	 *		- Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
4036
	 * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
4037
	 * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
4038
	 *     Done!
4039
	 */
4040
4041
	/**
4042
	 * Handles the page load events for the Jetpack admin page
4043
	 */
4044
	function admin_page_load() {
4045
		$error = false;
4046
4047
		// Make sure we have the right body class to hook stylings for subpages off of.
4048
		add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ) );
4049
4050
		if ( ! empty( $_GET['jetpack_restate'] ) ) {
4051
			// Should only be used in intermediate redirects to preserve state across redirects
4052
			Jetpack::restate();
4053
		}
4054
4055
		if ( isset( $_GET['connect_url_redirect'] ) ) {
4056
			// User clicked in the iframe to link their accounts
4057
			if ( ! Jetpack::is_user_connected() ) {
4058
				$from = ! empty( $_GET['from'] ) ? $_GET['from'] : 'iframe';
4059
				$redirect = ! empty( $_GET['redirect_after_auth'] ) ? $_GET['redirect_after_auth'] : false;
4060
4061
				add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_environments' ) );
4062
				$connect_url = $this->build_connect_url( true, $redirect, $from );
4063
				remove_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_environments' ) );
4064
4065
				if ( isset( $_GET['notes_iframe'] ) )
4066
					$connect_url .= '&notes_iframe';
4067
				wp_redirect( $connect_url );
4068
				exit;
4069
			} else {
4070
				if ( ! isset( $_GET['calypso_env'] ) ) {
4071
					Jetpack::state( 'message', 'already_authorized' );
4072
					wp_safe_redirect( Jetpack::admin_url() );
4073
					exit;
4074
				} else {
4075
					$connect_url = $this->build_connect_url( true, false, 'iframe' );
4076
					$connect_url .= '&already_authorized=true';
4077
					wp_redirect( $connect_url );
4078
					exit;
4079
				}
4080
			}
4081
		}
4082
4083
4084
		if ( isset( $_GET['action'] ) ) {
4085
			switch ( $_GET['action'] ) {
4086
			case 'authorize':
4087
				if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
4088
					Jetpack::state( 'message', 'already_authorized' );
4089
					wp_safe_redirect( Jetpack::admin_url() );
4090
					exit;
4091
				}
4092
				Jetpack::log( 'authorize' );
4093
				$client_server = new Jetpack_Client_Server;
4094
				$client_server->client_authorize();
4095
				exit;
4096
			case 'register' :
4097
				if ( ! current_user_can( 'jetpack_connect' ) ) {
4098
					$error = 'cheatin';
4099
					break;
4100
				}
4101
				check_admin_referer( 'jetpack-register' );
4102
				Jetpack::log( 'register' );
4103
				Jetpack::maybe_set_version_option();
4104
				$registered = Jetpack::try_registration();
4105
				if ( is_wp_error( $registered ) ) {
4106
					$error = $registered->get_error_code();
4107
					Jetpack::state( 'error', $error );
4108
					Jetpack::state( 'error', $registered->get_error_message() );
4109
					JetpackTracking::record_user_event( 'jpc_register_fail', array(
4110
						'error_code' => $error,
4111
						'error_message' => $registered->get_error_message()
4112
					) );
4113
					break;
4114
				}
4115
4116
				$from = isset( $_GET['from'] ) ? $_GET['from'] : false;
4117
				$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : false;
4118
4119
				JetpackTracking::record_user_event( 'jpc_register_success', array(
4120
					'from' => $from
4121
				) );
4122
4123
				$url = $this->build_connect_url( true, $redirect, $from );
4124
4125
				if ( ! empty( $_GET['onboarding'] ) ) {
4126
					$url = add_query_arg( 'onboarding', $_GET['onboarding'], $url );
4127
				}
4128
4129
				if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
4130
					$url = add_query_arg( 'auth_approved', 'true', $url );
4131
				}
4132
4133
				wp_redirect( $url );
4134
				exit;
4135
			case 'activate' :
4136
				if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
4137
					$error = 'cheatin';
4138
					break;
4139
				}
4140
4141
				$module = stripslashes( $_GET['module'] );
4142
				check_admin_referer( "jetpack_activate-$module" );
4143
				Jetpack::log( 'activate', $module );
4144
				if ( ! Jetpack::activate_module( $module ) ) {
4145
					Jetpack::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
4146
				}
4147
				// The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
4148
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4149
				exit;
4150
			case 'activate_default_modules' :
4151
				check_admin_referer( 'activate_default_modules' );
4152
				Jetpack::log( 'activate_default_modules' );
4153
				Jetpack::restate();
4154
				$min_version   = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
4155
				$max_version   = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
4156
				$other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
4157
				Jetpack::activate_default_modules( $min_version, $max_version, $other_modules );
4158
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4159
				exit;
4160
			case 'disconnect' :
4161
				if ( ! current_user_can( 'jetpack_disconnect' ) ) {
4162
					$error = 'cheatin';
4163
					break;
4164
				}
4165
4166
				check_admin_referer( 'jetpack-disconnect' );
4167
				Jetpack::log( 'disconnect' );
4168
				Jetpack::disconnect();
4169
				wp_safe_redirect( Jetpack::admin_url( 'disconnected=true' ) );
4170
				exit;
4171
			case 'reconnect' :
4172
				if ( ! current_user_can( 'jetpack_reconnect' ) ) {
4173
					$error = 'cheatin';
4174
					break;
4175
				}
4176
4177
				check_admin_referer( 'jetpack-reconnect' );
4178
				Jetpack::log( 'reconnect' );
4179
				$this->disconnect();
4180
				wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
4181
				exit;
4182 View Code Duplication
			case 'deactivate' :
4183
				if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
4184
					$error = 'cheatin';
4185
					break;
4186
				}
4187
4188
				$modules = stripslashes( $_GET['module'] );
4189
				check_admin_referer( "jetpack_deactivate-$modules" );
4190
				foreach ( explode( ',', $modules ) as $module ) {
4191
					Jetpack::log( 'deactivate', $module );
4192
					Jetpack::deactivate_module( $module );
4193
					Jetpack::state( 'message', 'module_deactivated' );
4194
				}
4195
				Jetpack::state( 'module', $modules );
4196
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4197
				exit;
4198
			case 'unlink' :
4199
				$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
4200
				check_admin_referer( 'jetpack-unlink' );
4201
				Jetpack::log( 'unlink' );
4202
				$this->unlink_user();
4203
				Jetpack::state( 'message', 'unlinked' );
4204
				if ( 'sub-unlink' == $redirect ) {
4205
					wp_safe_redirect( admin_url() );
4206
				} else {
4207
					wp_safe_redirect( Jetpack::admin_url( array( 'page' => $redirect ) ) );
4208
				}
4209
				exit;
4210
			case 'onboard' :
4211
				if ( ! current_user_can( 'manage_options' ) ) {
4212
					wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4213
				} else {
4214
					Jetpack::create_onboarding_token();
4215
					$url = $this->build_connect_url( true );
4216
4217
					if ( false !== ( $token = Jetpack_Options::get_option( 'onboarding' ) ) ) {
4218
						$url = add_query_arg( 'onboarding', $token, $url );
4219
					}
4220
4221
					$calypso_env = ! empty( $_GET[ 'calypso_env' ] ) ? $_GET[ 'calypso_env' ] : false;
4222
					if ( $calypso_env ) {
4223
						$url = add_query_arg( 'calypso_env', $calypso_env, $url );
4224
					}
4225
4226
					wp_redirect( $url );
4227
					exit;
4228
				}
4229
				exit;
4230
			default:
4231
				/**
4232
				 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
4233
				 *
4234
				 * @since 2.6.0
4235
				 *
4236
				 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
4237
				 */
4238
				do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
4239
			}
4240
		}
4241
4242
		if ( ! $error = $error ? $error : Jetpack::state( 'error' ) ) {
4243
			self::activate_new_modules( true );
4244
		}
4245
4246
		$message_code = Jetpack::state( 'message' );
4247
		if ( Jetpack::state( 'optin-manage' ) ) {
4248
			$activated_manage = $message_code;
4249
			$message_code = 'jetpack-manage';
4250
		}
4251
4252
		switch ( $message_code ) {
4253
		case 'jetpack-manage':
4254
			$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>';
4255
			if ( $activated_manage ) {
4256
				$this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack'  ) . '</strong>';
4257
			}
4258
			break;
4259
4260
		}
4261
4262
		$deactivated_plugins = Jetpack::state( 'deactivated_plugins' );
4263
4264
		if ( ! empty( $deactivated_plugins ) ) {
4265
			$deactivated_plugins = explode( ',', $deactivated_plugins );
4266
			$deactivated_titles  = array();
4267
			foreach ( $deactivated_plugins as $deactivated_plugin ) {
4268
				if ( ! isset( $this->plugins_to_deactivate[$deactivated_plugin] ) ) {
4269
					continue;
4270
				}
4271
4272
				$deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[$deactivated_plugin][1] ) . '</strong>';
4273
			}
4274
4275
			if ( $deactivated_titles ) {
4276
				if ( $this->message ) {
4277
					$this->message .= "<br /><br />\n";
4278
				}
4279
4280
				$this->message .= wp_sprintf(
4281
					_n(
4282
						'Jetpack contains the most recent version of the old %l plugin.',
4283
						'Jetpack contains the most recent versions of the old %l plugins.',
4284
						count( $deactivated_titles ),
4285
						'jetpack'
4286
					),
4287
					$deactivated_titles
4288
				);
4289
4290
				$this->message .= "<br />\n";
4291
4292
				$this->message .= _n(
4293
					'The old version has been deactivated and can be removed from your site.',
4294
					'The old versions have been deactivated and can be removed from your site.',
4295
					count( $deactivated_titles ),
4296
					'jetpack'
4297
				);
4298
			}
4299
		}
4300
4301
		$this->privacy_checks = Jetpack::state( 'privacy_checks' );
4302
4303
		if ( $this->message || $this->error || $this->privacy_checks || $this->can_display_jetpack_manage_notice() ) {
4304
			add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
4305
		}
4306
4307 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
4308
			/**
4309
			 * Fires when a module configuration page is loaded.
4310
			 * The dynamic part of the hook is the configure parameter from the URL.
4311
			 *
4312
			 * @since 1.1.0
4313
			 */
4314
			do_action( 'jetpack_module_configuration_load_' . $_GET['configure'] );
4315
		}
4316
4317
		add_filter( 'jetpack_short_module_description', 'wptexturize' );
4318
	}
4319
4320
	function admin_notices() {
4321
4322
		if ( $this->error ) {
4323
?>
4324
<div id="message" class="jetpack-message jetpack-err">
4325
	<div class="squeezer">
4326
		<h2><?php echo wp_kses( $this->error, array( 'a' => array( 'href' => array() ), 'small' => true, 'code' => true, 'strong' => true, 'br' => true, 'b' => true ) ); ?></h2>
4327
<?php	if ( $desc = Jetpack::state( 'error_description' ) ) : ?>
4328
		<p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
4329
<?php	endif; ?>
4330
	</div>
4331
</div>
4332
<?php
4333
		}
4334
4335
		if ( $this->message ) {
4336
?>
4337
<div id="message" class="jetpack-message">
4338
	<div class="squeezer">
4339
		<h2><?php echo wp_kses( $this->message, array( 'strong' => array(), 'a' => array( 'href' => true ), 'br' => true ) ); ?></h2>
4340
	</div>
4341
</div>
4342
<?php
4343
		}
4344
4345
		if ( $this->privacy_checks ) :
4346
			$module_names = $module_slugs = array();
4347
4348
			$privacy_checks = explode( ',', $this->privacy_checks );
4349
			$privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
4350
			foreach ( $privacy_checks as $module_slug ) {
4351
				$module = Jetpack::get_module( $module_slug );
4352
				if ( ! $module ) {
4353
					continue;
4354
				}
4355
4356
				$module_slugs[] = $module_slug;
4357
				$module_names[] = "<strong>{$module['name']}</strong>";
4358
			}
4359
4360
			$module_slugs = join( ',', $module_slugs );
4361
?>
4362
<div id="message" class="jetpack-message jetpack-err">
4363
	<div class="squeezer">
4364
		<h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
4365
		<p><?php
4366
			echo wp_kses(
4367
				wptexturize(
4368
					wp_sprintf(
4369
						_nx(
4370
							"Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
4371
							"Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
4372
							count( $privacy_checks ),
4373
							'%l = list of Jetpack module/feature names',
4374
							'jetpack'
4375
						),
4376
						$module_names
4377
					)
4378
				),
4379
				array( 'strong' => true )
4380
			);
4381
4382
			echo "\n<br />\n";
4383
4384
			echo wp_kses(
4385
				sprintf(
4386
					_nx(
4387
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
4388
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
4389
						count( $privacy_checks ),
4390
						'%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
4391
						'jetpack'
4392
					),
4393
					wp_nonce_url(
4394
						Jetpack::admin_url(
4395
							array(
4396
								'page'   => 'jetpack',
4397
								'action' => 'deactivate',
4398
								'module' => urlencode( $module_slugs ),
4399
							)
4400
						),
4401
						"jetpack_deactivate-$module_slugs"
4402
					),
4403
					esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
4404
				),
4405
				array( 'a' => array( 'href' => true, 'title' => true ) )
4406
			);
4407
		?></p>
4408
	</div>
4409
</div>
4410
<?php endif;
4411
	// only display the notice if the other stuff is not there
4412
	if( $this->can_display_jetpack_manage_notice() && !  $this->error && ! $this->message && ! $this->privacy_checks ) {
4413
		if( isset( $_GET['page'] ) && 'jetpack' != $_GET['page'] )
4414
			$this->opt_in_jetpack_manage_notice();
4415
		}
4416
	}
4417
4418
	/**
4419
	 * Record a stat for later output.  This will only currently output in the admin_footer.
4420
	 */
4421
	function stat( $group, $detail ) {
4422
		if ( ! isset( $this->stats[ $group ] ) )
4423
			$this->stats[ $group ] = array();
4424
		$this->stats[ $group ][] = $detail;
4425
	}
4426
4427
	/**
4428
	 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
4429
	 */
4430
	function do_stats( $method = '' ) {
4431
		if ( is_array( $this->stats ) && count( $this->stats ) ) {
4432
			foreach ( $this->stats as $group => $stats ) {
4433
				if ( is_array( $stats ) && count( $stats ) ) {
4434
					$args = array( "x_jetpack-{$group}" => implode( ',', $stats ) );
4435
					if ( 'server_side' === $method ) {
4436
						self::do_server_side_stat( $args );
4437
					} else {
4438
						echo '<img src="' . esc_url( self::build_stats_url( $args ) ) . '" width="1" height="1" style="display:none;" />';
4439
					}
4440
				}
4441
				unset( $this->stats[ $group ] );
4442
			}
4443
		}
4444
	}
4445
4446
	/**
4447
	 * Runs stats code for a one-off, server-side.
4448
	 *
4449
	 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
4450
	 *
4451
	 * @return bool If it worked.
4452
	 */
4453
	static function do_server_side_stat( $args ) {
4454
		$response = wp_remote_get( esc_url_raw( self::build_stats_url( $args ) ) );
4455
		if ( is_wp_error( $response ) )
4456
			return false;
4457
4458
		if ( 200 !== wp_remote_retrieve_response_code( $response ) )
4459
			return false;
4460
4461
		return true;
4462
	}
4463
4464
	/**
4465
	 * Builds the stats url.
4466
	 *
4467
	 * @param $args array|string The arguments to append to the URL.
4468
	 *
4469
	 * @return string The URL to be pinged.
4470
	 */
4471
	static function build_stats_url( $args ) {
4472
		$defaults = array(
4473
			'v'    => 'wpcom2',
4474
			'rand' => md5( mt_rand( 0, 999 ) . time() ),
4475
		);
4476
		$args     = wp_parse_args( $args, $defaults );
4477
		/**
4478
		 * Filter the URL used as the Stats tracking pixel.
4479
		 *
4480
		 * @since 2.3.2
4481
		 *
4482
		 * @param string $url Base URL used as the Stats tracking pixel.
4483
		 */
4484
		$base_url = apply_filters(
4485
			'jetpack_stats_base_url',
4486
			'https://pixel.wp.com/g.gif'
4487
		);
4488
		$url      = add_query_arg( $args, $base_url );
4489
		return $url;
4490
	}
4491
4492
	static function translate_current_user_to_role() {
4493
		foreach ( self::$capability_translations as $role => $cap ) {
4494
			if ( current_user_can( $role ) || current_user_can( $cap ) ) {
4495
				return $role;
4496
			}
4497
		}
4498
4499
		return false;
4500
	}
4501
4502
	static function translate_user_to_role( $user ) {
4503
		foreach ( self::$capability_translations as $role => $cap ) {
4504
			if ( user_can( $user, $role ) || user_can( $user, $cap ) ) {
4505
				return $role;
4506
			}
4507
		}
4508
4509
		return false;
4510
    }
4511
4512
	static function translate_role_to_cap( $role ) {
4513
		if ( ! isset( self::$capability_translations[$role] ) ) {
4514
			return false;
4515
		}
4516
4517
		return self::$capability_translations[$role];
4518
	}
4519
4520
	static function sign_role( $role, $user_id = null ) {
4521
		if ( empty( $user_id ) ) {
4522
			$user_id = (int) get_current_user_id();
4523
		}
4524
4525
		if ( ! $user_id  ) {
4526
			return false;
4527
		}
4528
4529
		$token = Jetpack_Data::get_access_token();
4530
		if ( ! $token || is_wp_error( $token ) ) {
4531
			return false;
4532
		}
4533
4534
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
4535
	}
4536
4537
4538
	/**
4539
	 * Builds a URL to the Jetpack connection auth page
4540
	 *
4541
	 * @since 3.9.5
4542
	 *
4543
	 * @param bool $raw If true, URL will not be escaped.
4544
	 * @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
4545
	 *                              If string, will be a custom redirect.
4546
	 * @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
4547
	 * @param bool $register If true, will generate a register URL regardless of the existing token, since 4.9.0
4548
	 *
4549
	 * @return string Connect URL
4550
	 */
4551
	function build_connect_url( $raw = false, $redirect = false, $from = false, $register = false ) {
4552
		$site_id = Jetpack_Options::get_option( 'id' );
4553
		$token = Jetpack_Options::get_option( 'blog_token' );
4554
4555
		if ( $register || ! $token || ! $site_id ) {
4556
			$url = Jetpack::nonce_url_no_esc( Jetpack::admin_url( 'action=register' ), 'jetpack-register' );
4557
4558
			if ( ! empty( $redirect ) ) {
4559
				$url = add_query_arg(
4560
					'redirect',
4561
					urlencode( wp_validate_redirect( esc_url_raw( $redirect ) ) ),
4562
					$url
4563
				);
4564
			}
4565
4566
			if( is_network_admin() ) {
4567
				$url = add_query_arg( 'is_multisite', network_admin_url( 'admin.php?page=jetpack-settings' ), $url );
4568
			}
4569
		} else {
4570
4571
			// Let's check the existing blog token to see if we need to re-register. We only check once per minute
4572
			// because otherwise this logic can get us in to a loop.
4573
			$last_connect_url_check = intval( Jetpack_Options::get_raw_option( 'jetpack_last_connect_url_check' ) );
4574
			if ( ! $last_connect_url_check || ( time() - $last_connect_url_check ) > MINUTE_IN_SECONDS ) {
4575
				Jetpack_Options::update_raw_option( 'jetpack_last_connect_url_check', time() );
4576
4577
				$response = Jetpack_Client::wpcom_json_api_request_as_blog(
4578
					sprintf( '/sites/%d', $site_id ) .'?force=wpcom',
4579
					'1.1'
4580
				);
4581
4582
				if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
4583
					// Generating a register URL instead to refresh the existing token
4584
					return $this->build_connect_url( $raw, $redirect, $from, true );
4585
				}
4586
			}
4587
4588
			if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && include_once JETPACK__GLOTPRESS_LOCALES_PATH ) {
4589
				$gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
4590
			}
4591
4592
			$role = self::translate_current_user_to_role();
4593
			$signed_role = self::sign_role( $role );
4594
4595
			$user = wp_get_current_user();
4596
4597
			$jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
4598
			$redirect = $redirect
4599
				? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
4600
				: $jetpack_admin_page;
4601
4602
			if( isset( $_REQUEST['is_multisite'] ) ) {
4603
				$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4604
			}
4605
4606
			$secrets = Jetpack::generate_secrets( 'authorize', false, 2 * HOUR_IN_SECONDS );
4607
4608
			$site_icon = ( function_exists( 'has_site_icon') && has_site_icon() )
4609
				? get_site_icon_url()
4610
				: false;
4611
4612
			/**
4613
			 * Filter the type of authorization.
4614
			 * 'calypso' completes authorization on wordpress.com/jetpack/connect
4615
			 * while 'jetpack' ( or any other value ) completes the authorization at jetpack.wordpress.com.
4616
			 *
4617
			 * @since 4.3.3
4618
			 *
4619
			 * @param string $auth_type Defaults to 'calypso', can also be 'jetpack'.
4620
			 */
4621
			$auth_type = apply_filters( 'jetpack_auth_type', 'calypso' );
4622
4623
			$tracks_identity = jetpack_tracks_get_identity( get_current_user_id() );
4624
4625
			$args = urlencode_deep(
4626
				array(
4627
					'response_type' => 'code',
4628
					'client_id'     => Jetpack_Options::get_option( 'id' ),
4629
					'redirect_uri'  => add_query_arg(
4630
						array(
4631
							'action'   => 'authorize',
4632
							'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
4633
							'redirect' => urlencode( $redirect ),
4634
						),
4635
						esc_url( admin_url( 'admin.php?page=jetpack' ) )
4636
					),
4637
					'state'         => $user->ID,
4638
					'scope'         => $signed_role,
4639
					'user_email'    => $user->user_email,
4640
					'user_login'    => $user->user_login,
4641
					'is_active'     => Jetpack::is_active(),
4642
					'jp_version'    => JETPACK__VERSION,
4643
					'auth_type'     => $auth_type,
4644
					'secret'        => $secrets['secret_1'],
4645
					'locale'        => ( isset( $gp_locale ) && isset( $gp_locale->slug ) ) ? $gp_locale->slug : '',
4646
					'blogname'      => get_option( 'blogname' ),
4647
					'site_url'      => site_url(),
4648
					'home_url'      => home_url(),
4649
					'site_icon'     => $site_icon,
4650
					'site_lang'     => get_locale(),
4651
					'_ui'           => $tracks_identity['_ui'],
4652
					'_ut'           => $tracks_identity['_ut']
4653
				)
4654
			);
4655
4656
			self::apply_activation_source_to_args( $args );
4657
4658
			$url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) );
4659
		}
4660
4661
		if ( $from ) {
4662
			$url = add_query_arg( 'from', $from, $url );
4663
		}
4664
4665
4666
		if ( isset( $_GET['calypso_env'] ) ) {
4667
			$url = add_query_arg( 'calypso_env', sanitize_key( $_GET['calypso_env'] ), $url );
4668
		}
4669
4670
		return $raw ? $url : esc_url( $url );
4671
	}
4672
4673
	public static function apply_activation_source_to_args( &$args ) {
4674
		list( $activation_source_name, $activation_source_keyword ) = get_option( 'jetpack_activation_source' );
4675
4676
		if ( $activation_source_name ) {
4677
			$args['_as'] = urlencode( $activation_source_name );
4678
		}
4679
4680
		if ( $activation_source_keyword ) {
4681
			$args['_ak'] = urlencode( $activation_source_keyword );
4682
		}
4683
	}
4684
4685
	function build_reconnect_url( $raw = false ) {
4686
		$url = wp_nonce_url( Jetpack::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4687
		return $raw ? $url : esc_url( $url );
4688
	}
4689
4690
	public static function admin_url( $args = null ) {
4691
		$args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
4692
		$url = add_query_arg( $args, admin_url( 'admin.php' ) );
4693
		return $url;
4694
	}
4695
4696
	public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4697
		$actionurl = str_replace( '&amp;', '&', $actionurl );
4698
		return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4699
	}
4700
4701
	function dismiss_jetpack_notice() {
4702
4703
		if ( ! isset( $_GET['jetpack-notice'] ) ) {
4704
			return;
4705
		}
4706
4707
		switch( $_GET['jetpack-notice'] ) {
4708
			case 'dismiss':
4709
				if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4710
4711
					require_once ABSPATH . 'wp-admin/includes/plugin.php';
4712
					deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4713
					wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4714
				}
4715
				break;
4716 View Code Duplication
			case 'jetpack-manage-opt-out':
4717
4718
				if ( check_admin_referer( 'jetpack_manage_banner_opt_out' ) ) {
4719
					// Don't show the banner again
4720
4721
					Jetpack_Options::update_option( 'dismissed_manage_banner', true );
4722
					// redirect back to the page that had the notice
4723
					if ( wp_get_referer() ) {
4724
						wp_safe_redirect( wp_get_referer() );
4725
					} else {
4726
						// Take me to Jetpack
4727
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4728
					}
4729
				}
4730
				break;
4731 View Code Duplication
			case 'jetpack-protect-multisite-opt-out':
4732
4733
				if ( check_admin_referer( 'jetpack_protect_multisite_banner_opt_out' ) ) {
4734
					// Don't show the banner again
4735
4736
					update_site_option( 'jetpack_dismissed_protect_multisite_banner', true );
4737
					// redirect back to the page that had the notice
4738
					if ( wp_get_referer() ) {
4739
						wp_safe_redirect( wp_get_referer() );
4740
					} else {
4741
						// Take me to Jetpack
4742
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4743
					}
4744
				}
4745
				break;
4746
			case 'jetpack-manage-opt-in':
4747
				if ( check_admin_referer( 'jetpack_manage_banner_opt_in' ) ) {
4748
					// This makes sure that we are redirect to jetpack home so that we can see the Success Message.
4749
4750
					$redirection_url = Jetpack::admin_url();
4751
					remove_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4752
4753
					// Don't redirect form the Jetpack Setting Page
4754
					$referer_parsed = parse_url ( wp_get_referer() );
4755
					// check that we do have a wp_get_referer and the query paramater is set orderwise go to the Jetpack Home
4756
					if ( isset( $referer_parsed['query'] ) && false !== strpos( $referer_parsed['query'], 'page=jetpack_modules' ) ) {
4757
						// Take the user to Jetpack home except when on the setting page
4758
						$redirection_url = wp_get_referer();
4759
						add_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4760
					}
4761
					// Also update the JSON API FULL MANAGEMENT Option
4762
					Jetpack::activate_module( 'manage', false, false );
4763
4764
					// Special Message when option in.
4765
					Jetpack::state( 'optin-manage', 'true' );
4766
					// Activate the Module if not activated already
4767
4768
					// Redirect properly
4769
					wp_safe_redirect( $redirection_url );
4770
4771
				}
4772
				break;
4773
		}
4774
	}
4775
4776
	public static function admin_screen_configure_module( $module_id ) {
4777
4778
		// User that doesn't have 'jetpack_configure_modules' will never end up here since Jetpack Landing Page woun't let them.
4779
		if ( ! in_array( $module_id, Jetpack::get_active_modules() ) && current_user_can( 'manage_options' ) ) {
4780
			if ( has_action( 'display_activate_module_setting_' . $module_id ) ) {
4781
				/**
4782
				 * Fires to diplay a custom module activation screen.
4783
				 *
4784
				 * To add a module actionation screen use Jetpack::module_configuration_activation_screen method.
4785
				 * Example: Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
4786
				 *
4787
				 * @module manage
4788
				 *
4789
				 * @since 3.8.0
4790
				 *
4791
				 * @param int $module_id Module ID.
4792
				 */
4793
				do_action( 'display_activate_module_setting_' . $module_id );
4794
			} else {
4795
				self::display_activate_module_link( $module_id );
4796
			}
4797
4798
			return false;
4799
		} ?>
4800
4801
		<div id="jp-settings-screen" style="position: relative">
4802
			<h3>
4803
			<?php
4804
				$module = Jetpack::get_module( $module_id );
4805
				echo '<a href="' . Jetpack::admin_url( 'page=jetpack_modules' ) . '">' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</a> &rarr; ';
4806
				printf( __( 'Configure %s', 'jetpack' ), $module['name'] );
4807
			?>
4808
			</h3>
4809
			<?php
4810
				/**
4811
				 * Fires within the displayed message when a feature configuation is updated.
4812
				 *
4813
				 * @since 3.4.0
4814
				 *
4815
				 * @param int $module_id Module ID.
4816
				 */
4817
				do_action( 'jetpack_notices_update_settings', $module_id );
4818
				/**
4819
				 * Fires when a feature configuation screen is loaded.
4820
				 * The dynamic part of the hook, $module_id, is the module ID.
4821
				 *
4822
				 * @since 1.1.0
4823
				 */
4824
				do_action( 'jetpack_module_configuration_screen_' . $module_id );
4825
			?>
4826
		</div><?php
4827
	}
4828
4829
	/**
4830
	 * Display link to activate the module to see the settings screen.
4831
	 * @param  string $module_id
4832
	 * @return null
4833
	 */
4834
	public static function display_activate_module_link( $module_id ) {
4835
4836
		$info =  Jetpack::get_module( $module_id );
4837
		$extra = '';
4838
		$activate_url = wp_nonce_url(
4839
				Jetpack::admin_url(
4840
					array(
4841
						'page'   => 'jetpack',
4842
						'action' => 'activate',
4843
						'module' => $module_id,
4844
					)
4845
				),
4846
				"jetpack_activate-$module_id"
4847
			);
4848
4849
		?>
4850
4851
		<div class="wrap configure-module">
4852
			<div id="jp-settings-screen">
4853
				<?php
4854
				if ( $module_id == 'json-api' ) {
4855
4856
					$info['name'] = esc_html__( 'Activate Site Management and JSON API', 'jetpack' );
4857
4858
					$activate_url = Jetpack::init()->opt_in_jetpack_manage_url();
4859
4860
					$info['description'] = sprintf( __( 'Manage your multiple Jetpack sites from our centralized dashboard at wordpress.com/sites. <a href="%s" target="_blank">Learn more</a>.', 'jetpack' ), 'https://jetpack.com/support/site-management' );
4861
4862
					// $extra = __( 'To use Site Management, you need to first activate JSON API to allow remote management of your site. ', 'jetpack' );
4863
				} ?>
4864
4865
				<h3><?php echo esc_html( $info['name'] ); ?></h3>
4866
				<div class="narrow">
4867
					<p><?php echo  $info['description']; ?></p>
4868
					<?php if( $extra ) { ?>
4869
					<p><?php echo esc_html( $extra ); ?></p>
4870
					<?php } ?>
4871
					<p>
4872
						<?php
4873
						if( wp_get_referer() ) {
4874
							printf( __( '<a class="button-primary" href="%s">Activate Now</a> or <a href="%s" >return to previous page</a>.', 'jetpack' ) , $activate_url, wp_get_referer() );
4875
						} else {
4876
							printf( __( '<a class="button-primary" href="%s">Activate Now</a>', 'jetpack' ) , $activate_url  );
4877
						} ?>
4878
					</p>
4879
				</div>
4880
4881
			</div>
4882
		</div>
4883
4884
		<?php
4885
	}
4886
4887
	public static function sort_modules( $a, $b ) {
4888
		if ( $a['sort'] == $b['sort'] )
4889
			return 0;
4890
4891
		return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
4892
	}
4893
4894
	function ajax_recheck_ssl() {
4895
		check_ajax_referer( 'recheck-ssl', 'ajax-nonce' );
4896
		$result = Jetpack::permit_ssl( true );
4897
		wp_send_json( array(
4898
			'enabled' => $result,
4899
			'message' => get_transient( 'jetpack_https_test_message' )
4900
		) );
4901
	}
4902
4903
/* Client API */
4904
4905
	/**
4906
	 * Returns the requested Jetpack API URL
4907
	 *
4908
	 * @return string
4909
	 */
4910
	public static function api_url( $relative_url ) {
4911
		return trailingslashit( JETPACK__API_BASE . $relative_url  ) . JETPACK__API_VERSION . '/';
4912
	}
4913
4914
	/**
4915
	 * Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
4916
	 */
4917
	public static function fix_url_for_bad_hosts( $url ) {
4918
		if ( 0 !== strpos( $url, 'https://' ) ) {
4919
			return $url;
4920
		}
4921
4922
		switch ( JETPACK_CLIENT__HTTPS ) {
4923
			case 'ALWAYS' :
4924
				return $url;
4925
			case 'NEVER' :
4926
				return set_url_scheme( $url, 'http' );
4927
			// default : case 'AUTO' :
4928
		}
4929
4930
		// we now return the unmodified SSL URL by default, as a security precaution
4931
		return $url;
4932
	}
4933
4934
	/**
4935
	 * Create a random secret for validating onboarding payload
4936
	 *
4937
	 * @return string Secret token
4938
	 */
4939
	public static function create_onboarding_token() {
4940
		if ( false === ( $token = Jetpack_Options::get_option( 'onboarding' ) ) ) {
4941
			$token = wp_generate_password( 32, false );
4942
			Jetpack_Options::update_option( 'onboarding', $token );
4943
		}
4944
4945
		return $token;
4946
	}
4947
4948
	/**
4949
	 * Remove the onboarding token
4950
	 *
4951
	 * @return bool True on success, false on failure
4952
	 */
4953
	public static function invalidate_onboarding_token() {
4954
		return Jetpack_Options::delete_option( 'onboarding' );
4955
	}
4956
4957
	/**
4958
	 * Validate an onboarding token for a specific action
4959
	 *
4960
	 * @return boolean True if token/action pair is accepted, false if not
4961
	 */
4962
	public static function validate_onboarding_token_action( $token, $action ) {
4963
		// Compare tokens, bail if tokens do not match
4964
		if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4965
			return false;
4966
		}
4967
4968
		// List of valid actions we can take
4969
		$valid_actions = array(
4970
			'/jetpack/v4/settings',
4971
		);
4972
4973
		// Whitelist the action
4974
		if ( ! in_array( $action, $valid_actions ) ) {
4975
			return false;
4976
		}
4977
4978
		return true;
4979
	}
4980
4981
	/**
4982
	 * Checks to see if the URL is using SSL to connect with Jetpack
4983
	 *
4984
	 * @since 2.3.3
4985
	 * @return boolean
4986
	 */
4987
	public static function permit_ssl( $force_recheck = false ) {
4988
		// Do some fancy tests to see if ssl is being supported
4989
		if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
4990
			$message = '';
4991
			if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
4992
				$ssl = 0;
4993
			} else {
4994
				switch ( JETPACK_CLIENT__HTTPS ) {
4995
					case 'NEVER':
4996
						$ssl = 0;
4997
						$message = __( 'JETPACK_CLIENT__HTTPS is set to NEVER', 'jetpack' );
4998
						break;
4999
					case 'ALWAYS':
5000
					case 'AUTO':
5001
					default:
5002
						$ssl = 1;
5003
						break;
5004
				}
5005
5006
				// If it's not 'NEVER', test to see
5007
				if ( $ssl ) {
5008
					if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
5009
						$ssl = 0;
5010
						$message = __( 'WordPress reports no SSL support', 'jetpack' );
5011
					} else {
5012
						$response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
5013
						if ( is_wp_error( $response ) ) {
5014
							$ssl = 0;
5015
							$message = __( 'WordPress reports no SSL support', 'jetpack' );
5016
						} elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
5017
							$ssl = 0;
5018
							$message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
5019
						}
5020
					}
5021
				}
5022
			}
5023
			set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
5024
			set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5025
		}
5026
5027
		return (bool) $ssl;
5028
	}
5029
5030
	/*
5031
	 * Displays an admin_notice, alerting the user to their JETPACK_CLIENT__HTTPS constant being 'AUTO' but SSL isn't working.
5032
	 */
5033
	public function alert_auto_ssl_fail() {
5034
		if ( ! current_user_can( 'manage_options' ) )
5035
			return;
5036
5037
		$ajax_nonce = wp_create_nonce( 'recheck-ssl' );
5038
		?>
5039
5040
		<div id="jetpack-ssl-warning" class="error jp-identity-crisis">
5041
			<div class="jp-banner__content">
5042
				<h2><?php _e( 'Outbound HTTPS not working', 'jetpack' ); ?></h2>
5043
				<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>
5044
				<p>
5045
					<?php _e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
5046
					<a href="#" id="jetpack-recheck-ssl-button"><?php _e( 'Try again', 'jetpack' ); ?></a>
5047
					<span id="jetpack-recheck-ssl-output"><?php echo get_transient( 'jetpack_https_test_message' ); ?></span>
5048
				</p>
5049
				<p>
5050
					<?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' ),
5051
							esc_url( Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) ),
5052
							esc_url( 'https://jetpack.com/support/getting-started-with-jetpack/troubleshooting-tips/' ) ); ?>
5053
				</p>
5054
			</div>
5055
		</div>
5056
		<style>
5057
			#jetpack-recheck-ssl-output { margin-left: 5px; color: red; }
5058
		</style>
5059
		<script type="text/javascript">
5060
			jQuery( document ).ready( function( $ ) {
5061
				$( '#jetpack-recheck-ssl-button' ).click( function( e ) {
5062
					var $this = $( this );
5063
					$this.html( <?php echo json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5064
					$( '#jetpack-recheck-ssl-output' ).html( '' );
5065
					e.preventDefault();
5066
					var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': '<?php echo $ajax_nonce; ?>' };
5067
					$.post( ajaxurl, data )
5068
					  .done( function( response ) {
5069
					  	if ( response.enabled ) {
5070
					  		$( '#jetpack-ssl-warning' ).hide();
5071
					  	} else {
5072
					  		this.html( <?php echo json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5073
					  		$( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
5074
					  	}
5075
					  }.bind( $this ) );
5076
				} );
5077
			} );
5078
		</script>
5079
5080
		<?php
5081
	}
5082
5083
	/**
5084
	 * Returns the Jetpack XML-RPC API
5085
	 *
5086
	 * @return string
5087
	 */
5088
	public static function xmlrpc_api_url() {
5089
		$base = preg_replace( '#(https?://[^?/]+)(/?.*)?$#', '\\1', JETPACK__API_BASE );
5090
		return untrailingslashit( $base ) . '/xmlrpc.php';
5091
	}
5092
5093
	/**
5094
	 * Creates two secret tokens and the end of life timestamp for them.
5095
	 *
5096
	 * Note these tokens are unique per call, NOT static per site for connecting.
5097
	 *
5098
	 * @since 2.6
5099
	 * @return array
5100
	 */
5101
	public static function generate_secrets( $action, $user_id = false, $exp = 600 ) {
5102
		if ( ! $user_id ) {
5103
			$user_id = get_current_user_id();
5104
		}
5105
5106
		$secret_name  = 'jetpack_' . $action . '_' . $user_id;
5107
		$secrets      = Jetpack_Options::get_raw_option( 'jetpack_secrets', array() );
5108
5109
		if (
5110
			isset( $secrets[ $secret_name ] ) &&
5111
			$secrets[ $secret_name ]['exp'] > time()
5112
		) {
5113
			return $secrets[ $secret_name ];
5114
		}
5115
5116
		$secret_value = array(
5117
			'secret_1'  => wp_generate_password( 32, false ),
5118
			'secret_2'  => wp_generate_password( 32, false ),
5119
			'exp'       => time() + $exp,
5120
		);
5121
5122
		$secrets[ $secret_name ] = $secret_value;
5123
5124
		Jetpack_Options::update_raw_option( 'jetpack_secrets', $secrets );
5125
		return $secrets[ $secret_name ];
5126
	}
5127
5128
	public static function get_secrets( $action, $user_id ) {
5129
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
5130
		$secrets = Jetpack_Options::get_raw_option( 'jetpack_secrets', array() );
5131
5132
		if ( ! isset( $secrets[ $secret_name ] ) ) {
5133
			return new WP_Error( 'verify_secrets_missing', 'Verification secrets not found' );
5134
		}
5135
5136
		if ( $secrets[ $secret_name ]['exp'] < time() ) {
5137
			self::delete_secrets( $action, $user_id );
5138
			return new WP_Error( 'verify_secrets_expired', 'Verification took too long' );
5139
		}
5140
5141
		return $secrets[ $secret_name ];
5142
	}
5143
5144
	public static function delete_secrets( $action, $user_id ) {
5145
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
5146
		$secrets = Jetpack_Options::get_raw_option( 'jetpack_secrets', array() );
5147
		if ( isset( $secrets[ $secret_name ] ) ) {
5148
			unset( $secrets[ $secret_name ] );
5149
			Jetpack_Options::update_raw_option( 'jetpack_secrets', $secrets );
5150
		}
5151
	}
5152
5153
	/**
5154
	 * Builds the timeout limit for queries talking with the wpcom servers.
5155
	 *
5156
	 * Based on local php max_execution_time in php.ini
5157
	 *
5158
	 * @since 2.6
5159
	 * @return int
5160
	 * @deprecated
5161
	 **/
5162
	public function get_remote_query_timeout_limit() {
5163
		_deprecated_function( __METHOD__, 'jetpack-5.4' );
5164
		return Jetpack::get_max_execution_time();
5165
	}
5166
5167
	/**
5168
	 * Builds the timeout limit for queries talking with the wpcom servers.
5169
	 *
5170
	 * Based on local php max_execution_time in php.ini
5171
	 *
5172
	 * @since 5.4
5173
	 * @return int
5174
	 **/
5175
	public static function get_max_execution_time() {
5176
		$timeout = (int) ini_get( 'max_execution_time' );
5177
5178
		// Ensure exec time set in php.ini
5179
		if ( ! $timeout ) {
5180
			$timeout = 30;
5181
		}
5182
		return $timeout;
5183
	}
5184
5185
	/**
5186
	 * Sets a minimum request timeout, and returns the current timeout
5187
	 *
5188
	 * @since 5.4
5189
	 **/
5190
	public static function set_min_time_limit( $min_timeout ) {
5191
		$timeout = self::get_max_execution_time();
5192
		if ( $timeout < $min_timeout ) {
5193
			$timeout = $min_timeout;
5194
			set_time_limit( $timeout );
5195
		}
5196
		return $timeout;
5197
	}
5198
5199
5200
	/**
5201
	 * Takes the response from the Jetpack register new site endpoint and
5202
	 * verifies it worked properly.
5203
	 *
5204
	 * @since 2.6
5205
	 * @return string|Jetpack_Error A JSON object on success or Jetpack_Error on failures
5206
	 **/
5207
	public function validate_remote_register_response( $response ) {
5208
	  if ( is_wp_error( $response ) ) {
5209
			return new Jetpack_Error( 'register_http_request_failed', $response->get_error_message() );
5210
		}
5211
5212
		$code   = wp_remote_retrieve_response_code( $response );
5213
		$entity = wp_remote_retrieve_body( $response );
5214
		if ( $entity )
5215
			$registration_response = json_decode( $entity );
5216
		else
5217
			$registration_response = false;
5218
5219
		$code_type = intval( $code / 100 );
5220
		if ( 5 == $code_type ) {
5221
			return new Jetpack_Error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
5222
		} elseif ( 408 == $code ) {
5223
			return new Jetpack_Error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
5224
		} elseif ( ! empty( $registration_response->error ) ) {
5225
			if ( 'xml_rpc-32700' == $registration_response->error && ! function_exists( 'xml_parser_create' ) ) {
5226
				$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' );
5227
			} else {
5228
				$error_description = isset( $registration_response->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $registration_response->error_description ) : '';
5229
			}
5230
5231
			return new Jetpack_Error( (string) $registration_response->error, $error_description, $code );
5232
		} elseif ( 200 != $code ) {
5233
			return new Jetpack_Error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
5234
		}
5235
5236
		// Jetpack ID error block
5237
		if ( empty( $registration_response->jetpack_id ) ) {
5238
			return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ), $entity );
5239
		} elseif ( ! is_scalar( $registration_response->jetpack_id ) ) {
5240
			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 );
5241
		} elseif ( preg_match( '/[^0-9]/', $registration_response->jetpack_id ) ) {
5242
			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 );
5243
		}
5244
5245
	    return $registration_response;
5246
	}
5247
	/**
5248
	 * @return bool|WP_Error
5249
	 */
5250
	public static function register() {
5251
		JetpackTracking::record_user_event( 'jpc_register_begin' );
5252
		add_action( 'pre_update_jetpack_option_register', array( 'Jetpack_Options', 'delete_option' ) );
5253
		$secrets = Jetpack::generate_secrets( 'register' );
5254
5255 View Code Duplication
		if (
5256
			empty( $secrets['secret_1'] ) ||
5257
			empty( $secrets['secret_2'] ) ||
5258
			empty( $secrets['exp'] )
5259
		) {
5260
			return new Jetpack_Error( 'missing_secrets' );
5261
		}
5262
5263
		// better to try (and fail) to set a higher timeout than this system
5264
		// supports than to have register fail for more users than it should
5265
		$timeout = Jetpack::set_min_time_limit( 60 ) / 2;
5266
5267
		$gmt_offset = get_option( 'gmt_offset' );
5268
		if ( ! $gmt_offset ) {
5269
			$gmt_offset = 0;
5270
		}
5271
5272
		$stats_options = get_option( 'stats_options' );
5273
		$stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null;
5274
5275
		$tracks_identity = jetpack_tracks_get_identity( get_current_user_id() );
5276
5277
		$args = array(
5278
			'method'  => 'POST',
5279
			'body'    => array(
5280
				'siteurl'         => site_url(),
5281
				'home'            => home_url(),
5282
				'gmt_offset'      => $gmt_offset,
5283
				'timezone_string' => (string) get_option( 'timezone_string' ),
5284
				'site_name'       => (string) get_option( 'blogname' ),
5285
				'secret_1'        => $secrets['secret_1'],
5286
				'secret_2'        => $secrets['secret_2'],
5287
				'site_lang'       => get_locale(),
5288
				'timeout'         => $timeout,
5289
				'stats_id'        => $stats_id,
5290
				'state'           => get_current_user_id(),
5291
				'_ui'             => $tracks_identity['_ui'],
5292
				'_ut'             => $tracks_identity['_ut'],
5293
				'jetpack_version' => JETPACK__VERSION
5294
			),
5295
			'headers' => array(
5296
				'Accept' => 'application/json',
5297
			),
5298
			'timeout' => $timeout,
5299
		);
5300
5301
		self::apply_activation_source_to_args( $args['body'] );
5302
5303
		$response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'register' ) ), $args, true );
5304
5305
		// Make sure the response is valid and does not contain any Jetpack errors
5306
		$registration_details = Jetpack::init()->validate_remote_register_response( $response );
5307
		if ( is_wp_error( $registration_details ) ) {
5308
			return $registration_details;
5309
		} elseif ( ! $registration_details ) {
5310
			return new Jetpack_Error( 'unknown_error', __( 'Unknown error registering your Jetpack site', 'jetpack' ), wp_remote_retrieve_response_code( $response ) );
5311
		}
5312
5313 View Code Duplication
		if ( empty( $registration_details->jetpack_secret ) || ! is_string( $registration_details->jetpack_secret ) ) {
5314
			return new Jetpack_Error( 'jetpack_secret', '', wp_remote_retrieve_response_code( $response ) );
5315
		}
5316
5317
		if ( isset( $registration_details->jetpack_public ) ) {
5318
			$jetpack_public = (int) $registration_details->jetpack_public;
5319
		} else {
5320
			$jetpack_public = false;
5321
		}
5322
5323
		Jetpack_Options::update_options(
5324
			array(
5325
				'id'         => (int)    $registration_details->jetpack_id,
5326
				'blog_token' => (string) $registration_details->jetpack_secret,
5327
				'public'     => $jetpack_public,
5328
			)
5329
		);
5330
5331
		/**
5332
		 * Fires when a site is registered on WordPress.com.
5333
		 *
5334
		 * @since 3.7.0
5335
		 *
5336
		 * @param int $json->jetpack_id Jetpack Blog ID.
5337
		 * @param string $json->jetpack_secret Jetpack Blog Token.
5338
		 * @param int|bool $jetpack_public Is the site public.
5339
		 */
5340
		do_action( 'jetpack_site_registered', $registration_details->jetpack_id, $registration_details->jetpack_secret, $jetpack_public );
5341
5342
		// Initialize Jump Start for the first and only time.
5343
		if ( ! Jetpack_Options::get_option( 'jumpstart' ) ) {
5344
			Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
5345
5346
			$jetpack = Jetpack::init();
5347
5348
			$jetpack->stat( 'jumpstart', 'unique-views' );
5349
			$jetpack->do_stats( 'server_side' );
5350
		};
5351
5352
		return true;
5353
	}
5354
5355
	/**
5356
	 * If the db version is showing something other that what we've got now, bump it to current.
5357
	 *
5358
	 * @return bool: True if the option was incorrect and updated, false if nothing happened.
5359
	 */
5360
	public static function maybe_set_version_option() {
5361
		list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
5362
		if ( JETPACK__VERSION != $version ) {
5363
			Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
5364
5365
			if ( version_compare( JETPACK__VERSION, $version, '>' ) ) {
5366
				/** This action is documented in class.jetpack.php */
5367
				do_action( 'updating_jetpack_version', JETPACK__VERSION, $version );
5368
			}
5369
5370
			return true;
5371
		}
5372
		return false;
5373
	}
5374
5375
/* Client Server API */
5376
5377
	/**
5378
	 * Loads the Jetpack XML-RPC client
5379
	 */
5380
	public static function load_xml_rpc_client() {
5381
		require_once ABSPATH . WPINC . '/class-IXR.php';
5382
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-ixr-client.php';
5383
	}
5384
5385
	/**
5386
	 * Resets the saved authentication state in between testing requests.
5387
	 */
5388
	public function reset_saved_auth_state() {
5389
		$this->xmlrpc_verification = null;
5390
		$this->rest_authentication_status = null;
5391
	}
5392
5393
	function verify_xml_rpc_signature() {
5394
		if ( $this->xmlrpc_verification ) {
5395
			return $this->xmlrpc_verification;
5396
		}
5397
5398
		// It's not for us
5399
		if ( ! isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
5400
			return false;
5401
		}
5402
5403
		@list( $token_key, $version, $user_id ) = explode( ':', $_GET['token'] );
5404
		if (
5405
			empty( $token_key )
5406
		||
5407
			empty( $version ) || strval( JETPACK__API_VERSION ) !== $version
5408
		) {
5409
			return false;
5410
		}
5411
5412
		if ( '0' === $user_id ) {
5413
			$token_type = 'blog';
5414
			$user_id = 0;
5415
		} else {
5416
			$token_type = 'user';
5417
			if ( empty( $user_id ) || ! ctype_digit( $user_id ) ) {
5418
				return false;
5419
			}
5420
			$user_id = (int) $user_id;
5421
5422
			$user = new WP_User( $user_id );
5423
			if ( ! $user || ! $user->exists() ) {
5424
				return false;
5425
			}
5426
		}
5427
5428
		$token = Jetpack_Data::get_access_token( $user_id );
5429
		if ( ! $token ) {
5430
			return false;
5431
		}
5432
5433
		$token_check = "$token_key.";
5434
		if ( ! hash_equals( substr( $token->secret, 0, strlen( $token_check ) ), $token_check ) ) {
5435
			return false;
5436
		}
5437
5438
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
5439
5440
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5441
		if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
5442
			$post_data   = $_POST;
5443
			$file_hashes = array();
5444
			foreach ( $post_data as $post_data_key => $post_data_value ) {
5445
				if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
5446
					continue;
5447
				}
5448
				$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
5449
				$file_hashes[$post_data_key] = $post_data_value;
5450
			}
5451
5452
			foreach ( $file_hashes as $post_data_key => $post_data_value ) {
5453
				unset( $post_data["_jetpack_file_hmac_{$post_data_key}"] );
5454
				$post_data[$post_data_key] = $post_data_value;
5455
			}
5456
5457
			ksort( $post_data );
5458
5459
			$body = http_build_query( stripslashes_deep( $post_data ) );
5460
		} elseif ( is_null( $this->HTTP_RAW_POST_DATA ) ) {
5461
			$body = file_get_contents( 'php://input' );
5462
		} else {
5463
			$body = null;
5464
		}
5465
5466
		$signature = $jetpack_signature->sign_current_request(
5467
			array( 'body' => is_null( $body ) ? $this->HTTP_RAW_POST_DATA : $body, )
5468
		);
5469
5470
		if ( ! $signature ) {
5471
			return false;
5472
		} else if ( is_wp_error( $signature ) ) {
5473
			return $signature;
5474
		} else if ( ! hash_equals( $signature, $_GET['signature'] ) ) {
5475
			return false;
5476
		}
5477
5478
		$timestamp = (int) $_GET['timestamp'];
5479
		$nonce     = stripslashes( (string) $_GET['nonce'] );
5480
5481
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
5482
			return false;
5483
		}
5484
5485
		// Let's see if this is onboarding. In such case, use user token type and the provided user id.
5486
		if ( isset( $this->HTTP_RAW_POST_DATA ) || ! empty( $_GET['onboarding'] ) ) {
5487
			if ( ! empty( $_GET['onboarding'] ) ) {
5488
				$jpo = $_GET;
5489
			} else {
5490
				$jpo = json_decode( $this->HTTP_RAW_POST_DATA, true );
5491
			}
5492
5493
			$jpo_token = ! empty( $jpo['onboarding']['token'] ) ? $jpo['onboarding']['token'] : null;
5494
			$jpo_user = ! empty( $jpo['onboarding']['jpUser'] ) ? $jpo['onboarding']['jpUser'] : null;
5495
5496
			if (
5497
				isset( $jpo_user ) && isset( $jpo_token ) &&
5498
				is_email( $jpo_user ) && ctype_alnum( $jpo_token ) &&
5499
				isset( $_GET['rest_route'] ) &&
5500
				self::validate_onboarding_token_action( $jpo_token, $_GET['rest_route'] )
5501
			) {
5502
				$jpUser = get_user_by( 'email', $jpo_user );
5503
				if ( is_a( $jpUser, 'WP_User' ) ) {
5504
					wp_set_current_user( $jpUser->ID );
5505
					$user_can = is_multisite()
5506
						? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
5507
						: current_user_can( 'manage_options' );
5508
					if ( $user_can ) {
5509
						$token_type = 'user';
5510
						$token->external_user_id = $jpUser->ID;
5511
					}
5512
				}
5513
			}
5514
		}
5515
5516
		$this->xmlrpc_verification = array(
5517
			'type'    => $token_type,
5518
			'user_id' => $token->external_user_id,
5519
		);
5520
5521
		return $this->xmlrpc_verification;
5522
	}
5523
5524
	/**
5525
	 * Authenticates XML-RPC and other requests from the Jetpack Server
5526
	 */
5527
	function authenticate_jetpack( $user, $username, $password ) {
5528
		if ( is_a( $user, 'WP_User' ) ) {
5529
			return $user;
5530
		}
5531
5532
		$token_details = $this->verify_xml_rpc_signature();
5533
5534
		if ( ! $token_details || is_wp_error( $token_details ) ) {
5535
			return $user;
5536
		}
5537
5538
		if ( 'user' !== $token_details['type'] ) {
5539
			return $user;
5540
		}
5541
5542
		if ( ! $token_details['user_id'] ) {
5543
			return $user;
5544
		}
5545
5546
		nocache_headers();
5547
5548
		return new WP_User( $token_details['user_id'] );
5549
	}
5550
5551
	// Authenticates requests from Jetpack server to WP REST API endpoints.
5552
	// Uses the existing XMLRPC request signing implementation.
5553
	function wp_rest_authenticate( $user ) {
5554
		if ( ! empty( $user ) ) {
5555
			// Another authentication method is in effect.
5556
			return $user;
5557
		}
5558
5559
		if ( ! isset( $_GET['_for'] ) || $_GET['_for'] !== 'jetpack' ) {
5560
			// Nothing to do for this authentication method.
5561
			return null;
5562
		}
5563
5564
		if ( ! isset( $_GET['token'] ) && ! isset( $_GET['signature'] ) ) {
5565
			// Nothing to do for this authentication method.
5566
			return null;
5567
		}
5568
5569
		// Ensure that we always have the request body available.  At this
5570
		// point, the WP REST API code to determine the request body has not
5571
		// run yet.  That code may try to read from 'php://input' later, but
5572
		// this can only be done once per request in PHP versions prior to 5.6.
5573
		// So we will go ahead and perform this read now if needed, and save
5574
		// the request body where both the Jetpack signature verification code
5575
		// and the WP REST API code can see it.
5576
		if ( ! isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ) {
5577
			$GLOBALS['HTTP_RAW_POST_DATA'] = file_get_contents( 'php://input' );
5578
		}
5579
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
5580
5581
		// Only support specific request parameters that have been tested and
5582
		// are known to work with signature verification.  A different method
5583
		// can be passed to the WP REST API via the '?_method=' parameter if
5584
		// needed.
5585
		if ( $_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
5586
			$this->rest_authentication_status = new WP_Error(
5587
				'rest_invalid_request',
5588
				__( 'This request method is not supported.', 'jetpack' ),
5589
				array( 'status' => 400 )
5590
			);
5591
			return null;
5592
		}
5593
		if ( $_SERVER['REQUEST_METHOD'] !== 'POST' && ! empty( $this->HTTP_RAW_POST_DATA ) ) {
5594
			$this->rest_authentication_status = new WP_Error(
5595
				'rest_invalid_request',
5596
				__( 'This request method does not support body parameters.', 'jetpack' ),
5597
				array( 'status' => 400 )
5598
			);
5599
			return null;
5600
		}
5601
5602
		if ( ! empty( $_SERVER['CONTENT_TYPE'] ) ) {
5603
			$content_type = $_SERVER['CONTENT_TYPE'];
5604
		} elseif ( ! empty( $_SERVER['HTTP_CONTENT_TYPE'] ) ) {
5605
			$content_type = $_SERVER['HTTP_CONTENT_TYPE'];
5606
		}
5607
5608
		if (
5609
			isset( $content_type ) &&
5610
			$content_type !== 'application/x-www-form-urlencoded' &&
5611
			$content_type !== 'application/json'
5612
		) {
5613
			$this->rest_authentication_status = new WP_Error(
5614
				'rest_invalid_request',
5615
				__( 'This Content-Type is not supported.', 'jetpack' ),
5616
				array( 'status' => 400 )
5617
			);
5618
			return null;
5619
		}
5620
5621
		$verified = $this->verify_xml_rpc_signature();
5622
5623
		if ( is_wp_error( $verified ) ) {
5624
			$this->rest_authentication_status = $verified;
5625
			return null;
5626
		}
5627
5628
		if (
5629
			$verified &&
5630
			isset( $verified['type'] ) &&
5631
			'user' === $verified['type'] &&
5632
			! empty( $verified['user_id'] )
5633
		) {
5634
			// Authentication successful.
5635
			$this->rest_authentication_status = true;
5636
			return $verified['user_id'];
5637
		}
5638
5639
		// Something else went wrong.  Probably a signature error.
5640
		$this->rest_authentication_status = new WP_Error(
5641
			'rest_invalid_signature',
5642
			__( 'The request is not signed correctly.', 'jetpack' ),
5643
			array( 'status' => 400 )
5644
		);
5645
		return null;
5646
	}
5647
5648
	/**
5649
	 * Report authentication status to the WP REST API.
5650
	 *
5651
	 * @param  WP_Error|mixed $result Error from another authentication handler, null if we should handle it, or another value if not
5652
	 * @return WP_Error|boolean|null {@see WP_JSON_Server::check_authentication}
5653
	 */
5654
	public function wp_rest_authentication_errors( $value ) {
5655
		if ( $value !== null ) {
5656
			return $value;
5657
		}
5658
		return $this->rest_authentication_status;
5659
	}
5660
5661
	function add_nonce( $timestamp, $nonce ) {
5662
		global $wpdb;
5663
		static $nonces_used_this_request = array();
5664
5665
		if ( isset( $nonces_used_this_request["$timestamp:$nonce"] ) ) {
5666
			return $nonces_used_this_request["$timestamp:$nonce"];
5667
		}
5668
5669
		// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce
5670
		$timestamp = (int) $timestamp;
5671
		$nonce     = esc_sql( $nonce );
5672
5673
		// Raw query so we can avoid races: add_option will also update
5674
		$show_errors = $wpdb->show_errors( false );
5675
5676
		$old_nonce = $wpdb->get_row(
5677
			$wpdb->prepare( "SELECT * FROM `$wpdb->options` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
5678
		);
5679
5680
		if ( is_null( $old_nonce ) ) {
5681
			$return = $wpdb->query(
5682
				$wpdb->prepare(
5683
					"INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
5684
					"jetpack_nonce_{$timestamp}_{$nonce}",
5685
					time(),
5686
					'no'
5687
				)
5688
			);
5689
		} else {
5690
			$return = false;
5691
		}
5692
5693
		$wpdb->show_errors( $show_errors );
5694
5695
		$nonces_used_this_request["$timestamp:$nonce"] = $return;
5696
5697
		return $return;
5698
	}
5699
5700
	/**
5701
	 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths since it is passed by reference to various methods.
5702
	 * Capture it here so we can verify the signature later.
5703
	 */
5704
	function xmlrpc_methods( $methods ) {
5705
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
5706
		return $methods;
5707
	}
5708
5709
	function public_xmlrpc_methods( $methods ) {
5710
		if ( array_key_exists( 'wp.getOptions', $methods ) ) {
5711
			$methods['wp.getOptions'] = array( $this, 'jetpack_getOptions' );
5712
		}
5713
		return $methods;
5714
	}
5715
5716
	function jetpack_getOptions( $args ) {
5717
		global $wp_xmlrpc_server;
5718
5719
		$wp_xmlrpc_server->escape( $args );
5720
5721
		$username	= $args[1];
5722
		$password	= $args[2];
5723
5724
		if ( !$user = $wp_xmlrpc_server->login($username, $password) ) {
5725
			return $wp_xmlrpc_server->error;
5726
		}
5727
5728
		$options = array();
5729
		$user_data = $this->get_connected_user_data();
5730
		if ( is_array( $user_data ) ) {
5731
			$options['jetpack_user_id'] = array(
5732
				'desc'          => __( 'The WP.com user ID of the connected user', 'jetpack' ),
5733
				'readonly'      => true,
5734
				'value'         => $user_data['ID'],
5735
			);
5736
			$options['jetpack_user_login'] = array(
5737
				'desc'          => __( 'The WP.com username of the connected user', 'jetpack' ),
5738
				'readonly'      => true,
5739
				'value'         => $user_data['login'],
5740
			);
5741
			$options['jetpack_user_email'] = array(
5742
				'desc'          => __( 'The WP.com user email of the connected user', 'jetpack' ),
5743
				'readonly'      => true,
5744
				'value'         => $user_data['email'],
5745
			);
5746
			$options['jetpack_user_site_count'] = array(
5747
				'desc'          => __( 'The number of sites of the connected WP.com user', 'jetpack' ),
5748
				'readonly'      => true,
5749
				'value'         => $user_data['site_count'],
5750
			);
5751
		}
5752
		$wp_xmlrpc_server->blog_options = array_merge( $wp_xmlrpc_server->blog_options, $options );
5753
		$args = stripslashes_deep( $args );
5754
		return $wp_xmlrpc_server->wp_getOptions( $args );
5755
	}
5756
5757
	function xmlrpc_options( $options ) {
5758
		$jetpack_client_id = false;
5759
		if ( self::is_active() ) {
5760
			$jetpack_client_id = Jetpack_Options::get_option( 'id' );
5761
		}
5762
		$options['jetpack_version'] = array(
5763
				'desc'          => __( 'Jetpack Plugin Version', 'jetpack' ),
5764
				'readonly'      => true,
5765
				'value'         => JETPACK__VERSION,
5766
		);
5767
5768
		$options['jetpack_client_id'] = array(
5769
				'desc'          => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack' ),
5770
				'readonly'      => true,
5771
				'value'         => $jetpack_client_id,
5772
		);
5773
		return $options;
5774
	}
5775
5776
	public static function clean_nonces( $all = false ) {
5777
		global $wpdb;
5778
5779
		$sql = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
5780
		$sql_args = array( $wpdb->esc_like( 'jetpack_nonce_' ) . '%' );
5781
5782
		if ( true !== $all ) {
5783
			$sql .= ' AND CAST( `option_value` AS UNSIGNED ) < %d';
5784
			$sql_args[] = time() - 3600;
5785
		}
5786
5787
		$sql .= ' ORDER BY `option_id` LIMIT 100';
5788
5789
		$sql = $wpdb->prepare( $sql, $sql_args );
5790
5791
		for ( $i = 0; $i < 1000; $i++ ) {
5792
			if ( ! $wpdb->query( $sql ) ) {
5793
				break;
5794
			}
5795
		}
5796
	}
5797
5798
	/**
5799
	 * State is passed via cookies from one request to the next, but never to subsequent requests.
5800
	 * SET: state( $key, $value );
5801
	 * GET: $value = state( $key );
5802
	 *
5803
	 * @param string $key
5804
	 * @param string $value
5805
	 * @param bool $restate private
5806
	 */
5807
	public static function state( $key = null, $value = null, $restate = false ) {
5808
		static $state = array();
5809
		static $path, $domain;
5810
		if ( ! isset( $path ) ) {
5811
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
5812
			$admin_url = Jetpack::admin_url();
5813
			$bits      = parse_url( $admin_url );
5814
5815
			if ( is_array( $bits ) ) {
5816
				$path   = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
5817
				$domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
5818
			} else {
5819
				$path = $domain = null;
5820
			}
5821
		}
5822
5823
		// Extract state from cookies and delete cookies
5824
		if ( isset( $_COOKIE[ 'jetpackState' ] ) && is_array( $_COOKIE[ 'jetpackState' ] ) ) {
5825
			$yum = $_COOKIE[ 'jetpackState' ];
5826
			unset( $_COOKIE[ 'jetpackState' ] );
5827
			foreach ( $yum as $k => $v ) {
5828
				if ( strlen( $v ) )
5829
					$state[ $k ] = $v;
5830
				setcookie( "jetpackState[$k]", false, 0, $path, $domain );
5831
			}
5832
		}
5833
5834
		if ( $restate ) {
5835
			foreach ( $state as $k => $v ) {
5836
				setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
5837
			}
5838
			return;
5839
		}
5840
5841
		// Get a state variable
5842
		if ( isset( $key ) && ! isset( $value ) ) {
5843
			if ( array_key_exists( $key, $state ) )
5844
				return $state[ $key ];
5845
			return null;
5846
		}
5847
5848
		// Set a state variable
5849
		if ( isset ( $key ) && isset( $value ) ) {
5850
			if( is_array( $value ) && isset( $value[0] ) ) {
5851
				$value = $value[0];
5852
			}
5853
			$state[ $key ] = $value;
5854
			setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
5855
		}
5856
	}
5857
5858
	public static function restate() {
5859
		Jetpack::state( null, null, true );
5860
	}
5861
5862
	public static function check_privacy( $file ) {
5863
		static $is_site_publicly_accessible = null;
5864
5865
		if ( is_null( $is_site_publicly_accessible ) ) {
5866
			$is_site_publicly_accessible = false;
5867
5868
			Jetpack::load_xml_rpc_client();
5869
			$rpc = new Jetpack_IXR_Client();
5870
5871
			$success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
5872
			if ( $success ) {
5873
				$response = $rpc->getResponse();
5874
				if ( $response ) {
5875
					$is_site_publicly_accessible = true;
5876
				}
5877
			}
5878
5879
			Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
5880
		}
5881
5882
		if ( $is_site_publicly_accessible ) {
5883
			return;
5884
		}
5885
5886
		$module_slug = self::get_module_slug( $file );
5887
5888
		$privacy_checks = Jetpack::state( 'privacy_checks' );
5889
		if ( ! $privacy_checks ) {
5890
			$privacy_checks = $module_slug;
5891
		} else {
5892
			$privacy_checks .= ",$module_slug";
5893
		}
5894
5895
		Jetpack::state( 'privacy_checks', $privacy_checks );
5896
	}
5897
5898
	/**
5899
	 * Helper method for multicall XMLRPC.
5900
	 */
5901
	public static function xmlrpc_async_call() {
5902
		global $blog_id;
5903
		static $clients = array();
5904
5905
		$client_blog_id = is_multisite() ? $blog_id : 0;
5906
5907
		if ( ! isset( $clients[$client_blog_id] ) ) {
5908
			Jetpack::load_xml_rpc_client();
5909
			$clients[$client_blog_id] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => JETPACK_MASTER_USER, ) );
5910
			if ( function_exists( 'ignore_user_abort' ) ) {
5911
				ignore_user_abort( true );
5912
			}
5913
			add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
5914
		}
5915
5916
		$args = func_get_args();
5917
5918
		if ( ! empty( $args[0] ) ) {
5919
			call_user_func_array( array( $clients[$client_blog_id], 'addCall' ), $args );
5920
		} elseif ( is_multisite() ) {
5921
			foreach ( $clients as $client_blog_id => $client ) {
5922
				if ( ! $client_blog_id || empty( $client->calls ) ) {
5923
					continue;
5924
				}
5925
5926
				$switch_success = switch_to_blog( $client_blog_id, true );
5927
				if ( ! $switch_success ) {
5928
					continue;
5929
				}
5930
5931
				flush();
5932
				$client->query();
5933
5934
				restore_current_blog();
5935
			}
5936
		} else {
5937
			if ( isset( $clients[0] ) && ! empty( $clients[0]->calls ) ) {
5938
				flush();
5939
				$clients[0]->query();
5940
			}
5941
		}
5942
	}
5943
5944
	public static function staticize_subdomain( $url ) {
5945
5946
		// Extract hostname from URL
5947
		$host = parse_url( $url, PHP_URL_HOST );
5948
5949
		// Explode hostname on '.'
5950
		$exploded_host = explode( '.', $host );
5951
5952
		// Retrieve the name and TLD
5953
		if ( count( $exploded_host ) > 1 ) {
5954
			$name = $exploded_host[ count( $exploded_host ) - 2 ];
5955
			$tld = $exploded_host[ count( $exploded_host ) - 1 ];
5956
			// Rebuild domain excluding subdomains
5957
			$domain = $name . '.' . $tld;
5958
		} else {
5959
			$domain = $host;
5960
		}
5961
		// Array of Automattic domains
5962
		$domain_whitelist = array( 'wordpress.com', 'wp.com' );
5963
5964
		// Return $url if not an Automattic domain
5965
		if ( ! in_array( $domain, $domain_whitelist ) ) {
5966
			return $url;
5967
		}
5968
5969
		if ( is_ssl() ) {
5970
			return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
5971
		}
5972
5973
		srand( crc32( basename( $url ) ) );
5974
		$static_counter = rand( 0, 2 );
5975
		srand(); // this resets everything that relies on this, like array_rand() and shuffle()
5976
5977
		return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
5978
	}
5979
5980
/* JSON API Authorization */
5981
5982
	/**
5983
	 * Handles the login action for Authorizing the JSON API
5984
	 */
5985
	function login_form_json_api_authorization() {
5986
		$this->verify_json_api_authorization_request();
5987
5988
		add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
5989
5990
		add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
5991
		add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5992
		add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
5993
	}
5994
5995
	// Make sure the login form is POSTed to the signed URL so we can reverify the request
5996
	function post_login_form_to_signed_url( $url, $path, $scheme ) {
5997
		if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
5998
			return $url;
5999
		}
6000
6001
		$parsed_url = parse_url( $url );
6002
		$url = strtok( $url, '?' );
6003
		$url = "$url?{$_SERVER['QUERY_STRING']}";
6004
		if ( ! empty( $parsed_url['query'] ) )
6005
			$url .= "&{$parsed_url['query']}";
6006
6007
		return $url;
6008
	}
6009
6010
	// Make sure the POSTed request is handled by the same action
6011
	function preserve_action_in_login_form_for_json_api_authorization() {
6012
		echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
6013
		echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) . "' />\n";
6014
	}
6015
6016
	// If someone logs in to approve API access, store the Access Code in usermeta
6017
	function store_json_api_authorization_token( $user_login, $user ) {
6018
		add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
6019
		add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
6020
		$token = wp_generate_password( 32, false );
6021
		update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
6022
	}
6023
6024
	// Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
6025
	function allow_wpcom_public_api_domain( $domains ) {
6026
		$domains[] = 'public-api.wordpress.com';
6027
		return $domains;
6028
	}
6029
6030
	// Add all wordpress.com environments to the safe redirect whitelist
6031
	function allow_wpcom_environments( $domains ) {
6032
		$domains[] = 'wordpress.com';
6033
		$domains[] = 'wpcalypso.wordpress.com';
6034
		$domains[] = 'horizon.wordpress.com';
6035
		$domains[] = 'calypso.localhost';
6036
		return $domains;
6037
	}
6038
6039
	// Add the Access Code details to the public-api.wordpress.com redirect
6040
	function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
6041
		return add_query_arg(
6042
			urlencode_deep(
6043
				array(
6044
					'jetpack-code'    => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
6045
					'jetpack-user-id' => (int) $user->ID,
6046
					'jetpack-state'   => $this->json_api_authorization_request['state'],
6047
				)
6048
			),
6049
			$redirect_to
6050
		);
6051
	}
6052
6053
6054
	/**
6055
	 * Verifies the request by checking the signature
6056
	 *
6057
	 * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
6058
	 * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
6059
	 *
6060
	 * @param null|array $environment
6061
	 */
6062
	function verify_json_api_authorization_request( $environment = null ) {
6063
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
6064
6065
		$environment = is_null( $environment )
6066
			? $_REQUEST
6067
			: $environment;
6068
6069
		list( $envToken, $envVersion, $envUserId ) = explode( ':', $environment['token'] );
6070
		$token = Jetpack_Data::get_access_token( $envUserId );
6071
		if ( ! $token || empty( $token->secret ) ) {
6072
			wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.' , 'jetpack' ) );
6073
		}
6074
6075
		$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' );
6076
6077
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
6078
6079
		if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
6080
			$signature = $jetpack_signature->sign_request(
6081
				$environment['token'],
6082
				$environment['timestamp'],
6083
				$environment['nonce'],
6084
				'',
6085
				'GET',
6086
				$environment['jetpack_json_api_original_query'],
6087
				null,
6088
				true
6089
			);
6090
		} else {
6091
			$signature = $jetpack_signature->sign_current_request( array( 'body' => null, 'method' => 'GET' ) );
6092
		}
6093
6094
		if ( ! $signature ) {
6095
			wp_die( $die_error );
6096
		} else if ( is_wp_error( $signature ) ) {
6097
			wp_die( $die_error );
6098
		} else if ( ! hash_equals( $signature, $environment['signature'] ) ) {
6099
			if ( is_ssl() ) {
6100
				// 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
6101
				$signature = $jetpack_signature->sign_current_request( array( 'scheme' => 'http', 'body' => null, 'method' => 'GET' ) );
6102
				if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
6103
					wp_die( $die_error );
6104
				}
6105
			} else {
6106
				wp_die( $die_error );
6107
			}
6108
		}
6109
6110
		$timestamp = (int) $environment['timestamp'];
6111
		$nonce     = stripslashes( (string) $environment['nonce'] );
6112
6113
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
6114
			// De-nonce the nonce, at least for 5 minutes.
6115
			// 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)
6116
			$old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
6117
			if ( $old_nonce_time < time() - 300 ) {
6118
				wp_die( __( 'The authorization process expired.  Please go back and try again.' , 'jetpack' ) );
6119
			}
6120
		}
6121
6122
		$data = json_decode( base64_decode( stripslashes( $environment['data'] ) ) );
6123
		$data_filters = array(
6124
			'state'        => 'opaque',
6125
			'client_id'    => 'int',
6126
			'client_title' => 'string',
6127
			'client_image' => 'url',
6128
		);
6129
6130
		foreach ( $data_filters as $key => $sanitation ) {
6131
			if ( ! isset( $data->$key ) ) {
6132
				wp_die( $die_error );
6133
			}
6134
6135
			switch ( $sanitation ) {
6136
			case 'int' :
6137
				$this->json_api_authorization_request[$key] = (int) $data->$key;
6138
				break;
6139
			case 'opaque' :
6140
				$this->json_api_authorization_request[$key] = (string) $data->$key;
6141
				break;
6142
			case 'string' :
6143
				$this->json_api_authorization_request[$key] = wp_kses( (string) $data->$key, array() );
6144
				break;
6145
			case 'url' :
6146
				$this->json_api_authorization_request[$key] = esc_url_raw( (string) $data->$key );
6147
				break;
6148
			}
6149
		}
6150
6151
		if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
6152
			wp_die( $die_error );
6153
		}
6154
	}
6155
6156
	function login_message_json_api_authorization( $message ) {
6157
		return '<p class="message">' . sprintf(
6158
			esc_html__( '%s wants to access your site&#8217;s data.  Log in to authorize that access.' , 'jetpack' ),
6159
			'<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
6160
		) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
6161
	}
6162
6163
	/**
6164
	 * Get $content_width, but with a <s>twist</s> filter.
6165
	 */
6166
	public static function get_content_width() {
6167
		$content_width = isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : false;
6168
		/**
6169
		 * Filter the Content Width value.
6170
		 *
6171
		 * @since 2.2.3
6172
		 *
6173
		 * @param string $content_width Content Width value.
6174
		 */
6175
		return apply_filters( 'jetpack_content_width', $content_width );
6176
	}
6177
6178
	/**
6179
	 * Pings the WordPress.com Mirror Site for the specified options.
6180
	 *
6181
	 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
6182
	 *
6183
	 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
6184
	 */
6185
	public function get_cloud_site_options( $option_names ) {
6186
		$option_names = array_filter( (array) $option_names, 'is_string' );
6187
6188
		Jetpack::load_xml_rpc_client();
6189
		$xml = new Jetpack_IXR_Client( array( 'user_id' => JETPACK_MASTER_USER, ) );
6190
		$xml->query( 'jetpack.fetchSiteOptions', $option_names );
6191
		if ( $xml->isError() ) {
6192
			return array(
6193
				'error_code' => $xml->getErrorCode(),
6194
				'error_msg'  => $xml->getErrorMessage(),
6195
			);
6196
		}
6197
		$cloud_site_options = $xml->getResponse();
6198
6199
		return $cloud_site_options;
6200
	}
6201
6202
	/**
6203
	 * Checks if the site is currently in an identity crisis.
6204
	 *
6205
	 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
6206
	 */
6207
	public static function check_identity_crisis() {
6208
		if ( ! Jetpack::is_active() || Jetpack::is_development_mode() || ! self::validate_sync_error_idc_option() ) {
6209
			return false;
6210
		}
6211
6212
		return Jetpack_Options::get_option( 'sync_error_idc' );
6213
	}
6214
6215
	/**
6216
	 * Checks whether the home and siteurl specifically are whitelisted
6217
	 * Written so that we don't have re-check $key and $value params every time
6218
	 * we want to check if this site is whitelisted, for example in footer.php
6219
	 *
6220
	 * @since  3.8.0
6221
	 * @return bool True = already whitelisted False = not whitelisted
6222
	 */
6223
	public static function is_staging_site() {
6224
		$is_staging = false;
6225
6226
		$known_staging = array(
6227
			'urls' => array(
6228
				'#\.staging\.wpengine\.com$#i', // WP Engine
6229
				'#\.staging\.kinsta\.com$#i',   // Kinsta.com
6230
				),
6231
			'constants' => array(
6232
				'IS_WPE_SNAPSHOT',      // WP Engine
6233
				'KINSTA_DEV_ENV',       // Kinsta.com
6234
				'WPSTAGECOACH_STAGING', // WP Stagecoach
6235
				'JETPACK_STAGING_MODE', // Generic
6236
				)
6237
			);
6238
		/**
6239
		 * Filters the flags of known staging sites.
6240
		 *
6241
		 * @since 3.9.0
6242
		 *
6243
		 * @param array $known_staging {
6244
		 *     An array of arrays that each are used to check if the current site is staging.
6245
		 *     @type array $urls      URLs of staging sites in regex to check against site_url.
6246
		 *     @type array $constants PHP constants of known staging/developement environments.
6247
		 *  }
6248
		 */
6249
		$known_staging = apply_filters( 'jetpack_known_staging', $known_staging );
6250
6251
		if ( isset( $known_staging['urls'] ) ) {
6252
			foreach ( $known_staging['urls'] as $url ){
6253
				if ( preg_match( $url, site_url() ) ) {
6254
					$is_staging = true;
6255
					break;
6256
				}
6257
			}
6258
		}
6259
6260
		if ( isset( $known_staging['constants'] ) ) {
6261
			foreach ( $known_staging['constants'] as $constant ) {
6262
				if ( defined( $constant ) && constant( $constant ) ) {
6263
					$is_staging = true;
6264
				}
6265
			}
6266
		}
6267
6268
		// Last, let's check if sync is erroring due to an IDC. If so, set the site to staging mode.
6269
		if ( ! $is_staging && self::validate_sync_error_idc_option() ) {
6270
			$is_staging = true;
6271
		}
6272
6273
		/**
6274
		 * Filters is_staging_site check.
6275
		 *
6276
		 * @since 3.9.0
6277
		 *
6278
		 * @param bool $is_staging If the current site is a staging site.
6279
		 */
6280
		return apply_filters( 'jetpack_is_staging_site', $is_staging );
6281
	}
6282
6283
	/**
6284
	 * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
6285
	 *
6286
	 * @since 4.4.0
6287
	 * @since 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC
6288
	 *
6289
	 * @return bool
6290
	 */
6291
	public static function validate_sync_error_idc_option() {
6292
		$is_valid = false;
6293
6294
		$idc_allowed = get_transient( 'jetpack_idc_allowed' );
6295
		if ( false === $idc_allowed ) {
6296
			$response = wp_remote_get( 'https://jetpack.com/is-idc-allowed/' );
6297
			if ( 200 === (int) wp_remote_retrieve_response_code( $response ) ) {
6298
				$json = json_decode( wp_remote_retrieve_body( $response ) );
6299
				$idc_allowed = isset( $json, $json->result ) && $json->result ? '1' : '0';
6300
				$transient_duration = HOUR_IN_SECONDS;
6301
			} else {
6302
				// If the request failed for some reason, then assume IDC is allowed and set shorter transient.
6303
				$idc_allowed = '1';
6304
				$transient_duration = 5 * MINUTE_IN_SECONDS;
6305
			}
6306
6307
			set_transient( 'jetpack_idc_allowed', $idc_allowed, $transient_duration );
6308
		}
6309
6310
		// Is the site opted in and does the stored sync_error_idc option match what we now generate?
6311
		$sync_error = Jetpack_Options::get_option( 'sync_error_idc' );
6312
		if ( $idc_allowed && $sync_error && self::sync_idc_optin() ) {
6313
			$local_options = self::get_sync_error_idc_option();
6314
			if ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) {
6315
				$is_valid = true;
6316
			}
6317
		}
6318
6319
		/**
6320
		 * Filters whether the sync_error_idc option is valid.
6321
		 *
6322
		 * @since 4.4.0
6323
		 *
6324
		 * @param bool $is_valid If the sync_error_idc is valid or not.
6325
		 */
6326
		$is_valid = (bool) apply_filters( 'jetpack_sync_error_idc_validation', $is_valid );
6327
6328
		if ( ! $idc_allowed || ( ! $is_valid && $sync_error ) ) {
6329
			// Since the option exists, and did not validate, delete it
6330
			Jetpack_Options::delete_option( 'sync_error_idc' );
6331
		}
6332
6333
		return $is_valid;
6334
	}
6335
6336
	/**
6337
	 * Normalizes a url by doing three things:
6338
	 *  - Strips protocol
6339
	 *  - Strips www
6340
	 *  - Adds a trailing slash
6341
	 *
6342
	 * @since 4.4.0
6343
	 * @param string $url
6344
	 * @return WP_Error|string
6345
	 */
6346
	public static function normalize_url_protocol_agnostic( $url ) {
6347
		$parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
6348
		if ( ! $parsed_url || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
6349
			return new WP_Error( 'cannot_parse_url', sprintf( esc_html__( 'Cannot parse URL %s', 'jetpack' ), $url ) );
6350
		}
6351
6352
		// Strip www and protocols
6353
		$url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
6354
		return $url;
6355
	}
6356
6357
	/**
6358
	 * Gets the value that is to be saved in the jetpack_sync_error_idc option.
6359
	 *
6360
	 * @since 4.4.0
6361
	 * @since 5.4.0 Add transient since home/siteurl retrieved directly from DB
6362
	 *
6363
	 * @param array $response
6364
	 * @return array Array of the local urls, wpcom urls, and error code
6365
	 */
6366
	public static function get_sync_error_idc_option( $response = array() ) {
6367
		// Since the local options will hit the database directly, store the values
6368
		// in a transient to allow for autoloading and caching on subsequent views.
6369
		$local_options = get_transient( 'jetpack_idc_local' );
6370
		if ( false === $local_options ) {
6371
			require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php';
6372
			$local_options = array(
6373
				'home'    => Jetpack_Sync_Functions::home_url(),
6374
				'siteurl' => Jetpack_Sync_Functions::site_url(),
6375
			);
6376
			set_transient( 'jetpack_idc_local', $local_options, MINUTE_IN_SECONDS );
6377
		}
6378
6379
		$options = array_merge( $local_options, $response );
6380
6381
		$returned_values = array();
6382
		foreach( $options as $key => $option ) {
6383
			if ( 'error_code' === $key ) {
6384
				$returned_values[ $key ] = $option;
6385
				continue;
6386
			}
6387
6388
			if ( is_wp_error( $normalized_url = self::normalize_url_protocol_agnostic( $option ) ) ) {
6389
				continue;
6390
			}
6391
6392
			$returned_values[ $key ] = $normalized_url;
6393
		}
6394
6395
		set_transient( 'jetpack_idc_option', $returned_values, MINUTE_IN_SECONDS );
6396
6397
		return $returned_values;
6398
	}
6399
6400
	/**
6401
	 * Returns the value of the jetpack_sync_idc_optin filter, or constant.
6402
	 * If set to true, the site will be put into staging mode.
6403
	 *
6404
	 * @since 4.3.2
6405
	 * @return bool
6406
	 */
6407
	public static function sync_idc_optin() {
6408
		if ( Jetpack_Constants::is_defined( 'JETPACK_SYNC_IDC_OPTIN' ) ) {
6409
			$default = Jetpack_Constants::get_constant( 'JETPACK_SYNC_IDC_OPTIN' );
6410
		} else {
6411
			$default = ! Jetpack_Constants::is_defined( 'SUNRISE' ) && ! is_multisite();
6412
		}
6413
6414
		/**
6415
		 * Allows sites to optin to IDC mitigation which blocks the site from syncing to WordPress.com when the home
6416
		 * URL or site URL do not match what WordPress.com expects. The default value is either false, or the value of
6417
		 * JETPACK_SYNC_IDC_OPTIN constant if set.
6418
		 *
6419
		 * @since 4.3.2
6420
		 *
6421
		 * @param bool $default Whether the site is opted in to IDC mitigation.
6422
		 */
6423
		return (bool) apply_filters( 'jetpack_sync_idc_optin', $default );
6424
	}
6425
6426
	/**
6427
	 * Maybe Use a .min.css stylesheet, maybe not.
6428
	 *
6429
	 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
6430
	 */
6431
	public static function maybe_min_asset( $url, $path, $plugin ) {
6432
		// Short out on things trying to find actual paths.
6433
		if ( ! $path || empty( $plugin ) ) {
6434
			return $url;
6435
		}
6436
6437
		$path = ltrim( $path, '/' );
6438
6439
		// Strip out the abspath.
6440
		$base = dirname( plugin_basename( $plugin ) );
6441
6442
		// Short out on non-Jetpack assets.
6443
		if ( 'jetpack/' !== substr( $base, 0, 8 ) ) {
6444
			return $url;
6445
		}
6446
6447
		// File name parsing.
6448
		$file              = "{$base}/{$path}";
6449
		$full_path         = JETPACK__PLUGIN_DIR . substr( $file, 8 );
6450
		$file_name         = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
6451
		$file_name_parts_r = array_reverse( explode( '.', $file_name ) );
6452
		$extension         = array_shift( $file_name_parts_r );
6453
6454
		if ( in_array( strtolower( $extension ), array( 'css', 'js' ) ) ) {
6455
			// Already pointing at the minified version.
6456
			if ( 'min' === $file_name_parts_r[0] ) {
6457
				return $url;
6458
			}
6459
6460
			$min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
6461
			if ( file_exists( $min_full_path ) ) {
6462
				$url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
6463
				// If it's a CSS file, stash it so we can set the .min suffix for rtl-ing.
6464
				if ( 'css' === $extension ) {
6465
					$key = str_replace( JETPACK__PLUGIN_DIR, 'jetpack/', $min_full_path );
6466
					self::$min_assets[ $key ] = $path;
6467
				}
6468
			}
6469
		}
6470
6471
		return $url;
6472
	}
6473
6474
	/**
6475
	 * If the asset is minified, let's flag .min as the suffix.
6476
	 *
6477
	 * Attached to `style_loader_src` filter.
6478
	 *
6479
	 * @param string $tag The tag that would link to the external asset.
6480
	 * @param string $handle The registered handle of the script in question.
6481
	 * @param string $href The url of the asset in question.
6482
	 */
6483
	public static function set_suffix_on_min( $src, $handle ) {
6484
		if ( false === strpos( $src, '.min.css' ) ) {
6485
			return $src;
6486
		}
6487
6488
		if ( ! empty( self::$min_assets ) ) {
6489
			foreach ( self::$min_assets as $file => $path ) {
6490
				if ( false !== strpos( $src, $file ) ) {
6491
					wp_style_add_data( $handle, 'suffix', '.min' );
6492
					return $src;
6493
				}
6494
			}
6495
		}
6496
6497
		return $src;
6498
	}
6499
6500
	/**
6501
	 * Maybe inlines a stylesheet.
6502
	 *
6503
	 * If you'd like to inline a stylesheet instead of printing a link to it,
6504
	 * wp_style_add_data( 'handle', 'jetpack-inline', true );
6505
	 *
6506
	 * Attached to `style_loader_tag` filter.
6507
	 *
6508
	 * @param string $tag The tag that would link to the external asset.
6509
	 * @param string $handle The registered handle of the script in question.
6510
	 *
6511
	 * @return string
6512
	 */
6513
	public static function maybe_inline_style( $tag, $handle ) {
6514
		global $wp_styles;
6515
		$item = $wp_styles->registered[ $handle ];
6516
6517
		if ( ! isset( $item->extra['jetpack-inline'] ) || ! $item->extra['jetpack-inline'] ) {
6518
			return $tag;
6519
		}
6520
6521
		if ( preg_match( '# href=\'([^\']+)\' #i', $tag, $matches ) ) {
6522
			$href = $matches[1];
6523
			// Strip off query string
6524
			if ( $pos = strpos( $href, '?' ) ) {
6525
				$href = substr( $href, 0, $pos );
6526
			}
6527
			// Strip off fragment
6528
			if ( $pos = strpos( $href, '#' ) ) {
6529
				$href = substr( $href, 0, $pos );
6530
			}
6531
		} else {
6532
			return $tag;
6533
		}
6534
6535
		$plugins_dir = plugin_dir_url( JETPACK__PLUGIN_FILE );
6536
		if ( $plugins_dir !== substr( $href, 0, strlen( $plugins_dir ) ) ) {
6537
			return $tag;
6538
		}
6539
6540
		// If this stylesheet has a RTL version, and the RTL version replaces normal...
6541
		if ( isset( $item->extra['rtl'] ) && 'replace' === $item->extra['rtl'] && is_rtl() ) {
6542
			// And this isn't the pass that actually deals with the RTL version...
6543
			if ( false === strpos( $tag, " id='$handle-rtl-css' " ) ) {
6544
				// Short out, as the RTL version will deal with it in a moment.
6545
				return $tag;
6546
			}
6547
		}
6548
6549
		$file = JETPACK__PLUGIN_DIR . substr( $href, strlen( $plugins_dir ) );
6550
		$css  = Jetpack::absolutize_css_urls( file_get_contents( $file ), $href );
6551
		if ( $css ) {
6552
			$tag = "<!-- Inline {$item->handle} -->\r\n";
6553
			if ( empty( $item->extra['after'] ) ) {
6554
				wp_add_inline_style( $handle, $css );
6555
			} else {
6556
				array_unshift( $item->extra['after'], $css );
6557
				wp_style_add_data( $handle, 'after', $item->extra['after'] );
6558
			}
6559
		}
6560
6561
		return $tag;
6562
	}
6563
6564
	/**
6565
	 * Loads a view file from the views
6566
	 *
6567
	 * Data passed in with the $data parameter will be available in the
6568
	 * template file as $data['value']
6569
	 *
6570
	 * @param string $template - Template file to load
6571
	 * @param array $data - Any data to pass along to the template
6572
	 * @return boolean - If template file was found
6573
	 **/
6574
	public function load_view( $template, $data = array() ) {
6575
		$views_dir = JETPACK__PLUGIN_DIR . 'views/';
6576
6577
		if( file_exists( $views_dir . $template ) ) {
6578
			require_once( $views_dir . $template );
6579
			return true;
6580
		}
6581
6582
		error_log( "Jetpack: Unable to find view file $views_dir$template" );
6583
		return false;
6584
	}
6585
6586
	/**
6587
	 * Throws warnings for deprecated hooks to be removed from Jetpack
6588
	 */
6589
	public function deprecated_hooks() {
6590
		global $wp_filter;
6591
6592
		/*
6593
		 * Format:
6594
		 * deprecated_filter_name => replacement_name
6595
		 *
6596
		 * If there is no replacement, use null for replacement_name
6597
		 */
6598
		$deprecated_list = array(
6599
			'jetpack_bail_on_shortcode'                              => 'jetpack_shortcodes_to_include',
6600
			'wpl_sharing_2014_1'                                     => null,
6601
			'jetpack-tools-to-include'                               => 'jetpack_tools_to_include',
6602
			'jetpack_identity_crisis_options_to_check'               => null,
6603
			'update_option_jetpack_single_user_site'                 => null,
6604
			'audio_player_default_colors'                            => null,
6605
			'add_option_jetpack_featured_images_enabled'             => null,
6606
			'add_option_jetpack_update_details'                      => null,
6607
			'add_option_jetpack_updates'                             => null,
6608
			'add_option_jetpack_network_name'                        => null,
6609
			'add_option_jetpack_network_allow_new_registrations'     => null,
6610
			'add_option_jetpack_network_add_new_users'               => null,
6611
			'add_option_jetpack_network_site_upload_space'           => null,
6612
			'add_option_jetpack_network_upload_file_types'           => null,
6613
			'add_option_jetpack_network_enable_administration_menus' => null,
6614
			'add_option_jetpack_is_multi_site'                       => null,
6615
			'add_option_jetpack_is_main_network'                     => null,
6616
			'add_option_jetpack_main_network_site'                   => null,
6617
			'jetpack_sync_all_registered_options'                    => null,
6618
			'jetpack_has_identity_crisis'                            => 'jetpack_sync_error_idc_validation',
6619
			'jetpack_is_post_mailable'                               => null,
6620
			'jetpack_seo_site_host'                                  => null,
6621
			'jetpack_installed_plugin'                               => 'jetpack_plugin_installed',
6622
			'jetpack_holiday_snow_option_name'                       => null,
6623
			'jetpack_holiday_chance_of_snow'                         => null,
6624
			'jetpack_holiday_snow_js_url'                            => null,
6625
			'jetpack_is_holiday_snow_season'                         => null,
6626
			'jetpack_holiday_snow_option_updated'                    => null,
6627
			'jetpack_holiday_snowing'                                => null,
6628
			'jetpack_sso_auth_cookie_expirtation'                    => 'jetpack_sso_auth_cookie_expiration',
6629
			'jetpack_cache_plans'                                    => null,
6630
			'jetpack_updated_theme'                                  => 'jetpack_updated_themes',
6631
		);
6632
6633
		// This is a silly loop depth. Better way?
6634
		foreach( $deprecated_list AS $hook => $hook_alt ) {
6635
			if ( has_action( $hook ) ) {
6636
				foreach( $wp_filter[ $hook ] AS $func => $values ) {
6637
					foreach( $values AS $hooked ) {
6638
						if ( is_callable( $hooked['function'] ) ) {
6639
							$function_name = 'an anonymous function';
6640
						} else {
6641
							$function_name = $hooked['function'];
6642
						}
6643
						_deprecated_function( $hook . ' used for ' . $function_name, null, $hook_alt );
6644
					}
6645
				}
6646
			}
6647
		}
6648
	}
6649
6650
	/**
6651
	 * Converts any url in a stylesheet, to the correct absolute url.
6652
	 *
6653
	 * Considerations:
6654
	 *  - Normal, relative URLs     `feh.png`
6655
	 *  - Data URLs                 `data:image/gif;base64,eh129ehiuehjdhsa==`
6656
	 *  - Schema-agnostic URLs      `//domain.com/feh.png`
6657
	 *  - Absolute URLs             `http://domain.com/feh.png`
6658
	 *  - Domain root relative URLs `/feh.png`
6659
	 *
6660
	 * @param $css string: The raw CSS -- should be read in directly from the file.
6661
	 * @param $css_file_url : The URL that the file can be accessed at, for calculating paths from.
6662
	 *
6663
	 * @return mixed|string
6664
	 */
6665
	public static function absolutize_css_urls( $css, $css_file_url ) {
6666
		$pattern = '#url\((?P<path>[^)]*)\)#i';
6667
		$css_dir = dirname( $css_file_url );
6668
		$p       = parse_url( $css_dir );
6669
		$domain  = sprintf(
6670
					'%1$s//%2$s%3$s%4$s',
6671
					isset( $p['scheme'] )           ? "{$p['scheme']}:" : '',
6672
					isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
6673
					$p['host'],
6674
					isset( $p['port'] )             ? ":{$p['port']}" : ''
6675
				);
6676
6677
		if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
6678
			$find = $replace = array();
6679
			foreach ( $matches as $match ) {
6680
				$url = trim( $match['path'], "'\" \t" );
6681
6682
				// If this is a data url, we don't want to mess with it.
6683
				if ( 'data:' === substr( $url, 0, 5 ) ) {
6684
					continue;
6685
				}
6686
6687
				// If this is an absolute or protocol-agnostic url,
6688
				// we don't want to mess with it.
6689
				if ( preg_match( '#^(https?:)?//#i', $url ) ) {
6690
					continue;
6691
				}
6692
6693
				switch ( substr( $url, 0, 1 ) ) {
6694
					case '/':
6695
						$absolute = $domain . $url;
6696
						break;
6697
					default:
6698
						$absolute = $css_dir . '/' . $url;
6699
				}
6700
6701
				$find[]    = $match[0];
6702
				$replace[] = sprintf( 'url("%s")', $absolute );
6703
			}
6704
			$css = str_replace( $find, $replace, $css );
6705
		}
6706
6707
		return $css;
6708
	}
6709
6710
	/**
6711
	 * This methods removes all of the registered css files on the front end
6712
	 * from Jetpack in favor of using a single file. In effect "imploding"
6713
	 * all the files into one file.
6714
	 *
6715
	 * Pros:
6716
	 * - Uses only ONE css asset connection instead of 15
6717
	 * - Saves a minimum of 56k
6718
	 * - Reduces server load
6719
	 * - Reduces time to first painted byte
6720
	 *
6721
	 * Cons:
6722
	 * - Loads css for ALL modules. However all selectors are prefixed so it
6723
	 *		should not cause any issues with themes.
6724
	 * - Plugins/themes dequeuing styles no longer do anything. See
6725
	 *		jetpack_implode_frontend_css filter for a workaround
6726
	 *
6727
	 * For some situations developers may wish to disable css imploding and
6728
	 * instead operate in legacy mode where each file loads seperately and
6729
	 * can be edited individually or dequeued. This can be accomplished with
6730
	 * the following line:
6731
	 *
6732
	 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6733
	 *
6734
	 * @since 3.2
6735
	 **/
6736
	public function implode_frontend_css( $travis_test = false ) {
6737
		$do_implode = true;
6738
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6739
			$do_implode = false;
6740
		}
6741
6742
		/**
6743
		 * Allow CSS to be concatenated into a single jetpack.css file.
6744
		 *
6745
		 * @since 3.2.0
6746
		 *
6747
		 * @param bool $do_implode Should CSS be concatenated? Default to true.
6748
		 */
6749
		$do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6750
6751
		// Do not use the imploded file when default behaviour was altered through the filter
6752
		if ( ! $do_implode ) {
6753
			return;
6754
		}
6755
6756
		// We do not want to use the imploded file in dev mode, or if not connected
6757
		if ( Jetpack::is_development_mode() || ! self::is_active() ) {
6758
			if ( ! $travis_test ) {
6759
				return;
6760
			}
6761
		}
6762
6763
		// Do not use the imploded file if sharing css was dequeued via the sharing settings screen
6764
		if ( get_option( 'sharedaddy_disable_resources' ) ) {
6765
			return;
6766
		}
6767
6768
		/*
6769
		 * Now we assume Jetpack is connected and able to serve the single
6770
		 * file.
6771
		 *
6772
		 * In the future there will be a check here to serve the file locally
6773
		 * or potentially from the Jetpack CDN
6774
		 *
6775
		 * For now:
6776
		 * - Enqueue a single imploded css file
6777
		 * - Zero out the style_loader_tag for the bundled ones
6778
		 * - Be happy, drink scotch
6779
		 */
6780
6781
		add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6782
6783
		$version = Jetpack::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6784
6785
		wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6786
		wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6787
	}
6788
6789
	function concat_remove_style_loader_tag( $tag, $handle ) {
6790
		if ( in_array( $handle, $this->concatenated_style_handles ) ) {
6791
			$tag = '';
6792
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6793
				$tag = "<!-- `" . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6794
			}
6795
		}
6796
6797
		return $tag;
6798
	}
6799
6800
	/*
6801
	 * Check the heartbeat data
6802
	 *
6803
	 * Organizes the heartbeat data by severity.  For example, if the site
6804
	 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
6805
	 *
6806
	 * Data will be added to "caution" array, if it either:
6807
	 *  - Out of date Jetpack version
6808
	 *  - Out of date WP version
6809
	 *  - Out of date PHP version
6810
	 *
6811
	 * $return array $filtered_data
6812
	 */
6813
	public static function jetpack_check_heartbeat_data() {
6814
		$raw_data = Jetpack_Heartbeat::generate_stats_array();
6815
6816
		$good    = array();
6817
		$caution = array();
6818
		$bad     = array();
6819
6820
		foreach ( $raw_data as $stat => $value ) {
6821
6822
			// Check jetpack version
6823
			if ( 'version' == $stat ) {
6824
				if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
6825
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__VERSION;
6826
					continue;
6827
				}
6828
			}
6829
6830
			// Check WP version
6831
			if ( 'wp-version' == $stat ) {
6832
				if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
6833
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__MINIMUM_WP_VERSION;
6834
					continue;
6835
				}
6836
			}
6837
6838
			// Check PHP version
6839
			if ( 'php-version' == $stat ) {
6840
				if ( version_compare( PHP_VERSION, '5.2.4', '<' ) ) {
6841
					$caution[ $stat ] = $value . " - min supported is 5.2.4";
6842
					continue;
6843
				}
6844
			}
6845
6846
			// Check ID crisis
6847
			if ( 'identitycrisis' == $stat ) {
6848
				if ( 'yes' == $value ) {
6849
					$bad[ $stat ] = $value;
6850
					continue;
6851
				}
6852
			}
6853
6854
			// The rest are good :)
6855
			$good[ $stat ] = $value;
6856
		}
6857
6858
		$filtered_data = array(
6859
			'good'    => $good,
6860
			'caution' => $caution,
6861
			'bad'     => $bad
6862
		);
6863
6864
		return $filtered_data;
6865
	}
6866
6867
6868
	/*
6869
	 * This method is used to organize all options that can be reset
6870
	 * without disconnecting Jetpack.
6871
	 *
6872
	 * It is used in class.jetpack-cli.php to reset options
6873
	 *
6874
	 * @since 5.4.0 Logic moved to Jetpack_Options class. Method left in Jetpack class for backwards compat.
6875
	 *
6876
	 * @return array of options to delete.
6877
	 */
6878
	public static function get_jetpack_options_for_reset() {
6879
		return Jetpack_Options::get_options_for_reset();
6880
	}
6881
6882
	/**
6883
	 * Check if an option of a Jetpack module has been updated.
6884
	 *
6885
	 * If any module option has been updated before Jump Start has been dismissed,
6886
	 * update the 'jumpstart' option so we can hide Jump Start.
6887
	 *
6888
	 * @param string $option_name
6889
	 *
6890
	 * @return bool
6891
	 */
6892
	public static function jumpstart_has_updated_module_option( $option_name = '' ) {
6893
		// Bail if Jump Start has already been dismissed
6894
		if ( 'new_connection' !== Jetpack_Options::get_option( 'jumpstart' ) ) {
6895
			return false;
6896
		}
6897
6898
		$jetpack = Jetpack::init();
6899
6900
		// Manual build of module options
6901
		$option_names = self::get_jetpack_options_for_reset();
6902
6903
		if ( in_array( $option_name, $option_names['wp_options'] ) ) {
6904
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
6905
6906
			//Jump start is being dismissed send data to MC Stats
6907
			$jetpack->stat( 'jumpstart', 'manual,'.$option_name );
6908
6909
			$jetpack->do_stats( 'server_side' );
6910
		}
6911
6912
	}
6913
6914
	/*
6915
	 * Strip http:// or https:// from a url, replaces forward slash with ::,
6916
	 * so we can bring them directly to their site in calypso.
6917
	 *
6918
	 * @param string | url
6919
	 * @return string | url without the guff
6920
	 */
6921
	public static function build_raw_urls( $url ) {
6922
		$strip_http = '/.*?:\/\//i';
6923
		$url = preg_replace( $strip_http, '', $url  );
6924
		$url = str_replace( '/', '::', $url );
6925
		return $url;
6926
	}
6927
6928
	/**
6929
	 * Stores and prints out domains to prefetch for page speed optimization.
6930
	 *
6931
	 * @param mixed $new_urls
6932
	 */
6933
	public static function dns_prefetch( $new_urls = null ) {
6934
		static $prefetch_urls = array();
6935
		if ( empty( $new_urls ) && ! empty( $prefetch_urls ) ) {
6936
			echo "\r\n";
6937
			foreach ( $prefetch_urls as $this_prefetch_url ) {
6938
				printf( "<link rel='dns-prefetch' href='%s'/>\r\n", esc_attr( $this_prefetch_url ) );
6939
			}
6940
		} elseif ( ! empty( $new_urls ) ) {
6941
			if ( ! has_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) ) ) {
6942
				add_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) );
6943
			}
6944
			foreach ( (array) $new_urls as $this_new_url ) {
6945
				$prefetch_urls[] = strtolower( untrailingslashit( preg_replace( '#^https?://#i', '//', $this_new_url ) ) );
6946
			}
6947
			$prefetch_urls = array_unique( $prefetch_urls );
6948
		}
6949
	}
6950
6951
	public function wp_dashboard_setup() {
6952
		if ( self::is_active() ) {
6953
			add_action( 'jetpack_dashboard_widget', array( __CLASS__, 'dashboard_widget_footer' ), 999 );
6954
		}
6955
6956
		if ( has_action( 'jetpack_dashboard_widget' ) ) {
6957
			wp_add_dashboard_widget(
6958
				'jetpack_summary_widget',
6959
				esc_html__( 'Site Stats', 'jetpack' ),
6960
				array( __CLASS__, 'dashboard_widget' )
6961
			);
6962
			wp_enqueue_style( 'jetpack-dashboard-widget', plugins_url( 'css/dashboard-widget.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
6963
6964
			// If we're inactive and not in development mode, sort our box to the top.
6965
			if ( ! self::is_active() && ! self::is_development_mode() ) {
6966
				global $wp_meta_boxes;
6967
6968
				$dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
6969
				$ours      = array( 'jetpack_summary_widget' => $dashboard['jetpack_summary_widget'] );
6970
6971
				$wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard );
6972
			}
6973
		}
6974
	}
6975
6976
	/**
6977
	 * @param mixed $result Value for the user's option
6978
	 * @return mixed
6979
	 */
6980
	function get_user_option_meta_box_order_dashboard( $sorted ) {
6981
		if ( ! is_array( $sorted ) ) {
6982
			return $sorted;
6983
		}
6984
6985
		foreach ( $sorted as $box_context => $ids ) {
6986
			if ( false === strpos( $ids, 'dashboard_stats' ) ) {
6987
				// If the old id isn't anywhere in the ids, don't bother exploding and fail out.
6988
				continue;
6989
			}
6990
6991
			$ids_array = explode( ',', $ids );
6992
			$key = array_search( 'dashboard_stats', $ids_array );
6993
6994
			if ( false !== $key ) {
6995
				// If we've found that exact value in the option (and not `google_dashboard_stats` for example)
6996
				$ids_array[ $key ] = 'jetpack_summary_widget';
6997
				$sorted[ $box_context ] = implode( ',', $ids_array );
6998
				// We've found it, stop searching, and just return.
6999
				break;
7000
			}
7001
		}
7002
7003
		return $sorted;
7004
	}
7005
7006
	public static function dashboard_widget() {
7007
		/**
7008
		 * Fires when the dashboard is loaded.
7009
		 *
7010
		 * @since 3.4.0
7011
		 */
7012
		do_action( 'jetpack_dashboard_widget' );
7013
	}
7014
7015
	public static function dashboard_widget_footer() {
7016
		?>
7017
		<footer>
7018
7019
		<div class="protect">
7020
			<?php if ( Jetpack::is_module_active( 'protect' ) ) : ?>
7021
				<h3><?php echo number_format_i18n( get_site_option( 'jetpack_protect_blocked_attempts', 0 ) ); ?></h3>
7022
				<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>
7023
			<?php elseif ( current_user_can( 'jetpack_activate_modules' ) && ! self::is_development_mode() ) : ?>
7024
				<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' ); ?>">
7025
					<?php esc_html_e( 'Activate Protect', 'jetpack' ); ?>
7026
				</a>
7027
			<?php else : ?>
7028
				<?php esc_html_e( 'Protect is inactive.', 'jetpack' ); ?>
7029
			<?php endif; ?>
7030
		</div>
7031
7032
		<div class="akismet">
7033
			<?php if ( is_plugin_active( 'akismet/akismet.php' ) ) : ?>
7034
				<h3><?php echo number_format_i18n( get_option( 'akismet_spam_count', 0 ) ); ?></h3>
7035
				<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>
7036
			<?php elseif ( current_user_can( 'activate_plugins' ) && ! is_wp_error( validate_plugin( 'akismet/akismet.php' ) ) ) : ?>
7037
				<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">
7038
					<?php esc_html_e( 'Activate Akismet', 'jetpack' ); ?>
7039
				</a>
7040
			<?php else : ?>
7041
				<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>
7042
			<?php endif; ?>
7043
		</div>
7044
7045
		</footer>
7046
		<?php
7047
	}
7048
7049
	/**
7050
	 * Return string containing the Jetpack logo.
7051
	 *
7052
	 * @since 3.9.0
7053
	 *
7054
	 * @return string
7055
	 */
7056
	public static function get_jp_emblem() {
7057
		return '<svg id="jetpack-logo__icon" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 32 32"><path fill="#00BE28" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16c8.8,0,16-7.2,16-16S24.8,0,16,0z M15.2,18.7h-8l8-15.5V18.7z M16.8,28.8 V13.3h8L16.8,28.8z"/></svg>';
7058
	}
7059
7060
	/*
7061
	 * Adds a "blank" column in the user admin table to display indication of user connection.
7062
	 */
7063
	function jetpack_icon_user_connected( $columns ) {
7064
		$columns['user_jetpack'] = '';
7065
		return $columns;
7066
	}
7067
7068
	/*
7069
	 * Show Jetpack icon if the user is linked.
7070
	 */
7071
	function jetpack_show_user_connected_icon( $val, $col, $user_id ) {
7072
		if ( 'user_jetpack' == $col && Jetpack::is_user_connected( $user_id ) ) {
7073
			$emblem_html = sprintf(
7074
				'<a title="%1$s" class="jp-emblem-user-admin">%2$s</a>',
7075
				esc_attr__( 'This user is linked and ready to fly with Jetpack.', 'jetpack' ),
7076
				Jetpack::get_jp_emblem()
7077
			);
7078
			return $emblem_html;
7079
		}
7080
7081
		return $val;
7082
	}
7083
7084
	/*
7085
	 * Style the Jetpack user column
7086
	 */
7087
	function jetpack_user_col_style() {
7088
		global $current_screen;
7089
		if ( ! empty( $current_screen->base ) && 'users' == $current_screen->base ) { ?>
7090
			<style>
7091
				.fixed .column-user_jetpack {
7092
					width: 21px;
7093
				}
7094
				.jp-emblem-user-admin svg {
7095
					width: 20px;
7096
					height: 20px;
7097
				}
7098
				.jp-emblem-user-admin path {
7099
					fill: #00BE28;
7100
				}
7101
			</style>
7102
		<?php }
7103
	}
7104
7105
	/**
7106
	 * Checks if Akismet is active and working.
7107
	 *
7108
	 * We dropped support for Akismet 3.0 with Jetpack 6.1.1 while introducing a check for an Akismet valid key
7109
	 * that implied usage of methods present since more recent version.
7110
	 * See https://github.com/Automattic/jetpack/pull/9585
7111
	 *
7112
	 * @since  5.1.0
7113
	 *
7114
	 * @return bool True = Akismet available. False = Aksimet not available.
7115
	 */
7116
	public static function is_akismet_active() {
7117
		if ( method_exists( 'Akismet' , 'http_post' ) ) {
7118
			$akismet_key = Akismet::get_api_key();
7119
			if ( ! $akismet_key ) {
7120
				return false;
7121
			}
7122
			$cached_key_verification = get_transient( 'jetpack_akismet_key_is_valid' );
7123
7124
			// We cache the result of the Akismet key verification for ten minutes.
7125
			if ( in_array( $cached_key_verification, array( 'valid', 'invalid' ) ) ) {
7126
				$akismet_key_state = $cached_key_verification;
7127
			} else {
7128
				$akismet_key_state = Akismet::verify_key( $akismet_key );
7129
				if ( 'failed' === $akismet_key_state ) {
7130
					return false;
7131
				}
7132
				set_transient( 'jetpack_akismet_key_is_valid', $akismet_key_state, 10 * MINUTE_IN_SECONDS );
7133
			}
7134
7135
			return ( 'valid' === $akismet_key_state );
7136
		}
7137
		return false;
7138
	}
7139
7140
	/**
7141
	 * Checks if one or more function names is in debug_backtrace
7142
	 *
7143
	 * @param $names Mixed string name of function or array of string names of functions
7144
	 *
7145
	 * @return bool
7146
	 */
7147
	public static function is_function_in_backtrace( $names ) {
7148
		$backtrace = debug_backtrace( false );
7149
		if ( ! is_array( $names ) ) {
7150
			$names = array( $names );
7151
		}
7152
		$names_as_keys = array_flip( $names );
7153
7154
		//Do check in constant O(1) time for PHP5.5+
7155
		if ( function_exists( 'array_column' ) ) {
7156
			$backtrace_functions = array_column( $backtrace, 'function' );
7157
			$backtrace_functions_as_keys = array_flip( $backtrace_functions );
7158
			$intersection = array_intersect_key( $backtrace_functions_as_keys, $names_as_keys );
7159
			return ! empty ( $intersection );
7160
		}
7161
7162
		//Do check in linear O(n) time for < PHP5.5 ( using isset at least prevents O(n^2) )
7163
		foreach ( $backtrace as $call ) {
7164
			if ( isset( $names_as_keys[ $call['function'] ] ) ) {
7165
				return true;
7166
			}
7167
		}
7168
		return false;
7169
	}
7170
7171
	/**
7172
	 * Given a minified path, and a non-minified path, will return
7173
	 * a minified or non-minified file URL based on whether SCRIPT_DEBUG is set and truthy.
7174
	 *
7175
	 * Both `$min_base` and `$non_min_base` are expected to be relative to the
7176
	 * root Jetpack directory.
7177
	 *
7178
	 * @since 5.6.0
7179
	 *
7180
	 * @param string $min_path
7181
	 * @param string $non_min_path
7182
	 * @return string The URL to the file
7183
	 */
7184
	public static function get_file_url_for_environment( $min_path, $non_min_path ) {
7185
		$path = ( Jetpack_Constants::is_defined( 'SCRIPT_DEBUG' ) && Jetpack_Constants::get_constant( 'SCRIPT_DEBUG' ) )
7186
			? $non_min_path
7187
			: $min_path;
7188
7189
		return plugins_url( $path, JETPACK__PLUGIN_FILE );
7190
	}
7191
7192
	/**
7193
	 * Checks for whether Jetpack Rewind is enabled.
7194
	 * Will return true if the state of Rewind is anything except "unavailable".
7195
	 * @return bool|int|mixed
7196
	 */
7197
	public static function is_rewind_enabled() {
7198
		if ( ! Jetpack::is_active() ) {
7199
			return false;
7200
		}
7201
7202
		$rewind_enabled = get_transient( 'jetpack_rewind_enabled' );
7203
		if ( false === $rewind_enabled ) {
7204
			jetpack_require_lib( 'class.core-rest-api-endpoints' );
7205
			$rewind_data = (array) Jetpack_Core_Json_Api_Endpoints::rewind_data();
7206
			$rewind_enabled = ( ! is_wp_error( $rewind_data )
7207
				&& ! empty( $rewind_data['state'] )
7208
				&& 'active' === $rewind_data['state'] )
7209
				? 1
7210
				: 0;
7211
7212
			set_transient( 'jetpack_rewind_enabled', $rewind_enabled, 10 * MINUTE_IN_SECONDS );
7213
		}
7214
		return $rewind_enabled;
7215
	}
7216
7217
	/**
7218
	 * Checks whether or not TOS has been agreed upon.
7219
	 * Will return true if a user has clicked to register, or is already connected.
7220
	 */
7221
	public static function jetpack_tos_agreed() {
7222
		return Jetpack_Options::get_option( 'tos_agreed' ) || Jetpack::is_active();
7223
	}
7224
7225
	/**
7226
	 * Handles activating default modules as well general cleanup for the new connection.
7227
	 *
7228
	 * @param boolean $activate_sso                 Whether to activate the SSO module when activating default modules.
7229
	 * @param boolean $redirect_on_activation_error Whether to redirect on activation error.
7230
	 * @return void
7231
	 */
7232
	public static function handle_post_authorization_actions( $activate_sso = false, $redirect_on_activation_error = false ) {
7233
		$other_modules = $activate_sso
7234
			? array( 'sso' )
7235
			: array();
7236
7237 View Code Duplication
		if ( $active_modules = Jetpack_Options::get_option( 'active_modules' ) ) {
7238
			Jetpack::delete_active_modules();
7239
7240
			Jetpack::activate_default_modules( 999, 1, array_merge( $active_modules, $other_modules ), $redirect_on_activation_error, false );
7241
		} else {
7242
			Jetpack::activate_default_modules( false, false, $other_modules, $redirect_on_activation_error, false );
7243
		}
7244
7245
		// Since this is a fresh connection, be sure to clear out IDC options
7246
		Jetpack_IDC::clear_all_idc_options();
7247
		Jetpack_Options::delete_raw_option( 'jetpack_last_connect_url_check' );
7248
7249
		// Start nonce cleaner
7250
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
7251
		wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
7252
7253
		Jetpack::state( 'message', 'authorized' );
7254
	}
7255
}
7256