Completed
Push — fix/notices_after_module_remov... ( 574316...30a40c )
by
unknown
40:56 queued 28:52
created

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