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