Completed
Push — feature/remove-markdown-option ( 68cc5c...d8de54 )
by
unknown
08:59
created

class.jetpack.php (15 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 :
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...
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;
0 ignored issues
show
Coding Style Compatibility introduced by
The method activate_module() 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...
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;
0 ignored issues
show
Coding Style Compatibility introduced by
The method activate_module() 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...
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
		}
2797
2798
		Jetpack::activate_markdown();
2799
2800
	}
2801
2802
	/**
2803
	 * Sets the internal version number and activation state.
2804
	 * @static
2805
	 */
2806
	public static function plugin_initialize() {
2807
		if ( ! Jetpack_Options::get_option( 'activated' ) ) {
2808
			Jetpack_Options::update_option( 'activated', 2 );
2809
		}
2810
2811 View Code Duplication
		if ( ! Jetpack_Options::get_option( 'version' ) ) {
2812
			$version = $old_version = JETPACK__VERSION . ':' . time();
2813
			/** This action is documented in class.jetpack.php */
2814
			do_action( 'updating_jetpack_version', $version, false );
2815
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2816
		}
2817
2818
		Jetpack::load_modules();
2819
2820
		Jetpack_Options::delete_option( 'do_activate' );
2821
	}
2822
2823
	/**
2824
	 * Removes all connection options
2825
	 * @static
2826
	 */
2827
	public static function plugin_deactivation( ) {
2828
		require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
2829
		if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
2830
			Jetpack_Network::init()->deactivate();
2831
		} else {
2832
			Jetpack::disconnect( false );
2833
			//Jetpack_Heartbeat::init()->deactivate();
2834
		}
2835
	}
2836
2837
	/**
2838
	 * Disconnects from the Jetpack servers.
2839
	 * Forgets all connection details and tells the Jetpack servers to do the same.
2840
	 * @static
2841
	 */
2842
	public static function disconnect( $update_activated_state = true ) {
2843
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
2844
		Jetpack::clean_nonces( true );
2845
2846
		// If the site is in an IDC because sync is not allowed,
2847
		// let's make sure to not disconnect the production site.
2848
		if ( ! self::validate_sync_error_idc_option() ) {
2849
			JetpackTracking::record_user_event( 'disconnect_site', array() );
2850
			Jetpack::load_xml_rpc_client();
2851
			$xml = new Jetpack_IXR_Client();
2852
			$xml->query( 'jetpack.deregister' );
2853
		}
2854
2855
		Jetpack_Options::delete_option(
2856
			array(
2857
				'register',
2858
				'blog_token',
2859
				'user_token',
2860
				'user_tokens',
2861
				'master_user',
2862
				'time_diff',
2863
				'fallback_no_verify_ssl_certs',
2864
			)
2865
		);
2866
2867
		Jetpack_IDC::clear_all_idc_options();
2868
2869
		if ( $update_activated_state ) {
2870
			Jetpack_Options::update_option( 'activated', 4 );
2871
		}
2872
2873
		if ( $jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' ) ) {
2874
			// Check then record unique disconnection if site has never been disconnected previously
2875
			if ( - 1 == $jetpack_unique_connection['disconnected'] ) {
2876
				$jetpack_unique_connection['disconnected'] = 1;
2877
			} else {
2878
				if ( 0 == $jetpack_unique_connection['disconnected'] ) {
2879
					//track unique disconnect
2880
					$jetpack = Jetpack::init();
2881
2882
					$jetpack->stat( 'connections', 'unique-disconnect' );
2883
					$jetpack->do_stats( 'server_side' );
2884
				}
2885
				// increment number of times disconnected
2886
				$jetpack_unique_connection['disconnected'] += 1;
2887
			}
2888
2889
			Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
2890
		}
2891
2892
		// Delete cached connected user data
2893
		$transient_key = "jetpack_connected_user_data_" . get_current_user_id();
2894
		delete_transient( $transient_key );
2895
2896
		// Delete all the sync related data. Since it could be taking up space.
2897
		require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php';
2898
		Jetpack_Sync_Sender::get_instance()->uninstall();
2899
2900
		// Disable the Heartbeat cron
2901
		Jetpack_Heartbeat::init()->deactivate();
2902
	}
2903
2904
	/**
2905
	 * Unlinks the current user from the linked WordPress.com user
2906
	 */
2907
	public static function unlink_user( $user_id = null ) {
2908
		if ( ! $tokens = Jetpack_Options::get_option( 'user_tokens' ) )
2909
			return false;
2910
2911
		$user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
2912
2913
		if ( Jetpack_Options::get_option( 'master_user' ) == $user_id )
2914
			return false;
2915
2916
		if ( ! isset( $tokens[ $user_id ] ) )
2917
			return false;
2918
2919
		Jetpack::load_xml_rpc_client();
2920
		$xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
2921
		$xml->query( 'jetpack.unlink_user', $user_id );
2922
2923
		unset( $tokens[ $user_id ] );
2924
2925
		Jetpack_Options::update_option( 'user_tokens', $tokens );
2926
2927
		/**
2928
		 * Fires after the current user has been unlinked from WordPress.com.
2929
		 *
2930
		 * @since 4.1.0
2931
		 *
2932
		 * @param int $user_id The current user's ID.
2933
		 */
2934
		do_action( 'jetpack_unlinked_user', $user_id );
2935
2936
		return true;
2937
	}
2938
2939
	/**
2940
	 * Attempts Jetpack registration.  If it fail, a state flag is set: @see ::admin_page_load()
2941
	 */
2942
	public static function try_registration() {
2943
		// Let's get some testing in beta versions and such.
2944
		if ( self::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
2945
			// Before attempting to connect, let's make sure that the domains are viable.
2946
			$domains_to_check = array_unique( array(
2947
				'siteurl' => parse_url( get_site_url(), PHP_URL_HOST ),
2948
				'homeurl' => parse_url( get_home_url(), PHP_URL_HOST ),
2949
			) );
2950
			foreach ( $domains_to_check as $domain ) {
2951
				$result = Jetpack_Data::is_usable_domain( $domain );
2952
				if ( is_wp_error( $result ) ) {
2953
					return $result;
2954
				}
2955
			}
2956
		}
2957
2958
		$result = Jetpack::register();
2959
2960
		// If there was an error with registration and the site was not registered, record this so we can show a message.
2961
		if ( ! $result || is_wp_error( $result ) ) {
2962
			return $result;
2963
		} else {
2964
			return true;
2965
		}
2966
	}
2967
2968
	/**
2969
	 * Tracking an internal event log. Try not to put too much chaff in here.
2970
	 *
2971
	 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
2972
	 */
2973
	public static function log( $code, $data = null ) {
2974
		// only grab the latest 200 entries
2975
		$log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
2976
2977
		// Append our event to the log
2978
		$log_entry = array(
2979
			'time'    => time(),
2980
			'user_id' => get_current_user_id(),
2981
			'blog_id' => Jetpack_Options::get_option( 'id' ),
2982
			'code'    => $code,
2983
		);
2984
		// Don't bother storing it unless we've got some.
2985
		if ( ! is_null( $data ) ) {
2986
			$log_entry['data'] = $data;
2987
		}
2988
		$log[] = $log_entry;
2989
2990
		// Try add_option first, to make sure it's not autoloaded.
2991
		// @todo: Add an add_option method to Jetpack_Options
2992
		if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
2993
			Jetpack_Options::update_option( 'log', $log );
2994
		}
2995
2996
		/**
2997
		 * Fires when Jetpack logs an internal event.
2998
		 *
2999
		 * @since 3.0.0
3000
		 *
3001
		 * @param array $log_entry {
3002
		 *	Array of details about the log entry.
3003
		 *
3004
		 *	@param string time Time of the event.
3005
		 *	@param int user_id ID of the user who trigerred the event.
3006
		 *	@param int blog_id Jetpack Blog ID.
3007
		 *	@param string code Unique name for the event.
3008
		 *	@param string data Data about the event.
3009
		 * }
3010
		 */
3011
		do_action( 'jetpack_log_entry', $log_entry );
3012
	}
3013
3014
	/**
3015
	 * Get the internal event log.
3016
	 *
3017
	 * @param $event (string) - only return the specific log events
3018
	 * @param $num   (int)    - get specific number of latest results, limited to 200
3019
	 *
3020
	 * @return array of log events || WP_Error for invalid params
3021
	 */
3022
	public static function get_log( $event = false, $num = false ) {
3023
		if ( $event && ! is_string( $event ) ) {
3024
			return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
3025
		}
3026
3027
		if ( $num && ! is_numeric( $num ) ) {
3028
			return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
3029
		}
3030
3031
		$entire_log = Jetpack_Options::get_option( 'log', array() );
3032
3033
		// If nothing set - act as it did before, otherwise let's start customizing the output
3034
		if ( ! $num && ! $event ) {
3035
			return $entire_log;
3036
		} else {
3037
			$entire_log = array_reverse( $entire_log );
3038
		}
3039
3040
		$custom_log_output = array();
3041
3042
		if ( $event ) {
3043
			foreach ( $entire_log as $log_event ) {
3044
				if ( $event == $log_event[ 'code' ] ) {
3045
					$custom_log_output[] = $log_event;
3046
				}
3047
			}
3048
		} else {
3049
			$custom_log_output = $entire_log;
3050
		}
3051
3052
		if ( $num ) {
3053
			$custom_log_output = array_slice( $custom_log_output, 0, $num );
3054
		}
3055
3056
		return $custom_log_output;
3057
	}
3058
3059
	/**
3060
	 * Log modification of important settings.
3061
	 */
3062
	public static function log_settings_change( $option, $old_value, $value ) {
3063
		switch( $option ) {
3064
			case 'jetpack_sync_non_public_post_stati':
3065
				self::log( $option, $value );
3066
				break;
3067
		}
3068
	}
3069
3070
	/**
3071
	 * Return stat data for WPCOM sync
3072
	 */
3073
	public static function get_stat_data( $encode = true, $extended = true ) {
3074
		$data = Jetpack_Heartbeat::generate_stats_array();
3075
3076
		if ( $extended ) {
3077
			$additional_data = self::get_additional_stat_data();
3078
			$data = array_merge( $data, $additional_data );
3079
		}
3080
3081
		if ( $encode ) {
3082
			return json_encode( $data );
3083
		}
3084
3085
		return $data;
3086
	}
3087
3088
	/**
3089
	 * Get additional stat data to sync to WPCOM
3090
	 */
3091
	public static function get_additional_stat_data( $prefix = '' ) {
3092
		$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...
3093
		$return["{$prefix}plugins-extra"]  = Jetpack::get_parsed_plugin_data();
3094
		$return["{$prefix}users"]          = (int) Jetpack::get_site_user_count();
3095
		$return["{$prefix}site-count"]     = 0;
3096
3097
		if ( function_exists( 'get_blog_count' ) ) {
3098
			$return["{$prefix}site-count"] = get_blog_count();
3099
		}
3100
		return $return;
3101
	}
3102
3103
	private static function get_site_user_count() {
3104
		global $wpdb;
3105
3106
		if ( function_exists( 'wp_is_large_network' ) ) {
3107
			if ( wp_is_large_network( 'users' ) ) {
3108
				return -1; // Not a real value but should tell us that we are dealing with a large network.
3109
			}
3110
		}
3111 View Code Duplication
		if ( false === ( $user_count = get_transient( 'jetpack_site_user_count' ) ) ) {
3112
			// It wasn't there, so regenerate the data and save the transient
3113
			$user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities'" );
3114
			set_transient( 'jetpack_site_user_count', $user_count, DAY_IN_SECONDS );
3115
		}
3116
		return $user_count;
3117
	}
3118
3119
	/* Admin Pages */
3120
3121
	function admin_init() {
3122
		// If the plugin is not connected, display a connect message.
3123
		if (
3124
			// the plugin was auto-activated and needs its candy
3125
			Jetpack_Options::get_option_and_ensure_autoload( 'do_activate', '0' )
3126
		||
3127
			// the plugin is active, but was never activated.  Probably came from a site-wide network activation
3128
			! Jetpack_Options::get_option( 'activated' )
3129
		) {
3130
			Jetpack::plugin_initialize();
3131
		}
3132
3133
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
3134
			Jetpack_Connection_Banner::init();
3135
		} elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
3136
			// Upgrade: 1.1 -> 1.1.1
3137
			// Check and see if host can verify the Jetpack servers' SSL certificate
3138
			$args = array();
3139
			Jetpack_Client::_wp_remote_request(
3140
				Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'test' ) ),
3141
				$args,
3142
				true
3143
			);
3144
		} else if ( $this->can_display_jetpack_manage_notice() && ! Jetpack_Options::get_option( 'dismissed_manage_banner' ) ) {
3145
			// Show the notice on the Dashboard only for now
3146
			add_action( 'load-index.php', array( $this, 'prepare_manage_jetpack_notice' ) );
3147
		}
3148
3149
		if ( current_user_can( 'manage_options' ) && 'AUTO' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
3150
			add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3151
		}
3152
3153
		add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3154
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3155
		add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3156
3157
		if ( Jetpack::is_active() || Jetpack::is_development_mode() ) {
3158
			// Artificially throw errors in certain whitelisted cases during plugin activation
3159
			add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3160
		}
3161
3162
		// Jetpack Manage Activation Screen from .com
3163
		Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
3164
3165
		// Add custom column in wp-admin/users.php to show whether user is linked.
3166
		add_filter( 'manage_users_columns',       array( $this, 'jetpack_icon_user_connected' ) );
3167
		add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_user_connected_icon' ), 10, 3 );
3168
		add_action( 'admin_print_styles',         array( $this, 'jetpack_user_col_style' ) );
3169
	}
3170
3171
	function admin_body_class( $admin_body_class = '' ) {
3172
		$classes = explode( ' ', trim( $admin_body_class ) );
3173
3174
		$classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
3175
3176
		$admin_body_class = implode( ' ', array_unique( $classes ) );
3177
		return " $admin_body_class ";
3178
	}
3179
3180
	static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3181
		return $admin_body_class . ' jetpack-pagestyles ';
3182
	}
3183
3184
	/**
3185
	 * Call this function if you want the Big Jetpack Manage Notice to show up.
3186
	 *
3187
	 * @return null
3188
	 */
3189
	function prepare_manage_jetpack_notice() {
3190
3191
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3192
		add_action( 'admin_notices', array( $this, 'admin_jetpack_manage_notice' ) );
3193
	}
3194
3195
	function manage_activate_screen() {
3196
		include ( JETPACK__PLUGIN_DIR . 'modules/manage/activate-admin.php' );
3197
	}
3198
	/**
3199
	 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3200
	 * This function artificially throws errors for such cases (whitelisted).
3201
	 *
3202
	 * @param string $plugin The activated plugin.
3203
	 */
3204
	function throw_error_on_activate_plugin( $plugin ) {
3205
		$active_modules = Jetpack::get_active_modules();
3206
3207
		// The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3208
		if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3209
			$throw = false;
3210
3211
			// Try and make sure it really was the stats plugin
3212
			if ( ! class_exists( 'ReflectionFunction' ) ) {
3213
				if ( 'stats.php' == basename( $plugin ) ) {
3214
					$throw = true;
3215
				}
3216
			} else {
3217
				$reflection = new ReflectionFunction( 'stats_get_api_key' );
3218
				if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3219
					$throw = true;
3220
				}
3221
			}
3222
3223
			if ( $throw ) {
3224
				trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3225
			}
3226
		}
3227
	}
3228
3229
	function intercept_plugin_error_scrape_init() {
3230
		add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3231
	}
3232
3233
	function intercept_plugin_error_scrape( $action, $result ) {
3234
		if ( ! $result ) {
3235
			return;
3236
		}
3237
3238
		foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3239
			if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3240
				Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3241
			}
3242
		}
3243
	}
3244
3245
	function add_remote_request_handlers() {
3246
		add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
3247
		add_action( 'wp_ajax_nopriv_jetpack_update_file', array( $this, 'remote_request_handlers' ) );
3248
	}
3249
3250
	function remote_request_handlers() {
3251
		$action = current_filter();
3252
3253
		switch ( current_filter() ) {
3254
		case 'wp_ajax_nopriv_jetpack_upload_file' :
3255
			$response = $this->upload_handler();
3256
			break;
3257
3258
		case 'wp_ajax_nopriv_jetpack_update_file' :
3259
			$response = $this->upload_handler( true );
3260
			break;
3261
		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...
3262
			$response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
3263
			break;
3264
		}
3265
3266
		if ( ! $response ) {
3267
			$response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
3268
		}
3269
3270
		if ( is_wp_error( $response ) ) {
3271
			$status_code       = $response->get_error_data();
3272
			$error             = $response->get_error_code();
3273
			$error_description = $response->get_error_message();
3274
3275
			if ( ! is_int( $status_code ) ) {
3276
				$status_code = 400;
3277
			}
3278
3279
			status_header( $status_code );
3280
			die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3281
		}
3282
3283
		status_header( 200 );
3284
		if ( true === $response ) {
3285
			exit;
3286
		}
3287
3288
		die( json_encode( (object) $response ) );
3289
	}
3290
3291
	/**
3292
	 * Uploads a file gotten from the global $_FILES.
3293
	 * If `$update_media_item` is true and `post_id` is defined
3294
	 * the attachment file of the media item (gotten through of the post_id)
3295
	 * will be updated instead of add a new one.
3296
	 *
3297
	 * @param  boolean $update_media_item - update media attachment
3298
	 * @return array - An array describing the uploadind files process
3299
	 */
3300
	function upload_handler( $update_media_item = false ) {
3301
		if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3302
			return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
3303
		}
3304
3305
		$user = wp_authenticate( '', '' );
3306
		if ( ! $user || is_wp_error( $user ) ) {
3307
			return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
3308
		}
3309
3310
		wp_set_current_user( $user->ID );
3311
3312
		if ( ! current_user_can( 'upload_files' ) ) {
3313
			return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3314
		}
3315
3316
		if ( empty( $_FILES ) ) {
3317
			return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3318
		}
3319
3320
		foreach ( array_keys( $_FILES ) as $files_key ) {
3321
			if ( ! isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
3322
				return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3323
			}
3324
		}
3325
3326
		$media_keys = array_keys( $_FILES['media'] );
3327
3328
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
3329
		if ( ! $token || is_wp_error( $token ) ) {
3330
			return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3331
		}
3332
3333
		$uploaded_files = array();
3334
		$global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3335
		unset( $GLOBALS['post'] );
3336
		foreach ( $_FILES['media']['name'] as $index => $name ) {
3337
			$file = array();
3338
			foreach ( $media_keys as $media_key ) {
3339
				$file[$media_key] = $_FILES['media'][$media_key][$index];
3340
			}
3341
3342
			list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
3343
3344
			$hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3345
			if ( $hmac_provided !== $hmac_file ) {
3346
				$uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
3347
				continue;
3348
			}
3349
3350
			$_FILES['.jetpack.upload.'] = $file;
3351
			$post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
3352
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
3353
				$post_id = 0;
3354
			}
3355
3356
			if ( $update_media_item ) {
3357
				if ( ! isset( $post_id ) || $post_id === 0 ) {
3358
					return new Jetpack_Error( 'invalid_input', 'Media ID must be defined.', 400 );
3359
				}
3360
3361
				$media_array = $_FILES['media'];
3362
3363
				$file_array['name'] = $media_array['name'][0];
3364
				$file_array['type'] = $media_array['type'][0];
3365
				$file_array['tmp_name'] = $media_array['tmp_name'][0];
3366
				$file_array['error'] = $media_array['error'][0];
3367
				$file_array['size'] = $media_array['size'][0];
3368
3369
				$edited_media_item = Jetpack_Media::edit_media_file( $post_id, $file_array );
3370
3371
				if ( is_wp_error( $edited_media_item ) ) {
3372
					return $edited_media_item;
3373
				}
3374
3375
				$response = (object) array(
3376
					'id'   => (string) $post_id,
3377
					'file' => (string) $edited_media_item->post_title,
3378
					'url'  => (string) wp_get_attachment_url( $post_id ),
3379
					'type' => (string) $edited_media_item->post_mime_type,
3380
					'meta' => (array) wp_get_attachment_metadata( $post_id ),
3381
				);
3382
3383
				return (array) array( $response );
3384
			}
3385
3386
			$attachment_id = media_handle_upload(
3387
				'.jetpack.upload.',
3388
				$post_id,
3389
				array(),
3390
				array(
3391
					'action' => 'jetpack_upload_file',
3392
				)
3393
			);
3394
3395
			if ( ! $attachment_id ) {
3396
				$uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
3397
			} elseif ( is_wp_error( $attachment_id ) ) {
3398
				$uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
3399
			} else {
3400
				$attachment = get_post( $attachment_id );
3401
				$uploaded_files[$index] = (object) array(
3402
					'id'   => (string) $attachment_id,
3403
					'file' => $attachment->post_title,
3404
					'url'  => wp_get_attachment_url( $attachment_id ),
3405
					'type' => $attachment->post_mime_type,
3406
					'meta' => wp_get_attachment_metadata( $attachment_id ),
3407
				);
3408
				// Zip files uploads are not supported unless they are done for installation purposed
3409
				// lets delete them in case something goes wrong in this whole process
3410
				if ( 'application/zip' === $attachment->post_mime_type ) {
3411
					// Schedule a cleanup for 2 hours from now in case of failed install.
3412
					wp_schedule_single_event( time() + 2 * HOUR_IN_SECONDS, 'upgrader_scheduled_cleanup', array( $attachment_id ) );
3413
				}
3414
			}
3415
		}
3416
		if ( ! is_null( $global_post ) ) {
3417
			$GLOBALS['post'] = $global_post;
3418
		}
3419
3420
		return $uploaded_files;
3421
	}
3422
3423
	/**
3424
	 * Add help to the Jetpack page
3425
	 *
3426
	 * @since Jetpack (1.2.3)
3427
	 * @return false if not the Jetpack page
3428
	 */
3429
	function admin_help() {
3430
		$current_screen = get_current_screen();
3431
3432
		// Overview
3433
		$current_screen->add_help_tab(
3434
			array(
3435
				'id'		=> 'home',
3436
				'title'		=> __( 'Home', 'jetpack' ),
3437
				'content'	=>
3438
					'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3439
					'<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3440
					'<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>',
3441
			)
3442
		);
3443
3444
		// Screen Content
3445
		if ( current_user_can( 'manage_options' ) ) {
3446
			$current_screen->add_help_tab(
3447
				array(
3448
					'id'		=> 'settings',
3449
					'title'		=> __( 'Settings', 'jetpack' ),
3450
					'content'	=>
3451
						'<p><strong>' . __( 'Jetpack by WordPress.com',                                              'jetpack' ) . '</strong></p>' .
3452
						'<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3453
						'<ol>' .
3454
							'<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.',														'jetpack' ) . '</li>' .
3455
							'<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>' .
3456
						'</ol>' .
3457
						'<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>'
3458
				)
3459
			);
3460
		}
3461
3462
		// Help Sidebar
3463
		$current_screen->set_help_sidebar(
3464
			'<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3465
			'<p><a href="https://jetpack.com/faq/" target="_blank">'     . __( 'Jetpack FAQ',     'jetpack' ) . '</a></p>' .
3466
			'<p><a href="https://jetpack.com/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3467
			'<p><a href="' . Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) .'">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3468
		);
3469
	}
3470
3471
	function admin_menu_css() {
3472
		wp_enqueue_style( 'jetpack-icons' );
3473
	}
3474
3475
	function admin_menu_order() {
3476
		return true;
3477
	}
3478
3479 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
3480
		$jp_menu_order = array();
3481
3482
		foreach ( $menu_order as $index => $item ) {
3483
			if ( $item != 'jetpack' ) {
3484
				$jp_menu_order[] = $item;
3485
			}
3486
3487
			if ( $index == 0 ) {
3488
				$jp_menu_order[] = 'jetpack';
3489
			}
3490
		}
3491
3492
		return $jp_menu_order;
3493
	}
3494
3495
	function admin_head() {
3496 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) )
3497
			/** This action is documented in class.jetpack-admin-page.php */
3498
			do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
3499
	}
3500
3501
	function admin_banner_styles() {
3502
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3503
3504 View Code Duplication
		if ( ! wp_style_is( 'jetpack-dops-style' ) ) {
3505
			wp_register_style(
3506
				'jetpack-dops-style',
3507
				plugins_url( '_inc/build/admin.dops-style.css', JETPACK__PLUGIN_FILE ),
3508
				array(),
3509
				JETPACK__VERSION
3510
			);
3511
		}
3512
3513
		wp_enqueue_style(
3514
			'jetpack',
3515
			plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ),
3516
			array( 'jetpack-dops-style' ),
3517
			 JETPACK__VERSION . '-20121016'
3518
		);
3519
		wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3520
		wp_style_add_data( 'jetpack', 'suffix', $min );
3521
	}
3522
3523
	function plugin_action_links( $actions ) {
3524
3525
		$jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack' ), __( 'Jetpack', 'jetpack' ) ) );
3526
3527
		if( current_user_can( 'jetpack_manage_modules' ) && ( Jetpack::is_active() || Jetpack::is_development_mode() ) ) {
3528
			return array_merge(
3529
				$jetpack_home,
3530
				array( 'settings' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack#/settings' ), __( 'Settings', 'jetpack' ) ) ),
3531
				array( 'support' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack-debugger '), __( 'Support', 'jetpack' ) ) ),
3532
				$actions
3533
				);
3534
			}
3535
3536
		return array_merge( $jetpack_home, $actions );
3537
	}
3538
3539
	/**
3540
	 * This is the first banner
3541
	 * It should be visible only to user that can update the option
3542
	 * Are not connected
3543
	 *
3544
	 * @return null
3545
	 */
3546
	function admin_jetpack_manage_notice() {
3547
		$screen = get_current_screen();
3548
3549
		// Don't show the connect notice on the jetpack settings page.
3550
		if ( ! in_array( $screen->base, array( 'dashboard' ) ) || $screen->is_network || $screen->action ) {
3551
			return;
3552
		}
3553
3554
		$opt_out_url = $this->opt_out_jetpack_manage_url();
3555
		$opt_in_url  = $this->opt_in_jetpack_manage_url();
3556
		/**
3557
		 * I think it would be great to have different wordsing depending on where you are
3558
		 * for example if we show the notice on dashboard and a different one if we show it on Plugins screen
3559
		 * etc..
3560
		 */
3561
3562
		?>
3563
		<div id="message" class="updated jp-banner">
3564
				<a href="<?php echo esc_url( $opt_out_url ); ?>" class="notice-dismiss" title="<?php esc_attr_e( 'Dismiss this notice', 'jetpack' ); ?>"></a>
3565
				<div class="jp-banner__description-container">
3566
					<h2 class="jp-banner__header"><?php esc_html_e( 'Jetpack Centralized Site Management', 'jetpack' ); ?></h2>
3567
					<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>
3568
					<p class="jp-banner__button-container">
3569
						<a href="<?php echo esc_url( $opt_in_url ); ?>" class="button button-primary" id="wpcom-connect"><?php _e( 'Activate Jetpack Manage', 'jetpack' ); ?></a>
3570
						<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>
3571
					</p>
3572
				</div>
3573
		</div>
3574
		<?php
3575
	}
3576
3577
	/**
3578
	 * Returns the url that the user clicks to remove the notice for the big banner
3579
	 * @return (string)
3580
	 */
3581
	function opt_out_jetpack_manage_url() {
3582
		$referer = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
3583
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-out' . $referer ), 'jetpack_manage_banner_opt_out' );
3584
	}
3585
	/**
3586
	 * Returns the url that the user clicks to opt in to Jetpack Manage
3587
	 * @return (string)
3588
	 */
3589
	function opt_in_jetpack_manage_url() {
3590
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-in' ), 'jetpack_manage_banner_opt_in' );
3591
	}
3592
3593
	function opt_in_jetpack_manage_notice() {
3594
		?>
3595
		<div class="wrap">
3596
			<div id="message" class="jetpack-message is-opt-in">
3597
				<?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' ); ?>
3598
			</div>
3599
		</div>
3600
		<?php
3601
3602
	}
3603
	/**
3604
	 * Determines whether to show the notice of not true = display notice
3605
	 * @return (bool)
3606
	 */
3607
	function can_display_jetpack_manage_notice() {
3608
		// never display the notice to users that can't do anything about it anyways
3609
		if( ! current_user_can( 'jetpack_manage_modules' ) )
3610
			return false;
3611
3612
		// don't display if we are in development more
3613
		if( Jetpack::is_development_mode() ) {
3614
			return false;
3615
		}
3616
		// don't display if the site is private
3617
		if(  ! Jetpack_Options::get_option( 'public' ) )
3618
			return false;
3619
3620
		/**
3621
		 * Should the Jetpack Remote Site Management notice be displayed.
3622
		 *
3623
		 * @since 3.3.0
3624
		 *
3625
		 * @param bool ! self::is_module_active( 'manage' ) Is the Manage module inactive.
3626
		 */
3627
		return apply_filters( 'can_display_jetpack_manage_notice', ! self::is_module_active( 'manage' ) );
3628
	}
3629
3630
	/*
3631
	 * Registration flow:
3632
	 * 1 - ::admin_page_load() action=register
3633
	 * 2 - ::try_registration()
3634
	 * 3 - ::register()
3635
	 *     - Creates jetpack_register option containing two secrets and a timestamp
3636
	 *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3637
	 *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3638
	 *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
3639
	 *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
3640
	 *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
3641
	 *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
3642
	 *       jetpack_id, jetpack_secret, jetpack_public
3643
	 *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
3644
	 * 4 - redirect to https://wordpress.com/start/jetpack-connect
3645
	 * 5 - user logs in with WP.com account
3646
	 * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
3647
	 *		- Jetpack_Client_Server::authorize()
3648
	 *		- Jetpack_Client_Server::get_token()
3649
	 *		- GET https://jetpack.wordpress.com/jetpack.token/1/ with
3650
	 *        client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
3651
	 *			- which responds with access_token, token_type, scope
3652
	 *		- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
3653
	 *		- Jetpack::activate_default_modules()
3654
	 *     		- Deactivates deprecated plugins
3655
	 *     		- Activates all default modules
3656
	 *		- Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
3657
	 * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
3658
	 * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
3659
	 *     Done!
3660
	 */
3661
3662
	/**
3663
	 * Handles the page load events for the Jetpack admin page
3664
	 */
3665
	function admin_page_load() {
3666
		$error = false;
3667
3668
		// Make sure we have the right body class to hook stylings for subpages off of.
3669
		add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ) );
3670
3671
		if ( ! empty( $_GET['jetpack_restate'] ) ) {
3672
			// Should only be used in intermediate redirects to preserve state across redirects
3673
			Jetpack::restate();
3674
		}
3675
3676
		if ( isset( $_GET['connect_url_redirect'] ) ) {
3677
			// User clicked in the iframe to link their accounts
3678
			if ( ! Jetpack::is_user_connected() ) {
3679
				$connect_url = $this->build_connect_url( true, false, 'iframe' );
3680
				if ( isset( $_GET['notes_iframe'] ) )
3681
					$connect_url .= '&notes_iframe';
3682
				wp_redirect( $connect_url );
3683
				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...
3684
			} else {
3685
				if ( ! isset( $_GET['calypso_env'] ) ) {
3686
					Jetpack::state( 'message', 'already_authorized' );
3687
					wp_safe_redirect( Jetpack::admin_url() );
3688
				} else {
3689
					$connect_url = $this->build_connect_url( true, false, 'iframe' );
3690
					$connect_url .= '&already_authorized=true';
3691
					wp_redirect( $connect_url );
3692
				}
3693
			}
3694
		}
3695
3696
3697
		if ( isset( $_GET['action'] ) ) {
3698
			switch ( $_GET['action'] ) {
3699
			case 'authorize':
3700
				if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
3701
					Jetpack::state( 'message', 'already_authorized' );
3702
					wp_safe_redirect( Jetpack::admin_url() );
3703
					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...
3704
				}
3705
				Jetpack::log( 'authorize' );
3706
				$client_server = new Jetpack_Client_Server;
3707
				$client_server->client_authorize();
3708
				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...
3709
			case 'register' :
3710
				if ( ! current_user_can( 'jetpack_connect' ) ) {
3711
					$error = 'cheatin';
3712
					break;
3713
				}
3714
				check_admin_referer( 'jetpack-register' );
3715
				Jetpack::log( 'register' );
3716
				Jetpack::maybe_set_version_option();
3717
				$registered = Jetpack::try_registration();
3718
				if ( is_wp_error( $registered ) ) {
3719
					$error = $registered->get_error_code();
3720
					Jetpack::state( 'error', $error );
3721
					Jetpack::state( 'error', $registered->get_error_message() );
3722
					break;
3723
				}
3724
3725
				$from = isset( $_GET['from'] ) ? $_GET['from'] : false;
3726
3727
				wp_redirect( $this->build_connect_url( true, false, $from ) );
3728
				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...
3729
			case 'activate' :
3730
				if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3731
					$error = 'cheatin';
3732
					break;
3733
				}
3734
3735
				$module = stripslashes( $_GET['module'] );
3736
				check_admin_referer( "jetpack_activate-$module" );
3737
				Jetpack::log( 'activate', $module );
3738
				Jetpack::activate_module( $module );
3739
				// The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3740
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3741
				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...
3742
			case 'activate_default_modules' :
3743
				check_admin_referer( 'activate_default_modules' );
3744
				Jetpack::log( 'activate_default_modules' );
3745
				Jetpack::restate();
3746
				$min_version   = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
3747
				$max_version   = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
3748
				$other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
3749
				Jetpack::activate_default_modules( $min_version, $max_version, $other_modules );
3750
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3751
				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...
3752
			case 'disconnect' :
3753
				if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3754
					$error = 'cheatin';
3755
					break;
3756
				}
3757
3758
				check_admin_referer( 'jetpack-disconnect' );
3759
				Jetpack::log( 'disconnect' );
3760
				Jetpack::disconnect();
3761
				wp_safe_redirect( Jetpack::admin_url( 'disconnected=true' ) );
3762
				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...
3763
			case 'reconnect' :
3764
				if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3765
					$error = 'cheatin';
3766
					break;
3767
				}
3768
3769
				check_admin_referer( 'jetpack-reconnect' );
3770
				Jetpack::log( 'reconnect' );
3771
				$this->disconnect();
3772
				wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3773
				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...
3774 View Code Duplication
			case 'deactivate' :
3775
				if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3776
					$error = 'cheatin';
3777
					break;
3778
				}
3779
3780
				$modules = stripslashes( $_GET['module'] );
3781
				check_admin_referer( "jetpack_deactivate-$modules" );
3782
				foreach ( explode( ',', $modules ) as $module ) {
3783
					Jetpack::log( 'deactivate', $module );
3784
					Jetpack::deactivate_module( $module );
3785
					Jetpack::state( 'message', 'module_deactivated' );
3786
				}
3787
				Jetpack::state( 'module', $modules );
3788
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3789
				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...
3790
			case 'unlink' :
3791
				$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
3792
				check_admin_referer( 'jetpack-unlink' );
3793
				Jetpack::log( 'unlink' );
3794
				$this->unlink_user();
3795
				Jetpack::state( 'message', 'unlinked' );
3796
				if ( 'sub-unlink' == $redirect ) {
3797
					wp_safe_redirect( admin_url() );
3798
				} else {
3799
					wp_safe_redirect( Jetpack::admin_url( array( 'page' => $redirect ) ) );
3800
				}
3801
				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...
3802
			default:
3803
				/**
3804
				 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3805
				 *
3806
				 * @since 2.6.0
3807
				 *
3808
				 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
3809
				 */
3810
				do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
3811
			}
3812
		}
3813
3814
		if ( ! $error = $error ? $error : Jetpack::state( 'error' ) ) {
3815
			self::activate_new_modules( true );
3816
		}
3817
3818
		$message_code = Jetpack::state( 'message' );
3819
		if ( Jetpack::state( 'optin-manage' ) ) {
3820
			$activated_manage = $message_code;
3821
			$message_code = 'jetpack-manage';
3822
		}
3823
3824
		switch ( $message_code ) {
3825
		case 'jetpack-manage':
3826
			$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>';
3827
			if ( $activated_manage ) {
3828
				$this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack'  ) . '</strong>';
3829
			}
3830
			break;
3831
3832
		}
3833
3834
		$deactivated_plugins = Jetpack::state( 'deactivated_plugins' );
3835
3836
		if ( ! empty( $deactivated_plugins ) ) {
3837
			$deactivated_plugins = explode( ',', $deactivated_plugins );
3838
			$deactivated_titles  = array();
3839
			foreach ( $deactivated_plugins as $deactivated_plugin ) {
3840
				if ( ! isset( $this->plugins_to_deactivate[$deactivated_plugin] ) ) {
3841
					continue;
3842
				}
3843
3844
				$deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[$deactivated_plugin][1] ) . '</strong>';
3845
			}
3846
3847
			if ( $deactivated_titles ) {
3848
				if ( $this->message ) {
3849
					$this->message .= "<br /><br />\n";
3850
				}
3851
3852
				$this->message .= wp_sprintf(
3853
					_n(
3854
						'Jetpack contains the most recent version of the old %l plugin.',
3855
						'Jetpack contains the most recent versions of the old %l plugins.',
3856
						count( $deactivated_titles ),
3857
						'jetpack'
3858
					),
3859
					$deactivated_titles
3860
				);
3861
3862
				$this->message .= "<br />\n";
3863
3864
				$this->message .= _n(
3865
					'The old version has been deactivated and can be removed from your site.',
3866
					'The old versions have been deactivated and can be removed from your site.',
3867
					count( $deactivated_titles ),
3868
					'jetpack'
3869
				);
3870
			}
3871
		}
3872
3873
		$this->privacy_checks = Jetpack::state( 'privacy_checks' );
3874
3875
		if ( $this->message || $this->error || $this->privacy_checks || $this->can_display_jetpack_manage_notice() ) {
3876
			add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
3877
		}
3878
3879 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
3880
			/**
3881
			 * Fires when a module configuration page is loaded.
3882
			 * The dynamic part of the hook is the configure parameter from the URL.
3883
			 *
3884
			 * @since 1.1.0
3885
			 */
3886
			do_action( 'jetpack_module_configuration_load_' . $_GET['configure'] );
3887
		}
3888
3889
		add_filter( 'jetpack_short_module_description', 'wptexturize' );
3890
	}
3891
3892
	function admin_notices() {
3893
3894
		if ( $this->error ) {
3895
?>
3896
<div id="message" class="jetpack-message jetpack-err">
3897
	<div class="squeezer">
3898
		<h2><?php echo wp_kses( $this->error, array( 'a' => array( 'href' => array() ), 'small' => true, 'code' => true, 'strong' => true, 'br' => true, 'b' => true ) ); ?></h2>
3899
<?php	if ( $desc = Jetpack::state( 'error_description' ) ) : ?>
3900
		<p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
3901
<?php	endif; ?>
3902
	</div>
3903
</div>
3904
<?php
3905
		}
3906
3907
		if ( $this->message ) {
3908
?>
3909
<div id="message" class="jetpack-message">
3910
	<div class="squeezer">
3911
		<h2><?php echo wp_kses( $this->message, array( 'strong' => array(), 'a' => array( 'href' => true ), 'br' => true ) ); ?></h2>
3912
	</div>
3913
</div>
3914
<?php
3915
		}
3916
3917
		if ( $this->privacy_checks ) :
3918
			$module_names = $module_slugs = array();
3919
3920
			$privacy_checks = explode( ',', $this->privacy_checks );
3921
			$privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
3922
			foreach ( $privacy_checks as $module_slug ) {
3923
				$module = Jetpack::get_module( $module_slug );
3924
				if ( ! $module ) {
3925
					continue;
3926
				}
3927
3928
				$module_slugs[] = $module_slug;
3929
				$module_names[] = "<strong>{$module['name']}</strong>";
3930
			}
3931
3932
			$module_slugs = join( ',', $module_slugs );
3933
?>
3934
<div id="message" class="jetpack-message jetpack-err">
3935
	<div class="squeezer">
3936
		<h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
3937
		<p><?php
3938
			echo wp_kses(
3939
				wptexturize(
3940
					wp_sprintf(
3941
						_nx(
3942
							"Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
3943
							"Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
3944
							count( $privacy_checks ),
3945
							'%l = list of Jetpack module/feature names',
3946
							'jetpack'
3947
						),
3948
						$module_names
3949
					)
3950
				),
3951
				array( 'strong' => true )
3952
			);
3953
3954
			echo "\n<br />\n";
3955
3956
			echo wp_kses(
3957
				sprintf(
3958
					_nx(
3959
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
3960
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
3961
						count( $privacy_checks ),
3962
						'%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
3963
						'jetpack'
3964
					),
3965
					wp_nonce_url(
3966
						Jetpack::admin_url(
3967
							array(
3968
								'page'   => 'jetpack',
3969
								'action' => 'deactivate',
3970
								'module' => urlencode( $module_slugs ),
3971
							)
3972
						),
3973
						"jetpack_deactivate-$module_slugs"
3974
					),
3975
					esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
3976
				),
3977
				array( 'a' => array( 'href' => true, 'title' => true ) )
3978
			);
3979
		?></p>
3980
	</div>
3981
</div>
3982
<?php endif;
3983
	// only display the notice if the other stuff is not there
3984
	if( $this->can_display_jetpack_manage_notice() && !  $this->error && ! $this->message && ! $this->privacy_checks ) {
3985
		if( isset( $_GET['page'] ) && 'jetpack' != $_GET['page'] )
3986
			$this->opt_in_jetpack_manage_notice();
3987
		}
3988
	}
3989
3990
	/**
3991
	 * Record a stat for later output.  This will only currently output in the admin_footer.
3992
	 */
3993
	function stat( $group, $detail ) {
3994
		if ( ! isset( $this->stats[ $group ] ) )
3995
			$this->stats[ $group ] = array();
3996
		$this->stats[ $group ][] = $detail;
3997
	}
3998
3999
	/**
4000
	 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
4001
	 */
4002
	function do_stats( $method = '' ) {
4003
		if ( is_array( $this->stats ) && count( $this->stats ) ) {
4004
			foreach ( $this->stats as $group => $stats ) {
4005
				if ( is_array( $stats ) && count( $stats ) ) {
4006
					$args = array( "x_jetpack-{$group}" => implode( ',', $stats ) );
4007
					if ( 'server_side' === $method ) {
4008
						self::do_server_side_stat( $args );
4009
					} else {
4010
						echo '<img src="' . esc_url( self::build_stats_url( $args ) ) . '" width="1" height="1" style="display:none;" />';
4011
					}
4012
				}
4013
				unset( $this->stats[ $group ] );
4014
			}
4015
		}
4016
	}
4017
4018
	/**
4019
	 * Runs stats code for a one-off, server-side.
4020
	 *
4021
	 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
4022
	 *
4023
	 * @return bool If it worked.
4024
	 */
4025
	static function do_server_side_stat( $args ) {
4026
		$response = wp_remote_get( esc_url_raw( self::build_stats_url( $args ) ) );
4027
		if ( is_wp_error( $response ) )
4028
			return false;
4029
4030
		if ( 200 !== wp_remote_retrieve_response_code( $response ) )
4031
			return false;
4032
4033
		return true;
4034
	}
4035
4036
	/**
4037
	 * Builds the stats url.
4038
	 *
4039
	 * @param $args array|string The arguments to append to the URL.
4040
	 *
4041
	 * @return string The URL to be pinged.
4042
	 */
4043
	static function build_stats_url( $args ) {
4044
		$defaults = array(
4045
			'v'    => 'wpcom2',
4046
			'rand' => md5( mt_rand( 0, 999 ) . time() ),
4047
		);
4048
		$args     = wp_parse_args( $args, $defaults );
4049
		/**
4050
		 * Filter the URL used as the Stats tracking pixel.
4051
		 *
4052
		 * @since 2.3.2
4053
		 *
4054
		 * @param string $url Base URL used as the Stats tracking pixel.
4055
		 */
4056
		$base_url = apply_filters(
4057
			'jetpack_stats_base_url',
4058
			'https://pixel.wp.com/g.gif'
4059
		);
4060
		$url      = add_query_arg( $args, $base_url );
4061
		return $url;
4062
	}
4063
4064
	static function translate_current_user_to_role() {
4065
		foreach ( self::$capability_translations as $role => $cap ) {
4066
			if ( current_user_can( $role ) || current_user_can( $cap ) ) {
4067
				return $role;
4068
			}
4069
		}
4070
4071
		return false;
4072
	}
4073
4074
	static function translate_role_to_cap( $role ) {
4075
		if ( ! isset( self::$capability_translations[$role] ) ) {
4076
			return false;
4077
		}
4078
4079
		return self::$capability_translations[$role];
4080
	}
4081
4082
	static function sign_role( $role ) {
4083
		if ( ! $user_id = (int) get_current_user_id() ) {
4084
			return false;
4085
		}
4086
4087
		$token = Jetpack_Data::get_access_token();
4088
		if ( ! $token || is_wp_error( $token ) ) {
4089
			return false;
4090
		}
4091
4092
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
4093
	}
4094
4095
4096
	/**
4097
	 * Builds a URL to the Jetpack connection auth page
4098
	 *
4099
	 * @since 3.9.5
4100
	 *
4101
	 * @param bool $raw If true, URL will not be escaped.
4102
	 * @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
4103
	 *                              If string, will be a custom redirect.
4104
	 * @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
4105
	 *
4106
	 * @return string Connect URL
4107
	 */
4108
	function build_connect_url( $raw = false, $redirect = false, $from = false ) {
4109
		if ( ! Jetpack_Options::get_option( 'blog_token' ) || ! Jetpack_Options::get_option( 'id' ) ) {
4110
			$url = Jetpack::nonce_url_no_esc( Jetpack::admin_url( 'action=register' ), 'jetpack-register' );
4111
			if( is_network_admin() ) {
4112
				$url = add_query_arg( 'is_multisite', network_admin_url( 'admin.php?page=jetpack-settings' ), $url );
4113
			}
4114
		} else {
4115
			if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && include_once JETPACK__GLOTPRESS_LOCALES_PATH ) {
4116
				$gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
4117
			}
4118
4119
			$role = self::translate_current_user_to_role();
4120
			$signed_role = self::sign_role( $role );
4121
4122
			$user = wp_get_current_user();
4123
4124
			$jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
4125
			$redirect = $redirect
4126
				? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
4127
				: $jetpack_admin_page;
4128
4129
			if( isset( $_REQUEST['is_multisite'] ) ) {
4130
				$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4131
			}
4132
4133
			$secrets = Jetpack::init()->generate_secrets( 'authorize' );
4134
			@list( $secret ) = explode( ':', $secrets );
4135
4136
			$site_icon = ( function_exists( 'has_site_icon') && has_site_icon() )
4137
				? get_site_icon_url()
4138
				: false;
4139
4140
			/**
4141
			 * Filter the type of authorization.
4142
			 * 'calypso' completes authorization on wordpress.com/jetpack/connect
4143
			 * while 'jetpack' ( or any other value ) completes the authorization at jetpack.wordpress.com.
4144
			 *
4145
			 * @since 4.3.3
4146
			 *
4147
			 * @param string $auth_type Defaults to 'calypso', can also be 'jetpack'.
4148
			 */
4149
			$auth_type = apply_filters( 'jetpack_auth_type', 'calypso' );
4150
4151
			$args = urlencode_deep(
4152
				array(
4153
					'response_type' => 'code',
4154
					'client_id'     => Jetpack_Options::get_option( 'id' ),
4155
					'redirect_uri'  => add_query_arg(
4156
						array(
4157
							'action'   => 'authorize',
4158
							'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
4159
							'redirect' => urlencode( $redirect ),
4160
						),
4161
						esc_url( admin_url( 'admin.php?page=jetpack' ) )
4162
					),
4163
					'state'         => $user->ID,
4164
					'scope'         => $signed_role,
4165
					'user_email'    => $user->user_email,
4166
					'user_login'    => $user->user_login,
4167
					'is_active'     => Jetpack::is_active(),
4168
					'jp_version'    => JETPACK__VERSION,
4169
					'auth_type'     => $auth_type,
4170
					'secret'        => $secret,
4171
					'locale'        => ( isset( $gp_locale ) && isset( $gp_locale->slug ) ) ? $gp_locale->slug : '',
4172
					'blogname'      => get_option( 'blogname' ),
4173
					'site_url'      => site_url(),
4174
					'home_url'      => home_url(),
4175
					'site_icon'     => $site_icon,
4176
				)
4177
			);
4178
4179
			$url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) );
4180
		}
4181
4182
		if ( $from ) {
4183
			$url = add_query_arg( 'from', $from, $url );
4184
		}
4185
4186
		if ( isset( $_GET['calypso_env'] ) ) {
4187
			$url = add_query_arg( 'calypso_env', sanitize_key( $_GET['calypso_env'] ), $url );
4188
		}
4189
4190
		return $raw ? $url : esc_url( $url );
4191
	}
4192
4193
	function build_reconnect_url( $raw = false ) {
4194
		$url = wp_nonce_url( Jetpack::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4195
		return $raw ? $url : esc_url( $url );
4196
	}
4197
4198
	public static function admin_url( $args = null ) {
4199
		$args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
4200
		$url = add_query_arg( $args, admin_url( 'admin.php' ) );
4201
		return $url;
4202
	}
4203
4204
	public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4205
		$actionurl = str_replace( '&amp;', '&', $actionurl );
4206
		return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4207
	}
4208
4209
	function dismiss_jetpack_notice() {
4210
4211
		if ( ! isset( $_GET['jetpack-notice'] ) ) {
4212
			return;
4213
		}
4214
4215
		switch( $_GET['jetpack-notice'] ) {
4216
			case 'dismiss':
4217
				if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4218
4219
					require_once ABSPATH . 'wp-admin/includes/plugin.php';
4220
					deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4221
					wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4222
				}
4223
				break;
4224 View Code Duplication
			case 'jetpack-manage-opt-out':
4225
4226
				if ( check_admin_referer( 'jetpack_manage_banner_opt_out' ) ) {
4227
					// Don't show the banner again
4228
4229
					Jetpack_Options::update_option( 'dismissed_manage_banner', true );
4230
					// redirect back to the page that had the notice
4231
					if ( wp_get_referer() ) {
4232
						wp_safe_redirect( wp_get_referer() );
4233
					} else {
4234
						// Take me to Jetpack
4235
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4236
					}
4237
				}
4238
				break;
4239 View Code Duplication
			case 'jetpack-protect-multisite-opt-out':
4240
4241
				if ( check_admin_referer( 'jetpack_protect_multisite_banner_opt_out' ) ) {
4242
					// Don't show the banner again
4243
4244
					update_site_option( 'jetpack_dismissed_protect_multisite_banner', true );
4245
					// redirect back to the page that had the notice
4246
					if ( wp_get_referer() ) {
4247
						wp_safe_redirect( wp_get_referer() );
4248
					} else {
4249
						// Take me to Jetpack
4250
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4251
					}
4252
				}
4253
				break;
4254
			case 'jetpack-manage-opt-in':
4255
				if ( check_admin_referer( 'jetpack_manage_banner_opt_in' ) ) {
4256
					// This makes sure that we are redirect to jetpack home so that we can see the Success Message.
4257
4258
					$redirection_url = Jetpack::admin_url();
4259
					remove_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4260
4261
					// Don't redirect form the Jetpack Setting Page
4262
					$referer_parsed = parse_url ( wp_get_referer() );
4263
					// check that we do have a wp_get_referer and the query paramater is set orderwise go to the Jetpack Home
4264
					if ( isset( $referer_parsed['query'] ) && false !== strpos( $referer_parsed['query'], 'page=jetpack_modules' ) ) {
4265
						// Take the user to Jetpack home except when on the setting page
4266
						$redirection_url = wp_get_referer();
4267
						add_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4268
					}
4269
					// Also update the JSON API FULL MANAGEMENT Option
4270
					Jetpack::activate_module( 'manage', false, false );
4271
4272
					// Special Message when option in.
4273
					Jetpack::state( 'optin-manage', 'true' );
4274
					// Activate the Module if not activated already
4275
4276
					// Redirect properly
4277
					wp_safe_redirect( $redirection_url );
4278
4279
				}
4280
				break;
4281
		}
4282
	}
4283
4284
	public static function admin_screen_configure_module( $module_id ) {
4285
4286
		// User that doesn't have 'jetpack_configure_modules' will never end up here since Jetpack Landing Page woun't let them.
4287
		if ( ! in_array( $module_id, Jetpack::get_active_modules() ) && current_user_can( 'manage_options' ) ) {
4288
			if ( has_action( 'display_activate_module_setting_' . $module_id ) ) {
4289
				/**
4290
				 * Fires to diplay a custom module activation screen.
4291
				 *
4292
				 * To add a module actionation screen use Jetpack::module_configuration_activation_screen method.
4293
				 * Example: Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
4294
				 *
4295
				 * @module manage
4296
				 *
4297
				 * @since 3.8.0
4298
				 *
4299
				 * @param int $module_id Module ID.
4300
				 */
4301
				do_action( 'display_activate_module_setting_' . $module_id );
4302
			} else {
4303
				self::display_activate_module_link( $module_id );
4304
			}
4305
4306
			return false;
4307
		} ?>
4308
4309
		<div id="jp-settings-screen" style="position: relative">
4310
			<h3>
4311
			<?php
4312
				$module = Jetpack::get_module( $module_id );
4313
				echo '<a href="' . Jetpack::admin_url( 'page=jetpack_modules' ) . '">' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</a> &rarr; ';
4314
				printf( __( 'Configure %s', 'jetpack' ), $module['name'] );
4315
			?>
4316
			</h3>
4317
			<?php
4318
				/**
4319
				 * Fires within the displayed message when a feature configuation is updated.
4320
				 *
4321
				 * @since 3.4.0
4322
				 *
4323
				 * @param int $module_id Module ID.
4324
				 */
4325
				do_action( 'jetpack_notices_update_settings', $module_id );
4326
				/**
4327
				 * Fires when a feature configuation screen is loaded.
4328
				 * The dynamic part of the hook, $module_id, is the module ID.
4329
				 *
4330
				 * @since 1.1.0
4331
				 */
4332
				do_action( 'jetpack_module_configuration_screen_' . $module_id );
4333
			?>
4334
		</div><?php
4335
	}
4336
4337
	/**
4338
	 * Display link to activate the module to see the settings screen.
4339
	 * @param  string $module_id
4340
	 * @return null
4341
	 */
4342
	public static function display_activate_module_link( $module_id ) {
4343
4344
		$info =  Jetpack::get_module( $module_id );
4345
		$extra = '';
4346
		$activate_url = wp_nonce_url(
4347
				Jetpack::admin_url(
4348
					array(
4349
						'page'   => 'jetpack',
4350
						'action' => 'activate',
4351
						'module' => $module_id,
4352
					)
4353
				),
4354
				"jetpack_activate-$module_id"
4355
			);
4356
4357
		?>
4358
4359
		<div class="wrap configure-module">
4360
			<div id="jp-settings-screen">
4361
				<?php
4362
				if ( $module_id == 'json-api' ) {
4363
4364
					$info['name'] = esc_html__( 'Activate Site Management and JSON API', 'jetpack' );
4365
4366
					$activate_url = Jetpack::init()->opt_in_jetpack_manage_url();
4367
4368
					$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' );
4369
4370
					// $extra = __( 'To use Site Management, you need to first activate JSON API to allow remote management of your site. ', 'jetpack' );
4371
				} ?>
4372
4373
				<h3><?php echo esc_html( $info['name'] ); ?></h3>
4374
				<div class="narrow">
4375
					<p><?php echo  $info['description']; ?></p>
4376
					<?php if( $extra ) { ?>
4377
					<p><?php echo esc_html( $extra ); ?></p>
4378
					<?php } ?>
4379
					<p>
4380
						<?php
4381
						if( wp_get_referer() ) {
4382
							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() );
4383
						} else {
4384
							printf( __( '<a class="button-primary" href="%s">Activate Now</a>', 'jetpack' ) , $activate_url  );
4385
						} ?>
4386
					</p>
4387
				</div>
4388
4389
			</div>
4390
		</div>
4391
4392
		<?php
4393
	}
4394
4395
	public static function sort_modules( $a, $b ) {
4396
		if ( $a['sort'] == $b['sort'] )
4397
			return 0;
4398
4399
		return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
4400
	}
4401
4402
	function ajax_recheck_ssl() {
4403
		check_ajax_referer( 'recheck-ssl', 'ajax-nonce' );
4404
		$result = Jetpack::permit_ssl( true );
4405
		wp_send_json( array(
4406
			'enabled' => $result,
4407
			'message' => get_transient( 'jetpack_https_test_message' )
4408
		) );
4409
	}
4410
4411
/* Client API */
4412
4413
	/**
4414
	 * Returns the requested Jetpack API URL
4415
	 *
4416
	 * @return string
4417
	 */
4418
	public static function api_url( $relative_url ) {
4419
		return trailingslashit( JETPACK__API_BASE . $relative_url  ) . JETPACK__API_VERSION . '/';
4420
	}
4421
4422
	/**
4423
	 * Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
4424
	 */
4425
	public static function fix_url_for_bad_hosts( $url ) {
4426
		if ( 0 !== strpos( $url, 'https://' ) ) {
4427
			return $url;
4428
		}
4429
4430
		switch ( JETPACK_CLIENT__HTTPS ) {
4431
			case 'ALWAYS' :
4432
				return $url;
4433
			case 'NEVER' :
4434
				return set_url_scheme( $url, 'http' );
4435
			// default : case 'AUTO' :
4436
		}
4437
4438
		// we now return the unmodified SSL URL by default, as a security precaution
4439
		return $url;
4440
	}
4441
4442
	/**
4443
	 * Checks to see if the URL is using SSL to connect with Jetpack
4444
	 *
4445
	 * @since 2.3.3
4446
	 * @return boolean
4447
	 */
4448
	public static function permit_ssl( $force_recheck = false ) {
4449
		// Do some fancy tests to see if ssl is being supported
4450
		if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
4451
			$message = '';
4452
			if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
4453
				$ssl = 0;
4454
			} else {
4455
				switch ( JETPACK_CLIENT__HTTPS ) {
4456
					case 'NEVER':
4457
						$ssl = 0;
4458
						$message = __( 'JETPACK_CLIENT__HTTPS is set to NEVER', 'jetpack' );
4459
						break;
4460
					case 'ALWAYS':
4461
					case 'AUTO':
4462
					default:
4463
						$ssl = 1;
4464
						break;
4465
				}
4466
4467
				// If it's not 'NEVER', test to see
4468
				if ( $ssl ) {
4469
					if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4470
						$ssl = 0;
4471
						$message = __( 'WordPress reports no SSL support', 'jetpack' );
4472
					} else {
4473
						$response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4474
						if ( is_wp_error( $response ) ) {
4475
							$ssl = 0;
4476
							$message = __( 'WordPress reports no SSL support', 'jetpack' );
4477
						} elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4478
							$ssl = 0;
4479
							$message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4480
						}
4481
					}
4482
				}
4483
			}
4484
			set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4485
			set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
4486
		}
4487
4488
		return (bool) $ssl;
4489
	}
4490
4491
	/*
4492
	 * Displays an admin_notice, alerting the user to their JETPACK_CLIENT__HTTPS constant being 'AUTO' but SSL isn't working.
4493
	 */
4494
	public function alert_auto_ssl_fail() {
4495
		if ( ! current_user_can( 'manage_options' ) )
4496
			return;
4497
4498
		$ajax_nonce = wp_create_nonce( 'recheck-ssl' );
4499
		?>
4500
4501
		<div id="jetpack-ssl-warning" class="error jp-identity-crisis">
4502
			<div class="jp-banner__content">
4503
				<h2><?php _e( 'Outbound HTTPS not working', 'jetpack' ); ?></h2>
4504
				<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>
4505
				<p>
4506
					<?php _e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
4507
					<a href="#" id="jetpack-recheck-ssl-button"><?php _e( 'Try again', 'jetpack' ); ?></a>
4508
					<span id="jetpack-recheck-ssl-output"><?php echo get_transient( 'jetpack_https_test_message' ); ?></span>
4509
				</p>
4510
				<p>
4511
					<?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' ),
4512
							esc_url( Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) ),
4513
							esc_url( 'https://jetpack.com/support/getting-started-with-jetpack/troubleshooting-tips/' ) ); ?>
4514
				</p>
4515
			</div>
4516
		</div>
4517
		<style>
4518
			#jetpack-recheck-ssl-output { margin-left: 5px; color: red; }
4519
		</style>
4520
		<script type="text/javascript">
4521
			jQuery( document ).ready( function( $ ) {
4522
				$( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4523
					var $this = $( this );
4524
					$this.html( <?php echo json_encode( __( 'Checking', 'jetpack' ) ); ?> );
4525
					$( '#jetpack-recheck-ssl-output' ).html( '' );
4526
					e.preventDefault();
4527
					var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': '<?php echo $ajax_nonce; ?>' };
4528
					$.post( ajaxurl, data )
4529
					  .done( function( response ) {
4530
					  	if ( response.enabled ) {
4531
					  		$( '#jetpack-ssl-warning' ).hide();
4532
					  	} else {
4533
					  		this.html( <?php echo json_encode( __( 'Try again', 'jetpack' ) ); ?> );
4534
					  		$( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4535
					  	}
4536
					  }.bind( $this ) );
4537
				} );
4538
			} );
4539
		</script>
4540
4541
		<?php
4542
	}
4543
4544
	/**
4545
	 * Returns the Jetpack XML-RPC API
4546
	 *
4547
	 * @return string
4548
	 */
4549
	public static function xmlrpc_api_url() {
4550
		$base = preg_replace( '#(https?://[^?/]+)(/?.*)?$#', '\\1', JETPACK__API_BASE );
4551
		return untrailingslashit( $base ) . '/xmlrpc.php';
4552
	}
4553
4554
	/**
4555
	 * Creates two secret tokens and the end of life timestamp for them.
4556
	 *
4557
	 * Note these tokens are unique per call, NOT static per site for connecting.
4558
	 *
4559
	 * @since 2.6
4560
	 * @return array
4561
	 */
4562
	public function generate_secrets( $action, $exp = 600 ) {
4563
	    $secret = wp_generate_password( 32, false ) // secret_1
4564
	    		. ':' . wp_generate_password( 32, false ) // secret_2
4565
	    		. ':' . ( time() + $exp ) // eol ( End of Life )
4566
	    		. ':' . get_current_user_id(); // ties the secrets to the current user
4567
		Jetpack_Options::update_option( $action, $secret );
4568
	    return Jetpack_Options::get_option( $action );
4569
	}
4570
4571
	/**
4572
	 * Builds the timeout limit for queries talking with the wpcom servers.
4573
	 *
4574
	 * Based on local php max_execution_time in php.ini
4575
	 *
4576
	 * @since 2.6
4577
	 * @return int
4578
	 **/
4579
	public function get_remote_query_timeout_limit() {
4580
	    $timeout = (int) ini_get( 'max_execution_time' );
4581
	    if ( ! $timeout ) // Ensure exec time set in php.ini
4582
				$timeout = 30;
4583
	    return intval( $timeout / 2 );
4584
	}
4585
4586
4587
	/**
4588
	 * Takes the response from the Jetpack register new site endpoint and
4589
	 * verifies it worked properly.
4590
	 *
4591
	 * @since 2.6
4592
	 * @return string|Jetpack_Error A JSON object on success or Jetpack_Error on failures
4593
	 **/
4594
	public function validate_remote_register_response( $response ) {
4595
	  if ( is_wp_error( $response ) ) {
4596
			return new Jetpack_Error( 'register_http_request_failed', $response->get_error_message() );
4597
		}
4598
4599
		$code   = wp_remote_retrieve_response_code( $response );
4600
		$entity = wp_remote_retrieve_body( $response );
4601
		if ( $entity )
4602
			$registration_response = json_decode( $entity );
4603
		else
4604
			$registration_response = false;
4605
4606
		$code_type = intval( $code / 100 );
4607
		if ( 5 == $code_type ) {
4608
			return new Jetpack_Error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4609
		} elseif ( 408 == $code ) {
4610
			return new Jetpack_Error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4611
		} elseif ( ! empty( $registration_response->error ) ) {
4612
			if ( 'xml_rpc-32700' == $registration_response->error && ! function_exists( 'xml_parser_create' ) ) {
4613
				$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' );
4614
			} else {
4615
				$error_description = isset( $registration_response->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $registration_response->error_description ) : '';
4616
			}
4617
4618
			return new Jetpack_Error( (string) $registration_response->error, $error_description, $code );
4619
		} elseif ( 200 != $code ) {
4620
			return new Jetpack_Error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4621
		}
4622
4623
		// Jetpack ID error block
4624
		if ( empty( $registration_response->jetpack_id ) ) {
4625
			return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ), $entity );
4626
		} elseif ( ! is_scalar( $registration_response->jetpack_id ) ) {
4627
			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 );
4628
		} elseif ( preg_match( '/[^0-9]/', $registration_response->jetpack_id ) ) {
4629
			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 );
4630
		}
4631
4632
	    return $registration_response;
4633
	}
4634
	/**
4635
	 * @return bool|WP_Error
4636
	 */
4637
	public static function register() {
4638
		add_action( 'pre_update_jetpack_option_register', array( 'Jetpack_Options', 'delete_option' ) );
4639
		$secrets = Jetpack::init()->generate_secrets( 'register' );
4640
4641
		@list( $secret_1, $secret_2, $secret_eol ) = explode( ':', $secrets );
4642 View Code Duplication
		if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) {
4643
			return new Jetpack_Error( 'missing_secrets' );
4644
		}
4645
4646
		$timeout = Jetpack::init()->get_remote_query_timeout_limit();
4647
4648
		$gmt_offset = get_option( 'gmt_offset' );
4649
		if ( ! $gmt_offset ) {
4650
			$gmt_offset = 0;
4651
		}
4652
4653
		$stats_options = get_option( 'stats_options' );
4654
		$stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null;
4655
4656
		$args = array(
4657
			'method'  => 'POST',
4658
			'body'    => array(
4659
				'siteurl'         => site_url(),
4660
				'home'            => home_url(),
4661
				'gmt_offset'      => $gmt_offset,
4662
				'timezone_string' => (string) get_option( 'timezone_string' ),
4663
				'site_name'       => (string) get_option( 'blogname' ),
4664
				'secret_1'        => $secret_1,
4665
				'secret_2'        => $secret_2,
4666
				'site_lang'       => get_locale(),
4667
				'timeout'         => $timeout,
4668
				'stats_id'        => $stats_id,
4669
				'state'           => get_current_user_id(),
4670
			),
4671
			'headers' => array(
4672
				'Accept' => 'application/json',
4673
			),
4674
			'timeout' => $timeout,
4675
		);
4676
		$response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'register' ) ), $args, true );
4677
4678
		// Make sure the response is valid and does not contain any Jetpack errors
4679
		$registration_details = Jetpack::init()->validate_remote_register_response( $response );
4680
		if ( is_wp_error( $registration_details ) ) {
4681
		    return $registration_details;
4682
		} elseif ( ! $registration_details ) {
4683
			return new Jetpack_Error( 'unknown_error', __( 'Unknown error registering your Jetpack site', 'jetpack' ), wp_remote_retrieve_response_code( $response ) );
4684
		}
4685
4686 View Code Duplication
		if ( empty( $registration_details->jetpack_secret ) || ! is_string( $registration_details->jetpack_secret ) ) {
4687
			return new Jetpack_Error( 'jetpack_secret', '', wp_remote_retrieve_response_code( $response ) );
4688
		}
4689
4690
		if ( isset( $registration_details->jetpack_public ) ) {
4691
			$jetpack_public = (int) $registration_details->jetpack_public;
4692
		} else {
4693
			$jetpack_public = false;
4694
		}
4695
4696
		Jetpack_Options::update_options(
4697
			array(
4698
				'id'         => (int)    $registration_details->jetpack_id,
4699
				'blog_token' => (string) $registration_details->jetpack_secret,
4700
				'public'     => $jetpack_public,
4701
			)
4702
		);
4703
4704
		/**
4705
		 * Fires when a site is registered on WordPress.com.
4706
		 *
4707
		 * @since 3.7.0
4708
		 *
4709
		 * @param int $json->jetpack_id Jetpack Blog ID.
4710
		 * @param string $json->jetpack_secret Jetpack Blog Token.
4711
		 * @param int|bool $jetpack_public Is the site public.
4712
		 */
4713
		do_action( 'jetpack_site_registered', $registration_details->jetpack_id, $registration_details->jetpack_secret, $jetpack_public );
4714
4715
		// Initialize Jump Start for the first and only time.
4716
		if ( ! Jetpack_Options::get_option( 'jumpstart' ) ) {
4717
			Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
4718
4719
			$jetpack = Jetpack::init();
4720
4721
			$jetpack->stat( 'jumpstart', 'unique-views' );
4722
			$jetpack->do_stats( 'server_side' );
4723
		};
4724
4725
		return true;
4726
	}
4727
4728
	/**
4729
	 * If the db version is showing something other that what we've got now, bump it to current.
4730
	 *
4731
	 * @return bool: True if the option was incorrect and updated, false if nothing happened.
4732
	 */
4733
	public static function maybe_set_version_option() {
4734
		list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
4735
		if ( JETPACK__VERSION != $version ) {
4736
			Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
4737
4738
			if ( version_compare( JETPACK__VERSION, $version, '>' ) ) {
4739
				/** This action is documented in class.jetpack.php */
4740
				do_action( 'updating_jetpack_version', JETPACK__VERSION, $version );
4741
			}
4742
4743
			return true;
4744
		}
4745
		return false;
4746
	}
4747
4748
/* Client Server API */
4749
4750
	/**
4751
	 * Loads the Jetpack XML-RPC client
4752
	 */
4753
	public static function load_xml_rpc_client() {
4754
		require_once ABSPATH . WPINC . '/class-IXR.php';
4755
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-ixr-client.php';
4756
	}
4757
4758
	/**
4759
	 * Resets the saved authentication state in between testing requests.
4760
	 */
4761
	public function reset_saved_auth_state() {
4762
		$this->xmlrpc_verification = null;
4763
		$this->rest_authentication_status = null;
4764
	}
4765
4766
	function verify_xml_rpc_signature() {
4767
		if ( $this->xmlrpc_verification ) {
4768
			return $this->xmlrpc_verification;
4769
		}
4770
4771
		// It's not for us
4772
		if ( ! isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
4773
			return false;
4774
		}
4775
4776
		@list( $token_key, $version, $user_id ) = explode( ':', $_GET['token'] );
4777
		if (
4778
			empty( $token_key )
4779
		||
4780
			empty( $version ) || strval( JETPACK__API_VERSION ) !== $version
4781
		) {
4782
			return false;
4783
		}
4784
4785
		if ( '0' === $user_id ) {
4786
			$token_type = 'blog';
4787
			$user_id = 0;
4788
		} else {
4789
			$token_type = 'user';
4790
			if ( empty( $user_id ) || ! ctype_digit( $user_id ) ) {
4791
				return false;
4792
			}
4793
			$user_id = (int) $user_id;
4794
4795
			$user = new WP_User( $user_id );
4796
			if ( ! $user || ! $user->exists() ) {
4797
				return false;
4798
			}
4799
		}
4800
4801
		$token = Jetpack_Data::get_access_token( $user_id );
4802
		if ( ! $token ) {
4803
			return false;
4804
		}
4805
4806
		$token_check = "$token_key.";
4807
		if ( ! hash_equals( substr( $token->secret, 0, strlen( $token_check ) ), $token_check ) ) {
4808
			return false;
4809
		}
4810
4811
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
4812
4813
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
4814
		if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
4815
			$post_data   = $_POST;
4816
			$file_hashes = array();
4817
			foreach ( $post_data as $post_data_key => $post_data_value ) {
4818
				if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
4819
					continue;
4820
				}
4821
				$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
4822
				$file_hashes[$post_data_key] = $post_data_value;
4823
			}
4824
4825
			foreach ( $file_hashes as $post_data_key => $post_data_value ) {
4826
				unset( $post_data["_jetpack_file_hmac_{$post_data_key}"] );
4827
				$post_data[$post_data_key] = $post_data_value;
4828
			}
4829
4830
			ksort( $post_data );
4831
4832
			$body = http_build_query( stripslashes_deep( $post_data ) );
4833
		} elseif ( is_null( $this->HTTP_RAW_POST_DATA ) ) {
4834
			$body = file_get_contents( 'php://input' );
4835
		} else {
4836
			$body = null;
4837
		}
4838
4839
		$signature = $jetpack_signature->sign_current_request(
4840
			array( 'body' => is_null( $body ) ? $this->HTTP_RAW_POST_DATA : $body, )
4841
		);
4842
4843
		if ( ! $signature ) {
4844
			return false;
4845
		} else if ( is_wp_error( $signature ) ) {
4846
			return $signature;
4847
		} else if ( ! hash_equals( $signature, $_GET['signature'] ) ) {
4848
			return false;
4849
		}
4850
4851
		$timestamp = (int) $_GET['timestamp'];
4852
		$nonce     = stripslashes( (string) $_GET['nonce'] );
4853
4854
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
4855
			return false;
4856
		}
4857
4858
		$this->xmlrpc_verification = array(
4859
			'type'    => $token_type,
4860
			'user_id' => $token->external_user_id,
4861
		);
4862
4863
		return $this->xmlrpc_verification;
4864
	}
4865
4866
	/**
4867
	 * Authenticates XML-RPC and other requests from the Jetpack Server
4868
	 */
4869
	function authenticate_jetpack( $user, $username, $password ) {
4870
		if ( is_a( $user, 'WP_User' ) ) {
4871
			return $user;
4872
		}
4873
4874
		$token_details = $this->verify_xml_rpc_signature();
4875
4876
		if ( ! $token_details || is_wp_error( $token_details ) ) {
4877
			return $user;
4878
		}
4879
4880
		if ( 'user' !== $token_details['type'] ) {
4881
			return $user;
4882
		}
4883
4884
		if ( ! $token_details['user_id'] ) {
4885
			return $user;
4886
		}
4887
4888
		nocache_headers();
4889
4890
		return new WP_User( $token_details['user_id'] );
4891
	}
4892
4893
	// Authenticates requests from Jetpack server to WP REST API endpoints.
4894
	// Uses the existing XMLRPC request signing implementation.
4895
	function wp_rest_authenticate( $user ) {
4896
		if ( ! empty( $user ) ) {
4897
			// Another authentication method is in effect.
4898
			return $user;
4899
		}
4900
4901
		if ( ! isset( $_GET['_for'] ) || $_GET['_for'] !== 'jetpack' ) {
4902
			// Nothing to do for this authentication method.
4903
			return null;
4904
		}
4905
4906
		if ( ! isset( $_GET['token'] ) && ! isset( $_GET['signature'] ) ) {
4907
			// Nothing to do for this authentication method.
4908
			return null;
4909
		}
4910
4911
		// Ensure that we always have the request body available.  At this
4912
		// point, the WP REST API code to determine the request body has not
4913
		// run yet.  That code may try to read from 'php://input' later, but
4914
		// this can only be done once per request in PHP versions prior to 5.6.
4915
		// So we will go ahead and perform this read now if needed, and save
4916
		// the request body where both the Jetpack signature verification code
4917
		// and the WP REST API code can see it.
4918
		if ( ! isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ) {
4919
			$GLOBALS['HTTP_RAW_POST_DATA'] = file_get_contents( 'php://input' );
4920
		}
4921
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
4922
4923
		// Only support specific request parameters that have been tested and
4924
		// are known to work with signature verification.  A different method
4925
		// can be passed to the WP REST API via the '?_method=' parameter if
4926
		// needed.
4927
		if ( $_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
4928
			$this->rest_authentication_status = new WP_Error(
4929
				'rest_invalid_request',
4930
				__( 'This request method is not supported.', 'jetpack' ),
4931
				array( 'status' => 400 )
4932
			);
4933
			return null;
4934
		}
4935
		if ( $_SERVER['REQUEST_METHOD'] !== 'POST' && ! empty( $this->HTTP_RAW_POST_DATA ) ) {
4936
			$this->rest_authentication_status = new WP_Error(
4937
				'rest_invalid_request',
4938
				__( 'This request method does not support body parameters.', 'jetpack' ),
4939
				array( 'status' => 400 )
4940
			);
4941
			return null;
4942
		}
4943
4944
		if ( ! empty( $_SERVER['CONTENT_TYPE'] ) ) {
4945
			$content_type = $_SERVER['CONTENT_TYPE'];
4946
		} elseif ( ! empty( $_SERVER['HTTP_CONTENT_TYPE'] ) ) {
4947
			$content_type = $_SERVER['HTTP_CONTENT_TYPE'];
4948
		}
4949
4950
		if (
4951
			isset( $content_type ) &&
4952
			$content_type !== 'application/x-www-form-urlencoded' &&
4953
			$content_type !== 'application/json'
4954
		) {
4955
			$this->rest_authentication_status = new WP_Error(
4956
				'rest_invalid_request',
4957
				__( 'This Content-Type is not supported.', 'jetpack' ),
4958
				array( 'status' => 400 )
4959
			);
4960
			return null;
4961
		}
4962
4963
		$verified = $this->verify_xml_rpc_signature();
4964
4965
		if ( is_wp_error( $verified ) ) {
4966
			$this->rest_authentication_status = $verified;
4967
			return null;
4968
		}
4969
4970
		if (
4971
			$verified &&
4972
			isset( $verified['type'] ) &&
4973
			'user' === $verified['type'] &&
4974
			! empty( $verified['user_id'] )
4975
		) {
4976
			// Authentication successful.
4977
			$this->rest_authentication_status = true;
4978
			return $verified['user_id'];
4979
		}
4980
4981
		// Something else went wrong.  Probably a signature error.
4982
		$this->rest_authentication_status = new WP_Error(
4983
			'rest_invalid_signature',
4984
			__( 'The request is not signed correctly.', 'jetpack' ),
4985
			array( 'status' => 400 )
4986
		);
4987
		return null;
4988
	}
4989
4990
	/**
4991
	 * Report authentication status to the WP REST API.
4992
	 *
4993
	 * @param  WP_Error|mixed $result Error from another authentication handler, null if we should handle it, or another value if not
4994
	 * @return WP_Error|boolean|null {@see WP_JSON_Server::check_authentication}
4995
	 */
4996
	public function wp_rest_authentication_errors( $value ) {
4997
		if ( $value !== null ) {
4998
			return $value;
4999
		}
5000
		return $this->rest_authentication_status;
5001
	}
5002
5003
	function add_nonce( $timestamp, $nonce ) {
5004
		global $wpdb;
5005
		static $nonces_used_this_request = array();
5006
5007
		if ( isset( $nonces_used_this_request["$timestamp:$nonce"] ) ) {
5008
			return $nonces_used_this_request["$timestamp:$nonce"];
5009
		}
5010
5011
		// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce
5012
		$timestamp = (int) $timestamp;
5013
		$nonce     = esc_sql( $nonce );
5014
5015
		// Raw query so we can avoid races: add_option will also update
5016
		$show_errors = $wpdb->show_errors( false );
5017
5018
		$old_nonce = $wpdb->get_row(
5019
			$wpdb->prepare( "SELECT * FROM `$wpdb->options` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
5020
		);
5021
5022
		if ( is_null( $old_nonce ) ) {
5023
			$return = $wpdb->query(
5024
				$wpdb->prepare(
5025
					"INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
5026
					"jetpack_nonce_{$timestamp}_{$nonce}",
5027
					time(),
5028
					'no'
5029
				)
5030
			);
5031
		} else {
5032
			$return = false;
5033
		}
5034
5035
		$wpdb->show_errors( $show_errors );
5036
5037
		$nonces_used_this_request["$timestamp:$nonce"] = $return;
5038
5039
		return $return;
5040
	}
5041
5042
	/**
5043
	 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths since it is passed by reference to various methods.
5044
	 * Capture it here so we can verify the signature later.
5045
	 */
5046
	function xmlrpc_methods( $methods ) {
5047
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
5048
		return $methods;
5049
	}
5050
5051
	function public_xmlrpc_methods( $methods ) {
5052
		if ( array_key_exists( 'wp.getOptions', $methods ) ) {
5053
			$methods['wp.getOptions'] = array( $this, 'jetpack_getOptions' );
5054
		}
5055
		return $methods;
5056
	}
5057
5058
	function jetpack_getOptions( $args ) {
5059
		global $wp_xmlrpc_server;
5060
5061
		$wp_xmlrpc_server->escape( $args );
5062
5063
		$username	= $args[1];
5064
		$password	= $args[2];
5065
5066
		if ( !$user = $wp_xmlrpc_server->login($username, $password) ) {
5067
			return $wp_xmlrpc_server->error;
5068
		}
5069
5070
		$options = array();
5071
		$user_data = $this->get_connected_user_data();
5072
		if ( is_array( $user_data ) ) {
5073
			$options['jetpack_user_id'] = array(
5074
				'desc'          => __( 'The WP.com user ID of the connected user', 'jetpack' ),
5075
				'readonly'      => true,
5076
				'value'         => $user_data['ID'],
5077
			);
5078
			$options['jetpack_user_login'] = array(
5079
				'desc'          => __( 'The WP.com username of the connected user', 'jetpack' ),
5080
				'readonly'      => true,
5081
				'value'         => $user_data['login'],
5082
			);
5083
			$options['jetpack_user_email'] = array(
5084
				'desc'          => __( 'The WP.com user email of the connected user', 'jetpack' ),
5085
				'readonly'      => true,
5086
				'value'         => $user_data['email'],
5087
			);
5088
			$options['jetpack_user_site_count'] = array(
5089
				'desc'          => __( 'The number of sites of the connected WP.com user', 'jetpack' ),
5090
				'readonly'      => true,
5091
				'value'         => $user_data['site_count'],
5092
			);
5093
		}
5094
		$wp_xmlrpc_server->blog_options = array_merge( $wp_xmlrpc_server->blog_options, $options );
5095
		$args = stripslashes_deep( $args );
5096
		return $wp_xmlrpc_server->wp_getOptions( $args );
5097
	}
5098
5099
	function xmlrpc_options( $options ) {
5100
		$jetpack_client_id = false;
5101
		if ( self::is_active() ) {
5102
			$jetpack_client_id = Jetpack_Options::get_option( 'id' );
5103
		}
5104
		$options['jetpack_version'] = array(
5105
				'desc'          => __( 'Jetpack Plugin Version', 'jetpack' ),
5106
				'readonly'      => true,
5107
				'value'         => JETPACK__VERSION,
5108
		);
5109
5110
		$options['jetpack_client_id'] = array(
5111
				'desc'          => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack' ),
5112
				'readonly'      => true,
5113
				'value'         => $jetpack_client_id,
5114
		);
5115
		return $options;
5116
	}
5117
5118
	public static function clean_nonces( $all = false ) {
5119
		global $wpdb;
5120
5121
		$sql = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
5122
		$sql_args = array( $wpdb->esc_like( 'jetpack_nonce_' ) . '%' );
5123
5124
		if ( true !== $all ) {
5125
			$sql .= ' AND CAST( `option_value` AS UNSIGNED ) < %d';
5126
			$sql_args[] = time() - 3600;
5127
		}
5128
5129
		$sql .= ' ORDER BY `option_id` LIMIT 100';
5130
5131
		$sql = $wpdb->prepare( $sql, $sql_args );
5132
5133
		for ( $i = 0; $i < 1000; $i++ ) {
5134
			if ( ! $wpdb->query( $sql ) ) {
5135
				break;
5136
			}
5137
		}
5138
	}
5139
5140
	/**
5141
	 * State is passed via cookies from one request to the next, but never to subsequent requests.
5142
	 * SET: state( $key, $value );
5143
	 * GET: $value = state( $key );
5144
	 *
5145
	 * @param string $key
5146
	 * @param string $value
5147
	 * @param bool $restate private
5148
	 */
5149
	public static function state( $key = null, $value = null, $restate = false ) {
5150
		static $state = array();
5151
		static $path, $domain;
5152
		if ( ! isset( $path ) ) {
5153
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
5154
			$admin_url = Jetpack::admin_url();
5155
			$bits      = parse_url( $admin_url );
5156
5157
			if ( is_array( $bits ) ) {
5158
				$path   = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
5159
				$domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
5160
			} else {
5161
				$path = $domain = null;
5162
			}
5163
		}
5164
5165
		// Extract state from cookies and delete cookies
5166
		if ( isset( $_COOKIE[ 'jetpackState' ] ) && is_array( $_COOKIE[ 'jetpackState' ] ) ) {
5167
			$yum = $_COOKIE[ 'jetpackState' ];
5168
			unset( $_COOKIE[ 'jetpackState' ] );
5169
			foreach ( $yum as $k => $v ) {
5170
				if ( strlen( $v ) )
5171
					$state[ $k ] = $v;
5172
				setcookie( "jetpackState[$k]", false, 0, $path, $domain );
5173
			}
5174
		}
5175
5176
		if ( $restate ) {
5177
			foreach ( $state as $k => $v ) {
5178
				setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
5179
			}
5180
			return;
5181
		}
5182
5183
		// Get a state variable
5184
		if ( isset( $key ) && ! isset( $value ) ) {
5185
			if ( array_key_exists( $key, $state ) )
5186
				return $state[ $key ];
5187
			return null;
5188
		}
5189
5190
		// Set a state variable
5191
		if ( isset ( $key ) && isset( $value ) ) {
5192
			if( is_array( $value ) && isset( $value[0] ) ) {
5193
				$value = $value[0];
5194
			}
5195
			$state[ $key ] = $value;
5196
			setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
5197
		}
5198
	}
5199
5200
	public static function restate() {
5201
		Jetpack::state( null, null, true );
5202
	}
5203
5204
	public static function check_privacy( $file ) {
5205
		static $is_site_publicly_accessible = null;
5206
5207
		if ( is_null( $is_site_publicly_accessible ) ) {
5208
			$is_site_publicly_accessible = false;
5209
5210
			Jetpack::load_xml_rpc_client();
5211
			$rpc = new Jetpack_IXR_Client();
5212
5213
			$success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
5214
			if ( $success ) {
5215
				$response = $rpc->getResponse();
5216
				if ( $response ) {
5217
					$is_site_publicly_accessible = true;
5218
				}
5219
			}
5220
5221
			Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
5222
		}
5223
5224
		if ( $is_site_publicly_accessible ) {
5225
			return;
5226
		}
5227
5228
		$module_slug = self::get_module_slug( $file );
5229
5230
		$privacy_checks = Jetpack::state( 'privacy_checks' );
5231
		if ( ! $privacy_checks ) {
5232
			$privacy_checks = $module_slug;
5233
		} else {
5234
			$privacy_checks .= ",$module_slug";
5235
		}
5236
5237
		Jetpack::state( 'privacy_checks', $privacy_checks );
5238
	}
5239
5240
	/**
5241
	 * Helper method for multicall XMLRPC.
5242
	 */
5243
	public static function xmlrpc_async_call() {
5244
		global $blog_id;
5245
		static $clients = array();
5246
5247
		$client_blog_id = is_multisite() ? $blog_id : 0;
5248
5249
		if ( ! isset( $clients[$client_blog_id] ) ) {
5250
			Jetpack::load_xml_rpc_client();
5251
			$clients[$client_blog_id] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => JETPACK_MASTER_USER, ) );
5252
			if ( function_exists( 'ignore_user_abort' ) ) {
5253
				ignore_user_abort( true );
5254
			}
5255
			add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
5256
		}
5257
5258
		$args = func_get_args();
5259
5260
		if ( ! empty( $args[0] ) ) {
5261
			call_user_func_array( array( $clients[$client_blog_id], 'addCall' ), $args );
5262
		} elseif ( is_multisite() ) {
5263
			foreach ( $clients as $client_blog_id => $client ) {
5264
				if ( ! $client_blog_id || empty( $client->calls ) ) {
5265
					continue;
5266
				}
5267
5268
				$switch_success = switch_to_blog( $client_blog_id, true );
5269
				if ( ! $switch_success ) {
5270
					continue;
5271
				}
5272
5273
				flush();
5274
				$client->query();
5275
5276
				restore_current_blog();
5277
			}
5278
		} else {
5279
			if ( isset( $clients[0] ) && ! empty( $clients[0]->calls ) ) {
5280
				flush();
5281
				$clients[0]->query();
5282
			}
5283
		}
5284
	}
5285
5286
	public static function staticize_subdomain( $url ) {
5287
5288
		// Extract hostname from URL
5289
		$host = parse_url( $url, PHP_URL_HOST );
5290
5291
		// Explode hostname on '.'
5292
		$exploded_host = explode( '.', $host );
5293
5294
		// Retrieve the name and TLD
5295
		if ( count( $exploded_host ) > 1 ) {
5296
			$name = $exploded_host[ count( $exploded_host ) - 2 ];
5297
			$tld = $exploded_host[ count( $exploded_host ) - 1 ];
5298
			// Rebuild domain excluding subdomains
5299
			$domain = $name . '.' . $tld;
5300
		} else {
5301
			$domain = $host;
5302
		}
5303
		// Array of Automattic domains
5304
		$domain_whitelist = array( 'wordpress.com', 'wp.com' );
5305
5306
		// Return $url if not an Automattic domain
5307
		if ( ! in_array( $domain, $domain_whitelist ) ) {
5308
			return $url;
5309
		}
5310
5311
		if ( is_ssl() ) {
5312
			return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
5313
		}
5314
5315
		srand( crc32( basename( $url ) ) );
5316
		$static_counter = rand( 0, 2 );
5317
		srand(); // this resets everything that relies on this, like array_rand() and shuffle()
5318
5319
		return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
5320
	}
5321
5322
/* JSON API Authorization */
5323
5324
	/**
5325
	 * Handles the login action for Authorizing the JSON API
5326
	 */
5327
	function login_form_json_api_authorization() {
5328
		$this->verify_json_api_authorization_request();
5329
5330
		add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
5331
5332
		add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
5333
		add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5334
		add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
5335
	}
5336
5337
	// Make sure the login form is POSTed to the signed URL so we can reverify the request
5338
	function post_login_form_to_signed_url( $url, $path, $scheme ) {
5339
		if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
5340
			return $url;
5341
		}
5342
5343
		$parsed_url = parse_url( $url );
5344
		$url = strtok( $url, '?' );
5345
		$url = "$url?{$_SERVER['QUERY_STRING']}";
5346
		if ( ! empty( $parsed_url['query'] ) )
5347
			$url .= "&{$parsed_url['query']}";
5348
5349
		return $url;
5350
	}
5351
5352
	// Make sure the POSTed request is handled by the same action
5353
	function preserve_action_in_login_form_for_json_api_authorization() {
5354
		echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
5355
		echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) . "' />\n";
5356
	}
5357
5358
	// If someone logs in to approve API access, store the Access Code in usermeta
5359
	function store_json_api_authorization_token( $user_login, $user ) {
5360
		add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5361
		add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
5362
		$token = wp_generate_password( 32, false );
5363
		update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5364
	}
5365
5366
	// Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
5367
	function allow_wpcom_public_api_domain( $domains ) {
5368
		$domains[] = 'public-api.wordpress.com';
5369
		return $domains;
5370
	}
5371
5372
	// Add the Access Code details to the public-api.wordpress.com redirect
5373
	function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5374
		return add_query_arg(
5375
			urlencode_deep(
5376
				array(
5377
					'jetpack-code'    => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5378
					'jetpack-user-id' => (int) $user->ID,
5379
					'jetpack-state'   => $this->json_api_authorization_request['state'],
5380
				)
5381
			),
5382
			$redirect_to
5383
		);
5384
	}
5385
5386
5387
	/**
5388
	 * Verifies the request by checking the signature
5389
	 *
5390
	 * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
5391
	 * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
5392
	 *
5393
	 * @param null|array $environment
5394
	 */
5395
	function verify_json_api_authorization_request( $environment = null ) {
5396
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
5397
5398
		$environment = is_null( $environment )
5399
			? $_REQUEST
5400
			: $environment;
5401
5402
		list( $envToken, $envVersion, $envUserId ) = explode( ':', $environment['token'] );
5403
		$token = Jetpack_Data::get_access_token( $envUserId );
5404
		if ( ! $token || empty( $token->secret ) ) {
5405
			wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.' , 'jetpack' ) );
5406
		}
5407
5408
		$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' );
5409
5410
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5411
5412
		if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
5413
			$signature = $jetpack_signature->sign_request(
5414
				$environment['token'],
5415
				$environment['timestamp'],
5416
				$environment['nonce'],
5417
				'',
5418
				'GET',
5419
				$environment['jetpack_json_api_original_query'],
5420
				null,
5421
				true
5422
			);
5423
		} else {
5424
			$signature = $jetpack_signature->sign_current_request( array( 'body' => null, 'method' => 'GET' ) );
5425
		}
5426
5427
		if ( ! $signature ) {
5428
			wp_die( $die_error );
5429
		} else if ( is_wp_error( $signature ) ) {
5430
			wp_die( $die_error );
5431
		} else if ( ! hash_equals( $signature, $environment['signature'] ) ) {
5432
			if ( is_ssl() ) {
5433
				// 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
5434
				$signature = $jetpack_signature->sign_current_request( array( 'scheme' => 'http', 'body' => null, 'method' => 'GET' ) );
5435
				if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
5436
					wp_die( $die_error );
5437
				}
5438
			} else {
5439
				wp_die( $die_error );
5440
			}
5441
		}
5442
5443
		$timestamp = (int) $environment['timestamp'];
5444
		$nonce     = stripslashes( (string) $environment['nonce'] );
5445
5446
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
5447
			// De-nonce the nonce, at least for 5 minutes.
5448
			// 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)
5449
			$old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5450
			if ( $old_nonce_time < time() - 300 ) {
5451
				wp_die( __( 'The authorization process expired.  Please go back and try again.' , 'jetpack' ) );
5452
			}
5453
		}
5454
5455
		$data = json_decode( base64_decode( stripslashes( $environment['data'] ) ) );
5456
		$data_filters = array(
5457
			'state'        => 'opaque',
5458
			'client_id'    => 'int',
5459
			'client_title' => 'string',
5460
			'client_image' => 'url',
5461
		);
5462
5463
		foreach ( $data_filters as $key => $sanitation ) {
5464
			if ( ! isset( $data->$key ) ) {
5465
				wp_die( $die_error );
5466
			}
5467
5468
			switch ( $sanitation ) {
5469
			case 'int' :
5470
				$this->json_api_authorization_request[$key] = (int) $data->$key;
5471
				break;
5472
			case 'opaque' :
5473
				$this->json_api_authorization_request[$key] = (string) $data->$key;
5474
				break;
5475
			case 'string' :
5476
				$this->json_api_authorization_request[$key] = wp_kses( (string) $data->$key, array() );
5477
				break;
5478
			case 'url' :
5479
				$this->json_api_authorization_request[$key] = esc_url_raw( (string) $data->$key );
5480
				break;
5481
			}
5482
		}
5483
5484
		if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5485
			wp_die( $die_error );
5486
		}
5487
	}
5488
5489
	function login_message_json_api_authorization( $message ) {
5490
		return '<p class="message">' . sprintf(
5491
			esc_html__( '%s wants to access your site&#8217;s data.  Log in to authorize that access.' , 'jetpack' ),
5492
			'<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5493
		) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5494
	}
5495
5496
	/**
5497
	 * Get $content_width, but with a <s>twist</s> filter.
5498
	 */
5499
	public static function get_content_width() {
5500
		$content_width = isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : false;
5501
		/**
5502
		 * Filter the Content Width value.
5503
		 *
5504
		 * @since 2.2.3
5505
		 *
5506
		 * @param string $content_width Content Width value.
5507
		 */
5508
		return apply_filters( 'jetpack_content_width', $content_width );
5509
	}
5510
5511
	/**
5512
	 * Pings the WordPress.com Mirror Site for the specified options.
5513
	 *
5514
	 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
5515
	 *
5516
	 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
5517
	 */
5518
	public function get_cloud_site_options( $option_names ) {
5519
		$option_names = array_filter( (array) $option_names, 'is_string' );
5520
5521
		Jetpack::load_xml_rpc_client();
5522
		$xml = new Jetpack_IXR_Client( array( 'user_id' => JETPACK_MASTER_USER, ) );
5523
		$xml->query( 'jetpack.fetchSiteOptions', $option_names );
5524
		if ( $xml->isError() ) {
5525
			return array(
5526
				'error_code' => $xml->getErrorCode(),
5527
				'error_msg'  => $xml->getErrorMessage(),
5528
			);
5529
		}
5530
		$cloud_site_options = $xml->getResponse();
5531
5532
		return $cloud_site_options;
5533
	}
5534
5535
	/**
5536
	 * Checks if the site is currently in an identity crisis.
5537
	 *
5538
	 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5539
	 */
5540
	public static function check_identity_crisis() {
5541
		if ( ! Jetpack::is_active() || Jetpack::is_development_mode() || ! self::validate_sync_error_idc_option() ) {
5542
			return false;
5543
		}
5544
5545
		return Jetpack_Options::get_option( 'sync_error_idc' );
5546
	}
5547
5548
	/**
5549
	 * Checks whether the home and siteurl specifically are whitelisted
5550
	 * Written so that we don't have re-check $key and $value params every time
5551
	 * we want to check if this site is whitelisted, for example in footer.php
5552
	 *
5553
	 * @since  3.8.0
5554
	 * @return bool True = already whitelisted False = not whitelisted
5555
	 */
5556
	public static function is_staging_site() {
5557
		$is_staging = false;
5558
5559
		$known_staging = array(
5560
			'urls' => array(
5561
				'#\.staging\.wpengine\.com$#i', // WP Engine
5562
				'#\.staging\.kinsta\.com$#i',   // Kinsta.com
5563
				),
5564
			'constants' => array(
5565
				'IS_WPE_SNAPSHOT',      // WP Engine
5566
				'KINSTA_DEV_ENV',       // Kinsta.com
5567
				'WPSTAGECOACH_STAGING', // WP Stagecoach
5568
				'JETPACK_STAGING_MODE', // Generic
5569
				)
5570
			);
5571
		/**
5572
		 * Filters the flags of known staging sites.
5573
		 *
5574
		 * @since 3.9.0
5575
		 *
5576
		 * @param array $known_staging {
5577
		 *     An array of arrays that each are used to check if the current site is staging.
5578
		 *     @type array $urls      URLs of staging sites in regex to check against site_url.
5579
		 *     @type array $constants PHP constants of known staging/developement environments.
5580
		 *  }
5581
		 */
5582
		$known_staging = apply_filters( 'jetpack_known_staging', $known_staging );
5583
5584
		if ( isset( $known_staging['urls'] ) ) {
5585
			foreach ( $known_staging['urls'] as $url ){
5586
				if ( preg_match( $url, site_url() ) ) {
5587
					$is_staging = true;
5588
					break;
5589
				}
5590
			}
5591
		}
5592
5593
		if ( isset( $known_staging['constants'] ) ) {
5594
			foreach ( $known_staging['constants'] as $constant ) {
5595
				if ( defined( $constant ) && constant( $constant ) ) {
5596
					$is_staging = true;
5597
				}
5598
			}
5599
		}
5600
5601
		// Last, let's check if sync is erroring due to an IDC. If so, set the site to staging mode.
5602
		if ( ! $is_staging && self::validate_sync_error_idc_option() ) {
5603
			$is_staging = true;
5604
		}
5605
5606
		/**
5607
		 * Filters is_staging_site check.
5608
		 *
5609
		 * @since 3.9.0
5610
		 *
5611
		 * @param bool $is_staging If the current site is a staging site.
5612
		 */
5613
		return apply_filters( 'jetpack_is_staging_site', $is_staging );
5614
	}
5615
5616
	/**
5617
	 * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
5618
	 *
5619
	 * @return bool
5620
	 */
5621
	public static function validate_sync_error_idc_option() {
5622
		$is_valid = false;
5623
5624
		$idc_allowed = get_transient( 'jetpack_idc_allowed' );
5625
		if ( false === $idc_allowed ) {
5626
			$response = wp_remote_get( 'https://jetpack.com/is-idc-allowed/' );
5627
			if ( 200 === (int) wp_remote_retrieve_response_code( $response ) ) {
5628
				$json = json_decode( wp_remote_retrieve_body( $response ) );
5629
				$idc_allowed = isset( $json, $json->result ) && $json->result ? '1' : '0';
5630
				$transient_duration = HOUR_IN_SECONDS;
5631
			} else {
5632
				// If the request failed for some reason, then assume IDC is allowed and set shorter transient.
5633
				$idc_allowed = '1';
5634
				$transient_duration = 5 * MINUTE_IN_SECONDS;
5635
			}
5636
5637
			set_transient( 'jetpack_idc_allowed', $idc_allowed, $transient_duration );
5638
		}
5639
5640
		// Is the site opted in and does the stored sync_error_idc option match what we now generate?
5641
		$sync_error = Jetpack_Options::get_option( 'sync_error_idc' );
5642
		$local_options = self::get_sync_error_idc_option();
5643
		if ( $idc_allowed && $sync_error && self::sync_idc_optin() ) {
5644
			if ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) {
5645
				$is_valid = true;
5646
			}
5647
		}
5648
5649
		/**
5650
		 * Filters whether the sync_error_idc option is valid.
5651
		 *
5652
		 * @since 4.4.0
5653
		 *
5654
		 * @param bool $is_valid If the sync_error_idc is valid or not.
5655
		 */
5656
		$is_valid = (bool) apply_filters( 'jetpack_sync_error_idc_validation', $is_valid );
5657
5658
		if ( ! $idc_allowed || ( ! $is_valid && $sync_error ) ) {
5659
			// Since the option exists, and did not validate, delete it
5660
			Jetpack_Options::delete_option( 'sync_error_idc' );
5661
		}
5662
5663
		return $is_valid;
5664
	}
5665
5666
	/**
5667
	 * Normalizes a url by doing three things:
5668
	 *  - Strips protocol
5669
	 *  - Strips www
5670
	 *  - Adds a trailing slash
5671
	 *
5672
	 * @since 4.4.0
5673
	 * @param string $url
5674
	 * @return WP_Error|string
5675
	 */
5676
	public static function normalize_url_protocol_agnostic( $url ) {
5677
		$parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
5678
		if ( ! $parsed_url ) {
5679
			return new WP_Error( 'cannot_parse_url', sprintf( esc_html__( 'Cannot parse URL %s', 'jetpack' ), $url ) );
5680
		}
5681
5682
		// Strip www and protocols
5683
		$url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
5684
		return $url;
5685
	}
5686
5687
	/**
5688
	 * Gets the value that is to be saved in the jetpack_sync_error_idc option.
5689
	 *
5690
	 * @since 4.4.0
5691
	 *
5692
	 * @param array $response
5693
	 * @return array Array of the local urls, wpcom urls, and error code
5694
	 */
5695
	public static function get_sync_error_idc_option( $response = array() ) {
5696
		$local_options = array(
5697
			'home' => get_home_url(),
5698
			'siteurl' => get_site_url(),
5699
		);
5700
5701
		$options = array_merge( $local_options, $response );
5702
5703
		$returned_values = array();
5704
		foreach( $options as $key => $option ) {
5705
			if ( 'error_code' === $key ) {
5706
				$returned_values[ $key ] = $option;
5707
				continue;
5708
			}
5709
5710
			if ( is_wp_error( $normalized_url = self::normalize_url_protocol_agnostic( $option ) ) ) {
5711
				continue;
5712
			}
5713
5714
			$returned_values[ $key ] = $normalized_url;
5715
		}
5716
5717
		return $returned_values;
5718
	}
5719
5720
	/**
5721
	 * Returns the value of the jetpack_sync_idc_optin filter, or constant.
5722
	 * If set to true, the site will be put into staging mode.
5723
	 *
5724
	 * @since 4.3.2
5725
	 * @return bool
5726
	 */
5727
	public static function sync_idc_optin() {
5728
		if ( Jetpack_Constants::is_defined( 'JETPACK_SYNC_IDC_OPTIN' ) ) {
5729
			$default = Jetpack_Constants::get_constant( 'JETPACK_SYNC_IDC_OPTIN' );
5730
		} else {
5731
			$default = ! Jetpack_Constants::is_defined( 'SUNRISE' ) && ! is_multisite();
5732
		}
5733
5734
		/**
5735
		 * Allows sites to optin to IDC mitigation which blocks the site from syncing to WordPress.com when the home
5736
		 * URL or site URL do not match what WordPress.com expects. The default value is either false, or the value of
5737
		 * JETPACK_SYNC_IDC_OPTIN constant if set.
5738
		 *
5739
		 * @since 4.3.2
5740
		 *
5741
		 * @param bool $default Whether the site is opted in to IDC mitigation.
5742
		 */
5743
		return (bool) apply_filters( 'jetpack_sync_idc_optin', $default );
5744
	}
5745
5746
	/**
5747
	 * Maybe Use a .min.css stylesheet, maybe not.
5748
	 *
5749
	 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
5750
	 */
5751
	public static function maybe_min_asset( $url, $path, $plugin ) {
5752
		// Short out on things trying to find actual paths.
5753
		if ( ! $path || empty( $plugin ) ) {
5754
			return $url;
5755
		}
5756
5757
		// Strip out the abspath.
5758
		$base = dirname( plugin_basename( $plugin ) );
5759
5760
		// Short out on non-Jetpack assets.
5761
		if ( 'jetpack/' !== substr( $base, 0, 8 ) ) {
5762
			return $url;
5763
		}
5764
5765
		// File name parsing.
5766
		$file              = "{$base}/{$path}";
5767
		$full_path         = JETPACK__PLUGIN_DIR . substr( $file, 8 );
5768
		$file_name         = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
5769
		$file_name_parts_r = array_reverse( explode( '.', $file_name ) );
5770
		$extension         = array_shift( $file_name_parts_r );
5771
5772
		if ( in_array( strtolower( $extension ), array( 'css', 'js' ) ) ) {
5773
			// Already pointing at the minified version.
5774
			if ( 'min' === $file_name_parts_r[0] ) {
5775
				return $url;
5776
			}
5777
5778
			$min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
5779
			if ( file_exists( $min_full_path ) ) {
5780
				$url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
5781
			}
5782
		}
5783
5784
		return $url;
5785
	}
5786
5787
	/**
5788
	 * Maybe inlines a stylesheet.
5789
	 *
5790
	 * If you'd like to inline a stylesheet instead of printing a link to it,
5791
	 * wp_style_add_data( 'handle', 'jetpack-inline', true );
5792
	 *
5793
	 * Attached to `style_loader_tag` filter.
5794
	 *
5795
	 * @param string $tag The tag that would link to the external asset.
5796
	 * @param string $handle The registered handle of the script in question.
5797
	 *
5798
	 * @return string
5799
	 */
5800
	public static function maybe_inline_style( $tag, $handle ) {
5801
		global $wp_styles;
5802
		$item = $wp_styles->registered[ $handle ];
5803
5804
		if ( ! isset( $item->extra['jetpack-inline'] ) || ! $item->extra['jetpack-inline'] ) {
5805
			return $tag;
5806
		}
5807
5808
		if ( preg_match( '# href=\'([^\']+)\' #i', $tag, $matches ) ) {
5809
			$href = $matches[1];
5810
			// Strip off query string
5811
			if ( $pos = strpos( $href, '?' ) ) {
5812
				$href = substr( $href, 0, $pos );
5813
			}
5814
			// Strip off fragment
5815
			if ( $pos = strpos( $href, '#' ) ) {
5816
				$href = substr( $href, 0, $pos );
5817
			}
5818
		} else {
5819
			return $tag;
5820
		}
5821
5822
		$plugins_dir = plugin_dir_url( JETPACK__PLUGIN_FILE );
5823
		if ( $plugins_dir !== substr( $href, 0, strlen( $plugins_dir ) ) ) {
5824
			return $tag;
5825
		}
5826
5827
		// If this stylesheet has a RTL version, and the RTL version replaces normal...
5828
		if ( isset( $item->extra['rtl'] ) && 'replace' === $item->extra['rtl'] && is_rtl() ) {
5829
			// And this isn't the pass that actually deals with the RTL version...
5830
			if ( false === strpos( $tag, " id='$handle-rtl-css' " ) ) {
5831
				// Short out, as the RTL version will deal with it in a moment.
5832
				return $tag;
5833
			}
5834
		}
5835
5836
		$file = JETPACK__PLUGIN_DIR . substr( $href, strlen( $plugins_dir ) );
5837
		$css  = Jetpack::absolutize_css_urls( file_get_contents( $file ), $href );
5838
		if ( $css ) {
5839
			$tag = "<!-- Inline {$item->handle} -->\r\n";
5840
			if ( empty( $item->extra['after'] ) ) {
5841
				wp_add_inline_style( $handle, $css );
5842
			} else {
5843
				array_unshift( $item->extra['after'], $css );
5844
				wp_style_add_data( $handle, 'after', $item->extra['after'] );
5845
			}
5846
		}
5847
5848
		return $tag;
5849
	}
5850
5851
	/**
5852
	 * Loads a view file from the views
5853
	 *
5854
	 * Data passed in with the $data parameter will be available in the
5855
	 * template file as $data['value']
5856
	 *
5857
	 * @param string $template - Template file to load
5858
	 * @param array $data - Any data to pass along to the template
5859
	 * @return boolean - If template file was found
5860
	 **/
5861
	public function load_view( $template, $data = array() ) {
5862
		$views_dir = JETPACK__PLUGIN_DIR . 'views/';
5863
5864
		if( file_exists( $views_dir . $template ) ) {
5865
			require_once( $views_dir . $template );
5866
			return true;
5867
		}
5868
5869
		error_log( "Jetpack: Unable to find view file $views_dir$template" );
5870
		return false;
5871
	}
5872
5873
	/**
5874
	 * Throws warnings for deprecated hooks to be removed from Jetpack
5875
	 */
5876
	public function deprecated_hooks() {
5877
		global $wp_filter;
5878
5879
		/*
5880
		 * Format:
5881
		 * deprecated_filter_name => replacement_name
5882
		 *
5883
		 * If there is no replacement, use null for replacement_name
5884
		 */
5885
		$deprecated_list = array(
5886
			'jetpack_bail_on_shortcode'                              => 'jetpack_shortcodes_to_include',
5887
			'wpl_sharing_2014_1'                                     => null,
5888
			'jetpack-tools-to-include'                               => 'jetpack_tools_to_include',
5889
			'jetpack_identity_crisis_options_to_check'               => null,
5890
			'update_option_jetpack_single_user_site'                 => null,
5891
			'audio_player_default_colors'                            => null,
5892
			'add_option_jetpack_featured_images_enabled'             => null,
5893
			'add_option_jetpack_update_details'                      => null,
5894
			'add_option_jetpack_updates'                             => null,
5895
			'add_option_jetpack_network_name'                        => null,
5896
			'add_option_jetpack_network_allow_new_registrations'     => null,
5897
			'add_option_jetpack_network_add_new_users'               => null,
5898
			'add_option_jetpack_network_site_upload_space'           => null,
5899
			'add_option_jetpack_network_upload_file_types'           => null,
5900
			'add_option_jetpack_network_enable_administration_menus' => null,
5901
			'add_option_jetpack_is_multi_site'                       => null,
5902
			'add_option_jetpack_is_main_network'                     => null,
5903
			'add_option_jetpack_main_network_site'                   => null,
5904
			'jetpack_sync_all_registered_options'                    => null,
5905
			'jetpack_has_identity_crisis'                            => 'jetpack_sync_error_idc_validation',
5906
			'jetpack_is_post_mailable'                               => null,
5907
		);
5908
5909
		// This is a silly loop depth. Better way?
5910
		foreach( $deprecated_list AS $hook => $hook_alt ) {
5911
			if ( has_action( $hook ) ) {
5912
				foreach( $wp_filter[ $hook ] AS $func => $values ) {
5913
					foreach( $values AS $hooked ) {
5914
						if ( is_callable( $hooked['function'] ) ) {
5915
							$function_name = 'an anonymous function';
5916
						} else {
5917
							$function_name = $hooked['function'];
5918
						}
5919
						_deprecated_function( $hook . ' used for ' . $function_name, null, $hook_alt );
5920
					}
5921
				}
5922
			}
5923
		}
5924
	}
5925
5926
	/**
5927
	 * Converts any url in a stylesheet, to the correct absolute url.
5928
	 *
5929
	 * Considerations:
5930
	 *  - Normal, relative URLs     `feh.png`
5931
	 *  - Data URLs                 `data:image/gif;base64,eh129ehiuehjdhsa==`
5932
	 *  - Schema-agnostic URLs      `//domain.com/feh.png`
5933
	 *  - Absolute URLs             `http://domain.com/feh.png`
5934
	 *  - Domain root relative URLs `/feh.png`
5935
	 *
5936
	 * @param $css string: The raw CSS -- should be read in directly from the file.
5937
	 * @param $css_file_url : The URL that the file can be accessed at, for calculating paths from.
5938
	 *
5939
	 * @return mixed|string
5940
	 */
5941
	public static function absolutize_css_urls( $css, $css_file_url ) {
5942
		$pattern = '#url\((?P<path>[^)]*)\)#i';
5943
		$css_dir = dirname( $css_file_url );
5944
		$p       = parse_url( $css_dir );
5945
		$domain  = sprintf(
5946
					'%1$s//%2$s%3$s%4$s',
5947
					isset( $p['scheme'] )           ? "{$p['scheme']}:" : '',
5948
					isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
5949
					$p['host'],
5950
					isset( $p['port'] )             ? ":{$p['port']}" : ''
5951
				);
5952
5953
		if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
5954
			$find = $replace = array();
5955
			foreach ( $matches as $match ) {
5956
				$url = trim( $match['path'], "'\" \t" );
5957
5958
				// If this is a data url, we don't want to mess with it.
5959
				if ( 'data:' === substr( $url, 0, 5 ) ) {
5960
					continue;
5961
				}
5962
5963
				// If this is an absolute or protocol-agnostic url,
5964
				// we don't want to mess with it.
5965
				if ( preg_match( '#^(https?:)?//#i', $url ) ) {
5966
					continue;
5967
				}
5968
5969
				switch ( substr( $url, 0, 1 ) ) {
5970
					case '/':
5971
						$absolute = $domain . $url;
5972
						break;
5973
					default:
5974
						$absolute = $css_dir . '/' . $url;
5975
				}
5976
5977
				$find[]    = $match[0];
5978
				$replace[] = sprintf( 'url("%s")', $absolute );
5979
			}
5980
			$css = str_replace( $find, $replace, $css );
5981
		}
5982
5983
		return $css;
5984
	}
5985
5986
	/**
5987
	 * This methods removes all of the registered css files on the front end
5988
	 * from Jetpack in favor of using a single file. In effect "imploding"
5989
	 * all the files into one file.
5990
	 *
5991
	 * Pros:
5992
	 * - Uses only ONE css asset connection instead of 15
5993
	 * - Saves a minimum of 56k
5994
	 * - Reduces server load
5995
	 * - Reduces time to first painted byte
5996
	 *
5997
	 * Cons:
5998
	 * - Loads css for ALL modules. However all selectors are prefixed so it
5999
	 *		should not cause any issues with themes.
6000
	 * - Plugins/themes dequeuing styles no longer do anything. See
6001
	 *		jetpack_implode_frontend_css filter for a workaround
6002
	 *
6003
	 * For some situations developers may wish to disable css imploding and
6004
	 * instead operate in legacy mode where each file loads seperately and
6005
	 * can be edited individually or dequeued. This can be accomplished with
6006
	 * the following line:
6007
	 *
6008
	 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6009
	 *
6010
	 * @since 3.2
6011
	 **/
6012
	public function implode_frontend_css( $travis_test = false ) {
6013
		$do_implode = true;
6014
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6015
			$do_implode = false;
6016
		}
6017
6018
		/**
6019
		 * Allow CSS to be concatenated into a single jetpack.css file.
6020
		 *
6021
		 * @since 3.2.0
6022
		 *
6023
		 * @param bool $do_implode Should CSS be concatenated? Default to true.
6024
		 */
6025
		$do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6026
6027
		// Do not use the imploded file when default behaviour was altered through the filter
6028
		if ( ! $do_implode ) {
6029
			return;
6030
		}
6031
6032
		// We do not want to use the imploded file in dev mode, or if not connected
6033
		if ( Jetpack::is_development_mode() || ! self::is_active() ) {
6034
			if ( ! $travis_test ) {
6035
				return;
6036
			}
6037
		}
6038
6039
		// Do not use the imploded file if sharing css was dequeued via the sharing settings screen
6040
		if ( get_option( 'sharedaddy_disable_resources' ) ) {
6041
			return;
6042
		}
6043
6044
		/*
6045
		 * Now we assume Jetpack is connected and able to serve the single
6046
		 * file.
6047
		 *
6048
		 * In the future there will be a check here to serve the file locally
6049
		 * or potentially from the Jetpack CDN
6050
		 *
6051
		 * For now:
6052
		 * - Enqueue a single imploded css file
6053
		 * - Zero out the style_loader_tag for the bundled ones
6054
		 * - Be happy, drink scotch
6055
		 */
6056
6057
		add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6058
6059
		$version = Jetpack::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6060
6061
		wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6062
		wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6063
	}
6064
6065
	function concat_remove_style_loader_tag( $tag, $handle ) {
6066
		if ( in_array( $handle, $this->concatenated_style_handles ) ) {
6067
			$tag = '';
6068
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6069
				$tag = "<!-- `" . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6070
			}
6071
		}
6072
6073
		return $tag;
6074
	}
6075
6076
	/*
6077
	 * Check the heartbeat data
6078
	 *
6079
	 * Organizes the heartbeat data by severity.  For example, if the site
6080
	 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
6081
	 *
6082
	 * Data will be added to "caution" array, if it either:
6083
	 *  - Out of date Jetpack version
6084
	 *  - Out of date WP version
6085
	 *  - Out of date PHP version
6086
	 *
6087
	 * $return array $filtered_data
6088
	 */
6089
	public static function jetpack_check_heartbeat_data() {
6090
		$raw_data = Jetpack_Heartbeat::generate_stats_array();
6091
6092
		$good    = array();
6093
		$caution = array();
6094
		$bad     = array();
6095
6096
		foreach ( $raw_data as $stat => $value ) {
6097
6098
			// Check jetpack version
6099
			if ( 'version' == $stat ) {
6100
				if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
6101
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__VERSION;
6102
					continue;
6103
				}
6104
			}
6105
6106
			// Check WP version
6107
			if ( 'wp-version' == $stat ) {
6108
				if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
6109
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__MINIMUM_WP_VERSION;
6110
					continue;
6111
				}
6112
			}
6113
6114
			// Check PHP version
6115
			if ( 'php-version' == $stat ) {
6116
				if ( version_compare( PHP_VERSION, '5.2.4', '<' ) ) {
6117
					$caution[ $stat ] = $value . " - min supported is 5.2.4";
6118
					continue;
6119
				}
6120
			}
6121
6122
			// Check ID crisis
6123
			if ( 'identitycrisis' == $stat ) {
6124
				if ( 'yes' == $value ) {
6125
					$bad[ $stat ] = $value;
6126
					continue;
6127
				}
6128
			}
6129
6130
			// The rest are good :)
6131
			$good[ $stat ] = $value;
6132
		}
6133
6134
		$filtered_data = array(
6135
			'good'    => $good,
6136
			'caution' => $caution,
6137
			'bad'     => $bad
6138
		);
6139
6140
		return $filtered_data;
6141
	}
6142
6143
6144
	/*
6145
	 * This method is used to organize all options that can be reset
6146
	 * without disconnecting Jetpack.
6147
	 *
6148
	 * It is used in class.jetpack-cli.php to reset options
6149
	 *
6150
	 * @return array of options to delete.
6151
	 */
6152
	public static function get_jetpack_options_for_reset() {
6153
		$jetpack_options            = Jetpack_Options::get_option_names();
6154
		$jetpack_options_non_compat = Jetpack_Options::get_option_names( 'non_compact' );
6155
		$jetpack_options_private    = Jetpack_Options::get_option_names( 'private' );
6156
6157
		$all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
6158
6159
		// A manual build of the wp options
6160
		$wp_options = array(
6161
			'sharing-options',
6162
			'disabled_likes',
6163
			'disabled_reblogs',
6164
			'jetpack_comments_likes_enabled',
6165
			'wp_mobile_excerpt',
6166
			'wp_mobile_featured_images',
6167
			'wp_mobile_app_promos',
6168
			'stats_options',
6169
			'stats_dashboard_widget',
6170
			'safecss_preview_rev',
6171
			'safecss_rev',
6172
			'safecss_revision_migrated',
6173
			'nova_menu_order',
6174
			'jetpack_portfolio',
6175
			'jetpack_portfolio_posts_per_page',
6176
			'jetpack_testimonial',
6177
			'jetpack_testimonial_posts_per_page',
6178
			'wp_mobile_custom_css',
6179
			'sharedaddy_disable_resources',
6180
			'sharing-options',
6181
			'sharing-services',
6182
			'site_icon_temp_data',
6183
			'featured-content',
6184
			'site_logo',
6185
			'jetpack_dismissed_notices',
6186
		);
6187
6188
		// Flag some Jetpack options as unsafe
6189
		$unsafe_options = array(
6190
			'id',                           // (int)    The Client ID/WP.com Blog ID of this site.
6191
			'master_user',                  // (int)    The local User ID of the user who connected this site to jetpack.wordpress.com.
6192
			'version',                      // (string) Used during upgrade procedure to auto-activate new modules. version:time
6193
			'jumpstart',                    // (string) A flag for whether or not to show the Jump Start.  Accepts: new_connection, jumpstart_activated, jetpack_action_taken, jumpstart_dismissed.
6194
6195
			// non_compact
6196
			'activated',
6197
6198
			// private
6199
			'register',
6200
			'blog_token',                  // (string) The Client Secret/Blog Token of this site.
6201
			'user_token',                  // (string) The User Token of this site. (deprecated)
6202
			'user_tokens'
6203
		);
6204
6205
		// Remove the unsafe Jetpack options
6206
		foreach ( $unsafe_options as $unsafe_option ) {
6207
			if ( false !== ( $key = array_search( $unsafe_option, $all_jp_options ) ) ) {
6208
				unset( $all_jp_options[ $key ] );
6209
			}
6210
		}
6211
6212
		$options = array(
6213
			'jp_options' => $all_jp_options,
6214
			'wp_options' => $wp_options
6215
		);
6216
6217
		return $options;
6218
	}
6219
6220
	/**
6221
	 * Check if an option of a Jetpack module has been updated.
6222
	 *
6223
	 * If any module option has been updated before Jump Start has been dismissed,
6224
	 * update the 'jumpstart' option so we can hide Jump Start.
6225
	 *
6226
	 * @param string $option_name
6227
	 *
6228
	 * @return bool
6229
	 */
6230
	public static function jumpstart_has_updated_module_option( $option_name = '' ) {
6231
		// Bail if Jump Start has already been dismissed
6232
		if ( 'new_connection' !== Jetpack_Options::get_option( 'jumpstart' ) ) {
6233
			return false;
6234
		}
6235
6236
		$jetpack = Jetpack::init();
6237
6238
		// Manual build of module options
6239
		$option_names = self::get_jetpack_options_for_reset();
6240
6241
		if ( in_array( $option_name, $option_names['wp_options'] ) ) {
6242
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
6243
6244
			//Jump start is being dismissed send data to MC Stats
6245
			$jetpack->stat( 'jumpstart', 'manual,'.$option_name );
6246
6247
			$jetpack->do_stats( 'server_side' );
6248
		}
6249
6250
	}
6251
6252
	/*
6253
	 * Strip http:// or https:// from a url, replaces forward slash with ::,
6254
	 * so we can bring them directly to their site in calypso.
6255
	 *
6256
	 * @param string | url
6257
	 * @return string | url without the guff
6258
	 */
6259
	public static function build_raw_urls( $url ) {
6260
		$strip_http = '/.*?:\/\//i';
6261
		$url = preg_replace( $strip_http, '', $url  );
6262
		$url = str_replace( '/', '::', $url );
6263
		return $url;
6264
	}
6265
6266
	/**
6267
	 * Stores and prints out domains to prefetch for page speed optimization.
6268
	 *
6269
	 * @param mixed $new_urls
6270
	 */
6271
	public static function dns_prefetch( $new_urls = null ) {
6272
		static $prefetch_urls = array();
6273
		if ( empty( $new_urls ) && ! empty( $prefetch_urls ) ) {
6274
			echo "\r\n";
6275
			foreach ( $prefetch_urls as $this_prefetch_url ) {
6276
				printf( "<link rel='dns-prefetch' href='%s'>\r\n", esc_attr( $this_prefetch_url ) );
6277
			}
6278
		} elseif ( ! empty( $new_urls ) ) {
6279
			if ( ! has_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) ) ) {
6280
				add_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) );
6281
			}
6282
			foreach ( (array) $new_urls as $this_new_url ) {
6283
				$prefetch_urls[] = strtolower( untrailingslashit( preg_replace( '#^https?://#i', '//', $this_new_url ) ) );
6284
			}
6285
			$prefetch_urls = array_unique( $prefetch_urls );
6286
		}
6287
	}
6288
6289
	public function wp_dashboard_setup() {
6290
		if ( self::is_active() ) {
6291
			add_action( 'jetpack_dashboard_widget', array( __CLASS__, 'dashboard_widget_footer' ), 999 );
6292
			$widget_title = __( 'Site Stats', 'jetpack' );
6293
		} elseif ( ! self::is_development_mode() && current_user_can( 'jetpack_connect' ) ) {
6294
			add_action( 'jetpack_dashboard_widget', array( $this, 'dashboard_widget_connect_to_wpcom' ) );
6295
			$widget_title = __( 'Please Connect Jetpack', 'jetpack' );
6296
		}
6297
6298
		if ( has_action( 'jetpack_dashboard_widget' ) ) {
6299
			wp_add_dashboard_widget(
6300
				'jetpack_summary_widget',
6301
				$widget_title,
6302
				array( __CLASS__, 'dashboard_widget' )
6303
			);
6304
			wp_enqueue_style( 'jetpack-dashboard-widget', plugins_url( 'css/dashboard-widget.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
6305
6306
			// If we're inactive and not in development mode, sort our box to the top.
6307
			if ( ! self::is_active() && ! self::is_development_mode() ) {
6308
				global $wp_meta_boxes;
6309
6310
				$dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
6311
				$ours      = array( 'jetpack_summary_widget' => $dashboard['jetpack_summary_widget'] );
6312
6313
				$wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard );
6314
			}
6315
		}
6316
	}
6317
6318
	/**
6319
	 * @param mixed $result Value for the user's option
6320
	 * @return mixed
6321
	 */
6322
	function get_user_option_meta_box_order_dashboard( $sorted ) {
6323
		if ( ! is_array( $sorted ) ) {
6324
			return $sorted;
6325
		}
6326
6327
		foreach ( $sorted as $box_context => $ids ) {
6328
			if ( false === strpos( $ids, 'dashboard_stats' ) ) {
6329
				// If the old id isn't anywhere in the ids, don't bother exploding and fail out.
6330
				continue;
6331
			}
6332
6333
			$ids_array = explode( ',', $ids );
6334
			$key = array_search( 'dashboard_stats', $ids_array );
6335
6336
			if ( false !== $key ) {
6337
				// If we've found that exact value in the option (and not `google_dashboard_stats` for example)
6338
				$ids_array[ $key ] = 'jetpack_summary_widget';
6339
				$sorted[ $box_context ] = implode( ',', $ids_array );
6340
				// We've found it, stop searching, and just return.
6341
				break;
6342
			}
6343
		}
6344
6345
		return $sorted;
6346
	}
6347
6348
	public static function dashboard_widget() {
6349
		/**
6350
		 * Fires when the dashboard is loaded.
6351
		 *
6352
		 * @since 3.4.0
6353
		 */
6354
		do_action( 'jetpack_dashboard_widget' );
6355
	}
6356
6357
	public static function dashboard_widget_footer() {
6358
		?>
6359
		<footer>
6360
6361
		<div class="protect">
6362
			<?php if ( Jetpack::is_module_active( 'protect' ) ) : ?>
6363
				<h3><?php echo number_format_i18n( get_site_option( 'jetpack_protect_blocked_attempts', 0 ) ); ?></h3>
6364
				<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>
6365
			<?php elseif ( current_user_can( 'jetpack_activate_modules' ) && ! self::is_development_mode() ) : ?>
6366
				<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' ); ?>">
6367
					<?php esc_html_e( 'Activate Protect', 'jetpack' ); ?>
6368
				</a>
6369
			<?php else : ?>
6370
				<?php esc_html_e( 'Protect is inactive.', 'jetpack' ); ?>
6371
			<?php endif; ?>
6372
		</div>
6373
6374
		<div class="akismet">
6375
			<?php if ( is_plugin_active( 'akismet/akismet.php' ) ) : ?>
6376
				<h3><?php echo number_format_i18n( get_option( 'akismet_spam_count', 0 ) ); ?></h3>
6377
				<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>
6378
			<?php elseif ( current_user_can( 'activate_plugins' ) && ! is_wp_error( validate_plugin( 'akismet/akismet.php' ) ) ) : ?>
6379
				<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">
6380
					<?php esc_html_e( 'Activate Akismet', 'jetpack' ); ?>
6381
				</a>
6382
			<?php else : ?>
6383
				<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>
6384
			<?php endif; ?>
6385
		</div>
6386
6387
		</footer>
6388
		<?php
6389
	}
6390
6391
	public function dashboard_widget_connect_to_wpcom() {
6392
		if ( Jetpack::is_active() || Jetpack::is_development_mode() || ! current_user_can( 'jetpack_connect' ) ) {
6393
			return;
6394
		}
6395
		?>
6396
		<div class="wpcom-connect">
6397
			<div class="jp-emblem">
6398
			<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">
6399
				<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"/>
6400
			</svg>
6401
			</div>
6402
			<h3><?php esc_html_e( 'Please Connect Jetpack', 'jetpack' ); ?></h3>
6403
			<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>
6404
6405
			<div class="actions">
6406
				<a href="<?php echo $this->build_connect_url( false, false, 'widget-btn' ); ?>" class="button button-primary">
6407
					<?php esc_html_e( 'Connect Jetpack', 'jetpack' ); ?>
6408
				</a>
6409
			</div>
6410
		</div>
6411
		<?php
6412
	}
6413
6414
	/**
6415
	 * Return string containing the Jetpack logo.
6416
	 *
6417
	 * @since 3.9.0
6418
	 *
6419
	 * @return string
6420
	 */
6421
	public static function get_jp_emblem() {
6422
		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>';
6423
	}
6424
6425
	/*
6426
	 * Adds a "blank" column in the user admin table to display indication of user connection.
6427
	 */
6428
	function jetpack_icon_user_connected( $columns ) {
6429
		$columns['user_jetpack'] = '';
6430
		return $columns;
6431
	}
6432
6433
	/*
6434
	 * Show Jetpack icon if the user is linked.
6435
	 */
6436
	function jetpack_show_user_connected_icon( $val, $col, $user_id ) {
6437
		if ( 'user_jetpack' == $col && Jetpack::is_user_connected( $user_id ) ) {
6438
			$emblem_html = sprintf(
6439
				'<a title="%1$s" class="jp-emblem-user-admin">%2$s</a>',
6440
				esc_attr__( 'This user is linked and ready to fly with Jetpack.', 'jetpack' ),
6441
				Jetpack::get_jp_emblem()
6442
			);
6443
			return $emblem_html;
6444
		}
6445
6446
		return $val;
6447
	}
6448
6449
	/*
6450
	 * Style the Jetpack user column
6451
	 */
6452
	function jetpack_user_col_style() {
6453
		global $current_screen;
6454
		if ( ! empty( $current_screen->base ) && 'users' == $current_screen->base ) { ?>
6455
			<style>
6456
				.fixed .column-user_jetpack {
6457
					width: 21px;
6458
				}
6459
				.jp-emblem-user-admin svg {
6460
					width: 20px;
6461
					height: 20px;
6462
				}
6463
				.jp-emblem-user-admin path {
6464
					fill: #8cc258;
6465
				}
6466
			</style>
6467
		<?php }
6468
	}
6469
}
6470