Completed
Push — add/xml-sitemap ( f1e7a4...063fac )
by Jeremy
110:17 queued 98:29
created

class.jetpack.php (7 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
class Jetpack {
26
	public $xmlrpc_server = null;
27
28
	private $xmlrpc_verification = null;
29
30
	public $HTTP_RAW_POST_DATA = null; // copy of $GLOBALS['HTTP_RAW_POST_DATA']
31
32
	/**
33
	 * @var array The handles of styles that are concatenated into jetpack.css
34
	 */
35
	public $concatenated_style_handles = array(
36
		'jetpack-carousel',
37
		'grunion.css',
38
		'the-neverending-homepage',
39
		'jetpack_likes',
40
		'jetpack_related-posts',
41
		'sharedaddy',
42
		'jetpack-slideshow',
43
		'presentations',
44
		'jetpack-subscriptions',
45
		'tiled-gallery',
46
		'widget-conditions',
47
		'jetpack_display_posts_widget',
48
		'gravatar-profile-widget',
49
		'widget-grid-and-list',
50
		'jetpack-widgets',
51
		'goodreads-widget',
52
	);
53
54
	public $plugins_to_deactivate = array(
55
		'stats'               => array( 'stats/stats.php', 'WordPress.com Stats' ),
56
		'shortlinks'          => array( 'stats/stats.php', 'WordPress.com Stats' ),
57
		'sharedaddy'          => array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
58
		'twitter-widget'      => array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
59
		'after-the-deadline'  => array( 'after-the-deadline/after-the-deadline.php', 'After The Deadline' ),
60
		'contact-form'        => array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
61
		'contact-form'        => array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
62
		'custom-css'          => array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
63
		'random-redirect'     => array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
64
		'videopress'          => array( 'video/video.php', 'VideoPress' ),
65
		'widget-visibility'   => array( 'jetpack-widget-visibility/widget-visibility.php', 'Jetpack Widget Visibility' ),
66
		'widget-visibility'   => array( 'widget-visibility-without-jetpack/widget-visibility-without-jetpack.php', 'Widget Visibility Without Jetpack' ),
67
		'sharedaddy'          => array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
68
		'omnisearch'          => array( 'jetpack-omnisearch/omnisearch.php', 'Jetpack Omnisearch' ),
69
		'gravatar-hovercards' => array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
70
		'latex'               => array( 'wp-latex/wp-latex.php', 'WP LaTeX' )
71
	);
72
73
	public $capability_translations = array(
74
		'administrator' => 'manage_options',
75
		'editor'        => 'edit_others_posts',
76
		'author'        => 'publish_posts',
77
		'contributor'   => 'edit_posts',
78
		'subscriber'    => 'read',
79
	);
80
81
	/**
82
	 * Map of modules that have conflicts with plugins and should not be auto-activated
83
	 * if the plugins are active.  Used by filter_default_modules
84
	 *
85
	 * Plugin Authors: If you'd like to prevent a single module from auto-activating,
86
	 * change `module-slug` and add this to your plugin:
87
	 *
88
	 * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
89
	 * function my_jetpack_get_default_modules( $modules ) {
90
	 *     return array_diff( $modules, array( 'module-slug' ) );
91
	 * }
92
	 *
93
	 * @var array
94
	 */
95
	private $conflicting_plugins = array(
96
		'comments'          => array(
97
			'Intense Debate'                    => 'intensedebate/intensedebate.php',
98
			'Disqus'                            => 'disqus-comment-system/disqus.php',
99
			'Livefyre'                          => 'livefyre-comments/livefyre.php',
100
			'Comments Evolved for WordPress'    => 'gplus-comments/comments-evolved.php',
101
			'Google+ Comments'                  => 'google-plus-comments/google-plus-comments.php',
102
			'WP-SpamShield Anti-Spam'           => 'wp-spamshield/wp-spamshield.php',
103
		),
104
		'contact-form'      => array(
105
			'Contact Form 7'                    => 'contact-form-7/wp-contact-form-7.php',
106
			'Gravity Forms'                     => 'gravityforms/gravityforms.php',
107
			'Contact Form Plugin'               => 'contact-form-plugin/contact_form.php',
108
			'Easy Contact Forms'                => 'easy-contact-forms/easy-contact-forms.php',
109
			'Fast Secure Contact Form'          => 'si-contact-form/si-contact-form.php',
110
		),
111
		'minileven'         => array(
112
			'WPtouch'                           => 'wptouch/wptouch.php',
113
		),
114
		'latex'             => array(
115
			'LaTeX for WordPress'               => 'latex/latex.php',
116
			'Youngwhans Simple Latex'           => 'youngwhans-simple-latex/yw-latex.php',
117
			'Easy WP LaTeX'                     => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
118
			'MathJax-LaTeX'                     => 'mathjax-latex/mathjax-latex.php',
119
			'Enable Latex'                      => 'enable-latex/enable-latex.php',
120
			'WP QuickLaTeX'                     => 'wp-quicklatex/wp-quicklatex.php',
121
		),
122
		'protect'           => array(
123
			'Limit Login Attempts'              => 'limit-login-attempts/limit-login-attempts.php',
124
			'Captcha'                           => 'captcha/captcha.php',
125
			'Brute Force Login Protection'      => 'brute-force-login-protection/brute-force-login-protection.php',
126
			'Login Security Solution'           => 'login-security-solution/login-security-solution.php',
127
			'WPSecureOps Brute Force Protect'   => 'wpsecureops-bruteforce-protect/wpsecureops-bruteforce-protect.php',
128
			'BulletProof Security'              => 'bulletproof-security/bulletproof-security.php',
129
			'SiteGuard WP Plugin'               => 'siteguard/siteguard.php',
130
			'Security-protection'               => 'security-protection/security-protection.php',
131
			'Login Security'                    => 'login-security/login-security.php',
132
			'Botnet Attack Blocker'             => 'botnet-attack-blocker/botnet-attack-blocker.php',
133
			'Wordfence Security'                => 'wordfence/wordfence.php',
134
			'All In One WP Security & Firewall' => 'all-in-one-wp-security-and-firewall/wp-security.php',
135
			'iThemes Security'                  => 'better-wp-security/better-wp-security.php',
136
		),
137
		'random-redirect'   => array(
138
			'Random Redirect 2'                 => 'random-redirect-2/random-redirect.php',
139
		),
140
		'related-posts'     => array(
141
			'YARPP'                             => 'yet-another-related-posts-plugin/yarpp.php',
142
			'WordPress Related Posts'           => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
143
			'nrelate Related Content'           => 'nrelate-related-content/nrelate-related.php',
144
			'Contextual Related Posts'          => 'contextual-related-posts/contextual-related-posts.php',
145
			'Related Posts for WordPress'       => 'microkids-related-posts/microkids-related-posts.php',
146
			'outbrain'                          => 'outbrain/outbrain.php',
147
			'Shareaholic'                       => 'shareaholic/shareaholic.php',
148
			'Sexybookmarks'                     => 'sexybookmarks/shareaholic.php',
149
		),
150
		'sharedaddy'        => array(
151
			'AddThis'                           => 'addthis/addthis_social_widget.php',
152
			'Add To Any'                        => 'add-to-any/add-to-any.php',
153
			'ShareThis'                         => 'share-this/sharethis.php',
154
			'Shareaholic'                       => 'shareaholic/shareaholic.php',
155
		),
156
		'verification-tools' => array(
157
			'WordPress SEO by Yoast'            => 'wordpress-seo/wp-seo.php',
158
			'WordPress SEO Premium by Yoast'    => 'wordpress-seo-premium/wp-seo-premium.php',
159
			'All in One SEO Pack'               => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
160
		),
161
		'widget-visibility' => array(
162
			'Widget Logic'                      => 'widget-logic/widget_logic.php',
163
			'Dynamic Widgets'                   => 'dynamic-widgets/dynamic-widgets.php',
164
		),
165
		'xml-sitemap' => array(
166
			'Google XML Sitemaps'                  => 'google-sitemap-generator/sitemap.php',
167
			'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
168
			'Google XML Sitemaps for qTranslate'   => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php',
169
			'XML Sitemap & Google News feeds'      => 'xml-sitemap-feed/xml-sitemap.php',
170
			'Google Sitemap by BestWebSoft'        => 'google-sitemap-plugin/google-sitemap-plugin.php',
171
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
172
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
173
			'All in One SEO Pack'                 => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
174
		),
175
	);
176
177
	/**
178
	 * Plugins for which we turn off our Facebook OG Tags implementation.
179
	 *
180
	 * Note: WordPress SEO by Yoast and WordPress SEO Premium by Yoast automatically deactivate
181
	 * Jetpack's Open Graph tags via filter when their Social Meta modules are active.
182
	 *
183
	 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
184
	 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
185
	 */
186
	private $open_graph_conflicting_plugins = array(
187
		'2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
188
		                                                         // 2 Click Social Media Buttons
189
		'add-link-to-facebook/add-link-to-facebook.php',         // Add Link to Facebook
190
		'add-meta-tags/add-meta-tags.php',                       // Add Meta Tags
191
		'easy-facebook-share-thumbnails/esft.php',               // Easy Facebook Share Thumbnail
192
		'facebook/facebook.php',                                 // Facebook (official plugin)
193
		'facebook-awd/AWD_facebook.php',                         // Facebook AWD All in one
194
		'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
195
		                                                         // Facebook Featured Image & OG Meta Tags
196
		'facebook-meta-tags/facebook-metatags.php',              // Facebook Meta Tags
197
		'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
198
		                                                         // Facebook Open Graph Meta Tags for WordPress
199
		'facebook-revised-open-graph-meta-tag/index.php',        // Facebook Revised Open Graph Meta Tag
200
		'facebook-thumb-fixer/_facebook-thumb-fixer.php',        // Facebook Thumb Fixer
201
		'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
202
		                                                         // Fedmich's Facebook Open Graph Meta
203
		'header-footer/plugin.php',                              // Header and Footer
204
		'network-publisher/networkpub.php',                      // Network Publisher
205
		'nextgen-facebook/nextgen-facebook.php',                 // NextGEN Facebook OG
206
		'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php',
207
		                                                         // NextScripts SNAP
208
		'opengraph/opengraph.php',                               // Open Graph
209
		'open-graph-protocol-framework/open-graph-protocol-framework.php',
210
		                                                         // Open Graph Protocol Framework
211
		'seo-facebook-comments/seofacebook.php',                 // SEO Facebook Comments
212
		'seo-ultimate/seo-ultimate.php',                         // SEO Ultimate
213
		'sexybookmarks/sexy-bookmarks.php',                      // Shareaholic
214
		'shareaholic/sexy-bookmarks.php',                        // Shareaholic
215
		'sharepress/sharepress.php',                             // SharePress
216
		'simple-facebook-connect/sfc.php',                       // Simple Facebook Connect
217
		'social-discussions/social-discussions.php',             // Social Discussions
218
		'social-sharing-toolkit/social_sharing_toolkit.php',     // Social Sharing Toolkit
219
		'socialize/socialize.php',                               // Socialize
220
		'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
221
		                                                         // Tweet, Like, Google +1 and Share
222
		'wordbooker/wordbooker.php',                             // Wordbooker
223
		'wpsso/wpsso.php',                                       // WordPress Social Sharing Optimization
224
		'wp-caregiver/wp-caregiver.php',                         // WP Caregiver
225
		'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
226
		                                                         // WP Facebook Like Send & Open Graph Meta
227
		'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',   // WP Facebook Open Graph protocol
228
		'wp-ogp/wp-ogp.php',                                     // WP-OGP
229
		'zoltonorg-social-plugin/zosp.php',                      // Zolton.org Social Plugin
230
		'wp-fb-share-like-button/wp_fb_share-like_widget.php'    // WP Facebook Like Button
231
	);
232
233
	/**
234
	 * Plugins for which we turn off our Twitter Cards Tags implementation.
235
	 */
236
	private $twitter_cards_conflicting_plugins = array(
237
	//	'twitter/twitter.php',                       // The official one handles this on its own.
238
	//	                                             // https://github.com/twitter/wordpress/blob/master/src/Twitter/WordPress/Cards/Compatibility.php
239
		'eewee-twitter-card/index.php',              // Eewee Twitter Card
240
		'ig-twitter-cards/ig-twitter-cards.php',     // IG:Twitter Cards
241
		'jm-twitter-cards/jm-twitter-cards.php',     // JM Twitter Cards
242
		'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',
243
		                                             // Pure Web Brilliant's Social Graph Twitter Cards Extension
244
		'twitter-cards/twitter-cards.php',           // Twitter Cards
245
		'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta
246
		'wp-twitter-cards/twitter_cards.php',        // WP Twitter Cards
247
	);
248
249
	/**
250
	 * Message to display in admin_notice
251
	 * @var string
252
	 */
253
	public $message = '';
254
255
	/**
256
	 * Error to display in admin_notice
257
	 * @var string
258
	 */
259
	public $error = '';
260
261
	/**
262
	 * Modules that need more privacy description.
263
	 * @var string
264
	 */
265
	public $privacy_checks = '';
266
267
	/**
268
	 * Stats to record once the page loads
269
	 *
270
	 * @var array
271
	 */
272
	public $stats = array();
273
274
	/**
275
	 * Allows us to build a temporary security report
276
	 *
277
	 * @var array
278
	 */
279
	static $security_report = array();
280
281
	/**
282
	 * Jetpack_Sync object
283
	 */
284
	public $sync;
285
286
	/**
287
	 * Verified data for JSON authorization request
288
	 */
289
	public $json_api_authorization_request = array();
290
291
	/**
292
	 * Holds the singleton instance of this class
293
	 * @since 2.3.3
294
	 * @var Jetpack
295
	 */
296
	static $instance = false;
297
298
	/**
299
	 * Singleton
300
	 * @static
301
	 */
302
	public static function init() {
303
		if ( ! self::$instance ) {
304
			if ( did_action( 'plugins_loaded' ) )
305
				self::plugin_textdomain();
306
			else
307
				add_action( 'plugins_loaded', array( __CLASS__, 'plugin_textdomain' ), 99 );
308
309
			self::$instance = new Jetpack;
310
311
			self::$instance->plugin_upgrade();
312
313
			add_action( 'init', array( __CLASS__, 'perform_security_reporting' ) );
314
315
		}
316
317
		return self::$instance;
318
	}
319
320
	/**
321
	 * Must never be called statically
322
	 */
323
	function plugin_upgrade() {
324
		// Upgrade: 1.1 -> 1.2
325
		if ( get_option( 'jetpack_id' ) ) {
326
			// Move individual jetpack options to single array of options
327
			$options = array();
328
			foreach ( Jetpack_Options::get_option_names() as $option ) {
329
				if ( false !== $value = get_option( "jetpack_$option" ) ) {
330
					$options[$option] = $value;
331
				}
332
			}
333
334
			if ( $options ) {
335
				Jetpack_Options::update_options( $options );
336
337
				foreach ( array_keys( $options ) as $option ) {
338
					delete_option( "jetpack_$option" );
339
				}
340
			}
341
342
			// Add missing version and old_version options
343 View Code Duplication
			if ( ! $version = Jetpack_Options::get_option( 'version' ) ) {
344
				$version = $old_version = '1.1:' . time();
345
				/**
346
				 * Fires on update, before bumping version numbers up to a new version.
347
				 *
348
				 * @since 3.4.0
349
				 *
350
				 * @param string $version Jetpack version number.
351
				 * @param bool false Does an old version exist. Default is false.
352
				 */
353
				do_action( 'updating_jetpack_version', $version, false );
354
				Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
355
			}
356
		}
357
358
		// Upgrade from a single user token to a user_id-indexed array and a master_user ID
359
		if ( ! Jetpack_Options::get_option( 'user_tokens' ) ) {
360
			if ( $user_token = Jetpack_Options::get_option( 'user_token' ) ) {
361
				$token_parts = explode( '.', $user_token );
362
				if ( isset( $token_parts[2] ) ) {
363
					$master_user = $token_parts[2];
364
					$user_tokens = array( $master_user => $user_token );
365
					Jetpack_Options::update_options( compact( 'master_user', 'user_tokens' ) );
366
					Jetpack_Options::delete_option( 'user_token' );
367
				} else {
368
					// @todo: is this even possible?
369
					trigger_error( sprintf( 'Jetpack::plugin_upgrade found no user_id in user_token "%s"', $user_token ), E_USER_WARNING );
370
				}
371
			}
372
		}
373
374
		// Clean up legacy G+ Authorship data.
375
		if ( get_option( 'gplus_authors' ) ) {
376
			delete_option( 'gplus_authors' );
377
			delete_option( 'hide_gplus' );
378
			delete_metadata( 'post', 0, 'gplus_authorship_disabled', null, true );
379
		}
380
381
		if ( ! get_option( 'jetpack_private_options' ) ) {
382
			$jetpack_options = get_option( 'jetpack_options', array() );
383
			foreach( Jetpack_Options::get_option_names( 'private' ) as $option_name ) {
384
				if ( isset( $jetpack_options[ $option_name ] ) ) {
385
					Jetpack_Options::update_option( $option_name, $jetpack_options[ $option_name ] );
386
					unset( $jetpack_options[ $option_name ] );
387
				}
388
			}
389
			update_option( 'jetpack_options', $jetpack_options );
390
		}
391
392
		if ( Jetpack::is_active() ) {
393
			list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
394
			if ( JETPACK__VERSION != $version ) {
395
				add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
396
				/**
397
				 * Fires when synchronizing all registered options and constants.
398
				 *
399
				 * @since 3.3.0
400
				 */
401
				do_action( 'jetpack_sync_all_registered_options' );
402
			}
403
404
			//if Jetpack is connected check if jetpack_unique_connection exists and if not then set it
405
			$jetpack_unique_connection = get_option( 'jetpack_unique_connection' );
406
			$is_unique_connection = $jetpack_unique_connection && array_key_exists( 'version', $jetpack_unique_connection );
407
			if ( ! $is_unique_connection ) {
408
				$jetpack_unique_connection = array(
409
					'connected'     => 1,
410
					'disconnected'  => -1,
411
					'version'       => '3.6.1'
412
				);
413
				update_option( 'jetpack_unique_connection', $jetpack_unique_connection );
414
			}
415
		}
416
417
		if ( get_option( 'jetpack_json_api_full_management' ) ) {
418
			delete_option( 'jetpack_json_api_full_management' );
419
			self::activate_manage();
420
		}
421
422
	}
423
424
	static function activate_manage( ) {
425
426
		if ( did_action( 'init' ) || current_filter() == 'init' ) {
427
			self::activate_module( 'manage', false, false );
428
		} else if ( !  has_action( 'init' , array( __CLASS__, 'activate_manage' ) ) ) {
429
			add_action( 'init', array( __CLASS__, 'activate_manage' ) );
430
		}
431
432
	}
433
434
	/**
435
	 * Constructor.  Initializes WordPress hooks
436
	 */
437
	private function __construct() {
438
		/*
439
		 * Check for and alert any deprecated hooks
440
		 */
441
		add_action( 'init', array( $this, 'deprecated_hooks' ) );
442
443
		/*
444
		 * Do things that should run even in the network admin
445
		 * here, before we potentially fail out.
446
		 */
447
		add_filter( 'jetpack_require_lib_dir', array( $this, 'require_lib_dir' ) );
448
449
		/**
450
		 * We need sync object even in Multisite mode
451
		 */
452
		$this->sync = new Jetpack_Sync;
453
454
		/**
455
		 * Trigger a wp_version sync when updating WP versions
456
		 **/
457
		add_action( 'upgrader_process_complete', array( 'Jetpack', 'update_get_wp_version' ), 10, 2 );
458
		$this->sync->mock_option( 'wp_version', array( 'Jetpack', 'get_wp_version' ) );
459
460
		add_action( 'init', array( $this, 'sync_update_data') );
461
		add_action( 'init', array( $this, 'sync_theme_data' ) );
462
463
		/*
464
		 * Load things that should only be in Network Admin.
465
		 *
466
		 * For now blow away everything else until a more full
467
		 * understanding of what is needed at the network level is
468
		 * available
469
		 */
470
		if( is_multisite() ) {
471
			Jetpack_Network::init();
472
473
			// Only sync this info if we are on a multi site
474
			// @since  3.7
475
			$this->sync->mock_option( 'network_name', array( 'Jetpack', 'network_name' ) );
476
			$this->sync->mock_option( 'network_allow_new_registrations', array( 'Jetpack', 'network_allow_new_registrations' ) );
477
			$this->sync->mock_option( 'network_add_new_users', array( 'Jetpack', 'network_add_new_users' ) );
478
			$this->sync->mock_option( 'network_site_upload_space', array( 'Jetpack', 'network_site_upload_space' ) );
479
			$this->sync->mock_option( 'network_upload_file_types', array( 'Jetpack', 'network_upload_file_types' ) );
480
			$this->sync->mock_option( 'network_enable_administration_menus', array( 'Jetpack', 'network_enable_administration_menus' ) );
481
482
			if( is_network_admin() ) {
483
				// Sync network site data if it is updated or not.
484
				add_action( 'update_wpmu_options', array( $this, 'update_jetpack_network_settings' ) );
485
				return; // End here to prevent single site actions from firing
486
			}
487
		}
488
489
490
		$theme_slug = get_option( 'stylesheet' );
491
492
493
		// Modules should do Jetpack_Sync::sync_options( __FILE__, $option, ... ); instead
494
		// We access the "internal" method here only because the Jetpack object isn't instantiated yet
495
		$this->sync->options(
496
			JETPACK__PLUGIN_DIR . 'jetpack.php',
497
			'home',
498
			'siteurl',
499
			'blogname',
500
			'gmt_offset',
501
			'timezone_string',
502
			'security_report',
503
			'stylesheet',
504
			"theme_mods_{$theme_slug}",
505
			'jetpack_sync_non_public_post_stati',
506
			'jetpack_options',
507
			'site_icon', // (int) - ID of core's Site Icon attachment ID
508
			'default_post_format',
509
			'default_category',
510
			'large_size_w',
511
			'large_size_h',
512
			'thumbnail_size_w',
513
			'thumbnail_size_h',
514
			'medium_size_w',
515
			'medium_size_h',
516
			'thumbnail_crop',
517
			'image_default_link_type'
518
		);
519
520
		foreach( Jetpack_Options::get_option_names( 'non-compact' ) as $option ) {
521
			$this->sync->options( __FILE__, 'jetpack_' . $option );
522
		}
523
524
		/**
525
		 * Sometimes you want to sync data to .com without adding options to .org sites.
526
		 * The mock option allows you to do just that.
527
		 */
528
		$this->sync->mock_option( 'is_main_network',   array( $this, 'is_main_network_option' ) );
529
		$this->sync->mock_option( 'is_multi_site', array( $this, 'is_multisite' ) );
530
		$this->sync->mock_option( 'main_network_site', array( $this, 'jetpack_main_network_site_option' ) );
531
		$this->sync->mock_option( 'single_user_site', array( 'Jetpack', 'is_single_user_site' ) );
532
		$this->sync->mock_option( 'stat_data', array( $this, 'get_stat_data' ) );
533
534
		$this->sync->mock_option( 'has_file_system_write_access', array( 'Jetpack', 'file_system_write_access' ) );
535
		$this->sync->mock_option( 'is_version_controlled', array( 'Jetpack', 'is_version_controlled' ) );
536
		$this->sync->mock_option( 'max_upload_size', 'wp_max_upload_size' );
537
		$this->sync->mock_option( 'content_width', array( 'Jetpack', 'get_content_width' ) );
538
539
		/**
540
		 * Trigger an update to the main_network_site when we update the blogname of a site.
541
		 *
542
		 */
543
		add_action( 'update_option_siteurl', array( $this, 'update_jetpack_main_network_site_option' ) );
544
545
		add_action( 'update_option', array( $this, 'log_settings_change' ), 10, 3 );
546
547
		// Update the settings everytime the we register a new user to the site or we delete a user.
548
		add_action( 'user_register', array( $this, 'is_single_user_site_invalidate' ) );
549
		add_action( 'deleted_user', array( $this, 'is_single_user_site_invalidate' ) );
550
551
		// Unlink user before deleting the user from .com
552
		add_action( 'deleted_user', array( $this, 'unlink_user' ), 10, 1 );
553
		add_action( 'remove_user_from_blog', array( $this, 'unlink_user' ), 10, 1 );
554
555
		if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && isset( $_GET['for'] ) && 'jetpack' == $_GET['for'] ) {
556
			@ini_set( 'display_errors', false ); // Display errors can cause the XML to be not well formed.
557
558
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
559
			$this->xmlrpc_server = new Jetpack_XMLRPC_Server();
560
561
			$this->require_jetpack_authentication();
562
563
			if ( Jetpack::is_active() ) {
564
				// Hack to preserve $HTTP_RAW_POST_DATA
565
				add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
566
567
				$signed = $this->verify_xml_rpc_signature();
568
				if ( $signed && ! is_wp_error( $signed ) ) {
569
					// The actual API methods.
570
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'xmlrpc_methods' ) );
571
				} else {
572
					add_filter( 'xmlrpc_methods', '__return_empty_array' );
573
				}
574
			} else {
575
				// The bootstrap API methods.
576
				add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'bootstrap_xmlrpc_methods' ) );
577
			}
578
579
			// Now that no one can authenticate, and we're whitelisting all XML-RPC methods, force enable_xmlrpc on.
580
			add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
581
		} elseif ( is_admin() && isset( $_POST['action'] ) && 'jetpack_upload_file' == $_POST['action'] ) {
582
			$this->require_jetpack_authentication();
583
			$this->add_remote_request_handlers();
584
		} else {
585
			if ( Jetpack::is_active() ) {
586
				add_action( 'login_form_jetpack_json_api_authorization', array( &$this, 'login_form_json_api_authorization' ) );
587
				add_filter( 'xmlrpc_methods', array( $this, 'public_xmlrpc_methods' ) );
588
			}
589
		}
590
591
		if ( Jetpack::is_active() ) {
592
			Jetpack_Heartbeat::init();
593
		}
594
595
		add_action( 'jetpack_clean_nonces', array( 'Jetpack', 'clean_nonces' ) );
596
		if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
597
			wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
598
		}
599
600
		add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ) );
601
602
		add_action( 'admin_init', array( $this, 'admin_init' ) );
603
		add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
604
605
		add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
606
607
		add_action( 'wp_dashboard_setup', array( $this, 'wp_dashboard_setup' ) );
608
		// Filter the dashboard meta box order to swap the new one in in place of the old one.
609
		add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) );
610
611
		add_action( 'wp_ajax_jetpack-sync-reindex-trigger', array( $this, 'sync_reindex_trigger' ) );
612
		add_action( 'wp_ajax_jetpack-sync-reindex-status', array( $this, 'sync_reindex_status' ) );
613
614
		// Jump Start AJAX callback function
615
		add_action( 'wp_ajax_jetpack_jumpstart_ajax',  array( $this, 'jetpack_jumpstart_ajax_callback' ) );
616
		add_action( 'update_option', array( $this, 'jumpstart_has_updated_module_option' ) );
617
618
		// Identity Crisis AJAX callback function
619
		add_action( 'wp_ajax_jetpack_resolve_identity_crisis', array( $this, 'resolve_identity_crisis_ajax_callback' ) );
620
621
		// JITM AJAX callback function
622
		add_action( 'wp_ajax_jitm_ajax',  array( $this, 'jetpack_jitm_ajax_callback' ) );
623
624
		add_action( 'wp_ajax_jetpack_admin_ajax',          array( $this, 'jetpack_admin_ajax_callback' ) );
625
		add_action( 'wp_ajax_jetpack_admin_ajax_refresh',  array( $this, 'jetpack_admin_ajax_refresh_data' ) );
626
627
		add_action( 'wp_loaded', array( $this, 'register_assets' ) );
628
		add_action( 'wp_enqueue_scripts', array( $this, 'devicepx' ) );
629
		add_action( 'customize_controls_enqueue_scripts', array( $this, 'devicepx' ) );
630
		add_action( 'admin_enqueue_scripts', array( $this, 'devicepx' ) );
631
632
		add_action( 'jetpack_activate_module', array( $this, 'activate_module_actions' ) );
633
634
		add_action( 'plugins_loaded', array( $this, 'extra_oembed_providers' ), 100 );
635
636
		add_action( 'jetpack_notices', array( $this, 'show_development_mode_notice' ) );
637
638
		/**
639
		 * These actions run checks to load additional files.
640
		 * They check for external files or plugins, so they need to run as late as possible.
641
		 */
642
		add_action( 'wp_head', array( $this, 'check_open_graph' ),       1 );
643
		add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ),     999 );
644
		add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
645
646
		add_filter( 'plugins_url',      array( 'Jetpack', 'maybe_min_asset' ),     1, 3 );
647
		add_filter( 'style_loader_tag', array( 'Jetpack', 'maybe_inline_style' ), 10, 2 );
648
649
		add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 4 );
650
651
		add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
652
		add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
653
654
		// A filter to control all just in time messages
655
		add_filter( 'jetpack_just_in_time_msgs', '__return_true' );
656
657
		/**
658
		 * This is the hack to concatinate all css files into one.
659
		 * For description and reasoning see the implode_frontend_css method
660
		 *
661
		 * Super late priority so we catch all the registered styles
662
		 */
663
		if( !is_admin() ) {
664
			add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first
665
			add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`
666
		}
667
668
		// Sync Core Icon: Detect changes in Core's Site Icon and make it syncable.
669
		add_action( 'add_option_site_icon',    array( $this, 'jetpack_sync_core_icon' ) );
670
		add_action( 'update_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
671
		add_action( 'delete_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
672
		add_action( 'jetpack_heartbeat',       array( $this, 'jetpack_sync_core_icon' ) );
673
674
	}
675
676
	/*
677
	 * Make sure any site icon added to core can get
678
	 * synced back to dotcom, so we can display it there.
679
	 */
680
	function jetpack_sync_core_icon() {
681
		if ( function_exists( 'get_site_icon_url' ) ) {
682
			$url = get_site_icon_url();
683
		} else {
684
			return;
685
		}
686
687
		require_once( JETPACK__PLUGIN_DIR . 'modules/site-icon/site-icon-functions.php' );
688
		// If there's a core icon, maybe update the option.  If not, fall back to Jetpack's.
689
		if ( ! empty( $url ) && $url !== jetpack_site_icon_url() ) {
690
			// This is the option that is synced with dotcom
691
			Jetpack_Options::update_option( 'site_icon_url', $url );
692
		} else if ( empty( $url ) && did_action( 'delete_option_site_icon' ) ) {
693
			Jetpack_Options::delete_option( 'site_icon_url' );
694
		}
695
	}
696
697
	function jetpack_admin_ajax_callback() {
698
		// Check for nonce
699 View Code Duplication
		if ( ! isset( $_REQUEST['adminNonce'] ) || ! wp_verify_nonce( $_REQUEST['adminNonce'], 'jetpack-admin-nonce' ) || ! current_user_can( 'jetpack_manage_modules' ) ) {
700
			wp_die( 'permissions check failed' );
701
		}
702
703
		if ( isset( $_REQUEST['toggleModule'] ) && 'nux-toggle-module' == $_REQUEST['toggleModule'] ) {
704
			$slug = $_REQUEST['thisModuleSlug'];
705
706
			if ( ! in_array( $slug, Jetpack::get_available_modules() ) ) {
707
				wp_die( 'That is not a Jetpack module slug' );
708
			}
709
710
			if ( Jetpack::is_module_active( $slug ) ) {
711
				Jetpack::deactivate_module( $slug );
712
			} else {
713
				Jetpack::activate_module( $slug, false, false );
714
			}
715
716
			$modules = Jetpack_Admin::init()->get_modules();
717
			echo json_encode( $modules[ $slug ] );
718
719
			exit;
720
		}
721
722
		wp_die();
723
	}
724
725
	/*
726
	 * Sometimes we need to refresh the data,
727
	 * especially if the page is visited via a 'history'
728
	 * event like back/forward
729
	 */
730
	function jetpack_admin_ajax_refresh_data() {
731
		// Check for nonce
732 View Code Duplication
		if ( ! isset( $_REQUEST['adminNonce'] ) || ! wp_verify_nonce( $_REQUEST['adminNonce'], 'jetpack-admin-nonce' ) ) {
733
			wp_die( 'permissions check failed' );
734
		}
735
736
		if ( isset( $_REQUEST['refreshData'] ) && 'refresh' == $_REQUEST['refreshData'] ) {
737
			$modules = Jetpack_Admin::init()->get_modules();
738
			echo json_encode( $modules );
739
			exit;
740
		}
741
742
		wp_die();
743
	}
744
745
	/**
746
	 * The callback for the Jump Start ajax requests.
747
	 */
748
	function jetpack_jumpstart_ajax_callback() {
749
		// Check for nonce
750
		if ( ! isset( $_REQUEST['jumpstartNonce'] ) || ! wp_verify_nonce( $_REQUEST['jumpstartNonce'], 'jetpack-jumpstart-nonce' ) )
751
			wp_die( 'permissions check failed' );
752
753
		if ( isset( $_REQUEST['jumpStartActivate'] ) && 'jump-start-activate' == $_REQUEST['jumpStartActivate'] ) {
754
			// Update the jumpstart option
755
			if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
756
				Jetpack_Options::update_option( 'jumpstart', 'jumpstart_activated' );
757
			}
758
759
			// Loops through the requested "Jump Start" modules, and activates them.
760
			// Custom 'no_message' state, so that no message will be shown on reload.
761
			$modules = $_REQUEST['jumpstartModSlug'];
762
			$module_slugs = array();
763
			foreach( $modules as $module => $value ) {
764
				$module_slugs[] = $value['module_slug'];
765
			}
766
767
			// Check for possible conflicting plugins
768
			$module_slugs_filtered = $this->filter_default_modules( $module_slugs );
769
770
			foreach ( $module_slugs_filtered as $module_slug ) {
771
				Jetpack::log( 'activate', $module_slug );
772
				Jetpack::activate_module( $module_slug, false, false );
773
				Jetpack::state( 'message', 'no_message' );
774
			}
775
776
			// Set the default sharing buttons and set to display on posts if none have been set.
777
			$sharing_services = get_option( 'sharing-services' );
778
			$sharing_options  = get_option( 'sharing-options' );
779
			if ( empty( $sharing_services['visible'] ) ) {
780
				// Default buttons to set
781
				$visible = array(
782
					'twitter',
783
					'facebook',
784
					'google-plus-1',
785
				);
786
				$hidden = array();
787
788
				// Set some sharing settings
789
				$sharing = new Sharing_Service();
790
				$sharing_options['global'] = array(
791
					'button_style'  => 'icon',
792
					'sharing_label' => $sharing->default_sharing_label,
793
					'open_links'    => 'same',
794
					'show'          => array( 'post' ),
795
					'custom'        => isset( $sharing_options['global']['custom'] ) ? $sharing_options['global']['custom'] : array()
796
				);
797
798
				update_option( 'sharing-options', $sharing_options );
799
800
				// Send a success response so that we can display an error message.
801
				$success = update_option( 'sharing-services', array( 'visible' => $visible, 'hidden' => $hidden ) );
802
				echo json_encode( $success );
803
				exit;
804
			}
805
806
		} elseif ( isset( $_REQUEST['disableJumpStart'] ) && true == $_REQUEST['disableJumpStart'] ) {
807
			// If dismissed, flag the jumpstart option as such.
808
			// Send a success response so that we can display an error message.
809
			if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
810
				$success = Jetpack_Options::update_option( 'jumpstart', 'jumpstart_dismissed' );
811
				echo json_encode( $success );
812
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method jetpack_jumpstart_ajax_callback() 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...
813
			}
814
815
		} elseif ( isset( $_REQUEST['jumpStartDeactivate'] ) && 'jump-start-deactivate' == $_REQUEST['jumpStartDeactivate'] ) {
816
817
			// FOR TESTING ONLY
818
			// @todo remove
819
			$modules = (array) $_REQUEST['jumpstartModSlug'];
820
			foreach( $modules as $module => $value ) {
821
				if ( !in_array( $value['module_slug'], Jetpack::get_default_modules() ) ) {
822
					Jetpack::log( 'deactivate', $value['module_slug'] );
823
					Jetpack::deactivate_module( $value['module_slug'] );
824
					Jetpack::state( 'message', 'no_message' );
825
				} else {
826
					Jetpack::log( 'activate', $value['module_slug'] );
827
					Jetpack::activate_module( $value['module_slug'], false, false );
828
					Jetpack::state( 'message', 'no_message' );
829
				}
830
			}
831
832
			Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
833
			echo "reload the page";
834
		}
835
836
		wp_die();
837
	}
838
839
	/**
840
	 * The callback for the JITM ajax requests.
841
	 */
842
	function jetpack_jitm_ajax_callback() {
843
		// Check for nonce
844
		if ( ! isset( $_REQUEST['jitmNonce'] ) || ! wp_verify_nonce( $_REQUEST['jitmNonce'], 'jetpack-jitm-nonce' ) ) {
845
			wp_die( 'Module activation failed due to lack of appropriate permissions' );
846
		}
847
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'activate' == $_REQUEST['jitmActionToTake'] ) {
848
			$module_slug = $_REQUEST['jitmModule'];
849
			Jetpack::log( 'activate', $module_slug );
850
			Jetpack::activate_module( $module_slug, false, false );
851
			Jetpack::state( 'message', 'no_message' );
852
853
			//A Jetpack module is being activated through a JITM, track it
854
			$this->stat( 'jitm', $module_slug.'-activated-' . JETPACK__VERSION );
855
			$this->do_stats( 'server_side' );
856
857
			wp_send_json_success();
858
		}
859
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'dismiss' == $_REQUEST['jitmActionToTake'] ) {
860
			// get the hide_jitm options array
861
			$jetpack_hide_jitm = Jetpack_Options::get_option( 'hide_jitm' );
862
			$module_slug = $_REQUEST['jitmModule'];
863
864
			if( ! $jetpack_hide_jitm ) {
865
				$jetpack_hide_jitm = array(
866
					$module_slug => 'hide'
867
				);
868
			} else {
869
				$jetpack_hide_jitm[$module_slug] = 'hide';
870
			}
871
872
			Jetpack_Options::update_option( 'hide_jitm', $jetpack_hide_jitm );
873
874
			//jitm is being dismissed forever, track it
875
			$this->stat( 'jitm', $module_slug.'-dismissed-' . JETPACK__VERSION );
876
			$this->do_stats( 'server_side' );
877
878
			wp_send_json_success();
879
		}
880
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'launch' == $_REQUEST['jitmActionToTake'] ) {
881
			$module_slug = $_REQUEST['jitmModule'];
882
883
			// User went to WordPress.com, track this
884
			$this->stat( 'jitm', $module_slug.'-wordpress-tools-' . JETPACK__VERSION );
885
			$this->do_stats( 'server_side' );
886
887
			wp_send_json_success();
888
		}
889
	}
890
891
	/**
892
	 * If there are any stats that need to be pushed, but haven't been, push them now.
893
	 */
894
	function __destruct() {
895
		if ( ! empty( $this->stats ) ) {
896
			$this->do_stats( 'server_side' );
897
		}
898
	}
899
900
	function jetpack_custom_caps( $caps, $cap, $user_id, $args ) {
901
		switch( $cap ) {
902
			case 'jetpack_connect' :
903
			case 'jetpack_reconnect' :
904
				if ( Jetpack::is_development_mode() ) {
905
					$caps = array( 'do_not_allow' );
906
					break;
907
				}
908
				/**
909
				 * Pass through. If it's not development mode, these should match disconnect.
910
				 * Let users disconnect if it's development mode, just in case things glitch.
911
				 */
912
			case 'jetpack_disconnect' :
913
				/**
914
				 * In multisite, can individual site admins manage their own connection?
915
				 *
916
				 * Ideally, this should be extracted out to a separate filter in the Jetpack_Network class.
917
				 */
918
				if ( is_multisite() && ! is_super_admin() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
919
					if ( ! Jetpack_Network::init()->get_option( 'sub-site-connection-override' ) ) {
920
						/**
921
						 * We need to update the option name -- it's terribly unclear which
922
						 * direction the override goes.
923
						 *
924
						 * @todo: Update the option name to `sub-sites-can-manage-own-connections`
925
						 */
926
						$caps = array( 'do_not_allow' );
927
						break;
928
					}
929
				}
930
931
				$caps = array( 'manage_options' );
932
				break;
933
			case 'jetpack_manage_modules' :
934
			case 'jetpack_activate_modules' :
935
			case 'jetpack_deactivate_modules' :
936
				$caps = array( 'manage_options' );
937
				break;
938
			case 'jetpack_configure_modules' :
939
				$caps = array( 'manage_options' );
940
				break;
941
			case 'jetpack_network_admin_page':
942
			case 'jetpack_network_settings_page':
943
				$caps = array( 'manage_network_plugins' );
944
				break;
945
			case 'jetpack_network_sites_page':
946
				$caps = array( 'manage_sites' );
947
				break;
948
			case 'jetpack_admin_page' :
949
				if ( Jetpack::is_development_mode() ) {
950
					$caps = array( 'manage_options' );
951
					break;
952
				}
953
954
				// Don't ever show to subscribers, but allow access to the page if they're trying to unlink.
955
				if ( ! current_user_can( 'edit_posts' ) ) {
956
					if ( isset( $_GET['redirect'] ) && 'sub-unlink' == $_GET['redirect'] ) {
957
						// We need this in order to unlink the user.
958
						$this->admin_page_load();
959
					}
960
					if ( ! wp_verify_nonce( 'jetpack-unlink' ) ) {
961
						$caps = array( 'do_not_allow' );
962
						break;
963
					}
964
				}
965
966
				if ( ! self::is_active() && ! current_user_can( 'jetpack_connect' ) ) {
967
					$caps = array( 'do_not_allow' );
968
					break;
969
				}
970
				/**
971
				 * Pass through. If it's not development mode, these should match the admin page.
972
				 * Let users disconnect if it's development mode, just in case things glitch.
973
				 */
974
			case 'jetpack_connect_user' :
975
				if ( Jetpack::is_development_mode() ) {
976
					$caps = array( 'do_not_allow' );
977
					break;
978
				}
979
				$caps = array( 'read' );
980
				break;
981
		}
982
		return $caps;
983
	}
984
985
	function require_jetpack_authentication() {
986
		// Don't let anyone authenticate
987
		$_COOKIE = array();
988
		remove_all_filters( 'authenticate' );
989
990
		/**
991
		 * For the moment, remove Limit Login Attempts if its xmlrpc for Jetpack.
992
		 * If Limit Login Attempts is installed as a mu-plugin, it can occasionally
993
		 * generate false-positives.
994
		 */
995
		remove_filter( 'wp_login_failed', 'limit_login_failed' );
996
997
		if ( Jetpack::is_active() ) {
998
			// Allow Jetpack authentication
999
			add_filter( 'authenticate', array( $this, 'authenticate_jetpack' ), 10, 3 );
1000
		}
1001
	}
1002
1003
	/**
1004
	 * Load language files
1005
	 */
1006
	public static function plugin_textdomain() {
1007
		// Note to self, the third argument must not be hardcoded, to account for relocated folders.
1008
		load_plugin_textdomain( 'jetpack', false, dirname( plugin_basename( JETPACK__PLUGIN_FILE ) ) . '/languages/' );
1009
	}
1010
1011
	/**
1012
	 * Register assets for use in various modules and the Jetpack admin page.
1013
	 *
1014
	 * @uses wp_script_is, wp_register_script, plugins_url
1015
	 * @action wp_loaded
1016
	 * @return null
1017
	 */
1018
	public function register_assets() {
1019
		if ( ! wp_script_is( 'spin', 'registered' ) ) {
1020
			wp_register_script( 'spin', plugins_url( '_inc/spin.js', JETPACK__PLUGIN_FILE ), false, '1.3' );
1021
		}
1022
1023
		if ( ! wp_script_is( 'jquery.spin', 'registered' ) ) {
1024
			wp_register_script( 'jquery.spin', plugins_url( '_inc/jquery.spin.js', JETPACK__PLUGIN_FILE ) , array( 'jquery', 'spin' ), '1.3' );
1025
		}
1026
1027 View Code Duplication
		if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) ) {
1028
			wp_register_script( 'jetpack-gallery-settings', plugins_url( '_inc/gallery-settings.js', JETPACK__PLUGIN_FILE ), array( 'media-views' ), '20121225' );
1029
		}
1030
1031
		/**
1032
		 * As jetpack_register_genericons is by default fired off a hook,
1033
		 * the hook may have already fired by this point.
1034
		 * So, let's just trigger it manually.
1035
		 */
1036
		require_once( JETPACK__PLUGIN_DIR . '_inc/genericons.php' );
1037
		jetpack_register_genericons();
1038
1039 View Code Duplication
		if ( ! wp_style_is( 'jetpack-icons', 'registered' ) )
1040
			wp_register_style( 'jetpack-icons', plugins_url( 'css/jetpack-icons.min.css', JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
1041
	}
1042
1043
	/**
1044
	 * Device Pixels support
1045
	 * This improves the resolution of gravatars and wordpress.com uploads on hi-res and zoomed browsers.
1046
	 */
1047
	function devicepx() {
1048
		if ( Jetpack::is_active() ) {
1049
			wp_enqueue_script( 'devicepx', set_url_scheme( 'http://s0.wp.com/wp-content/js/devicepx-jetpack.js' ), array(), gmdate( 'oW' ), true );
1050
		}
1051
	}
1052
1053
	/*
1054
	 * Returns the location of Jetpack's lib directory. This filter is applied
1055
	 * in require_lib().
1056
	 *
1057
	 * @filter require_lib_dir
1058
	 */
1059
	function require_lib_dir() {
1060
		return JETPACK__PLUGIN_DIR . '_inc/lib';
1061
	}
1062
1063
	/**
1064
	 * Return the network_site_url so that .com knows what network this site is a part of.
1065
	 * @param  bool $option
1066
	 * @return string
1067
	 */
1068
	public function jetpack_main_network_site_option( $option ) {
1069
		return network_site_url();
1070
	}
1071
	/**
1072
	 * Network Name.
1073
	 */
1074
	static function network_name( $option = null ) {
1075
		global $current_site;
1076
		return $current_site->site_name;
1077
	}
1078
	/**
1079
	 * Does the network allow new user and site registrations.
1080
	 * @return string
1081
	 */
1082
	static function network_allow_new_registrations( $option = null ) {
1083
		return ( in_array( get_site_option( 'registration' ), array('none', 'user', 'blog', 'all' ) ) ? get_site_option( 'registration') : 'none' );
1084
	}
1085
	/**
1086
	 * Does the network allow admins to add new users.
1087
	 * @return boolian
1088
	 */
1089
	static function network_add_new_users( $option = null ) {
1090
		return (bool) get_site_option( 'add_new_users' );
1091
	}
1092
	/**
1093
	 * File upload psace left per site in MB.
1094
	 *  -1 means NO LIMIT.
1095
	 * @return number
1096
	 */
1097
	static function network_site_upload_space( $option = null ) {
1098
		// value in MB
1099
		return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
1100
	}
1101
1102
	/**
1103
	 * Network allowed file types.
1104
	 * @return string
1105
	 */
1106
	static function network_upload_file_types( $option = null ) {
1107
		return get_site_option( 'upload_filetypes', 'jpg jpeg png gif' );
1108
	}
1109
1110
	/**
1111
	 * Maximum file upload size set by the network.
1112
	 * @return number
1113
	 */
1114
	static function network_max_upload_file_size( $option = null ) {
1115
		// value in KB
1116
		return get_site_option( 'fileupload_maxk', 300 );
1117
	}
1118
1119
	/**
1120
	 * Lets us know if a site allows admins to manage the network.
1121
	 * @return array
1122
	 */
1123
	static function network_enable_administration_menus( $option = null ) {
1124
		return get_site_option( 'menu_items' );
1125
	}
1126
1127
	/**
1128
	 * Return whether we are dealing with a multi network setup or not.
1129
	 * The reason we are type casting this is because we want to avoid the situation where
1130
	 * the result is false since when is_main_network_option return false it cases
1131
	 * the rest the get_option( 'jetpack_is_multi_network' ); to return the value that is set in the
1132
	 * database which could be set to anything as opposed to what this function returns.
1133
	 * @param  bool  $option
1134
	 *
1135
	 * @return boolean
1136
	 */
1137
	public function is_main_network_option( $option ) {
1138
		// return '1' or ''
1139
		return (string) (bool) Jetpack::is_multi_network();
1140
	}
1141
1142
	/**
1143
	 * Return true if we are with multi-site or multi-network false if we are dealing with single site.
1144
	 *
1145
	 * @param  string  $option
1146
	 * @return boolean
1147
	 */
1148
	public function is_multisite( $option ) {
1149
		return (string) (bool) is_multisite();
1150
	}
1151
1152
	/**
1153
	 * Implemented since there is no core is multi network function
1154
	 * Right now there is no way to tell if we which network is the dominant network on the system
1155
	 *
1156
	 * @since  3.3
1157
	 * @return boolean
1158
	 */
1159
	public static function is_multi_network() {
1160
		global  $wpdb;
1161
1162
		// if we don't have a multi site setup no need to do any more
1163
		if ( ! is_multisite() ) {
1164
			return false;
1165
		}
1166
1167
		$num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
1168
		if ( $num_sites > 1 ) {
1169
			return true;
1170
		} else {
1171
			return false;
1172
		}
1173
	}
1174
1175
	/**
1176
	 * Trigger an update to the main_network_site when we update the siteurl of a site.
1177
	 * @return null
1178
	 */
1179
	function update_jetpack_main_network_site_option() {
1180
		// do_action( 'add_option_$option', '$option', '$value-of-the-option' );
1181
		/**
1182
		 * Fires when the site URL is updated.
1183
		 * Determines if the site is the main site of a Mulitiste network.
1184
		 *
1185
		 * @since 3.3.0
1186
		 *
1187
		 * @param string jetpack_main_network_site.
1188
		 * @param string network_site_url() Site URL for the "main" site of the current Multisite network.
1189
		 */
1190
		do_action( 'add_option_jetpack_main_network_site', 'jetpack_main_network_site', network_site_url() );
1191
		/**
1192
		 * Fires when the site URL is updated.
1193
		 * Determines if the is part of a multi network.
1194
		 *
1195
		 * @since 3.3.0
1196
		 *
1197
		 * @param string jetpack_is_main_network.
1198
		 * @param bool Jetpack::is_multi_network() Is the site part of a multi network.
1199
		 */
1200
		do_action( 'add_option_jetpack_is_main_network', 'jetpack_is_main_network', (string) (bool) Jetpack::is_multi_network() );
1201
		/**
1202
		 * Fires when the site URL is updated.
1203
		 * Determines if the site is part of a multisite network.
1204
		 *
1205
		 * @since 3.4.0
1206
		 *
1207
		 * @param string jetpack_is_multi_site.
1208
		 * @param bool is_multisite() Is the site part of a mutlisite network.
1209
		 */
1210
		do_action( 'add_option_jetpack_is_multi_site', 'jetpack_is_multi_site', (string) (bool) is_multisite() );
1211
	}
1212
	/**
1213
	 * Triggered after a user updates the network settings via Network Settings Admin Page
1214
	 *
1215
	 */
1216
	function update_jetpack_network_settings() {
1217
		// Only sync this info for the main network site.
1218
		do_action( 'add_option_jetpack_network_name', 'jetpack_network_name', Jetpack::network_name() );
1219
		do_action( 'add_option_jetpack_network_allow_new_registrations', 'jetpack_network_allow_new_registrations', Jetpack::network_allow_new_registrations() );
1220
		do_action( 'add_option_jetpack_network_add_new_users', 'jetpack_network_add_new_users', Jetpack::network_add_new_users() );
1221
		do_action( 'add_option_jetpack_network_site_upload_space', 'jetpack_network_site_upload_space', Jetpack::network_site_upload_space() );
1222
		do_action( 'add_option_jetpack_network_upload_file_types', 'jetpack_network_upload_file_types', Jetpack::network_upload_file_types() );
1223
		do_action( 'add_option_jetpack_network_enable_administration_menus', 'jetpack_network_enable_administration_menus', Jetpack::network_enable_administration_menus() );
1224
1225
	}
1226
1227
	/**
1228
	 * Get back if the current site is single user site.
1229
	 *
1230
	 * @return bool
1231
	 */
1232
	public static function is_single_user_site() {
1233
1234
		$user_query = new WP_User_Query( array(
1235
			'blog_id' => get_current_blog_id(),
1236
			'fields'  => 'ID',
1237
			'number' => 2
1238
		) );
1239
		return 1 === (int) $user_query->get_total();
1240
	}
1241
1242
	/**
1243
	 * Returns true if the site has file write access false otherwise.
1244
	 * @return string ( '1' | '0' )
1245
	 **/
1246
	public static function file_system_write_access() {
1247
		if ( ! function_exists( 'get_filesystem_method' ) ) {
1248
			require_once( ABSPATH . 'wp-admin/includes/file.php' );
1249
		}
1250
1251
		require_once( ABSPATH . 'wp-admin/includes/template.php' );
1252
1253
		$filesystem_method = get_filesystem_method();
1254
		if ( $filesystem_method === 'direct' ) {
1255
			return 1;
1256
		}
1257
1258
		ob_start();
1259
		$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1260
		ob_end_clean();
1261
		if ( $filesystem_credentials_are_stored ) {
1262
			return 1;
1263
		}
1264
		return 0;
1265
	}
1266
1267
	/**
1268
	 * Finds out if a site is using a version control system.
1269
	 * @return string ( '1' | '0' )
1270
	 **/
1271
	public static function is_version_controlled() {
1272
1273
		if ( !class_exists( 'WP_Automatic_Updater' ) ) {
1274
			require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
1275
		}
1276
		$updater = new WP_Automatic_Updater();
1277
		$is_version_controlled = strval( $updater->is_vcs_checkout( $context = ABSPATH ) );
1278
		// transients should not be empty
1279
		if ( empty( $is_version_controlled ) ) {
1280
			$is_version_controlled = '0';
1281
		}
1282
		return $is_version_controlled;
1283
	}
1284
1285
	/**
1286
	 * Determines whether the current theme supports featured images or not.
1287
	 * @return string ( '1' | '0' )
1288
	 */
1289
	public static function featured_images_enabled() {
1290
		return current_theme_supports( 'post-thumbnails' ) ? '1' : '0';
1291
	}
1292
1293
	/*
1294
	 * Sync back wp_version
1295
	 */
1296
	public static function get_wp_version() {
1297
		global $wp_version;
1298
		return $wp_version;
1299
	}
1300
1301
	/**
1302
	 * Keeps wp_version in sync with .com when WordPress core updates
1303
	 **/
1304
	public static function update_get_wp_version( $update, $meta_data ) {
1305
		if ( 'update' === $meta_data['action'] && 'core' === $meta_data['type'] ) {
1306
			/** This action is documented in wp-includes/option.php */
1307
			/**
1308
			 * This triggers the sync for the jetpack version
1309
			 * See Jetpack_Sync options method for more info.
1310
			 */
1311
			do_action( 'add_option_jetpack_wp_version', 'jetpack_wp_version', (string) Jetpack::get_wp_version() );
1312
		}
1313
	}
1314
1315
	/**
1316
	 * Triggers a sync of update counts and update details
1317
	 */
1318
	function sync_update_data() {
1319
		// Anytime WordPress saves update data, we'll want to sync update data
1320
		add_action( 'set_site_transient_update_plugins', array( 'Jetpack', 'refresh_update_data' ) );
1321
		add_action( 'set_site_transient_update_themes', array( 'Jetpack', 'refresh_update_data' ) );
1322
		add_action( 'set_site_transient_update_core', array( 'Jetpack', 'refresh_update_data' ) );
1323
		// Anytime a connection to jetpack is made, sync the update data
1324
		add_action( 'jetpack_site_registered', array( 'Jetpack', 'refresh_update_data' ) );
1325
		// Anytime the Jetpack Version changes, sync the the update data
1326
		add_action( 'updating_jetpack_version', array( 'Jetpack', 'refresh_update_data' ) );
1327
1328
		if ( current_user_can( 'update_core' ) && current_user_can( 'update_plugins' ) && current_user_can( 'update_themes' ) ) {
1329
			$this->sync->mock_option( 'updates', array( 'Jetpack', 'get_updates' ) );
1330
		}
1331
1332
		$this->sync->mock_option( 'update_details', array( 'Jetpack', 'get_update_details' ) );
1333
	}
1334
1335
	/**
1336
	 * Triggers a sync of information specific to the current theme.
1337
	 */
1338
	function sync_theme_data() {
1339
		add_action( 'switch_theme', array( 'Jetpack', 'refresh_theme_data' ) );
1340
		$this->sync->mock_option( 'featured_images_enabled', array( 'Jetpack', 'featured_images_enabled' ) );
1341
	}
1342
1343
	/**
1344
	 * jetpack_updates is saved in the following schema:
1345
	 *
1346
	 * array (
1347
	 *      'plugins'                       => (int) Number of plugin updates available.
1348
	 *      'themes'                        => (int) Number of theme updates available.
1349
	 *      'wordpress'                     => (int) Number of WordPress core updates available.
1350
	 *      'translations'                  => (int) Number of translation updates available.
1351
	 *      'total'                         => (int) Total of all available updates.
1352
	 *      'wp_update_version'             => (string) The latest available version of WordPress, only present if a WordPress update is needed.
1353
	 * )
1354
	 * @return array
1355
	 */
1356
	public static function get_updates() {
1357
		$update_data = wp_get_update_data();
1358
1359
		// Stores the individual update counts as well as the total count.
1360
		if ( isset( $update_data['counts'] ) ) {
1361
			$updates = $update_data['counts'];
1362
		}
1363
1364
		// If we need to update WordPress core, let's find the latest version number.
1365 View Code Duplication
		if ( ! empty( $updates['wordpress'] ) ) {
1366
			$cur = get_preferred_from_update_core();
1367
			if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
1368
				$updates['wp_update_version'] = $cur->current;
1369
			}
1370
		}
1371
		return isset( $updates ) ? $updates : array();
1372
	}
1373
1374
	public static function get_update_details() {
1375
		$update_details = array(
1376
			'update_core' => get_site_transient( 'update_core' ),
1377
			'update_plugins' => get_site_transient( 'update_plugins' ),
1378
			'update_themes' => get_site_transient( 'update_themes' ),
1379
		);
1380
		return $update_details;
1381
	}
1382
1383
	public static function refresh_update_data() {
1384
		if ( current_user_can( 'update_core' ) && current_user_can( 'update_plugins' ) && current_user_can( 'update_themes' ) ) {
1385
			/**
1386
			 * Fires whenever the amount of updates needed for a site changes.
1387
			 * Syncs an array that includes the number of theme, plugin, and core updates available, as well as the latest core version available.
1388
			 *
1389
			 * @since 3.7.0
1390
			 *
1391
			 * @param string jetpack_updates
1392
			 * @param array Update counts calculated by Jetpack::get_updates
1393
			 */
1394
			do_action( 'add_option_jetpack_updates', 'jetpack_updates', Jetpack::get_updates() );
1395
		}
1396
		/**
1397
		 * Fires whenever the amount of updates needed for a site changes.
1398
		 * Syncs an array of core, theme, and plugin data, and which of each is out of date
1399
		 *
1400
		 * @since 3.7.0
1401
		 *
1402
		 * @param string jetpack_update_details
1403
		 * @param array Update details calculated by Jetpack::get_update_details
1404
		 */
1405
		do_action( 'add_option_jetpack_update_details', 'jetpack_update_details', Jetpack::get_update_details() );
1406
	}
1407
1408
	public static function refresh_theme_data() {
1409
		/**
1410
		 * Fires whenever a theme change is made.
1411
		 *
1412
		 * @since 3.8.1
1413
		 *
1414
		 * @param string featured_images_enabled
1415
		 * @param boolean Whether featured images are enabled or not
1416
		 */
1417
		do_action( 'add_option_jetpack_featured_images_enabled', 'jetpack_featured_images_enabled', Jetpack::featured_images_enabled() );
1418
	}
1419
1420
	/**
1421
	 * Invalides the transient as well as triggers the update of the mock option.
1422
	 *
1423
	 * @return null
1424
	 */
1425
	function is_single_user_site_invalidate() {
1426
		/**
1427
		 * Fires when a user is added or removed from a site.
1428
		 * Determines if the site is a single user site.
1429
		 *
1430
		 * @since 3.4.0
1431
		 *
1432
		 * @param string jetpack_single_user_site.
1433
		 * @param bool Jetpack::is_single_user_site() Is the current site a single user site.
1434
		 */
1435
		do_action( 'update_option_jetpack_single_user_site', 'jetpack_single_user_site', (bool) Jetpack::is_single_user_site() );
1436
	}
1437
1438
	/**
1439
	 * Is Jetpack active?
1440
	 */
1441
	public static function is_active() {
1442
		return (bool) Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1443
	}
1444
1445
	/**
1446
	 * Is Jetpack in development (offline) mode?
1447
	 */
1448
	public static function is_development_mode() {
1449
		$development_mode = false;
1450
1451
		if ( defined( 'JETPACK_DEV_DEBUG' ) ) {
1452
			$development_mode = JETPACK_DEV_DEBUG;
1453
		}
1454
1455
		elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1456
			$development_mode = true;
1457
		}
1458
		/**
1459
		 * Filters Jetpack's development mode.
1460
		 *
1461
		 * @see http://jetpack.me/support/development-mode/
1462
		 *
1463
		 * @since 2.2.1
1464
		 *
1465
		 * @param bool $development_mode Is Jetpack's development mode active.
1466
		 */
1467
		return apply_filters( 'jetpack_development_mode', $development_mode );
1468
	}
1469
1470
	/**
1471
	* Get Jetpack development mode notice text and notice class.
1472
	*
1473
	* Mirrors the checks made in Jetpack::is_development_mode
1474
	*
1475
	*/
1476
	public static function show_development_mode_notice() {
1477
		if ( Jetpack::is_development_mode() ) {
1478
			if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
1479
				$notice = sprintf(
1480
					/* translators: %s is a URL */
1481
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the JETPACK_DEV_DEBUG constant being defined in wp-config.php or elsewhere.', 'jetpack' ),
1482
					'http://jetpack.me/support/development-mode/'
1483
				);
1484
			} elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1485
				$notice = sprintf(
1486
					/* translators: %s is a URL */
1487
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via site URL lacking a dot (e.g. http://localhost).', 'jetpack' ),
1488
					'http://jetpack.me/support/development-mode/'
1489
				);
1490
			} else {
1491
				$notice = sprintf(
1492
					/* translators: %s is a URL */
1493
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the jetpack_development_mode filter.', 'jetpack' ),
1494
					'http://jetpack.me/support/development-mode/'
1495
				);
1496
			}
1497
1498
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1499
		}
1500
1501
		// Throw up a notice if using a development version and as for feedback.
1502
		if ( Jetpack::is_development_version() ) {
1503
			/* translators: %s is a URL */
1504
			$notice = sprintf( __( 'You are currently running a development version of Jetpack. <a href="%s" target="_blank">Submit your feedback</a>', 'jetpack' ), 'https://jetpack.me/contact-support/beta-group/' );
1505
1506
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1507
		}
1508
	}
1509
1510
	/**
1511
	 * Whether Jetpack's version maps to a public release, or a development version.
1512
	 */
1513
	public static function is_development_version() {
1514
		return ! preg_match( '/^\d+(\.\d+)+$/', JETPACK__VERSION );
1515
	}
1516
1517
	/**
1518
	 * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1519
	 */
1520
	public static function is_user_connected( $user_id = false ) {
1521
		$user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
1522
		if ( ! $user_id ) {
1523
			return false;
1524
		}
1525
		return (bool) Jetpack_Data::get_access_token( $user_id );
1526
	}
1527
1528
	/**
1529
	 * Get the wpcom user data of the current|specified connected user.
1530
	 */
1531 View Code Duplication
	public static function get_connected_user_data( $user_id = null ) {
1532
		if ( ! $user_id ) {
1533
			$user_id = get_current_user_id();
1534
		}
1535
		Jetpack::load_xml_rpc_client();
1536
		$xml = new Jetpack_IXR_Client( array(
1537
			'user_id' => $user_id,
1538
		) );
1539
		$xml->query( 'wpcom.getUser' );
1540
		if ( ! $xml->isError() ) {
1541
			return $xml->getResponse();
1542
		}
1543
		return false;
1544
	}
1545
1546
	/**
1547
	 * Get the wpcom email of the current|specified connected user.
1548
	 */
1549 View Code Duplication
	public static function get_connected_user_email( $user_id = null ) {
1550
		if ( ! $user_id ) {
1551
			$user_id = get_current_user_id();
1552
		}
1553
		Jetpack::load_xml_rpc_client();
1554
		$xml = new Jetpack_IXR_Client( array(
1555
			'user_id' => $user_id,
1556
		) );
1557
		$xml->query( 'wpcom.getUserEmail' );
1558
		if ( ! $xml->isError() ) {
1559
			return $xml->getResponse();
1560
		}
1561
		return false;
1562
	}
1563
1564
	/**
1565
	 * Get the wpcom email of the master user.
1566
	 */
1567
	public static function get_master_user_email() {
1568
		$master_user_id = Jetpack_Options::get_option( 'master_user' );
1569
		if ( $master_user_id ) {
1570
			return self::get_connected_user_email( $master_user_id );
1571
		}
1572
		return '';
1573
	}
1574
1575
	function current_user_is_connection_owner() {
1576
		$user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1577
		return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && get_current_user_id() === $user_token->external_user_id;
1578
	}
1579
1580
	/**
1581
	 * Add any extra oEmbed providers that we know about and use on wpcom for feature parity.
1582
	 */
1583
	function extra_oembed_providers() {
1584
		// Cloudup: https://dev.cloudup.com/#oembed
1585
		wp_oembed_add_provider( 'https://cloudup.com/*' , 'https://cloudup.com/oembed' );
1586
		wp_oembed_add_provider( 'https://me.sh/*', 'https://me.sh/oembed?format=json' );
1587
		wp_oembed_add_provider( '#https?://(www\.)?gfycat\.com/.*#i', 'https://api.gfycat.com/v1/oembed', true );
1588
	}
1589
1590
	/**
1591
	 * Synchronize connected user role changes
1592
	 */
1593
	function user_role_change( $user_id ) {
1594
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $user_id ) ) {
1595
			$current_user_id = get_current_user_id();
1596
			wp_set_current_user( $user_id );
1597
			$role = $this->translate_current_user_to_role();
1598
			$signed_role = $this->sign_role( $role );
1599
			wp_set_current_user( $current_user_id );
1600
1601
			$master_token   = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1602
			$master_user_id = absint( $master_token->external_user_id );
1603
1604
			if ( ! $master_user_id )
1605
				return; // this shouldn't happen
1606
1607
			Jetpack::xmlrpc_async_call( 'jetpack.updateRole', $user_id, $signed_role );
1608
			//@todo retry on failure
1609
1610
			//try to choose a new master if we're demoting the current one
1611
			if ( $user_id == $master_user_id && 'administrator' != $role ) {
1612
				$query = new WP_User_Query(
1613
					array(
1614
						'fields'  => array( 'id' ),
1615
						'role'    => 'administrator',
1616
						'orderby' => 'id',
1617
						'exclude' => array( $master_user_id ),
1618
					)
1619
				);
1620
				$new_master = false;
1621
				foreach ( $query->results as $result ) {
1622
					$uid = absint( $result->id );
1623
					if ( $uid && Jetpack::is_user_connected( $uid ) ) {
1624
						$new_master = $uid;
1625
						break;
1626
					}
1627
				}
1628
1629
				if ( $new_master ) {
1630
					Jetpack_Options::update_option( 'master_user', $new_master );
1631
				}
1632
				// else disconnect..?
1633
			}
1634
		}
1635
	}
1636
1637
	/**
1638
	 * Loads the currently active modules.
1639
	 */
1640
	public static function load_modules() {
1641
		if ( ! self::is_active() && !self::is_development_mode() ) {
1642
			if ( ! is_multisite() || ! get_site_option( 'jetpack_protect_active' ) ) {
1643
				return;
1644
			}
1645
		}
1646
1647
		$version = Jetpack_Options::get_option( 'version' );
1648 View Code Duplication
		if ( ! $version ) {
1649
			$version = $old_version = JETPACK__VERSION . ':' . time();
1650
			/** This action is documented in class.jetpack.php */
1651
			do_action( 'updating_jetpack_version', $version, false );
1652
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1653
		}
1654
		list( $version ) = explode( ':', $version );
1655
1656
		$modules = array_filter( Jetpack::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1657
1658
		$modules_data = array();
1659
1660
		// Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1661
		if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1662
			$updated_modules = array();
1663
			foreach ( $modules as $module ) {
1664
				$modules_data[ $module ] = Jetpack::get_module( $module );
1665
				if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1666
					continue;
1667
				}
1668
1669
				if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1670
					continue;
1671
				}
1672
1673
				$updated_modules[] = $module;
1674
			}
1675
1676
			$modules = array_diff( $modules, $updated_modules );
1677
		}
1678
1679
		$is_development_mode = Jetpack::is_development_mode();
1680
1681
		foreach ( $modules as $module ) {
1682
			// If we're in dev mode, disable modules requiring a connection
1683
			if ( $is_development_mode ) {
1684
				// Prime the pump if we need to
1685
				if ( empty( $modules_data[ $module ] ) ) {
1686
					$modules_data[ $module ] = Jetpack::get_module( $module );
1687
				}
1688
				// If the module requires a connection, but we're in local mode, don't include it.
1689
				if ( $modules_data[ $module ]['requires_connection'] ) {
1690
					continue;
1691
				}
1692
			}
1693
1694
			if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1695
				continue;
1696
			}
1697
1698
			require Jetpack::get_module_path( $module );
1699
			/**
1700
			 * Fires when a specific module is loaded.
1701
			 * The dynamic part of the hook, $module, is the module slug.
1702
			 *
1703
			 * @since 1.1.0
1704
			 */
1705
			do_action( 'jetpack_module_loaded_' . $module );
1706
		}
1707
1708
		/**
1709
		 * Fires when all the modules are loaded.
1710
		 *
1711
		 * @since 1.1.0
1712
		 */
1713
		do_action( 'jetpack_modules_loaded' );
1714
1715
		// 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.
1716
		if ( Jetpack::is_active() || Jetpack::is_development_mode() )
1717
			require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' );
1718
	}
1719
1720
	/**
1721
	 * Check if Jetpack's REST API compat file should be included
1722
	 * @action plugins_loaded
1723
	 * @return null
1724
	 */
1725
	public function check_rest_api_compat() {
1726
		/**
1727
		 * Filters the list of REST API compat files to be included.
1728
		 *
1729
		 * @since 2.2.5
1730
		 *
1731
		 * @param array $args Array of REST API compat files to include.
1732
		 */
1733
		$_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1734
1735
		if ( function_exists( 'bbpress' ) )
1736
			$_jetpack_rest_api_compat_includes[] = JETPACK__PLUGIN_DIR . 'class.jetpack-bbpress-json-api-compat.php';
1737
1738
		foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include )
1739
			require_once $_jetpack_rest_api_compat_include;
1740
	}
1741
1742
	/**
1743
	 * Gets all plugins currently active in values, regardless of whether they're
1744
	 * traditionally activated or network activated.
1745
	 *
1746
	 * @todo Store the result in core's object cache maybe?
1747
	 */
1748
	public static function get_active_plugins() {
1749
		$active_plugins = (array) get_option( 'active_plugins', array() );
1750
1751
		if ( is_multisite() ) {
1752
			// Due to legacy code, active_sitewide_plugins stores them in the keys,
1753
			// whereas active_plugins stores them in the values.
1754
			$network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1755
			if ( $network_plugins ) {
1756
				$active_plugins = array_merge( $active_plugins, $network_plugins );
1757
			}
1758
		}
1759
1760
		sort( $active_plugins );
1761
1762
		return array_unique( $active_plugins );
1763
	}
1764
1765
	/**
1766
	 * Gets and parses additional plugin data to send with the heartbeat data
1767
	 *
1768
	 * @since 3.8.1
1769
	 *
1770
	 * @return array Array of plugin data
1771
	 */
1772
	public static function get_parsed_plugin_data() {
1773
		if ( ! function_exists( 'get_plugins' ) ) {
1774
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
1775
		}
1776
		$all_plugins    = get_plugins();
1777
		$active_plugins = Jetpack::get_active_plugins();
1778
1779
		$plugins = array();
1780
		foreach ( $all_plugins as $path => $plugin_data ) {
1781
			$plugins[ $path ] = array(
1782
					'is_active' => in_array( $path, $active_plugins ),
1783
					'file'      => $path,
1784
					'name'      => $plugin_data['Name'],
1785
					'version'   => $plugin_data['Version'],
1786
					'author'    => $plugin_data['Author'],
1787
			);
1788
		}
1789
1790
		return $plugins;
1791
	}
1792
1793
	/**
1794
	 * Gets and parses theme data to send with the heartbeat data
1795
	 *
1796
	 * @since 3.8.1
1797
	 *
1798
	 * @return array Array of theme data
1799
	 */
1800
	public static function get_parsed_theme_data() {
1801
		$all_themes = wp_get_themes( array( 'allowed' => true ) );
1802
		$header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
1803
1804
		$themes = array();
1805
		foreach ( $all_themes as $slug => $theme_data ) {
1806
			$theme_headers = array();
1807
			foreach ( $header_keys as $header_key ) {
1808
				$theme_headers[ $header_key ] = $theme_data->get( $header_key );
1809
			}
1810
1811
			$themes[ $slug ] = array(
1812
					'is_active_theme' => $slug == wp_get_theme()->get_template(),
1813
					'slug' => $slug,
1814
					'theme_root' => $theme_data->get_theme_root_uri(),
1815
					'parent' => $theme_data->parent(),
1816
					'headers' => $theme_headers
1817
			);
1818
		}
1819
1820
		return $themes;
1821
	}
1822
1823
	/**
1824
	 * Checks whether a specific plugin is active.
1825
	 *
1826
	 * We don't want to store these in a static variable, in case
1827
	 * there are switch_to_blog() calls involved.
1828
	 */
1829
	public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
1830
		return in_array( $plugin, self::get_active_plugins() );
1831
	}
1832
1833
	/**
1834
	 * Check if Jetpack's Open Graph tags should be used.
1835
	 * If certain plugins are active, Jetpack's og tags are suppressed.
1836
	 *
1837
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1838
	 * @action plugins_loaded
1839
	 * @return null
1840
	 */
1841
	public function check_open_graph() {
1842
		if ( in_array( 'publicize', Jetpack::get_active_modules() ) || in_array( 'sharedaddy', Jetpack::get_active_modules() ) ) {
1843
			add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
1844
		}
1845
1846
		$active_plugins = self::get_active_plugins();
1847
1848
		if ( ! empty( $active_plugins ) ) {
1849
			foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
1850
				if ( in_array( $plugin, $active_plugins ) ) {
1851
					add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
1852
					break;
1853
				}
1854
			}
1855
		}
1856
1857
		/**
1858
		 * Allow the addition of Open Graph Meta Tags to all pages.
1859
		 *
1860
		 * @since 2.0.3
1861
		 *
1862
		 * @param bool false Should Open Graph Meta tags be added. Default to false.
1863
		 */
1864
		if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
1865
			require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
1866
		}
1867
	}
1868
1869
	/**
1870
	 * Check if Jetpack's Twitter tags should be used.
1871
	 * If certain plugins are active, Jetpack's twitter tags are suppressed.
1872
	 *
1873
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1874
	 * @action plugins_loaded
1875
	 * @return null
1876
	 */
1877
	public function check_twitter_tags() {
1878
1879
		$active_plugins = self::get_active_plugins();
1880
1881
		if ( ! empty( $active_plugins ) ) {
1882
			foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
1883
				if ( in_array( $plugin, $active_plugins ) ) {
1884
					add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
1885
					break;
1886
				}
1887
			}
1888
		}
1889
1890
		/**
1891
		 * Allow Twitter Card Meta tags to be disabled.
1892
		 *
1893
		 * @since 2.6.0
1894
		 *
1895
		 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
1896
		 */
1897
		if ( apply_filters( 'jetpack_disable_twitter_cards', true ) ) {
1898
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
1899
		}
1900
	}
1901
1902
1903
1904
1905
	/*
1906
	 *
1907
	 * Jetpack Security Reports
1908
	 *
1909
	 * Allowed types: login_form, backup, file_scanning, spam
1910
	 *
1911
	 * Args for login_form and spam: 'blocked'=>(int)(optional), 'status'=>(string)(ok, warning, error), 'message'=>(optional, disregarded if status is ok, allowed tags: a, em, strong)
1912
	 *
1913
	 * Args for backup and file_scanning: 'last'=>(timestamp)(optional), 'next'=>(timestamp)(optional), 'status'=>(string)(ok, warning, error), 'message'=>(optional, disregarded if status is ok, allowed tags: a, em, strong)
1914
	 *
1915
	 *
1916
	 * Example code to submit a security report:
1917
	 *
1918
	 *  function akismet_submit_jetpack_security_report() {
1919
	 *  	Jetpack::submit_security_report( 'spam', __FILE__, $args = array( 'blocked' => 138284, status => 'ok' ) );
1920
	 *  }
1921
	 *  add_action( 'jetpack_security_report', 'akismet_submit_jetpack_security_report' );
1922
	 *
1923
	 */
1924
1925
1926
	/**
1927
	 * Calls for security report submissions.
1928
	 *
1929
	 * @return null
1930
	 */
1931
	public static function perform_security_reporting() {
1932
		$no_check_needed = get_site_transient( 'security_report_performed_recently' );
1933
1934
		if ( $no_check_needed ) {
1935
			return;
1936
		}
1937
1938
		/**
1939
		 * Fires before a security report is created.
1940
		 *
1941
		 * @since 3.4.0
1942
		 */
1943
		do_action( 'jetpack_security_report' );
1944
1945
		Jetpack_Options::update_option( 'security_report', self::$security_report );
1946
		set_site_transient( 'security_report_performed_recently', 1, 15 * MINUTE_IN_SECONDS );
1947
	}
1948
1949
	/**
1950
	 * Allows plugins to submit security reports.
1951
 	 *
1952
	 * @param string  $type         Report type (login_form, backup, file_scanning, spam)
1953
	 * @param string  $plugin_file  Plugin __FILE__, so that we can pull plugin data
1954
	 * @param array   $args         See definitions above
1955
	 */
1956
	public static function submit_security_report( $type = '', $plugin_file = '', $args = array() ) {
1957
1958
		if( !doing_action( 'jetpack_security_report' ) ) {
1959
			return new WP_Error( 'not_collecting_report', 'Not currently collecting security reports.  Please use the jetpack_security_report hook.' );
1960
		}
1961
1962
		if( !is_string( $type ) || !is_string( $plugin_file ) ) {
1963
			return new WP_Error( 'invalid_security_report', 'Invalid Security Report' );
1964
		}
1965
1966
		if( !function_exists( 'get_plugin_data' ) ) {
1967
			include( ABSPATH . 'wp-admin/includes/plugin.php' );
1968
		}
1969
1970
		//Get rid of any non-allowed args
1971
		$args = array_intersect_key( $args, array_flip( array( 'blocked', 'last', 'next', 'status', 'message' ) ) );
1972
1973
		$plugin = get_plugin_data( $plugin_file );
1974
1975
		if ( !$plugin['Name'] ) {
1976
			return new WP_Error( 'security_report_missing_plugin_name', 'Invalid Plugin File Provided' );
1977
		}
1978
1979
		// Sanitize everything to make sure we're not syncing something wonky
1980
		$type = sanitize_key( $type );
1981
1982
		$args['plugin'] = $plugin;
1983
1984
		// Cast blocked, last and next as integers.
1985
		// Last and next should be in unix timestamp format
1986
		if ( isset( $args['blocked'] ) ) {
1987
			$args['blocked'] = (int) $args['blocked'];
1988
		}
1989
		if ( isset( $args['last'] ) ) {
1990
			$args['last'] = (int) $args['last'];
1991
		}
1992
		if ( isset( $args['next'] ) ) {
1993
			$args['next'] = (int) $args['next'];
1994
		}
1995
		if ( !in_array( $args['status'], array( 'ok', 'warning', 'error' ) ) ) {
1996
			$args['status'] = 'ok';
1997
		}
1998
		if ( isset( $args['message'] ) ) {
1999
2000
			if( $args['status'] == 'ok' ) {
2001
				unset( $args['message'] );
2002
			}
2003
2004
			$allowed_html = array(
2005
			    'a' => array(
2006
			        'href' => array(),
2007
			        'title' => array()
2008
			    ),
2009
			    'em' => array(),
2010
			    'strong' => array(),
2011
			);
2012
2013
			$args['message'] = wp_kses( $args['message'], $allowed_html );
2014
		}
2015
2016
		$plugin_name = $plugin[ 'Name' ];
2017
2018
		self::$security_report[ $type ][ $plugin_name ] = $args;
2019
	}
2020
2021
	/**
2022
	 * Collects a new report if needed, then returns it.
2023
	 */
2024
	public function get_security_report() {
2025
		self::perform_security_reporting();
2026
		return Jetpack_Options::get_option( 'security_report' );
2027
	}
2028
2029
2030
/* Jetpack Options API */
2031
2032
	public static function get_option_names( $type = 'compact' ) {
2033
		return Jetpack_Options::get_option_names( $type );
2034
	}
2035
2036
	/**
2037
	 * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
2038
 	 *
2039
	 * @param string $name    Option name
2040
	 * @param mixed  $default (optional)
2041
	 */
2042
	public static function get_option( $name, $default = false ) {
2043
		return Jetpack_Options::get_option( $name, $default );
2044
	}
2045
2046
	/**
2047
	* Stores two secrets and a timestamp so WordPress.com can make a request back and verify an action
2048
	* Does some extra verification so urls (such as those to public-api, register, etc) can't just be crafted
2049
	* $name must be a registered option name.
2050
	*/
2051
	public static function create_nonce( $name ) {
2052
		$secret = wp_generate_password( 32, false ) . ':' . wp_generate_password( 32, false ) . ':' . ( time() + 600 );
2053
2054
		Jetpack_Options::update_option( $name, $secret );
2055
		@list( $secret_1, $secret_2, $eol ) = explode( ':', Jetpack_Options::get_option( $name ) );
2056
		if ( empty( $secret_1 ) || empty( $secret_2 ) || $eol < time() )
2057
			return new Jetpack_Error( 'missing_secrets' );
2058
2059
		return array(
2060
			'secret_1' => $secret_1,
2061
			'secret_2' => $secret_2,
2062
			'eol'      => $eol,
2063
		);
2064
	}
2065
2066
	/**
2067
	 * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
2068
 	 *
2069
	 * @deprecated 3.4 use Jetpack_Options::update_option() instead.
2070
	 * @param string $name  Option name
2071
	 * @param mixed  $value Option value
2072
	 */
2073
	public static function update_option( $name, $value ) {
2074
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_option()' );
2075
		return Jetpack_Options::update_option( $name, $value );
2076
	}
2077
2078
	/**
2079
	 * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
2080
 	 *
2081
	 * @deprecated 3.4 use Jetpack_Options::update_options() instead.
2082
	 * @param array $array array( option name => option value, ... )
2083
	 */
2084
	public static function update_options( $array ) {
2085
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_options()' );
2086
		return Jetpack_Options::update_options( $array );
2087
	}
2088
2089
	/**
2090
	 * Deletes the given option.  May be passed multiple option names as an array.
2091
	 * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
2092
	 *
2093
	 * @deprecated 3.4 use Jetpack_Options::delete_option() instead.
2094
	 * @param string|array $names
2095
	 */
2096
	public static function delete_option( $names ) {
2097
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::delete_option()' );
2098
		return Jetpack_Options::delete_option( $names );
2099
	}
2100
2101
	/**
2102
	 * Enters a user token into the user_tokens option
2103
	 *
2104
	 * @param int $user_id
2105
	 * @param string $token
2106
	 * return bool
2107
	 */
2108
	public static function update_user_token( $user_id, $token, $is_master_user ) {
2109
		// not designed for concurrent updates
2110
		$user_tokens = Jetpack_Options::get_option( 'user_tokens' );
2111
		if ( ! is_array( $user_tokens ) )
2112
			$user_tokens = array();
2113
		$user_tokens[$user_id] = $token;
2114
		if ( $is_master_user ) {
2115
			$master_user = $user_id;
2116
			$options     = compact( 'user_tokens', 'master_user' );
2117
		} else {
2118
			$options = compact( 'user_tokens' );
2119
		}
2120
		return Jetpack_Options::update_options( $options );
2121
	}
2122
2123
	/**
2124
	 * Returns an array of all PHP files in the specified absolute path.
2125
	 * Equivalent to glob( "$absolute_path/*.php" ).
2126
	 *
2127
	 * @param string $absolute_path The absolute path of the directory to search.
2128
	 * @return array Array of absolute paths to the PHP files.
2129
	 */
2130
	public static function glob_php( $absolute_path ) {
2131
		if ( function_exists( 'glob' ) ) {
2132
			return glob( "$absolute_path/*.php" );
2133
		}
2134
2135
		$absolute_path = untrailingslashit( $absolute_path );
2136
		$files = array();
2137
		if ( ! $dir = @opendir( $absolute_path ) ) {
2138
			return $files;
2139
		}
2140
2141
		while ( false !== $file = readdir( $dir ) ) {
2142
			if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
2143
				continue;
2144
			}
2145
2146
			$file = "$absolute_path/$file";
2147
2148
			if ( ! is_file( $file ) ) {
2149
				continue;
2150
			}
2151
2152
			$files[] = $file;
2153
		}
2154
2155
		closedir( $dir );
2156
2157
		return $files;
2158
	}
2159
2160
	public static function activate_new_modules( $redirect = false ) {
2161
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
2162
			return;
2163
		}
2164
2165
		$jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
2166 View Code Duplication
		if ( ! $jetpack_old_version ) {
2167
			$jetpack_old_version = $version = $old_version = '1.1:' . time();
2168
			/** This action is documented in class.jetpack.php */
2169
			do_action( 'updating_jetpack_version', $version, false );
2170
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2171
		}
2172
2173
		list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
2174
2175
		if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
2176
			return;
2177
		}
2178
2179
		$active_modules     = Jetpack::get_active_modules();
2180
		$reactivate_modules = array();
2181
		foreach ( $active_modules as $active_module ) {
2182
			$module = Jetpack::get_module( $active_module );
2183
			if ( ! isset( $module['changed'] ) ) {
2184
				continue;
2185
			}
2186
2187
			if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
2188
				continue;
2189
			}
2190
2191
			$reactivate_modules[] = $active_module;
2192
			Jetpack::deactivate_module( $active_module );
2193
		}
2194
2195
		$new_version = JETPACK__VERSION . ':' . time();
2196
		/** This action is documented in class.jetpack.php */
2197
		do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
2198
		Jetpack_Options::update_options(
2199
			array(
2200
				'version'     => $new_version,
2201
				'old_version' => $jetpack_old_version,
2202
			)
2203
		);
2204
2205
		Jetpack::state( 'message', 'modules_activated' );
2206
		Jetpack::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules );
2207
2208
		if ( $redirect ) {
2209
			$page = 'jetpack'; // make sure we redirect to either settings or the jetpack page
2210
			if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ) ) ) {
2211
				$page = $_GET['page'];
2212
			}
2213
2214
			wp_safe_redirect( Jetpack::admin_url( 'page=' . $page ) );
2215
			exit;
2216
		}
2217
	}
2218
2219
	/**
2220
	 * List available Jetpack modules. Simply lists .php files in /modules/.
2221
	 * Make sure to tuck away module "library" files in a sub-directory.
2222
	 */
2223
	public static function get_available_modules( $min_version = false, $max_version = false ) {
2224
		static $modules = null;
2225
2226
		if ( ! isset( $modules ) ) {
2227
			$available_modules_option = Jetpack_Options::get_option( 'available_modules', array() );
2228
			// Use the cache if we're on the front-end and it's available...
2229
			if ( ! is_admin() && ! empty( $available_modules_option[ JETPACK__VERSION ] ) ) {
2230
				$modules = $available_modules_option[ JETPACK__VERSION ];
2231
			} else {
2232
				$files = Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
2233
2234
				$modules = array();
2235
2236
				foreach ( $files as $file ) {
2237
					if ( ! $headers = Jetpack::get_module( $file ) ) {
2238
						continue;
2239
					}
2240
2241
					$modules[ Jetpack::get_module_slug( $file ) ] = $headers['introduced'];
2242
				}
2243
2244
				Jetpack_Options::update_option( 'available_modules', array(
2245
					JETPACK__VERSION => $modules,
2246
				) );
2247
			}
2248
		}
2249
2250
		/**
2251
		 * Filters the array of modules available to be activated.
2252
		 *
2253
		 * @since 2.4.0
2254
		 *
2255
		 * @param array $modules Array of available modules.
2256
		 * @param string $min_version Minimum version number required to use modules.
2257
		 * @param string $max_version Maximum version number required to use modules.
2258
		 */
2259
		$mods = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version );
2260
2261
		if ( ! $min_version && ! $max_version ) {
2262
			return array_keys( $mods );
2263
		}
2264
2265
		$r = array();
2266
		foreach ( $mods as $slug => $introduced ) {
2267
			if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
2268
				continue;
2269
			}
2270
2271
			if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
2272
				continue;
2273
			}
2274
2275
			$r[] = $slug;
2276
		}
2277
2278
		return $r;
2279
	}
2280
2281
	/**
2282
	 * Default modules loaded on activation.
2283
	 */
2284
	public static function get_default_modules( $min_version = false, $max_version = false ) {
2285
		$return = array();
2286
2287
		foreach ( Jetpack::get_available_modules( $min_version, $max_version ) as $module ) {
2288
			$module_data = Jetpack::get_module( $module );
2289
2290
			switch ( strtolower( $module_data['auto_activate'] ) ) {
2291
				case 'yes' :
2292
					$return[] = $module;
2293
					break;
2294
				case 'public' :
2295
					if ( Jetpack_Options::get_option( 'public' ) ) {
2296
						$return[] = $module;
2297
					}
2298
					break;
2299
				case 'no' :
2300
				default :
2301
					break;
2302
			}
2303
		}
2304
		/**
2305
		 * Filters the array of default modules.
2306
		 *
2307
		 * @since 2.5.0
2308
		 *
2309
		 * @param array $return Array of default modules.
2310
		 * @param string $min_version Minimum version number required to use modules.
2311
		 * @param string $max_version Maximum version number required to use modules.
2312
		 */
2313
		return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version );
2314
	}
2315
2316
	/**
2317
	 * Checks activated modules during auto-activation to determine
2318
	 * if any of those modules are being deprecated.  If so, close
2319
	 * them out, and add any replacement modules.
2320
	 *
2321
	 * Runs at priority 99 by default.
2322
	 *
2323
	 * This is run late, so that it can still activate a module if
2324
	 * the new module is a replacement for another that the user
2325
	 * currently has active, even if something at the normal priority
2326
	 * would kibosh everything.
2327
	 *
2328
	 * @since 2.6
2329
	 * @uses jetpack_get_default_modules filter
2330
	 * @param array $modules
2331
	 * @return array
2332
	 */
2333
	function handle_deprecated_modules( $modules ) {
2334
		$deprecated_modules = array(
2335
			'debug'            => null,  // Closed out and moved to ./class.jetpack-debugger.php
2336
			'wpcc'             => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2337
			'gplus-authorship' => null,  // Closed out in 3.2 -- Google dropped support.
2338
		);
2339
2340
		// Don't activate SSO if they never completed activating WPCC.
2341
		if ( Jetpack::is_module_active( 'wpcc' ) ) {
2342
			$wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2343
			if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2344
				$deprecated_modules['wpcc'] = null;
2345
			}
2346
		}
2347
2348
		foreach ( $deprecated_modules as $module => $replacement ) {
2349
			if ( Jetpack::is_module_active( $module ) ) {
2350
				self::deactivate_module( $module );
2351
				if ( $replacement ) {
2352
					$modules[] = $replacement;
2353
				}
2354
			}
2355
		}
2356
2357
		return array_unique( $modules );
2358
	}
2359
2360
	/**
2361
	 * Checks activated plugins during auto-activation to determine
2362
	 * if any of those plugins are in the list with a corresponding module
2363
	 * that is not compatible with the plugin. The module will not be allowed
2364
	 * to auto-activate.
2365
	 *
2366
	 * @since 2.6
2367
	 * @uses jetpack_get_default_modules filter
2368
	 * @param array $modules
2369
	 * @return array
2370
	 */
2371
	function filter_default_modules( $modules ) {
2372
2373
		$active_plugins = self::get_active_plugins();
2374
2375
		if ( ! empty( $active_plugins ) ) {
2376
2377
			// For each module we'd like to auto-activate...
2378
			foreach ( $modules as $key => $module ) {
2379
				// If there are potential conflicts for it...
2380
				if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2381
					// For each potential conflict...
2382
					foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
2383
						// If that conflicting plugin is active...
2384
						if ( in_array( $plugin, $active_plugins ) ) {
2385
							// Remove that item from being auto-activated.
2386
							unset( $modules[ $key ] );
2387
						}
2388
					}
2389
				}
2390
			}
2391
		}
2392
2393
		return $modules;
2394
	}
2395
2396
	/**
2397
	 * Extract a module's slug from its full path.
2398
	 */
2399
	public static function get_module_slug( $file ) {
2400
		return str_replace( '.php', '', basename( $file ) );
2401
	}
2402
2403
	/**
2404
	 * Generate a module's path from its slug.
2405
	 */
2406
	public static function get_module_path( $slug ) {
2407
		return JETPACK__PLUGIN_DIR . "modules/$slug.php";
2408
	}
2409
2410
	/**
2411
	 * Load module data from module file. Headers differ from WordPress
2412
	 * plugin headers to avoid them being identified as standalone
2413
	 * plugins on the WordPress plugins page.
2414
	 */
2415
	public static function get_module( $module ) {
2416
		$headers = array(
2417
			'name'                      => 'Module Name',
2418
			'description'               => 'Module Description',
2419
			'jumpstart_desc'            => 'Jumpstart Description',
2420
			'sort'                      => 'Sort Order',
2421
			'recommendation_order'      => 'Recommendation Order',
2422
			'introduced'                => 'First Introduced',
2423
			'changed'                   => 'Major Changes In',
2424
			'deactivate'                => 'Deactivate',
2425
			'free'                      => 'Free',
2426
			'requires_connection'       => 'Requires Connection',
2427
			'auto_activate'             => 'Auto Activate',
2428
			'module_tags'               => 'Module Tags',
2429
			'feature'                   => 'Feature',
2430
			'additional_search_queries' => 'Additional Search Queries',
2431
		);
2432
2433
		$file = Jetpack::get_module_path( Jetpack::get_module_slug( $module ) );
2434
2435
		$mod = Jetpack::get_file_data( $file, $headers );
2436
		if ( empty( $mod['name'] ) ) {
2437
			return false;
2438
		}
2439
2440
		$mod['sort']                    = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
2441
		$mod['recommendation_order']    = empty( $mod['recommendation_order'] ) ? 20 : (int) $mod['recommendation_order'];
2442
		$mod['deactivate']              = empty( $mod['deactivate'] );
2443
		$mod['free']                    = empty( $mod['free'] );
2444
		$mod['requires_connection']     = ( ! empty( $mod['requires_connection'] ) && 'No' == $mod['requires_connection'] ) ? false : true;
2445
2446
		if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ) ) ) {
2447
			$mod['auto_activate'] = 'No';
2448
		} else {
2449
			$mod['auto_activate'] = (string) $mod['auto_activate'];
2450
		}
2451
2452
		if ( $mod['module_tags'] ) {
2453
			$mod['module_tags'] = explode( ',', $mod['module_tags'] );
2454
			$mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
2455
			$mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
2456
		} else {
2457
			$mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
2458
		}
2459
2460
		if ( $mod['feature'] ) {
2461
			$mod['feature'] = explode( ',', $mod['feature'] );
2462
			$mod['feature'] = array_map( 'trim', $mod['feature'] );
2463
		} else {
2464
			$mod['feature'] = array( self::translate_module_tag( 'Other' ) );
2465
		}
2466
2467
		/**
2468
		 * Filters the feature array on a module.
2469
		 *
2470
		 * This filter allows you to control where each module is filtered: Recommended,
2471
		 * Jumpstart, and the default "Other" listing.
2472
		 *
2473
		 * @since 3.5.0
2474
		 *
2475
		 * @param array   $mod['feature'] The areas to feature this module:
2476
		 *     'Jumpstart' adds to the "Jumpstart" option to activate many modules at once.
2477
		 *     'Recommended' shows on the main Jetpack admin screen.
2478
		 *     'Other' should be the default if no other value is in the array.
2479
		 * @param string  $module The slug of the module, e.g. sharedaddy.
2480
		 * @param array   $mod All the currently assembled module data.
2481
		 */
2482
		$mod['feature'] = apply_filters( 'jetpack_module_feature', $mod['feature'], $module, $mod );
2483
2484
		/**
2485
		 * Filter the returned data about a module.
2486
		 *
2487
		 * This filter allows overriding any info about Jetpack modules. It is dangerous,
2488
		 * so please be careful.
2489
		 *
2490
		 * @since 3.6.0
2491
		 *
2492
		 * @param array   $mod    The details of the requested module.
2493
		 * @param string  $module The slug of the module, e.g. sharedaddy
2494
		 * @param string  $file   The path to the module source file.
2495
		 */
2496
		return apply_filters( 'jetpack_get_module', $mod, $module, $file );
2497
	}
2498
2499
	/**
2500
	 * Like core's get_file_data implementation, but caches the result.
2501
	 */
2502
	public static function get_file_data( $file, $headers ) {
2503
		//Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated
2504
		$file_name = basename( $file );
2505
		$file_data_option = Jetpack_Options::get_option( 'file_data', array() );
2506
		$key              = md5( $file_name . serialize( $headers ) );
2507
		$refresh_cache    = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 );
2508
2509
		// If we don't need to refresh the cache, and already have the value, short-circuit!
2510
		if ( ! $refresh_cache && isset( $file_data_option[ JETPACK__VERSION ][ $key ] ) ) {
2511
			return $file_data_option[ JETPACK__VERSION ][ $key ];
2512
		}
2513
2514
		$data = get_file_data( $file, $headers );
2515
2516
		// Strip out any old Jetpack versions that are cluttering the option.
2517
		$file_data_option = array_intersect_key( (array) $file_data_option, array( JETPACK__VERSION => null ) );
2518
		$file_data_option[ JETPACK__VERSION ][ $key ] = $data;
2519
		Jetpack_Options::update_option( 'file_data', $file_data_option );
2520
2521
		return $data;
2522
	}
2523
2524
	public static function translate_module_tag( $untranslated_tag ) {
2525
		// Tags are aggregated by tools/build-module-headings-translations.php
2526
		// and output in modules/module-headings.php
2527
		return _x( $untranslated_tag, 'Module Tag', 'jetpack' );
2528
	}
2529
2530
	/**
2531
	 * Get a list of activated modules as an array of module slugs.
2532
	 */
2533
	public static function get_active_modules() {
2534
		$active = Jetpack_Options::get_option( 'active_modules' );
2535
		if ( ! is_array( $active ) )
2536
			$active = array();
2537
		if ( is_admin() && ( class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ) ) ) {
2538
			$active[] = 'vaultpress';
2539
		} else {
2540
			$active = array_diff( $active, array( 'vaultpress' ) );
2541
		}
2542
2543
		//If protect is active on the main site of a multisite, it should be active on all sites.
2544
		if ( ! in_array( 'protect', $active ) && is_multisite() && get_site_option( 'jetpack_protect_active' ) ) {
2545
			$active[] = 'protect';
2546
		}
2547
2548
		return array_unique( $active );
2549
	}
2550
2551
	/**
2552
	 * Check whether or not a Jetpack module is active.
2553
	 *
2554
	 * @param string $module The slug of a Jetpack module.
2555
	 * @return bool
2556
	 *
2557
	 * @static
2558
	 */
2559
	public static function is_module_active( $module ) {
2560
		return in_array( $module, self::get_active_modules() );
2561
	}
2562
2563
	public static function is_module( $module ) {
2564
		return ! empty( $module ) && ! validate_file( $module, Jetpack::get_available_modules() );
2565
	}
2566
2567
	/**
2568
	 * Catches PHP errors.  Must be used in conjunction with output buffering.
2569
	 *
2570
	 * @param bool $catch True to start catching, False to stop.
2571
	 *
2572
	 * @static
2573
	 */
2574
	public static function catch_errors( $catch ) {
2575
		static $display_errors, $error_reporting;
2576
2577
		if ( $catch ) {
2578
			$display_errors  = @ini_set( 'display_errors', 1 );
2579
			$error_reporting = @error_reporting( E_ALL );
2580
			add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2581
		} else {
2582
			@ini_set( 'display_errors', $display_errors );
2583
			@error_reporting( $error_reporting );
2584
			remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2585
		}
2586
	}
2587
2588
	/**
2589
	 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2590
	 */
2591
	public static function catch_errors_on_shutdown() {
2592
		Jetpack::state( 'php_errors', ob_get_clean() );
2593
	}
2594
2595
	public static function activate_default_modules( $min_version = false, $max_version = false, $other_modules = array() ) {
2596
		$jetpack = Jetpack::init();
2597
2598
		$modules = Jetpack::get_default_modules( $min_version, $max_version );
2599
		$modules = array_merge( $other_modules, $modules );
2600
2601
		// Look for standalone plugins and disable if active.
2602
2603
		$to_deactivate = array();
2604
		foreach ( $modules as $module ) {
2605
			if ( isset( $jetpack->plugins_to_deactivate[$module] ) ) {
2606
				$to_deactivate[$module] = $jetpack->plugins_to_deactivate[$module];
2607
			}
2608
		}
2609
2610
		$deactivated = array();
2611
		foreach ( $to_deactivate as $module => $deactivate_me ) {
2612
			list( $probable_file, $probable_title ) = $deactivate_me;
2613
			if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2614
				$deactivated[] = $module;
2615
			}
2616
		}
2617
2618
		if ( $deactivated ) {
2619
			Jetpack::state( 'deactivated_plugins', join( ',', $deactivated ) );
2620
2621
			$url = add_query_arg(
2622
				array(
2623
					'action'   => 'activate_default_modules',
2624
					'_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2625
				),
2626
				add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), Jetpack::admin_url( 'page=jetpack' ) )
2627
			);
2628
			wp_safe_redirect( $url );
2629
			exit;
2630
		}
2631
2632
		/**
2633
		 * Fires before default modules are activated.
2634
		 *
2635
		 * @since 1.9.0
2636
		 *
2637
		 * @param string $min_version Minimum version number required to use modules.
2638
		 * @param string $max_version Maximum version number required to use modules.
2639
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2640
		 */
2641
		do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
2642
2643
		// Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
2644
		Jetpack::restate();
2645
		Jetpack::catch_errors( true );
2646
2647
		$active = Jetpack::get_active_modules();
2648
2649
		foreach ( $modules as $module ) {
2650
			if ( did_action( "jetpack_module_loaded_$module" ) ) {
2651
				$active[] = $module;
2652
				Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2653
				continue;
2654
			}
2655
2656
			if ( in_array( $module, $active ) ) {
2657
				$module_info = Jetpack::get_module( $module );
2658
				if ( ! $module_info['deactivate'] ) {
2659
					$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2660 View Code Duplication
					if ( $active_state = Jetpack::state( $state ) ) {
2661
						$active_state = explode( ',', $active_state );
2662
					} else {
2663
						$active_state = array();
2664
					}
2665
					$active_state[] = $module;
2666
					Jetpack::state( $state, implode( ',', $active_state ) );
2667
				}
2668
				continue;
2669
			}
2670
2671
			$file = Jetpack::get_module_path( $module );
2672
			if ( ! file_exists( $file ) ) {
2673
				continue;
2674
			}
2675
2676
			// we'll override this later if the plugin can be included without fatal error
2677
			wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2678
			Jetpack::state( 'error', 'module_activation_failed' );
2679
			Jetpack::state( 'module', $module );
2680
			ob_start();
2681
			require $file;
2682
			/**
2683
			 * Fires when a specific module is activated.
2684
			 *
2685
			 * @since 1.9.0
2686
			 *
2687
			 * @param string $module Module slug.
2688
			 */
2689
			do_action( 'jetpack_activate_module', $module );
2690
			$active[] = $module;
2691
			$state    = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2692 View Code Duplication
			if ( $active_state = Jetpack::state( $state ) ) {
2693
				$active_state = explode( ',', $active_state );
2694
			} else {
2695
				$active_state = array();
2696
			}
2697
			$active_state[] = $module;
2698
			Jetpack::state( $state, implode( ',', $active_state ) );
2699
			Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2700
			ob_end_clean();
2701
		}
2702
		Jetpack::state( 'error', false );
2703
		Jetpack::state( 'module', false );
2704
		Jetpack::catch_errors( false );
2705
		/**
2706
		 * Fires when default modules are activated.
2707
		 *
2708
		 * @since 1.9.0
2709
		 *
2710
		 * @param string $min_version Minimum version number required to use modules.
2711
		 * @param string $max_version Maximum version number required to use modules.
2712
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2713
		 */
2714
		do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
2715
	}
2716
2717
	public static function activate_module( $module, $exit = true, $redirect = true ) {
2718
		/**
2719
		 * Fires before a module is activated.
2720
		 *
2721
		 * @since 2.6.0
2722
		 *
2723
		 * @param string $module Module slug.
2724
		 * @param bool $exit Should we exit after the module has been activated. Default to true.
2725
		 * @param bool $redirect Should the user be redirected after module activation? Default to true.
2726
		 */
2727
		do_action( 'jetpack_pre_activate_module', $module, $exit, $redirect );
2728
2729
		$jetpack = Jetpack::init();
2730
2731
		if ( ! strlen( $module ) )
2732
			return false;
2733
2734
		if ( ! Jetpack::is_module( $module ) )
2735
			return false;
2736
2737
		// If it's already active, then don't do it again
2738
		$active = Jetpack::get_active_modules();
2739
		foreach ( $active as $act ) {
2740
			if ( $act == $module )
2741
				return true;
2742
		}
2743
2744
		$module_data = Jetpack::get_module( $module );
2745
2746
		if ( ! Jetpack::is_active() ) {
2747
			if ( !Jetpack::is_development_mode() )
2748
				return false;
2749
2750
			// If we're not connected but in development mode, make sure the module doesn't require a connection
2751
			if ( Jetpack::is_development_mode() && $module_data['requires_connection'] )
2752
				return false;
2753
		}
2754
2755
		// Check and see if the old plugin is active
2756
		if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2757
			// Deactivate the old plugin
2758
			if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[ $module ][0], $jetpack->plugins_to_deactivate[ $module ][1] ) ) {
2759
				// If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
2760
				// We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
2761
				Jetpack::state( 'deactivated_plugins', $module );
2762
				wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
2763
				exit;
2764
			}
2765
		}
2766
2767
		// Check the file for fatal errors, a la wp-admin/plugins.php::activate
2768
		Jetpack::state( 'module', $module );
2769
		Jetpack::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
2770
2771
		Jetpack::catch_errors( true );
2772
		ob_start();
2773
		require Jetpack::get_module_path( $module );
2774
		/** This action is documented in class.jetpack.php */
2775
		do_action( 'jetpack_activate_module', $module );
2776
		$active[] = $module;
2777
		Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2778
		Jetpack::state( 'error', false ); // the override
2779
		Jetpack::state( 'message', 'module_activated' );
2780
		Jetpack::state( 'module', $module );
2781
		ob_end_clean();
2782
		Jetpack::catch_errors( false );
2783
2784
		// A flag for Jump Start so it's not shown again. Only set if it hasn't been yet.
2785 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2786
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2787
2788
			//Jump start is being dismissed send data to MC Stats
2789
			$jetpack->stat( 'jumpstart', 'manual,'.$module );
2790
2791
			$jetpack->do_stats( 'server_side' );
2792
		}
2793
2794
		if ( $redirect ) {
2795
			wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2796
		}
2797
		if ( $exit ) {
2798
			exit;
2799
		}
2800
	}
2801
2802
	function activate_module_actions( $module ) {
2803
		/**
2804
		 * Fires when a module is activated.
2805
		 * The dynamic part of the filter, $module, is the module slug.
2806
		 *
2807
		 * @since 1.9.0
2808
		 *
2809
		 * @param string $module Module slug.
2810
		 */
2811
		do_action( "jetpack_activate_module_$module", $module );
2812
2813
		$this->sync->sync_all_module_options( $module );
2814
	}
2815
2816
	public static function deactivate_module( $module ) {
2817
		/**
2818
		 * Fires when a module is deactivated.
2819
		 *
2820
		 * @since 1.9.0
2821
		 *
2822
		 * @param string $module Module slug.
2823
		 */
2824
		do_action( 'jetpack_pre_deactivate_module', $module );
2825
2826
		$jetpack = Jetpack::init();
2827
2828
		$active = Jetpack::get_active_modules();
2829
		$new    = array_filter( array_diff( $active, (array) $module ) );
2830
2831
		/**
2832
		 * Fires when a module is deactivated.
2833
		 * The dynamic part of the filter, $module, is the module slug.
2834
		 *
2835
		 * @since 1.9.0
2836
		 *
2837
		 * @param string $module Module slug.
2838
		 */
2839
		do_action( "jetpack_deactivate_module_$module", $module );
2840
2841
		// A flag for Jump Start so it's not shown again.
2842 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2843
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2844
2845
			//Jump start is being dismissed send data to MC Stats
2846
			$jetpack->stat( 'jumpstart', 'manual,deactivated-'.$module );
2847
2848
			$jetpack->do_stats( 'server_side' );
2849
		}
2850
2851
		return Jetpack_Options::update_option( 'active_modules', array_unique( $new ) );
2852
	}
2853
2854
	public static function enable_module_configurable( $module ) {
2855
		$module = Jetpack::get_module_slug( $module );
2856
		add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
2857
	}
2858
2859
	public static function module_configuration_url( $module ) {
2860
		$module = Jetpack::get_module_slug( $module );
2861
		return Jetpack::admin_url( array( 'page' => 'jetpack', 'configure' => $module ) );
2862
	}
2863
2864
	public static function module_configuration_load( $module, $method ) {
2865
		$module = Jetpack::get_module_slug( $module );
2866
		add_action( 'jetpack_module_configuration_load_' . $module, $method );
2867
	}
2868
2869
	public static function module_configuration_head( $module, $method ) {
2870
		$module = Jetpack::get_module_slug( $module );
2871
		add_action( 'jetpack_module_configuration_head_' . $module, $method );
2872
	}
2873
2874
	public static function module_configuration_screen( $module, $method ) {
2875
		$module = Jetpack::get_module_slug( $module );
2876
		add_action( 'jetpack_module_configuration_screen_' . $module, $method );
2877
	}
2878
2879
	public static function module_configuration_activation_screen( $module, $method ) {
2880
		$module = Jetpack::get_module_slug( $module );
2881
		add_action( 'display_activate_module_setting_' . $module, $method );
2882
	}
2883
2884
/* Installation */
2885
2886
	public static function bail_on_activation( $message, $deactivate = true ) {
2887
?>
2888
<!doctype html>
2889
<html>
2890
<head>
2891
<meta charset="<?php bloginfo( 'charset' ); ?>">
2892
<style>
2893
* {
2894
	text-align: center;
2895
	margin: 0;
2896
	padding: 0;
2897
	font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
2898
}
2899
p {
2900
	margin-top: 1em;
2901
	font-size: 18px;
2902
}
2903
</style>
2904
<body>
2905
<p><?php echo esc_html( $message ); ?></p>
2906
</body>
2907
</html>
2908
<?php
2909
		if ( $deactivate ) {
2910
			$plugins = get_option( 'active_plugins' );
2911
			$jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
2912
			$update  = false;
2913
			foreach ( $plugins as $i => $plugin ) {
2914
				if ( $plugin === $jetpack ) {
2915
					$plugins[$i] = false;
2916
					$update = true;
2917
				}
2918
			}
2919
2920
			if ( $update ) {
2921
				update_option( 'active_plugins', array_filter( $plugins ) );
2922
			}
2923
		}
2924
		exit;
2925
	}
2926
2927
	/**
2928
	 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
2929
	 * @static
2930
	 */
2931
	public static function plugin_activation( $network_wide ) {
2932
		Jetpack_Options::update_option( 'activated', 1 );
2933
2934
		if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
2935
			Jetpack::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
2936
		}
2937
2938
		if ( $network_wide )
2939
			Jetpack::state( 'network_nag', true );
2940
2941
		Jetpack::plugin_initialize();
2942
	}
2943
	/**
2944
	 * Runs before bumping version numbers up to a new version
2945
	 * @param  (string) $version    Version:timestamp
2946
	 * @param  (string) $old_version Old Version:timestamp or false if not set yet.
2947
	 * @return null              [description]
2948
	 */
2949
	public static function do_version_bump( $version, $old_version ) {
2950
2951
		if ( ! $old_version ) { // For new sites
2952
			// Setting up jetpack manage
2953
			Jetpack::activate_manage();
2954
		}
2955
	}
2956
2957
	/**
2958
	 * Sets the internal version number and activation state.
2959
	 * @static
2960
	 */
2961
	public static function plugin_initialize() {
2962
		if ( ! Jetpack_Options::get_option( 'activated' ) ) {
2963
			Jetpack_Options::update_option( 'activated', 2 );
2964
		}
2965
2966 View Code Duplication
		if ( ! Jetpack_Options::get_option( 'version' ) ) {
2967
			$version = $old_version = JETPACK__VERSION . ':' . time();
2968
			/** This action is documented in class.jetpack.php */
2969
			do_action( 'updating_jetpack_version', $version, false );
2970
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2971
		}
2972
2973
		Jetpack::load_modules();
2974
2975
		Jetpack_Options::delete_option( 'do_activate' );
2976
	}
2977
2978
	/**
2979
	 * Removes all connection options
2980
	 * @static
2981
	 */
2982
	public static function plugin_deactivation( ) {
2983
		require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
2984
		if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
2985
			Jetpack_Network::init()->deactivate();
2986
		} else {
2987
			Jetpack::disconnect( false );
2988
			//Jetpack_Heartbeat::init()->deactivate();
2989
		}
2990
	}
2991
2992
	/**
2993
	 * Disconnects from the Jetpack servers.
2994
	 * Forgets all connection details and tells the Jetpack servers to do the same.
2995
	 * @static
2996
	 */
2997
	public static function disconnect( $update_activated_state = true ) {
2998
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
2999
		Jetpack::clean_nonces( true );
3000
3001
		Jetpack::load_xml_rpc_client();
3002
		$xml = new Jetpack_IXR_Client();
3003
		$xml->query( 'jetpack.deregister' );
3004
3005
		Jetpack_Options::delete_option(
3006
			array(
3007
				'register',
3008
				'blog_token',
3009
				'user_token',
3010
				'user_tokens',
3011
				'master_user',
3012
				'time_diff',
3013
				'fallback_no_verify_ssl_certs',
3014
			)
3015
		);
3016
3017
		if ( $update_activated_state ) {
3018
			Jetpack_Options::update_option( 'activated', 4 );
3019
		}
3020
3021
		$jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' );
3022
		// Check then record unique disconnection if site has never been disconnected previously
3023
		if ( -1 == $jetpack_unique_connection['disconnected'] ) {
3024
			$jetpack_unique_connection['disconnected'] = 1;
3025
		}
3026
		else {
3027
			if ( 0 == $jetpack_unique_connection['disconnected'] ) {
3028
				//track unique disconnect
3029
				$jetpack = Jetpack::init();
3030
3031
				$jetpack->stat( 'connections', 'unique-disconnect' );
3032
				$jetpack->do_stats( 'server_side' );
3033
			}
3034
			// increment number of times disconnected
3035
			$jetpack_unique_connection['disconnected'] += 1;
3036
		}
3037
3038
		Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
3039
3040
		// Disable the Heartbeat cron
3041
		Jetpack_Heartbeat::init()->deactivate();
3042
	}
3043
3044
	/**
3045
	 * Unlinks the current user from the linked WordPress.com user
3046
	 */
3047
	public static function unlink_user( $user_id = null ) {
3048
		if ( ! $tokens = Jetpack_Options::get_option( 'user_tokens' ) )
3049
			return false;
3050
3051
		$user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
3052
3053
		if ( Jetpack_Options::get_option( 'master_user' ) == $user_id )
3054
			return false;
3055
3056
		if ( ! isset( $tokens[ $user_id ] ) )
3057
			return false;
3058
3059
		Jetpack::load_xml_rpc_client();
3060
		$xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
3061
		$xml->query( 'jetpack.unlink_user', $user_id );
3062
3063
		unset( $tokens[ $user_id ] );
3064
3065
		Jetpack_Options::update_option( 'user_tokens', $tokens );
3066
3067
		return true;
3068
	}
3069
3070
	/**
3071
	 * Attempts Jetpack registration.  If it fail, a state flag is set: @see ::admin_page_load()
3072
	 */
3073
	public static function try_registration() {
3074
		// Let's get some testing in beta versions and such.
3075
		if ( self::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
3076
			// Before attempting to connect, let's make sure that the domains are viable.
3077
			$domains_to_check = array_unique( array(
3078
				'siteurl' => parse_url( get_site_url(), PHP_URL_HOST ),
3079
				'homeurl' => parse_url( get_home_url(), PHP_URL_HOST ),
3080
			) );
3081
			foreach ( $domains_to_check as $domain ) {
3082
				$result = Jetpack_Data::is_usable_domain( $domain );
3083
				if ( is_wp_error( $result ) ) {
3084
					return $result;
3085
				}
3086
			}
3087
		}
3088
3089
		$result = Jetpack::register();
3090
3091
		// If there was an error with registration and the site was not registered, record this so we can show a message.
3092
		if ( ! $result || is_wp_error( $result ) ) {
3093
			return $result;
3094
		} else {
3095
			return true;
3096
		}
3097
	}
3098
3099
	/**
3100
	 * Tracking an internal event log. Try not to put too much chaff in here.
3101
	 *
3102
	 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
3103
	 */
3104
	public static function log( $code, $data = null ) {
3105
		// only grab the latest 200 entries
3106
		$log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3107
3108
		// Append our event to the log
3109
		$log_entry = array(
3110
			'time'    => time(),
3111
			'user_id' => get_current_user_id(),
3112
			'blog_id' => Jetpack_Options::get_option( 'id' ),
3113
			'code'    => $code,
3114
		);
3115
		// Don't bother storing it unless we've got some.
3116
		if ( ! is_null( $data ) ) {
3117
			$log_entry['data'] = $data;
3118
		}
3119
		$log[] = $log_entry;
3120
3121
		// Try add_option first, to make sure it's not autoloaded.
3122
		// @todo: Add an add_option method to Jetpack_Options
3123
		if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
3124
			Jetpack_Options::update_option( 'log', $log );
3125
		}
3126
3127
		/**
3128
		 * Fires when Jetpack logs an internal event.
3129
		 *
3130
		 * @since 3.0.0
3131
		 *
3132
		 * @param array $log_entry {
3133
		 *	Array of details about the log entry.
3134
		 *
3135
		 *	@param string time Time of the event.
3136
		 *	@param int user_id ID of the user who trigerred the event.
3137
		 *	@param int blog_id Jetpack Blog ID.
3138
		 *	@param string code Unique name for the event.
3139
		 *	@param string data Data about the event.
3140
		 * }
3141
		 */
3142
		do_action( 'jetpack_log_entry', $log_entry );
3143
	}
3144
3145
	/**
3146
	 * Get the internal event log.
3147
	 *
3148
	 * @param $event (string) - only return the specific log events
3149
	 * @param $num   (int)    - get specific number of latest results, limited to 200
3150
	 *
3151
	 * @return array of log events || WP_Error for invalid params
3152
	 */
3153
	public static function get_log( $event = false, $num = false ) {
3154
		if ( $event && ! is_string( $event ) ) {
3155
			return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
3156
		}
3157
3158
		if ( $num && ! is_numeric( $num ) ) {
3159
			return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
3160
		}
3161
3162
		$entire_log = Jetpack_Options::get_option( 'log', array() );
3163
3164
		// If nothing set - act as it did before, otherwise let's start customizing the output
3165
		if ( ! $num && ! $event ) {
3166
			return $entire_log;
3167
		} else {
3168
			$entire_log = array_reverse( $entire_log );
3169
		}
3170
3171
		$custom_log_output = array();
3172
3173
		if ( $event ) {
3174
			foreach ( $entire_log as $log_event ) {
3175
				if ( $event == $log_event[ 'code' ] ) {
3176
					$custom_log_output[] = $log_event;
3177
				}
3178
			}
3179
		} else {
3180
			$custom_log_output = $entire_log;
3181
		}
3182
3183
		if ( $num ) {
3184
			$custom_log_output = array_slice( $custom_log_output, 0, $num );
3185
		}
3186
3187
		return $custom_log_output;
3188
	}
3189
3190
	/**
3191
	 * Log modification of important settings.
3192
	 */
3193
	public static function log_settings_change( $option, $old_value, $value ) {
3194
		switch( $option ) {
3195
			case 'jetpack_sync_non_public_post_stati':
3196
				self::log( $option, $value );
3197
				break;
3198
		}
3199
	}
3200
3201
	/**
3202
	 * Return stat data for WPCOM sync
3203
	 */
3204
	function get_stat_data() {
3205
		$heartbeat_data = Jetpack_Heartbeat::generate_stats_array();
3206
		$additional_data = $this->get_additional_stat_data();
3207
3208
		return json_encode( array_merge( $heartbeat_data, $additional_data ) );
3209
	}
3210
3211
	/**
3212
	 * Get additional stat data to sync to WPCOM
3213
	 */
3214
	function get_additional_stat_data( $prefix = '' ) {
3215
		$return["{$prefix}themes"]         = Jetpack::get_parsed_theme_data();
3216
		$return["{$prefix}plugins-extra"]  = Jetpack::get_parsed_plugin_data();
3217
		$return["{$prefix}users"]          = count_users();
3218
		$return["{$prefix}site-count"]     = 0;
3219
		if ( function_exists( 'get_blog_count' ) ) {
3220
			$return["{$prefix}site-count"] = get_blog_count();
3221
		}
3222
		return $return;
3223
	}
3224
3225
	/* Admin Pages */
3226
3227
	function admin_init() {
3228
		// If the plugin is not connected, display a connect message.
3229
		if (
3230
			// the plugin was auto-activated and needs its candy
3231
			Jetpack_Options::get_option( 'do_activate' )
3232
		||
3233
			// the plugin is active, but was never activated.  Probably came from a site-wide network activation
3234
			! Jetpack_Options::get_option( 'activated' )
3235
		) {
3236
			Jetpack::plugin_initialize();
3237
		}
3238
3239
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
3240
			if ( 4 != Jetpack_Options::get_option( 'activated' ) ) {
3241
				// Show connect notice on dashboard and plugins pages
3242
				add_action( 'load-index.php', array( $this, 'prepare_connect_notice' ) );
3243
				add_action( 'load-plugins.php', array( $this, 'prepare_connect_notice' ) );
3244
			}
3245
		} elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
3246
			// Upgrade: 1.1 -> 1.1.1
3247
			// Check and see if host can verify the Jetpack servers' SSL certificate
3248
			$args = array();
3249
			Jetpack_Client::_wp_remote_request(
3250
				Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'test' ) ),
3251
				$args,
3252
				true
3253
			);
3254
		} else {
3255
			// Show the notice on the Dashboard only for now
3256
3257
			add_action( 'load-index.php', array( $this, 'prepare_manage_jetpack_notice' ) );
3258
3259
			// Identity crisis notices
3260
			add_action( 'jetpack_notices', array( $this, 'alert_identity_crisis' ) );
3261
		}
3262
3263
		// If the plugin has just been disconnected from WP.com, show the survey notice
3264
		if ( isset( $_GET['disconnected'] ) && 'true' === $_GET['disconnected'] ) {
3265
			add_action( 'jetpack_notices', array( $this, 'disconnect_survey_notice' ) );
3266
		}
3267
3268
		if ( current_user_can( 'manage_options' ) && 'ALWAYS' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
3269
			add_action( 'admin_notices', array( $this, 'alert_required_ssl_fail' ) );
3270
		}
3271
3272
		add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3273
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3274
		add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3275
3276
		if ( Jetpack::is_active() || Jetpack::is_development_mode() ) {
3277
			// Artificially throw errors in certain whitelisted cases during plugin activation
3278
			add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3279
3280
			// Kick off synchronization of user role when it changes
3281
			add_action( 'set_user_role', array( $this, 'user_role_change' ) );
3282
		}
3283
3284
		// Jetpack Manage Activation Screen from .com
3285
		Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
3286
	}
3287
3288
	function admin_body_class( $admin_body_class = '' ) {
3289
		$classes = explode( ' ', trim( $admin_body_class ) );
3290
3291
		$classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
3292
3293
		$admin_body_class = implode( ' ', array_unique( $classes ) );
3294
		return " $admin_body_class ";
3295
	}
3296
3297
	static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3298
		return $admin_body_class . ' jetpack-pagestyles ';
3299
	}
3300
3301
	function prepare_connect_notice() {
3302
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3303
3304
		add_action( 'admin_notices', array( $this, 'admin_connect_notice' ) );
3305
3306
		if ( Jetpack::state( 'network_nag' ) )
3307
			add_action( 'network_admin_notices', array( $this, 'network_connect_notice' ) );
3308
	}
3309
	/**
3310
	 * Call this function if you want the Big Jetpack Manage Notice to show up.
3311
	 *
3312
	 * @return null
3313
	 */
3314
	function prepare_manage_jetpack_notice() {
3315
3316
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3317
		add_action( 'admin_notices', array( $this, 'admin_jetpack_manage_notice' ) );
3318
	}
3319
3320
	function manage_activate_screen() {
3321
		include ( JETPACK__PLUGIN_DIR . 'modules/manage/activate-admin.php' );
3322
	}
3323
	/**
3324
	 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3325
	 * This function artificially throws errors for such cases (whitelisted).
3326
	 *
3327
	 * @param string $plugin The activated plugin.
3328
	 */
3329
	function throw_error_on_activate_plugin( $plugin ) {
3330
		$active_modules = Jetpack::get_active_modules();
3331
3332
		// The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3333
		if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3334
			$throw = false;
3335
3336
			// Try and make sure it really was the stats plugin
3337
			if ( ! class_exists( 'ReflectionFunction' ) ) {
3338
				if ( 'stats.php' == basename( $plugin ) ) {
3339
					$throw = true;
3340
				}
3341
			} else {
3342
				$reflection = new ReflectionFunction( 'stats_get_api_key' );
3343
				if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3344
					$throw = true;
3345
				}
3346
			}
3347
3348
			if ( $throw ) {
3349
				trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3350
			}
3351
		}
3352
	}
3353
3354
	function intercept_plugin_error_scrape_init() {
3355
		add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3356
	}
3357
3358
	function intercept_plugin_error_scrape( $action, $result ) {
3359
		if ( ! $result ) {
3360
			return;
3361
		}
3362
3363
		foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3364
			if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3365
				Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3366
			}
3367
		}
3368
	}
3369
3370
	function add_remote_request_handlers() {
3371
		add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
3372
	}
3373
3374
	function remote_request_handlers() {
3375
		switch ( current_filter() ) {
3376
		case 'wp_ajax_nopriv_jetpack_upload_file' :
3377
			$response = $this->upload_handler();
3378
			break;
3379
		default :
3380
			$response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
3381
			break;
3382
		}
3383
3384
		if ( ! $response ) {
3385
			$response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
3386
		}
3387
3388
		if ( is_wp_error( $response ) ) {
3389
			$status_code       = $response->get_error_data();
3390
			$error             = $response->get_error_code();
3391
			$error_description = $response->get_error_message();
3392
3393
			if ( ! is_int( $status_code ) ) {
3394
				$status_code = 400;
3395
			}
3396
3397
			status_header( $status_code );
3398
			die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3399
		}
3400
3401
		status_header( 200 );
3402
		if ( true === $response ) {
3403
			exit;
3404
		}
3405
3406
		die( json_encode( (object) $response ) );
3407
	}
3408
3409
	function upload_handler() {
3410
		if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3411
			return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
3412
		}
3413
3414
		$user = wp_authenticate( '', '' );
3415
		if ( ! $user || is_wp_error( $user ) ) {
3416
			return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
3417
		}
3418
3419
		wp_set_current_user( $user->ID );
3420
3421
		if ( ! current_user_can( 'upload_files' ) ) {
3422
			return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3423
		}
3424
3425
		if ( empty( $_FILES ) ) {
3426
			return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3427
		}
3428
3429
		foreach ( array_keys( $_FILES ) as $files_key ) {
3430
			if ( ! isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
3431
				return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3432
			}
3433
		}
3434
3435
		$media_keys = array_keys( $_FILES['media'] );
3436
3437
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
3438
		if ( ! $token || is_wp_error( $token ) ) {
3439
			return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3440
		}
3441
3442
		$uploaded_files = array();
3443
		$global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3444
		unset( $GLOBALS['post'] );
3445
		foreach ( $_FILES['media']['name'] as $index => $name ) {
3446
			$file = array();
3447
			foreach ( $media_keys as $media_key ) {
3448
				$file[$media_key] = $_FILES['media'][$media_key][$index];
3449
			}
3450
3451
			list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
3452
3453
			$hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3454
			if ( $hmac_provided !== $hmac_file ) {
3455
				$uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
3456
				continue;
3457
			}
3458
3459
			$_FILES['.jetpack.upload.'] = $file;
3460
			$post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
3461
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
3462
				$post_id = 0;
3463
			}
3464
			$attachment_id = media_handle_upload(
3465
				'.jetpack.upload.',
3466
				$post_id,
3467
				array(),
3468
				array(
3469
					'action' => 'jetpack_upload_file',
3470
				)
3471
			);
3472
3473
			if ( ! $attachment_id ) {
3474
				$uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
3475
			} elseif ( is_wp_error( $attachment_id ) ) {
3476
				$uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
3477
			} else {
3478
				$attachment = get_post( $attachment_id );
3479
				$uploaded_files[$index] = (object) array(
3480
					'id'   => (string) $attachment_id,
3481
					'file' => $attachment->post_title,
3482
					'url'  => wp_get_attachment_url( $attachment_id ),
3483
					'type' => $attachment->post_mime_type,
3484
					'meta' => wp_get_attachment_metadata( $attachment_id ),
3485
				);
3486
			}
3487
		}
3488
		if ( ! is_null( $global_post ) ) {
3489
			$GLOBALS['post'] = $global_post;
3490
		}
3491
3492
		return $uploaded_files;
3493
	}
3494
3495
	/**
3496
	 * Add help to the Jetpack page
3497
	 *
3498
	 * @since Jetpack (1.2.3)
3499
	 * @return false if not the Jetpack page
3500
	 */
3501
	function admin_help() {
3502
		$current_screen = get_current_screen();
3503
3504
		// Overview
3505
		$current_screen->add_help_tab(
3506
			array(
3507
				'id'		=> 'home',
3508
				'title'		=> __( 'Home', 'jetpack' ),
3509
				'content'	=>
3510
					'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3511
					'<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3512
					'<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>',
3513
			)
3514
		);
3515
3516
		// Screen Content
3517
		if ( current_user_can( 'manage_options' ) ) {
3518
			$current_screen->add_help_tab(
3519
				array(
3520
					'id'		=> 'settings',
3521
					'title'		=> __( 'Settings', 'jetpack' ),
3522
					'content'	=>
3523
						'<p><strong>' . __( 'Jetpack by WordPress.com',                                              'jetpack' ) . '</strong></p>' .
3524
						'<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3525
						'<ol>' .
3526
							'<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.',														'jetpack' ) . '</li>' .
3527
							'<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>' .
3528
						'</ol>' .
3529
						'<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>'
3530
				)
3531
			);
3532
		}
3533
3534
		// Help Sidebar
3535
		$current_screen->set_help_sidebar(
3536
			'<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3537
			'<p><a href="http://jetpack.me/faq/" target="_blank">'     . __( 'Jetpack FAQ',     'jetpack' ) . '</a></p>' .
3538
			'<p><a href="http://jetpack.me/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3539
			'<p><a href="' . Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) .'">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3540
		);
3541
	}
3542
3543
	function admin_menu_css() {
3544
		wp_enqueue_style( 'jetpack-icons' );
3545
	}
3546
3547
	function admin_menu_order() {
3548
		return true;
3549
	}
3550
3551 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
3552
		$jp_menu_order = array();
3553
3554
		foreach ( $menu_order as $index => $item ) {
3555
			if ( $item != 'jetpack' ) {
3556
				$jp_menu_order[] = $item;
3557
			}
3558
3559
			if ( $index == 0 ) {
3560
				$jp_menu_order[] = 'jetpack';
3561
			}
3562
		}
3563
3564
		return $jp_menu_order;
3565
	}
3566
3567
	function admin_head() {
3568 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) )
3569
			/** This action is documented in class.jetpack-admin-page.php */
3570
			do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
3571
	}
3572
3573
	function admin_banner_styles() {
3574
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3575
3576
		wp_enqueue_style( 'jetpack', plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION . '-20121016' );
3577
		wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3578
		wp_style_add_data( 'jetpack', 'suffix', $min );
3579
	}
3580
3581
	function admin_scripts() {
3582
		wp_enqueue_script( 'jetpack-js', plugins_url( '_inc/jp.js', JETPACK__PLUGIN_FILE ), array( 'jquery', 'wp-util' ), JETPACK__VERSION . '-20121111' );
3583
		wp_localize_script(
3584
			'jetpack-js',
3585
			'jetpackL10n',
3586
			array(
3587
				'ays_disconnect' => "This will deactivate all Jetpack modules.\nAre you sure you want to disconnect?",
3588
				'ays_unlink'     => "This will prevent user-specific modules such as Publicize, Notifications and Post By Email from working.\nAre you sure you want to unlink?",
3589
				'ays_dismiss'    => "This will deactivate Jetpack.\nAre you sure you want to deactivate Jetpack?",
3590
			)
3591
		);
3592
		add_action( 'admin_footer', array( $this, 'do_stats' ) );
3593
	}
3594
3595
	function plugin_action_links( $actions ) {
3596
3597
		$jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack' ), __( 'Jetpack', 'jetpack' ) ) );
3598
3599
		if( current_user_can( 'jetpack_manage_modules' ) && ( Jetpack::is_active() || Jetpack::is_development_mode() ) ) {
3600
			return array_merge(
3601
				$jetpack_home,
3602
				array( 'settings' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack_modules' ), __( 'Settings', 'jetpack' ) ) ),
3603
				array( 'support' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack-debugger '), __( 'Support', 'jetpack' ) ) ),
3604
				$actions
3605
				);
3606
			}
3607
3608
		return array_merge( $jetpack_home, $actions );
3609
	}
3610
3611
	function admin_connect_notice() {
3612
		// Don't show the connect notice anywhere but the plugins.php after activating
3613
		$current = get_current_screen();
3614
		if ( 'plugins' !== $current->parent_base )
3615
			return;
3616
3617
		if ( ! current_user_can( 'jetpack_connect' ) )
3618
			return;
3619
3620
		$dismiss_and_deactivate_url = wp_nonce_url( Jetpack::admin_url( '?page=jetpack&jetpack-notice=dismiss' ), 'jetpack-deactivate' );
3621
		?>
3622
		<div id="message" class="updated jetpack-message jp-banner" style="display:block !important;">
3623
			<a class="jp-banner__dismiss" href="<?php echo esc_url( $dismiss_and_deactivate_url ); ?>" title="<?php esc_attr_e( 'Dismiss this notice and deactivate Jetpack.', 'jetpack' ); ?>"></a>
3624
			<?php if ( in_array( Jetpack_Options::get_option( 'activated' ) , array( 1, 2, 3 ) ) ) : ?>
3625
				<div class="jp-banner__content is-connection">
3626
					<h2><?php _e( 'Your Jetpack is almost ready!', 'jetpack' ); ?></h2>
3627
					<p><?php _e( 'Connect now to enable features like Stats, Likes, and Social Sharing.', 'jetpack' ); ?></p>
3628
				</div>
3629
				<div class="jp-banner__action-container is-connection">
3630
						<a href="<?php echo $this->build_connect_url() ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'Connect to WordPress.com', 'jetpack' ); ?></a>
3631
				</div>
3632 View Code Duplication
			<?php else : ?>
3633
				<div class="jp-banner__content">
3634
					<h2><?php _e( 'Jetpack is installed!', 'jetpack' ) ?></h2>
3635
					<p><?php _e( 'It\'s ready to bring awesome, WordPress.com cloud-powered features to your site.', 'jetpack' ) ?></p>
3636
				</div>
3637
				<div class="jp-banner__action-container">
3638
					<a href="<?php echo Jetpack::admin_url() ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'Learn More', 'jetpack' ); ?></a>
3639
				</div>
3640
			<?php endif; ?>
3641
		</div>
3642
3643
		<?php
3644
	}
3645
3646
	/**
3647
	 * This is the first banner
3648
	 * It should be visible only to user that can update the option
3649
	 * Are not connected
3650
	 *
3651
	 * @return null
3652
	 */
3653
	function admin_jetpack_manage_notice() {
3654
		$screen = get_current_screen();
3655
3656
		// Don't show the connect notice on the jetpack settings page.
3657
		if ( ! in_array( $screen->base, array( 'dashboard' ) ) || $screen->is_network || $screen->action )
3658
			return;
3659
3660
		// Only show it if don't have the managment option set.
3661
		// And not dismissed it already.
3662
		if ( ! $this->can_display_jetpack_manage_notice() || Jetpack_Options::get_option( 'dismissed_manage_banner' ) ) {
3663
			return;
3664
		}
3665
3666
		$opt_out_url = $this->opt_out_jetpack_manage_url();
3667
		$opt_in_url  = $this->opt_in_jetpack_manage_url();
3668
		/**
3669
		 * I think it would be great to have different wordsing depending on where you are
3670
		 * for example if we show the notice on dashboard and a different one if we show it on Plugins screen
3671
		 * etc..
3672
		 */
3673
3674
		?>
3675
		<div id="message" class="updated jetpack-message jp-banner is-opt-in" style="display:block !important;">
3676
			<a class="jp-banner__dismiss" href="<?php echo esc_url( $opt_out_url ); ?>" title="<?php esc_attr_e( 'Dismiss this notice for now.', 'jetpack' ); ?>"></a>
3677
			<div class="jp-banner__content">
3678
				<h2><?php esc_html_e( 'New in Jetpack: Centralized Site Management', 'jetpack' ); ?></h2>
3679
				<p><?php printf( __( 'Manage multiple sites from one dashboard at wordpress.com/sites. Enabling allows all existing, connected Administrators to modify your site from WordPress.com. <a href="%s" target="_blank">Learn More</a>.', 'jetpack' ), 'http://jetpack.me/support/site-management' ); ?></p>
3680
			</div>
3681
			<div class="jp-banner__action-container is-opt-in">
3682
				<a href="<?php echo esc_url( $opt_in_url ); ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'Activate now', 'jetpack' ); ?></a>
3683
			</div>
3684
		</div>
3685
		<?php
3686
	}
3687
3688
	/**
3689
	 * Returns the url that the user clicks to remove the notice for the big banner
3690
	 * @return (string)
3691
	 */
3692
	function opt_out_jetpack_manage_url() {
3693
		$referer = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
3694
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-out' . $referer ), 'jetpack_manage_banner_opt_out' );
3695
	}
3696
	/**
3697
	 * Returns the url that the user clicks to opt in to Jetpack Manage
3698
	 * @return (string)
3699
	 */
3700
	function opt_in_jetpack_manage_url() {
3701
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-in' ), 'jetpack_manage_banner_opt_in' );
3702
	}
3703
3704
	function opt_in_jetpack_manage_notice() {
3705
		?>
3706
		<div class="wrap">
3707
			<div id="message" class="jetpack-message is-opt-in">
3708
				<?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(), 'http://jetpack.me/support/site-management' ); ?>
3709
			</div>
3710
		</div>
3711
		<?php
3712
3713
	}
3714
	/**
3715
	 * Determines whether to show the notice of not true = display notice
3716
	 * @return (bool)
3717
	 */
3718
	function can_display_jetpack_manage_notice() {
3719
		// never display the notice to users that can't do anything about it anyways
3720
		if( ! current_user_can( 'jetpack_manage_modules' ) )
3721
			return false;
3722
3723
		// don't display if we are in development more
3724
		if( Jetpack::is_development_mode() ) {
3725
			return false;
3726
		}
3727
		// don't display if the site is private
3728
		if(  ! Jetpack_Options::get_option( 'public' ) )
3729
			return false;
3730
3731
		/**
3732
		 * Should the Jetpack Remote Site Management notice be displayed.
3733
		 *
3734
		 * @since 3.3.0
3735
		 *
3736
		 * @param bool ! self::is_module_active( 'manage' ) Is the Manage module inactive.
3737
		 */
3738
		return apply_filters( 'can_display_jetpack_manage_notice', ! self::is_module_active( 'manage' ) );
3739
	}
3740
3741
	function network_connect_notice() {
3742
		?>
3743
		<div id="message" class="updated jetpack-message">
3744
			<div class="squeezer">
3745
				<h2><?php _e( '<strong>Jetpack is activated!</strong> Each site on your network must be connected individually by an admin on that site.', 'jetpack' ) ?></h2>
3746
			</div>
3747
		</div>
3748
		<?php
3749
	}
3750
3751
	public static function jetpack_comment_notice() {
3752
		if ( in_array( 'comments', Jetpack::get_active_modules() ) ) {
3753
			return '';
3754
		}
3755
3756
		$jetpack_old_version = explode( ':', Jetpack_Options::get_option( 'old_version' ) );
3757
		$jetpack_new_version = explode( ':', Jetpack_Options::get_option( 'version' ) );
3758
3759
		if ( $jetpack_old_version ) {
3760
			if ( version_compare( $jetpack_old_version[0], '1.4', '>=' ) ) {
3761
				return '';
3762
			}
3763
		}
3764
3765
		if ( $jetpack_new_version ) {
3766
			if ( version_compare( $jetpack_new_version[0], '1.4-something', '<' ) ) {
3767
				return '';
3768
			}
3769
		}
3770
3771
		return '<br /><br />' . sprintf(
3772
			__( 'Jetpack now includes Comments, which enables your visitors to use their WordPress.com, Twitter, or Facebook accounts when commenting on your site. To activate Comments, <a href="%s">%s</a>.', 'jetpack' ),
3773
			wp_nonce_url(
3774
				Jetpack::admin_url(
3775
					array(
3776
						'page'   => 'jetpack',
3777
						'action' => 'activate',
3778
						'module' => 'comments',
3779
					)
3780
				),
3781
				'jetpack_activate-comments'
3782
			),
3783
			__( 'click here', 'jetpack' )
3784
		);
3785
	}
3786
3787
	/**
3788
	 * Show the survey link when the user has just disconnected Jetpack.
3789
	 */
3790
	function disconnect_survey_notice() {
3791
		?>
3792
		<div class="wrap">
3793
			<div id="message" class="jetpack-message stay-visible">
3794
				<div class="squeezer">
3795
					<h2>
3796
						<?php _e( 'You have successfully disconnected Jetpack.', 'jetpack' ); ?>
3797
						<br />
3798
						<?php echo sprintf(
3799
							__( 'Would you tell us why? Just <a href="%1$s" target="%2$s">answering two simple questions</a> would help us improve Jetpack.', 'jetpack' ),
3800
							'https://jetpack.me/survey-disconnected/',
3801
							'_blank'
3802
						); ?>
3803
					</h2>
3804
				</div>
3805
			</div>
3806
		</div>
3807
		<?php
3808
	}
3809
3810
	/*
3811
	 * Registration flow:
3812
	 * 1 - ::admin_page_load() action=register
3813
	 * 2 - ::try_registration()
3814
	 * 3 - ::register()
3815
	 *     - Creates jetpack_register option containing two secrets and a timestamp
3816
	 *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3817
	 *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3818
	 *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
3819
	 *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
3820
	 *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
3821
	 *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
3822
	 *       jetpack_id, jetpack_secret, jetpack_public
3823
	 *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
3824
	 * 4 - redirect to https://jetpack.wordpress.com/jetpack.authorize/1/
3825
	 * 5 - user logs in with WP.com account
3826
	 * 6 - redirect to this site's wp-admin/index.php?page=jetpack&action=authorize with
3827
	 *     code <-- OAuth2 style authorization code
3828
	 * 7 - ::admin_page_load() action=authorize
3829
	 * 8 - Jetpack_Client_Server::authorize()
3830
	 * 9 - Jetpack_Client_Server::get_token()
3831
	 * 10- GET https://jetpack.wordpress.com/jetpack.token/1/ with
3832
	 *     client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
3833
	 * 11- which responds with
3834
	 *     access_token, token_type, scope
3835
	 * 12- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
3836
	 * 13- Jetpack::activate_default_modules()
3837
	 *     Deactivates deprecated plugins
3838
	 *     Activates all default modules
3839
	 *     Catches errors: redirects to wp-admin/index.php?page=jetpack state:error=something
3840
	 * 14- redirect to this site's wp-admin/index.php?page=jetpack with state:message=authorized
3841
	 *     Done!
3842
	 */
3843
3844
	/**
3845
	 * Handles the page load events for the Jetpack admin page
3846
	 */
3847
	function admin_page_load() {
3848
		$error = false;
3849
3850
		// Make sure we have the right body class to hook stylings for subpages off of.
3851
		add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ) );
3852
3853
		if ( ! empty( $_GET['jetpack_restate'] ) ) {
3854
			// Should only be used in intermediate redirects to preserve state across redirects
3855
			Jetpack::restate();
3856
		}
3857
3858
		if ( isset( $_GET['connect_url_redirect'] ) ) {
3859
			// User clicked in the iframe to link their accounts
3860
			if ( ! Jetpack::is_user_connected() ) {
3861
				$connect_url = $this->build_connect_url( true );
3862
				if ( isset( $_GET['notes_iframe'] ) )
3863
					$connect_url .= '&notes_iframe';
3864
				wp_redirect( $connect_url );
3865
				exit;
3866
			} else {
3867
				Jetpack::state( 'message', 'already_authorized' );
3868
				wp_safe_redirect( Jetpack::admin_url() );
3869
				exit;
3870
			}
3871
		}
3872
3873
3874
		if ( isset( $_GET['action'] ) ) {
3875
			switch ( $_GET['action'] ) {
3876
			case 'authorize' :
3877
				if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
3878
					Jetpack::state( 'message', 'already_authorized' );
3879
					wp_safe_redirect( Jetpack::admin_url() );
3880
					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...
3881
				}
3882
				Jetpack::log( 'authorize' );
3883
				$client_server = new Jetpack_Client_Server;
3884
				$client_server->authorize();
3885
				exit;
3886
			case 'register' :
3887
				if ( ! current_user_can( 'jetpack_connect' ) ) {
3888
					$error = 'cheatin';
3889
					break;
3890
				}
3891
				check_admin_referer( 'jetpack-register' );
3892
				Jetpack::log( 'register' );
3893
				Jetpack::maybe_set_version_option();
3894
				$registered = Jetpack::try_registration();
3895
				if ( is_wp_error( $registered ) ) {
3896
					$error = $registered->get_error_code();
3897
					Jetpack::state( 'error_description', $registered->get_error_message() );
3898
					break;
3899
				}
3900
3901
				wp_redirect( $this->build_connect_url( true ) );
3902
				exit;
3903
			case 'activate' :
3904
				if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3905
					$error = 'cheatin';
3906
					break;
3907
				}
3908
3909
				$module = stripslashes( $_GET['module'] );
3910
				check_admin_referer( "jetpack_activate-$module" );
3911
				Jetpack::log( 'activate', $module );
3912
				Jetpack::activate_module( $module );
3913
				// The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3914
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3915
				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...
3916
			case 'activate_default_modules' :
3917
				check_admin_referer( 'activate_default_modules' );
3918
				Jetpack::log( 'activate_default_modules' );
3919
				Jetpack::restate();
3920
				$min_version   = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
3921
				$max_version   = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
3922
				$other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
3923
				Jetpack::activate_default_modules( $min_version, $max_version, $other_modules );
3924
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3925
				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...
3926
			case 'disconnect' :
3927
				if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3928
					$error = 'cheatin';
3929
					break;
3930
				}
3931
3932
				check_admin_referer( 'jetpack-disconnect' );
3933
				Jetpack::log( 'disconnect' );
3934
				Jetpack::disconnect();
3935
				wp_safe_redirect( Jetpack::admin_url( 'disconnected=true' ) );
3936
				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...
3937
			case 'reconnect' :
3938
				if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3939
					$error = 'cheatin';
3940
					break;
3941
				}
3942
3943
				check_admin_referer( 'jetpack-reconnect' );
3944
				Jetpack::log( 'reconnect' );
3945
				$this->disconnect();
3946
				wp_redirect( $this->build_connect_url( true ) );
3947
				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...
3948 View Code Duplication
			case 'deactivate' :
3949
				if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3950
					$error = 'cheatin';
3951
					break;
3952
				}
3953
3954
				$modules = stripslashes( $_GET['module'] );
3955
				check_admin_referer( "jetpack_deactivate-$modules" );
3956
				foreach ( explode( ',', $modules ) as $module ) {
3957
					Jetpack::log( 'deactivate', $module );
3958
					Jetpack::deactivate_module( $module );
3959
					Jetpack::state( 'message', 'module_deactivated' );
3960
				}
3961
				Jetpack::state( 'module', $modules );
3962
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3963
				exit;
3964
			case 'unlink' :
3965
				$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
3966
				check_admin_referer( 'jetpack-unlink' );
3967
				Jetpack::log( 'unlink' );
3968
				$this->unlink_user();
3969
				Jetpack::state( 'message', 'unlinked' );
3970
				if ( 'sub-unlink' == $redirect ) {
3971
					wp_safe_redirect( admin_url() );
3972
				} else {
3973
					wp_safe_redirect( Jetpack::admin_url( array( 'page' => $redirect ) ) );
3974
				}
3975
				exit;
3976
			default:
3977
				/**
3978
				 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3979
				 *
3980
				 * @since 2.6.0
3981
				 *
3982
				 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
3983
				 */
3984
				do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
3985
			}
3986
		}
3987
3988
		if ( ! $error = $error ? $error : Jetpack::state( 'error' ) ) {
3989
			self::activate_new_modules( true );
3990
		}
3991
3992
		switch ( $error ) {
3993
		case 'cheatin' :
3994
			$this->error = __( 'Cheatin&#8217; uh?', 'jetpack' );
3995
			break;
3996
		case 'access_denied' :
3997
			$this->error = __( 'You need to authorize the Jetpack connection between your site and WordPress.com to enable the awesome features.', 'jetpack' );
3998
			break;
3999
		case 'wrong_state' :
4000
			$this->error = __( 'Don&#8217;t cross the streams!  You need to stay logged in to your WordPress blog while you authorize Jetpack.', 'jetpack' );
4001
			break;
4002
		case 'invalid_client' :
4003
			// @todo re-register instead of deactivate/reactivate
4004
			$this->error = __( 'Return to sender.  Whoops! It looks like you got the wrong Jetpack in the mail; deactivate then reactivate the Jetpack plugin to get a new one.', 'jetpack' );
4005
			break;
4006
		case 'invalid_grant' :
4007
			$this->error = __( 'Wrong size.  Hm&#8230; it seems your Jetpack doesn&#8217;t quite fit.  Have you lost weight? Click &#8220;Connect to WordPress.com&#8221; again to get your Jetpack adjusted.', 'jetpack' );
4008
			break;
4009
		case 'site_inaccessible' :
4010
		case 'site_requires_authorization' :
4011
			$this->error = sprintf( __( 'Your website needs to be publicly accessible to use Jetpack: %s', 'jetpack' ), "<code>$error</code>" );
4012
			break;
4013
		case 'module_activation_failed' :
4014
			$module = Jetpack::state( 'module' );
4015
			if ( ! empty( $module ) && $mod = Jetpack::get_module( $module ) ) {
4016
				$this->error = sprintf( __( '%s could not be activated because it triggered a <strong>fatal error</strong>. Perhaps there is a conflict with another plugin you have installed?', 'jetpack' ), $mod['name'] );
4017
				if ( isset( $this->plugins_to_deactivate[$module] ) ) {
4018
					$this->error .= ' ' . sprintf( __( 'Do you still have the %s plugin installed?', 'jetpack' ), $this->plugins_to_deactivate[$module][1] );
4019
				}
4020
			} else {
4021
				$this->error = __( 'Module could not be activated because it triggered a <strong>fatal error</strong>. Perhaps there is a conflict with another plugin you have installed?', 'jetpack' );
4022
			}
4023
			if ( $php_errors = Jetpack::state( 'php_errors' ) ) {
4024
				$this->error .= "<br />\n";
4025
				$this->error .= $php_errors;
4026
			}
4027
			break;
4028
		case 'master_user_required' :
4029
			$module = Jetpack::state( 'module' );
4030
			$module_name = '';
4031
			if ( ! empty( $module ) && $mod = Jetpack::get_module( $module ) ) {
4032
				$module_name = $mod['name'];
4033
			}
4034
4035
			$master_user = Jetpack_Options::get_option( 'master_user' );
4036
			$master_userdata = get_userdata( $master_user ) ;
4037
			if ( $master_userdata ) {
4038
				if ( ! in_array( $module, Jetpack::get_active_modules() ) ) {
4039
					$this->error = sprintf( __( '%s was not activated.' , 'jetpack' ), $module_name );
4040
				} else {
4041
					$this->error = sprintf( __( '%s was not deactivated.' , 'jetpack' ), $module_name );
4042
				}
4043
				$this->error .= '  ' . sprintf( __( 'This module can only be altered by %s, the user who initiated the Jetpack connection on this site.' , 'jetpack' ), esc_html( $master_userdata->display_name ) );
4044
4045
			} else {
4046
				$this->error = sprintf( __( 'Only the user who initiated the Jetpack connection on this site can toggle %s, but that user no longer exists. This should not happen.', 'jetpack' ), $module_name );
4047
			}
4048
			break;
4049
		case 'not_public' :
4050
			$this->error = __( '<strong>Your Jetpack has a glitch.</strong> Connecting this site with WordPress.com is not possible. This usually means your site is not publicly accessible (localhost).', 'jetpack' );
4051
			break;
4052
		case 'wpcom_408' :
4053
		case 'wpcom_5??' :
4054
		case 'wpcom_bad_response' :
4055
		case 'wpcom_outage' :
4056
			$this->error = __( 'WordPress.com is currently having problems and is unable to fuel up your Jetpack.  Please try again later.', 'jetpack' );
4057
			break;
4058
		case 'register_http_request_failed' :
4059
		case 'token_http_request_failed' :
4060
			$this->error = sprintf( __( 'Jetpack could not contact WordPress.com: %s.  This usually means something is incorrectly configured on your web host.', 'jetpack' ), "<code>$error</code>" );
4061
			break;
4062
		default :
4063
			if ( empty( $error ) ) {
4064
				break;
4065
			}
4066
			$error = trim( substr( strip_tags( $error ), 0, 20 ) );
4067
			// no break: fall through
4068
		case 'no_role' :
4069
		case 'no_cap' :
4070
		case 'no_code' :
4071
		case 'no_state' :
4072
		case 'invalid_state' :
4073
		case 'invalid_request' :
4074
		case 'invalid_scope' :
4075
		case 'unsupported_response_type' :
4076
		case 'invalid_token' :
4077
		case 'no_token' :
4078
		case 'missing_secrets' :
4079
		case 'home_missing' :
4080
		case 'siteurl_missing' :
4081
		case 'gmt_offset_missing' :
4082
		case 'site_name_missing' :
4083
		case 'secret_1_missing' :
4084
		case 'secret_2_missing' :
4085
		case 'site_lang_missing' :
4086
		case 'home_malformed' :
4087
		case 'siteurl_malformed' :
4088
		case 'gmt_offset_malformed' :
4089
		case 'timezone_string_malformed' :
4090
		case 'site_name_malformed' :
4091
		case 'secret_1_malformed' :
4092
		case 'secret_2_malformed' :
4093
		case 'site_lang_malformed' :
4094
		case 'secrets_mismatch' :
4095
		case 'verify_secret_1_missing' :
4096
		case 'verify_secret_1_malformed' :
4097
		case 'verify_secrets_missing' :
4098
		case 'verify_secrets_mismatch' :
4099
			$error = esc_html( $error );
4100
			$this->error = sprintf( __( '<strong>Your Jetpack has a glitch.</strong>  Something went wrong that&#8217;s never supposed to happen.  Guess you&#8217;re just lucky: %s', 'jetpack' ), "<code>$error</code>" );
4101
			if ( ! Jetpack::is_active() ) {
4102
				$this->error .= '<br />';
4103
				$this->error .= sprintf( __( 'Try connecting again.', 'jetpack' ) );
4104
			}
4105
			break;
4106
		}
4107
4108
		$message_code = Jetpack::state( 'message' );
4109
4110
		$active_state = Jetpack::state( 'activated_modules' );
4111
		if ( ! empty( $active_state ) ) {
4112
			$available    = Jetpack::get_available_modules();
4113
			$active_state = explode( ',', $active_state );
4114
			$active_state = array_intersect( $active_state, $available );
4115
			if ( count( $active_state ) ) {
4116
				foreach ( $active_state as $mod ) {
4117
					$this->stat( 'module-activated', $mod );
4118
				}
4119
			} else {
4120
				$active_state = false;
4121
			}
4122
		}
4123
		if( Jetpack::state( 'optin-manage' ) ) {
4124
			$activated_manage = $message_code;
4125
			$message_code = 'jetpack-manage';
4126
4127
		}
4128
		switch ( $message_code ) {
4129
		case 'modules_activated' :
4130
			$this->message = sprintf(
4131
				__( 'Welcome to <strong>Jetpack %s</strong>!', 'jetpack' ),
4132
				JETPACK__VERSION
4133
			);
4134
4135
			if ( $active_state ) {
4136
				$titles = array();
4137 View Code Duplication
				foreach ( $active_state as $mod ) {
4138
					if ( $mod_headers = Jetpack::get_module( $mod ) ) {
4139
						$titles[] = '<strong>' . preg_replace( '/\s+(?![^<>]++>)/', '&nbsp;', $mod_headers['name'] ) . '</strong>';
4140
					}
4141
				}
4142
				if ( $titles ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $titles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
4143
					$this->message .= '<br /><br />' . wp_sprintf( __( 'The following new modules have been activated: %l.', 'jetpack' ), $titles );
4144
				}
4145
			}
4146
4147
			if ( $reactive_state = Jetpack::state( 'reactivated_modules' ) ) {
4148
				$titles = array();
4149 View Code Duplication
				foreach ( explode( ',',  $reactive_state ) as $mod ) {
4150
					if ( $mod_headers = Jetpack::get_module( $mod ) ) {
4151
						$titles[] = '<strong>' . preg_replace( '/\s+(?![^<>]++>)/', '&nbsp;', $mod_headers['name'] ) . '</strong>';
4152
					}
4153
				}
4154
				if ( $titles ) {
4155
					$this->message .= '<br /><br />' . wp_sprintf( __( 'The following modules have been updated: %l.', 'jetpack' ), $titles );
4156
				}
4157
			}
4158
4159
			$this->message .= Jetpack::jetpack_comment_notice();
4160
			break;
4161
		case 'jetpack-manage':
4162
			$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>';
4163
			if ( $activated_manage ) {
4164
				$this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack'  ) . '</strong>';
4165
			}
4166
			break;
4167
		case 'module_activated' :
4168
			if ( $module = Jetpack::get_module( Jetpack::state( 'module' ) ) ) {
4169
				$this->message = sprintf( __( '<strong>%s Activated!</strong> You can deactivate at any time by clicking the Deactivate link next to each module.', 'jetpack' ), $module['name'] );
4170
				$this->stat( 'module-activated', Jetpack::state( 'module' ) );
4171
			}
4172
			break;
4173
4174
		case 'module_deactivated' :
4175
			$modules = Jetpack::state( 'module' );
4176
			if ( ! $modules ) {
4177
				break;
4178
			}
4179
4180
			$module_names = array();
4181
			foreach ( explode( ',', $modules ) as $module_slug ) {
4182
				$module = Jetpack::get_module( $module_slug );
4183
				if ( $module ) {
4184
					$module_names[] = $module['name'];
4185
				}
4186
4187
				$this->stat( 'module-deactivated', $module_slug );
4188
			}
4189
4190
			if ( ! $module_names ) {
4191
				break;
4192
			}
4193
4194
			$this->message = wp_sprintf(
4195
				_nx(
4196
					'<strong>%l Deactivated!</strong> You can activate it again at any time using the activate link next to each module.',
4197
					'<strong>%l Deactivated!</strong> You can activate them again at any time using the activate links next to each module.',
4198
					count( $module_names ),
4199
					'%l = list of Jetpack module/feature names',
4200
					'jetpack'
4201
				),
4202
				$module_names
4203
			);
4204
			break;
4205
4206
		case 'module_configured' :
4207
			$this->message = __( '<strong>Module settings were saved.</strong> ', 'jetpack' );
4208
			break;
4209
4210
		case 'already_authorized' :
4211
			$this->message = __( '<strong>Your Jetpack is already connected.</strong> ', 'jetpack' );
4212
			break;
4213
4214
		case 'authorized' :
4215
			$this->message  = __( '<strong>You&#8217;re fueled up and ready to go, Jetpack is now active.</strong> ', 'jetpack' );
4216
			$this->message .= Jetpack::jetpack_comment_notice();
4217
			break;
4218
4219
		case 'linked' :
4220
			$this->message  = __( '<strong>You&#8217;re fueled up and ready to go.</strong> ', 'jetpack' );
4221
			$this->message .= Jetpack::jetpack_comment_notice();
4222
			break;
4223
4224
		case 'unlinked' :
4225
			$user = wp_get_current_user();
4226
			$this->message = sprintf( __( '<strong>You have unlinked your account (%s) from WordPress.com.</strong>', 'jetpack' ), $user->user_login );
4227
			break;
4228
4229
		case 'switch_master' :
4230
			global $current_user;
4231
			$is_master_user = $current_user->ID == Jetpack_Options::get_option( 'master_user' );
4232
			$master_userdata = get_userdata( Jetpack_Options::get_option( 'master_user' ) );
4233
			if ( $is_master_user ) {
4234
				$this->message = __( 'You have successfully set yourself as Jetpack’s primary user.', 'jetpack' );
4235
			} else {
4236
				$this->message = sprintf( _x( 'You have successfully set %s as Jetpack’s primary user.', '%s is a username', 'jetpack' ), $master_userdata->user_login );
4237
			}
4238
			break;
4239
		}
4240
4241
		$deactivated_plugins = Jetpack::state( 'deactivated_plugins' );
4242
4243
		if ( ! empty( $deactivated_plugins ) ) {
4244
			$deactivated_plugins = explode( ',', $deactivated_plugins );
4245
			$deactivated_titles  = array();
4246
			foreach ( $deactivated_plugins as $deactivated_plugin ) {
4247
				if ( ! isset( $this->plugins_to_deactivate[$deactivated_plugin] ) ) {
4248
					continue;
4249
				}
4250
4251
				$deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[$deactivated_plugin][1] ) . '</strong>';
4252
			}
4253
4254
			if ( $deactivated_titles ) {
4255
				if ( $this->message ) {
4256
					$this->message .= "<br /><br />\n";
4257
				}
4258
4259
				$this->message .= wp_sprintf(
4260
					_n(
4261
						'Jetpack contains the most recent version of the old %l plugin.',
4262
						'Jetpack contains the most recent versions of the old %l plugins.',
4263
						count( $deactivated_titles ),
4264
						'jetpack'
4265
					),
4266
					$deactivated_titles
4267
				);
4268
4269
				$this->message .= "<br />\n";
4270
4271
				$this->message .= _n(
4272
					'The old version has been deactivated and can be removed from your site.',
4273
					'The old versions have been deactivated and can be removed from your site.',
4274
					count( $deactivated_titles ),
4275
					'jetpack'
4276
				);
4277
			}
4278
		}
4279
4280
		$this->privacy_checks = Jetpack::state( 'privacy_checks' );
4281
4282
		if ( $this->message || $this->error || $this->privacy_checks || $this->can_display_jetpack_manage_notice() ) {
4283
			add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
4284
		}
4285
4286 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
4287
			/**
4288
			 * Fires when a module configuration page is loaded.
4289
			 * The dynamic part of the hook is the configure parameter from the URL.
4290
			 *
4291
			 * @since 1.1.0
4292
			 */
4293
			do_action( 'jetpack_module_configuration_load_' . $_GET['configure'] );
4294
		}
4295
4296
		add_filter( 'jetpack_short_module_description', 'wptexturize' );
4297
	}
4298
4299
	function admin_notices() {
4300
4301
		if ( $this->error ) {
4302
?>
4303
<div id="message" class="jetpack-message jetpack-err">
4304
	<div class="squeezer">
4305
		<h2><?php echo wp_kses( $this->error, array( 'code' => true, 'strong' => true, 'br' => true, 'b' => true ) ); ?></h2>
4306
<?php	if ( $desc = Jetpack::state( 'error_description' ) ) : ?>
4307
		<p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
4308
<?php	endif; ?>
4309
	</div>
4310
</div>
4311
<?php
4312
		}
4313
4314
		if ( $this->message ) {
4315
?>
4316
<div id="message" class="jetpack-message">
4317
	<div class="squeezer">
4318
		<h2><?php echo wp_kses( $this->message, array( 'strong' => array(), 'a' => array( 'href' => true ), 'br' => true ) ); ?></h2>
4319
	</div>
4320
</div>
4321
<?php
4322
		}
4323
4324
		if ( $this->privacy_checks ) :
4325
			$module_names = $module_slugs = array();
4326
4327
			$privacy_checks = explode( ',', $this->privacy_checks );
4328
			$privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
4329
			foreach ( $privacy_checks as $module_slug ) {
4330
				$module = Jetpack::get_module( $module_slug );
4331
				if ( ! $module ) {
4332
					continue;
4333
				}
4334
4335
				$module_slugs[] = $module_slug;
4336
				$module_names[] = "<strong>{$module['name']}</strong>";
4337
			}
4338
4339
			$module_slugs = join( ',', $module_slugs );
4340
?>
4341
<div id="message" class="jetpack-message jetpack-err">
4342
	<div class="squeezer">
4343
		<h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
4344
		<p><?php
4345
			echo wp_kses(
4346
				wptexturize(
4347
					wp_sprintf(
4348
						_nx(
4349
							"Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
4350
							"Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
4351
							count( $privacy_checks ),
4352
							'%l = list of Jetpack module/feature names',
4353
							'jetpack'
4354
						),
4355
						$module_names
4356
					)
4357
				),
4358
				array( 'strong' => true )
4359
			);
4360
4361
			echo "\n<br />\n";
4362
4363
			echo wp_kses(
4364
				sprintf(
4365
					_nx(
4366
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
4367
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
4368
						count( $privacy_checks ),
4369
						'%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
4370
						'jetpack'
4371
					),
4372
					wp_nonce_url(
4373
						Jetpack::admin_url(
4374
							array(
4375
								'page'   => 'jetpack',
4376
								'action' => 'deactivate',
4377
								'module' => urlencode( $module_slugs ),
4378
							)
4379
						),
4380
						"jetpack_deactivate-$module_slugs"
4381
					),
4382
					esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
4383
				),
4384
				array( 'a' => array( 'href' => true, 'title' => true ) )
4385
			);
4386
		?></p>
4387
	</div>
4388
</div>
4389
<?php endif;
4390
	// only display the notice if the other stuff is not there
4391
	if( $this->can_display_jetpack_manage_notice() && !  $this->error && ! $this->message && ! $this->privacy_checks ) {
4392
		if( isset( $_GET['page'] ) && 'jetpack' != $_GET['page'] )
4393
			$this->opt_in_jetpack_manage_notice();
4394
		}
4395
	}
4396
4397
	/**
4398
	 * Record a stat for later output.  This will only currently output in the admin_footer.
4399
	 */
4400
	function stat( $group, $detail ) {
4401
		if ( ! isset( $this->stats[ $group ] ) )
4402
			$this->stats[ $group ] = array();
4403
		$this->stats[ $group ][] = $detail;
4404
	}
4405
4406
	/**
4407
	 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
4408
	 */
4409
	function do_stats( $method = '' ) {
4410
		if ( is_array( $this->stats ) && count( $this->stats ) ) {
4411
			foreach ( $this->stats as $group => $stats ) {
4412
				if ( is_array( $stats ) && count( $stats ) ) {
4413
					$args = array( "x_jetpack-{$group}" => implode( ',', $stats ) );
4414
					if ( 'server_side' === $method ) {
4415
						self::do_server_side_stat( $args );
4416
					} else {
4417
						echo '<img src="' . esc_url( self::build_stats_url( $args ) ) . '" width="1" height="1" style="display:none;" />';
4418
					}
4419
				}
4420
				unset( $this->stats[ $group ] );
4421
			}
4422
		}
4423
	}
4424
4425
	/**
4426
	 * Runs stats code for a one-off, server-side.
4427
	 *
4428
	 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
4429
	 *
4430
	 * @return bool If it worked.
4431
	 */
4432
	static function do_server_side_stat( $args ) {
4433
		$response = wp_remote_get( esc_url_raw( self::build_stats_url( $args ) ) );
4434
		if ( is_wp_error( $response ) )
4435
			return false;
4436
4437
		if ( 200 !== wp_remote_retrieve_response_code( $response ) )
4438
			return false;
4439
4440
		return true;
4441
	}
4442
4443
	/**
4444
	 * Builds the stats url.
4445
	 *
4446
	 * @param $args array|string The arguments to append to the URL.
4447
	 *
4448
	 * @return string The URL to be pinged.
4449
	 */
4450
	static function build_stats_url( $args ) {
4451
		$defaults = array(
4452
			'v'    => 'wpcom2',
4453
			'rand' => md5( mt_rand( 0, 999 ) . time() ),
4454
		);
4455
		$args     = wp_parse_args( $args, $defaults );
4456
		/**
4457
		 * Filter the URL used as the Stats tracking pixel.
4458
		 *
4459
		 * @since 2.3.2
4460
		 *
4461
		 * @param string $url Base URL used as the Stats tracking pixel.
4462
		 */
4463
		$base_url = apply_filters(
4464
			'jetpack_stats_base_url',
4465
			set_url_scheme( 'http://pixel.wp.com/g.gif' )
4466
		);
4467
		$url      = add_query_arg( $args, $base_url );
4468
		return $url;
4469
	}
4470
4471
	function translate_current_user_to_role() {
4472
		foreach ( $this->capability_translations as $role => $cap ) {
4473
			if ( current_user_can( $role ) || current_user_can( $cap ) ) {
4474
				return $role;
4475
			}
4476
		}
4477
4478
		return false;
4479
	}
4480
4481
	function translate_role_to_cap( $role ) {
4482
		if ( ! isset( $this->capability_translations[$role] ) ) {
4483
			return false;
4484
		}
4485
4486
		return $this->capability_translations[$role];
4487
	}
4488
4489
	function sign_role( $role ) {
4490
		if ( ! $user_id = (int) get_current_user_id() ) {
4491
			return false;
4492
		}
4493
4494
		$token = Jetpack_Data::get_access_token();
4495
		if ( ! $token || is_wp_error( $token ) ) {
4496
			return false;
4497
		}
4498
4499
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
4500
	}
4501
4502
	function build_connect_url( $raw = false, $redirect = false ) {
4503
		if ( ! Jetpack_Options::get_option( 'blog_token' ) || ! Jetpack_Options::get_option( 'id' ) ) {
4504
			$url = Jetpack::nonce_url_no_esc( Jetpack::admin_url( 'action=register' ), 'jetpack-register' );
4505
			if( is_network_admin() ) {
4506
			    $url = add_query_arg( 'is_multisite', network_admin_url(
4507
			    'admin.php?page=jetpack-settings' ), $url );
4508
			}
4509
		} else {
4510
			$role = $this->translate_current_user_to_role();
4511
			$signed_role = $this->sign_role( $role );
4512
4513
			$user = wp_get_current_user();
4514
4515
			$redirect = $redirect ? esc_url_raw( $redirect ) : '';
4516
4517
			if( isset( $_REQUEST['is_multisite'] ) ) {
4518
				$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4519
			}
4520
4521
			$args = urlencode_deep(
4522
				array(
4523
					'response_type' => 'code',
4524
					'client_id'     => Jetpack_Options::get_option( 'id' ),
4525
					'redirect_uri'  => add_query_arg(
4526
						array(
4527
							'action'   => 'authorize',
4528
							'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
4529
							'redirect' => $redirect ? urlencode( $redirect ) : false,
4530
						),
4531
						menu_page_url( 'jetpack', false )
4532
					),
4533
					'state'         => $user->ID,
4534
					'scope'         => $signed_role,
4535
					'user_email'    => $user->user_email,
4536
					'user_login'    => $user->user_login,
4537
					'is_active'     => Jetpack::is_active(),
4538
					'jp_version'    => JETPACK__VERSION,
4539
				)
4540
			);
4541
4542
			$url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) );
4543
		}
4544
4545
		return $raw ? $url : esc_url( $url );
4546
	}
4547
4548
	function build_reconnect_url( $raw = false ) {
4549
		$url = wp_nonce_url( Jetpack::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4550
		return $raw ? $url : esc_url( $url );
4551
	}
4552
4553
	public static function admin_url( $args = null ) {
4554
		$args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
4555
		$url = add_query_arg( $args, admin_url( 'admin.php' ) );
4556
		return $url;
4557
	}
4558
4559
	public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4560
		$actionurl = str_replace( '&amp;', '&', $actionurl );
4561
		return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4562
	}
4563
4564
	function dismiss_jetpack_notice() {
4565
4566
		if ( ! isset( $_GET['jetpack-notice'] ) ) {
4567
			return;
4568
		}
4569
4570
		switch( $_GET['jetpack-notice'] ) {
4571
			case 'dismiss':
4572
				if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4573
4574
					require_once ABSPATH . 'wp-admin/includes/plugin.php';
4575
					deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4576
					wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4577
				}
4578
				break;
4579 View Code Duplication
			case 'jetpack-manage-opt-out':
4580
4581
				if ( check_admin_referer( 'jetpack_manage_banner_opt_out' ) ) {
4582
					// Don't show the banner again
4583
4584
					Jetpack_Options::update_option( 'dismissed_manage_banner', true );
4585
					// redirect back to the page that had the notice
4586
					if ( wp_get_referer() ) {
4587
						wp_safe_redirect( wp_get_referer() );
4588
					} else {
4589
						// Take me to Jetpack
4590
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4591
					}
4592
				}
4593
				break;
4594 View Code Duplication
			case 'jetpack-protect-multisite-opt-out':
4595
4596
				if ( check_admin_referer( 'jetpack_protect_multisite_banner_opt_out' ) ) {
4597
					// Don't show the banner again
4598
4599
					update_site_option( 'jetpack_dismissed_protect_multisite_banner', true );
4600
					// redirect back to the page that had the notice
4601
					if ( wp_get_referer() ) {
4602
						wp_safe_redirect( wp_get_referer() );
4603
					} else {
4604
						// Take me to Jetpack
4605
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4606
					}
4607
				}
4608
				break;
4609
			case 'jetpack-manage-opt-in':
4610
				if ( check_admin_referer( 'jetpack_manage_banner_opt_in' ) ) {
4611
					// This makes sure that we are redirect to jetpack home so that we can see the Success Message.
4612
4613
					$redirection_url = Jetpack::admin_url();
4614
					remove_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4615
4616
					// Don't redirect form the Jetpack Setting Page
4617
					$referer_parsed = parse_url ( wp_get_referer() );
4618
					// check that we do have a wp_get_referer and the query paramater is set orderwise go to the Jetpack Home
4619
					if ( isset( $referer_parsed['query'] ) && false !== strpos( $referer_parsed['query'], 'page=jetpack_modules' ) ) {
4620
						// Take the user to Jetpack home except when on the setting page
4621
						$redirection_url = wp_get_referer();
4622
						add_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4623
					}
4624
					// Also update the JSON API FULL MANAGEMENT Option
4625
					Jetpack::activate_module( 'manage', false, false );
4626
4627
					// Special Message when option in.
4628
					Jetpack::state( 'optin-manage', 'true' );
4629
					// Activate the Module if not activated already
4630
4631
					// Redirect properly
4632
					wp_safe_redirect( $redirection_url );
4633
4634
				}
4635
				break;
4636
		}
4637
	}
4638
4639
	function debugger_page() {
4640
		nocache_headers();
4641
		if ( ! current_user_can( 'manage_options' ) ) {
4642
			die( '-1' );
4643
		}
4644
		Jetpack_Debugger::jetpack_debug_display_handler();
4645
		exit;
4646
	}
4647
4648
	public static function admin_screen_configure_module( $module_id ) {
4649
4650
		// User that doesn't have 'jetpack_configure_modules' will never end up here since Jetpack Landing Page woun't let them.
4651
		if ( ! in_array( $module_id, Jetpack::get_active_modules() ) && current_user_can( 'manage_options' ) ) {
4652
			if ( has_action( 'display_activate_module_setting_' . $module_id ) ) {
4653
				/**
4654
				 * Fires to diplay a custom module activation screen.
4655
				 *
4656
				 * To add a module actionation screen use Jetpack::module_configuration_activation_screen method.
4657
				 * Example: Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
4658
				 *
4659
				 * @module manage
4660
				 *
4661
				 * @since 3.8.0
4662
				 *
4663
				 * @param int $module_id Module ID.
4664
				 */
4665
				do_action( 'display_activate_module_setting_' . $module_id );
4666
			} else {
4667
				self::display_activate_module_link( $module_id );
4668
			}
4669
4670
			return false;
4671
		} ?>
4672
4673
		<div id="jp-settings-screen" style="position: relative">
4674
			<h3>
4675
			<?php
4676
				$module = Jetpack::get_module( $module_id );
4677
				echo '<a href="' . Jetpack::admin_url( 'page=jetpack_modules' ) . '">' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</a> &rarr; ';
4678
				printf( __( 'Configure %s', 'jetpack' ), $module['name'] );
4679
			?>
4680
			</h3>
4681
			<?php
4682
				/**
4683
				 * Fires within the displayed message when a feature configuation is updated.
4684
				 *
4685
				 * @since 3.4.0
4686
				 *
4687
				 * @param int $module_id Module ID.
4688
				 */
4689
				do_action( 'jetpack_notices_update_settings', $module_id );
4690
				/**
4691
				 * Fires when a feature configuation screen is loaded.
4692
				 * The dynamic part of the hook, $module_id, is the module ID.
4693
				 *
4694
				 * @since 1.1.0
4695
				 */
4696
				do_action( 'jetpack_module_configuration_screen_' . $module_id );
4697
			?>
4698
		</div><?php
4699
	}
4700
4701
	/**
4702
	 * Display link to activate the module to see the settings screen.
4703
	 * @param  string $module_id
4704
	 * @return null
4705
	 */
4706
	public static function display_activate_module_link( $module_id ) {
4707
4708
		$info =  Jetpack::get_module( $module_id );
4709
		$extra = '';
4710
		$activate_url = wp_nonce_url(
4711
				Jetpack::admin_url(
4712
					array(
4713
						'page'   => 'jetpack',
4714
						'action' => 'activate',
4715
						'module' => $module_id,
4716
					)
4717
				),
4718
				"jetpack_activate-$module_id"
4719
			);
4720
4721
		?>
4722
4723
		<div class="wrap configure-module">
4724
			<div id="jp-settings-screen">
4725
				<?php
4726
				if ( $module_id == 'json-api' ) {
4727
4728
					$info['name'] = esc_html__( 'Activate Site Management and JSON API', 'jetpack' );
4729
4730
					$activate_url = Jetpack::init()->opt_in_jetpack_manage_url();
4731
4732
					$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' ), 'http://jetpack.me/support/site-management' );
4733
4734
					// $extra = __( 'To use Site Management, you need to first activate JSON API to allow remote management of your site. ', 'jetpack' );
4735
				} ?>
4736
4737
				<h3><?php echo esc_html( $info['name'] ); ?></h3>
4738
				<div class="narrow">
4739
					<p><?php echo  $info['description']; ?></p>
4740
					<?php if( $extra ) { ?>
4741
					<p><?php echo esc_html( $extra ); ?></p>
4742
					<?php } ?>
4743
					<p>
4744
						<?php
4745
						if( wp_get_referer() ) {
4746
							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() );
4747
						} else {
4748
							printf( __( '<a class="button-primary" href="%s">Activate Now</a>', 'jetpack' ) , $activate_url  );
4749
						} ?>
4750
					</p>
4751
				</div>
4752
4753
			</div>
4754
		</div>
4755
4756
		<?php
4757
	}
4758
4759
	public static function sort_modules( $a, $b ) {
4760
		if ( $a['sort'] == $b['sort'] )
4761
			return 0;
4762
4763
		return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
4764
	}
4765
4766 View Code Duplication
	function sync_reindex_trigger() {
4767
		if ( $this->current_user_is_connection_owner() && current_user_can( 'manage_options' ) ) {
4768
			echo json_encode( $this->sync->reindex_trigger() );
4769
		} else {
4770
			echo '{"status":"ERROR"}';
4771
		}
4772
		exit;
4773
	}
4774
4775 View Code Duplication
	function sync_reindex_status(){
4776
		if ( $this->current_user_is_connection_owner() && current_user_can( 'manage_options' ) ) {
4777
			echo json_encode( $this->sync->reindex_status() );
4778
		} else {
4779
			echo '{"status":"ERROR"}';
4780
		}
4781
		exit;
4782
	}
4783
4784
/* Client API */
4785
4786
	/**
4787
	 * Returns the requested Jetpack API URL
4788
	 *
4789
	 * @return string
4790
	 */
4791
	public static function api_url( $relative_url ) {
4792
		return trailingslashit( JETPACK__API_BASE . $relative_url  ) . JETPACK__API_VERSION . '/';
4793
	}
4794
4795
	/**
4796
	 * Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
4797
	 */
4798
	public static function fix_url_for_bad_hosts( $url ) {
4799
		if ( 0 !== strpos( $url, 'https://' ) ) {
4800
			return $url;
4801
		}
4802
4803
		switch ( JETPACK_CLIENT__HTTPS ) {
4804
			case 'ALWAYS' :
4805
				return $url;
4806
			case 'NEVER' :
4807
				return set_url_scheme( $url, 'http' );
4808
			// default : case 'AUTO' :
4809
		}
4810
4811
		// Yay! Your host is good!
4812
		if ( self::permit_ssl() && wp_http_supports( array( 'ssl' => true ) ) ) {
4813
			return $url;
4814
		}
4815
4816
		// Boo! Your host is bad and makes Jetpack cry!
4817
		return set_url_scheme( $url, 'http' );
4818
	}
4819
4820
	/**
4821
	 * Checks to see if the URL is using SSL to connect with Jetpack
4822
	 *
4823
	 * @since 2.3.3
4824
	 * @return boolean
4825
	 */
4826
	public static function permit_ssl( $force_recheck = false ) {
4827
		// Do some fancy tests to see if ssl is being supported
4828
		if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
4829
			if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
4830
				$ssl = 0;
4831
			} else {
4832
				switch ( JETPACK_CLIENT__HTTPS ) {
4833
					case 'NEVER':
4834
						$ssl = 0;
4835
						break;
4836
					case 'ALWAYS':
4837
					case 'AUTO':
4838
					default:
4839
						$ssl = 1;
4840
						break;
4841
				}
4842
4843
				// If it's not 'NEVER', test to see
4844
				if ( $ssl ) {
4845
					$response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4846
					if ( is_wp_error( $response ) || ( 'OK' !== wp_remote_retrieve_body( $response ) ) ) {
4847
						$ssl = 0;
4848
					}
4849
				}
4850
			}
4851
			set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4852
		}
4853
4854
		return (bool) $ssl;
4855
	}
4856
4857
	/*
4858
	 * Displays an admin_notice, alerting the user to their JETPACK_CLIENT__HTTPS constant being 'ALWAYS' but SSL isn't working.
4859
	 */
4860
	public function alert_required_ssl_fail() {
4861
		if ( ! current_user_can( 'manage_options' ) )
4862
			return;
4863
		?>
4864
4865
		<div id="message" class="error jetpack-message jp-identity-crisis">
4866
			<div class="jp-banner__content">
4867
				<h2><?php _e( 'Something is being cranky!', 'jetpack' ); ?></h2>
4868
				<p><?php _e( 'Your site is configured to only permit SSL connections to Jetpack, but SSL connections don\'t seem to be functional!', 'jetpack' ); ?></p>
4869
			</div>
4870
		</div>
4871
4872
		<?php
4873
	}
4874
4875
	/**
4876
	 * Returns the Jetpack XML-RPC API
4877
	 *
4878
	 * @return string
4879
	 */
4880
	public static function xmlrpc_api_url() {
4881
		$base = preg_replace( '#(https?://[^?/]+)(/?.*)?$#', '\\1', JETPACK__API_BASE );
4882
		return untrailingslashit( $base ) . '/xmlrpc.php';
4883
	}
4884
4885
	/**
4886
	 * Creates two secret tokens and the end of life timestamp for them.
4887
	 *
4888
	 * Note these tokens are unique per call, NOT static per site for connecting.
4889
	 *
4890
	 * @since 2.6
4891
	 * @return array
4892
	 */
4893
	public function generate_secrets() {
4894
	    $secrets = array(
4895
		wp_generate_password( 32, false ), // secret_1
4896
		wp_generate_password( 32, false ), // secret_2
4897
		( time() + 600 ), // eol ( End of Life )
4898
	    );
4899
4900
	    return $secrets;
4901
	}
4902
4903
	/**
4904
	 * Builds the timeout limit for queries talking with the wpcom servers.
4905
	 *
4906
	 * Based on local php max_execution_time in php.ini
4907
	 *
4908
	 * @since 2.6
4909
	 * @return int
4910
	 **/
4911
	public function get_remote_query_timeout_limit() {
4912
	    $timeout = (int) ini_get( 'max_execution_time' );
4913
	    if ( ! $timeout ) // Ensure exec time set in php.ini
4914
		$timeout = 30;
4915
	    return intval( $timeout / 2 );
4916
	}
4917
4918
4919
	/**
4920
	 * Takes the response from the Jetpack register new site endpoint and
4921
	 * verifies it worked properly.
4922
	 *
4923
	 * @since 2.6
4924
	 * @return true or Jetpack_Error
4925
	 **/
4926
	public function validate_remote_register_response( $response ) {
4927
	    	if ( is_wp_error( $response ) ) {
4928
			return new Jetpack_Error( 'register_http_request_failed', $response->get_error_message() );
4929
		}
4930
4931
		$code   = wp_remote_retrieve_response_code( $response );
4932
		$entity = wp_remote_retrieve_body( $response );
4933
		if ( $entity )
4934
			$json = json_decode( $entity );
4935
		else
4936
			$json = false;
4937
4938
		$code_type = intval( $code / 100 );
4939
		if ( 5 == $code_type ) {
4940
			return new Jetpack_Error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4941
		} elseif ( 408 == $code ) {
4942
			return new Jetpack_Error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4943
		} elseif ( ! empty( $json->error ) ) {
4944
			$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
4945
			return new Jetpack_Error( (string) $json->error, $error_description, $code );
4946
		} elseif ( 200 != $code ) {
4947
			return new Jetpack_Error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4948
		}
4949
4950
		// Jetpack ID error block
4951
		if ( empty( $json->jetpack_id ) ) {
4952
			return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ), $entity );
4953
		} elseif ( ! is_scalar( $json->jetpack_id ) ) {
4954
			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 );
4955
		} elseif ( preg_match( '/[^0-9]/', $json->jetpack_id ) ) {
4956
			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 );
4957
		}
4958
4959
	    return true;
4960
	}
4961
	/**
4962
	 * @return bool|WP_Error
4963
	 */
4964
	public static function register() {
4965
		add_action( 'pre_update_jetpack_option_register', array( 'Jetpack_Options', 'delete_option' ) );
4966
		$secrets = Jetpack::init()->generate_secrets();
4967
4968
		Jetpack_Options::update_option( 'register', $secrets[0] . ':' . $secrets[1] . ':' . $secrets[2] );
4969
4970
		@list( $secret_1, $secret_2, $secret_eol ) = explode( ':', Jetpack_Options::get_option( 'register' ) );
4971
		if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) {
4972
			return new Jetpack_Error( 'missing_secrets' );
4973
		}
4974
4975
		$timeout = Jetpack::init()->get_remote_query_timeout_limit();
4976
4977
		$gmt_offset = get_option( 'gmt_offset' );
4978
		if ( ! $gmt_offset ) {
4979
			$gmt_offset = 0;
4980
		}
4981
4982
		$stats_options = get_option( 'stats_options' );
4983
		$stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null;
4984
4985
		$args = array(
4986
			'method'  => 'POST',
4987
			'body'    => array(
4988
				'siteurl'         => site_url(),
4989
				'home'            => home_url(),
4990
				'gmt_offset'      => $gmt_offset,
4991
				'timezone_string' => (string) get_option( 'timezone_string' ),
4992
				'site_name'       => (string) get_option( 'blogname' ),
4993
				'secret_1'        => $secret_1,
4994
				'secret_2'        => $secret_2,
4995
				'site_lang'       => get_locale(),
4996
				'timeout'         => $timeout,
4997
				'stats_id'        => $stats_id,
4998
			),
4999
			'headers' => array(
5000
				'Accept' => 'application/json',
5001
			),
5002
			'timeout' => $timeout,
5003
		);
5004
		$response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'register' ) ), $args, true );
5005
5006
5007
		// Make sure the response is valid and does not contain any Jetpack errors
5008
		$valid_response = Jetpack::init()->validate_remote_register_response( $response );
5009
		if( is_wp_error( $valid_response ) || !$valid_response ) {
5010
		    return $valid_response;
5011
		}
5012
5013
5014
		// Grab the response values to work with
5015
		$code   = wp_remote_retrieve_response_code( $response );
5016
		$entity = wp_remote_retrieve_body( $response );
5017
5018
		if ( $entity )
5019
			$json = json_decode( $entity );
5020
		else
5021
			$json = false;
5022
5023 View Code Duplication
		if ( empty( $json->jetpack_secret ) || ! is_string( $json->jetpack_secret ) )
5024
			return new Jetpack_Error( 'jetpack_secret', '', $code );
5025
5026
		if ( isset( $json->jetpack_public ) ) {
5027
			$jetpack_public = (int) $json->jetpack_public;
5028
		} else {
5029
			$jetpack_public = false;
5030
		}
5031
5032
		Jetpack_Options::update_options(
5033
			array(
5034
				'id'         => (int)    $json->jetpack_id,
5035
				'blog_token' => (string) $json->jetpack_secret,
5036
				'public'     => $jetpack_public,
5037
			)
5038
		);
5039
5040
		/**
5041
		 * Fires when a site is registered on WordPress.com.
5042
		 *
5043
		 * @since 3.7.0
5044
		 *
5045
		 * @param int $json->jetpack_id Jetpack Blog ID.
5046
		 * @param string $json->jetpack_secret Jetpack Blog Token.
5047
		 * @param int|bool $jetpack_public Is the site public.
5048
		 */
5049
		do_action( 'jetpack_site_registered', $json->jetpack_id, $json->jetpack_secret, $jetpack_public );
5050
5051
		// Initialize Jump Start for the first and only time.
5052
		if ( ! Jetpack_Options::get_option( 'jumpstart' ) ) {
5053
			Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
5054
5055
			$jetpack = Jetpack::init();
5056
5057
			$jetpack->stat( 'jumpstart', 'unique-views' );
5058
			$jetpack->do_stats( 'server_side' );
5059
		};
5060
5061
		return true;
5062
	}
5063
5064
	/**
5065
	 * If the db version is showing something other that what we've got now, bump it to current.
5066
	 *
5067
	 * @return bool: True if the option was incorrect and updated, false if nothing happened.
5068
	 */
5069
	public static function maybe_set_version_option() {
5070
		list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
5071
		if ( JETPACK__VERSION != $version ) {
5072
			Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
5073
			return true;
5074
		}
5075
		return false;
5076
	}
5077
5078
/* Client Server API */
5079
5080
	/**
5081
	 * Loads the Jetpack XML-RPC client
5082
	 */
5083
	public static function load_xml_rpc_client() {
5084
		require_once ABSPATH . WPINC . '/class-IXR.php';
5085
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-ixr-client.php';
5086
	}
5087
5088
	function verify_xml_rpc_signature() {
5089
		if ( $this->xmlrpc_verification ) {
5090
			return $this->xmlrpc_verification;
5091
		}
5092
5093
		// It's not for us
5094
		if ( ! isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
5095
			return false;
5096
		}
5097
5098
		@list( $token_key, $version, $user_id ) = explode( ':', $_GET['token'] );
5099
		if (
5100
			empty( $token_key )
5101
		||
5102
			empty( $version ) || strval( JETPACK__API_VERSION ) !== $version
5103
		) {
5104
			return false;
5105
		}
5106
5107
		if ( '0' === $user_id ) {
5108
			$token_type = 'blog';
5109
			$user_id = 0;
5110
		} else {
5111
			$token_type = 'user';
5112
			if ( empty( $user_id ) || ! ctype_digit( $user_id ) ) {
5113
				return false;
5114
			}
5115
			$user_id = (int) $user_id;
5116
5117
			$user = new WP_User( $user_id );
5118
			if ( ! $user || ! $user->exists() ) {
5119
				return false;
5120
			}
5121
		}
5122
5123
		$token = Jetpack_Data::get_access_token( $user_id );
5124
		if ( ! $token ) {
5125
			return false;
5126
		}
5127
5128
		$token_check = "$token_key.";
5129
		if ( ! hash_equals( substr( $token->secret, 0, strlen( $token_check ) ), $token_check ) ) {
5130
			return false;
5131
		}
5132
5133
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
5134
5135
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5136
		if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
5137
			$post_data   = $_POST;
5138
			$file_hashes = array();
5139
			foreach ( $post_data as $post_data_key => $post_data_value ) {
5140
				if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
5141
					continue;
5142
				}
5143
				$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
5144
				$file_hashes[$post_data_key] = $post_data_value;
5145
			}
5146
5147
			foreach ( $file_hashes as $post_data_key => $post_data_value ) {
5148
				unset( $post_data["_jetpack_file_hmac_{$post_data_key}"] );
5149
				$post_data[$post_data_key] = $post_data_value;
5150
			}
5151
5152
			ksort( $post_data );
5153
5154
			$body = http_build_query( stripslashes_deep( $post_data ) );
5155
		} elseif ( is_null( $this->HTTP_RAW_POST_DATA ) ) {
5156
			$body = file_get_contents( 'php://input' );
5157
		} else {
5158
			$body = null;
5159
		}
5160
		$signature = $jetpack_signature->sign_current_request(
5161
			array( 'body' => is_null( $body ) ? $this->HTTP_RAW_POST_DATA : $body, )
5162
		);
5163
5164
		if ( ! $signature ) {
5165
			return false;
5166
		} else if ( is_wp_error( $signature ) ) {
5167
			return $signature;
5168
		} else if ( ! hash_equals( $signature, $_GET['signature'] ) ) {
5169
			return false;
5170
		}
5171
5172
		$timestamp = (int) $_GET['timestamp'];
5173
		$nonce     = stripslashes( (string) $_GET['nonce'] );
5174
5175
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
5176
			return false;
5177
		}
5178
5179
		$this->xmlrpc_verification = array(
5180
			'type'    => $token_type,
5181
			'user_id' => $token->external_user_id,
5182
		);
5183
5184
		return $this->xmlrpc_verification;
5185
	}
5186
5187
	/**
5188
	 * Authenticates XML-RPC and other requests from the Jetpack Server
5189
	 */
5190
	function authenticate_jetpack( $user, $username, $password ) {
5191
		if ( is_a( $user, 'WP_User' ) ) {
5192
			return $user;
5193
		}
5194
5195
		$token_details = $this->verify_xml_rpc_signature();
5196
5197
		if ( ! $token_details || is_wp_error( $token_details ) ) {
5198
			return $user;
5199
		}
5200
5201
		if ( 'user' !== $token_details['type'] ) {
5202
			return $user;
5203
		}
5204
5205
		if ( ! $token_details['user_id'] ) {
5206
			return $user;
5207
		}
5208
5209
		nocache_headers();
5210
5211
		return new WP_User( $token_details['user_id'] );
5212
	}
5213
5214
	function add_nonce( $timestamp, $nonce ) {
5215
		global $wpdb;
5216
		static $nonces_used_this_request = array();
5217
5218
		if ( isset( $nonces_used_this_request["$timestamp:$nonce"] ) ) {
5219
			return $nonces_used_this_request["$timestamp:$nonce"];
5220
		}
5221
5222
		// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce
5223
		$timestamp = (int) $timestamp;
5224
		$nonce     = esc_sql( $nonce );
5225
5226
		// Raw query so we can avoid races: add_option will also update
5227
		$show_errors = $wpdb->show_errors( false );
5228
5229
		$old_nonce = $wpdb->get_row(
5230
			$wpdb->prepare( "SELECT * FROM `$wpdb->options` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
5231
		);
5232
5233
		if ( is_null( $old_nonce ) ) {
5234
			$return = $wpdb->query(
5235
				$wpdb->prepare(
5236
					"INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
5237
					"jetpack_nonce_{$timestamp}_{$nonce}",
5238
					time(),
5239
					'no'
5240
				)
5241
			);
5242
		} else {
5243
			$return = false;
5244
		}
5245
5246
		$wpdb->show_errors( $show_errors );
5247
5248
		$nonces_used_this_request["$timestamp:$nonce"] = $return;
5249
5250
		return $return;
5251
	}
5252
5253
	/**
5254
	 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths since it is passed by reference to various methods.
5255
	 * Capture it here so we can verify the signature later.
5256
	 */
5257
	function xmlrpc_methods( $methods ) {
5258
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
5259
		return $methods;
5260
	}
5261
5262
	function public_xmlrpc_methods( $methods ) {
5263
		if ( array_key_exists( 'wp.getOptions', $methods ) ) {
5264
			$methods['wp.getOptions'] = array( $this, 'jetpack_getOptions' );
5265
		}
5266
		return $methods;
5267
	}
5268
5269
	function jetpack_getOptions( $args ) {
5270
		global $wp_xmlrpc_server;
5271
5272
		$wp_xmlrpc_server->escape( $args );
5273
5274
		$username	= $args[1];
5275
		$password	= $args[2];
5276
5277
		if ( !$user = $wp_xmlrpc_server->login($username, $password) ) {
5278
			return $wp_xmlrpc_server->error;
5279
		}
5280
5281
		$options = array();
5282
		$user_data = $this->get_connected_user_data();
5283
		if ( is_array( $user_data ) ) {
5284
			$options['jetpack_user_id'] = array(
5285
				'desc'          => __( 'The WP.com user ID of the connected user', 'jetpack' ),
5286
				'readonly'      => true,
5287
				'value'         => $user_data['ID'],
5288
			);
5289
			$options['jetpack_user_login'] = array(
5290
				'desc'          => __( 'The WP.com username of the connected user', 'jetpack' ),
5291
				'readonly'      => true,
5292
				'value'         => $user_data['login'],
5293
			);
5294
			$options['jetpack_user_email'] = array(
5295
				'desc'          => __( 'The WP.com user email of the connected user', 'jetpack' ),
5296
				'readonly'      => true,
5297
				'value'         => $user_data['email'],
5298
			);
5299
			$options['jetpack_user_site_count'] = array(
5300
				'desc'          => __( 'The number of sites of the connected WP.com user', 'jetpack' ),
5301
				'readonly'      => true,
5302
				'value'         => $user_data['site_count'],
5303
			);
5304
		}
5305
		$wp_xmlrpc_server->blog_options = array_merge( $wp_xmlrpc_server->blog_options, $options );
5306
		$args = stripslashes_deep( $args );
5307
		return $wp_xmlrpc_server->wp_getOptions( $args );
5308
	}
5309
5310
	function xmlrpc_options( $options ) {
5311
		$jetpack_client_id = false;
5312
		if ( self::is_active() ) {
5313
			$jetpack_client_id = Jetpack_Options::get_option( 'id' );
5314
		}
5315
		$options['jetpack_version'] = array(
5316
				'desc'          => __( 'Jetpack Plugin Version', 'jetpack' ),
5317
				'readonly'      => true,
5318
				'value'         => JETPACK__VERSION,
5319
		);
5320
5321
		$options['jetpack_client_id'] = array(
5322
				'desc'          => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack' ),
5323
				'readonly'      => true,
5324
				'value'         => $jetpack_client_id,
5325
		);
5326
		return $options;
5327
	}
5328
5329
	public static function clean_nonces( $all = false ) {
5330
		global $wpdb;
5331
5332
		$sql = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
5333
		if ( method_exists ( $wpdb , 'esc_like' ) ) {
5334
			$sql_args = array( $wpdb->esc_like( 'jetpack_nonce_' ) . '%' );
5335
		} else {
5336
			$sql_args = array( like_escape( 'jetpack_nonce_' ) . '%' );
5337
		}
5338
5339
		if ( true !== $all ) {
5340
			$sql .= ' AND CAST( `option_value` AS UNSIGNED ) < %d';
5341
			$sql_args[] = time() - 3600;
5342
		}
5343
5344
		$sql .= ' ORDER BY `option_id` LIMIT 100';
5345
5346
		$sql = $wpdb->prepare( $sql, $sql_args );
5347
5348
		for ( $i = 0; $i < 1000; $i++ ) {
5349
			if ( ! $wpdb->query( $sql ) ) {
5350
				break;
5351
			}
5352
		}
5353
	}
5354
5355
	/**
5356
	 * State is passed via cookies from one request to the next, but never to subsequent requests.
5357
	 * SET: state( $key, $value );
5358
	 * GET: $value = state( $key );
5359
	 *
5360
	 * @param string $key
5361
	 * @param string $value
5362
	 * @param bool $restate private
5363
	 */
5364
	public static function state( $key = null, $value = null, $restate = false ) {
5365
		static $state = array();
5366
		static $path, $domain;
5367
		if ( ! isset( $path ) ) {
5368
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
5369
			$admin_url = Jetpack::admin_url();
5370
			$bits      = parse_url( $admin_url );
5371
5372
			if ( is_array( $bits ) ) {
5373
				$path   = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
5374
				$domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
5375
			} else {
5376
				$path = $domain = null;
5377
			}
5378
		}
5379
5380
		// Extract state from cookies and delete cookies
5381
		if ( isset( $_COOKIE[ 'jetpackState' ] ) && is_array( $_COOKIE[ 'jetpackState' ] ) ) {
5382
			$yum = $_COOKIE[ 'jetpackState' ];
5383
			unset( $_COOKIE[ 'jetpackState' ] );
5384
			foreach ( $yum as $k => $v ) {
5385
				if ( strlen( $v ) )
5386
					$state[ $k ] = $v;
5387
				setcookie( "jetpackState[$k]", false, 0, $path, $domain );
5388
			}
5389
		}
5390
5391
		if ( $restate ) {
5392
			foreach ( $state as $k => $v ) {
5393
				setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
5394
			}
5395
			return;
5396
		}
5397
5398
		// Get a state variable
5399
		if ( isset( $key ) && ! isset( $value ) ) {
5400
			if ( array_key_exists( $key, $state ) )
5401
				return $state[ $key ];
5402
			return null;
5403
		}
5404
5405
		// Set a state variable
5406
		if ( isset ( $key ) && isset( $value ) ) {
5407
			if( is_array( $value ) && isset( $value[0] ) ) {
5408
				$value = $value[0];
5409
			}
5410
			$state[ $key ] = $value;
5411
			setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
5412
		}
5413
	}
5414
5415
	public static function restate() {
5416
		Jetpack::state( null, null, true );
5417
	}
5418
5419
	public static function check_privacy( $file ) {
5420
		static $is_site_publicly_accessible = null;
5421
5422
		if ( is_null( $is_site_publicly_accessible ) ) {
5423
			$is_site_publicly_accessible = false;
5424
5425
			Jetpack::load_xml_rpc_client();
5426
			$rpc = new Jetpack_IXR_Client();
5427
5428
			$success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
5429
			if ( $success ) {
5430
				$response = $rpc->getResponse();
5431
				if ( $response ) {
5432
					$is_site_publicly_accessible = true;
5433
				}
5434
			}
5435
5436
			Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
5437
		}
5438
5439
		if ( $is_site_publicly_accessible ) {
5440
			return;
5441
		}
5442
5443
		$module_slug = self::get_module_slug( $file );
5444
5445
		$privacy_checks = Jetpack::state( 'privacy_checks' );
5446
		if ( ! $privacy_checks ) {
5447
			$privacy_checks = $module_slug;
5448
		} else {
5449
			$privacy_checks .= ",$module_slug";
5450
		}
5451
5452
		Jetpack::state( 'privacy_checks', $privacy_checks );
5453
	}
5454
5455
	/**
5456
	 * Helper method for multicall XMLRPC.
5457
	 */
5458
	public static function xmlrpc_async_call() {
5459
		global $blog_id;
5460
		static $clients = array();
5461
5462
		$client_blog_id = is_multisite() ? $blog_id : 0;
5463
5464
		if ( ! isset( $clients[$client_blog_id] ) ) {
5465
			Jetpack::load_xml_rpc_client();
5466
			$clients[$client_blog_id] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => JETPACK_MASTER_USER, ) );
5467
			if ( function_exists( 'ignore_user_abort' ) ) {
5468
				ignore_user_abort( true );
5469
			}
5470
			add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
5471
		}
5472
5473
		$args = func_get_args();
5474
5475
		if ( ! empty( $args[0] ) ) {
5476
			call_user_func_array( array( $clients[$client_blog_id], 'addCall' ), $args );
5477
		} elseif ( is_multisite() ) {
5478
			foreach ( $clients as $client_blog_id => $client ) {
5479
				if ( ! $client_blog_id || empty( $client->calls ) ) {
5480
					continue;
5481
				}
5482
5483
				$switch_success = switch_to_blog( $client_blog_id, true );
5484
				if ( ! $switch_success ) {
5485
					continue;
5486
				}
5487
5488
				flush();
5489
				$client->query();
5490
5491
				restore_current_blog();
5492
			}
5493
		} else {
5494
			if ( isset( $clients[0] ) && ! empty( $clients[0]->calls ) ) {
5495
				flush();
5496
				$clients[0]->query();
5497
			}
5498
		}
5499
	}
5500
5501
	public static function staticize_subdomain( $url ) {
5502
5503
		// Extract hostname from URL
5504
		$host = parse_url( $url, PHP_URL_HOST );
5505
5506
		// Explode hostname on '.'
5507
		$exploded_host = explode( '.', $host );
5508
5509
		// Retrieve the name and TLD
5510
		if ( count( $exploded_host ) > 1 ) {
5511
			$name = $exploded_host[ count( $exploded_host ) - 2 ];
5512
			$tld = $exploded_host[ count( $exploded_host ) - 1 ];
5513
			// Rebuild domain excluding subdomains
5514
			$domain = $name . '.' . $tld;
5515
		} else {
5516
			$domain = $host;
5517
		}
5518
		// Array of Automattic domains
5519
		$domain_whitelist = array( 'wordpress.com', 'wp.com' );
5520
5521
		// Return $url if not an Automattic domain
5522
		if ( ! in_array( $domain, $domain_whitelist ) ) {
5523
			return $url;
5524
		}
5525
5526
		if ( is_ssl() ) {
5527
			return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
5528
		}
5529
5530
		srand( crc32( basename( $url ) ) );
5531
		$static_counter = rand( 0, 2 );
5532
		srand(); // this resets everything that relies on this, like array_rand() and shuffle()
5533
5534
		return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
5535
	}
5536
5537
/* JSON API Authorization */
5538
5539
	/**
5540
	 * Handles the login action for Authorizing the JSON API
5541
	 */
5542
	function login_form_json_api_authorization() {
5543
		$this->verify_json_api_authorization_request();
5544
5545
		add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
5546
5547
		add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
5548
		add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5549
		add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
5550
	}
5551
5552
	// Make sure the login form is POSTed to the signed URL so we can reverify the request
5553
	function post_login_form_to_signed_url( $url, $path, $scheme ) {
5554
		if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
5555
			return $url;
5556
		}
5557
5558
		$parsed_url = parse_url( $url );
5559
		$url = strtok( $url, '?' );
5560
		$url = "$url?{$_SERVER['QUERY_STRING']}";
5561
		if ( ! empty( $parsed_url['query'] ) )
5562
			$url .= "&{$parsed_url['query']}";
5563
5564
		return $url;
5565
	}
5566
5567
	// Make sure the POSTed request is handled by the same action
5568
	function preserve_action_in_login_form_for_json_api_authorization() {
5569
		echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
5570
		echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) . "' />\n";
5571
	}
5572
5573
	// If someone logs in to approve API access, store the Access Code in usermeta
5574
	function store_json_api_authorization_token( $user_login, $user ) {
5575
		add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5576
		add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
5577
		$token = wp_generate_password( 32, false );
5578
		update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5579
	}
5580
5581
	// Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
5582
	function allow_wpcom_public_api_domain( $domains ) {
5583
		$domains[] = 'public-api.wordpress.com';
5584
		return $domains;
5585
	}
5586
5587
	// Add the Access Code details to the public-api.wordpress.com redirect
5588
	function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5589
		return add_query_arg(
5590
			urlencode_deep(
5591
				array(
5592
					'jetpack-code'    => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5593
					'jetpack-user-id' => (int) $user->ID,
5594
					'jetpack-state'   => $this->json_api_authorization_request['state'],
5595
				)
5596
			),
5597
			$redirect_to
5598
		);
5599
	}
5600
5601
	// Verifies the request by checking the signature
5602
	function verify_json_api_authorization_request() {
5603
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
5604
5605
		$token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
5606
		if ( ! $token || empty( $token->secret ) ) {
5607
			wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.' , 'jetpack' ) );
5608
		}
5609
5610
		$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' );
5611
5612
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5613
5614
		if ( isset( $_POST['jetpack_json_api_original_query'] ) ) {
5615
			$signature = $jetpack_signature->sign_request( $_GET['token'], $_GET['timestamp'], $_GET['nonce'], '', 'GET', $_POST['jetpack_json_api_original_query'], null, true );
5616
		} else {
5617
			$signature = $jetpack_signature->sign_current_request( array( 'body' => null, 'method' => 'GET' ) );
5618
		}
5619
5620
		if ( ! $signature ) {
5621
			wp_die( $die_error );
5622
		} else if ( is_wp_error( $signature ) ) {
5623
			wp_die( $die_error );
5624
		} else if ( $signature !== $_GET['signature'] ) {
5625
			if ( is_ssl() ) {
5626
				// 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
5627
				$signature = $jetpack_signature->sign_current_request( array( 'scheme' => 'http', 'body' => null, 'method' => 'GET' ) );
5628
				if ( ! $signature || is_wp_error( $signature ) || $signature !== $_GET['signature'] ) {
5629
					wp_die( $die_error );
5630
				}
5631
			} else {
5632
				wp_die( $die_error );
5633
			}
5634
		}
5635
5636
		$timestamp = (int) $_GET['timestamp'];
5637
		$nonce     = stripslashes( (string) $_GET['nonce'] );
5638
5639
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
5640
			// De-nonce the nonce, at least for 5 minutes.
5641
			// 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)
5642
			$old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5643
			if ( $old_nonce_time < time() - 300 ) {
5644
				wp_die( __( 'The authorization process expired.  Please go back and try again.' , 'jetpack' ) );
5645
			}
5646
		}
5647
5648
		$data = json_decode( base64_decode( stripslashes( $_GET['data'] ) ) );
5649
		$data_filters = array(
5650
			'state'        => 'opaque',
5651
			'client_id'    => 'int',
5652
			'client_title' => 'string',
5653
			'client_image' => 'url',
5654
		);
5655
5656
		foreach ( $data_filters as $key => $sanitation ) {
5657
			if ( ! isset( $data->$key ) ) {
5658
				wp_die( $die_error );
5659
			}
5660
5661
			switch ( $sanitation ) {
5662
			case 'int' :
5663
				$this->json_api_authorization_request[$key] = (int) $data->$key;
5664
				break;
5665
			case 'opaque' :
5666
				$this->json_api_authorization_request[$key] = (string) $data->$key;
5667
				break;
5668
			case 'string' :
5669
				$this->json_api_authorization_request[$key] = wp_kses( (string) $data->$key, array() );
5670
				break;
5671
			case 'url' :
5672
				$this->json_api_authorization_request[$key] = esc_url_raw( (string) $data->$key );
5673
				break;
5674
			}
5675
		}
5676
5677
		if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5678
			wp_die( $die_error );
5679
		}
5680
	}
5681
5682
	function login_message_json_api_authorization( $message ) {
5683
		return '<p class="message">' . sprintf(
5684
			esc_html__( '%s wants to access your site&#8217;s data.  Log in to authorize that access.' , 'jetpack' ),
5685
			'<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5686
		) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5687
	}
5688
5689
	/**
5690
	 * Get $content_width, but with a <s>twist</s> filter.
5691
	 */
5692
	public static function get_content_width() {
5693
		$content_width = isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : false;
5694
		/**
5695
		 * Filter the Content Width value.
5696
		 *
5697
		 * @since 2.2.3
5698
		 *
5699
		 * @param string $content_width Content Width value.
5700
		 */
5701
		return apply_filters( 'jetpack_content_width', $content_width );
5702
	}
5703
5704
	/**
5705
	 * Centralize the function here until it gets added to core.
5706
	 *
5707
	 * @param int|string|object $id_or_email A user ID,  email address, or comment object
5708
	 * @param int $size Size of the avatar image
5709
	 * @param string $default URL to a default image to use if no avatar is available
5710
	 * @param bool $force_display Whether to force it to return an avatar even if show_avatars is disabled
5711
	 *
5712
	 * @return array First element is the URL, second is the class.
5713
	 */
5714
	public static function get_avatar_url( $id_or_email, $size = 96, $default = '', $force_display = false ) {
5715
		// Don't bother adding the __return_true filter if it's already there.
5716
		$has_filter = has_filter( 'pre_option_show_avatars', '__return_true' );
5717
5718
		if ( $force_display && ! $has_filter )
5719
			add_filter( 'pre_option_show_avatars', '__return_true' );
5720
5721
		$avatar = get_avatar( $id_or_email, $size, $default );
5722
5723
		if ( $force_display && ! $has_filter )
5724
			remove_filter( 'pre_option_show_avatars', '__return_true' );
5725
5726
		// If no data, fail out.
5727
		if ( is_wp_error( $avatar ) || ! $avatar )
5728
			return array( null, null );
5729
5730
		// Pull out the URL.  If it's not there, fail out.
5731
		if ( ! preg_match( '/src=["\']([^"\']+)["\']/', $avatar, $url_matches ) )
5732
			return array( null, null );
5733
		$url = wp_specialchars_decode( $url_matches[1], ENT_QUOTES );
5734
5735
		// Pull out the class, but it's not a big deal if it's missing.
5736
		$class = '';
5737
		if ( preg_match( '/class=["\']([^"\']+)["\']/', $avatar, $class_matches ) )
5738
			$class = wp_specialchars_decode( $class_matches[1], ENT_QUOTES );
5739
5740
		return array( $url, $class );
5741
	}
5742
5743
	/**
5744
	 * Pings the WordPress.com Mirror Site for the specified options.
5745
	 *
5746
	 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
5747
	 *
5748
	 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
5749
	 */
5750
	public function get_cloud_site_options( $option_names ) {
5751
		$option_names = array_filter( (array) $option_names, 'is_string' );
5752
5753
		Jetpack::load_xml_rpc_client();
5754
		$xml = new Jetpack_IXR_Client( array( 'user_id' => JETPACK_MASTER_USER, ) );
5755
		$xml->query( 'jetpack.fetchSiteOptions', $option_names );
5756
		if ( $xml->isError() ) {
5757
			return array(
5758
				'error_code' => $xml->getErrorCode(),
5759
				'error_msg'  => $xml->getErrorMessage(),
5760
			);
5761
		}
5762
		$cloud_site_options = $xml->getResponse();
5763
5764
		return $cloud_site_options;
5765
	}
5766
5767
	/**
5768
	 * Fetch the filtered array of options that we should compare to determine an identity crisis.
5769
	 *
5770
	 * @return array An array of options to check.
5771
	 */
5772
	public static function identity_crisis_options_to_check() {
5773
		$options = array(
5774
			'siteurl',
5775
			'home',
5776
		);
5777
		/**
5778
		 * Filter the options that we should compare to determine an identity crisis.
5779
		 *
5780
		 * @since 2.5.0
5781
		 *
5782
		 * @param array $options Array of options to compare to determine an identity crisis.
5783
		 */
5784
		return apply_filters( 'jetpack_identity_crisis_options_to_check', $options );
5785
	}
5786
5787
	/**
5788
	 * Checks to make sure that local options have the same values as remote options.  Will cache the results for up to 24 hours.
5789
	 *
5790
	 * @param bool $force_recheck Whether to ignore any cached transient and manually re-check.
5791
	 *
5792
	 * @return array An array of options that do not match.  If everything is good, it will evaluate to false.
5793
	 */
5794
	public static function check_identity_crisis( $force_recheck = false ) {
5795
		if ( ! Jetpack::is_active() || Jetpack::is_development_mode() )
5796
			return false;
5797
5798
		if ( $force_recheck || false === ( $errors = get_transient( 'jetpack_has_identity_crisis' ) ) ) {
5799
			$options_to_check = self::identity_crisis_options_to_check();
5800
			$cloud_options = Jetpack::init()->get_cloud_site_options( $options_to_check );
5801
			$errors        = array();
5802
5803
			foreach ( $cloud_options as $cloud_key => $cloud_value ) {
5804
5805
				// If it's not the same as the local value...
5806
				if ( $cloud_value !== get_option( $cloud_key ) ) {
5807
5808
					// Break out if we're getting errors.  We are going to check the error keys later when we alert.
5809
					if ( 'error_code' == $cloud_key ) {
5810
						$errors[ $cloud_key ] = $cloud_value;
5811
						break;
5812
					}
5813
5814
					$parsed_cloud_value = parse_url( $cloud_value );
5815
					// If the current options is an IP address
5816
					if ( filter_var( $parsed_cloud_value['host'], FILTER_VALIDATE_IP ) ) {
5817
						// Give the new value a Jetpack to fly in to the clouds
5818
						Jetpack::resolve_identity_crisis( $cloud_key );
5819
						continue;
5820
					}
5821
5822
					// And it's not been added to the whitelist...
5823
					if ( ! self::is_identity_crisis_value_whitelisted( $cloud_key, $cloud_value ) ) {
5824
						/*
5825
						 * This should be a temporary hack until a cleaner solution is found.
5826
						 *
5827
						 * The siteurl and home can be set to use http in General > Settings
5828
						 * however some constants can be defined that can force https in wp-admin
5829
						 * when this happens wpcom can confuse wporg with a fake identity
5830
						 * crisis with a mismatch of http vs https when it should be allowed.
5831
						 * we need to check that here.
5832
						 *
5833
						 * @see https://github.com/Automattic/jetpack/issues/1006
5834
						 */
5835
						if ( ( 'home' == $cloud_key || 'siteurl' == $cloud_key )
5836
							&& ( substr( $cloud_value, 0, 8 ) == "https://" )
5837
							&& Jetpack::init()->is_ssl_required_to_visit_site() ) {
5838
							// Ok, we found a mismatch of http and https because of wp-config, not an invalid url
5839
							continue;
5840
						}
5841
5842
5843
						// Then kick an error!
5844
						$errors[ $cloud_key ] = $cloud_value;
5845
					}
5846
				}
5847
			}
5848
		}
5849
5850
		/**
5851
		 * Filters the errors returned when checking for an Identity Crisis.
5852
		 *
5853
		 * @since 2.3.2
5854
		 *
5855
		 * @param array $errors Array of Identity Crisis errors.
5856
		 * @param bool $force_recheck Ignore any cached transient and manually re-check. Default to false.
5857
		 */
5858
		return apply_filters( 'jetpack_has_identity_crisis', $errors, $force_recheck );
5859
	}
5860
5861
	/*
5862
	 * Resolve ID crisis
5863
	 *
5864
	 * If the URL has changed, but the rest of the options are the same (i.e. blog/user tokens)
5865
	 * The user has the option to update the shadow site with the new URL before a new
5866
	 * token is created.
5867
	 *
5868
	 * @param $key : Which option to sync.  null defautlts to home and siteurl
5869
	 */
5870
	public static function resolve_identity_crisis( $key = null ) {
5871
		if ( $key ) {
5872
			$identity_options = array( $key );
5873
		} else {
5874
			$identity_options = self::identity_crisis_options_to_check();
5875
		}
5876
5877
		if ( is_array( $identity_options ) ) {
5878
			foreach( $identity_options as $identity_option ) {
5879
				Jetpack_Sync::sync_options( __FILE__, $identity_option );
5880
5881
				/**
5882
				 * Fires when a shadow site option is updated.
5883
				 * These options are updated via the Identity Crisis UI.
5884
				 * $identity_option is the option that gets updated.
5885
				 *
5886
				 * @since 3.7.0
5887
				 */
5888
				do_action( "update_option_{$identity_option}" );
5889
			}
5890
		}
5891
	}
5892
5893
	/*
5894
	 * Whitelist URL
5895
	 *
5896
	 * Ignore the URL differences between the blog and the shadow site.
5897
	 */
5898
	public static function whitelist_current_url() {
5899
		$options_to_check = Jetpack::identity_crisis_options_to_check();
5900
		$cloud_options = Jetpack::init()->get_cloud_site_options( $options_to_check );
5901
5902
		foreach ( $cloud_options as $cloud_key => $cloud_value ) {
5903
			Jetpack::whitelist_identity_crisis_value( $cloud_key, $cloud_value );
5904
		}
5905
	}
5906
5907
	/*
5908
	 * Ajax callbacks for ID crisis resolutions
5909
	 *
5910
	 * Things that could happen here:
5911
	 *  - site_migrated : Update the URL on the shadow blog to match new domain
5912
	 *  - whitelist     : Ignore the URL difference
5913
	 *  - default       : Error message
5914
	 */
5915
	public static function resolve_identity_crisis_ajax_callback() {
5916
		check_ajax_referer( 'resolve-identity-crisis', 'ajax-nonce' );
5917
5918
		switch ( $_POST[ 'crisis_resolution_action' ] ) {
5919
			case 'site_migrated':
5920
				Jetpack::resolve_identity_crisis();
5921
				echo 'resolved';
5922
				break;
5923
5924
			case 'whitelist':
5925
				Jetpack::whitelist_current_url();
5926
				echo 'whitelisted';
5927
				break;
5928
5929
			case 'reset_connection':
5930
				// Delete the options first so it doesn't get confused which site to disconnect dotcom-side
5931
				Jetpack_Options::delete_option(
5932
					array(
5933
						'register',
5934
						'blog_token',
5935
						'user_token',
5936
						'user_tokens',
5937
						'master_user',
5938
						'time_diff',
5939
						'fallback_no_verify_ssl_certs',
5940
						'id',
5941
					)
5942
				);
5943
				delete_transient( 'jetpack_has_identity_crisis' );
5944
5945
				echo 'reset-connection-success';
5946
				break;
5947
5948
			default:
5949
				echo 'missing action';
5950
				break;
5951
		}
5952
5953
		wp_die();
5954
	}
5955
5956
	/**
5957
	 * Adds a value to the whitelist for the specified key.
5958
	 *
5959
	 * @param string $key The option name that we're whitelisting the value for.
5960
	 * @param string $value The value that we're intending to add to the whitelist.
5961
	 *
5962
	 * @return bool Whether the value was added to the whitelist, or false if it was already there.
5963
	 */
5964
	public static function whitelist_identity_crisis_value( $key, $value ) {
5965
		if ( Jetpack::is_identity_crisis_value_whitelisted( $key, $value ) ) {
5966
			return false;
5967
		}
5968
5969
		$whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist', array() );
5970
		if ( empty( $whitelist[ $key ] ) || ! is_array( $whitelist[ $key ] ) ) {
5971
			$whitelist[ $key ] = array();
5972
		}
5973
		array_push( $whitelist[ $key ], $value );
5974
5975
		Jetpack_Options::update_option( 'identity_crisis_whitelist', $whitelist );
5976
		return true;
5977
	}
5978
5979
	/**
5980
	 * Checks whether a value is already whitelisted.
5981
	 *
5982
	 * @param string $key The option name that we're checking the value for.
5983
	 * @param string $value The value that we're curious to see if it's on the whitelist.
5984
	 *
5985
	 * @return bool Whether the value is whitelisted.
5986
	 */
5987
	public static function is_identity_crisis_value_whitelisted( $key, $value ) {
5988
		$whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist', array() );
5989
		if ( ! empty( $whitelist[ $key ] ) && is_array( $whitelist[ $key ] ) && in_array( $value, $whitelist[ $key ] ) ) {
5990
			return true;
5991
		}
5992
		return false;
5993
	}
5994
5995
	/**
5996
	 * Checks whether the home and siteurl specifically are whitelisted
5997
	 * Written so that we don't have re-check $key and $value params every time
5998
	 * we want to check if this site is whitelisted, for example in footer.php
5999
	 *
6000
	 * @return bool True = already whitelsisted False = not whitelisted
6001
	 */
6002
	public static function jetpack_is_staging_site() {
6003
		$current_whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist' );
6004
		if ( ! $current_whitelist ) {
6005
			return false;
6006
		}
6007
6008
		$options_to_check  = Jetpack::identity_crisis_options_to_check();
6009
		$cloud_options     = Jetpack::init()->get_cloud_site_options( $options_to_check );
6010
6011
		foreach ( $cloud_options as $cloud_key => $cloud_value ) {
6012
			if ( ! self::is_identity_crisis_value_whitelisted( $cloud_key, $cloud_value ) ) {
6013
				return false;
6014
			}
6015
		}
6016
		return true;
6017
	}
6018
6019
	public function identity_crisis_js( $nonce ) {
6020
?>
6021
<script>
6022
(function( $ ) {
6023
	var SECOND_IN_MS = 1000;
6024
6025
	function contactSupport( e ) {
6026
		e.preventDefault();
6027
		$( '.jp-id-crisis-question' ).hide();
6028
		$( '#jp-id-crisis-contact-support' ).show();
6029
	}
6030
6031
	function autodismissSuccessBanner() {
6032
		$( '.jp-identity-crisis' ).fadeOut(600); //.addClass( 'dismiss' );
6033
	}
6034
6035
	var data = { action: 'jetpack_resolve_identity_crisis', 'ajax-nonce': '<?php echo $nonce; ?>' };
6036
6037
	$( document ).ready(function() {
6038
6039
		// Site moved: Update the URL on the shadow blog
6040
		$( '.site-moved' ).click(function( e ) {
6041
			e.preventDefault();
6042
			data.crisis_resolution_action = 'site_migrated';
6043
			$( '#jp-id-crisis-question-1 .spinner' ).show();
6044
			$.post( ajaxurl, data, function() {
6045
				$( '.jp-id-crisis-question' ).hide();
6046
				$( '.banner-title' ).hide();
6047
				$( '#jp-id-crisis-success' ).show();
6048
				setTimeout( autodismissSuccessBanner, 6 * SECOND_IN_MS );
6049
			});
6050
6051
		});
6052
6053
		// URL hasn't changed, next question please.
6054
		$( '.site-not-moved' ).click(function( e ) {
6055
			e.preventDefault();
6056
			$( '.jp-id-crisis-question' ).hide();
6057
			$( '#jp-id-crisis-question-2' ).show();
6058
		});
6059
6060
		// Reset connection: two separate sites.
6061
		$( '.reset-connection' ).click(function( e ) {
6062
			data.crisis_resolution_action = 'reset_connection';
6063
			$.post( ajaxurl, data, function( response ) {
6064
				if ( 'reset-connection-success' === response ) {
6065
					window.location.replace( '<?php echo Jetpack::admin_url(); ?>' );
6066
				}
6067
			});
6068
		});
6069
6070
		// It's a dev environment.  Ignore.
6071
		$( '.is-dev-env' ).click(function( e ) {
6072
			data.crisis_resolution_action = 'whitelist';
6073
			$( '#jp-id-crisis-question-2 .spinner' ).show();
6074
			$.post( ajaxurl, data, function() {
6075
				$( '.jp-id-crisis-question' ).hide();
6076
				$( '.banner-title' ).hide();
6077
				$( '#jp-id-crisis-success' ).show();
6078
				setTimeout( autodismissSuccessBanner, 4 * SECOND_IN_MS );
6079
			});
6080
		});
6081
6082
		$( '.not-reconnecting' ).click(contactSupport);
6083
		$( '.not-staging-or-dev' ).click(contactSupport);
6084
	});
6085
})( jQuery );
6086
</script>
6087
<?php
6088
	}
6089
6090
	/**
6091
	 * Displays an admin_notice, alerting the user to an identity crisis.
6092
	 */
6093
	public function alert_identity_crisis() {
6094
		// @todo temporary killing of feature in 3.8.1 as it revealed a number of scenarios not foreseen.
6095
		if ( ! Jetpack::is_development_version() ) {
6096
			return;
6097
		}
6098
6099
		// @todo temporary copout for dealing with domain mapping
6100
		// @see https://github.com/Automattic/jetpack/issues/2702
6101
		if ( is_multisite() && defined( 'SUNRISE' ) && ! Jetpack::is_development_version() ) {
6102
			return;
6103
		}
6104
6105
		if ( ! current_user_can( 'jetpack_disconnect' ) ) {
6106
			return;
6107
		}
6108
6109
		if ( ! $errors = self::check_identity_crisis() ) {
6110
			return;
6111
		}
6112
6113
		// Only show on dashboard and jetpack pages
6114
		$screen = get_current_screen();
6115
		if ( 'dashboard' !== $screen->base && ! did_action( 'jetpack_notices' ) ) {
6116
			return;
6117
		}
6118
6119
		// Include the js!
6120
		$ajax_nonce = wp_create_nonce( 'resolve-identity-crisis' );
6121
		$this->identity_crisis_js( $ajax_nonce );
6122
6123
		// Include the CSS!
6124
		if ( ! wp_script_is( 'jetpack', 'done' ) ) {
6125
			$this->admin_banner_styles();
6126
		}
6127
6128
		if ( ! array_key_exists( 'error_code', $errors ) ) {
6129
			$key = 'siteurl';
6130
			if ( ! $errors[ $key ] ) {
6131
				$key = 'home';
6132
			}
6133
		} else {
6134
			$key = 'error_code';
6135
			// 401 is the only error we care about.  Any other errors should not trigger the alert.
6136
			if ( 401 !== $errors[ $key ] ) {
6137
				return;
6138
			}
6139
		}
6140
6141
		?>
6142
6143
		<style>
6144
			.jp-identity-crisis .jp-btn-group {
6145
					margin: 15px 0;
6146
				}
6147
			.jp-identity-crisis strong {
6148
					color: #518d2a;
6149
				}
6150
			.jp-identity-crisis.dismiss {
6151
				display: none;
6152
			}
6153
			.jp-identity-crisis .button {
6154
				margin-right: 4px;
6155
			}
6156
		</style>
6157
6158
		<div id="message" class="error jetpack-message jp-identity-crisis stay-visible">
6159
			<div class="service-mark"></div>
6160
			<div class="jp-id-banner__content">
6161
				<!-- <h3 class="banner-title"><?php _e( 'Something\'s not quite right with your Jetpack connection! Let\'s fix that.', 'jetpack' ); ?></h3> -->
6162
6163
				<div class="jp-id-crisis-question" id="jp-id-crisis-question-1">
6164
					<?php
6165
					// 401 means that this site has been disconnected from wpcom, but the remote site still thinks it's connected.
6166
					if ( 'error_code' == $key && '401' == $errors[ $key ] ) : ?>
6167
						<div class="banner-content">
6168
							<p><?php
6169
								/* translators: %s is a URL */
6170
								printf( __( 'Our records show that this site does not have a valid connection to WordPress.com. Please reset your connection to fix this. <a href="%s" target="_blank">What caused this?</a>', 'jetpack' ), 'https://jetpack.me/support/no-valid-wordpress-com-connection/' );
6171
							?></p>
6172
						</div>
6173
						<div class="jp-btn-group">
6174
							<a href="#" class="reset-connection"><?php _e( 'Reset the connection', 'jetpack' ); ?></a>
6175
							<span class="idc-separator">|</span>
6176
							<a href="<?php echo esc_url( wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=dismiss' ), 'jetpack-deactivate' ) ); ?>"><?php _e( 'Deactivate Jetpack', 'jetpack' ); ?></a>
6177
						</div>
6178
					<?php else : ?>
6179
							<div class="banner-content">
6180
							<p><?php printf( __( 'It looks like you may have changed your domain. Is <strong>%1$s</strong> still your site\'s domain, or have you updated it to <strong> %2$s </strong>?', 'jetpack' ), $errors[ $key ], (string) get_option( $key ) ); ?></p>
6181
							</div>
6182
						<div class="jp-btn-group">
6183
							<a href="#" class="regular site-moved"><?php printf( __( '%s is now my domain.', 'jetpack' ), $errors[ $key ] ); ?></a> <span class="idc-separator">|</span> <a href="#" class="site-not-moved" ><?php printf( __( '%s is still my domain.', 'jetpack' ), (string) get_option( $key ) ); ?></a>
6184
							<span class="spinner"></span>
6185
						</div>
6186
					<?php endif ; ?>
6187
				</div>
6188
6189
				<div class="jp-id-crisis-question" id="jp-id-crisis-question-2" style="display: none;">
6190
					<div class="banner-content">
6191
						<p><?php printf(
6192
							/* translators: %1$s, %2$s and %3$s are URLs */
6193
							__(
6194
								'Are <strong> %2$s </strong> and <strong> %1$s </strong> two completely separate websites? If so we should create a new connection, which will reset your followers and linked services. <a href="%3$s"><em>What does this mean?</em></a>',
6195
								'jetpack'
6196
							),
6197
							$errors[ $key ],
6198
							(string) get_option( $key ),
6199
							'https://jetpack.me/support/what-does-resetting-the-connection-mean/'
6200
						); ?></p>
6201
					</div>
6202
					<div class="jp-btn-group">
6203
						<a href="#" class="reset-connection"><?php _e( 'Reset the connection', 'jetpack' ); ?></a> <span class="idc-separator">|</span>
6204
						<a href="#" class="is-dev-env"><?php _e( 'This is a development environment', 'jetpack' ); ?></a> <span class="idc-separator">|</span>
6205
						<a href="https://jetpack.me/contact-support/" class="contact-support"><?php _e( 'Submit a support ticket', 'jetpack' ); ?></a>
6206
						<span class="spinner"></span>
6207
					</div>
6208
				</div>
6209
6210
				<div class="jp-id-crisis-success" id="jp-id-crisis-success" style="display: none;">
6211
					<h3 class="success-notice"><?php printf( __( 'Thanks for taking the time to sort things out. We&#039;ve updated our records accordingly!', 'jetpack' ) ); ?></h3>
6212
				</div>
6213
			</div>
6214
		</div>
6215
6216
		<?php
6217
	}
6218
6219
	/**
6220
	 * Maybe Use a .min.css stylesheet, maybe not.
6221
	 *
6222
	 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
6223
	 */
6224
	public static function maybe_min_asset( $url, $path, $plugin ) {
6225
		// Short out on things trying to find actual paths.
6226
		if ( ! $path || empty( $plugin ) ) {
6227
			return $url;
6228
		}
6229
6230
		// Strip out the abspath.
6231
		$base = dirname( plugin_basename( $plugin ) );
6232
6233
		// Short out on non-Jetpack assets.
6234
		if ( 'jetpack/' !== substr( $base, 0, 8 ) ) {
6235
			return $url;
6236
		}
6237
6238
		// File name parsing.
6239
		$file              = "{$base}/{$path}";
6240
		$full_path         = JETPACK__PLUGIN_DIR . substr( $file, 8 );
6241
		$file_name         = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
6242
		$file_name_parts_r = array_reverse( explode( '.', $file_name ) );
6243
		$extension         = array_shift( $file_name_parts_r );
6244
6245
		if ( in_array( strtolower( $extension ), array( 'css', 'js' ) ) ) {
6246
			// Already pointing at the minified version.
6247
			if ( 'min' === $file_name_parts_r[0] ) {
6248
				return $url;
6249
			}
6250
6251
			$min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
6252
			if ( file_exists( $min_full_path ) ) {
6253
				$url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
6254
			}
6255
		}
6256
6257
		return $url;
6258
	}
6259
6260
	/**
6261
	 * Maybe inlines a stylesheet.
6262
	 *
6263
	 * If you'd like to inline a stylesheet instead of printing a link to it,
6264
	 * wp_style_add_data( 'handle', 'jetpack-inline', true );
6265
	 *
6266
	 * Attached to `style_loader_tag` filter.
6267
	 *
6268
	 * @param string $tag The tag that would link to the external asset.
6269
	 * @param string $handle The registered handle of the script in question.
6270
	 *
6271
	 * @return string
6272
	 */
6273
	public static function maybe_inline_style( $tag, $handle ) {
6274
		global $wp_styles;
6275
		$item = $wp_styles->registered[ $handle ];
6276
6277
		if ( ! isset( $item->extra['jetpack-inline'] ) || ! $item->extra['jetpack-inline'] ) {
6278
			return $tag;
6279
		}
6280
6281
		if ( preg_match( '# href=\'([^\']+)\' #i', $tag, $matches ) ) {
6282
			$href = $matches[1];
6283
			// Strip off query string
6284
			if ( $pos = strpos( $href, '?' ) ) {
6285
				$href = substr( $href, 0, $pos );
6286
			}
6287
			// Strip off fragment
6288
			if ( $pos = strpos( $href, '#' ) ) {
6289
				$href = substr( $href, 0, $pos );
6290
			}
6291
		} else {
6292
			return $tag;
6293
		}
6294
6295
		$plugins_dir = plugin_dir_url( JETPACK__PLUGIN_FILE );
6296
		if ( $plugins_dir !== substr( $href, 0, strlen( $plugins_dir ) ) ) {
6297
			return $tag;
6298
		}
6299
6300
		// If this stylesheet has a RTL version, and the RTL version replaces normal...
6301
		if ( isset( $item->extra['rtl'] ) && 'replace' === $item->extra['rtl'] && is_rtl() ) {
6302
			// And this isn't the pass that actually deals with the RTL version...
6303
			if ( false === strpos( $tag, " id='$handle-rtl-css' " ) ) {
6304
				// Short out, as the RTL version will deal with it in a moment.
6305
				return $tag;
6306
			}
6307
		}
6308
6309
		$file = JETPACK__PLUGIN_DIR . substr( $href, strlen( $plugins_dir ) );
6310
		$css  = Jetpack::absolutize_css_urls( file_get_contents( $file ), $href );
6311
		if ( $css ) {
6312
			$tag = "<!-- Inline {$item->handle} -->\r\n";
6313
			if ( empty( $item->extra['after'] ) ) {
6314
				wp_add_inline_style( $handle, $css );
6315
			} else {
6316
				array_unshift( $item->extra['after'], $css );
6317
				wp_style_add_data( $handle, 'after', $item->extra['after'] );
6318
			}
6319
		}
6320
6321
		return $tag;
6322
	}
6323
6324
	/**
6325
	 * Loads a view file from the views
6326
	 *
6327
	 * Data passed in with the $data parameter will be available in the
6328
	 * template file as $data['value']
6329
	 *
6330
	 * @param string $template - Template file to load
6331
	 * @param array $data - Any data to pass along to the template
6332
	 * @return boolean - If template file was found
6333
	 **/
6334
	public function load_view( $template, $data = array() ) {
6335
		$views_dir = JETPACK__PLUGIN_DIR . 'views/';
6336
6337
		if( file_exists( $views_dir . $template ) ) {
6338
			require_once( $views_dir . $template );
6339
			return true;
6340
		}
6341
6342
		error_log( "Jetpack: Unable to find view file $views_dir$template" );
6343
		return false;
6344
	}
6345
6346
	/**
6347
	 * Sends a ping to the Jetpack servers to toggle on/off remote portions
6348
	 * required by some modules.
6349
	 *
6350
	 * @param string $module_slug
6351
	 */
6352
	public function toggle_module_on_wpcom( $module_slug ) {
6353
		Jetpack::init()->sync->register( 'noop' );
6354
6355
		if ( false !== strpos( current_filter(), 'jetpack_activate_module_' ) ) {
6356
			self::check_privacy( $module_slug );
6357
		}
6358
6359
	}
6360
6361
	/**
6362
	 * Throws warnings for deprecated hooks to be removed from Jetpack
6363
	 */
6364
	public function deprecated_hooks() {
6365
		global $wp_filter;
6366
6367
		/*
6368
		 * Format:
6369
		 * deprecated_filter_name => replacement_name
6370
		 *
6371
		 * If there is no replacement us null for replacement_name
6372
		 */
6373
		$deprecated_list = array(
6374
			'jetpack_bail_on_shortcode' => 'jetpack_shortcodes_to_include',
6375
			'wpl_sharing_2014_1'        => null,
6376
		);
6377
6378
		// This is a silly loop depth. Better way?
6379
		foreach( $deprecated_list AS $hook => $hook_alt ) {
6380
			if( isset( $wp_filter[ $hook ] ) && is_array( $wp_filter[ $hook ] ) ) {
6381
				foreach( $wp_filter[$hook] AS $func => $values ) {
6382
					foreach( $values AS $hooked ) {
6383
						_deprecated_function( $hook . ' used for ' . $hooked['function'], null, $hook_alt );
6384
					}
6385
				}
6386
			}
6387
		}
6388
	}
6389
6390
	/**
6391
	 * Converts any url in a stylesheet, to the correct absolute url.
6392
	 *
6393
	 * Considerations:
6394
	 *  - Normal, relative URLs     `feh.png`
6395
	 *  - Data URLs                 `data:image/gif;base64,eh129ehiuehjdhsa==`
6396
	 *  - Schema-agnostic URLs      `//domain.com/feh.png`
6397
	 *  - Absolute URLs             `http://domain.com/feh.png`
6398
	 *  - Domain root relative URLs `/feh.png`
6399
	 *
6400
	 * @param $css string: The raw CSS -- should be read in directly from the file.
6401
	 * @param $css_file_url : The URL that the file can be accessed at, for calculating paths from.
6402
	 *
6403
	 * @return mixed|string
6404
	 */
6405
	public static function absolutize_css_urls( $css, $css_file_url ) {
6406
		$pattern = '#url\((?P<path>[^)]*)\)#i';
6407
		$css_dir = dirname( $css_file_url );
6408
		$p       = parse_url( $css_dir );
6409
		$domain  = sprintf(
6410
					'%1$s//%2$s%3$s%4$s',
6411
					isset( $p['scheme'] )           ? "{$p['scheme']}:" : '',
6412
					isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
6413
					$p['host'],
6414
					isset( $p['port'] )             ? ":{$p['port']}" : ''
6415
				);
6416
6417
		if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
6418
			$find = $replace = array();
6419
			foreach ( $matches as $match ) {
6420
				$url = trim( $match['path'], "'\" \t" );
6421
6422
				// If this is a data url, we don't want to mess with it.
6423
				if ( 'data:' === substr( $url, 0, 5 ) ) {
6424
					continue;
6425
				}
6426
6427
				// If this is an absolute or protocol-agnostic url,
6428
				// we don't want to mess with it.
6429
				if ( preg_match( '#^(https?:)?//#i', $url ) ) {
6430
					continue;
6431
				}
6432
6433
				switch ( substr( $url, 0, 1 ) ) {
6434
					case '/':
6435
						$absolute = $domain . $url;
6436
						break;
6437
					default:
6438
						$absolute = $css_dir . '/' . $url;
6439
				}
6440
6441
				$find[]    = $match[0];
6442
				$replace[] = sprintf( 'url("%s")', $absolute );
6443
			}
6444
			$css = str_replace( $find, $replace, $css );
6445
		}
6446
6447
		return $css;
6448
	}
6449
6450
	/**
6451
	 * This method checks to see if SSL is required by the site in
6452
	 * order to visit it in some way other than only setting the
6453
	 * https value in the home or siteurl values.
6454
	 *
6455
	 * @since 3.2
6456
	 * @return boolean
6457
	 **/
6458
	private function is_ssl_required_to_visit_site() {
6459
		global $wp_version;
6460
		$ssl = is_ssl();
6461
6462
		if ( version_compare( $wp_version, '4.4-alpha', '<=' ) && force_ssl_login() ) { // force_ssl_login deprecated WP 4.4.
6463
			$ssl = true;
6464
		} else if ( force_ssl_admin() ) {
6465
			$ssl = true;
6466
		}
6467
		return $ssl;
6468
	}
6469
6470
	/**
6471
	 * This methods removes all of the registered css files on the frontend
6472
	 * from Jetpack in favor of using a single file. In effect "imploding"
6473
	 * all the files into one file.
6474
	 *
6475
	 * Pros:
6476
	 * - Uses only ONE css asset connection instead of 15
6477
	 * - Saves a minimum of 56k
6478
	 * - Reduces server load
6479
	 * - Reduces time to first painted byte
6480
	 *
6481
	 * Cons:
6482
	 * - Loads css for ALL modules. However all selectors are prefixed so it
6483
	 *		should not cause any issues with themes.
6484
	 * - Plugins/themes dequeuing styles no longer do anything. See
6485
	 *		jetpack_implode_frontend_css filter for a workaround
6486
	 *
6487
	 * For some situations developers may wish to disable css imploding and
6488
	 * instead operate in legacy mode where each file loads seperately and
6489
	 * can be edited individually or dequeued. This can be accomplished with
6490
	 * the following line:
6491
	 *
6492
	 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6493
	 *
6494
	 * @since 3.2
6495
	 **/
6496
	public function implode_frontend_css( $travis_test = false ) {
6497
		$do_implode = true;
6498
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6499
			$do_implode = false;
6500
		}
6501
6502
		/**
6503
		 * Allow CSS to be concatenated into a single jetpack.css file.
6504
		 *
6505
		 * @since 3.2.0
6506
		 *
6507
		 * @param bool $do_implode Should CSS be concatenated? Default to true.
6508
		 */
6509
		$do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6510
6511
		// Do not use the imploded file when default behaviour was altered through the filter
6512
		if ( ! $do_implode ) {
6513
			return;
6514
		}
6515
6516
		// We do not want to use the imploded file in dev mode, or if not connected
6517
		if ( Jetpack::is_development_mode() || ! self::is_active() ) {
6518
			if ( ! $travis_test ) {
6519
				return;
6520
			}
6521
		}
6522
6523
		// Do not use the imploded file if sharing css was dequeued via the sharing settings screen
6524
		if ( get_option( 'sharedaddy_disable_resources' ) ) {
6525
			return;
6526
		}
6527
6528
		/*
6529
		 * Now we assume Jetpack is connected and able to serve the single
6530
		 * file.
6531
		 *
6532
		 * In the future there will be a check here to serve the file locally
6533
		 * or potentially from the Jetpack CDN
6534
		 *
6535
		 * For now:
6536
		 * - Enqueue a single imploded css file
6537
		 * - Zero out the style_loader_tag for the bundled ones
6538
		 * - Be happy, drink scotch
6539
		 */
6540
6541
		add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6542
6543
		$version = Jetpack::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6544
6545
		wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6546
		wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6547
	}
6548
6549
	function concat_remove_style_loader_tag( $tag, $handle ) {
6550
		if ( in_array( $handle, $this->concatenated_style_handles ) ) {
6551
			$tag = '';
6552
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6553
				$tag = "<!-- `" . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6554
			}
6555
		}
6556
6557
		return $tag;
6558
	}
6559
6560
	/*
6561
	 * Check the heartbeat data
6562
	 *
6563
	 * Organizes the heartbeat data by severity.  For example, if the site
6564
	 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
6565
	 *
6566
	 * Data will be added to "caution" array, if it either:
6567
	 *  - Out of date Jetpack version
6568
	 *  - Out of date WP version
6569
	 *  - Out of date PHP version
6570
	 *
6571
	 * $return array $filtered_data
6572
	 */
6573
	public static function jetpack_check_heartbeat_data() {
6574
		$raw_data = Jetpack_Heartbeat::generate_stats_array();
6575
6576
		$good    = array();
6577
		$caution = array();
6578
		$bad     = array();
6579
6580
		foreach ( $raw_data as $stat => $value ) {
6581
6582
			// Check jetpack version
6583
			if ( 'version' == $stat ) {
6584
				if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
6585
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__VERSION;
6586
					continue;
6587
				}
6588
			}
6589
6590
			// Check WP version
6591
			if ( 'wp-version' == $stat ) {
6592
				if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
6593
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__MINIMUM_WP_VERSION;
6594
					continue;
6595
				}
6596
			}
6597
6598
			// Check PHP version
6599
			if ( 'php-version' == $stat ) {
6600
				if ( version_compare( PHP_VERSION, '5.2.4', '<' ) ) {
6601
					$caution[ $stat ] = $value . " - min supported is 5.2.4";
6602
					continue;
6603
				}
6604
			}
6605
6606
			// Check ID crisis
6607
			if ( 'identitycrisis' == $stat ) {
6608
				if ( 'yes' == $value ) {
6609
					$bad[ $stat ] = $value;
6610
					continue;
6611
				}
6612
			}
6613
6614
			// The rest are good :)
6615
			$good[ $stat ] = $value;
6616
		}
6617
6618
		$filtered_data = array(
6619
			'good'    => $good,
6620
			'caution' => $caution,
6621
			'bad'     => $bad
6622
		);
6623
6624
		return $filtered_data;
6625
	}
6626
6627
6628
	/*
6629
	 * This method is used to organize all options that can be reset
6630
	 * without disconnecting Jetpack.
6631
	 *
6632
	 * It is used in class.jetpack-cli.php to reset options
6633
	 *
6634
	 * @return array of options to delete.
6635
	 */
6636
	public static function get_jetpack_options_for_reset() {
6637
		$jetpack_options            = Jetpack_Options::get_option_names();
6638
		$jetpack_options_non_compat = Jetpack_Options::get_option_names( 'non_compact' );
6639
		$jetpack_options_private    = Jetpack_Options::get_option_names( 'private' );
6640
6641
		$all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
6642
6643
		// A manual build of the wp options
6644
		$wp_options = array(
6645
			'sharing-options',
6646
			'disabled_likes',
6647
			'disabled_reblogs',
6648
			'jetpack_comments_likes_enabled',
6649
			'wp_mobile_excerpt',
6650
			'wp_mobile_featured_images',
6651
			'wp_mobile_app_promos',
6652
			'stats_options',
6653
			'stats_dashboard_widget',
6654
			'safecss_preview_rev',
6655
			'safecss_rev',
6656
			'safecss_revision_migrated',
6657
			'nova_menu_order',
6658
			'jetpack_portfolio',
6659
			'jetpack_portfolio_posts_per_page',
6660
			'jetpack_testimonial',
6661
			'jetpack_testimonial_posts_per_page',
6662
			'wp_mobile_custom_css',
6663
			'sharedaddy_disable_resources',
6664
			'sharing-options',
6665
			'sharing-services',
6666
			'site_icon_temp_data',
6667
			'featured-content',
6668
			'site_logo',
6669
		);
6670
6671
		// Flag some Jetpack options as unsafe
6672
		$unsafe_options = array(
6673
			'id',                           // (int)    The Client ID/WP.com Blog ID of this site.
6674
			'master_user',                  // (int)    The local User ID of the user who connected this site to jetpack.wordpress.com.
6675
			'version',                      // (string) Used during upgrade procedure to auto-activate new modules. version:time
6676
			'jumpstart',                    // (string) A flag for whether or not to show the Jump Start.  Accepts: new_connection, jumpstart_activated, jetpack_action_taken, jumpstart_dismissed.
6677
6678
			// non_compact
6679
			'activated',
6680
6681
			// private
6682
			'register',
6683
			'blog_token',                  // (string) The Client Secret/Blog Token of this site.
6684
			'user_token',                  // (string) The User Token of this site. (deprecated)
6685
			'user_tokens'
6686
		);
6687
6688
		// Remove the unsafe Jetpack options
6689
		foreach ( $unsafe_options as $unsafe_option ) {
6690
			if ( false !== ( $key = array_search( $unsafe_option, $all_jp_options ) ) ) {
6691
				unset( $all_jp_options[ $key ] );
6692
			}
6693
		}
6694
6695
		$options = array(
6696
			'jp_options' => $all_jp_options,
6697
			'wp_options' => $wp_options
6698
		);
6699
6700
		return $options;
6701
	}
6702
6703
	/*
6704
	 * Check if an option of a Jetpack module has been updated.
6705
	 *
6706
	 * If any module option has been updated before Jump Start has been dismissed,
6707
	 * update the 'jumpstart' option so we can hide Jump Start.
6708
	 */
6709
	public static function jumpstart_has_updated_module_option( $option_name = '' ) {
6710
		// Bail if Jump Start has already been dismissed
6711
		if ( 'new_connection' !== Jetpack::get_option( 'jumpstart' ) ) {
6712
			return false;
6713
		}
6714
6715
		$jetpack = Jetpack::init();
6716
6717
6718
		// Manual build of module options
6719
		$option_names = self::get_jetpack_options_for_reset();
6720
6721
		if ( in_array( $option_name, $option_names['wp_options'] ) ) {
6722
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
6723
6724
			//Jump start is being dismissed send data to MC Stats
6725
			$jetpack->stat( 'jumpstart', 'manual,'.$option_name );
6726
6727
			$jetpack->do_stats( 'server_side' );
6728
		}
6729
6730
	}
6731
6732
	/*
6733
	 * Strip http:// or https:// from a url, replaces forward slash with ::,
6734
	 * so we can bring them directly to their site in calypso.
6735
	 *
6736
	 * @param string | url
6737
	 * @return string | url without the guff
6738
	 */
6739
	public static function build_raw_urls( $url ) {
6740
		$strip_http = '/.*?:\/\//i';
6741
		$url = preg_replace( $strip_http, '', $url  );
6742
		$url = str_replace( '/', '::', $url );
6743
		return $url;
6744
	}
6745
6746
	/**
6747
	 * Stores and prints out domains to prefetch for page speed optimization.
6748
	 *
6749
	 * @param mixed $new_urls
6750
	 */
6751
	public static function dns_prefetch( $new_urls = null ) {
6752
		static $prefetch_urls = array();
6753
		if ( empty( $new_urls ) && ! empty( $prefetch_urls ) ) {
6754
			echo "\r\n";
6755
			foreach ( $prefetch_urls as $this_prefetch_url ) {
6756
				printf( "<link rel='dns-prefetch' href='%s'>\r\n", esc_attr( $this_prefetch_url ) );
6757
			}
6758
		} elseif ( ! empty( $new_urls ) ) {
6759
			if ( ! has_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) ) ) {
6760
				add_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) );
6761
			}
6762
			foreach ( (array) $new_urls as $this_new_url ) {
6763
				$prefetch_urls[] = strtolower( untrailingslashit( preg_replace( '#^https?://#i', '//', $this_new_url ) ) );
6764
			}
6765
			$prefetch_urls = array_unique( $prefetch_urls );
6766
		}
6767
	}
6768
6769
	public function wp_dashboard_setup() {
6770
		if ( self::is_active() ) {
6771
			add_action( 'jetpack_dashboard_widget', array( __CLASS__, 'dashboard_widget_footer' ), 999 );
6772
			$widget_title = __( 'Site Stats', 'jetpack' );
6773
		} elseif ( ! self::is_development_mode() && current_user_can( 'jetpack_connect' ) ) {
6774
			add_action( 'jetpack_dashboard_widget', array( $this, 'dashboard_widget_connect_to_wpcom' ) );
6775
			$widget_title = __( 'Please Connect Jetpack', 'jetpack' );
6776
		}
6777
6778
		if ( has_action( 'jetpack_dashboard_widget' ) ) {
6779
			wp_add_dashboard_widget(
6780
				'jetpack_summary_widget',
6781
				$widget_title,
6782
				array( __CLASS__, 'dashboard_widget' )
6783
			);
6784
			wp_enqueue_style( 'jetpack-dashboard-widget', plugins_url( 'css/dashboard-widget.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
6785
6786
			// If we're inactive and not in development mode, sort our box to the top.
6787
			if ( ! self::is_active() && ! self::is_development_mode() ) {
6788
				global $wp_meta_boxes;
6789
6790
				$dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
6791
				$ours      = array( 'jetpack_summary_widget' => $dashboard['jetpack_summary_widget'] );
6792
6793
				$wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard );
6794
			}
6795
		}
6796
	}
6797
6798
	/**
6799
	 * @param mixed $result Value for the user's option
6800
	 * @return mixed
6801
	 */
6802
	function get_user_option_meta_box_order_dashboard( $sorted ) {
6803
		if ( ! is_array( $sorted ) ) {
6804
			return $sorted;
6805
		}
6806
6807
		foreach ( $sorted as $box_context => $ids ) {
6808
			if ( false === strpos( $ids, 'dashboard_stats' ) ) {
6809
				// If the old id isn't anywhere in the ids, don't bother exploding and fail out.
6810
				continue;
6811
			}
6812
6813
			$ids_array = explode( ',', $ids );
6814
			$key = array_search( 'dashboard_stats', $ids_array );
6815
6816
			if ( false !== $key ) {
6817
				// If we've found that exact value in the option (and not `google_dashboard_stats` for example)
6818
				$ids_array[ $key ] = 'jetpack_summary_widget';
6819
				$sorted[ $box_context ] = implode( ',', $ids_array );
6820
				// We've found it, stop searching, and just return.
6821
				break;
6822
			}
6823
		}
6824
6825
		return $sorted;
6826
	}
6827
6828
	public static function dashboard_widget() {
6829
		/**
6830
		 * Fires when the dashboard is loaded.
6831
		 *
6832
		 * @since 3.4.0
6833
		 */
6834
		do_action( 'jetpack_dashboard_widget' );
6835
	}
6836
6837
	public static function dashboard_widget_footer() {
6838
		?>
6839
		<footer>
6840
6841
		<div class="protect">
6842
			<?php if ( Jetpack::is_module_active( 'protect' ) ) : ?>
6843
				<h3><?php echo number_format_i18n( get_site_option( 'jetpack_protect_blocked_attempts', 0 ) ); ?></h3>
6844
				<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>
6845
			<?php elseif ( current_user_can( 'jetpack_activate_modules' ) && ! self::is_development_mode() ) : ?>
6846
				<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' ); ?>">
6847
					<?php esc_html_e( 'Activate Protect', 'jetpack' ); ?>
6848
				</a>
6849
			<?php else : ?>
6850
				<?php esc_html_e( 'Protect is inactive.', 'jetpack' ); ?>
6851
			<?php endif; ?>
6852
		</div>
6853
6854
		<div class="akismet">
6855
			<?php if ( is_plugin_active( 'akismet/akismet.php' ) ) : ?>
6856
				<h3><?php echo number_format_i18n( get_option( 'akismet_spam_count', 0 ) ); ?></h3>
6857
				<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>
6858 View Code Duplication
			<?php elseif ( current_user_can( 'activate_plugins' ) && ! is_wp_error( validate_plugin( 'akismet/akismet.php' ) ) ) : ?>
6859
				<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">
6860
					<?php esc_html_e( 'Activate Akismet', 'jetpack' ); ?>
6861
				</a>
6862
			<?php else : ?>
6863
				<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>
6864
			<?php endif; ?>
6865
		</div>
6866
6867
6868 View Code Duplication
		<?php if ( ! current_user_can( 'edit_posts' ) && self::is_user_connected() ) : ?>
6869
			<div style="width: 100%; text-align: center; padding-top: 20px; clear: both;"><a class="button" title="<?php esc_attr_e( 'Unlink your account from WordPress.com', 'jetpack' ); ?>" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'unlink', 'redirect' => 'sub-unlink' ), admin_url( 'index.php' ) ), 'jetpack-unlink' ) ); ?>"><?php esc_html_e( 'Unlink your account from WordPress.com', 'jetpack' ); ?></a></div>
6870
		<?php endif; ?>
6871
6872
		</footer>
6873
		<?php
6874
	}
6875
6876
	public function dashboard_widget_connect_to_wpcom() {
6877
		if ( Jetpack::is_active() || Jetpack::is_development_mode() || ! current_user_can( 'jetpack_connect' ) ) {
6878
			return;
6879
		}
6880
		?>
6881
		<div class="wpcom-connect">
6882
			<div class="jp-emblem">
6883
			<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">
6884
				<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"/>
6885
			</svg>
6886
			</div>
6887
			<h3><?php esc_html_e( 'Please Connect Jetpack', 'jetpack' ); ?></h3>
6888
			<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>
6889
6890
			<div class="actions">
6891
				<a href="<?php echo $this->build_connect_url() ?>" class="button button-primary">
6892
					<?php esc_html_e( 'Connect Jetpack', 'jetpack' ); ?>
6893
				</a>
6894
			</div>
6895
		</div>
6896
		<?php
6897
	}
6898
6899
	/*
6900
	 * A graceful transition to using Core's site icon.
6901
	 *
6902
	 * All of the hard work has already been done with the image
6903
	 * in all_done_page(). All that needs to be done now is update
6904
	 * the option and display proper messaging.
6905
	 *
6906
	 * @todo remove when WP 4.3 is minimum
6907
	 *
6908
	 * @since 3.6.1
6909
	 *
6910
	 * @return bool false = Core's icon not available || true = Core's icon is available
6911
	 */
6912
	public static function jetpack_site_icon_available_in_core() {
6913
		global $wp_version;
6914
		$core_icon_available = function_exists( 'has_site_icon' ) && version_compare( $wp_version, '4.3-beta' ) >= 0;
6915
6916
		if ( ! $core_icon_available ) {
6917
			return false;
6918
		}
6919
6920
		// No need for Jetpack's site icon anymore if core's is already set
6921
		if ( has_site_icon() ) {
6922
			if ( Jetpack::is_module_active( 'site-icon' ) ) {
6923
				Jetpack::log( 'deactivate', 'site-icon' );
6924
				Jetpack::deactivate_module( 'site-icon' );
6925
			}
6926
			return true;
6927
		}
6928
6929
		// Transfer Jetpack's site icon to use core.
6930
		$site_icon_id = Jetpack::get_option( 'site_icon_id' );
6931
		if ( $site_icon_id ) {
6932
			// Update core's site icon
6933
			update_option( 'site_icon', $site_icon_id );
6934
6935
			// Delete Jetpack's icon option. We still want the blavatar and attached data though.
6936
			delete_option( 'site_icon_id' );
6937
		}
6938
6939
		// No need for Jetpack's site icon anymore
6940
		if ( Jetpack::is_module_active( 'site-icon' ) ) {
6941
			Jetpack::log( 'deactivate', 'site-icon' );
6942
			Jetpack::deactivate_module( 'site-icon' );
6943
		}
6944
6945
		return true;
6946
	}
6947
6948
}
6949