Completed
Push — feature/remove-markdown-option ( 2caa5e...68cc5c )
by
unknown
18:13 queued 09:42
created

class.jetpack.php (12 issues)

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
	);
60
61
	public $plugins_to_deactivate = array(
62
		'stats'               => array( 'stats/stats.php', 'WordPress.com Stats' ),
63
		'shortlinks'          => array( 'stats/stats.php', 'WordPress.com Stats' ),
64
		'sharedaddy'          => array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
65
		'twitter-widget'      => array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
66
		'after-the-deadline'  => array( 'after-the-deadline/after-the-deadline.php', 'After The Deadline' ),
67
		'contact-form'        => array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
68
		'contact-form'        => array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
69
		'custom-css'          => array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
70
		'random-redirect'     => array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
71
		'videopress'          => array( 'video/video.php', 'VideoPress' ),
72
		'widget-visibility'   => array( 'jetpack-widget-visibility/widget-visibility.php', 'Jetpack Widget Visibility' ),
73
		'widget-visibility'   => array( 'widget-visibility-without-jetpack/widget-visibility-without-jetpack.php', 'Widget Visibility Without Jetpack' ),
74
		'sharedaddy'          => array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
75
		'omnisearch'          => array( 'jetpack-omnisearch/omnisearch.php', 'Jetpack Omnisearch' ),
76
		'gravatar-hovercards' => array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
77
		'latex'               => array( 'wp-latex/wp-latex.php', 'WP LaTeX' )
78
	);
79
80
	static $capability_translations = array(
81
		'administrator' => 'manage_options',
82
		'editor'        => 'edit_others_posts',
83
		'author'        => 'publish_posts',
84
		'contributor'   => 'edit_posts',
85
		'subscriber'    => 'read',
86
	);
87
88
	/**
89
	 * Map of modules that have conflicts with plugins and should not be auto-activated
90
	 * if the plugins are active.  Used by filter_default_modules
91
	 *
92
	 * Plugin Authors: If you'd like to prevent a single module from auto-activating,
93
	 * change `module-slug` and add this to your plugin:
94
	 *
95
	 * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
96
	 * function my_jetpack_get_default_modules( $modules ) {
97
	 *     return array_diff( $modules, array( 'module-slug' ) );
98
	 * }
99
	 *
100
	 * @var array
101
	 */
102
	private $conflicting_plugins = array(
103
		'comments'          => array(
104
			'Intense Debate'                       => 'intensedebate/intensedebate.php',
105
			'Disqus'                               => 'disqus-comment-system/disqus.php',
106
			'Livefyre'                             => 'livefyre-comments/livefyre.php',
107
			'Comments Evolved for WordPress'       => 'gplus-comments/comments-evolved.php',
108
			'Google+ Comments'                     => 'google-plus-comments/google-plus-comments.php',
109
			'WP-SpamShield Anti-Spam'              => 'wp-spamshield/wp-spamshield.php',
110
		),
111
		'contact-form'      => array(
112
			'Contact Form 7'                       => 'contact-form-7/wp-contact-form-7.php',
113
			'Gravity Forms'                        => 'gravityforms/gravityforms.php',
114
			'Contact Form Plugin'                  => 'contact-form-plugin/contact_form.php',
115
			'Easy Contact Forms'                   => 'easy-contact-forms/easy-contact-forms.php',
116
			'Fast Secure Contact Form'             => 'si-contact-form/si-contact-form.php',
117
		),
118
		'minileven'         => array(
119
			'WPtouch'                              => 'wptouch/wptouch.php',
120
		),
121
		'latex'             => array(
122
			'LaTeX for WordPress'                  => 'latex/latex.php',
123
			'Youngwhans Simple Latex'              => 'youngwhans-simple-latex/yw-latex.php',
124
			'Easy WP LaTeX'                        => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
125
			'MathJax-LaTeX'                        => 'mathjax-latex/mathjax-latex.php',
126
			'Enable Latex'                         => 'enable-latex/enable-latex.php',
127
			'WP QuickLaTeX'                        => 'wp-quicklatex/wp-quicklatex.php',
128
		),
129
		'protect'           => array(
130
			'Limit Login Attempts'                 => 'limit-login-attempts/limit-login-attempts.php',
131
			'Captcha'                              => 'captcha/captcha.php',
132
			'Brute Force Login Protection'         => 'brute-force-login-protection/brute-force-login-protection.php',
133
			'Login Security Solution'              => 'login-security-solution/login-security-solution.php',
134
			'WPSecureOps Brute Force Protect'      => 'wpsecureops-bruteforce-protect/wpsecureops-bruteforce-protect.php',
135
			'BulletProof Security'                 => 'bulletproof-security/bulletproof-security.php',
136
			'SiteGuard WP Plugin'                  => 'siteguard/siteguard.php',
137
			'Security-protection'                  => 'security-protection/security-protection.php',
138
			'Login Security'                       => 'login-security/login-security.php',
139
			'Botnet Attack Blocker'                => 'botnet-attack-blocker/botnet-attack-blocker.php',
140
			'Wordfence Security'                   => 'wordfence/wordfence.php',
141
			'All In One WP Security & Firewall'    => 'all-in-one-wp-security-and-firewall/wp-security.php',
142
			'iThemes Security'                     => 'better-wp-security/better-wp-security.php',
143
		),
144
		'random-redirect'   => array(
145
			'Random Redirect 2'                    => 'random-redirect-2/random-redirect.php',
146
		),
147
		'related-posts'     => array(
148
			'YARPP'                                => 'yet-another-related-posts-plugin/yarpp.php',
149
			'WordPress Related Posts'              => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
150
			'nrelate Related Content'              => 'nrelate-related-content/nrelate-related.php',
151
			'Contextual Related Posts'             => 'contextual-related-posts/contextual-related-posts.php',
152
			'Related Posts for WordPress'          => 'microkids-related-posts/microkids-related-posts.php',
153
			'outbrain'                             => 'outbrain/outbrain.php',
154
			'Shareaholic'                          => 'shareaholic/shareaholic.php',
155
			'Sexybookmarks'                        => 'sexybookmarks/shareaholic.php',
156
		),
157
		'sharedaddy'        => array(
158
			'AddThis'                              => 'addthis/addthis_social_widget.php',
159
			'Add To Any'                           => 'add-to-any/add-to-any.php',
160
			'ShareThis'                            => 'share-this/sharethis.php',
161
			'Shareaholic'                          => 'shareaholic/shareaholic.php',
162
		),
163
		'seo-tools' => array(
164
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
165
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
166
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
167
			'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
168
		),
169
		'verification-tools' => array(
170
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
171
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
172
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
173
			'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
174
		),
175
		'widget-visibility' => array(
176
			'Widget Logic'                         => 'widget-logic/widget_logic.php',
177
			'Dynamic Widgets'                      => 'dynamic-widgets/dynamic-widgets.php',
178
		),
179
		'sitemaps' => array(
180
			'Google XML Sitemaps'                  => 'google-sitemap-generator/sitemap.php',
181
			'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
182
			'Google XML Sitemaps for qTranslate'   => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php',
183
			'XML Sitemap & Google News feeds'      => 'xml-sitemap-feed/xml-sitemap.php',
184
			'Google Sitemap by BestWebSoft'        => 'google-sitemap-plugin/google-sitemap-plugin.php',
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
			'Sitemap'                              => 'sitemap/sitemap.php',
190
			'Simple Wp Sitemap'                    => 'simple-wp-sitemap/simple-wp-sitemap.php',
191
			'Simple Sitemap'                       => 'simple-sitemap/simple-sitemap.php',
192
			'XML Sitemaps'                         => 'xml-sitemaps/xml-sitemaps.php',
193
			'MSM Sitemaps'                         => 'msm-sitemap/msm-sitemap.php',
194
		),
195
	);
196
197
	/**
198
	 * Plugins for which we turn off our Facebook OG Tags implementation.
199
	 *
200
	 * Note: All in One SEO Pack, All in one SEO Pack Pro, WordPress SEO by Yoast, and WordPress SEO Premium by Yoast automatically deactivate
201
	 * Jetpack's Open Graph tags via filter when their Social Meta modules are active.
202
	 *
203
	 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
204
	 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
205
	 */
206
	private $open_graph_conflicting_plugins = array(
207
		'2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
208
		                                                         // 2 Click Social Media Buttons
209
		'add-link-to-facebook/add-link-to-facebook.php',         // Add Link to Facebook
210
		'add-meta-tags/add-meta-tags.php',                       // Add Meta Tags
211
		'autodescription/autodescription.php',                   // The SEO Framework
212
		'easy-facebook-share-thumbnails/esft.php',               // Easy Facebook Share Thumbnail
213
		'heateor-open-graph-meta-tags/heateor-open-graph-meta-tags.php',
214
		                                                         // Open Graph Meta Tags by Heateor
215
		'facebook/facebook.php',                                 // Facebook (official plugin)
216
		'facebook-awd/AWD_facebook.php',                         // Facebook AWD All in one
217
		'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
218
		                                                         // Facebook Featured Image & OG Meta Tags
219
		'facebook-meta-tags/facebook-metatags.php',              // Facebook Meta Tags
220
		'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
221
		                                                         // Facebook Open Graph Meta Tags for WordPress
222
		'facebook-revised-open-graph-meta-tag/index.php',        // Facebook Revised Open Graph Meta Tag
223
		'facebook-thumb-fixer/_facebook-thumb-fixer.php',        // Facebook Thumb Fixer
224
		'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
225
		                                                         // Fedmich's Facebook Open Graph Meta
226
		'network-publisher/networkpub.php',                      // Network Publisher
227
		'nextgen-facebook/nextgen-facebook.php',                 // NextGEN Facebook OG
228
		'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php',
229
		                                                         // NextScripts SNAP
230
		'og-tags/og-tags.php',                                   // OG Tags
231
		'opengraph/opengraph.php',                               // Open Graph
232
		'open-graph-protocol-framework/open-graph-protocol-framework.php',
233
		                                                         // Open Graph Protocol Framework
234
		'seo-facebook-comments/seofacebook.php',                 // SEO Facebook Comments
235
		'seo-ultimate/seo-ultimate.php',                         // SEO Ultimate
236
		'sexybookmarks/sexy-bookmarks.php',                      // Shareaholic
237
		'shareaholic/sexy-bookmarks.php',                        // Shareaholic
238
		'sharepress/sharepress.php',                             // SharePress
239
		'simple-facebook-connect/sfc.php',                       // Simple Facebook Connect
240
		'social-discussions/social-discussions.php',             // Social Discussions
241
		'social-sharing-toolkit/social_sharing_toolkit.php',     // Social Sharing Toolkit
242
		'socialize/socialize.php',                               // Socialize
243
		'squirrly-seo/squirrly.php',                             // SEO by SQUIRRLY™
244
		'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
245
		                                                         // Tweet, Like, Google +1 and Share
246
		'wordbooker/wordbooker.php',                             // Wordbooker
247
		'wpsso/wpsso.php',                                       // WordPress Social Sharing Optimization
248
		'wp-caregiver/wp-caregiver.php',                         // WP Caregiver
249
		'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
250
		                                                         // WP Facebook Like Send & Open Graph Meta
251
		'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',   // WP Facebook Open Graph protocol
252
		'wp-ogp/wp-ogp.php',                                     // WP-OGP
253
		'zoltonorg-social-plugin/zosp.php',                      // Zolton.org Social Plugin
254
		'wp-fb-share-like-button/wp_fb_share-like_widget.php'    // WP Facebook Like Button
255
	);
256
257
	/**
258
	 * Plugins for which we turn off our Twitter Cards Tags implementation.
259
	 */
260
	private $twitter_cards_conflicting_plugins = array(
261
	//	'twitter/twitter.php',                       // The official one handles this on its own.
262
	//	                                             // https://github.com/twitter/wordpress/blob/master/src/Twitter/WordPress/Cards/Compatibility.php
263
		'eewee-twitter-card/index.php',              // Eewee Twitter Card
264
		'ig-twitter-cards/ig-twitter-cards.php',     // IG:Twitter Cards
265
		'jm-twitter-cards/jm-twitter-cards.php',     // JM Twitter Cards
266
		'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',
267
		                                             // Pure Web Brilliant's Social Graph Twitter Cards Extension
268
		'twitter-cards/twitter-cards.php',           // Twitter Cards
269
		'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta
270
		'wp-twitter-cards/twitter_cards.php',        // WP Twitter Cards
271
	);
272
273
	/**
274
	 * Message to display in admin_notice
275
	 * @var string
276
	 */
277
	public $message = '';
278
279
	/**
280
	 * Error to display in admin_notice
281
	 * @var string
282
	 */
283
	public $error = '';
284
285
	/**
286
	 * Modules that need more privacy description.
287
	 * @var string
288
	 */
289
	public $privacy_checks = '';
290
291
	/**
292
	 * Stats to record once the page loads
293
	 *
294
	 * @var array
295
	 */
296
	public $stats = array();
297
298
	/**
299
	 * Jetpack_Sync object
300
	 */
301
	public $sync;
302
303
	/**
304
	 * Verified data for JSON authorization request
305
	 */
306
	public $json_api_authorization_request = array();
307
308
	/**
309
	 * Holds the singleton instance of this class
310
	 * @since 2.3.3
311
	 * @var Jetpack
312
	 */
313
	static $instance = false;
314
315
	/**
316
	 * Singleton
317
	 * @static
318
	 */
319
	public static function init() {
320
		if ( ! self::$instance ) {
321
			self::$instance = new Jetpack;
322
323
			self::$instance->plugin_upgrade();
324
		}
325
326
		return self::$instance;
327
	}
328
329
	/**
330
	 * Must never be called statically
331
	 */
332
	function plugin_upgrade() {
333
		if ( Jetpack::is_active() ) {
334
			list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
335
			if ( JETPACK__VERSION != $version ) {
336
337
				// check which active modules actually exist and remove others from active_modules list
338
				$unfiltered_modules = Jetpack::get_active_modules();
339
				$modules = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) );
340
				if ( array_diff( $unfiltered_modules, $modules ) ) {
341
					Jetpack::update_active_modules( $modules );
342
				}
343
344
				add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
345
346
				// Upgrade to 4.3.0
347
				if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
348
					Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
349
				}
350
351
				// Make sure Markdown for posts gets turned back on
352
				if ( ! get_option( 'wpcom_publish_posts_with_markdown' ) ) {
353
					update_option( 'wpcom_publish_posts_with_markdown', true );
354
				}
355
356
				if ( did_action( 'wp_loaded' ) ) {
357
					self::upgrade_on_load();
358
				} else {
359
					add_action(
360
						'wp_loaded',
361
						array( __CLASS__, 'upgrade_on_load' )
362
					);
363
				}
364
			}
365
		}
366
	}
367
368
	/**
369
	 * Runs upgrade routines that need to have modules loaded.
370
	 */
371
	static function upgrade_on_load() {
372
373
		// Not attempting any upgrades if jetpack_modules_loaded did not fire.
374
		// This can happen in case Jetpack has been just upgraded and is
375
		// being initialized late during the page load. In this case we wait
376
		// until the next proper admin page load with Jetpack active.
377
		if ( ! did_action( 'jetpack_modules_loaded' ) ) {
378
			return;
379
		}
380
381
		Jetpack::maybe_set_version_option();
382
383
		if ( class_exists( 'Jetpack_Widget_Conditions' ) ) {
384
			Jetpack_Widget_Conditions::migrate_post_type_rules();
385
		}
386
	}
387
388 View Code Duplication
	static function activate_manage( ) {
389
		if ( did_action( 'init' ) || current_filter() == 'init' ) {
390
			self::activate_module( 'manage', false, false );
391
		} else if ( !  has_action( 'init' , array( __CLASS__, 'activate_manage' ) ) ) {
392
			add_action( 'init', array( __CLASS__, 'activate_manage' ) );
393
		}
394
	}
395
396
	/**
397
	 * Markdown is now a default, always-on feature of Jetpack
398
	 * Since: 4.8
399
	 */
400 View Code Duplication
	static function activate_markdown( ) {
401
		if ( did_action( 'init' ) || current_filter() == 'init' ) {
402
			self::activate_module( 'markdown', false, false );
403
		} else if ( !  has_action( 'init' , array( __CLASS__, 'activate_markdown' ) ) ) {
404
			add_action( 'init', array( __CLASS__, 'activate_markdown' ) );
405
		}
406
	}
407
408
	static function update_active_modules( $modules ) {
409
		$current_modules = Jetpack_Options::get_option( 'active_modules', array() );
410
411
		$success = Jetpack_Options::update_option( 'active_modules', array_unique( $modules ) );
412
413
		if ( is_array( $modules ) && is_array( $current_modules ) ) {
414
			$new_active_modules = array_diff( $modules, $current_modules );
415
			foreach( $new_active_modules as $module ) {
416
				/**
417
				 * Fires when a specific module is activated.
418
				 *
419
				 * @since 1.9.0
420
				 *
421
				 * @param string $module Module slug.
422
				 * @param boolean $success whether the module was activated. @since 4.2
423
				 */
424
				do_action( 'jetpack_activate_module', $module, $success );
425
426
				/**
427
				 * Fires when a module is activated.
428
				 * The dynamic part of the filter, $module, is the module slug.
429
				 *
430
				 * @since 1.9.0
431
				 *
432
				 * @param string $module Module slug.
433
				 */
434
				do_action( "jetpack_activate_module_$module", $module );
435
			}
436
437
			$new_deactive_modules = array_diff( $current_modules, $modules );
438
			foreach( $new_deactive_modules as $module ) {
439
				/**
440
				 * Fired after a module has been deactivated.
441
				 *
442
				 * @since 4.2.0
443
				 *
444
				 * @param string $module Module slug.
445
				 * @param boolean $success whether the module was deactivated.
446
				 */
447
				do_action( 'jetpack_deactivate_module', $module, $success );
448
				/**
449
				 * Fires when a module is deactivated.
450
				 * The dynamic part of the filter, $module, is the module slug.
451
				 *
452
				 * @since 1.9.0
453
				 *
454
				 * @param string $module Module slug.
455
				 */
456
				do_action( "jetpack_deactivate_module_$module", $module );
457
			}
458
		}
459
460
		return $success;
461
	}
462
463
	static function delete_active_modules() {
464
		self::update_active_modules( array() );
465
	}
466
467
	/**
468
	 * Constructor.  Initializes WordPress hooks
469
	 */
470
	private function __construct() {
471
		/*
472
		 * Check for and alert any deprecated hooks
473
		 */
474
		add_action( 'init', array( $this, 'deprecated_hooks' ) );
475
476
		/*
477
		 * Load things that should only be in Network Admin.
478
		 *
479
		 * For now blow away everything else until a more full
480
		 * understanding of what is needed at the network level is
481
		 * available
482
		 */
483
		if( is_multisite() ) {
484
			Jetpack_Network::init();
485
		}
486
487
		add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
488
489
		// Unlink user before deleting the user from .com
490
		add_action( 'deleted_user', array( $this, 'unlink_user' ), 10, 1 );
491
		add_action( 'remove_user_from_blog', array( $this, 'unlink_user' ), 10, 1 );
492
493
		if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && isset( $_GET['for'] ) && 'jetpack' == $_GET['for'] ) {
494
			@ini_set( 'display_errors', false ); // Display errors can cause the XML to be not well formed.
495
496
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
497
			$this->xmlrpc_server = new Jetpack_XMLRPC_Server();
498
499
			$this->require_jetpack_authentication();
500
501
			if ( Jetpack::is_active() ) {
502
				// Hack to preserve $HTTP_RAW_POST_DATA
503
				add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
504
505
				$signed = $this->verify_xml_rpc_signature();
506
				if ( $signed && ! is_wp_error( $signed ) ) {
507
					// The actual API methods.
508
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'xmlrpc_methods' ) );
509
				} else {
510
					// The jetpack.authorize method should be available for unauthenticated users on a site with an
511
					// active Jetpack connection, so that additional users can link their account.
512
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'authorize_xmlrpc_methods' ) );
513
				}
514
			} else {
515
				// The bootstrap API methods.
516
				add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'bootstrap_xmlrpc_methods' ) );
517
			}
518
519
			// Now that no one can authenticate, and we're whitelisting all XML-RPC methods, force enable_xmlrpc on.
520
			add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
521
		} elseif (
522
			is_admin() &&
523
			isset( $_POST['action'] ) && (
524
				'jetpack_upload_file' == $_POST['action'] ||
525
				'jetpack_update_file' == $_POST['action']
526
			)
527
		) {
528
			$this->require_jetpack_authentication();
529
			$this->add_remote_request_handlers();
530
		} else {
531
			if ( Jetpack::is_active() ) {
532
				add_action( 'login_form_jetpack_json_api_authorization', array( &$this, 'login_form_json_api_authorization' ) );
533
				add_filter( 'xmlrpc_methods', array( $this, 'public_xmlrpc_methods' ) );
534
			}
535
		}
536
537
		if ( Jetpack::is_active() ) {
538
			Jetpack_Heartbeat::init();
539
		}
540
541
		add_filter( 'determine_current_user', array( $this, 'wp_rest_authenticate' ) );
542
		add_filter( 'rest_authentication_errors', array( $this, 'wp_rest_authentication_errors' ) );
543
544
		add_action( 'jetpack_clean_nonces', array( 'Jetpack', 'clean_nonces' ) );
545
		if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
546
			wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
547
		}
548
549
		add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ) );
550
551
		add_action( 'admin_init', array( $this, 'admin_init' ) );
552
		add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
553
554
		add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
555
556
		add_action( 'wp_dashboard_setup', array( $this, 'wp_dashboard_setup' ) );
557
		// Filter the dashboard meta box order to swap the new one in in place of the old one.
558
		add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) );
559
560
		// returns HTTPS support status
561
		add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) );
562
563
		// If any module option is updated before Jump Start is dismissed, hide Jump Start.
564
		add_action( 'update_option', array( $this, 'jumpstart_has_updated_module_option' ) );
565
566
		// JITM AJAX callback function
567
		add_action( 'wp_ajax_jitm_ajax',  array( $this, 'jetpack_jitm_ajax_callback' ) );
568
569
		// Universal ajax callback for all tracking events triggered via js
570
		add_action( 'wp_ajax_jetpack_tracks', array( $this, 'jetpack_admin_ajax_tracks_callback' ) );
571
572
		add_action( 'wp_loaded', array( $this, 'register_assets' ) );
573
		add_action( 'wp_enqueue_scripts', array( $this, 'devicepx' ) );
574
		add_action( 'customize_controls_enqueue_scripts', array( $this, 'devicepx' ) );
575
		add_action( 'admin_enqueue_scripts', array( $this, 'devicepx' ) );
576
577
		add_action( 'plugins_loaded', array( $this, 'extra_oembed_providers' ), 100 );
578
579
		/**
580
		 * These actions run checks to load additional files.
581
		 * They check for external files or plugins, so they need to run as late as possible.
582
		 */
583
		add_action( 'wp_head', array( $this, 'check_open_graph' ),       1 );
584
		add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ),     999 );
585
		add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
586
587
		add_filter( 'plugins_url',      array( 'Jetpack', 'maybe_min_asset' ),     1, 3 );
588
		add_filter( 'style_loader_tag', array( 'Jetpack', 'maybe_inline_style' ), 10, 2 );
589
590
		add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 4 );
591
592
		add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
593
		add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
594
595
		// A filter to control all just in time messages
596
		add_filter( 'jetpack_just_in_time_msgs', '__return_true' );
597
598
		// Update the Jetpack plan from API on heartbeats
599
		add_action( 'jetpack_heartbeat', array( $this, 'refresh_active_plan_from_wpcom' ) );
600
601
		/**
602
		 * This is the hack to concatinate all css files into one.
603
		 * For description and reasoning see the implode_frontend_css method
604
		 *
605
		 * Super late priority so we catch all the registered styles
606
		 */
607
		if( !is_admin() ) {
608
			add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first
609
			add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`
610
		}
611
	}
612
613
	function jetpack_admin_ajax_tracks_callback() {
614
		// Check for nonce
615
		if ( ! isset( $_REQUEST['tracksNonce'] ) || ! wp_verify_nonce( $_REQUEST['tracksNonce'], 'jp-tracks-ajax-nonce' ) ) {
616
			wp_die( 'Permissions check failed.' );
617
		}
618
619
		if ( ! isset( $_REQUEST['tracksEventName'] ) || ! isset( $_REQUEST['tracksEventType'] )  ) {
620
			wp_die( 'No valid event name or type.' );
621
		}
622
623
		$tracks_data = array();
624
		if ( 'click' === $_REQUEST['tracksEventType'] && isset( $_REQUEST['tracksEventProp'] ) ) {
625
			$tracks_data = array( 'clicked' => $_REQUEST['tracksEventProp'] );
626
		}
627
628
		JetpackTracking::record_user_event( $_REQUEST['tracksEventName'], $tracks_data );
629
		wp_send_json_success();
630
		wp_die();
631
	}
632
633
	/**
634
	 * The callback for the JITM ajax requests.
635
	 */
636
	function jetpack_jitm_ajax_callback() {
637
		// Check for nonce
638
		if ( ! isset( $_REQUEST['jitmNonce'] ) || ! wp_verify_nonce( $_REQUEST['jitmNonce'], 'jetpack-jitm-nonce' ) ) {
639
			wp_die( 'Module activation failed due to lack of appropriate permissions' );
640
		}
641
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'activate' == $_REQUEST['jitmActionToTake'] ) {
642
			$module_slug = $_REQUEST['jitmModule'];
643
			Jetpack::log( 'activate', $module_slug );
644
			Jetpack::activate_module( $module_slug, false, false );
645
			Jetpack::state( 'message', 'no_message' );
646
647
			//A Jetpack module is being activated through a JITM, track it
648
			$this->stat( 'jitm', $module_slug.'-activated-' . JETPACK__VERSION );
649
			$this->do_stats( 'server_side' );
650
651
			wp_send_json_success();
652
		}
653
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'dismiss' == $_REQUEST['jitmActionToTake'] ) {
654
			// get the hide_jitm options array
655
			$jetpack_hide_jitm = Jetpack_Options::get_option( 'hide_jitm' );
656
			$module_slug = $_REQUEST['jitmModule'];
657
658
			if( ! $jetpack_hide_jitm ) {
659
				$jetpack_hide_jitm = array(
660
					$module_slug => 'hide'
661
				);
662
			} else {
663
				$jetpack_hide_jitm[$module_slug] = 'hide';
664
			}
665
666
			Jetpack_Options::update_option( 'hide_jitm', $jetpack_hide_jitm );
667
668
			//jitm is being dismissed forever, track it
669
			$this->stat( 'jitm', $module_slug.'-dismissed-' . JETPACK__VERSION );
670
			$this->do_stats( 'server_side' );
671
672
			wp_send_json_success();
673
		}
674 View Code Duplication
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'launch' == $_REQUEST['jitmActionToTake'] ) {
675
			$module_slug = $_REQUEST['jitmModule'];
676
677
			// User went to WordPress.com, track this
678
			$this->stat( 'jitm', $module_slug.'-wordpress-tools-' . JETPACK__VERSION );
679
			$this->do_stats( 'server_side' );
680
681
			wp_send_json_success();
682
		}
683 View Code Duplication
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'viewed' == $_REQUEST['jitmActionToTake'] ) {
684
			$track = $_REQUEST['jitmModule'];
685
686
			// User is viewing JITM, track it.
687
			$this->stat( 'jitm', $track . '-viewed-' . JETPACK__VERSION );
688
			$this->do_stats( 'server_side' );
689
690
			wp_send_json_success();
691
		}
692
	}
693
694
	/**
695
	 * If there are any stats that need to be pushed, but haven't been, push them now.
696
	 */
697
	function __destruct() {
698
		if ( ! empty( $this->stats ) ) {
699
			$this->do_stats( 'server_side' );
700
		}
701
	}
702
703
	function jetpack_custom_caps( $caps, $cap, $user_id, $args ) {
704
		switch( $cap ) {
705
			case 'jetpack_connect' :
706
			case 'jetpack_reconnect' :
707
				if ( Jetpack::is_development_mode() ) {
708
					$caps = array( 'do_not_allow' );
709
					break;
710
				}
711
				/**
712
				 * Pass through. If it's not development mode, these should match disconnect.
713
				 * Let users disconnect if it's development mode, just in case things glitch.
714
				 */
715
			case 'jetpack_disconnect' :
716
				/**
717
				 * In multisite, can individual site admins manage their own connection?
718
				 *
719
				 * Ideally, this should be extracted out to a separate filter in the Jetpack_Network class.
720
				 */
721
				if ( is_multisite() && ! is_super_admin() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
722
					if ( ! Jetpack_Network::init()->get_option( 'sub-site-connection-override' ) ) {
723
						/**
724
						 * We need to update the option name -- it's terribly unclear which
725
						 * direction the override goes.
726
						 *
727
						 * @todo: Update the option name to `sub-sites-can-manage-own-connections`
728
						 */
729
						$caps = array( 'do_not_allow' );
730
						break;
731
					}
732
				}
733
734
				$caps = array( 'manage_options' );
735
				break;
736
			case 'jetpack_manage_modules' :
737
			case 'jetpack_activate_modules' :
738
			case 'jetpack_deactivate_modules' :
739
				$caps = array( 'manage_options' );
740
				break;
741
			case 'jetpack_configure_modules' :
742
				$caps = array( 'manage_options' );
743
				break;
744
			case 'jetpack_network_admin_page':
745
			case 'jetpack_network_settings_page':
746
				$caps = array( 'manage_network_plugins' );
747
				break;
748
			case 'jetpack_network_sites_page':
749
				$caps = array( 'manage_sites' );
750
				break;
751
			case 'jetpack_admin_page' :
752
				if ( Jetpack::is_development_mode() ) {
753
					$caps = array( 'manage_options' );
754
					break;
755
				} else {
756
					$caps = array( 'read' );
757
				}
758
				break;
759
			case 'jetpack_connect_user' :
760
				if ( Jetpack::is_development_mode() ) {
761
					$caps = array( 'do_not_allow' );
762
					break;
763
				}
764
				$caps = array( 'read' );
765
				break;
766
		}
767
		return $caps;
768
	}
769
770
	function require_jetpack_authentication() {
771
		// Don't let anyone authenticate
772
		$_COOKIE = array();
773
		remove_all_filters( 'authenticate' );
774
		remove_all_actions( 'wp_login_failed' );
775
776
		if ( Jetpack::is_active() ) {
777
			// Allow Jetpack authentication
778
			add_filter( 'authenticate', array( $this, 'authenticate_jetpack' ), 10, 3 );
779
		}
780
	}
781
782
	/**
783
	 * Load language files
784
	 * @action plugins_loaded
785
	 */
786
	public static function plugin_textdomain() {
787
		// Note to self, the third argument must not be hardcoded, to account for relocated folders.
788
		load_plugin_textdomain( 'jetpack', false, dirname( plugin_basename( JETPACK__PLUGIN_FILE ) ) . '/languages/' );
789
	}
790
791
	/**
792
	 * Register assets for use in various modules and the Jetpack admin page.
793
	 *
794
	 * @uses wp_script_is, wp_register_script, plugins_url
795
	 * @action wp_loaded
796
	 * @return null
797
	 */
798
	public function register_assets() {
799
		if ( ! wp_script_is( 'spin', 'registered' ) ) {
800
			wp_register_script( 'spin', plugins_url( '_inc/spin.js', JETPACK__PLUGIN_FILE ), false, '1.3' );
801
		}
802
803 View Code Duplication
		if ( ! wp_script_is( 'jquery.spin', 'registered' ) ) {
804
			wp_register_script( 'jquery.spin', plugins_url( '_inc/jquery.spin.js', JETPACK__PLUGIN_FILE ) , array( 'jquery', 'spin' ), '1.3' );
805
		}
806
807 View Code Duplication
		if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) ) {
808
			wp_register_script( 'jetpack-gallery-settings', plugins_url( '_inc/gallery-settings.js', JETPACK__PLUGIN_FILE ), array( 'media-views' ), '20121225' );
809
		}
810
811 View Code Duplication
		if ( ! wp_script_is( 'jetpack-twitter-timeline', 'registered' ) ) {
812
			wp_register_script( 'jetpack-twitter-timeline', plugins_url( '_inc/twitter-timeline.js', JETPACK__PLUGIN_FILE ) , array( 'jquery' ), '4.0.0', true );
813
		}
814
815
		if ( ! wp_script_is( 'jetpack-facebook-embed', 'registered' ) ) {
816
			wp_register_script( 'jetpack-facebook-embed', plugins_url( '_inc/facebook-embed.js', __FILE__ ), array( 'jquery' ), null, true );
817
818
			/** This filter is documented in modules/sharedaddy/sharing-sources.php */
819
			$fb_app_id = apply_filters( 'jetpack_sharing_facebook_app_id', '249643311490' );
820
			if ( ! is_numeric( $fb_app_id ) ) {
821
				$fb_app_id = '';
822
			}
823
			wp_localize_script(
824
				'jetpack-facebook-embed',
825
				'jpfbembed',
826
				array(
827
					'appid' => $fb_app_id,
828
					'locale' => $this->get_locale(),
829
				)
830
			);
831
		}
832
833
		/**
834
		 * As jetpack_register_genericons is by default fired off a hook,
835
		 * the hook may have already fired by this point.
836
		 * So, let's just trigger it manually.
837
		 */
838
		require_once( JETPACK__PLUGIN_DIR . '_inc/genericons.php' );
839
		jetpack_register_genericons();
840
841
		/**
842
		 * Register the social logos
843
		 */
844
		require_once( JETPACK__PLUGIN_DIR . '_inc/social-logos.php' );
845
		jetpack_register_social_logos();
846
847 View Code Duplication
		if ( ! wp_style_is( 'jetpack-icons', 'registered' ) )
848
			wp_register_style( 'jetpack-icons', plugins_url( 'css/jetpack-icons.min.css', JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
849
	}
850
851
	/**
852
	 * Guess locale from language code.
853
	 *
854
	 * @param string $lang Language code.
855
	 * @return string|bool
856
	 */
857 View Code Duplication
	function guess_locale_from_lang( $lang ) {
858
		if ( 'en' === $lang || 'en_US' === $lang || ! $lang ) {
859
			return 'en_US';
860
		}
861
862
		if ( ! class_exists( 'GP_Locales' ) ) {
863
			if ( ! defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || ! file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
864
				return false;
865
			}
866
867
			require JETPACK__GLOTPRESS_LOCALES_PATH;
868
		}
869
870
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
871
			// WP.com: get_locale() returns 'it'
872
			$locale = GP_Locales::by_slug( $lang );
873
		} else {
874
			// Jetpack: get_locale() returns 'it_IT';
875
			$locale = GP_Locales::by_field( 'facebook_locale', $lang );
876
		}
877
878
		if ( ! $locale ) {
879
			return false;
880
		}
881
882
		if ( empty( $locale->facebook_locale ) ) {
883
			if ( empty( $locale->wp_locale ) ) {
884
				return false;
885
			} else {
886
				// Facebook SDK is smart enough to fall back to en_US if a
887
				// locale isn't supported. Since supported Facebook locales
888
				// can fall out of sync, we'll attempt to use the known
889
				// wp_locale value and rely on said fallback.
890
				return $locale->wp_locale;
891
			}
892
		}
893
894
		return $locale->facebook_locale;
895
	}
896
897
	/**
898
	 * Get the locale.
899
	 *
900
	 * @return string|bool
901
	 */
902
	function get_locale() {
903
		$locale = $this->guess_locale_from_lang( get_locale() );
904
905
		if ( ! $locale ) {
906
			$locale = 'en_US';
907
		}
908
909
		return $locale;
910
	}
911
912
	/**
913
	 * Device Pixels support
914
	 * This improves the resolution of gravatars and wordpress.com uploads on hi-res and zoomed browsers.
915
	 */
916
	function devicepx() {
917
		if ( Jetpack::is_active() ) {
918
			wp_enqueue_script( 'devicepx', 'https://s0.wp.com/wp-content/js/devicepx-jetpack.js', array(), gmdate( 'oW' ), true );
919
		}
920
	}
921
922
	/**
923
	 * Return the network_site_url so that .com knows what network this site is a part of.
924
	 * @param  bool $option
925
	 * @return string
926
	 */
927
	public function jetpack_main_network_site_option( $option ) {
928
		return network_site_url();
929
	}
930
	/**
931
	 * Network Name.
932
	 */
933
	static function network_name( $option = null ) {
934
		global $current_site;
935
		return $current_site->site_name;
936
	}
937
	/**
938
	 * Does the network allow new user and site registrations.
939
	 * @return string
940
	 */
941
	static function network_allow_new_registrations( $option = null ) {
942
		return ( in_array( get_site_option( 'registration' ), array('none', 'user', 'blog', 'all' ) ) ? get_site_option( 'registration') : 'none' );
943
	}
944
	/**
945
	 * Does the network allow admins to add new users.
946
	 * @return boolian
947
	 */
948
	static function network_add_new_users( $option = null ) {
949
		return (bool) get_site_option( 'add_new_users' );
950
	}
951
	/**
952
	 * File upload psace left per site in MB.
953
	 *  -1 means NO LIMIT.
954
	 * @return number
955
	 */
956
	static function network_site_upload_space( $option = null ) {
957
		// value in MB
958
		return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
959
	}
960
961
	/**
962
	 * Network allowed file types.
963
	 * @return string
964
	 */
965
	static function network_upload_file_types( $option = null ) {
966
		return get_site_option( 'upload_filetypes', 'jpg jpeg png gif' );
967
	}
968
969
	/**
970
	 * Maximum file upload size set by the network.
971
	 * @return number
972
	 */
973
	static function network_max_upload_file_size( $option = null ) {
974
		// value in KB
975
		return get_site_option( 'fileupload_maxk', 300 );
976
	}
977
978
	/**
979
	 * Lets us know if a site allows admins to manage the network.
980
	 * @return array
981
	 */
982
	static function network_enable_administration_menus( $option = null ) {
983
		return get_site_option( 'menu_items' );
984
	}
985
986
	/**
987
	 * If a user has been promoted to or demoted from admin, we need to clear the
988
	 * jetpack_other_linked_admins transient.
989
	 *
990
	 * @since 4.3.2
991
	 * @since 4.4.0  $old_roles is null by default and if it's not passed, the transient is cleared.
992
	 *
993
	 * @param int    $user_id   The user ID whose role changed.
994
	 * @param string $role      The new role.
995
	 * @param array  $old_roles An array of the user's previous roles.
996
	 */
997
	function maybe_clear_other_linked_admins_transient( $user_id, $role, $old_roles = null ) {
998
		if ( 'administrator' == $role
999
			|| ( is_array( $old_roles ) && in_array( 'administrator', $old_roles ) )
1000
			|| is_null( $old_roles )
1001
		) {
1002
			delete_transient( 'jetpack_other_linked_admins' );
1003
		}
1004
	}
1005
1006
	/**
1007
	 * Checks to see if there are any other users available to become primary
1008
	 * Users must both:
1009
	 * - Be linked to wpcom
1010
	 * - Be an admin
1011
	 *
1012
	 * @return mixed False if no other users are linked, Int if there are.
1013
	 */
1014
	static function get_other_linked_admins() {
1015
		$other_linked_users = get_transient( 'jetpack_other_linked_admins' );
1016
1017
		if ( false === $other_linked_users ) {
1018
			$admins = get_users( array( 'role' => 'administrator' ) );
1019
			if ( count( $admins ) > 1 ) {
1020
				$available = array();
1021
				foreach ( $admins as $admin ) {
1022
					if ( Jetpack::is_user_connected( $admin->ID ) ) {
1023
						$available[] = $admin->ID;
1024
					}
1025
				}
1026
1027
				$count_connected_admins = count( $available );
1028
				if ( count( $available ) > 1 ) {
1029
					$other_linked_users = $count_connected_admins;
1030
				} else {
1031
					$other_linked_users = 0;
1032
				}
1033
			} else {
1034
				$other_linked_users = 0;
1035
			}
1036
1037
			set_transient( 'jetpack_other_linked_admins', $other_linked_users, HOUR_IN_SECONDS );
1038
		}
1039
1040
		return ( 0 === $other_linked_users ) ? false : $other_linked_users;
1041
	}
1042
1043
	/**
1044
	 * Return whether we are dealing with a multi network setup or not.
1045
	 * The reason we are type casting this is because we want to avoid the situation where
1046
	 * the result is false since when is_main_network_option return false it cases
1047
	 * the rest the get_option( 'jetpack_is_multi_network' ); to return the value that is set in the
1048
	 * database which could be set to anything as opposed to what this function returns.
1049
	 * @param  bool  $option
1050
	 *
1051
	 * @return boolean
1052
	 */
1053
	public function is_main_network_option( $option ) {
1054
		// return '1' or ''
1055
		return (string) (bool) Jetpack::is_multi_network();
1056
	}
1057
1058
	/**
1059
	 * Return true if we are with multi-site or multi-network false if we are dealing with single site.
1060
	 *
1061
	 * @param  string  $option
1062
	 * @return boolean
1063
	 */
1064
	public function is_multisite( $option ) {
1065
		return (string) (bool) is_multisite();
1066
	}
1067
1068
	/**
1069
	 * Implemented since there is no core is multi network function
1070
	 * Right now there is no way to tell if we which network is the dominant network on the system
1071
	 *
1072
	 * @since  3.3
1073
	 * @return boolean
1074
	 */
1075
	public static function is_multi_network() {
1076
		global  $wpdb;
1077
1078
		// if we don't have a multi site setup no need to do any more
1079
		if ( ! is_multisite() ) {
1080
			return false;
1081
		}
1082
1083
		$num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
1084
		if ( $num_sites > 1 ) {
1085
			return true;
1086
		} else {
1087
			return false;
1088
		}
1089
	}
1090
1091
	/**
1092
	 * Trigger an update to the main_network_site when we update the siteurl of a site.
1093
	 * @return null
1094
	 */
1095
	function update_jetpack_main_network_site_option() {
1096
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1097
	}
1098
	/**
1099
	 * Triggered after a user updates the network settings via Network Settings Admin Page
1100
	 *
1101
	 */
1102
	function update_jetpack_network_settings() {
1103
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1104
		// Only sync this info for the main network site.
1105
	}
1106
1107
	/**
1108
	 * Get back if the current site is single user site.
1109
	 *
1110
	 * @return bool
1111
	 */
1112
	public static function is_single_user_site() {
1113
		global $wpdb;
1114
1115 View Code Duplication
		if ( false === ( $some_users = get_transient( 'jetpack_is_single_user' ) ) ) {
1116
			$some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
1117
			set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
1118
		}
1119
		return 1 === (int) $some_users;
1120
	}
1121
1122
	/**
1123
	 * Returns true if the site has file write access false otherwise.
1124
	 * @return string ( '1' | '0' )
1125
	 **/
1126
	public static function file_system_write_access() {
1127
		if ( ! function_exists( 'get_filesystem_method' ) ) {
1128
			require_once( ABSPATH . 'wp-admin/includes/file.php' );
1129
		}
1130
1131
		require_once( ABSPATH . 'wp-admin/includes/template.php' );
1132
1133
		$filesystem_method = get_filesystem_method();
1134
		if ( $filesystem_method === 'direct' ) {
1135
			return 1;
1136
		}
1137
1138
		ob_start();
1139
		$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1140
		ob_end_clean();
1141
		if ( $filesystem_credentials_are_stored ) {
1142
			return 1;
1143
		}
1144
		return 0;
1145
	}
1146
1147
	/**
1148
	 * Finds out if a site is using a version control system.
1149
	 * @return string ( '1' | '0' )
1150
	 **/
1151
	public static function is_version_controlled() {
1152
		_deprecated_function( __METHOD__, 'jetpack-4.2', 'Jetpack_Sync_Functions::is_version_controlled' );
1153
		return (string) (int) Jetpack_Sync_Functions::is_version_controlled();
1154
	}
1155
1156
	/**
1157
	 * Determines whether the current theme supports featured images or not.
1158
	 * @return string ( '1' | '0' )
1159
	 */
1160
	public static function featured_images_enabled() {
1161
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1162
		return current_theme_supports( 'post-thumbnails' ) ? '1' : '0';
1163
	}
1164
1165
	/**
1166
	 * Wrapper for core's get_avatar_url().  This one is deprecated.
1167
	 *
1168
	 * @deprecated 4.7 use get_avatar_url instead.
1169
	 * @param int|string|object $id_or_email A user ID,  email address, or comment object
1170
	 * @param int $size Size of the avatar image
1171
	 * @param string $default URL to a default image to use if no avatar is available
1172
	 * @param bool $force_display Whether to force it to return an avatar even if show_avatars is disabled
1173
	 *
1174
	 * @return array
1175
	 */
1176
	public static function get_avatar_url( $id_or_email, $size = 96, $default = '', $force_display = false ) {
1177
		_deprecated_function( __METHOD__, 'jetpack-4.7', 'get_avatar_url' );
1178
		return get_avatar_url( $id_or_email, array(
1179
			'size' => $size,
1180
			'default' => $default,
1181
			'force_default' => $force_display,
1182
		) );
1183
	}
1184
1185
	/**
1186
	 * jetpack_updates is saved in the following schema:
1187
	 *
1188
	 * array (
1189
	 *      'plugins'                       => (int) Number of plugin updates available.
1190
	 *      'themes'                        => (int) Number of theme updates available.
1191
	 *      'wordpress'                     => (int) Number of WordPress core updates available.
1192
	 *      'translations'                  => (int) Number of translation updates available.
1193
	 *      'total'                         => (int) Total of all available updates.
1194
	 *      'wp_update_version'             => (string) The latest available version of WordPress, only present if a WordPress update is needed.
1195
	 * )
1196
	 * @return array
1197
	 */
1198
	public static function get_updates() {
1199
		$update_data = wp_get_update_data();
1200
1201
		// Stores the individual update counts as well as the total count.
1202
		if ( isset( $update_data['counts'] ) ) {
1203
			$updates = $update_data['counts'];
1204
		}
1205
1206
		// If we need to update WordPress core, let's find the latest version number.
1207 View Code Duplication
		if ( ! empty( $updates['wordpress'] ) ) {
1208
			$cur = get_preferred_from_update_core();
1209
			if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
1210
				$updates['wp_update_version'] = $cur->current;
1211
			}
1212
		}
1213
		return isset( $updates ) ? $updates : array();
1214
	}
1215
1216
	public static function get_update_details() {
1217
		$update_details = array(
1218
			'update_core' => get_site_transient( 'update_core' ),
1219
			'update_plugins' => get_site_transient( 'update_plugins' ),
1220
			'update_themes' => get_site_transient( 'update_themes' ),
1221
		);
1222
		return $update_details;
1223
	}
1224
1225
	public static function refresh_update_data() {
1226
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1227
1228
	}
1229
1230
	public static function refresh_theme_data() {
1231
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1232
	}
1233
1234
	/**
1235
	 * Is Jetpack active?
1236
	 */
1237
	public static function is_active() {
1238
		return (bool) Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1239
	}
1240
1241
	/**
1242
	 * Make an API call to WordPress.com for plan status
1243
	 *
1244
	 * @uses Jetpack_Options::get_option()
1245
	 * @uses Jetpack_Client::wpcom_json_api_request_as_blog()
1246
	 * @uses update_option()
1247
	 *
1248
	 * @access public
1249
	 * @static
1250
	 *
1251
	 * @return bool True if plan is updated, false if no update
1252
	 */
1253
	public static function refresh_active_plan_from_wpcom() {
1254
		// Make the API request
1255
		$request = sprintf( '/sites/%d', Jetpack_Options::get_option( 'id' ) );
1256
		$response = Jetpack_Client::wpcom_json_api_request_as_blog( $request, '1.1' );
1257
1258
		// Bail if there was an error or malformed response
1259
		if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
1260
			return false;
1261
		}
1262
1263
		// Decode the results
1264
		$results = json_decode( $response['body'], true );
1265
1266
		// Bail if there were no results or plan details returned
1267
		if ( ! is_array( $results ) || ! isset( $results['plan'] ) ) {
1268
			return false;
1269
		}
1270
1271
		// Store the option and return true if updated
1272
		return update_option( 'jetpack_active_plan', $results['plan'] );
1273
	}
1274
1275
	/**
1276
	 * Get the plan that this Jetpack site is currently using
1277
	 *
1278
	 * @uses get_option()
1279
	 *
1280
	 * @access public
1281
	 * @static
1282
	 *
1283
	 * @return array Active Jetpack plan details
1284
	 */
1285
	public static function get_active_plan() {
1286
		$plan = get_option( 'jetpack_active_plan' );
1287
1288
		// Set the default options
1289
		if ( ! $plan ) {
1290
			$plan = array(
1291
				'product_slug' => 'jetpack_free',
1292
				'supports'     => array(),
1293
				'class'        => 'free',
1294
			);
1295
		}
1296
1297
		// Define what paid modules are supported by personal plans
1298
		$personal_plans = array(
1299
			'jetpack_personal',
1300
			'jetpack_personal_monthly',
1301
		);
1302
1303
		if ( in_array( $plan['product_slug'], $personal_plans ) ) {
1304
			$plan['supports'] = array(
1305
				'akismet',
1306
			);
1307
			$plan['class'] = 'personal';
1308
		}
1309
1310
		// Define what paid modules are supported by premium plans
1311
		$premium_plans = array(
1312
			'jetpack_premium',
1313
			'jetpack_premium_monthly',
1314
		);
1315
1316 View Code Duplication
		if ( in_array( $plan['product_slug'], $premium_plans ) ) {
1317
			$plan['supports'] = array(
1318
				'videopress',
1319
				'akismet',
1320
				'vaultpress',
1321
				'wordads',
1322
			);
1323
			$plan['class'] = 'premium';
1324
		}
1325
1326
		// Define what paid modules are supported by professional plans
1327
		$business_plans = array(
1328
			'jetpack_business',
1329
			'jetpack_business_monthly',
1330
		);
1331
1332 View Code Duplication
		if ( in_array( $plan['product_slug'], $business_plans ) ) {
1333
			$plan['supports'] = array(
1334
				'videopress',
1335
				'akismet',
1336
				'vaultpress',
1337
				'seo-tools',
1338
				'google-analytics',
1339
				'wordads',
1340
			);
1341
			$plan['class'] = 'business';
1342
		}
1343
1344
		// Make sure we have an array here in the event database data is stale
1345
		if ( ! isset( $plan['supports'] ) ) {
1346
			$plan['supports'] = array();
1347
		}
1348
1349
		return $plan;
1350
	}
1351
1352
	/**
1353
	 * Determine whether the active plan supports a particular feature
1354
	 *
1355
	 * @uses Jetpack::get_active_plan()
1356
	 *
1357
	 * @access public
1358
	 * @static
1359
	 *
1360
	 * @return bool True if plan supports feature, false if not
1361
	 */
1362
	public static function active_plan_supports( $feature ) {
1363
		$plan = Jetpack::get_active_plan();
1364
1365
		if ( in_array( $feature, $plan['supports'] ) ) {
1366
			return true;
1367
		}
1368
1369
		return false;
1370
	}
1371
1372
	/**
1373
	 * Is Jetpack in development (offline) mode?
1374
	 */
1375
	public static function is_development_mode() {
1376
		$development_mode = false;
1377
1378
		if ( defined( 'JETPACK_DEV_DEBUG' ) ) {
1379
			$development_mode = JETPACK_DEV_DEBUG;
1380
		} elseif ( $site_url = site_url() ) {
1381
			$development_mode = false === strpos( $site_url, '.' );
1382
		}
1383
1384
		/**
1385
		 * Filters Jetpack's development mode.
1386
		 *
1387
		 * @see https://jetpack.com/support/development-mode/
1388
		 *
1389
		 * @since 2.2.1
1390
		 *
1391
		 * @param bool $development_mode Is Jetpack's development mode active.
1392
		 */
1393
		return apply_filters( 'jetpack_development_mode', $development_mode );
1394
	}
1395
1396
	/**
1397
	* Get Jetpack development mode notice text and notice class.
1398
	*
1399
	* Mirrors the checks made in Jetpack::is_development_mode
1400
	*
1401
	*/
1402
	public static function show_development_mode_notice() {
1403
		if ( Jetpack::is_development_mode() ) {
1404
			if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
1405
				$notice = sprintf(
1406
					/* translators: %s is a URL */
1407
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the JETPACK_DEV_DEBUG constant being defined in wp-config.php or elsewhere.', 'jetpack' ),
1408
					'https://jetpack.com/support/development-mode/'
1409
				);
1410
			} elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1411
				$notice = sprintf(
1412
					/* translators: %s is a URL */
1413
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via site URL lacking a dot (e.g. http://localhost).', 'jetpack' ),
1414
					'https://jetpack.com/support/development-mode/'
1415
				);
1416
			} else {
1417
				$notice = sprintf(
1418
					/* translators: %s is a URL */
1419
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the jetpack_development_mode filter.', 'jetpack' ),
1420
					'https://jetpack.com/support/development-mode/'
1421
				);
1422
			}
1423
1424
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1425
		}
1426
1427
		// Throw up a notice if using a development version and as for feedback.
1428
		if ( Jetpack::is_development_version() ) {
1429
			/* translators: %s is a URL */
1430
			$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/' );
1431
1432
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1433
		}
1434
		// Throw up a notice if using staging mode
1435
		if ( Jetpack::is_staging_site() ) {
1436
			/* translators: %s is a URL */
1437
			$notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), 'https://jetpack.com/support/staging-sites/' );
1438
1439
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1440
		}
1441
	}
1442
1443
	/**
1444
	 * Whether Jetpack's version maps to a public release, or a development version.
1445
	 */
1446
	public static function is_development_version() {
1447
		/**
1448
		 * Allows filtering whether this is a development version of Jetpack.
1449
		 *
1450
		 * This filter is especially useful for tests.
1451
		 *
1452
		 * @since 4.3.0
1453
		 *
1454
		 * @param bool $development_version Is this a develoment version of Jetpack?
1455
		 */
1456
		return (bool) apply_filters(
1457
			'jetpack_development_version',
1458
			! preg_match( '/^\d+(\.\d+)+$/', Jetpack_Constants::get_constant( 'JETPACK__VERSION' ) )
1459
		);
1460
	}
1461
1462
	/**
1463
	 * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1464
	 */
1465
	public static function is_user_connected( $user_id = false ) {
1466
		$user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
1467
		if ( ! $user_id ) {
1468
			return false;
1469
		}
1470
1471
		return (bool) Jetpack_Data::get_access_token( $user_id );
1472
	}
1473
1474
	/**
1475
	 * Get the wpcom user data of the current|specified connected user.
1476
	 */
1477
	public static function get_connected_user_data( $user_id = null ) {
1478
		if ( ! $user_id ) {
1479
			$user_id = get_current_user_id();
1480
		}
1481
1482
		$transient_key = "jetpack_connected_user_data_$user_id";
1483
1484
		if ( $cached_user_data = get_transient( $transient_key ) ) {
1485
			return $cached_user_data;
1486
		}
1487
1488
		Jetpack::load_xml_rpc_client();
1489
		$xml = new Jetpack_IXR_Client( array(
1490
			'user_id' => $user_id,
1491
		) );
1492
		$xml->query( 'wpcom.getUser' );
1493
		if ( ! $xml->isError() ) {
1494
			$user_data = $xml->getResponse();
1495
			set_transient( $transient_key, $xml->getResponse(), DAY_IN_SECONDS );
1496
			return $user_data;
1497
		}
1498
1499
		return false;
1500
	}
1501
1502
	/**
1503
	 * Get the wpcom email of the current|specified connected user.
1504
	 */
1505 View Code Duplication
	public static function get_connected_user_email( $user_id = null ) {
1506
		if ( ! $user_id ) {
1507
			$user_id = get_current_user_id();
1508
		}
1509
		Jetpack::load_xml_rpc_client();
1510
		$xml = new Jetpack_IXR_Client( array(
1511
			'user_id' => $user_id,
1512
		) );
1513
		$xml->query( 'wpcom.getUserEmail' );
1514
		if ( ! $xml->isError() ) {
1515
			return $xml->getResponse();
1516
		}
1517
		return false;
1518
	}
1519
1520
	/**
1521
	 * Get the wpcom email of the master user.
1522
	 */
1523
	public static function get_master_user_email() {
1524
		$master_user_id = Jetpack_Options::get_option( 'master_user' );
1525
		if ( $master_user_id ) {
1526
			return self::get_connected_user_email( $master_user_id );
1527
		}
1528
		return '';
1529
	}
1530
1531
	function current_user_is_connection_owner() {
1532
		$user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1533
		return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && get_current_user_id() === $user_token->external_user_id;
1534
	}
1535
1536
	/**
1537
	 * Add any extra oEmbed providers that we know about and use on wpcom for feature parity.
1538
	 */
1539
	function extra_oembed_providers() {
1540
		// Cloudup: https://dev.cloudup.com/#oembed
1541
		wp_oembed_add_provider( 'https://cloudup.com/*' , 'https://cloudup.com/oembed' );
1542
		wp_oembed_add_provider( 'https://me.sh/*', 'https://me.sh/oembed?format=json' );
1543
		wp_oembed_add_provider( '#https?://(www\.)?gfycat\.com/.*#i', 'https://api.gfycat.com/v1/oembed', true );
1544
		wp_oembed_add_provider( '#https?://[^.]+\.(wistia\.com|wi\.st)/(medias|embed)/.*#', 'https://fast.wistia.com/oembed', true );
1545
		wp_oembed_add_provider( '#https?://sketchfab\.com/.*#i', 'https://sketchfab.com/oembed', true );
1546
	}
1547
1548
	/**
1549
	 * Synchronize connected user role changes
1550
	 */
1551
	function user_role_change( $user_id ) {
1552
		_deprecated_function( __METHOD__, 'jetpack-4.2', 'Jetpack_Sync_Users::user_role_change()' );
1553
		Jetpack_Sync_Users::user_role_change( $user_id );
1554
	}
1555
1556
	/**
1557
	 * Loads the currently active modules.
1558
	 */
1559
	public static function load_modules() {
1560
		if ( ! self::is_active() && !self::is_development_mode() ) {
1561
			if ( ! is_multisite() || ! get_site_option( 'jetpack_protect_active' ) ) {
1562
				return;
1563
			}
1564
		}
1565
1566
		$version = Jetpack_Options::get_option( 'version' );
1567 View Code Duplication
		if ( ! $version ) {
1568
			$version = $old_version = JETPACK__VERSION . ':' . time();
1569
			/** This action is documented in class.jetpack.php */
1570
			do_action( 'updating_jetpack_version', $version, false );
1571
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1572
		}
1573
		list( $version ) = explode( ':', $version );
1574
1575
		$modules = array_filter( Jetpack::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1576
1577
		$modules_data = array();
1578
1579
		// Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1580
		if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1581
			$updated_modules = array();
1582
			foreach ( $modules as $module ) {
1583
				$modules_data[ $module ] = Jetpack::get_module( $module );
1584
				if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1585
					continue;
1586
				}
1587
1588
				if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1589
					continue;
1590
				}
1591
1592
				$updated_modules[] = $module;
1593
			}
1594
1595
			$modules = array_diff( $modules, $updated_modules );
1596
		}
1597
1598
		$is_development_mode = Jetpack::is_development_mode();
1599
1600
		foreach ( $modules as $index => $module ) {
1601
			// If we're in dev mode, disable modules requiring a connection
1602
			if ( $is_development_mode ) {
1603
				// Prime the pump if we need to
1604
				if ( empty( $modules_data[ $module ] ) ) {
1605
					$modules_data[ $module ] = Jetpack::get_module( $module );
1606
				}
1607
				// If the module requires a connection, but we're in local mode, don't include it.
1608
				if ( $modules_data[ $module ]['requires_connection'] ) {
1609
					continue;
1610
				}
1611
			}
1612
1613
			if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1614
				continue;
1615
			}
1616
1617
			if ( ! @include( Jetpack::get_module_path( $module ) ) ) {
1618
				unset( $modules[ $index ] );
1619
				self::update_active_modules( array_values( $modules ) );
1620
				continue;
1621
			}
1622
1623
			/**
1624
			 * Fires when a specific module is loaded.
1625
			 * The dynamic part of the hook, $module, is the module slug.
1626
			 *
1627
			 * @since 1.1.0
1628
			 */
1629
			do_action( 'jetpack_module_loaded_' . $module );
1630
		}
1631
1632
		/**
1633
		 * Fires when all the modules are loaded.
1634
		 *
1635
		 * @since 1.1.0
1636
		 */
1637
		do_action( 'jetpack_modules_loaded' );
1638
1639
		// 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.
1640
		if ( Jetpack::is_active() || Jetpack::is_development_mode() )
1641
			require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' );
1642
	}
1643
1644
	/**
1645
	 * Check if Jetpack's REST API compat file should be included
1646
	 * @action plugins_loaded
1647
	 * @return null
1648
	 */
1649
	public function check_rest_api_compat() {
1650
		/**
1651
		 * Filters the list of REST API compat files to be included.
1652
		 *
1653
		 * @since 2.2.5
1654
		 *
1655
		 * @param array $args Array of REST API compat files to include.
1656
		 */
1657
		$_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1658
1659
		if ( function_exists( 'bbpress' ) )
1660
			$_jetpack_rest_api_compat_includes[] = JETPACK__PLUGIN_DIR . 'class.jetpack-bbpress-json-api-compat.php';
1661
1662
		foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include )
1663
			require_once $_jetpack_rest_api_compat_include;
1664
	}
1665
1666
	/**
1667
	 * Gets all plugins currently active in values, regardless of whether they're
1668
	 * traditionally activated or network activated.
1669
	 *
1670
	 * @todo Store the result in core's object cache maybe?
1671
	 */
1672
	public static function get_active_plugins() {
1673
		$active_plugins = (array) get_option( 'active_plugins', array() );
1674
1675
		if ( is_multisite() ) {
1676
			// Due to legacy code, active_sitewide_plugins stores them in the keys,
1677
			// whereas active_plugins stores them in the values.
1678
			$network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1679
			if ( $network_plugins ) {
1680
				$active_plugins = array_merge( $active_plugins, $network_plugins );
1681
			}
1682
		}
1683
1684
		sort( $active_plugins );
1685
1686
		return array_unique( $active_plugins );
1687
	}
1688
1689
	/**
1690
	 * Gets and parses additional plugin data to send with the heartbeat data
1691
	 *
1692
	 * @since 3.8.1
1693
	 *
1694
	 * @return array Array of plugin data
1695
	 */
1696
	public static function get_parsed_plugin_data() {
1697
		if ( ! function_exists( 'get_plugins' ) ) {
1698
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
1699
		}
1700
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
1701
		$all_plugins    = apply_filters( 'all_plugins', get_plugins() );
1702
		$active_plugins = Jetpack::get_active_plugins();
1703
1704
		$plugins = array();
1705
		foreach ( $all_plugins as $path => $plugin_data ) {
1706
			$plugins[ $path ] = array(
1707
					'is_active' => in_array( $path, $active_plugins ),
1708
					'file'      => $path,
1709
					'name'      => $plugin_data['Name'],
1710
					'version'   => $plugin_data['Version'],
1711
					'author'    => $plugin_data['Author'],
1712
			);
1713
		}
1714
1715
		return $plugins;
1716
	}
1717
1718
	/**
1719
	 * Gets and parses theme data to send with the heartbeat data
1720
	 *
1721
	 * @since 3.8.1
1722
	 *
1723
	 * @return array Array of theme data
1724
	 */
1725
	public static function get_parsed_theme_data() {
1726
		$all_themes = wp_get_themes( array( 'allowed' => true ) );
1727
		$header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
1728
1729
		$themes = array();
1730
		foreach ( $all_themes as $slug => $theme_data ) {
1731
			$theme_headers = array();
1732
			foreach ( $header_keys as $header_key ) {
1733
				$theme_headers[ $header_key ] = $theme_data->get( $header_key );
1734
			}
1735
1736
			$themes[ $slug ] = array(
1737
					'is_active_theme' => $slug == wp_get_theme()->get_template(),
1738
					'slug' => $slug,
1739
					'theme_root' => $theme_data->get_theme_root_uri(),
1740
					'parent' => $theme_data->parent(),
1741
					'headers' => $theme_headers
1742
			);
1743
		}
1744
1745
		return $themes;
1746
	}
1747
1748
	/**
1749
	 * Checks whether a specific plugin is active.
1750
	 *
1751
	 * We don't want to store these in a static variable, in case
1752
	 * there are switch_to_blog() calls involved.
1753
	 */
1754
	public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
1755
		return in_array( $plugin, self::get_active_plugins() );
1756
	}
1757
1758
	/**
1759
	 * Check if Jetpack's Open Graph tags should be used.
1760
	 * If certain plugins are active, Jetpack's og tags are suppressed.
1761
	 *
1762
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1763
	 * @action plugins_loaded
1764
	 * @return null
1765
	 */
1766
	public function check_open_graph() {
1767
		if ( in_array( 'publicize', Jetpack::get_active_modules() ) || in_array( 'sharedaddy', Jetpack::get_active_modules() ) ) {
1768
			add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
1769
		}
1770
1771
		$active_plugins = self::get_active_plugins();
1772
1773
		if ( ! empty( $active_plugins ) ) {
1774
			foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
1775
				if ( in_array( $plugin, $active_plugins ) ) {
1776
					add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
1777
					break;
1778
				}
1779
			}
1780
		}
1781
1782
		/**
1783
		 * Allow the addition of Open Graph Meta Tags to all pages.
1784
		 *
1785
		 * @since 2.0.3
1786
		 *
1787
		 * @param bool false Should Open Graph Meta tags be added. Default to false.
1788
		 */
1789
		if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
1790
			require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
1791
		}
1792
	}
1793
1794
	/**
1795
	 * Check if Jetpack's Twitter tags should be used.
1796
	 * If certain plugins are active, Jetpack's twitter tags are suppressed.
1797
	 *
1798
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1799
	 * @action plugins_loaded
1800
	 * @return null
1801
	 */
1802
	public function check_twitter_tags() {
1803
1804
		$active_plugins = self::get_active_plugins();
1805
1806
		if ( ! empty( $active_plugins ) ) {
1807
			foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
1808
				if ( in_array( $plugin, $active_plugins ) ) {
1809
					add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
1810
					break;
1811
				}
1812
			}
1813
		}
1814
1815
		/**
1816
		 * Allow Twitter Card Meta tags to be disabled.
1817
		 *
1818
		 * @since 2.6.0
1819
		 *
1820
		 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
1821
		 */
1822
		if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
1823
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
1824
		}
1825
	}
1826
1827
	/**
1828
	 * Allows plugins to submit security reports.
1829
 	 *
1830
	 * @param string  $type         Report type (login_form, backup, file_scanning, spam)
1831
	 * @param string  $plugin_file  Plugin __FILE__, so that we can pull plugin data
1832
	 * @param array   $args         See definitions above
1833
	 */
1834
	public static function submit_security_report( $type = '', $plugin_file = '', $args = array() ) {
1835
		_deprecated_function( __FUNCTION__, 'jetpack-4.2', null );
1836
	}
1837
1838
/* Jetpack Options API */
1839
1840
	public static function get_option_names( $type = 'compact' ) {
1841
		return Jetpack_Options::get_option_names( $type );
1842
	}
1843
1844
	/**
1845
	 * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
1846
 	 *
1847
	 * @param string $name    Option name
1848
	 * @param mixed  $default (optional)
1849
	 */
1850
	public static function get_option( $name, $default = false ) {
1851
		return Jetpack_Options::get_option( $name, $default );
1852
	}
1853
1854
	/**
1855
	* Stores two secrets and a timestamp so WordPress.com can make a request back and verify an action
1856
	* Does some extra verification so urls (such as those to public-api, register, etc) can't just be crafted
1857
	* $name must be a registered option name.
1858
	*/
1859
	public static function create_nonce( $name ) {
1860
		$secret = wp_generate_password( 32, false ) . ':' . wp_generate_password( 32, false ) . ':' . ( time() + 600 );
1861
1862
		Jetpack_Options::update_option( $name, $secret );
1863
		@list( $secret_1, $secret_2, $eol ) = explode( ':', Jetpack_Options::get_option( $name ) );
1864
		if ( empty( $secret_1 ) || empty( $secret_2 ) || $eol < time() )
1865
			return new Jetpack_Error( 'missing_secrets' );
1866
1867
		return array(
1868
			'secret_1' => $secret_1,
1869
			'secret_2' => $secret_2,
1870
			'eol'      => $eol,
1871
		);
1872
	}
1873
1874
	/**
1875
	 * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
1876
 	 *
1877
	 * @deprecated 3.4 use Jetpack_Options::update_option() instead.
1878
	 * @param string $name  Option name
1879
	 * @param mixed  $value Option value
1880
	 */
1881
	public static function update_option( $name, $value ) {
1882
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_option()' );
1883
		return Jetpack_Options::update_option( $name, $value );
1884
	}
1885
1886
	/**
1887
	 * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
1888
 	 *
1889
	 * @deprecated 3.4 use Jetpack_Options::update_options() instead.
1890
	 * @param array $array array( option name => option value, ... )
1891
	 */
1892
	public static function update_options( $array ) {
1893
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_options()' );
1894
		return Jetpack_Options::update_options( $array );
1895
	}
1896
1897
	/**
1898
	 * Deletes the given option.  May be passed multiple option names as an array.
1899
	 * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
1900
	 *
1901
	 * @deprecated 3.4 use Jetpack_Options::delete_option() instead.
1902
	 * @param string|array $names
1903
	 */
1904
	public static function delete_option( $names ) {
1905
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::delete_option()' );
1906
		return Jetpack_Options::delete_option( $names );
1907
	}
1908
1909
	/**
1910
	 * Enters a user token into the user_tokens option
1911
	 *
1912
	 * @param int $user_id
1913
	 * @param string $token
1914
	 * return bool
1915
	 */
1916
	public static function update_user_token( $user_id, $token, $is_master_user ) {
1917
		// not designed for concurrent updates
1918
		$user_tokens = Jetpack_Options::get_option( 'user_tokens' );
1919
		if ( ! is_array( $user_tokens ) )
1920
			$user_tokens = array();
1921
		$user_tokens[$user_id] = $token;
1922
		if ( $is_master_user ) {
1923
			$master_user = $user_id;
1924
			$options     = compact( 'user_tokens', 'master_user' );
1925
		} else {
1926
			$options = compact( 'user_tokens' );
1927
		}
1928
		return Jetpack_Options::update_options( $options );
1929
	}
1930
1931
	/**
1932
	 * Returns an array of all PHP files in the specified absolute path.
1933
	 * Equivalent to glob( "$absolute_path/*.php" ).
1934
	 *
1935
	 * @param string $absolute_path The absolute path of the directory to search.
1936
	 * @return array Array of absolute paths to the PHP files.
1937
	 */
1938
	public static function glob_php( $absolute_path ) {
1939
		if ( function_exists( 'glob' ) ) {
1940
			return glob( "$absolute_path/*.php" );
1941
		}
1942
1943
		$absolute_path = untrailingslashit( $absolute_path );
1944
		$files = array();
1945
		if ( ! $dir = @opendir( $absolute_path ) ) {
1946
			return $files;
1947
		}
1948
1949
		while ( false !== $file = readdir( $dir ) ) {
1950
			if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
1951
				continue;
1952
			}
1953
1954
			$file = "$absolute_path/$file";
1955
1956
			if ( ! is_file( $file ) ) {
1957
				continue;
1958
			}
1959
1960
			$files[] = $file;
1961
		}
1962
1963
		closedir( $dir );
1964
1965
		return $files;
1966
	}
1967
1968
	public static function activate_new_modules( $redirect = false ) {
1969
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
1970
			return;
1971
		}
1972
1973
		$jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
1974 View Code Duplication
		if ( ! $jetpack_old_version ) {
1975
			$jetpack_old_version = $version = $old_version = '1.1:' . time();
1976
			/** This action is documented in class.jetpack.php */
1977
			do_action( 'updating_jetpack_version', $version, false );
1978
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1979
		}
1980
1981
		list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
1982
1983
		if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
1984
			return;
1985
		}
1986
1987
		$active_modules     = Jetpack::get_active_modules();
1988
		$reactivate_modules = array();
1989
		foreach ( $active_modules as $active_module ) {
1990
			$module = Jetpack::get_module( $active_module );
1991
			if ( ! isset( $module['changed'] ) ) {
1992
				continue;
1993
			}
1994
1995
			if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
1996
				continue;
1997
			}
1998
1999
			$reactivate_modules[] = $active_module;
2000
			Jetpack::deactivate_module( $active_module );
2001
		}
2002
2003
		$new_version = JETPACK__VERSION . ':' . time();
2004
		/** This action is documented in class.jetpack.php */
2005
		do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
2006
		Jetpack_Options::update_options(
2007
			array(
2008
				'version'     => $new_version,
2009
				'old_version' => $jetpack_old_version,
2010
			)
2011
		);
2012
2013
		Jetpack::state( 'message', 'modules_activated' );
2014
		Jetpack::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules );
2015
2016
		if ( $redirect ) {
2017
			$page = 'jetpack'; // make sure we redirect to either settings or the jetpack page
2018
			if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ) ) ) {
2019
				$page = $_GET['page'];
2020
			}
2021
2022
			wp_safe_redirect( Jetpack::admin_url( 'page=' . $page ) );
2023
			exit;
2024
		}
2025
	}
2026
2027
	/**
2028
	 * List available Jetpack modules. Simply lists .php files in /modules/.
2029
	 * Make sure to tuck away module "library" files in a sub-directory.
2030
	 */
2031
	public static function get_available_modules( $min_version = false, $max_version = false ) {
2032
		static $modules = null;
2033
2034
		if ( ! isset( $modules ) ) {
2035
			$available_modules_option = Jetpack_Options::get_option( 'available_modules', array() );
2036
			// Use the cache if we're on the front-end and it's available...
2037
			if ( ! is_admin() && ! empty( $available_modules_option[ JETPACK__VERSION ] ) ) {
2038
				$modules = $available_modules_option[ JETPACK__VERSION ];
2039
			} else {
2040
				$files = Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
2041
2042
				$modules = array();
2043
2044
				foreach ( $files as $file ) {
2045
					if ( ! $headers = Jetpack::get_module( $file ) ) {
2046
						continue;
2047
					}
2048
2049
					$modules[ Jetpack::get_module_slug( $file ) ] = $headers['introduced'];
2050
				}
2051
2052
				Jetpack_Options::update_option( 'available_modules', array(
2053
					JETPACK__VERSION => $modules,
2054
				) );
2055
			}
2056
		}
2057
2058
		/**
2059
		 * Filters the array of modules available to be activated.
2060
		 *
2061
		 * @since 2.4.0
2062
		 *
2063
		 * @param array $modules Array of available modules.
2064
		 * @param string $min_version Minimum version number required to use modules.
2065
		 * @param string $max_version Maximum version number required to use modules.
2066
		 */
2067
		$mods = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version );
2068
2069
		if ( ! $min_version && ! $max_version ) {
2070
			return array_keys( $mods );
2071
		}
2072
2073
		$r = array();
2074
		foreach ( $mods as $slug => $introduced ) {
2075
			if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
2076
				continue;
2077
			}
2078
2079
			if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
2080
				continue;
2081
			}
2082
2083
			$r[] = $slug;
2084
		}
2085
2086
		return $r;
2087
	}
2088
2089
	/**
2090
	 * Default modules loaded on activation.
2091
	 */
2092
	public static function get_default_modules( $min_version = false, $max_version = false ) {
2093
		$return = array();
2094
2095
		foreach ( Jetpack::get_available_modules( $min_version, $max_version ) as $module ) {
2096
			$module_data = Jetpack::get_module( $module );
2097
2098
			switch ( strtolower( $module_data['auto_activate'] ) ) {
2099
				case 'yes' :
2100
					$return[] = $module;
2101
					break;
2102
				case 'public' :
2103
					if ( Jetpack_Options::get_option( 'public' ) ) {
2104
						$return[] = $module;
2105
					}
2106
					break;
2107
				case 'no' :
2108
				default :
2109
					break;
2110
			}
2111
		}
2112
		/**
2113
		 * Filters the array of default modules.
2114
		 *
2115
		 * @since 2.5.0
2116
		 *
2117
		 * @param array $return Array of default modules.
2118
		 * @param string $min_version Minimum version number required to use modules.
2119
		 * @param string $max_version Maximum version number required to use modules.
2120
		 */
2121
		return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version );
2122
	}
2123
2124
	/**
2125
	 * Checks activated modules during auto-activation to determine
2126
	 * if any of those modules are being deprecated.  If so, close
2127
	 * them out, and add any replacement modules.
2128
	 *
2129
	 * Runs at priority 99 by default.
2130
	 *
2131
	 * This is run late, so that it can still activate a module if
2132
	 * the new module is a replacement for another that the user
2133
	 * currently has active, even if something at the normal priority
2134
	 * would kibosh everything.
2135
	 *
2136
	 * @since 2.6
2137
	 * @uses jetpack_get_default_modules filter
2138
	 * @param array $modules
2139
	 * @return array
2140
	 */
2141
	function handle_deprecated_modules( $modules ) {
2142
		$deprecated_modules = array(
2143
			'debug'            => null,  // Closed out and moved to ./class.jetpack-debugger.php
2144
			'wpcc'             => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2145
			'gplus-authorship' => null,  // Closed out in 3.2 -- Google dropped support.
2146
		);
2147
2148
		// Don't activate SSO if they never completed activating WPCC.
2149
		if ( Jetpack::is_module_active( 'wpcc' ) ) {
2150
			$wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2151
			if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2152
				$deprecated_modules['wpcc'] = null;
2153
			}
2154
		}
2155
2156
		foreach ( $deprecated_modules as $module => $replacement ) {
2157
			if ( Jetpack::is_module_active( $module ) ) {
2158
				self::deactivate_module( $module );
2159
				if ( $replacement ) {
2160
					$modules[] = $replacement;
2161
				}
2162
			}
2163
		}
2164
2165
		return array_unique( $modules );
2166
	}
2167
2168
	/**
2169
	 * Checks activated plugins during auto-activation to determine
2170
	 * if any of those plugins are in the list with a corresponding module
2171
	 * that is not compatible with the plugin. The module will not be allowed
2172
	 * to auto-activate.
2173
	 *
2174
	 * @since 2.6
2175
	 * @uses jetpack_get_default_modules filter
2176
	 * @param array $modules
2177
	 * @return array
2178
	 */
2179
	function filter_default_modules( $modules ) {
2180
2181
		$active_plugins = self::get_active_plugins();
2182
2183
		if ( ! empty( $active_plugins ) ) {
2184
2185
			// For each module we'd like to auto-activate...
2186
			foreach ( $modules as $key => $module ) {
2187
				// If there are potential conflicts for it...
2188
				if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2189
					// For each potential conflict...
2190
					foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
2191
						// If that conflicting plugin is active...
2192
						if ( in_array( $plugin, $active_plugins ) ) {
2193
							// Remove that item from being auto-activated.
2194
							unset( $modules[ $key ] );
2195
						}
2196
					}
2197
				}
2198
			}
2199
		}
2200
2201
		return $modules;
2202
	}
2203
2204
	/**
2205
	 * Extract a module's slug from its full path.
2206
	 */
2207
	public static function get_module_slug( $file ) {
2208
		return str_replace( '.php', '', basename( $file ) );
2209
	}
2210
2211
	/**
2212
	 * Generate a module's path from its slug.
2213
	 */
2214
	public static function get_module_path( $slug ) {
2215
		return JETPACK__PLUGIN_DIR . "modules/$slug.php";
2216
	}
2217
2218
	/**
2219
	 * Load module data from module file. Headers differ from WordPress
2220
	 * plugin headers to avoid them being identified as standalone
2221
	 * plugins on the WordPress plugins page.
2222
	 */
2223
	public static function get_module( $module ) {
2224
		$headers = array(
2225
			'name'                      => 'Module Name',
2226
			'description'               => 'Module Description',
2227
			'jumpstart_desc'            => 'Jumpstart Description',
2228
			'sort'                      => 'Sort Order',
2229
			'recommendation_order'      => 'Recommendation Order',
2230
			'introduced'                => 'First Introduced',
2231
			'changed'                   => 'Major Changes In',
2232
			'deactivate'                => 'Deactivate',
2233
			'free'                      => 'Free',
2234
			'requires_connection'       => 'Requires Connection',
2235
			'auto_activate'             => 'Auto Activate',
2236
			'module_tags'               => 'Module Tags',
2237
			'feature'                   => 'Feature',
2238
			'additional_search_queries' => 'Additional Search Queries',
2239
		);
2240
2241
		$file = Jetpack::get_module_path( Jetpack::get_module_slug( $module ) );
2242
2243
		$mod = Jetpack::get_file_data( $file, $headers );
2244
		if ( empty( $mod['name'] ) ) {
2245
			return false;
2246
		}
2247
2248
		$mod['sort']                    = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
2249
		$mod['recommendation_order']    = empty( $mod['recommendation_order'] ) ? 20 : (int) $mod['recommendation_order'];
2250
		$mod['deactivate']              = empty( $mod['deactivate'] );
2251
		$mod['free']                    = empty( $mod['free'] );
2252
		$mod['requires_connection']     = ( ! empty( $mod['requires_connection'] ) && 'No' == $mod['requires_connection'] ) ? false : true;
2253
2254
		if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ) ) ) {
2255
			$mod['auto_activate'] = 'No';
2256
		} else {
2257
			$mod['auto_activate'] = (string) $mod['auto_activate'];
2258
		}
2259
2260
		if ( $mod['module_tags'] ) {
2261
			$mod['module_tags'] = explode( ',', $mod['module_tags'] );
2262
			$mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
2263
			$mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
2264
		} else {
2265
			$mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
2266
		}
2267
2268
		if ( $mod['feature'] ) {
2269
			$mod['feature'] = explode( ',', $mod['feature'] );
2270
			$mod['feature'] = array_map( 'trim', $mod['feature'] );
2271
		} else {
2272
			$mod['feature'] = array( self::translate_module_tag( 'Other' ) );
2273
		}
2274
2275
		/**
2276
		 * Filters the feature array on a module.
2277
		 *
2278
		 * This filter allows you to control where each module is filtered: Recommended,
2279
		 * Jumpstart, and the default "Other" listing.
2280
		 *
2281
		 * @since 3.5.0
2282
		 *
2283
		 * @param array   $mod['feature'] The areas to feature this module:
2284
		 *     'Jumpstart' adds to the "Jumpstart" option to activate many modules at once.
2285
		 *     'Recommended' shows on the main Jetpack admin screen.
2286
		 *     'Other' should be the default if no other value is in the array.
2287
		 * @param string  $module The slug of the module, e.g. sharedaddy.
2288
		 * @param array   $mod All the currently assembled module data.
2289
		 */
2290
		$mod['feature'] = apply_filters( 'jetpack_module_feature', $mod['feature'], $module, $mod );
2291
2292
		/**
2293
		 * Filter the returned data about a module.
2294
		 *
2295
		 * This filter allows overriding any info about Jetpack modules. It is dangerous,
2296
		 * so please be careful.
2297
		 *
2298
		 * @since 3.6.0
2299
		 *
2300
		 * @param array   $mod    The details of the requested module.
2301
		 * @param string  $module The slug of the module, e.g. sharedaddy
2302
		 * @param string  $file   The path to the module source file.
2303
		 */
2304
		return apply_filters( 'jetpack_get_module', $mod, $module, $file );
2305
	}
2306
2307
	/**
2308
	 * Like core's get_file_data implementation, but caches the result.
2309
	 */
2310
	public static function get_file_data( $file, $headers ) {
2311
		//Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated
2312
		$file_name = basename( $file );
2313
		$file_data_option = Jetpack_Options::get_option( 'file_data', array() );
2314
		$key              = md5( $file_name . serialize( $headers ) );
2315
		$refresh_cache    = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 );
2316
2317
		// If we don't need to refresh the cache, and already have the value, short-circuit!
2318
		if ( ! $refresh_cache && isset( $file_data_option[ JETPACK__VERSION ][ $key ] ) ) {
2319
			return $file_data_option[ JETPACK__VERSION ][ $key ];
2320
		}
2321
2322
		$data = get_file_data( $file, $headers );
2323
2324
		// Strip out any old Jetpack versions that are cluttering the option.
2325
		$file_data_option = array_intersect_key( (array) $file_data_option, array( JETPACK__VERSION => null ) );
2326
		$file_data_option[ JETPACK__VERSION ][ $key ] = $data;
2327
		Jetpack_Options::update_option( 'file_data', $file_data_option );
2328
2329
		return $data;
2330
	}
2331
2332
	/**
2333
	 * Return translated module tag.
2334
	 *
2335
	 * @param string $tag Tag as it appears in each module heading.
2336
	 *
2337
	 * @return mixed
2338
	 */
2339
	public static function translate_module_tag( $tag ) {
2340
		return jetpack_get_module_i18n_tag( $tag );
2341
	}
2342
2343
	/**
2344
	 * Return module name translation. Uses matching string created in modules/module-headings.php.
2345
	 *
2346
	 * @since 3.9.2
2347
	 *
2348
	 * @param array $modules
2349
	 *
2350
	 * @return string|void
2351
	 */
2352
	public static function get_translated_modules( $modules ) {
2353
		foreach ( $modules as $index => $module ) {
2354
			$i18n_module = jetpack_get_module_i18n( $module['module'] );
2355
			if ( isset( $module['name'] ) ) {
2356
				$modules[ $index ]['name'] = $i18n_module['name'];
2357
			}
2358
			if ( isset( $module['description'] ) ) {
2359
				$modules[ $index ]['description'] = $i18n_module['description'];
2360
				$modules[ $index ]['short_description'] = $i18n_module['description'];
2361
			}
2362
		}
2363
		return $modules;
2364
	}
2365
2366
	/**
2367
	 * Get a list of activated modules as an array of module slugs.
2368
	 */
2369
	public static function get_active_modules() {
2370
		$active = Jetpack_Options::get_option( 'active_modules' );
2371
2372
		if ( ! is_array( $active ) ) {
2373
			$active = array();
2374
		}
2375
2376
		if ( class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ) ) {
2377
			$active[] = 'vaultpress';
2378
		} else {
2379
			$active = array_diff( $active, array( 'vaultpress' ) );
2380
		}
2381
2382
		//If protect is active on the main site of a multisite, it should be active on all sites.
2383
		if ( ! in_array( 'protect', $active ) && is_multisite() && get_site_option( 'jetpack_protect_active' ) ) {
2384
			$active[] = 'protect';
2385
		}
2386
2387
		return array_unique( $active );
2388
	}
2389
2390
	/**
2391
	 * Check whether or not a Jetpack module is active.
2392
	 *
2393
	 * @param string $module The slug of a Jetpack module.
2394
	 * @return bool
2395
	 *
2396
	 * @static
2397
	 */
2398
	public static function is_module_active( $module ) {
2399
		return in_array( $module, self::get_active_modules() );
2400
	}
2401
2402
	public static function is_module( $module ) {
2403
		return ! empty( $module ) && ! validate_file( $module, Jetpack::get_available_modules() );
2404
	}
2405
2406
	/**
2407
	 * Catches PHP errors.  Must be used in conjunction with output buffering.
2408
	 *
2409
	 * @param bool $catch True to start catching, False to stop.
2410
	 *
2411
	 * @static
2412
	 */
2413
	public static function catch_errors( $catch ) {
2414
		static $display_errors, $error_reporting;
2415
2416
		if ( $catch ) {
2417
			$display_errors  = @ini_set( 'display_errors', 1 );
2418
			$error_reporting = @error_reporting( E_ALL );
2419
			add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2420
		} else {
2421
			@ini_set( 'display_errors', $display_errors );
2422
			@error_reporting( $error_reporting );
2423
			remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2424
		}
2425
	}
2426
2427
	/**
2428
	 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2429
	 */
2430
	public static function catch_errors_on_shutdown() {
2431
		Jetpack::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2432
	}
2433
2434
	/**
2435
	 * Rewrite any string to make paths easier to read.
2436
	 *
2437
	 * Rewrites ABSPATH (eg `/home/jetpack/wordpress/`) to ABSPATH, and if WP_CONTENT_DIR
2438
	 * is located outside of ABSPATH, rewrites that to WP_CONTENT_DIR.
2439
	 *
2440
	 * @param $string
2441
	 * @return mixed
2442
	 */
2443
	public static function alias_directories( $string ) {
2444
		// ABSPATH has a trailing slash.
2445
		$string = str_replace( ABSPATH, 'ABSPATH/', $string );
2446
		// WP_CONTENT_DIR does not have a trailing slash.
2447
		$string = str_replace( WP_CONTENT_DIR, 'WP_CONTENT_DIR', $string );
2448
2449
		return $string;
2450
	}
2451
2452
	public static function activate_default_modules( $min_version = false, $max_version = false, $other_modules = array(), $redirect = true ) {
2453
		$jetpack = Jetpack::init();
2454
2455
		$modules = Jetpack::get_default_modules( $min_version, $max_version );
2456
		$modules = array_merge( $other_modules, $modules );
2457
2458
		// Look for standalone plugins and disable if active.
2459
2460
		$to_deactivate = array();
2461
		foreach ( $modules as $module ) {
2462
			if ( isset( $jetpack->plugins_to_deactivate[$module] ) ) {
2463
				$to_deactivate[$module] = $jetpack->plugins_to_deactivate[$module];
2464
			}
2465
		}
2466
2467
		$deactivated = array();
2468
		foreach ( $to_deactivate as $module => $deactivate_me ) {
2469
			list( $probable_file, $probable_title ) = $deactivate_me;
2470
			if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2471
				$deactivated[] = $module;
2472
			}
2473
		}
2474
2475
		if ( $deactivated && $redirect ) {
2476
			Jetpack::state( 'deactivated_plugins', join( ',', $deactivated ) );
2477
2478
			$url = add_query_arg(
2479
				array(
2480
					'action'   => 'activate_default_modules',
2481
					'_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2482
				),
2483
				add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), Jetpack::admin_url( 'page=jetpack' ) )
2484
			);
2485
			wp_safe_redirect( $url );
2486
			exit;
2487
		}
2488
2489
		/**
2490
		 * Fires before default modules are activated.
2491
		 *
2492
		 * @since 1.9.0
2493
		 *
2494
		 * @param string $min_version Minimum version number required to use modules.
2495
		 * @param string $max_version Maximum version number required to use modules.
2496
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2497
		 */
2498
		do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
2499
2500
		// Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
2501
		Jetpack::restate();
2502
		Jetpack::catch_errors( true );
2503
2504
		$active = Jetpack::get_active_modules();
2505
2506
		foreach ( $modules as $module ) {
2507
			if ( did_action( "jetpack_module_loaded_$module" ) ) {
2508
				$active[] = $module;
2509
				self::update_active_modules( $active );
2510
				continue;
2511
			}
2512
2513
			if ( in_array( $module, $active ) ) {
2514
				$module_info = Jetpack::get_module( $module );
2515
				if ( ! $module_info['deactivate'] ) {
2516
					$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2517 View Code Duplication
					if ( $active_state = Jetpack::state( $state ) ) {
2518
						$active_state = explode( ',', $active_state );
2519
					} else {
2520
						$active_state = array();
2521
					}
2522
					$active_state[] = $module;
2523
					Jetpack::state( $state, implode( ',', $active_state ) );
2524
				}
2525
				continue;
2526
			}
2527
2528
			$file = Jetpack::get_module_path( $module );
2529
			if ( ! file_exists( $file ) ) {
2530
				continue;
2531
			}
2532
2533
			// we'll override this later if the plugin can be included without fatal error
2534
			if ( $redirect ) {
2535
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2536
			}
2537
			Jetpack::state( 'error', 'module_activation_failed' );
2538
			Jetpack::state( 'module', $module );
2539
			ob_start();
2540
			require $file;
2541
2542
			$active[] = $module;
2543
			$state    = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2544 View Code Duplication
			if ( $active_state = Jetpack::state( $state ) ) {
2545
				$active_state = explode( ',', $active_state );
2546
			} else {
2547
				$active_state = array();
2548
			}
2549
			$active_state[] = $module;
2550
			Jetpack::state( $state, implode( ',', $active_state ) );
2551
			Jetpack::update_active_modules( $active );
2552
2553
			ob_end_clean();
2554
		}
2555
		Jetpack::state( 'error', false );
2556
		Jetpack::state( 'module', false );
2557
		Jetpack::catch_errors( false );
2558
		/**
2559
		 * Fires when default modules are activated.
2560
		 *
2561
		 * @since 1.9.0
2562
		 *
2563
		 * @param string $min_version Minimum version number required to use modules.
2564
		 * @param string $max_version Maximum version number required to use modules.
2565
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2566
		 */
2567
		do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
2568
	}
2569
2570
	public static function activate_module( $module, $exit = true, $redirect = true ) {
2571
		/**
2572
		 * Fires before a module is activated.
2573
		 *
2574
		 * @since 2.6.0
2575
		 *
2576
		 * @param string $module Module slug.
2577
		 * @param bool $exit Should we exit after the module has been activated. Default to true.
2578
		 * @param bool $redirect Should the user be redirected after module activation? Default to true.
2579
		 */
2580
		do_action( 'jetpack_pre_activate_module', $module, $exit, $redirect );
2581
2582
		$jetpack = Jetpack::init();
2583
2584
		if ( ! strlen( $module ) )
2585
			return false;
2586
2587
		if ( ! Jetpack::is_module( $module ) )
2588
			return false;
2589
2590
		// If it's already active, then don't do it again
2591
		$active = Jetpack::get_active_modules();
2592
		foreach ( $active as $act ) {
2593
			if ( $act == $module )
2594
				return true;
2595
		}
2596
2597
		$module_data = Jetpack::get_module( $module );
2598
2599
		if ( ! Jetpack::is_active() ) {
2600
			if ( !Jetpack::is_development_mode() )
2601
				return false;
2602
2603
			// If we're not connected but in development mode, make sure the module doesn't require a connection
2604
			if ( Jetpack::is_development_mode() && $module_data['requires_connection'] )
2605
				return false;
2606
		}
2607
2608
		// Check and see if the old plugin is active
2609
		if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2610
			// Deactivate the old plugin
2611
			if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[ $module ][0], $jetpack->plugins_to_deactivate[ $module ][1] ) ) {
2612
				// If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
2613
				// We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
2614
				Jetpack::state( 'deactivated_plugins', $module );
2615
				wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
2616
				exit;
2617
			}
2618
		}
2619
2620
		// Protect won't work with mis-configured IPs
2621
		if ( 'protect' === $module ) {
2622
			include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
2623
			if ( ! jetpack_protect_get_ip() ) {
2624
				Jetpack::state( 'message', 'protect_misconfigured_ip' );
2625
				return false;
2626
			}
2627
		}
2628
2629
		// Check the file for fatal errors, a la wp-admin/plugins.php::activate
2630
		Jetpack::state( 'module', $module );
2631
		Jetpack::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
2632
2633
		Jetpack::catch_errors( true );
2634
		ob_start();
2635
		require Jetpack::get_module_path( $module );
2636
		/** This action is documented in class.jetpack.php */
2637
		do_action( 'jetpack_activate_module', $module );
2638
		$active[] = $module;
2639
		Jetpack::update_active_modules( $active );
2640
2641
		Jetpack::state( 'error', false ); // the override
2642
		ob_end_clean();
2643
		Jetpack::catch_errors( false );
2644
2645
		// A flag for Jump Start so it's not shown again. Only set if it hasn't been yet.
2646 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2647
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2648
2649
			//Jump start is being dismissed send data to MC Stats
2650
			$jetpack->stat( 'jumpstart', 'manual,'.$module );
2651
2652
			$jetpack->do_stats( 'server_side' );
2653
		}
2654
2655
		if ( $redirect ) {
2656
			wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2657
		}
2658
		if ( $exit ) {
2659
			exit;
2660
		}
2661
		return true;
2662
	}
2663
2664
	function activate_module_actions( $module ) {
2665
		_deprecated_function( __METHOD__, 'jeptack-4.2' );
2666
	}
2667
2668
	public static function deactivate_module( $module ) {
2669
		/**
2670
		 * Fires when a module is deactivated.
2671
		 *
2672
		 * @since 1.9.0
2673
		 *
2674
		 * @param string $module Module slug.
2675
		 */
2676
		do_action( 'jetpack_pre_deactivate_module', $module );
2677
2678
		$jetpack = Jetpack::init();
2679
2680
		$active = Jetpack::get_active_modules();
2681
		$new    = array_filter( array_diff( $active, (array) $module ) );
2682
2683
		// A flag for Jump Start so it's not shown again.
2684 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2685
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2686
2687
			//Jump start is being dismissed send data to MC Stats
2688
			$jetpack->stat( 'jumpstart', 'manual,deactivated-'.$module );
2689
2690
			$jetpack->do_stats( 'server_side' );
2691
		}
2692
2693
		return self::update_active_modules( $new );
2694
	}
2695
2696
	public static function enable_module_configurable( $module ) {
2697
		$module = Jetpack::get_module_slug( $module );
2698
		add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
2699
	}
2700
2701
	public static function module_configuration_url( $module ) {
2702
		$module = Jetpack::get_module_slug( $module );
2703
		return Jetpack::admin_url( array( 'page' => 'jetpack', 'configure' => $module ) );
2704
	}
2705
2706
	public static function module_configuration_load( $module, $method ) {
2707
		$module = Jetpack::get_module_slug( $module );
2708
		add_action( 'jetpack_module_configuration_load_' . $module, $method );
2709
	}
2710
2711
	public static function module_configuration_head( $module, $method ) {
2712
		$module = Jetpack::get_module_slug( $module );
2713
		add_action( 'jetpack_module_configuration_head_' . $module, $method );
2714
	}
2715
2716
	public static function module_configuration_screen( $module, $method ) {
2717
		$module = Jetpack::get_module_slug( $module );
2718
		add_action( 'jetpack_module_configuration_screen_' . $module, $method );
2719
	}
2720
2721
	public static function module_configuration_activation_screen( $module, $method ) {
2722
		$module = Jetpack::get_module_slug( $module );
2723
		add_action( 'display_activate_module_setting_' . $module, $method );
2724
	}
2725
2726
/* Installation */
2727
2728
	public static function bail_on_activation( $message, $deactivate = true ) {
2729
?>
2730
<!doctype html>
2731
<html>
2732
<head>
2733
<meta charset="<?php bloginfo( 'charset' ); ?>">
2734
<style>
2735
* {
2736
	text-align: center;
2737
	margin: 0;
2738
	padding: 0;
2739
	font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
2740
}
2741
p {
2742
	margin-top: 1em;
2743
	font-size: 18px;
2744
}
2745
</style>
2746
<body>
2747
<p><?php echo esc_html( $message ); ?></p>
2748
</body>
2749
</html>
2750
<?php
2751
		if ( $deactivate ) {
2752
			$plugins = get_option( 'active_plugins' );
2753
			$jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
2754
			$update  = false;
2755
			foreach ( $plugins as $i => $plugin ) {
2756
				if ( $plugin === $jetpack ) {
2757
					$plugins[$i] = false;
2758
					$update = true;
2759
				}
2760
			}
2761
2762
			if ( $update ) {
2763
				update_option( 'active_plugins', array_filter( $plugins ) );
2764
			}
2765
		}
2766
		exit;
2767
	}
2768
2769
	/**
2770
	 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
2771
	 * @static
2772
	 */
2773
	public static function plugin_activation( $network_wide ) {
2774
		Jetpack_Options::update_option( 'activated', 1 );
2775
2776
		if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
2777
			Jetpack::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
2778
		}
2779
2780
		if ( $network_wide )
2781
			Jetpack::state( 'network_nag', true );
2782
2783
		Jetpack::plugin_initialize();
2784
	}
2785
	/**
2786
	 * Runs before bumping version numbers up to a new version
2787
	 * @param  (string) $version    Version:timestamp
2788
	 * @param  (string) $old_version Old Version:timestamp or false if not set yet.
2789
	 * @return null              [description]
2790
	 */
2791
	public static function do_version_bump( $version, $old_version ) {
2792
2793
		if ( ! $old_version ) { // For new sites
2794
			// Setting up jetpack manage
2795
			Jetpack::activate_manage();
2796
			Jetpack::activate_markdown();
2797
		}
2798
	}
2799
2800
	/**
2801
	 * Sets the internal version number and activation state.
2802
	 * @static
2803
	 */
2804
	public static function plugin_initialize() {
2805
		if ( ! Jetpack_Options::get_option( 'activated' ) ) {
2806
			Jetpack_Options::update_option( 'activated', 2 );
2807
		}
2808
2809 View Code Duplication
		if ( ! Jetpack_Options::get_option( 'version' ) ) {
2810
			$version = $old_version = JETPACK__VERSION . ':' . time();
2811
			/** This action is documented in class.jetpack.php */
2812
			do_action( 'updating_jetpack_version', $version, false );
2813
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2814
		}
2815
2816
		Jetpack::load_modules();
2817
2818
		Jetpack_Options::delete_option( 'do_activate' );
2819
	}
2820
2821
	/**
2822
	 * Removes all connection options
2823
	 * @static
2824
	 */
2825
	public static function plugin_deactivation( ) {
2826
		require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
2827
		if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
2828
			Jetpack_Network::init()->deactivate();
2829
		} else {
2830
			Jetpack::disconnect( false );
2831
			//Jetpack_Heartbeat::init()->deactivate();
2832
		}
2833
	}
2834
2835
	/**
2836
	 * Disconnects from the Jetpack servers.
2837
	 * Forgets all connection details and tells the Jetpack servers to do the same.
2838
	 * @static
2839
	 */
2840
	public static function disconnect( $update_activated_state = true ) {
2841
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
2842
		Jetpack::clean_nonces( true );
2843
2844
		// If the site is in an IDC because sync is not allowed,
2845
		// let's make sure to not disconnect the production site.
2846
		if ( ! self::validate_sync_error_idc_option() ) {
2847
			JetpackTracking::record_user_event( 'disconnect_site', array() );
2848
			Jetpack::load_xml_rpc_client();
2849
			$xml = new Jetpack_IXR_Client();
2850
			$xml->query( 'jetpack.deregister' );
2851
		}
2852
2853
		Jetpack_Options::delete_option(
2854
			array(
2855
				'register',
2856
				'blog_token',
2857
				'user_token',
2858
				'user_tokens',
2859
				'master_user',
2860
				'time_diff',
2861
				'fallback_no_verify_ssl_certs',
2862
			)
2863
		);
2864
2865
		Jetpack_IDC::clear_all_idc_options();
2866
2867
		if ( $update_activated_state ) {
2868
			Jetpack_Options::update_option( 'activated', 4 );
2869
		}
2870
2871
		if ( $jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' ) ) {
2872
			// Check then record unique disconnection if site has never been disconnected previously
2873
			if ( - 1 == $jetpack_unique_connection['disconnected'] ) {
2874
				$jetpack_unique_connection['disconnected'] = 1;
2875
			} else {
2876
				if ( 0 == $jetpack_unique_connection['disconnected'] ) {
2877
					//track unique disconnect
2878
					$jetpack = Jetpack::init();
2879
2880
					$jetpack->stat( 'connections', 'unique-disconnect' );
2881
					$jetpack->do_stats( 'server_side' );
2882
				}
2883
				// increment number of times disconnected
2884
				$jetpack_unique_connection['disconnected'] += 1;
2885
			}
2886
2887
			Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
2888
		}
2889
2890
		// Delete cached connected user data
2891
		$transient_key = "jetpack_connected_user_data_" . get_current_user_id();
2892
		delete_transient( $transient_key );
2893
2894
		// Delete all the sync related data. Since it could be taking up space.
2895
		require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php';
2896
		Jetpack_Sync_Sender::get_instance()->uninstall();
2897
2898
		// Disable the Heartbeat cron
2899
		Jetpack_Heartbeat::init()->deactivate();
2900
	}
2901
2902
	/**
2903
	 * Unlinks the current user from the linked WordPress.com user
2904
	 */
2905
	public static function unlink_user( $user_id = null ) {
2906
		if ( ! $tokens = Jetpack_Options::get_option( 'user_tokens' ) )
2907
			return false;
2908
2909
		$user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
2910
2911
		if ( Jetpack_Options::get_option( 'master_user' ) == $user_id )
2912
			return false;
2913
2914
		if ( ! isset( $tokens[ $user_id ] ) )
2915
			return false;
2916
2917
		Jetpack::load_xml_rpc_client();
2918
		$xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
2919
		$xml->query( 'jetpack.unlink_user', $user_id );
2920
2921
		unset( $tokens[ $user_id ] );
2922
2923
		Jetpack_Options::update_option( 'user_tokens', $tokens );
2924
2925
		/**
2926
		 * Fires after the current user has been unlinked from WordPress.com.
2927
		 *
2928
		 * @since 4.1.0
2929
		 *
2930
		 * @param int $user_id The current user's ID.
2931
		 */
2932
		do_action( 'jetpack_unlinked_user', $user_id );
2933
2934
		return true;
2935
	}
2936
2937
	/**
2938
	 * Attempts Jetpack registration.  If it fail, a state flag is set: @see ::admin_page_load()
2939
	 */
2940
	public static function try_registration() {
2941
		// Let's get some testing in beta versions and such.
2942
		if ( self::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
2943
			// Before attempting to connect, let's make sure that the domains are viable.
2944
			$domains_to_check = array_unique( array(
2945
				'siteurl' => parse_url( get_site_url(), PHP_URL_HOST ),
2946
				'homeurl' => parse_url( get_home_url(), PHP_URL_HOST ),
2947
			) );
2948
			foreach ( $domains_to_check as $domain ) {
2949
				$result = Jetpack_Data::is_usable_domain( $domain );
2950
				if ( is_wp_error( $result ) ) {
2951
					return $result;
2952
				}
2953
			}
2954
		}
2955
2956
		$result = Jetpack::register();
2957
2958
		// If there was an error with registration and the site was not registered, record this so we can show a message.
2959
		if ( ! $result || is_wp_error( $result ) ) {
2960
			return $result;
2961
		} else {
2962
			return true;
2963
		}
2964
	}
2965
2966
	/**
2967
	 * Tracking an internal event log. Try not to put too much chaff in here.
2968
	 *
2969
	 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
2970
	 */
2971
	public static function log( $code, $data = null ) {
2972
		// only grab the latest 200 entries
2973
		$log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
2974
2975
		// Append our event to the log
2976
		$log_entry = array(
2977
			'time'    => time(),
2978
			'user_id' => get_current_user_id(),
2979
			'blog_id' => Jetpack_Options::get_option( 'id' ),
2980
			'code'    => $code,
2981
		);
2982
		// Don't bother storing it unless we've got some.
2983
		if ( ! is_null( $data ) ) {
2984
			$log_entry['data'] = $data;
2985
		}
2986
		$log[] = $log_entry;
2987
2988
		// Try add_option first, to make sure it's not autoloaded.
2989
		// @todo: Add an add_option method to Jetpack_Options
2990
		if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
2991
			Jetpack_Options::update_option( 'log', $log );
2992
		}
2993
2994
		/**
2995
		 * Fires when Jetpack logs an internal event.
2996
		 *
2997
		 * @since 3.0.0
2998
		 *
2999
		 * @param array $log_entry {
3000
		 *	Array of details about the log entry.
3001
		 *
3002
		 *	@param string time Time of the event.
3003
		 *	@param int user_id ID of the user who trigerred the event.
3004
		 *	@param int blog_id Jetpack Blog ID.
3005
		 *	@param string code Unique name for the event.
3006
		 *	@param string data Data about the event.
3007
		 * }
3008
		 */
3009
		do_action( 'jetpack_log_entry', $log_entry );
3010
	}
3011
3012
	/**
3013
	 * Get the internal event log.
3014
	 *
3015
	 * @param $event (string) - only return the specific log events
3016
	 * @param $num   (int)    - get specific number of latest results, limited to 200
3017
	 *
3018
	 * @return array of log events || WP_Error for invalid params
3019
	 */
3020
	public static function get_log( $event = false, $num = false ) {
3021
		if ( $event && ! is_string( $event ) ) {
3022
			return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
3023
		}
3024
3025
		if ( $num && ! is_numeric( $num ) ) {
3026
			return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
3027
		}
3028
3029
		$entire_log = Jetpack_Options::get_option( 'log', array() );
3030
3031
		// If nothing set - act as it did before, otherwise let's start customizing the output
3032
		if ( ! $num && ! $event ) {
3033
			return $entire_log;
3034
		} else {
3035
			$entire_log = array_reverse( $entire_log );
3036
		}
3037
3038
		$custom_log_output = array();
3039
3040
		if ( $event ) {
3041
			foreach ( $entire_log as $log_event ) {
3042
				if ( $event == $log_event[ 'code' ] ) {
3043
					$custom_log_output[] = $log_event;
3044
				}
3045
			}
3046
		} else {
3047
			$custom_log_output = $entire_log;
3048
		}
3049
3050
		if ( $num ) {
3051
			$custom_log_output = array_slice( $custom_log_output, 0, $num );
3052
		}
3053
3054
		return $custom_log_output;
3055
	}
3056
3057
	/**
3058
	 * Log modification of important settings.
3059
	 */
3060
	public static function log_settings_change( $option, $old_value, $value ) {
3061
		switch( $option ) {
3062
			case 'jetpack_sync_non_public_post_stati':
3063
				self::log( $option, $value );
3064
				break;
3065
		}
3066
	}
3067
3068
	/**
3069
	 * Return stat data for WPCOM sync
3070
	 */
3071
	public static function get_stat_data( $encode = true, $extended = true ) {
3072
		$data = Jetpack_Heartbeat::generate_stats_array();
3073
3074
		if ( $extended ) {
3075
			$additional_data = self::get_additional_stat_data();
3076
			$data = array_merge( $data, $additional_data );
3077
		}
3078
3079
		if ( $encode ) {
3080
			return json_encode( $data );
3081
		}
3082
3083
		return $data;
3084
	}
3085
3086
	/**
3087
	 * Get additional stat data to sync to WPCOM
3088
	 */
3089
	public static function get_additional_stat_data( $prefix = '' ) {
3090
		$return["{$prefix}themes"]         = Jetpack::get_parsed_theme_data();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
3091
		$return["{$prefix}plugins-extra"]  = Jetpack::get_parsed_plugin_data();
3092
		$return["{$prefix}users"]          = (int) Jetpack::get_site_user_count();
3093
		$return["{$prefix}site-count"]     = 0;
3094
3095
		if ( function_exists( 'get_blog_count' ) ) {
3096
			$return["{$prefix}site-count"] = get_blog_count();
3097
		}
3098
		return $return;
3099
	}
3100
3101
	private static function get_site_user_count() {
3102
		global $wpdb;
3103
3104
		if ( function_exists( 'wp_is_large_network' ) ) {
3105
			if ( wp_is_large_network( 'users' ) ) {
3106
				return -1; // Not a real value but should tell us that we are dealing with a large network.
3107
			}
3108
		}
3109 View Code Duplication
		if ( false === ( $user_count = get_transient( 'jetpack_site_user_count' ) ) ) {
3110
			// It wasn't there, so regenerate the data and save the transient
3111
			$user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities'" );
3112
			set_transient( 'jetpack_site_user_count', $user_count, DAY_IN_SECONDS );
3113
		}
3114
		return $user_count;
3115
	}
3116
3117
	/* Admin Pages */
3118
3119
	function admin_init() {
3120
		// If the plugin is not connected, display a connect message.
3121
		if (
3122
			// the plugin was auto-activated and needs its candy
3123
			Jetpack_Options::get_option_and_ensure_autoload( 'do_activate', '0' )
3124
		||
3125
			// the plugin is active, but was never activated.  Probably came from a site-wide network activation
3126
			! Jetpack_Options::get_option( 'activated' )
3127
		) {
3128
			Jetpack::plugin_initialize();
3129
		}
3130
3131
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
3132
			Jetpack_Connection_Banner::init();
3133
		} elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
3134
			// Upgrade: 1.1 -> 1.1.1
3135
			// Check and see if host can verify the Jetpack servers' SSL certificate
3136
			$args = array();
3137
			Jetpack_Client::_wp_remote_request(
3138
				Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'test' ) ),
3139
				$args,
3140
				true
3141
			);
3142
		} else if ( $this->can_display_jetpack_manage_notice() && ! Jetpack_Options::get_option( 'dismissed_manage_banner' ) ) {
3143
			// Show the notice on the Dashboard only for now
3144
			add_action( 'load-index.php', array( $this, 'prepare_manage_jetpack_notice' ) );
3145
		}
3146
3147
		if ( current_user_can( 'manage_options' ) && 'AUTO' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
3148
			add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3149
		}
3150
3151
		add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3152
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3153
		add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3154
3155
		if ( Jetpack::is_active() || Jetpack::is_development_mode() ) {
3156
			// Artificially throw errors in certain whitelisted cases during plugin activation
3157
			add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3158
		}
3159
3160
		// Jetpack Manage Activation Screen from .com
3161
		Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
3162
3163
		// Add custom column in wp-admin/users.php to show whether user is linked.
3164
		add_filter( 'manage_users_columns',       array( $this, 'jetpack_icon_user_connected' ) );
3165
		add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_user_connected_icon' ), 10, 3 );
3166
		add_action( 'admin_print_styles',         array( $this, 'jetpack_user_col_style' ) );
3167
	}
3168
3169
	function admin_body_class( $admin_body_class = '' ) {
3170
		$classes = explode( ' ', trim( $admin_body_class ) );
3171
3172
		$classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
3173
3174
		$admin_body_class = implode( ' ', array_unique( $classes ) );
3175
		return " $admin_body_class ";
3176
	}
3177
3178
	static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3179
		return $admin_body_class . ' jetpack-pagestyles ';
3180
	}
3181
3182
	/**
3183
	 * Call this function if you want the Big Jetpack Manage Notice to show up.
3184
	 *
3185
	 * @return null
3186
	 */
3187
	function prepare_manage_jetpack_notice() {
3188
3189
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3190
		add_action( 'admin_notices', array( $this, 'admin_jetpack_manage_notice' ) );
3191
	}
3192
3193
	function manage_activate_screen() {
3194
		include ( JETPACK__PLUGIN_DIR . 'modules/manage/activate-admin.php' );
3195
	}
3196
	/**
3197
	 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3198
	 * This function artificially throws errors for such cases (whitelisted).
3199
	 *
3200
	 * @param string $plugin The activated plugin.
3201
	 */
3202
	function throw_error_on_activate_plugin( $plugin ) {
3203
		$active_modules = Jetpack::get_active_modules();
3204
3205
		// The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3206
		if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3207
			$throw = false;
3208
3209
			// Try and make sure it really was the stats plugin
3210
			if ( ! class_exists( 'ReflectionFunction' ) ) {
3211
				if ( 'stats.php' == basename( $plugin ) ) {
3212
					$throw = true;
3213
				}
3214
			} else {
3215
				$reflection = new ReflectionFunction( 'stats_get_api_key' );
3216
				if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3217
					$throw = true;
3218
				}
3219
			}
3220
3221
			if ( $throw ) {
3222
				trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3223
			}
3224
		}
3225
	}
3226
3227
	function intercept_plugin_error_scrape_init() {
3228
		add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3229
	}
3230
3231
	function intercept_plugin_error_scrape( $action, $result ) {
3232
		if ( ! $result ) {
3233
			return;
3234
		}
3235
3236
		foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3237
			if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3238
				Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3239
			}
3240
		}
3241
	}
3242
3243
	function add_remote_request_handlers() {
3244
		add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
3245
		add_action( 'wp_ajax_nopriv_jetpack_update_file', array( $this, 'remote_request_handlers' ) );
3246
	}
3247
3248
	function remote_request_handlers() {
3249
		$action = current_filter();
3250
3251
		switch ( current_filter() ) {
3252
		case 'wp_ajax_nopriv_jetpack_upload_file' :
3253
			$response = $this->upload_handler();
3254
			break;
3255
3256
		case 'wp_ajax_nopriv_jetpack_update_file' :
3257
			$response = $this->upload_handler( true );
3258
			break;
3259
		default :
0 ignored issues
show
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
3260
			$response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
3261
			break;
3262
		}
3263
3264
		if ( ! $response ) {
3265
			$response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
3266
		}
3267
3268
		if ( is_wp_error( $response ) ) {
3269
			$status_code       = $response->get_error_data();
3270
			$error             = $response->get_error_code();
3271
			$error_description = $response->get_error_message();
3272
3273
			if ( ! is_int( $status_code ) ) {
3274
				$status_code = 400;
3275
			}
3276
3277
			status_header( $status_code );
3278
			die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3279
		}
3280
3281
		status_header( 200 );
3282
		if ( true === $response ) {
3283
			exit;
3284
		}
3285
3286
		die( json_encode( (object) $response ) );
3287
	}
3288
3289
	/**
3290
	 * Uploads a file gotten from the global $_FILES.
3291
	 * If `$update_media_item` is true and `post_id` is defined
3292
	 * the attachment file of the media item (gotten through of the post_id)
3293
	 * will be updated instead of add a new one.
3294
	 *
3295
	 * @param  boolean $update_media_item - update media attachment
3296
	 * @return array - An array describing the uploadind files process
3297
	 */
3298
	function upload_handler( $update_media_item = false ) {
3299
		if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3300
			return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
3301
		}
3302
3303
		$user = wp_authenticate( '', '' );
3304
		if ( ! $user || is_wp_error( $user ) ) {
3305
			return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
3306
		}
3307
3308
		wp_set_current_user( $user->ID );
3309
3310
		if ( ! current_user_can( 'upload_files' ) ) {
3311
			return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3312
		}
3313
3314
		if ( empty( $_FILES ) ) {
3315
			return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3316
		}
3317
3318
		foreach ( array_keys( $_FILES ) as $files_key ) {
3319
			if ( ! isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
3320
				return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3321
			}
3322
		}
3323
3324
		$media_keys = array_keys( $_FILES['media'] );
3325
3326
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
3327
		if ( ! $token || is_wp_error( $token ) ) {
3328
			return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3329
		}
3330
3331
		$uploaded_files = array();
3332
		$global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3333
		unset( $GLOBALS['post'] );
3334
		foreach ( $_FILES['media']['name'] as $index => $name ) {
3335
			$file = array();
3336
			foreach ( $media_keys as $media_key ) {
3337
				$file[$media_key] = $_FILES['media'][$media_key][$index];
3338
			}
3339
3340
			list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
3341
3342
			$hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3343
			if ( $hmac_provided !== $hmac_file ) {
3344
				$uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
3345
				continue;
3346
			}
3347
3348
			$_FILES['.jetpack.upload.'] = $file;
3349
			$post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
3350
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
3351
				$post_id = 0;
3352
			}
3353
3354
			if ( $update_media_item ) {
3355
				if ( ! isset( $post_id ) || $post_id === 0 ) {
3356
					return new Jetpack_Error( 'invalid_input', 'Media ID must be defined.', 400 );
3357
				}
3358
3359
				$media_array = $_FILES['media'];
3360
3361
				$file_array['name'] = $media_array['name'][0];
3362
				$file_array['type'] = $media_array['type'][0];
3363
				$file_array['tmp_name'] = $media_array['tmp_name'][0];
3364
				$file_array['error'] = $media_array['error'][0];
3365
				$file_array['size'] = $media_array['size'][0];
3366
3367
				$edited_media_item = Jetpack_Media::edit_media_file( $post_id, $file_array );
3368
3369
				if ( is_wp_error( $edited_media_item ) ) {
3370
					return $edited_media_item;
3371
				}
3372
3373
				$response = (object) array(
3374
					'id'   => (string) $post_id,
3375
					'file' => (string) $edited_media_item->post_title,
3376
					'url'  => (string) wp_get_attachment_url( $post_id ),
3377
					'type' => (string) $edited_media_item->post_mime_type,
3378
					'meta' => (array) wp_get_attachment_metadata( $post_id ),
3379
				);
3380
3381
				return (array) array( $response );
3382
			}
3383
3384
			$attachment_id = media_handle_upload(
3385
				'.jetpack.upload.',
3386
				$post_id,
3387
				array(),
3388
				array(
3389
					'action' => 'jetpack_upload_file',
3390
				)
3391
			);
3392
3393
			if ( ! $attachment_id ) {
3394
				$uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
3395
			} elseif ( is_wp_error( $attachment_id ) ) {
3396
				$uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
3397
			} else {
3398
				$attachment = get_post( $attachment_id );
3399
				$uploaded_files[$index] = (object) array(
3400
					'id'   => (string) $attachment_id,
3401
					'file' => $attachment->post_title,
3402
					'url'  => wp_get_attachment_url( $attachment_id ),
3403
					'type' => $attachment->post_mime_type,
3404
					'meta' => wp_get_attachment_metadata( $attachment_id ),
3405
				);
3406
				// Zip files uploads are not supported unless they are done for installation purposed
3407
				// lets delete them in case something goes wrong in this whole process
3408
				if ( 'application/zip' === $attachment->post_mime_type ) {
3409
					// Schedule a cleanup for 2 hours from now in case of failed install.
3410
					wp_schedule_single_event( time() + 2 * HOUR_IN_SECONDS, 'upgrader_scheduled_cleanup', array( $attachment_id ) );
3411
				}
3412
			}
3413
		}
3414
		if ( ! is_null( $global_post ) ) {
3415
			$GLOBALS['post'] = $global_post;
3416
		}
3417
3418
		return $uploaded_files;
3419
	}
3420
3421
	/**
3422
	 * Add help to the Jetpack page
3423
	 *
3424
	 * @since Jetpack (1.2.3)
3425
	 * @return false if not the Jetpack page
3426
	 */
3427
	function admin_help() {
3428
		$current_screen = get_current_screen();
3429
3430
		// Overview
3431
		$current_screen->add_help_tab(
3432
			array(
3433
				'id'		=> 'home',
3434
				'title'		=> __( 'Home', 'jetpack' ),
3435
				'content'	=>
3436
					'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3437
					'<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3438
					'<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>',
3439
			)
3440
		);
3441
3442
		// Screen Content
3443
		if ( current_user_can( 'manage_options' ) ) {
3444
			$current_screen->add_help_tab(
3445
				array(
3446
					'id'		=> 'settings',
3447
					'title'		=> __( 'Settings', 'jetpack' ),
3448
					'content'	=>
3449
						'<p><strong>' . __( 'Jetpack by WordPress.com',                                              'jetpack' ) . '</strong></p>' .
3450
						'<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3451
						'<ol>' .
3452
							'<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.',														'jetpack' ) . '</li>' .
3453
							'<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>' .
3454
						'</ol>' .
3455
						'<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>'
3456
				)
3457
			);
3458
		}
3459
3460
		// Help Sidebar
3461
		$current_screen->set_help_sidebar(
3462
			'<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3463
			'<p><a href="https://jetpack.com/faq/" target="_blank">'     . __( 'Jetpack FAQ',     'jetpack' ) . '</a></p>' .
3464
			'<p><a href="https://jetpack.com/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3465
			'<p><a href="' . Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) .'">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3466
		);
3467
	}
3468
3469
	function admin_menu_css() {
3470
		wp_enqueue_style( 'jetpack-icons' );
3471
	}
3472
3473
	function admin_menu_order() {
3474
		return true;
3475
	}
3476
3477 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
3478
		$jp_menu_order = array();
3479
3480
		foreach ( $menu_order as $index => $item ) {
3481
			if ( $item != 'jetpack' ) {
3482
				$jp_menu_order[] = $item;
3483
			}
3484
3485
			if ( $index == 0 ) {
3486
				$jp_menu_order[] = 'jetpack';
3487
			}
3488
		}
3489
3490
		return $jp_menu_order;
3491
	}
3492
3493
	function admin_head() {
3494 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) )
3495
			/** This action is documented in class.jetpack-admin-page.php */
3496
			do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
3497
	}
3498
3499
	function admin_banner_styles() {
3500
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3501
3502 View Code Duplication
		if ( ! wp_style_is( 'jetpack-dops-style' ) ) {
3503
			wp_register_style(
3504
				'jetpack-dops-style',
3505
				plugins_url( '_inc/build/admin.dops-style.css', JETPACK__PLUGIN_FILE ),
3506
				array(),
3507
				JETPACK__VERSION
3508
			);
3509
		}
3510
3511
		wp_enqueue_style(
3512
			'jetpack',
3513
			plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ),
3514
			array( 'jetpack-dops-style' ),
3515
			 JETPACK__VERSION . '-20121016'
3516
		);
3517
		wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3518
		wp_style_add_data( 'jetpack', 'suffix', $min );
3519
	}
3520
3521
	function plugin_action_links( $actions ) {
3522
3523
		$jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack' ), __( 'Jetpack', 'jetpack' ) ) );
3524
3525
		if( current_user_can( 'jetpack_manage_modules' ) && ( Jetpack::is_active() || Jetpack::is_development_mode() ) ) {
3526
			return array_merge(
3527
				$jetpack_home,
3528
				array( 'settings' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack#/settings' ), __( 'Settings', 'jetpack' ) ) ),
3529
				array( 'support' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack-debugger '), __( 'Support', 'jetpack' ) ) ),
3530
				$actions
3531
				);
3532
			}
3533
3534
		return array_merge( $jetpack_home, $actions );
3535
	}
3536
3537
	/**
3538
	 * This is the first banner
3539
	 * It should be visible only to user that can update the option
3540
	 * Are not connected
3541
	 *
3542
	 * @return null
3543
	 */
3544
	function admin_jetpack_manage_notice() {
3545
		$screen = get_current_screen();
3546
3547
		// Don't show the connect notice on the jetpack settings page.
3548
		if ( ! in_array( $screen->base, array( 'dashboard' ) ) || $screen->is_network || $screen->action ) {
3549
			return;
3550
		}
3551
3552
		$opt_out_url = $this->opt_out_jetpack_manage_url();
3553
		$opt_in_url  = $this->opt_in_jetpack_manage_url();
3554
		/**
3555
		 * I think it would be great to have different wordsing depending on where you are
3556
		 * for example if we show the notice on dashboard and a different one if we show it on Plugins screen
3557
		 * etc..
3558
		 */
3559
3560
		?>
3561
		<div id="message" class="updated jp-banner">
3562
				<a href="<?php echo esc_url( $opt_out_url ); ?>" class="notice-dismiss" title="<?php esc_attr_e( 'Dismiss this notice', 'jetpack' ); ?>"></a>
3563
				<div class="jp-banner__description-container">
3564
					<h2 class="jp-banner__header"><?php esc_html_e( 'Jetpack Centralized Site Management', 'jetpack' ); ?></h2>
3565
					<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>
3566
					<p class="jp-banner__button-container">
3567
						<a href="<?php echo esc_url( $opt_in_url ); ?>" class="button button-primary" id="wpcom-connect"><?php _e( 'Activate Jetpack Manage', 'jetpack' ); ?></a>
3568
						<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>
3569
					</p>
3570
				</div>
3571
		</div>
3572
		<?php
3573
	}
3574
3575
	/**
3576
	 * Returns the url that the user clicks to remove the notice for the big banner
3577
	 * @return (string)
3578
	 */
3579
	function opt_out_jetpack_manage_url() {
3580
		$referer = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
3581
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-out' . $referer ), 'jetpack_manage_banner_opt_out' );
3582
	}
3583
	/**
3584
	 * Returns the url that the user clicks to opt in to Jetpack Manage
3585
	 * @return (string)
3586
	 */
3587
	function opt_in_jetpack_manage_url() {
3588
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-in' ), 'jetpack_manage_banner_opt_in' );
3589
	}
3590
3591
	function opt_in_jetpack_manage_notice() {
3592
		?>
3593
		<div class="wrap">
3594
			<div id="message" class="jetpack-message is-opt-in">
3595
				<?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' ); ?>
3596
			</div>
3597
		</div>
3598
		<?php
3599
3600
	}
3601
	/**
3602
	 * Determines whether to show the notice of not true = display notice
3603
	 * @return (bool)
3604
	 */
3605
	function can_display_jetpack_manage_notice() {
3606
		// never display the notice to users that can't do anything about it anyways
3607
		if( ! current_user_can( 'jetpack_manage_modules' ) )
3608
			return false;
3609
3610
		// don't display if we are in development more
3611
		if( Jetpack::is_development_mode() ) {
3612
			return false;
3613
		}
3614
		// don't display if the site is private
3615
		if(  ! Jetpack_Options::get_option( 'public' ) )
3616
			return false;
3617
3618
		/**
3619
		 * Should the Jetpack Remote Site Management notice be displayed.
3620
		 *
3621
		 * @since 3.3.0
3622
		 *
3623
		 * @param bool ! self::is_module_active( 'manage' ) Is the Manage module inactive.
3624
		 */
3625
		return apply_filters( 'can_display_jetpack_manage_notice', ! self::is_module_active( 'manage' ) );
3626
	}
3627
3628
	/*
3629
	 * Registration flow:
3630
	 * 1 - ::admin_page_load() action=register
3631
	 * 2 - ::try_registration()
3632
	 * 3 - ::register()
3633
	 *     - Creates jetpack_register option containing two secrets and a timestamp
3634
	 *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3635
	 *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3636
	 *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
3637
	 *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
3638
	 *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
3639
	 *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
3640
	 *       jetpack_id, jetpack_secret, jetpack_public
3641
	 *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
3642
	 * 4 - redirect to https://wordpress.com/start/jetpack-connect
3643
	 * 5 - user logs in with WP.com account
3644
	 * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
3645
	 *		- Jetpack_Client_Server::authorize()
3646
	 *		- Jetpack_Client_Server::get_token()
3647
	 *		- GET https://jetpack.wordpress.com/jetpack.token/1/ with
3648
	 *        client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
3649
	 *			- which responds with access_token, token_type, scope
3650
	 *		- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
3651
	 *		- Jetpack::activate_default_modules()
3652
	 *     		- Deactivates deprecated plugins
3653
	 *     		- Activates all default modules
3654
	 *		- Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
3655
	 * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
3656
	 * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
3657
	 *     Done!
3658
	 */
3659
3660
	/**
3661
	 * Handles the page load events for the Jetpack admin page
3662
	 */
3663
	function admin_page_load() {
3664
		$error = false;
3665
3666
		// Make sure we have the right body class to hook stylings for subpages off of.
3667
		add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ) );
3668
3669
		if ( ! empty( $_GET['jetpack_restate'] ) ) {
3670
			// Should only be used in intermediate redirects to preserve state across redirects
3671
			Jetpack::restate();
3672
		}
3673
3674
		if ( isset( $_GET['connect_url_redirect'] ) ) {
3675
			// User clicked in the iframe to link their accounts
3676
			if ( ! Jetpack::is_user_connected() ) {
3677
				$connect_url = $this->build_connect_url( true, false, 'iframe' );
3678
				if ( isset( $_GET['notes_iframe'] ) )
3679
					$connect_url .= '&notes_iframe';
3680
				wp_redirect( $connect_url );
3681
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3682
			} else {
3683
				if ( ! isset( $_GET['calypso_env'] ) ) {
3684
					Jetpack::state( 'message', 'already_authorized' );
3685
					wp_safe_redirect( Jetpack::admin_url() );
3686
				} else {
3687
					$connect_url = $this->build_connect_url( true, false, 'iframe' );
3688
					$connect_url .= '&already_authorized=true';
3689
					wp_redirect( $connect_url );
3690
				}
3691
			}
3692
		}
3693
3694
3695
		if ( isset( $_GET['action'] ) ) {
3696
			switch ( $_GET['action'] ) {
3697
			case 'authorize':
3698
				if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
3699
					Jetpack::state( 'message', 'already_authorized' );
3700
					wp_safe_redirect( Jetpack::admin_url() );
3701
					exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3702
				}
3703
				Jetpack::log( 'authorize' );
3704
				$client_server = new Jetpack_Client_Server;
3705
				$client_server->client_authorize();
3706
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3707
			case 'register' :
3708
				if ( ! current_user_can( 'jetpack_connect' ) ) {
3709
					$error = 'cheatin';
3710
					break;
3711
				}
3712
				check_admin_referer( 'jetpack-register' );
3713
				Jetpack::log( 'register' );
3714
				Jetpack::maybe_set_version_option();
3715
				$registered = Jetpack::try_registration();
3716
				if ( is_wp_error( $registered ) ) {
3717
					$error = $registered->get_error_code();
3718
					Jetpack::state( 'error', $error );
3719
					Jetpack::state( 'error', $registered->get_error_message() );
3720
					break;
3721
				}
3722
3723
				$from = isset( $_GET['from'] ) ? $_GET['from'] : false;
3724
3725
				wp_redirect( $this->build_connect_url( true, false, $from ) );
3726
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3727
			case 'activate' :
3728
				if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3729
					$error = 'cheatin';
3730
					break;
3731
				}
3732
3733
				$module = stripslashes( $_GET['module'] );
3734
				check_admin_referer( "jetpack_activate-$module" );
3735
				Jetpack::log( 'activate', $module );
3736
				Jetpack::activate_module( $module );
3737
				// The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3738
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3739
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3740
			case 'activate_default_modules' :
3741
				check_admin_referer( 'activate_default_modules' );
3742
				Jetpack::log( 'activate_default_modules' );
3743
				Jetpack::restate();
3744
				$min_version   = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
3745
				$max_version   = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
3746
				$other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
3747
				Jetpack::activate_default_modules( $min_version, $max_version, $other_modules );
3748
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3749
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3750
			case 'disconnect' :
3751
				if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3752
					$error = 'cheatin';
3753
					break;
3754
				}
3755
3756
				check_admin_referer( 'jetpack-disconnect' );
3757
				Jetpack::log( 'disconnect' );
3758
				Jetpack::disconnect();
3759
				wp_safe_redirect( Jetpack::admin_url( 'disconnected=true' ) );
3760
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3761
			case 'reconnect' :
3762
				if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3763
					$error = 'cheatin';
3764
					break;
3765
				}
3766
3767
				check_admin_referer( 'jetpack-reconnect' );
3768
				Jetpack::log( 'reconnect' );
3769
				$this->disconnect();
3770
				wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3771
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3772 View Code Duplication
			case 'deactivate' :
3773
				if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3774
					$error = 'cheatin';
3775
					break;
3776
				}
3777
3778
				$modules = stripslashes( $_GET['module'] );
3779
				check_admin_referer( "jetpack_deactivate-$modules" );
3780
				foreach ( explode( ',', $modules ) as $module ) {
3781
					Jetpack::log( 'deactivate', $module );
3782
					Jetpack::deactivate_module( $module );
3783
					Jetpack::state( 'message', 'module_deactivated' );
3784
				}
3785
				Jetpack::state( 'module', $modules );
3786
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3787
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3788
			case 'unlink' :
3789
				$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
3790
				check_admin_referer( 'jetpack-unlink' );
3791
				Jetpack::log( 'unlink' );
3792
				$this->unlink_user();
3793
				Jetpack::state( 'message', 'unlinked' );
3794
				if ( 'sub-unlink' == $redirect ) {
3795
					wp_safe_redirect( admin_url() );
3796
				} else {
3797
					wp_safe_redirect( Jetpack::admin_url( array( 'page' => $redirect ) ) );
3798
				}
3799
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_page_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3800
			default:
3801
				/**
3802
				 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3803
				 *
3804
				 * @since 2.6.0
3805
				 *
3806
				 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
3807
				 */
3808
				do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
3809
			}
3810
		}
3811
3812
		if ( ! $error = $error ? $error : Jetpack::state( 'error' ) ) {
3813
			self::activate_new_modules( true );
3814
		}
3815
3816
		$message_code = Jetpack::state( 'message' );
3817
		if ( Jetpack::state( 'optin-manage' ) ) {
3818
			$activated_manage = $message_code;
3819
			$message_code = 'jetpack-manage';
3820
		}
3821
3822
		switch ( $message_code ) {
3823
		case 'jetpack-manage':
3824
			$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>';
3825
			if ( $activated_manage ) {
3826
				$this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack'  ) . '</strong>';
3827
			}
3828
			break;
3829
3830
		}
3831
3832
		$deactivated_plugins = Jetpack::state( 'deactivated_plugins' );
3833
3834
		if ( ! empty( $deactivated_plugins ) ) {
3835
			$deactivated_plugins = explode( ',', $deactivated_plugins );
3836
			$deactivated_titles  = array();
3837
			foreach ( $deactivated_plugins as $deactivated_plugin ) {
3838
				if ( ! isset( $this->plugins_to_deactivate[$deactivated_plugin] ) ) {
3839
					continue;
3840
				}
3841
3842
				$deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[$deactivated_plugin][1] ) . '</strong>';
3843
			}
3844
3845
			if ( $deactivated_titles ) {
3846
				if ( $this->message ) {
3847
					$this->message .= "<br /><br />\n";
3848
				}
3849
3850
				$this->message .= wp_sprintf(
3851
					_n(
3852
						'Jetpack contains the most recent version of the old %l plugin.',
3853
						'Jetpack contains the most recent versions of the old %l plugins.',
3854
						count( $deactivated_titles ),
3855
						'jetpack'
3856
					),
3857
					$deactivated_titles
3858
				);
3859
3860
				$this->message .= "<br />\n";
3861
3862
				$this->message .= _n(
3863
					'The old version has been deactivated and can be removed from your site.',
3864
					'The old versions have been deactivated and can be removed from your site.',
3865
					count( $deactivated_titles ),
3866
					'jetpack'
3867
				);
3868
			}
3869
		}
3870
3871
		$this->privacy_checks = Jetpack::state( 'privacy_checks' );
3872
3873
		if ( $this->message || $this->error || $this->privacy_checks || $this->can_display_jetpack_manage_notice() ) {
3874
			add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
3875
		}
3876
3877 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
3878
			/**
3879
			 * Fires when a module configuration page is loaded.
3880
			 * The dynamic part of the hook is the configure parameter from the URL.
3881
			 *
3882
			 * @since 1.1.0
3883
			 */
3884
			do_action( 'jetpack_module_configuration_load_' . $_GET['configure'] );
3885
		}
3886
3887
		add_filter( 'jetpack_short_module_description', 'wptexturize' );
3888
	}
3889
3890
	function admin_notices() {
3891
3892
		if ( $this->error ) {
3893
?>
3894
<div id="message" class="jetpack-message jetpack-err">
3895
	<div class="squeezer">
3896
		<h2><?php echo wp_kses( $this->error, array( 'a' => array( 'href' => array() ), 'small' => true, 'code' => true, 'strong' => true, 'br' => true, 'b' => true ) ); ?></h2>
3897
<?php	if ( $desc = Jetpack::state( 'error_description' ) ) : ?>
3898
		<p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
3899
<?php	endif; ?>
3900
	</div>
3901
</div>
3902
<?php
3903
		}
3904
3905
		if ( $this->message ) {
3906
?>
3907
<div id="message" class="jetpack-message">
3908
	<div class="squeezer">
3909
		<h2><?php echo wp_kses( $this->message, array( 'strong' => array(), 'a' => array( 'href' => true ), 'br' => true ) ); ?></h2>
3910
	</div>
3911
</div>
3912
<?php
3913
		}
3914
3915
		if ( $this->privacy_checks ) :
3916
			$module_names = $module_slugs = array();
3917
3918
			$privacy_checks = explode( ',', $this->privacy_checks );
3919
			$privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
3920
			foreach ( $privacy_checks as $module_slug ) {
3921
				$module = Jetpack::get_module( $module_slug );
3922
				if ( ! $module ) {
3923
					continue;
3924
				}
3925
3926
				$module_slugs[] = $module_slug;
3927
				$module_names[] = "<strong>{$module['name']}</strong>";
3928
			}
3929
3930
			$module_slugs = join( ',', $module_slugs );
3931
?>
3932
<div id="message" class="jetpack-message jetpack-err">
3933
	<div class="squeezer">
3934
		<h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
3935
		<p><?php
3936
			echo wp_kses(
3937
				wptexturize(
3938
					wp_sprintf(
3939
						_nx(
3940
							"Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
3941
							"Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
3942
							count( $privacy_checks ),
3943
							'%l = list of Jetpack module/feature names',
3944
							'jetpack'
3945
						),
3946
						$module_names
3947
					)
3948
				),
3949
				array( 'strong' => true )
3950
			);
3951
3952
			echo "\n<br />\n";
3953
3954
			echo wp_kses(
3955
				sprintf(
3956
					_nx(
3957
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
3958
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
3959
						count( $privacy_checks ),
3960
						'%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
3961
						'jetpack'
3962
					),
3963
					wp_nonce_url(
3964
						Jetpack::admin_url(
3965
							array(
3966
								'page'   => 'jetpack',
3967
								'action' => 'deactivate',
3968
								'module' => urlencode( $module_slugs ),
3969
							)
3970
						),
3971
						"jetpack_deactivate-$module_slugs"
3972
					),
3973
					esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
3974
				),
3975
				array( 'a' => array( 'href' => true, 'title' => true ) )
3976
			);
3977
		?></p>
3978
	</div>
3979
</div>
3980
<?php endif;
3981
	// only display the notice if the other stuff is not there
3982
	if( $this->can_display_jetpack_manage_notice() && !  $this->error && ! $this->message && ! $this->privacy_checks ) {
3983
		if( isset( $_GET['page'] ) && 'jetpack' != $_GET['page'] )
3984
			$this->opt_in_jetpack_manage_notice();
3985
		}
3986
	}
3987
3988
	/**
3989
	 * Record a stat for later output.  This will only currently output in the admin_footer.
3990
	 */
3991
	function stat( $group, $detail ) {
3992
		if ( ! isset( $this->stats[ $group ] ) )
3993
			$this->stats[ $group ] = array();
3994
		$this->stats[ $group ][] = $detail;
3995
	}
3996
3997
	/**
3998
	 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
3999
	 */
4000
	function do_stats( $method = '' ) {
4001
		if ( is_array( $this->stats ) && count( $this->stats ) ) {
4002
			foreach ( $this->stats as $group => $stats ) {
4003
				if ( is_array( $stats ) && count( $stats ) ) {
4004
					$args = array( "x_jetpack-{$group}" => implode( ',', $stats ) );
4005
					if ( 'server_side' === $method ) {
4006
						self::do_server_side_stat( $args );
4007
					} else {
4008
						echo '<img src="' . esc_url( self::build_stats_url( $args ) ) . '" width="1" height="1" style="display:none;" />';
4009
					}
4010
				}
4011
				unset( $this->stats[ $group ] );
4012
			}
4013
		}
4014
	}
4015
4016
	/**
4017
	 * Runs stats code for a one-off, server-side.
4018
	 *
4019
	 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
4020
	 *
4021
	 * @return bool If it worked.
4022
	 */
4023
	static function do_server_side_stat( $args ) {
4024
		$response = wp_remote_get( esc_url_raw( self::build_stats_url( $args ) ) );
4025
		if ( is_wp_error( $response ) )
4026
			return false;
4027
4028
		if ( 200 !== wp_remote_retrieve_response_code( $response ) )
4029
			return false;
4030
4031
		return true;
4032
	}
4033
4034
	/**
4035
	 * Builds the stats url.
4036
	 *
4037
	 * @param $args array|string The arguments to append to the URL.
4038
	 *
4039
	 * @return string The URL to be pinged.
4040
	 */
4041
	static function build_stats_url( $args ) {
4042
		$defaults = array(
4043
			'v'    => 'wpcom2',
4044
			'rand' => md5( mt_rand( 0, 999 ) . time() ),
4045
		);
4046
		$args     = wp_parse_args( $args, $defaults );
4047
		/**
4048
		 * Filter the URL used as the Stats tracking pixel.
4049
		 *
4050
		 * @since 2.3.2
4051
		 *
4052
		 * @param string $url Base URL used as the Stats tracking pixel.
4053
		 */
4054
		$base_url = apply_filters(
4055
			'jetpack_stats_base_url',
4056
			'https://pixel.wp.com/g.gif'
4057
		);
4058
		$url      = add_query_arg( $args, $base_url );
4059
		return $url;
4060
	}
4061
4062
	static function translate_current_user_to_role() {
4063
		foreach ( self::$capability_translations as $role => $cap ) {
4064
			if ( current_user_can( $role ) || current_user_can( $cap ) ) {
4065
				return $role;
4066
			}
4067
		}
4068
4069
		return false;
4070
	}
4071
4072
	static function translate_role_to_cap( $role ) {
4073
		if ( ! isset( self::$capability_translations[$role] ) ) {
4074
			return false;
4075
		}
4076
4077
		return self::$capability_translations[$role];
4078
	}
4079
4080
	static function sign_role( $role ) {
4081
		if ( ! $user_id = (int) get_current_user_id() ) {
4082
			return false;
4083
		}
4084
4085
		$token = Jetpack_Data::get_access_token();
4086
		if ( ! $token || is_wp_error( $token ) ) {
4087
			return false;
4088
		}
4089
4090
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
4091
	}
4092
4093
4094
	/**
4095
	 * Builds a URL to the Jetpack connection auth page
4096
	 *
4097
	 * @since 3.9.5
4098
	 *
4099
	 * @param bool $raw If true, URL will not be escaped.
4100
	 * @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
4101
	 *                              If string, will be a custom redirect.
4102
	 * @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
4103
	 *
4104
	 * @return string Connect URL
4105
	 */
4106
	function build_connect_url( $raw = false, $redirect = false, $from = false ) {
4107
		if ( ! Jetpack_Options::get_option( 'blog_token' ) || ! Jetpack_Options::get_option( 'id' ) ) {
4108
			$url = Jetpack::nonce_url_no_esc( Jetpack::admin_url( 'action=register' ), 'jetpack-register' );
4109
			if( is_network_admin() ) {
4110
				$url = add_query_arg( 'is_multisite', network_admin_url( 'admin.php?page=jetpack-settings' ), $url );
4111
			}
4112
		} else {
4113
			if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && include_once JETPACK__GLOTPRESS_LOCALES_PATH ) {
4114
				$gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
4115
			}
4116
4117
			$role = self::translate_current_user_to_role();
4118
			$signed_role = self::sign_role( $role );
4119
4120
			$user = wp_get_current_user();
4121
4122
			$jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
4123
			$redirect = $redirect
4124
				? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
4125
				: $jetpack_admin_page;
4126
4127
			if( isset( $_REQUEST['is_multisite'] ) ) {
4128
				$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4129
			}
4130
4131
			$secrets = Jetpack::init()->generate_secrets( 'authorize' );
4132
			@list( $secret ) = explode( ':', $secrets );
4133
4134
			$site_icon = ( function_exists( 'has_site_icon') && has_site_icon() )
4135
				? get_site_icon_url()
4136
				: false;
4137
4138
			/**
4139
			 * Filter the type of authorization.
4140
			 * 'calypso' completes authorization on wordpress.com/jetpack/connect
4141
			 * while 'jetpack' ( or any other value ) completes the authorization at jetpack.wordpress.com.
4142
			 *
4143
			 * @since 4.3.3
4144
			 *
4145
			 * @param string $auth_type Defaults to 'calypso', can also be 'jetpack'.
4146
			 */
4147
			$auth_type = apply_filters( 'jetpack_auth_type', 'calypso' );
4148
4149
			$args = urlencode_deep(
4150
				array(
4151
					'response_type' => 'code',
4152
					'client_id'     => Jetpack_Options::get_option( 'id' ),
4153
					'redirect_uri'  => add_query_arg(
4154
						array(
4155
							'action'   => 'authorize',
4156
							'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
4157
							'redirect' => urlencode( $redirect ),
4158
						),
4159
						esc_url( admin_url( 'admin.php?page=jetpack' ) )
4160
					),
4161
					'state'         => $user->ID,
4162
					'scope'         => $signed_role,
4163
					'user_email'    => $user->user_email,
4164
					'user_login'    => $user->user_login,
4165
					'is_active'     => Jetpack::is_active(),
4166
					'jp_version'    => JETPACK__VERSION,
4167
					'auth_type'     => $auth_type,
4168
					'secret'        => $secret,
4169
					'locale'        => ( isset( $gp_locale ) && isset( $gp_locale->slug ) ) ? $gp_locale->slug : '',
4170
					'blogname'      => get_option( 'blogname' ),
4171
					'site_url'      => site_url(),
4172
					'home_url'      => home_url(),
4173
					'site_icon'     => $site_icon,
4174
				)
4175
			);
4176
4177
			$url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) );
4178
		}
4179
4180
		if ( $from ) {
4181
			$url = add_query_arg( 'from', $from, $url );
4182
		}
4183
4184
		if ( isset( $_GET['calypso_env'] ) ) {
4185
			$url = add_query_arg( 'calypso_env', sanitize_key( $_GET['calypso_env'] ), $url );
4186
		}
4187
4188
		return $raw ? $url : esc_url( $url );
4189
	}
4190
4191
	function build_reconnect_url( $raw = false ) {
4192
		$url = wp_nonce_url( Jetpack::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4193
		return $raw ? $url : esc_url( $url );
4194
	}
4195
4196
	public static function admin_url( $args = null ) {
4197
		$args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
4198
		$url = add_query_arg( $args, admin_url( 'admin.php' ) );
4199
		return $url;
4200
	}
4201
4202
	public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4203
		$actionurl = str_replace( '&amp;', '&', $actionurl );
4204
		return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4205
	}
4206
4207
	function dismiss_jetpack_notice() {
4208
4209
		if ( ! isset( $_GET['jetpack-notice'] ) ) {
4210
			return;
4211
		}
4212
4213
		switch( $_GET['jetpack-notice'] ) {
4214
			case 'dismiss':
4215
				if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4216
4217
					require_once ABSPATH . 'wp-admin/includes/plugin.php';
4218
					deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4219
					wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4220
				}
4221
				break;
4222 View Code Duplication
			case 'jetpack-manage-opt-out':
4223
4224
				if ( check_admin_referer( 'jetpack_manage_banner_opt_out' ) ) {
4225
					// Don't show the banner again
4226
4227
					Jetpack_Options::update_option( 'dismissed_manage_banner', true );
4228
					// redirect back to the page that had the notice
4229
					if ( wp_get_referer() ) {
4230
						wp_safe_redirect( wp_get_referer() );
4231
					} else {
4232
						// Take me to Jetpack
4233
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4234
					}
4235
				}
4236
				break;
4237 View Code Duplication
			case 'jetpack-protect-multisite-opt-out':
4238
4239
				if ( check_admin_referer( 'jetpack_protect_multisite_banner_opt_out' ) ) {
4240
					// Don't show the banner again
4241
4242
					update_site_option( 'jetpack_dismissed_protect_multisite_banner', true );
4243
					// redirect back to the page that had the notice
4244
					if ( wp_get_referer() ) {
4245
						wp_safe_redirect( wp_get_referer() );
4246
					} else {
4247
						// Take me to Jetpack
4248
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4249
					}
4250
				}
4251
				break;
4252
			case 'jetpack-manage-opt-in':
4253
				if ( check_admin_referer( 'jetpack_manage_banner_opt_in' ) ) {
4254
					// This makes sure that we are redirect to jetpack home so that we can see the Success Message.
4255
4256
					$redirection_url = Jetpack::admin_url();
4257
					remove_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4258
4259
					// Don't redirect form the Jetpack Setting Page
4260
					$referer_parsed = parse_url ( wp_get_referer() );
4261
					// check that we do have a wp_get_referer and the query paramater is set orderwise go to the Jetpack Home
4262
					if ( isset( $referer_parsed['query'] ) && false !== strpos( $referer_parsed['query'], 'page=jetpack_modules' ) ) {
4263
						// Take the user to Jetpack home except when on the setting page
4264
						$redirection_url = wp_get_referer();
4265
						add_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4266
					}
4267
					// Also update the JSON API FULL MANAGEMENT Option
4268
					Jetpack::activate_module( 'manage', false, false );
4269
4270
					// Special Message when option in.
4271
					Jetpack::state( 'optin-manage', 'true' );
4272
					// Activate the Module if not activated already
4273
4274
					// Redirect properly
4275
					wp_safe_redirect( $redirection_url );
4276
4277
				}
4278
				break;
4279
		}
4280
	}
4281
4282
	public static function admin_screen_configure_module( $module_id ) {
4283
4284
		// User that doesn't have 'jetpack_configure_modules' will never end up here since Jetpack Landing Page woun't let them.
4285
		if ( ! in_array( $module_id, Jetpack::get_active_modules() ) && current_user_can( 'manage_options' ) ) {
4286
			if ( has_action( 'display_activate_module_setting_' . $module_id ) ) {
4287
				/**
4288
				 * Fires to diplay a custom module activation screen.
4289
				 *
4290
				 * To add a module actionation screen use Jetpack::module_configuration_activation_screen method.
4291
				 * Example: Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
4292
				 *
4293
				 * @module manage
4294
				 *
4295
				 * @since 3.8.0
4296
				 *
4297
				 * @param int $module_id Module ID.
4298
				 */
4299
				do_action( 'display_activate_module_setting_' . $module_id );
4300
			} else {
4301
				self::display_activate_module_link( $module_id );
4302
			}
4303
4304
			return false;
4305
		} ?>
4306
4307
		<div id="jp-settings-screen" style="position: relative">
4308
			<h3>
4309
			<?php
4310
				$module = Jetpack::get_module( $module_id );
4311
				echo '<a href="' . Jetpack::admin_url( 'page=jetpack_modules' ) . '">' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</a> &rarr; ';
4312
				printf( __( 'Configure %s', 'jetpack' ), $module['name'] );
4313
			?>
4314
			</h3>
4315
			<?php
4316
				/**
4317
				 * Fires within the displayed message when a feature configuation is updated.
4318
				 *
4319
				 * @since 3.4.0
4320
				 *
4321
				 * @param int $module_id Module ID.
4322
				 */
4323
				do_action( 'jetpack_notices_update_settings', $module_id );
4324
				/**
4325
				 * Fires when a feature configuation screen is loaded.
4326
				 * The dynamic part of the hook, $module_id, is the module ID.
4327
				 *
4328
				 * @since 1.1.0
4329
				 */
4330
				do_action( 'jetpack_module_configuration_screen_' . $module_id );
4331
			?>
4332
		</div><?php
4333
	}
4334
4335
	/**
4336
	 * Display link to activate the module to see the settings screen.
4337
	 * @param  string $module_id
4338
	 * @return null
4339
	 */
4340
	public static function display_activate_module_link( $module_id ) {
4341
4342
		$info =  Jetpack::get_module( $module_id );
4343
		$extra = '';
4344
		$activate_url = wp_nonce_url(
4345
				Jetpack::admin_url(
4346
					array(
4347
						'page'   => 'jetpack',
4348
						'action' => 'activate',
4349
						'module' => $module_id,
4350
					)
4351
				),
4352
				"jetpack_activate-$module_id"
4353
			);
4354
4355
		?>
4356
4357
		<div class="wrap configure-module">
4358
			<div id="jp-settings-screen">
4359
				<?php
4360
				if ( $module_id == 'json-api' ) {
4361
4362
					$info['name'] = esc_html__( 'Activate Site Management and JSON API', 'jetpack' );
4363
4364
					$activate_url = Jetpack::init()->opt_in_jetpack_manage_url();
4365
4366
					$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' );
4367
4368
					// $extra = __( 'To use Site Management, you need to first activate JSON API to allow remote management of your site. ', 'jetpack' );
4369
				} ?>
4370
4371
				<h3><?php echo esc_html( $info['name'] ); ?></h3>
4372
				<div class="narrow">
4373
					<p><?php echo  $info['description']; ?></p>
4374
					<?php if( $extra ) { ?>
4375
					<p><?php echo esc_html( $extra ); ?></p>
4376
					<?php } ?>
4377
					<p>
4378
						<?php
4379
						if( wp_get_referer() ) {
4380
							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() );
4381
						} else {
4382
							printf( __( '<a class="button-primary" href="%s">Activate Now</a>', 'jetpack' ) , $activate_url  );
4383
						} ?>
4384
					</p>
4385
				</div>
4386
4387
			</div>
4388
		</div>
4389
4390
		<?php
4391
	}
4392
4393
	public static function sort_modules( $a, $b ) {
4394
		if ( $a['sort'] == $b['sort'] )
4395
			return 0;
4396
4397
		return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
4398
	}
4399
4400
	function ajax_recheck_ssl() {
4401
		check_ajax_referer( 'recheck-ssl', 'ajax-nonce' );
4402
		$result = Jetpack::permit_ssl( true );
4403
		wp_send_json( array(
4404
			'enabled' => $result,
4405
			'message' => get_transient( 'jetpack_https_test_message' )
4406
		) );
4407
	}
4408
4409
/* Client API */
4410
4411
	/**
4412
	 * Returns the requested Jetpack API URL
4413
	 *
4414
	 * @return string
4415
	 */
4416
	public static function api_url( $relative_url ) {
4417
		return trailingslashit( JETPACK__API_BASE . $relative_url  ) . JETPACK__API_VERSION . '/';
4418
	}
4419
4420
	/**
4421
	 * Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
4422
	 */
4423
	public static function fix_url_for_bad_hosts( $url ) {
4424
		if ( 0 !== strpos( $url, 'https://' ) ) {
4425
			return $url;
4426
		}
4427
4428
		switch ( JETPACK_CLIENT__HTTPS ) {
4429
			case 'ALWAYS' :
4430
				return $url;
4431
			case 'NEVER' :
4432
				return set_url_scheme( $url, 'http' );
4433
			// default : case 'AUTO' :
4434
		}
4435
4436
		// we now return the unmodified SSL URL by default, as a security precaution
4437
		return $url;
4438
	}
4439
4440
	/**
4441
	 * Checks to see if the URL is using SSL to connect with Jetpack
4442
	 *
4443
	 * @since 2.3.3
4444
	 * @return boolean
4445
	 */
4446
	public static function permit_ssl( $force_recheck = false ) {
4447
		// Do some fancy tests to see if ssl is being supported
4448
		if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
4449
			$message = '';
4450
			if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
4451
				$ssl = 0;
4452
			} else {
4453
				switch ( JETPACK_CLIENT__HTTPS ) {
4454
					case 'NEVER':
4455
						$ssl = 0;
4456
						$message = __( 'JETPACK_CLIENT__HTTPS is set to NEVER', 'jetpack' );
4457
						break;
4458
					case 'ALWAYS':
4459
					case 'AUTO':
4460
					default:
4461
						$ssl = 1;
4462
						break;
4463
				}
4464
4465
				// If it's not 'NEVER', test to see
4466
				if ( $ssl ) {
4467
					if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4468
						$ssl = 0;
4469
						$message = __( 'WordPress reports no SSL support', 'jetpack' );
4470
					} else {
4471
						$response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4472
						if ( is_wp_error( $response ) ) {
4473
							$ssl = 0;
4474
							$message = __( 'WordPress reports no SSL support', 'jetpack' );
4475
						} elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4476
							$ssl = 0;
4477
							$message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4478
						}
4479
					}
4480
				}
4481
			}
4482
			set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4483
			set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
4484
		}
4485
4486
		return (bool) $ssl;
4487
	}
4488
4489
	/*
4490
	 * Displays an admin_notice, alerting the user to their JETPACK_CLIENT__HTTPS constant being 'AUTO' but SSL isn't working.
4491
	 */
4492
	public function alert_auto_ssl_fail() {
4493
		if ( ! current_user_can( 'manage_options' ) )
4494
			return;
4495
4496
		$ajax_nonce = wp_create_nonce( 'recheck-ssl' );
4497
		?>
4498
4499
		<div id="jetpack-ssl-warning" class="error jp-identity-crisis">
4500
			<div class="jp-banner__content">
4501
				<h2><?php _e( 'Outbound HTTPS not working', 'jetpack' ); ?></h2>
4502
				<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>
4503
				<p>
4504
					<?php _e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
4505
					<a href="#" id="jetpack-recheck-ssl-button"><?php _e( 'Try again', 'jetpack' ); ?></a>
4506
					<span id="jetpack-recheck-ssl-output"><?php echo get_transient( 'jetpack_https_test_message' ); ?></span>
4507
				</p>
4508
				<p>
4509
					<?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' ),
4510
							esc_url( Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) ),
4511
							esc_url( 'https://jetpack.com/support/getting-started-with-jetpack/troubleshooting-tips/' ) ); ?>
4512
				</p>
4513
			</div>
4514
		</div>
4515
		<style>
4516
			#jetpack-recheck-ssl-output { margin-left: 5px; color: red; }
4517
		</style>
4518
		<script type="text/javascript">
4519
			jQuery( document ).ready( function( $ ) {
4520
				$( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4521
					var $this = $( this );
4522
					$this.html( <?php echo json_encode( __( 'Checking', 'jetpack' ) ); ?> );
4523
					$( '#jetpack-recheck-ssl-output' ).html( '' );
4524
					e.preventDefault();
4525
					var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': '<?php echo $ajax_nonce; ?>' };
4526
					$.post( ajaxurl, data )
4527
					  .done( function( response ) {
4528
					  	if ( response.enabled ) {
4529
					  		$( '#jetpack-ssl-warning' ).hide();
4530
					  	} else {
4531
					  		this.html( <?php echo json_encode( __( 'Try again', 'jetpack' ) ); ?> );
4532
					  		$( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4533
					  	}
4534
					  }.bind( $this ) );
4535
				} );
4536
			} );
4537
		</script>
4538
4539
		<?php
4540
	}
4541
4542
	/**
4543
	 * Returns the Jetpack XML-RPC API
4544
	 *
4545
	 * @return string
4546
	 */
4547
	public static function xmlrpc_api_url() {
4548
		$base = preg_replace( '#(https?://[^?/]+)(/?.*)?$#', '\\1', JETPACK__API_BASE );
4549
		return untrailingslashit( $base ) . '/xmlrpc.php';
4550
	}
4551
4552
	/**
4553
	 * Creates two secret tokens and the end of life timestamp for them.
4554
	 *
4555
	 * Note these tokens are unique per call, NOT static per site for connecting.
4556
	 *
4557
	 * @since 2.6
4558
	 * @return array
4559
	 */
4560
	public function generate_secrets( $action, $exp = 600 ) {
4561
	    $secret = wp_generate_password( 32, false ) // secret_1
4562
	    		. ':' . wp_generate_password( 32, false ) // secret_2
4563
	    		. ':' . ( time() + $exp ) // eol ( End of Life )
4564
	    		. ':' . get_current_user_id(); // ties the secrets to the current user
4565
		Jetpack_Options::update_option( $action, $secret );
4566
	    return Jetpack_Options::get_option( $action );
4567
	}
4568
4569
	/**
4570
	 * Builds the timeout limit for queries talking with the wpcom servers.
4571
	 *
4572
	 * Based on local php max_execution_time in php.ini
4573
	 *
4574
	 * @since 2.6
4575
	 * @return int
4576
	 **/
4577
	public function get_remote_query_timeout_limit() {
4578
	    $timeout = (int) ini_get( 'max_execution_time' );
4579
	    if ( ! $timeout ) // Ensure exec time set in php.ini
4580
				$timeout = 30;
4581
	    return intval( $timeout / 2 );
4582
	}
4583
4584
4585
	/**
4586
	 * Takes the response from the Jetpack register new site endpoint and
4587
	 * verifies it worked properly.
4588
	 *
4589
	 * @since 2.6
4590
	 * @return string|Jetpack_Error A JSON object on success or Jetpack_Error on failures
4591
	 **/
4592
	public function validate_remote_register_response( $response ) {
4593
	  if ( is_wp_error( $response ) ) {
4594
			return new Jetpack_Error( 'register_http_request_failed', $response->get_error_message() );
4595
		}
4596
4597
		$code   = wp_remote_retrieve_response_code( $response );
4598
		$entity = wp_remote_retrieve_body( $response );
4599
		if ( $entity )
4600
			$registration_response = json_decode( $entity );
4601
		else
4602
			$registration_response = false;
4603
4604
		$code_type = intval( $code / 100 );
4605
		if ( 5 == $code_type ) {
4606
			return new Jetpack_Error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4607
		} elseif ( 408 == $code ) {
4608
			return new Jetpack_Error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4609
		} elseif ( ! empty( $registration_response->error ) ) {
4610
			if ( 'xml_rpc-32700' == $registration_response->error && ! function_exists( 'xml_parser_create' ) ) {
4611
				$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' );
4612
			} else {
4613
				$error_description = isset( $registration_response->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $registration_response->error_description ) : '';
4614
			}
4615
4616
			return new Jetpack_Error( (string) $registration_response->error, $error_description, $code );
4617
		} elseif ( 200 != $code ) {
4618
			return new Jetpack_Error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4619
		}
4620
4621
		// Jetpack ID error block
4622
		if ( empty( $registration_response->jetpack_id ) ) {
4623
			return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ), $entity );
4624
		} elseif ( ! is_scalar( $registration_response->jetpack_id ) ) {
4625
			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 );
4626
		} elseif ( preg_match( '/[^0-9]/', $registration_response->jetpack_id ) ) {
4627
			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 );
4628
		}
4629
4630
	    return $registration_response;
4631
	}
4632
	/**
4633
	 * @return bool|WP_Error
4634
	 */
4635
	public static function register() {
4636
		add_action( 'pre_update_jetpack_option_register', array( 'Jetpack_Options', 'delete_option' ) );
4637
		$secrets = Jetpack::init()->generate_secrets( 'register' );
4638
4639
		@list( $secret_1, $secret_2, $secret_eol ) = explode( ':', $secrets );
4640 View Code Duplication
		if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) {
4641
			return new Jetpack_Error( 'missing_secrets' );
4642
		}
4643
4644
		$timeout = Jetpack::init()->get_remote_query_timeout_limit();
4645
4646
		$gmt_offset = get_option( 'gmt_offset' );
4647
		if ( ! $gmt_offset ) {
4648
			$gmt_offset = 0;
4649
		}
4650
4651
		$stats_options = get_option( 'stats_options' );
4652
		$stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null;
4653
4654
		$args = array(
4655
			'method'  => 'POST',
4656
			'body'    => array(
4657
				'siteurl'         => site_url(),
4658
				'home'            => home_url(),
4659
				'gmt_offset'      => $gmt_offset,
4660
				'timezone_string' => (string) get_option( 'timezone_string' ),
4661
				'site_name'       => (string) get_option( 'blogname' ),
4662
				'secret_1'        => $secret_1,
4663
				'secret_2'        => $secret_2,
4664
				'site_lang'       => get_locale(),
4665
				'timeout'         => $timeout,
4666
				'stats_id'        => $stats_id,
4667
				'state'           => get_current_user_id(),
4668
			),
4669
			'headers' => array(
4670
				'Accept' => 'application/json',
4671
			),
4672
			'timeout' => $timeout,
4673
		);
4674
		$response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'register' ) ), $args, true );
4675
4676
		// Make sure the response is valid and does not contain any Jetpack errors
4677
		$registration_details = Jetpack::init()->validate_remote_register_response( $response );
4678
		if ( is_wp_error( $registration_details ) ) {
4679
		    return $registration_details;
4680
		} elseif ( ! $registration_details ) {
4681
			return new Jetpack_Error( 'unknown_error', __( 'Unknown error registering your Jetpack site', 'jetpack' ), wp_remote_retrieve_response_code( $response ) );
4682
		}
4683
4684 View Code Duplication
		if ( empty( $registration_details->jetpack_secret ) || ! is_string( $registration_details->jetpack_secret ) ) {
4685
			return new Jetpack_Error( 'jetpack_secret', '', wp_remote_retrieve_response_code( $response ) );
4686
		}
4687
4688
		if ( isset( $registration_details->jetpack_public ) ) {
4689
			$jetpack_public = (int) $registration_details->jetpack_public;
4690
		} else {
4691
			$jetpack_public = false;
4692
		}
4693
4694
		Jetpack_Options::update_options(
4695
			array(
4696
				'id'         => (int)    $registration_details->jetpack_id,
4697
				'blog_token' => (string) $registration_details->jetpack_secret,
4698
				'public'     => $jetpack_public,
4699
			)
4700
		);
4701
4702
		/**
4703
		 * Fires when a site is registered on WordPress.com.
4704
		 *
4705
		 * @since 3.7.0
4706
		 *
4707
		 * @param int $json->jetpack_id Jetpack Blog ID.
4708
		 * @param string $json->jetpack_secret Jetpack Blog Token.
4709
		 * @param int|bool $jetpack_public Is the site public.
4710
		 */
4711
		do_action( 'jetpack_site_registered', $registration_details->jetpack_id, $registration_details->jetpack_secret, $jetpack_public );
4712
4713
		// Initialize Jump Start for the first and only time.
4714
		if ( ! Jetpack_Options::get_option( 'jumpstart' ) ) {
4715
			Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
4716
4717
			$jetpack = Jetpack::init();
4718
4719
			$jetpack->stat( 'jumpstart', 'unique-views' );
4720
			$jetpack->do_stats( 'server_side' );
4721
		};
4722
4723
		return true;
4724
	}
4725
4726
	/**
4727
	 * If the db version is showing something other that what we've got now, bump it to current.
4728
	 *
4729
	 * @return bool: True if the option was incorrect and updated, false if nothing happened.
4730
	 */
4731
	public static function maybe_set_version_option() {
4732
		list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
4733
		if ( JETPACK__VERSION != $version ) {
4734
			Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
4735
4736
			if ( version_compare( JETPACK__VERSION, $version, '>' ) ) {
4737
				/** This action is documented in class.jetpack.php */
4738
				do_action( 'updating_jetpack_version', JETPACK__VERSION, $version );
4739
			}
4740
4741
			return true;
4742
		}
4743
		return false;
4744
	}
4745
4746
/* Client Server API */
4747
4748
	/**
4749
	 * Loads the Jetpack XML-RPC client
4750
	 */
4751
	public static function load_xml_rpc_client() {
4752
		require_once ABSPATH . WPINC . '/class-IXR.php';
4753
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-ixr-client.php';
4754
	}
4755
4756
	/**
4757
	 * Resets the saved authentication state in between testing requests.
4758
	 */
4759
	public function reset_saved_auth_state() {
4760
		$this->xmlrpc_verification = null;
4761
		$this->rest_authentication_status = null;
4762
	}
4763
4764
	function verify_xml_rpc_signature() {
4765
		if ( $this->xmlrpc_verification ) {
4766
			return $this->xmlrpc_verification;
4767
		}
4768
4769
		// It's not for us
4770
		if ( ! isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
4771
			return false;
4772
		}
4773
4774
		@list( $token_key, $version, $user_id ) = explode( ':', $_GET['token'] );
4775
		if (
4776
			empty( $token_key )
4777
		||
4778
			empty( $version ) || strval( JETPACK__API_VERSION ) !== $version
4779
		) {
4780
			return false;
4781
		}
4782
4783
		if ( '0' === $user_id ) {
4784
			$token_type = 'blog';
4785
			$user_id = 0;
4786
		} else {
4787
			$token_type = 'user';
4788
			if ( empty( $user_id ) || ! ctype_digit( $user_id ) ) {
4789
				return false;
4790
			}
4791
			$user_id = (int) $user_id;
4792
4793
			$user = new WP_User( $user_id );
4794
			if ( ! $user || ! $user->exists() ) {
4795
				return false;
4796
			}
4797
		}
4798
4799
		$token = Jetpack_Data::get_access_token( $user_id );
4800
		if ( ! $token ) {
4801
			return false;
4802
		}
4803
4804
		$token_check = "$token_key.";
4805
		if ( ! hash_equals( substr( $token->secret, 0, strlen( $token_check ) ), $token_check ) ) {
4806
			return false;
4807
		}
4808
4809
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
4810
4811
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
4812
		if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
4813
			$post_data   = $_POST;
4814
			$file_hashes = array();
4815
			foreach ( $post_data as $post_data_key => $post_data_value ) {
4816
				if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
4817
					continue;
4818
				}
4819
				$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
4820
				$file_hashes[$post_data_key] = $post_data_value;
4821
			}
4822
4823
			foreach ( $file_hashes as $post_data_key => $post_data_value ) {
4824
				unset( $post_data["_jetpack_file_hmac_{$post_data_key}"] );
4825
				$post_data[$post_data_key] = $post_data_value;
4826
			}
4827
4828
			ksort( $post_data );
4829
4830
			$body = http_build_query( stripslashes_deep( $post_data ) );
4831
		} elseif ( is_null( $this->HTTP_RAW_POST_DATA ) ) {
4832
			$body = file_get_contents( 'php://input' );
4833
		} else {
4834
			$body = null;
4835
		}
4836
4837
		$signature = $jetpack_signature->sign_current_request(
4838
			array( 'body' => is_null( $body ) ? $this->HTTP_RAW_POST_DATA : $body, )
4839
		);
4840
4841
		if ( ! $signature ) {
4842
			return false;
4843
		} else if ( is_wp_error( $signature ) ) {
4844
			return $signature;
4845
		} else if ( ! hash_equals( $signature, $_GET['signature'] ) ) {
4846
			return false;
4847
		}
4848
4849
		$timestamp = (int) $_GET['timestamp'];
4850
		$nonce     = stripslashes( (string) $_GET['nonce'] );
4851
4852
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
4853
			return false;
4854
		}
4855
4856
		$this->xmlrpc_verification = array(
4857
			'type'    => $token_type,
4858
			'user_id' => $token->external_user_id,
4859
		);
4860
4861
		return $this->xmlrpc_verification;
4862
	}
4863
4864
	/**
4865
	 * Authenticates XML-RPC and other requests from the Jetpack Server
4866
	 */
4867
	function authenticate_jetpack( $user, $username, $password ) {
4868
		if ( is_a( $user, 'WP_User' ) ) {
4869
			return $user;
4870
		}
4871
4872
		$token_details = $this->verify_xml_rpc_signature();
4873
4874
		if ( ! $token_details || is_wp_error( $token_details ) ) {
4875
			return $user;
4876
		}
4877
4878
		if ( 'user' !== $token_details['type'] ) {
4879
			return $user;
4880
		}
4881
4882
		if ( ! $token_details['user_id'] ) {
4883
			return $user;
4884
		}
4885
4886
		nocache_headers();
4887
4888
		return new WP_User( $token_details['user_id'] );
4889
	}
4890
4891
	// Authenticates requests from Jetpack server to WP REST API endpoints.
4892
	// Uses the existing XMLRPC request signing implementation.
4893
	function wp_rest_authenticate( $user ) {
4894
		if ( ! empty( $user ) ) {
4895
			// Another authentication method is in effect.
4896
			return $user;
4897
		}
4898
4899
		if ( ! isset( $_GET['_for'] ) || $_GET['_for'] !== 'jetpack' ) {
4900
			// Nothing to do for this authentication method.
4901
			return null;
4902
		}
4903
4904
		if ( ! isset( $_GET['token'] ) && ! isset( $_GET['signature'] ) ) {
4905
			// Nothing to do for this authentication method.
4906
			return null;
4907
		}
4908
4909
		// Ensure that we always have the request body available.  At this
4910
		// point, the WP REST API code to determine the request body has not
4911
		// run yet.  That code may try to read from 'php://input' later, but
4912
		// this can only be done once per request in PHP versions prior to 5.6.
4913
		// So we will go ahead and perform this read now if needed, and save
4914
		// the request body where both the Jetpack signature verification code
4915
		// and the WP REST API code can see it.
4916
		if ( ! isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ) {
4917
			$GLOBALS['HTTP_RAW_POST_DATA'] = file_get_contents( 'php://input' );
4918
		}
4919
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
4920
4921
		// Only support specific request parameters that have been tested and
4922
		// are known to work with signature verification.  A different method
4923
		// can be passed to the WP REST API via the '?_method=' parameter if
4924
		// needed.
4925
		if ( $_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
4926
			$this->rest_authentication_status = new WP_Error(
4927
				'rest_invalid_request',
4928
				__( 'This request method is not supported.', 'jetpack' ),
4929
				array( 'status' => 400 )
4930
			);
4931
			return null;
4932
		}
4933
		if ( $_SERVER['REQUEST_METHOD'] !== 'POST' && ! empty( $this->HTTP_RAW_POST_DATA ) ) {
4934
			$this->rest_authentication_status = new WP_Error(
4935
				'rest_invalid_request',
4936
				__( 'This request method does not support body parameters.', 'jetpack' ),
4937
				array( 'status' => 400 )
4938
			);
4939
			return null;
4940
		}
4941
4942
		if ( ! empty( $_SERVER['CONTENT_TYPE'] ) ) {
4943
			$content_type = $_SERVER['CONTENT_TYPE'];
4944
		} elseif ( ! empty( $_SERVER['HTTP_CONTENT_TYPE'] ) ) {
4945
			$content_type = $_SERVER['HTTP_CONTENT_TYPE'];
4946
		}
4947
4948
		if (
4949
			isset( $content_type ) &&
4950
			$content_type !== 'application/x-www-form-urlencoded' &&
4951
			$content_type !== 'application/json'
4952
		) {
4953
			$this->rest_authentication_status = new WP_Error(
4954
				'rest_invalid_request',
4955
				__( 'This Content-Type is not supported.', 'jetpack' ),
4956
				array( 'status' => 400 )
4957
			);
4958
			return null;
4959
		}
4960
4961
		$verified = $this->verify_xml_rpc_signature();
4962
4963
		if ( is_wp_error( $verified ) ) {
4964
			$this->rest_authentication_status = $verified;
4965
			return null;
4966
		}
4967
4968
		if (
4969
			$verified &&
4970
			isset( $verified['type'] ) &&
4971
			'user' === $verified['type'] &&
4972
			! empty( $verified['user_id'] )
4973
		) {
4974
			// Authentication successful.
4975
			$this->rest_authentication_status = true;
4976
			return $verified['user_id'];
4977
		}
4978
4979
		// Something else went wrong.  Probably a signature error.
4980
		$this->rest_authentication_status = new WP_Error(
4981
			'rest_invalid_signature',
4982
			__( 'The request is not signed correctly.', 'jetpack' ),
4983
			array( 'status' => 400 )
4984
		);
4985
		return null;
4986
	}
4987
4988
	/**
4989
	 * Report authentication status to the WP REST API.
4990
	 *
4991
	 * @param  WP_Error|mixed $result Error from another authentication handler, null if we should handle it, or another value if not
4992
	 * @return WP_Error|boolean|null {@see WP_JSON_Server::check_authentication}
4993
	 */
4994
	public function wp_rest_authentication_errors( $value ) {
4995
		if ( $value !== null ) {
4996
			return $value;
4997
		}
4998
		return $this->rest_authentication_status;
4999
	}
5000
5001
	function add_nonce( $timestamp, $nonce ) {
5002
		global $wpdb;
5003
		static $nonces_used_this_request = array();
5004
5005
		if ( isset( $nonces_used_this_request["$timestamp:$nonce"] ) ) {
5006
			return $nonces_used_this_request["$timestamp:$nonce"];
5007
		}
5008
5009
		// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce
5010
		$timestamp = (int) $timestamp;
5011
		$nonce     = esc_sql( $nonce );
5012
5013
		// Raw query so we can avoid races: add_option will also update
5014
		$show_errors = $wpdb->show_errors( false );
5015
5016
		$old_nonce = $wpdb->get_row(
5017
			$wpdb->prepare( "SELECT * FROM `$wpdb->options` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
5018
		);
5019
5020
		if ( is_null( $old_nonce ) ) {
5021
			$return = $wpdb->query(
5022
				$wpdb->prepare(
5023
					"INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
5024
					"jetpack_nonce_{$timestamp}_{$nonce}",
5025
					time(),
5026
					'no'
5027
				)
5028
			);
5029
		} else {
5030
			$return = false;
5031
		}
5032
5033
		$wpdb->show_errors( $show_errors );
5034
5035
		$nonces_used_this_request["$timestamp:$nonce"] = $return;
5036
5037
		return $return;
5038
	}
5039
5040
	/**
5041
	 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths since it is passed by reference to various methods.
5042
	 * Capture it here so we can verify the signature later.
5043
	 */
5044
	function xmlrpc_methods( $methods ) {
5045
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
5046
		return $methods;
5047
	}
5048
5049
	function public_xmlrpc_methods( $methods ) {
5050
		if ( array_key_exists( 'wp.getOptions', $methods ) ) {
5051
			$methods['wp.getOptions'] = array( $this, 'jetpack_getOptions' );
5052
		}
5053
		return $methods;
5054
	}
5055
5056
	function jetpack_getOptions( $args ) {
5057
		global $wp_xmlrpc_server;
5058
5059
		$wp_xmlrpc_server->escape( $args );
5060
5061
		$username	= $args[1];
5062
		$password	= $args[2];
5063
5064
		if ( !$user = $wp_xmlrpc_server->login($username, $password) ) {
5065
			return $wp_xmlrpc_server->error;
5066
		}
5067
5068
		$options = array();
5069
		$user_data = $this->get_connected_user_data();
5070
		if ( is_array( $user_data ) ) {
5071
			$options['jetpack_user_id'] = array(
5072
				'desc'          => __( 'The WP.com user ID of the connected user', 'jetpack' ),
5073
				'readonly'      => true,
5074
				'value'         => $user_data['ID'],
5075
			);
5076
			$options['jetpack_user_login'] = array(
5077
				'desc'          => __( 'The WP.com username of the connected user', 'jetpack' ),
5078
				'readonly'      => true,
5079
				'value'         => $user_data['login'],
5080
			);
5081
			$options['jetpack_user_email'] = array(
5082
				'desc'          => __( 'The WP.com user email of the connected user', 'jetpack' ),
5083
				'readonly'      => true,
5084
				'value'         => $user_data['email'],
5085
			);
5086
			$options['jetpack_user_site_count'] = array(
5087
				'desc'          => __( 'The number of sites of the connected WP.com user', 'jetpack' ),
5088
				'readonly'      => true,
5089
				'value'         => $user_data['site_count'],
5090
			);
5091
		}
5092
		$wp_xmlrpc_server->blog_options = array_merge( $wp_xmlrpc_server->blog_options, $options );
5093
		$args = stripslashes_deep( $args );
5094
		return $wp_xmlrpc_server->wp_getOptions( $args );
5095
	}
5096
5097
	function xmlrpc_options( $options ) {
5098
		$jetpack_client_id = false;
5099
		if ( self::is_active() ) {
5100
			$jetpack_client_id = Jetpack_Options::get_option( 'id' );
5101
		}
5102
		$options['jetpack_version'] = array(
5103
				'desc'          => __( 'Jetpack Plugin Version', 'jetpack' ),
5104
				'readonly'      => true,
5105
				'value'         => JETPACK__VERSION,
5106
		);
5107
5108
		$options['jetpack_client_id'] = array(
5109
				'desc'          => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack' ),
5110
				'readonly'      => true,
5111
				'value'         => $jetpack_client_id,
5112
		);
5113
		return $options;
5114
	}
5115
5116
	public static function clean_nonces( $all = false ) {
5117
		global $wpdb;
5118
5119
		$sql = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
5120
		$sql_args = array( $wpdb->esc_like( 'jetpack_nonce_' ) . '%' );
5121
5122
		if ( true !== $all ) {
5123
			$sql .= ' AND CAST( `option_value` AS UNSIGNED ) < %d';
5124
			$sql_args[] = time() - 3600;
5125
		}
5126
5127
		$sql .= ' ORDER BY `option_id` LIMIT 100';
5128
5129
		$sql = $wpdb->prepare( $sql, $sql_args );
5130
5131
		for ( $i = 0; $i < 1000; $i++ ) {
5132
			if ( ! $wpdb->query( $sql ) ) {
5133
				break;
5134
			}
5135
		}
5136
	}
5137
5138
	/**
5139
	 * State is passed via cookies from one request to the next, but never to subsequent requests.
5140
	 * SET: state( $key, $value );
5141
	 * GET: $value = state( $key );
5142
	 *
5143
	 * @param string $key
5144
	 * @param string $value
5145
	 * @param bool $restate private
5146
	 */
5147
	public static function state( $key = null, $value = null, $restate = false ) {
5148
		static $state = array();
5149
		static $path, $domain;
5150
		if ( ! isset( $path ) ) {
5151
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
5152
			$admin_url = Jetpack::admin_url();
5153
			$bits      = parse_url( $admin_url );
5154
5155
			if ( is_array( $bits ) ) {
5156
				$path   = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
5157
				$domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
5158
			} else {
5159
				$path = $domain = null;
5160
			}
5161
		}
5162
5163
		// Extract state from cookies and delete cookies
5164
		if ( isset( $_COOKIE[ 'jetpackState' ] ) && is_array( $_COOKIE[ 'jetpackState' ] ) ) {
5165
			$yum = $_COOKIE[ 'jetpackState' ];
5166
			unset( $_COOKIE[ 'jetpackState' ] );
5167
			foreach ( $yum as $k => $v ) {
5168
				if ( strlen( $v ) )
5169
					$state[ $k ] = $v;
5170
				setcookie( "jetpackState[$k]", false, 0, $path, $domain );
5171
			}
5172
		}
5173
5174
		if ( $restate ) {
5175
			foreach ( $state as $k => $v ) {
5176
				setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
5177
			}
5178
			return;
5179
		}
5180
5181
		// Get a state variable
5182
		if ( isset( $key ) && ! isset( $value ) ) {
5183
			if ( array_key_exists( $key, $state ) )
5184
				return $state[ $key ];
5185
			return null;
5186
		}
5187
5188
		// Set a state variable
5189
		if ( isset ( $key ) && isset( $value ) ) {
5190
			if( is_array( $value ) && isset( $value[0] ) ) {
5191
				$value = $value[0];
5192
			}
5193
			$state[ $key ] = $value;
5194
			setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
5195
		}
5196
	}
5197
5198
	public static function restate() {
5199
		Jetpack::state( null, null, true );
5200
	}
5201
5202
	public static function check_privacy( $file ) {
5203
		static $is_site_publicly_accessible = null;
5204
5205
		if ( is_null( $is_site_publicly_accessible ) ) {
5206
			$is_site_publicly_accessible = false;
5207
5208
			Jetpack::load_xml_rpc_client();
5209
			$rpc = new Jetpack_IXR_Client();
5210
5211
			$success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
5212
			if ( $success ) {
5213
				$response = $rpc->getResponse();
5214
				if ( $response ) {
5215
					$is_site_publicly_accessible = true;
5216
				}
5217
			}
5218
5219
			Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
5220
		}
5221
5222
		if ( $is_site_publicly_accessible ) {
5223
			return;
5224
		}
5225
5226
		$module_slug = self::get_module_slug( $file );
5227
5228
		$privacy_checks = Jetpack::state( 'privacy_checks' );
5229
		if ( ! $privacy_checks ) {
5230
			$privacy_checks = $module_slug;
5231
		} else {
5232
			$privacy_checks .= ",$module_slug";
5233
		}
5234
5235
		Jetpack::state( 'privacy_checks', $privacy_checks );
5236
	}
5237
5238
	/**
5239
	 * Helper method for multicall XMLRPC.
5240
	 */
5241
	public static function xmlrpc_async_call() {
5242
		global $blog_id;
5243
		static $clients = array();
5244
5245
		$client_blog_id = is_multisite() ? $blog_id : 0;
5246
5247
		if ( ! isset( $clients[$client_blog_id] ) ) {
5248
			Jetpack::load_xml_rpc_client();
5249
			$clients[$client_blog_id] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => JETPACK_MASTER_USER, ) );
5250
			if ( function_exists( 'ignore_user_abort' ) ) {
5251
				ignore_user_abort( true );
5252
			}
5253
			add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
5254
		}
5255
5256
		$args = func_get_args();
5257
5258
		if ( ! empty( $args[0] ) ) {
5259
			call_user_func_array( array( $clients[$client_blog_id], 'addCall' ), $args );
5260
		} elseif ( is_multisite() ) {
5261
			foreach ( $clients as $client_blog_id => $client ) {
5262
				if ( ! $client_blog_id || empty( $client->calls ) ) {
5263
					continue;
5264
				}
5265
5266
				$switch_success = switch_to_blog( $client_blog_id, true );
5267
				if ( ! $switch_success ) {
5268
					continue;
5269
				}
5270
5271
				flush();
5272
				$client->query();
5273
5274
				restore_current_blog();
5275
			}
5276
		} else {
5277
			if ( isset( $clients[0] ) && ! empty( $clients[0]->calls ) ) {
5278
				flush();
5279
				$clients[0]->query();
5280
			}
5281
		}
5282
	}
5283
5284
	public static function staticize_subdomain( $url ) {
5285
5286
		// Extract hostname from URL
5287
		$host = parse_url( $url, PHP_URL_HOST );
5288
5289
		// Explode hostname on '.'
5290
		$exploded_host = explode( '.', $host );
5291
5292
		// Retrieve the name and TLD
5293
		if ( count( $exploded_host ) > 1 ) {
5294
			$name = $exploded_host[ count( $exploded_host ) - 2 ];
5295
			$tld = $exploded_host[ count( $exploded_host ) - 1 ];
5296
			// Rebuild domain excluding subdomains
5297
			$domain = $name . '.' . $tld;
5298
		} else {
5299
			$domain = $host;
5300
		}
5301
		// Array of Automattic domains
5302
		$domain_whitelist = array( 'wordpress.com', 'wp.com' );
5303
5304
		// Return $url if not an Automattic domain
5305
		if ( ! in_array( $domain, $domain_whitelist ) ) {
5306
			return $url;
5307
		}
5308
5309
		if ( is_ssl() ) {
5310
			return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
5311
		}
5312
5313
		srand( crc32( basename( $url ) ) );
5314
		$static_counter = rand( 0, 2 );
5315
		srand(); // this resets everything that relies on this, like array_rand() and shuffle()
5316
5317
		return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
5318
	}
5319
5320
/* JSON API Authorization */
5321
5322
	/**
5323
	 * Handles the login action for Authorizing the JSON API
5324
	 */
5325
	function login_form_json_api_authorization() {
5326
		$this->verify_json_api_authorization_request();
5327
5328
		add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
5329
5330
		add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
5331
		add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5332
		add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
5333
	}
5334
5335
	// Make sure the login form is POSTed to the signed URL so we can reverify the request
5336
	function post_login_form_to_signed_url( $url, $path, $scheme ) {
5337
		if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
5338
			return $url;
5339
		}
5340
5341
		$parsed_url = parse_url( $url );
5342
		$url = strtok( $url, '?' );
5343
		$url = "$url?{$_SERVER['QUERY_STRING']}";
5344
		if ( ! empty( $parsed_url['query'] ) )
5345
			$url .= "&{$parsed_url['query']}";
5346
5347
		return $url;
5348
	}
5349
5350
	// Make sure the POSTed request is handled by the same action
5351
	function preserve_action_in_login_form_for_json_api_authorization() {
5352
		echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
5353
		echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) . "' />\n";
5354
	}
5355
5356
	// If someone logs in to approve API access, store the Access Code in usermeta
5357
	function store_json_api_authorization_token( $user_login, $user ) {
5358
		add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5359
		add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
5360
		$token = wp_generate_password( 32, false );
5361
		update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5362
	}
5363
5364
	// Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
5365
	function allow_wpcom_public_api_domain( $domains ) {
5366
		$domains[] = 'public-api.wordpress.com';
5367
		return $domains;
5368
	}
5369
5370
	// Add the Access Code details to the public-api.wordpress.com redirect
5371
	function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5372
		return add_query_arg(
5373
			urlencode_deep(
5374
				array(
5375
					'jetpack-code'    => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5376
					'jetpack-user-id' => (int) $user->ID,
5377
					'jetpack-state'   => $this->json_api_authorization_request['state'],
5378
				)
5379
			),
5380
			$redirect_to
5381
		);
5382
	}
5383
5384
5385
	/**
5386
	 * Verifies the request by checking the signature
5387
	 *
5388
	 * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
5389
	 * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
5390
	 *
5391
	 * @param null|array $environment
5392
	 */
5393
	function verify_json_api_authorization_request( $environment = null ) {
5394
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
5395
5396
		$environment = is_null( $environment )
5397
			? $_REQUEST
5398
			: $environment;
5399
5400
		list( $envToken, $envVersion, $envUserId ) = explode( ':', $environment['token'] );
5401
		$token = Jetpack_Data::get_access_token( $envUserId );
5402
		if ( ! $token || empty( $token->secret ) ) {
5403
			wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.' , 'jetpack' ) );
5404
		}
5405
5406
		$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' );
5407
5408
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5409
5410
		if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
5411
			$signature = $jetpack_signature->sign_request(
5412
				$environment['token'],
5413
				$environment['timestamp'],
5414
				$environment['nonce'],
5415
				'',
5416
				'GET',
5417
				$environment['jetpack_json_api_original_query'],
5418
				null,
5419
				true
5420
			);
5421
		} else {
5422
			$signature = $jetpack_signature->sign_current_request( array( 'body' => null, 'method' => 'GET' ) );
5423
		}
5424
5425
		if ( ! $signature ) {
5426
			wp_die( $die_error );
5427
		} else if ( is_wp_error( $signature ) ) {
5428
			wp_die( $die_error );
5429
		} else if ( ! hash_equals( $signature, $environment['signature'] ) ) {
5430
			if ( is_ssl() ) {
5431
				// 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
5432
				$signature = $jetpack_signature->sign_current_request( array( 'scheme' => 'http', 'body' => null, 'method' => 'GET' ) );
5433
				if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
5434
					wp_die( $die_error );
5435
				}
5436
			} else {
5437
				wp_die( $die_error );
5438
			}
5439
		}
5440
5441
		$timestamp = (int) $environment['timestamp'];
5442
		$nonce     = stripslashes( (string) $environment['nonce'] );
5443
5444
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
5445
			// De-nonce the nonce, at least for 5 minutes.
5446
			// 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)
5447
			$old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5448
			if ( $old_nonce_time < time() - 300 ) {
5449
				wp_die( __( 'The authorization process expired.  Please go back and try again.' , 'jetpack' ) );
5450
			}
5451
		}
5452
5453
		$data = json_decode( base64_decode( stripslashes( $environment['data'] ) ) );
5454
		$data_filters = array(
5455
			'state'        => 'opaque',
5456
			'client_id'    => 'int',
5457
			'client_title' => 'string',
5458
			'client_image' => 'url',
5459
		);
5460
5461
		foreach ( $data_filters as $key => $sanitation ) {
5462
			if ( ! isset( $data->$key ) ) {
5463
				wp_die( $die_error );
5464
			}
5465
5466
			switch ( $sanitation ) {
5467
			case 'int' :
5468
				$this->json_api_authorization_request[$key] = (int) $data->$key;
5469
				break;
5470
			case 'opaque' :
5471
				$this->json_api_authorization_request[$key] = (string) $data->$key;
5472
				break;
5473
			case 'string' :
5474
				$this->json_api_authorization_request[$key] = wp_kses( (string) $data->$key, array() );
5475
				break;
5476
			case 'url' :
5477
				$this->json_api_authorization_request[$key] = esc_url_raw( (string) $data->$key );
5478
				break;
5479
			}
5480
		}
5481
5482
		if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5483
			wp_die( $die_error );
5484
		}
5485
	}
5486
5487
	function login_message_json_api_authorization( $message ) {
5488
		return '<p class="message">' . sprintf(
5489
			esc_html__( '%s wants to access your site&#8217;s data.  Log in to authorize that access.' , 'jetpack' ),
5490
			'<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5491
		) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5492
	}
5493
5494
	/**
5495
	 * Get $content_width, but with a <s>twist</s> filter.
5496
	 */
5497
	public static function get_content_width() {
5498
		$content_width = isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : false;
5499
		/**
5500
		 * Filter the Content Width value.
5501
		 *
5502
		 * @since 2.2.3
5503
		 *
5504
		 * @param string $content_width Content Width value.
5505
		 */
5506
		return apply_filters( 'jetpack_content_width', $content_width );
5507
	}
5508
5509
	/**
5510
	 * Pings the WordPress.com Mirror Site for the specified options.
5511
	 *
5512
	 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
5513
	 *
5514
	 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
5515
	 */
5516
	public function get_cloud_site_options( $option_names ) {
5517
		$option_names = array_filter( (array) $option_names, 'is_string' );
5518
5519
		Jetpack::load_xml_rpc_client();
5520
		$xml = new Jetpack_IXR_Client( array( 'user_id' => JETPACK_MASTER_USER, ) );
5521
		$xml->query( 'jetpack.fetchSiteOptions', $option_names );
5522
		if ( $xml->isError() ) {
5523
			return array(
5524
				'error_code' => $xml->getErrorCode(),
5525
				'error_msg'  => $xml->getErrorMessage(),
5526
			);
5527
		}
5528
		$cloud_site_options = $xml->getResponse();
5529
5530
		return $cloud_site_options;
5531
	}
5532
5533
	/**
5534
	 * Checks if the site is currently in an identity crisis.
5535
	 *
5536
	 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5537
	 */
5538
	public static function check_identity_crisis() {
5539
		if ( ! Jetpack::is_active() || Jetpack::is_development_mode() || ! self::validate_sync_error_idc_option() ) {
5540
			return false;
5541
		}
5542
5543
		return Jetpack_Options::get_option( 'sync_error_idc' );
5544
	}
5545
5546
	/**
5547
	 * Checks whether the home and siteurl specifically are whitelisted
5548
	 * Written so that we don't have re-check $key and $value params every time
5549
	 * we want to check if this site is whitelisted, for example in footer.php
5550
	 *
5551
	 * @since  3.8.0
5552
	 * @return bool True = already whitelisted False = not whitelisted
5553
	 */
5554
	public static function is_staging_site() {
5555
		$is_staging = false;
5556
5557
		$known_staging = array(
5558
			'urls' => array(
5559
				'#\.staging\.wpengine\.com$#i', // WP Engine
5560
				'#\.staging\.kinsta\.com$#i',   // Kinsta.com
5561
				),
5562
			'constants' => array(
5563
				'IS_WPE_SNAPSHOT',      // WP Engine
5564
				'KINSTA_DEV_ENV',       // Kinsta.com
5565
				'WPSTAGECOACH_STAGING', // WP Stagecoach
5566
				'JETPACK_STAGING_MODE', // Generic
5567
				)
5568
			);
5569
		/**
5570
		 * Filters the flags of known staging sites.
5571
		 *
5572
		 * @since 3.9.0
5573
		 *
5574
		 * @param array $known_staging {
5575
		 *     An array of arrays that each are used to check if the current site is staging.
5576
		 *     @type array $urls      URLs of staging sites in regex to check against site_url.
5577
		 *     @type array $constants PHP constants of known staging/developement environments.
5578
		 *  }
5579
		 */
5580
		$known_staging = apply_filters( 'jetpack_known_staging', $known_staging );
5581
5582
		if ( isset( $known_staging['urls'] ) ) {
5583
			foreach ( $known_staging['urls'] as $url ){
5584
				if ( preg_match( $url, site_url() ) ) {
5585
					$is_staging = true;
5586
					break;
5587
				}
5588
			}
5589
		}
5590
5591
		if ( isset( $known_staging['constants'] ) ) {
5592
			foreach ( $known_staging['constants'] as $constant ) {
5593
				if ( defined( $constant ) && constant( $constant ) ) {
5594
					$is_staging = true;
5595
				}
5596
			}
5597
		}
5598
5599
		// Last, let's check if sync is erroring due to an IDC. If so, set the site to staging mode.
5600
		if ( ! $is_staging && self::validate_sync_error_idc_option() ) {
5601
			$is_staging = true;
5602
		}
5603
5604
		/**
5605
		 * Filters is_staging_site check.
5606
		 *
5607
		 * @since 3.9.0
5608
		 *
5609
		 * @param bool $is_staging If the current site is a staging site.
5610
		 */
5611
		return apply_filters( 'jetpack_is_staging_site', $is_staging );
5612
	}
5613
5614
	/**
5615
	 * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
5616
	 *
5617
	 * @return bool
5618
	 */
5619
	public static function validate_sync_error_idc_option() {
5620
		$is_valid = false;
5621
5622
		$idc_allowed = get_transient( 'jetpack_idc_allowed' );
5623
		if ( false === $idc_allowed ) {
5624
			$response = wp_remote_get( 'https://jetpack.com/is-idc-allowed/' );
5625
			if ( 200 === (int) wp_remote_retrieve_response_code( $response ) ) {
5626
				$json = json_decode( wp_remote_retrieve_body( $response ) );
5627
				$idc_allowed = isset( $json, $json->result ) && $json->result ? '1' : '0';
5628
				$transient_duration = HOUR_IN_SECONDS;
5629
			} else {
5630
				// If the request failed for some reason, then assume IDC is allowed and set shorter transient.
5631
				$idc_allowed = '1';
5632
				$transient_duration = 5 * MINUTE_IN_SECONDS;
5633
			}
5634
5635
			set_transient( 'jetpack_idc_allowed', $idc_allowed, $transient_duration );
5636
		}
5637
5638
		// Is the site opted in and does the stored sync_error_idc option match what we now generate?
5639
		$sync_error = Jetpack_Options::get_option( 'sync_error_idc' );
5640
		$local_options = self::get_sync_error_idc_option();
5641
		if ( $idc_allowed && $sync_error && self::sync_idc_optin() ) {
5642
			if ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) {
5643
				$is_valid = true;
5644
			}
5645
		}
5646
5647
		/**
5648
		 * Filters whether the sync_error_idc option is valid.
5649
		 *
5650
		 * @since 4.4.0
5651
		 *
5652
		 * @param bool $is_valid If the sync_error_idc is valid or not.
5653
		 */
5654
		$is_valid = (bool) apply_filters( 'jetpack_sync_error_idc_validation', $is_valid );
5655
5656
		if ( ! $idc_allowed || ( ! $is_valid && $sync_error ) ) {
5657
			// Since the option exists, and did not validate, delete it
5658
			Jetpack_Options::delete_option( 'sync_error_idc' );
5659
		}
5660
5661
		return $is_valid;
5662
	}
5663
5664
	/**
5665
	 * Normalizes a url by doing three things:
5666
	 *  - Strips protocol
5667
	 *  - Strips www
5668
	 *  - Adds a trailing slash
5669
	 *
5670
	 * @since 4.4.0
5671
	 * @param string $url
5672
	 * @return WP_Error|string
5673
	 */
5674
	public static function normalize_url_protocol_agnostic( $url ) {
5675
		$parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
5676
		if ( ! $parsed_url ) {
5677
			return new WP_Error( 'cannot_parse_url', sprintf( esc_html__( 'Cannot parse URL %s', 'jetpack' ), $url ) );
5678
		}
5679
5680
		// Strip www and protocols
5681
		$url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
5682
		return $url;
5683
	}
5684
5685
	/**
5686
	 * Gets the value that is to be saved in the jetpack_sync_error_idc option.
5687
	 *
5688
	 * @since 4.4.0
5689
	 *
5690
	 * @param array $response
5691
	 * @return array Array of the local urls, wpcom urls, and error code
5692
	 */
5693
	public static function get_sync_error_idc_option( $response = array() ) {
5694
		$local_options = array(
5695
			'home' => get_home_url(),
5696
			'siteurl' => get_site_url(),
5697
		);
5698
5699
		$options = array_merge( $local_options, $response );
5700
5701
		$returned_values = array();
5702
		foreach( $options as $key => $option ) {
5703
			if ( 'error_code' === $key ) {
5704
				$returned_values[ $key ] = $option;
5705
				continue;
5706
			}
5707
5708
			if ( is_wp_error( $normalized_url = self::normalize_url_protocol_agnostic( $option ) ) ) {
5709
				continue;
5710
			}
5711
5712
			$returned_values[ $key ] = $normalized_url;
5713
		}
5714
5715
		return $returned_values;
5716
	}
5717
5718
	/**
5719
	 * Returns the value of the jetpack_sync_idc_optin filter, or constant.
5720
	 * If set to true, the site will be put into staging mode.
5721
	 *
5722
	 * @since 4.3.2
5723
	 * @return bool
5724
	 */
5725
	public static function sync_idc_optin() {
5726
		if ( Jetpack_Constants::is_defined( 'JETPACK_SYNC_IDC_OPTIN' ) ) {
5727
			$default = Jetpack_Constants::get_constant( 'JETPACK_SYNC_IDC_OPTIN' );
5728
		} else {
5729
			$default = ! Jetpack_Constants::is_defined( 'SUNRISE' ) && ! is_multisite();
5730
		}
5731
5732
		/**
5733
		 * Allows sites to optin to IDC mitigation which blocks the site from syncing to WordPress.com when the home
5734
		 * URL or site URL do not match what WordPress.com expects. The default value is either false, or the value of
5735
		 * JETPACK_SYNC_IDC_OPTIN constant if set.
5736
		 *
5737
		 * @since 4.3.2
5738
		 *
5739
		 * @param bool $default Whether the site is opted in to IDC mitigation.
5740
		 */
5741
		return (bool) apply_filters( 'jetpack_sync_idc_optin', $default );
5742
	}
5743
5744
	/**
5745
	 * Maybe Use a .min.css stylesheet, maybe not.
5746
	 *
5747
	 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
5748
	 */
5749
	public static function maybe_min_asset( $url, $path, $plugin ) {
5750
		// Short out on things trying to find actual paths.
5751
		if ( ! $path || empty( $plugin ) ) {
5752
			return $url;
5753
		}
5754
5755
		// Strip out the abspath.
5756
		$base = dirname( plugin_basename( $plugin ) );
5757
5758
		// Short out on non-Jetpack assets.
5759
		if ( 'jetpack/' !== substr( $base, 0, 8 ) ) {
5760
			return $url;
5761
		}
5762
5763
		// File name parsing.
5764
		$file              = "{$base}/{$path}";
5765
		$full_path         = JETPACK__PLUGIN_DIR . substr( $file, 8 );
5766
		$file_name         = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
5767
		$file_name_parts_r = array_reverse( explode( '.', $file_name ) );
5768
		$extension         = array_shift( $file_name_parts_r );
5769
5770
		if ( in_array( strtolower( $extension ), array( 'css', 'js' ) ) ) {
5771
			// Already pointing at the minified version.
5772
			if ( 'min' === $file_name_parts_r[0] ) {
5773
				return $url;
5774
			}
5775
5776
			$min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
5777
			if ( file_exists( $min_full_path ) ) {
5778
				$url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
5779
			}
5780
		}
5781
5782
		return $url;
5783
	}
5784
5785
	/**
5786
	 * Maybe inlines a stylesheet.
5787
	 *
5788
	 * If you'd like to inline a stylesheet instead of printing a link to it,
5789
	 * wp_style_add_data( 'handle', 'jetpack-inline', true );
5790
	 *
5791
	 * Attached to `style_loader_tag` filter.
5792
	 *
5793
	 * @param string $tag The tag that would link to the external asset.
5794
	 * @param string $handle The registered handle of the script in question.
5795
	 *
5796
	 * @return string
5797
	 */
5798
	public static function maybe_inline_style( $tag, $handle ) {
5799
		global $wp_styles;
5800
		$item = $wp_styles->registered[ $handle ];
5801
5802
		if ( ! isset( $item->extra['jetpack-inline'] ) || ! $item->extra['jetpack-inline'] ) {
5803
			return $tag;
5804
		}
5805
5806
		if ( preg_match( '# href=\'([^\']+)\' #i', $tag, $matches ) ) {
5807
			$href = $matches[1];
5808
			// Strip off query string
5809
			if ( $pos = strpos( $href, '?' ) ) {
5810
				$href = substr( $href, 0, $pos );
5811
			}
5812
			// Strip off fragment
5813
			if ( $pos = strpos( $href, '#' ) ) {
5814
				$href = substr( $href, 0, $pos );
5815
			}
5816
		} else {
5817
			return $tag;
5818
		}
5819
5820
		$plugins_dir = plugin_dir_url( JETPACK__PLUGIN_FILE );
5821
		if ( $plugins_dir !== substr( $href, 0, strlen( $plugins_dir ) ) ) {
5822
			return $tag;
5823
		}
5824
5825
		// If this stylesheet has a RTL version, and the RTL version replaces normal...
5826
		if ( isset( $item->extra['rtl'] ) && 'replace' === $item->extra['rtl'] && is_rtl() ) {
5827
			// And this isn't the pass that actually deals with the RTL version...
5828
			if ( false === strpos( $tag, " id='$handle-rtl-css' " ) ) {
5829
				// Short out, as the RTL version will deal with it in a moment.
5830
				return $tag;
5831
			}
5832
		}
5833
5834
		$file = JETPACK__PLUGIN_DIR . substr( $href, strlen( $plugins_dir ) );
5835
		$css  = Jetpack::absolutize_css_urls( file_get_contents( $file ), $href );
5836
		if ( $css ) {
5837
			$tag = "<!-- Inline {$item->handle} -->\r\n";
5838
			if ( empty( $item->extra['after'] ) ) {
5839
				wp_add_inline_style( $handle, $css );
5840
			} else {
5841
				array_unshift( $item->extra['after'], $css );
5842
				wp_style_add_data( $handle, 'after', $item->extra['after'] );
5843
			}
5844
		}
5845
5846
		return $tag;
5847
	}
5848
5849
	/**
5850
	 * Loads a view file from the views
5851
	 *
5852
	 * Data passed in with the $data parameter will be available in the
5853
	 * template file as $data['value']
5854
	 *
5855
	 * @param string $template - Template file to load
5856
	 * @param array $data - Any data to pass along to the template
5857
	 * @return boolean - If template file was found
5858
	 **/
5859
	public function load_view( $template, $data = array() ) {
5860
		$views_dir = JETPACK__PLUGIN_DIR . 'views/';
5861
5862
		if( file_exists( $views_dir . $template ) ) {
5863
			require_once( $views_dir . $template );
5864
			return true;
5865
		}
5866
5867
		error_log( "Jetpack: Unable to find view file $views_dir$template" );
5868
		return false;
5869
	}
5870
5871
	/**
5872
	 * Throws warnings for deprecated hooks to be removed from Jetpack
5873
	 */
5874
	public function deprecated_hooks() {
5875
		global $wp_filter;
5876
5877
		/*
5878
		 * Format:
5879
		 * deprecated_filter_name => replacement_name
5880
		 *
5881
		 * If there is no replacement, use null for replacement_name
5882
		 */
5883
		$deprecated_list = array(
5884
			'jetpack_bail_on_shortcode'                              => 'jetpack_shortcodes_to_include',
5885
			'wpl_sharing_2014_1'                                     => null,
5886
			'jetpack-tools-to-include'                               => 'jetpack_tools_to_include',
5887
			'jetpack_identity_crisis_options_to_check'               => null,
5888
			'update_option_jetpack_single_user_site'                 => null,
5889
			'audio_player_default_colors'                            => null,
5890
			'add_option_jetpack_featured_images_enabled'             => null,
5891
			'add_option_jetpack_update_details'                      => null,
5892
			'add_option_jetpack_updates'                             => null,
5893
			'add_option_jetpack_network_name'                        => null,
5894
			'add_option_jetpack_network_allow_new_registrations'     => null,
5895
			'add_option_jetpack_network_add_new_users'               => null,
5896
			'add_option_jetpack_network_site_upload_space'           => null,
5897
			'add_option_jetpack_network_upload_file_types'           => null,
5898
			'add_option_jetpack_network_enable_administration_menus' => null,
5899
			'add_option_jetpack_is_multi_site'                       => null,
5900
			'add_option_jetpack_is_main_network'                     => null,
5901
			'add_option_jetpack_main_network_site'                   => null,
5902
			'jetpack_sync_all_registered_options'                    => null,
5903
			'jetpack_has_identity_crisis'                            => 'jetpack_sync_error_idc_validation',
5904
			'jetpack_is_post_mailable'                               => null,
5905
		);
5906
5907
		// This is a silly loop depth. Better way?
5908
		foreach( $deprecated_list AS $hook => $hook_alt ) {
5909
			if ( has_action( $hook ) ) {
5910
				foreach( $wp_filter[ $hook ] AS $func => $values ) {
5911
					foreach( $values AS $hooked ) {
5912
						if ( is_callable( $hooked['function'] ) ) {
5913
							$function_name = 'an anonymous function';
5914
						} else {
5915
							$function_name = $hooked['function'];
5916
						}
5917
						_deprecated_function( $hook . ' used for ' . $function_name, null, $hook_alt );
5918
					}
5919
				}
5920
			}
5921
		}
5922
	}
5923
5924
	/**
5925
	 * Converts any url in a stylesheet, to the correct absolute url.
5926
	 *
5927
	 * Considerations:
5928
	 *  - Normal, relative URLs     `feh.png`
5929
	 *  - Data URLs                 `data:image/gif;base64,eh129ehiuehjdhsa==`
5930
	 *  - Schema-agnostic URLs      `//domain.com/feh.png`
5931
	 *  - Absolute URLs             `http://domain.com/feh.png`
5932
	 *  - Domain root relative URLs `/feh.png`
5933
	 *
5934
	 * @param $css string: The raw CSS -- should be read in directly from the file.
5935
	 * @param $css_file_url : The URL that the file can be accessed at, for calculating paths from.
5936
	 *
5937
	 * @return mixed|string
5938
	 */
5939
	public static function absolutize_css_urls( $css, $css_file_url ) {
5940
		$pattern = '#url\((?P<path>[^)]*)\)#i';
5941
		$css_dir = dirname( $css_file_url );
5942
		$p       = parse_url( $css_dir );
5943
		$domain  = sprintf(
5944
					'%1$s//%2$s%3$s%4$s',
5945
					isset( $p['scheme'] )           ? "{$p['scheme']}:" : '',
5946
					isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
5947
					$p['host'],
5948
					isset( $p['port'] )             ? ":{$p['port']}" : ''
5949
				);
5950
5951
		if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
5952
			$find = $replace = array();
5953
			foreach ( $matches as $match ) {
5954
				$url = trim( $match['path'], "'\" \t" );
5955
5956
				// If this is a data url, we don't want to mess with it.
5957
				if ( 'data:' === substr( $url, 0, 5 ) ) {
5958
					continue;
5959
				}
5960
5961
				// If this is an absolute or protocol-agnostic url,
5962
				// we don't want to mess with it.
5963
				if ( preg_match( '#^(https?:)?//#i', $url ) ) {
5964
					continue;
5965
				}
5966
5967
				switch ( substr( $url, 0, 1 ) ) {
5968
					case '/':
5969
						$absolute = $domain . $url;
5970
						break;
5971
					default:
5972
						$absolute = $css_dir . '/' . $url;
5973
				}
5974
5975
				$find[]    = $match[0];
5976
				$replace[] = sprintf( 'url("%s")', $absolute );
5977
			}
5978
			$css = str_replace( $find, $replace, $css );
5979
		}
5980
5981
		return $css;
5982
	}
5983
5984
	/**
5985
	 * This methods removes all of the registered css files on the front end
5986
	 * from Jetpack in favor of using a single file. In effect "imploding"
5987
	 * all the files into one file.
5988
	 *
5989
	 * Pros:
5990
	 * - Uses only ONE css asset connection instead of 15
5991
	 * - Saves a minimum of 56k
5992
	 * - Reduces server load
5993
	 * - Reduces time to first painted byte
5994
	 *
5995
	 * Cons:
5996
	 * - Loads css for ALL modules. However all selectors are prefixed so it
5997
	 *		should not cause any issues with themes.
5998
	 * - Plugins/themes dequeuing styles no longer do anything. See
5999
	 *		jetpack_implode_frontend_css filter for a workaround
6000
	 *
6001
	 * For some situations developers may wish to disable css imploding and
6002
	 * instead operate in legacy mode where each file loads seperately and
6003
	 * can be edited individually or dequeued. This can be accomplished with
6004
	 * the following line:
6005
	 *
6006
	 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6007
	 *
6008
	 * @since 3.2
6009
	 **/
6010
	public function implode_frontend_css( $travis_test = false ) {
6011
		$do_implode = true;
6012
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6013
			$do_implode = false;
6014
		}
6015
6016
		/**
6017
		 * Allow CSS to be concatenated into a single jetpack.css file.
6018
		 *
6019
		 * @since 3.2.0
6020
		 *
6021
		 * @param bool $do_implode Should CSS be concatenated? Default to true.
6022
		 */
6023
		$do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6024
6025
		// Do not use the imploded file when default behaviour was altered through the filter
6026
		if ( ! $do_implode ) {
6027
			return;
6028
		}
6029
6030
		// We do not want to use the imploded file in dev mode, or if not connected
6031
		if ( Jetpack::is_development_mode() || ! self::is_active() ) {
6032
			if ( ! $travis_test ) {
6033
				return;
6034
			}
6035
		}
6036
6037
		// Do not use the imploded file if sharing css was dequeued via the sharing settings screen
6038
		if ( get_option( 'sharedaddy_disable_resources' ) ) {
6039
			return;
6040
		}
6041
6042
		/*
6043
		 * Now we assume Jetpack is connected and able to serve the single
6044
		 * file.
6045
		 *
6046
		 * In the future there will be a check here to serve the file locally
6047
		 * or potentially from the Jetpack CDN
6048
		 *
6049
		 * For now:
6050
		 * - Enqueue a single imploded css file
6051
		 * - Zero out the style_loader_tag for the bundled ones
6052
		 * - Be happy, drink scotch
6053
		 */
6054
6055
		add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6056
6057
		$version = Jetpack::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6058
6059
		wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6060
		wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6061
	}
6062
6063
	function concat_remove_style_loader_tag( $tag, $handle ) {
6064
		if ( in_array( $handle, $this->concatenated_style_handles ) ) {
6065
			$tag = '';
6066
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6067
				$tag = "<!-- `" . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6068
			}
6069
		}
6070
6071
		return $tag;
6072
	}
6073
6074
	/*
6075
	 * Check the heartbeat data
6076
	 *
6077
	 * Organizes the heartbeat data by severity.  For example, if the site
6078
	 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
6079
	 *
6080
	 * Data will be added to "caution" array, if it either:
6081
	 *  - Out of date Jetpack version
6082
	 *  - Out of date WP version
6083
	 *  - Out of date PHP version
6084
	 *
6085
	 * $return array $filtered_data
6086
	 */
6087
	public static function jetpack_check_heartbeat_data() {
6088
		$raw_data = Jetpack_Heartbeat::generate_stats_array();
6089
6090
		$good    = array();
6091
		$caution = array();
6092
		$bad     = array();
6093
6094
		foreach ( $raw_data as $stat => $value ) {
6095
6096
			// Check jetpack version
6097
			if ( 'version' == $stat ) {
6098
				if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
6099
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__VERSION;
6100
					continue;
6101
				}
6102
			}
6103
6104
			// Check WP version
6105
			if ( 'wp-version' == $stat ) {
6106
				if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
6107
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__MINIMUM_WP_VERSION;
6108
					continue;
6109
				}
6110
			}
6111
6112
			// Check PHP version
6113
			if ( 'php-version' == $stat ) {
6114
				if ( version_compare( PHP_VERSION, '5.2.4', '<' ) ) {
6115
					$caution[ $stat ] = $value . " - min supported is 5.2.4";
6116
					continue;
6117
				}
6118
			}
6119
6120
			// Check ID crisis
6121
			if ( 'identitycrisis' == $stat ) {
6122
				if ( 'yes' == $value ) {
6123
					$bad[ $stat ] = $value;
6124
					continue;
6125
				}
6126
			}
6127
6128
			// The rest are good :)
6129
			$good[ $stat ] = $value;
6130
		}
6131
6132
		$filtered_data = array(
6133
			'good'    => $good,
6134
			'caution' => $caution,
6135
			'bad'     => $bad
6136
		);
6137
6138
		return $filtered_data;
6139
	}
6140
6141
6142
	/*
6143
	 * This method is used to organize all options that can be reset
6144
	 * without disconnecting Jetpack.
6145
	 *
6146
	 * It is used in class.jetpack-cli.php to reset options
6147
	 *
6148
	 * @return array of options to delete.
6149
	 */
6150
	public static function get_jetpack_options_for_reset() {
6151
		$jetpack_options            = Jetpack_Options::get_option_names();
6152
		$jetpack_options_non_compat = Jetpack_Options::get_option_names( 'non_compact' );
6153
		$jetpack_options_private    = Jetpack_Options::get_option_names( 'private' );
6154
6155
		$all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
6156
6157
		// A manual build of the wp options
6158
		$wp_options = array(
6159
			'sharing-options',
6160
			'disabled_likes',
6161
			'disabled_reblogs',
6162
			'jetpack_comments_likes_enabled',
6163
			'wp_mobile_excerpt',
6164
			'wp_mobile_featured_images',
6165
			'wp_mobile_app_promos',
6166
			'stats_options',
6167
			'stats_dashboard_widget',
6168
			'safecss_preview_rev',
6169
			'safecss_rev',
6170
			'safecss_revision_migrated',
6171
			'nova_menu_order',
6172
			'jetpack_portfolio',
6173
			'jetpack_portfolio_posts_per_page',
6174
			'jetpack_testimonial',
6175
			'jetpack_testimonial_posts_per_page',
6176
			'wp_mobile_custom_css',
6177
			'sharedaddy_disable_resources',
6178
			'sharing-options',
6179
			'sharing-services',
6180
			'site_icon_temp_data',
6181
			'featured-content',
6182
			'site_logo',
6183
			'jetpack_dismissed_notices',
6184
		);
6185
6186
		// Flag some Jetpack options as unsafe
6187
		$unsafe_options = array(
6188
			'id',                           // (int)    The Client ID/WP.com Blog ID of this site.
6189
			'master_user',                  // (int)    The local User ID of the user who connected this site to jetpack.wordpress.com.
6190
			'version',                      // (string) Used during upgrade procedure to auto-activate new modules. version:time
6191
			'jumpstart',                    // (string) A flag for whether or not to show the Jump Start.  Accepts: new_connection, jumpstart_activated, jetpack_action_taken, jumpstart_dismissed.
6192
6193
			// non_compact
6194
			'activated',
6195
6196
			// private
6197
			'register',
6198
			'blog_token',                  // (string) The Client Secret/Blog Token of this site.
6199
			'user_token',                  // (string) The User Token of this site. (deprecated)
6200
			'user_tokens'
6201
		);
6202
6203
		// Remove the unsafe Jetpack options
6204
		foreach ( $unsafe_options as $unsafe_option ) {
6205
			if ( false !== ( $key = array_search( $unsafe_option, $all_jp_options ) ) ) {
6206
				unset( $all_jp_options[ $key ] );
6207
			}
6208
		}
6209
6210
		$options = array(
6211
			'jp_options' => $all_jp_options,
6212
			'wp_options' => $wp_options
6213
		);
6214
6215
		return $options;
6216
	}
6217
6218
	/**
6219
	 * Check if an option of a Jetpack module has been updated.
6220
	 *
6221
	 * If any module option has been updated before Jump Start has been dismissed,
6222
	 * update the 'jumpstart' option so we can hide Jump Start.
6223
	 *
6224
	 * @param string $option_name
6225
	 *
6226
	 * @return bool
6227
	 */
6228
	public static function jumpstart_has_updated_module_option( $option_name = '' ) {
6229
		// Bail if Jump Start has already been dismissed
6230
		if ( 'new_connection' !== Jetpack_Options::get_option( 'jumpstart' ) ) {
6231
			return false;
6232
		}
6233
6234
		$jetpack = Jetpack::init();
6235
6236
		// Manual build of module options
6237
		$option_names = self::get_jetpack_options_for_reset();
6238
6239
		if ( in_array( $option_name, $option_names['wp_options'] ) ) {
6240
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
6241
6242
			//Jump start is being dismissed send data to MC Stats
6243
			$jetpack->stat( 'jumpstart', 'manual,'.$option_name );
6244
6245
			$jetpack->do_stats( 'server_side' );
6246
		}
6247
6248
	}
6249
6250
	/*
6251
	 * Strip http:// or https:// from a url, replaces forward slash with ::,
6252
	 * so we can bring them directly to their site in calypso.
6253
	 *
6254
	 * @param string | url
6255
	 * @return string | url without the guff
6256
	 */
6257
	public static function build_raw_urls( $url ) {
6258
		$strip_http = '/.*?:\/\//i';
6259
		$url = preg_replace( $strip_http, '', $url  );
6260
		$url = str_replace( '/', '::', $url );
6261
		return $url;
6262
	}
6263
6264
	/**
6265
	 * Stores and prints out domains to prefetch for page speed optimization.
6266
	 *
6267
	 * @param mixed $new_urls
6268
	 */
6269
	public static function dns_prefetch( $new_urls = null ) {
6270
		static $prefetch_urls = array();
6271
		if ( empty( $new_urls ) && ! empty( $prefetch_urls ) ) {
6272
			echo "\r\n";
6273
			foreach ( $prefetch_urls as $this_prefetch_url ) {
6274
				printf( "<link rel='dns-prefetch' href='%s'>\r\n", esc_attr( $this_prefetch_url ) );
6275
			}
6276
		} elseif ( ! empty( $new_urls ) ) {
6277
			if ( ! has_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) ) ) {
6278
				add_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) );
6279
			}
6280
			foreach ( (array) $new_urls as $this_new_url ) {
6281
				$prefetch_urls[] = strtolower( untrailingslashit( preg_replace( '#^https?://#i', '//', $this_new_url ) ) );
6282
			}
6283
			$prefetch_urls = array_unique( $prefetch_urls );
6284
		}
6285
	}
6286
6287
	public function wp_dashboard_setup() {
6288
		if ( self::is_active() ) {
6289
			add_action( 'jetpack_dashboard_widget', array( __CLASS__, 'dashboard_widget_footer' ), 999 );
6290
			$widget_title = __( 'Site Stats', 'jetpack' );
6291
		} elseif ( ! self::is_development_mode() && current_user_can( 'jetpack_connect' ) ) {
6292
			add_action( 'jetpack_dashboard_widget', array( $this, 'dashboard_widget_connect_to_wpcom' ) );
6293
			$widget_title = __( 'Please Connect Jetpack', 'jetpack' );
6294
		}
6295
6296
		if ( has_action( 'jetpack_dashboard_widget' ) ) {
6297
			wp_add_dashboard_widget(
6298
				'jetpack_summary_widget',
6299
				$widget_title,
6300
				array( __CLASS__, 'dashboard_widget' )
6301
			);
6302
			wp_enqueue_style( 'jetpack-dashboard-widget', plugins_url( 'css/dashboard-widget.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
6303
6304
			// If we're inactive and not in development mode, sort our box to the top.
6305
			if ( ! self::is_active() && ! self::is_development_mode() ) {
6306
				global $wp_meta_boxes;
6307
6308
				$dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
6309
				$ours      = array( 'jetpack_summary_widget' => $dashboard['jetpack_summary_widget'] );
6310
6311
				$wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard );
6312
			}
6313
		}
6314
	}
6315
6316
	/**
6317
	 * @param mixed $result Value for the user's option
6318
	 * @return mixed
6319
	 */
6320
	function get_user_option_meta_box_order_dashboard( $sorted ) {
6321
		if ( ! is_array( $sorted ) ) {
6322
			return $sorted;
6323
		}
6324
6325
		foreach ( $sorted as $box_context => $ids ) {
6326
			if ( false === strpos( $ids, 'dashboard_stats' ) ) {
6327
				// If the old id isn't anywhere in the ids, don't bother exploding and fail out.
6328
				continue;
6329
			}
6330
6331
			$ids_array = explode( ',', $ids );
6332
			$key = array_search( 'dashboard_stats', $ids_array );
6333
6334
			if ( false !== $key ) {
6335
				// If we've found that exact value in the option (and not `google_dashboard_stats` for example)
6336
				$ids_array[ $key ] = 'jetpack_summary_widget';
6337
				$sorted[ $box_context ] = implode( ',', $ids_array );
6338
				// We've found it, stop searching, and just return.
6339
				break;
6340
			}
6341
		}
6342
6343
		return $sorted;
6344
	}
6345
6346
	public static function dashboard_widget() {
6347
		/**
6348
		 * Fires when the dashboard is loaded.
6349
		 *
6350
		 * @since 3.4.0
6351
		 */
6352
		do_action( 'jetpack_dashboard_widget' );
6353
	}
6354
6355
	public static function dashboard_widget_footer() {
6356
		?>
6357
		<footer>
6358
6359
		<div class="protect">
6360
			<?php if ( Jetpack::is_module_active( 'protect' ) ) : ?>
6361
				<h3><?php echo number_format_i18n( get_site_option( 'jetpack_protect_blocked_attempts', 0 ) ); ?></h3>
6362
				<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>
6363
			<?php elseif ( current_user_can( 'jetpack_activate_modules' ) && ! self::is_development_mode() ) : ?>
6364
				<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' ); ?>">
6365
					<?php esc_html_e( 'Activate Protect', 'jetpack' ); ?>
6366
				</a>
6367
			<?php else : ?>
6368
				<?php esc_html_e( 'Protect is inactive.', 'jetpack' ); ?>
6369
			<?php endif; ?>
6370
		</div>
6371
6372
		<div class="akismet">
6373
			<?php if ( is_plugin_active( 'akismet/akismet.php' ) ) : ?>
6374
				<h3><?php echo number_format_i18n( get_option( 'akismet_spam_count', 0 ) ); ?></h3>
6375
				<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>
6376
			<?php elseif ( current_user_can( 'activate_plugins' ) && ! is_wp_error( validate_plugin( 'akismet/akismet.php' ) ) ) : ?>
6377
				<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">
6378
					<?php esc_html_e( 'Activate Akismet', 'jetpack' ); ?>
6379
				</a>
6380
			<?php else : ?>
6381
				<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>
6382
			<?php endif; ?>
6383
		</div>
6384
6385
		</footer>
6386
		<?php
6387
	}
6388
6389
	public function dashboard_widget_connect_to_wpcom() {
6390
		if ( Jetpack::is_active() || Jetpack::is_development_mode() || ! current_user_can( 'jetpack_connect' ) ) {
6391
			return;
6392
		}
6393
		?>
6394
		<div class="wpcom-connect">
6395
			<div class="jp-emblem">
6396
			<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0" y="0" viewBox="0 0 172.9 172.9" enable-background="new 0 0 172.9 172.9" xml:space="preserve">
6397
				<path d="M86.4 0C38.7 0 0 38.7 0 86.4c0 47.7 38.7 86.4 86.4 86.4s86.4-38.7 86.4-86.4C172.9 38.7 134.2 0 86.4 0zM83.1 106.6l-27.1-6.9C49 98 45.7 90.1 49.3 84l33.8-58.5V106.6zM124.9 88.9l-33.8 58.5V66.3l27.1 6.9C125.1 74.9 128.4 82.8 124.9 88.9z"/>
6398
			</svg>
6399
			</div>
6400
			<h3><?php esc_html_e( 'Please Connect Jetpack', 'jetpack' ); ?></h3>
6401
			<p><?php echo wp_kses( __( 'Connecting Jetpack will show you <strong>stats</strong> about your traffic, <strong>protect</strong> you from brute force attacks, <strong>speed up</strong> your images and photos, and enable other <strong>traffic and security</strong> features.', 'jetpack' ), 'jetpack' ) ?></p>
6402
6403
			<div class="actions">
6404
				<a href="<?php echo $this->build_connect_url( false, false, 'widget-btn' ); ?>" class="button button-primary">
6405
					<?php esc_html_e( 'Connect Jetpack', 'jetpack' ); ?>
6406
				</a>
6407
			</div>
6408
		</div>
6409
		<?php
6410
	}
6411
6412
	/**
6413
	 * Return string containing the Jetpack logo.
6414
	 *
6415
	 * @since 3.9.0
6416
	 *
6417
	 * @return string
6418
	 */
6419
	public static function get_jp_emblem() {
6420
		return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0" y="0" viewBox="0 0 172.9 172.9" enable-background="new 0 0 172.9 172.9" xml:space="preserve">	<path d="M86.4 0C38.7 0 0 38.7 0 86.4c0 47.7 38.7 86.4 86.4 86.4s86.4-38.7 86.4-86.4C172.9 38.7 134.2 0 86.4 0zM83.1 106.6l-27.1-6.9C49 98 45.7 90.1 49.3 84l33.8-58.5V106.6zM124.9 88.9l-33.8 58.5V66.3l27.1 6.9C125.1 74.9 128.4 82.8 124.9 88.9z" /></svg>';
6421
	}
6422
6423
	/*
6424
	 * Adds a "blank" column in the user admin table to display indication of user connection.
6425
	 */
6426
	function jetpack_icon_user_connected( $columns ) {
6427
		$columns['user_jetpack'] = '';
6428
		return $columns;
6429
	}
6430
6431
	/*
6432
	 * Show Jetpack icon if the user is linked.
6433
	 */
6434
	function jetpack_show_user_connected_icon( $val, $col, $user_id ) {
6435
		if ( 'user_jetpack' == $col && Jetpack::is_user_connected( $user_id ) ) {
6436
			$emblem_html = sprintf(
6437
				'<a title="%1$s" class="jp-emblem-user-admin">%2$s</a>',
6438
				esc_attr__( 'This user is linked and ready to fly with Jetpack.', 'jetpack' ),
6439
				Jetpack::get_jp_emblem()
6440
			);
6441
			return $emblem_html;
6442
		}
6443
6444
		return $val;
6445
	}
6446
6447
	/*
6448
	 * Style the Jetpack user column
6449
	 */
6450
	function jetpack_user_col_style() {
6451
		global $current_screen;
6452
		if ( ! empty( $current_screen->base ) && 'users' == $current_screen->base ) { ?>
6453
			<style>
6454
				.fixed .column-user_jetpack {
6455
					width: 21px;
6456
				}
6457
				.jp-emblem-user-admin svg {
6458
					width: 20px;
6459
					height: 20px;
6460
				}
6461
				.jp-emblem-user-admin path {
6462
					fill: #8cc258;
6463
				}
6464
			</style>
6465
		<?php }
6466
	}
6467
}
6468