Completed
Push — fix/early-require-error ( 10ec1f )
by
unknown
11:18
created

class.jetpack.php (18 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
		if ( Jetpack::is_active() ) {
331
			list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
332
			if ( JETPACK__VERSION != $version ) {
333
334
				// Check which active modules actually exist and remove others from active_modules list
335
				$unfiltered_modules = Jetpack::get_active_modules();
336
				$modules = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) );
337
				if ( array_diff( $unfiltered_modules, $modules ) ) {
338
					Jetpack_Options::update_option( 'active_modules', $modules );
339
				}
340
341
				add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
342
				/**
343
				 * Fires when synchronizing all registered options and constants.
344
				 *
345
				 * @since 3.3.0
346
				 */
347
				do_action( 'jetpack_sync_all_registered_options' );
348
			}
349
		}
350
	}
351
352
	static function activate_manage( ) {
353
354
		if ( did_action( 'init' ) || current_filter() == 'init' ) {
355
			self::activate_module( 'manage', false, false );
356
		} else if ( !  has_action( 'init' , array( __CLASS__, 'activate_manage' ) ) ) {
357
			add_action( 'init', array( __CLASS__, 'activate_manage' ) );
358
		}
359
360
	}
361
362
	/**
363
	 * Constructor.  Initializes WordPress hooks
364
	 */
365
	private function __construct() {
366
		/*
367
		 * Check for and alert any deprecated hooks
368
		 */
369
		add_action( 'init', array( $this, 'deprecated_hooks' ) );
370
371
		/*
372
		 * Do things that should run even in the network admin
373
		 * here, before we potentially fail out.
374
		 */
375
376
		/**
377
		 * We need sync object even in Multisite mode
378
		 */
379
		$this->sync = new Jetpack_Sync;
380
381
		/**
382
		 * Trigger a wp_version sync when updating WP versions
383
		 **/
384
		add_action( 'upgrader_process_complete', array( 'Jetpack', 'update_get_wp_version' ), 10, 2 );
385
		$this->sync->mock_option( 'wp_version', array( 'Jetpack', 'get_wp_version' ) );
386
387
		add_action( 'init', array( $this, 'sync_update_data') );
388
		add_action( 'init', array( $this, 'sync_theme_data' ) );
389
390
		/*
391
		 * Load things that should only be in Network Admin.
392
		 *
393
		 * For now blow away everything else until a more full
394
		 * understanding of what is needed at the network level is
395
		 * available
396
		 */
397
		if( is_multisite() ) {
398
			Jetpack_Network::init();
399
400
			// Only sync this info if we are on a multi site
401
			// @since  3.7
402
			$this->sync->mock_option( 'network_name', array( 'Jetpack', 'network_name' ) );
403
			$this->sync->mock_option( 'network_allow_new_registrations', array( 'Jetpack', 'network_allow_new_registrations' ) );
404
			$this->sync->mock_option( 'network_add_new_users', array( 'Jetpack', 'network_add_new_users' ) );
405
			$this->sync->mock_option( 'network_site_upload_space', array( 'Jetpack', 'network_site_upload_space' ) );
406
			$this->sync->mock_option( 'network_upload_file_types', array( 'Jetpack', 'network_upload_file_types' ) );
407
			$this->sync->mock_option( 'network_enable_administration_menus', array( 'Jetpack', 'network_enable_administration_menus' ) );
408
409
			if( is_network_admin() ) {
410
				// Sync network site data if it is updated or not.
411
				add_action( 'update_wpmu_options', array( $this, 'update_jetpack_network_settings' ) );
412
				return; // End here to prevent single site actions from firing
413
			}
414
		}
415
416
417
		$theme_slug = get_option( 'stylesheet' );
418
419
420
		// Modules should do Jetpack_Sync::sync_options( __FILE__, $option, ... ); instead
421
		// We access the "internal" method here only because the Jetpack object isn't instantiated yet
422
		$this->sync->options(
423
			JETPACK__PLUGIN_DIR . 'jetpack.php',
424
			'home',
425
			'siteurl',
426
			'blogname',
427
			'gmt_offset',
428
			'timezone_string',
429
			'security_report',
430
			'stylesheet',
431
			"theme_mods_{$theme_slug}",
432
			'jetpack_sync_non_public_post_stati',
433
			'jetpack_options',
434
			'site_icon', // (int) - ID of core's Site Icon attachment ID
435
			'default_post_format',
436
			'default_category',
437
			'large_size_w',
438
			'large_size_h',
439
			'thumbnail_size_w',
440
			'thumbnail_size_h',
441
			'medium_size_w',
442
			'medium_size_h',
443
			'thumbnail_crop',
444
			'image_default_link_type'
445
		);
446
447
		foreach( Jetpack_Options::get_option_names( 'non-compact' ) as $option ) {
448
			$this->sync->options( __FILE__, 'jetpack_' . $option );
449
		}
450
451
		/**
452
		 * Sometimes you want to sync data to .com without adding options to .org sites.
453
		 * The mock option allows you to do just that.
454
		 */
455
		$this->sync->mock_option( 'is_main_network',   array( $this, 'is_main_network_option' ) );
456
		$this->sync->mock_option( 'is_multi_site', array( $this, 'is_multisite' ) );
457
		$this->sync->mock_option( 'main_network_site', array( $this, 'jetpack_main_network_site_option' ) );
458
		$this->sync->mock_option( 'single_user_site', array( 'Jetpack', 'is_single_user_site' ) );
459
		$this->sync->mock_option( 'stat_data', array( $this, 'get_stat_data' ) );
460
461
		$this->sync->mock_option( 'has_file_system_write_access', array( 'Jetpack', 'file_system_write_access' ) );
462
		$this->sync->mock_option( 'is_version_controlled', array( 'Jetpack', 'is_version_controlled' ) );
463
		$this->sync->mock_option( 'max_upload_size', 'wp_max_upload_size' );
464
		$this->sync->mock_option( 'content_width', array( 'Jetpack', 'get_content_width' ) );
465
466
		/**
467
		 * Trigger an update to the main_network_site when we update the blogname of a site.
468
		 *
469
		 */
470
		add_action( 'update_option_siteurl', array( $this, 'update_jetpack_main_network_site_option' ) );
471
472
		add_action( 'update_option', array( $this, 'log_settings_change' ), 10, 3 );
473
474
		// Update the settings everytime the we register a new user to the site or we delete a user.
475
		add_action( 'user_register', array( $this, 'is_single_user_site_invalidate' ) );
476
		add_action( 'deleted_user', array( $this, 'is_single_user_site_invalidate' ) );
477
478
		// Unlink user before deleting the user from .com
479
		add_action( 'deleted_user', array( $this, 'unlink_user' ), 10, 1 );
480
		add_action( 'remove_user_from_blog', array( $this, 'unlink_user' ), 10, 1 );
481
482
		if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && isset( $_GET['for'] ) && 'jetpack' == $_GET['for'] ) {
483
			@ini_set( 'display_errors', false ); // Display errors can cause the XML to be not well formed.
484
485
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
486
			$this->xmlrpc_server = new Jetpack_XMLRPC_Server();
487
488
			$this->require_jetpack_authentication();
489
490
			if ( Jetpack::is_active() ) {
491
				// Hack to preserve $HTTP_RAW_POST_DATA
492
				add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
493
494
				$signed = $this->verify_xml_rpc_signature();
495
				if ( $signed && ! is_wp_error( $signed ) ) {
496
					// The actual API methods.
497
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'xmlrpc_methods' ) );
498
				} else {
499
					// The jetpack.authorize method should be available for unauthenticated users on a site with an
500
					// active Jetpack connection, so that additional users can link their account.
501
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'authorize_xmlrpc_methods' ) );
502
				}
503
			} else {
504
				// The bootstrap API methods.
505
				add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'bootstrap_xmlrpc_methods' ) );
506
			}
507
508
			// Now that no one can authenticate, and we're whitelisting all XML-RPC methods, force enable_xmlrpc on.
509
			add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
510
		} elseif ( is_admin() && isset( $_POST['action'] ) && 'jetpack_upload_file' == $_POST['action'] ) {
511
			$this->require_jetpack_authentication();
512
			$this->add_remote_request_handlers();
513
		} else {
514
			if ( Jetpack::is_active() ) {
515
				add_action( 'login_form_jetpack_json_api_authorization', array( &$this, 'login_form_json_api_authorization' ) );
516
				add_filter( 'xmlrpc_methods', array( $this, 'public_xmlrpc_methods' ) );
517
			}
518
		}
519
520
		if ( Jetpack::is_active() ) {
521
			Jetpack_Heartbeat::init();
522
		}
523
524
		add_action( 'jetpack_clean_nonces', array( 'Jetpack', 'clean_nonces' ) );
525
		if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
526
			wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
527
		}
528
529
		add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ) );
530
531
		add_action( 'admin_init', array( $this, 'admin_init' ) );
532
		add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
533
534
		add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
535
536
		add_action( 'wp_dashboard_setup', array( $this, 'wp_dashboard_setup' ) );
537
		// Filter the dashboard meta box order to swap the new one in in place of the old one.
538
		add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) );
539
540
		add_action( 'wp_ajax_jetpack-sync-reindex-trigger', array( $this, 'sync_reindex_trigger' ) );
541
		add_action( 'wp_ajax_jetpack-sync-reindex-status', array( $this, 'sync_reindex_status' ) );
542
543
		// Jump Start AJAX callback function
544
		add_action( 'wp_ajax_jetpack_jumpstart_ajax',  array( $this, 'jetpack_jumpstart_ajax_callback' ) );
545
		add_action( 'update_option', array( $this, 'jumpstart_has_updated_module_option' ) );
546
547
		// Identity Crisis AJAX callback function
548
		add_action( 'wp_ajax_jetpack_resolve_identity_crisis', array( $this, 'resolve_identity_crisis_ajax_callback' ) );
549
550
		// JITM AJAX callback function
551
		add_action( 'wp_ajax_jitm_ajax',  array( $this, 'jetpack_jitm_ajax_callback' ) );
552
553
		add_action( 'wp_ajax_jetpack_admin_ajax',          array( $this, 'jetpack_admin_ajax_callback' ) );
554
		add_action( 'wp_ajax_jetpack_admin_ajax_refresh',  array( $this, 'jetpack_admin_ajax_refresh_data' ) );
555
556
		// Universal ajax callback for all tracking events triggered via js
557
		add_action( 'wp_ajax_jetpack_tracks', array( $this, 'jetpack_admin_ajax_tracks_callback' ) );
558
559
		add_action( 'wp_loaded', array( $this, 'register_assets' ) );
560
		add_action( 'wp_enqueue_scripts', array( $this, 'devicepx' ) );
561
		add_action( 'customize_controls_enqueue_scripts', array( $this, 'devicepx' ) );
562
		add_action( 'admin_enqueue_scripts', array( $this, 'devicepx' ) );
563
564
		add_action( 'jetpack_activate_module', array( $this, 'activate_module_actions' ) );
565
566
		add_action( 'plugins_loaded', array( $this, 'extra_oembed_providers' ), 100 );
567
568
		add_action( 'jetpack_notices', array( $this, 'show_development_mode_notice' ) );
569
570
		/**
571
		 * These actions run checks to load additional files.
572
		 * They check for external files or plugins, so they need to run as late as possible.
573
		 */
574
		add_action( 'wp_head', array( $this, 'check_open_graph' ),       1 );
575
		add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ),     999 );
576
		add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
577
578
		add_filter( 'plugins_url',      array( 'Jetpack', 'maybe_min_asset' ),     1, 3 );
579
		add_filter( 'style_loader_tag', array( 'Jetpack', 'maybe_inline_style' ), 10, 2 );
580
581
		add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 4 );
582
583
		add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
584
		add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
585
586
		// A filter to control all just in time messages
587
		add_filter( 'jetpack_just_in_time_msgs', '__return_false' );
588
589
		/**
590
		 * This is the hack to concatinate all css files into one.
591
		 * For description and reasoning see the implode_frontend_css method
592
		 *
593
		 * Super late priority so we catch all the registered styles
594
		 */
595
		if( !is_admin() ) {
596
			add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first
597
			add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`
598
		}
599
600
		// Sync Core Icon: Detect changes in Core's Site Icon and make it syncable.
601
		add_action( 'add_option_site_icon',    array( $this, 'jetpack_sync_core_icon' ) );
602
		add_action( 'update_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
603
		add_action( 'delete_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
604
		add_action( 'jetpack_heartbeat',       array( $this, 'jetpack_sync_core_icon' ) );
605
606
	}
607
608
	/*
609
	 * Make sure any site icon added to core can get
610
	 * synced back to dotcom, so we can display it there.
611
	 */
612
	function jetpack_sync_core_icon() {
613
		if ( function_exists( 'get_site_icon_url' ) ) {
614
			$url = get_site_icon_url();
615
		} else {
616
			return;
617
		}
618
619
		require_once( JETPACK__PLUGIN_DIR . 'modules/site-icon/site-icon-functions.php' );
620
		// If there's a core icon, maybe update the option.  If not, fall back to Jetpack's.
621
		if ( ! empty( $url ) && $url !== jetpack_site_icon_url() ) {
622
			// This is the option that is synced with dotcom
623
			Jetpack_Options::update_option( 'site_icon_url', $url );
624
		} else if ( empty( $url ) && did_action( 'delete_option_site_icon' ) ) {
625
			Jetpack_Options::delete_option( 'site_icon_url' );
626
		}
627
	}
628
629
	function jetpack_admin_ajax_tracks_callback() {
630
		// Check for nonce
631
		if ( ! isset( $_REQUEST['tracksNonce'] ) || ! wp_verify_nonce( $_REQUEST['tracksNonce'], 'jp-tracks-ajax-nonce' ) ) {
632
			wp_die( 'Permissions check failed.' );
633
		}
634
635
		if ( ! isset( $_REQUEST['tracksEventName'] ) || ! isset( $_REQUEST['tracksEventType'] )  ) {
636
			wp_die( 'No valid event name or type.' );
637
		}
638
639
		$tracks_data = array();
640
		if ( 'click' === $_REQUEST['tracksEventType'] && isset( $_REQUEST['tracksEventProp'] ) ) {
641
			$tracks_data = array( 'clicked' => $_REQUEST['tracksEventProp'] );
642
		}
643
644
		JetpackTracking::record_user_event( $_REQUEST['tracksEventName'], $tracks_data );
645
		wp_send_json_success();
646
		wp_die();
647
	}
648
649
	function jetpack_admin_ajax_callback() {
650
		// Check for nonce
651 View Code Duplication
		if ( ! isset( $_REQUEST['adminNonce'] ) || ! wp_verify_nonce( $_REQUEST['adminNonce'], 'jetpack-admin-nonce' ) || ! current_user_can( 'jetpack_manage_modules' ) ) {
652
			wp_die( 'permissions check failed' );
653
		}
654
655
		if ( isset( $_REQUEST['toggleModule'] ) && 'nux-toggle-module' == $_REQUEST['toggleModule'] ) {
656
			$slug = $_REQUEST['thisModuleSlug'];
657
658
			if ( ! in_array( $slug, Jetpack::get_available_modules() ) ) {
659
				wp_die( 'That is not a Jetpack module slug' );
660
			}
661
662
			if ( Jetpack::is_module_active( $slug ) ) {
663
				Jetpack::deactivate_module( $slug );
664
			} else {
665
				Jetpack::activate_module( $slug, false, false );
666
			}
667
668
			$modules = Jetpack_Admin::init()->get_modules();
669
			echo json_encode( $modules[ $slug ] );
670
671
			exit;
672
		}
673
674
		wp_die();
675
	}
676
677
	/*
678
	 * Sometimes we need to refresh the data,
679
	 * especially if the page is visited via a 'history'
680
	 * event like back/forward
681
	 */
682
	function jetpack_admin_ajax_refresh_data() {
683
		// Check for nonce
684 View Code Duplication
		if ( ! isset( $_REQUEST['adminNonce'] ) || ! wp_verify_nonce( $_REQUEST['adminNonce'], 'jetpack-admin-nonce' ) ) {
685
			wp_die( 'permissions check failed' );
686
		}
687
688
		if ( isset( $_REQUEST['refreshData'] ) && 'refresh' == $_REQUEST['refreshData'] ) {
689
			$modules = Jetpack_Admin::init()->get_modules();
690
			echo json_encode( $modules );
691
			exit;
692
		}
693
694
		wp_die();
695
	}
696
697
	/**
698
	 * The callback for the Jump Start ajax requests.
699
	 */
700
	function jetpack_jumpstart_ajax_callback() {
701
		// Check for nonce
702
		if ( ! isset( $_REQUEST['jumpstartNonce'] ) || ! wp_verify_nonce( $_REQUEST['jumpstartNonce'], 'jetpack-jumpstart-nonce' ) )
703
			wp_die( 'permissions check failed' );
704
705
		if ( isset( $_REQUEST['jumpStartActivate'] ) && 'jump-start-activate' == $_REQUEST['jumpStartActivate'] ) {
706
			// Update the jumpstart option
707
			if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
708
				Jetpack_Options::update_option( 'jumpstart', 'jumpstart_activated' );
709
			}
710
711
			// Loops through the requested "Jump Start" modules, and activates them.
712
			// Custom 'no_message' state, so that no message will be shown on reload.
713
			$modules = $_REQUEST['jumpstartModSlug'];
714
			$module_slugs = array();
715
			foreach( $modules as $module => $value ) {
716
				$module_slugs[] = $value['module_slug'];
717
			}
718
719
			// Check for possible conflicting plugins
720
			$module_slugs_filtered = $this->filter_default_modules( $module_slugs );
721
722
			foreach ( $module_slugs_filtered as $module_slug ) {
723
				Jetpack::log( 'activate', $module_slug );
724
				Jetpack::activate_module( $module_slug, false, false );
725
				Jetpack::state( 'message', 'no_message' );
726
			}
727
728
			// Set the default sharing buttons and set to display on posts if none have been set.
729
			$sharing_services = get_option( 'sharing-services' );
730
			$sharing_options  = get_option( 'sharing-options' );
731
			if ( empty( $sharing_services['visible'] ) ) {
732
				// Default buttons to set
733
				$visible = array(
734
					'twitter',
735
					'facebook',
736
					'google-plus-1',
737
				);
738
				$hidden = array();
739
740
				// Set some sharing settings
741
				$sharing = new Sharing_Service();
742
				$sharing_options['global'] = array(
743
					'button_style'  => 'icon',
744
					'sharing_label' => $sharing->default_sharing_label,
745
					'open_links'    => 'same',
746
					'show'          => array( 'post' ),
747
					'custom'        => isset( $sharing_options['global']['custom'] ) ? $sharing_options['global']['custom'] : array()
748
				);
749
750
				update_option( 'sharing-options', $sharing_options );
751
752
				// Send a success response so that we can display an error message.
753
				$success = update_option( 'sharing-services', array( 'visible' => $visible, 'hidden' => $hidden ) );
754
				echo json_encode( $success );
755
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method jetpack_jumpstart_ajax_callback() contains an exit expression.

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

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

Loading history...
756
			}
757
758
		} elseif ( isset( $_REQUEST['disableJumpStart'] ) && true == $_REQUEST['disableJumpStart'] ) {
759
			// If dismissed, flag the jumpstart option as such.
760
			// Send a success response so that we can display an error message.
761
			if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
762
				$success = Jetpack_Options::update_option( 'jumpstart', 'jumpstart_dismissed' );
763
				echo json_encode( $success );
764
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method jetpack_jumpstart_ajax_callback() contains an exit expression.

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

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

Loading history...
765
			}
766
767
		} elseif ( isset( $_REQUEST['jumpStartDeactivate'] ) && 'jump-start-deactivate' == $_REQUEST['jumpStartDeactivate'] ) {
768
769
			// FOR TESTING ONLY
770
			// @todo remove
771
			$modules = (array) $_REQUEST['jumpstartModSlug'];
772
			foreach( $modules as $module => $value ) {
773
				if ( !in_array( $value['module_slug'], Jetpack::get_default_modules() ) ) {
774
					Jetpack::log( 'deactivate', $value['module_slug'] );
775
					Jetpack::deactivate_module( $value['module_slug'] );
776
					Jetpack::state( 'message', 'no_message' );
777
				} else {
778
					Jetpack::log( 'activate', $value['module_slug'] );
779
					Jetpack::activate_module( $value['module_slug'], false, false );
780
					Jetpack::state( 'message', 'no_message' );
781
				}
782
			}
783
784
			Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
785
			echo "reload the page";
786
		}
787
788
		wp_die();
789
	}
790
791
	/**
792
	 * The callback for the JITM ajax requests.
793
	 */
794
	function jetpack_jitm_ajax_callback() {
795
		// Check for nonce
796
		if ( ! isset( $_REQUEST['jitmNonce'] ) || ! wp_verify_nonce( $_REQUEST['jitmNonce'], 'jetpack-jitm-nonce' ) ) {
797
			wp_die( 'Module activation failed due to lack of appropriate permissions' );
798
		}
799
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'activate' == $_REQUEST['jitmActionToTake'] ) {
800
			$module_slug = $_REQUEST['jitmModule'];
801
			Jetpack::log( 'activate', $module_slug );
802
			Jetpack::activate_module( $module_slug, false, false );
803
			Jetpack::state( 'message', 'no_message' );
804
805
			//A Jetpack module is being activated through a JITM, track it
806
			$this->stat( 'jitm', $module_slug.'-activated-' . JETPACK__VERSION );
807
			$this->do_stats( 'server_side' );
808
809
			wp_send_json_success();
810
		}
811
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'dismiss' == $_REQUEST['jitmActionToTake'] ) {
812
			// get the hide_jitm options array
813
			$jetpack_hide_jitm = Jetpack_Options::get_option( 'hide_jitm' );
814
			$module_slug = $_REQUEST['jitmModule'];
815
816
			if( ! $jetpack_hide_jitm ) {
817
				$jetpack_hide_jitm = array(
818
					$module_slug => 'hide'
819
				);
820
			} else {
821
				$jetpack_hide_jitm[$module_slug] = 'hide';
822
			}
823
824
			Jetpack_Options::update_option( 'hide_jitm', $jetpack_hide_jitm );
825
826
			//jitm is being dismissed forever, track it
827
			$this->stat( 'jitm', $module_slug.'-dismissed-' . JETPACK__VERSION );
828
			$this->do_stats( 'server_side' );
829
830
			wp_send_json_success();
831
		}
832 View Code Duplication
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'launch' == $_REQUEST['jitmActionToTake'] ) {
833
			$module_slug = $_REQUEST['jitmModule'];
834
835
			// User went to WordPress.com, track this
836
			$this->stat( 'jitm', $module_slug.'-wordpress-tools-' . JETPACK__VERSION );
837
			$this->do_stats( 'server_side' );
838
839
			wp_send_json_success();
840
		}
841 View Code Duplication
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'viewed' == $_REQUEST['jitmActionToTake'] ) {
842
			$track = $_REQUEST['jitmModule'];
843
844
			// User is viewing JITM, track it.
845
			$this->stat( 'jitm', $track . '-viewed-' . JETPACK__VERSION );
846
			$this->do_stats( 'server_side' );
847
848
			wp_send_json_success();
849
		}
850
	}
851
852
	/**
853
	 * If there are any stats that need to be pushed, but haven't been, push them now.
854
	 */
855
	function __destruct() {
856
		if ( ! empty( $this->stats ) ) {
857
			$this->do_stats( 'server_side' );
858
		}
859
	}
860
861
	function jetpack_custom_caps( $caps, $cap, $user_id, $args ) {
862
		switch( $cap ) {
863
			case 'jetpack_connect' :
864
			case 'jetpack_reconnect' :
865
				if ( Jetpack::is_development_mode() ) {
866
					$caps = array( 'do_not_allow' );
867
					break;
868
				}
869
				/**
870
				 * Pass through. If it's not development mode, these should match disconnect.
871
				 * Let users disconnect if it's development mode, just in case things glitch.
872
				 */
873
			case 'jetpack_disconnect' :
874
				/**
875
				 * In multisite, can individual site admins manage their own connection?
876
				 *
877
				 * Ideally, this should be extracted out to a separate filter in the Jetpack_Network class.
878
				 */
879
				if ( is_multisite() && ! is_super_admin() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
880
					if ( ! Jetpack_Network::init()->get_option( 'sub-site-connection-override' ) ) {
881
						/**
882
						 * We need to update the option name -- it's terribly unclear which
883
						 * direction the override goes.
884
						 *
885
						 * @todo: Update the option name to `sub-sites-can-manage-own-connections`
886
						 */
887
						$caps = array( 'do_not_allow' );
888
						break;
889
					}
890
				}
891
892
				$caps = array( 'manage_options' );
893
				break;
894
			case 'jetpack_manage_modules' :
895
			case 'jetpack_activate_modules' :
896
			case 'jetpack_deactivate_modules' :
897
				$caps = array( 'manage_options' );
898
				break;
899
			case 'jetpack_configure_modules' :
900
				$caps = array( 'manage_options' );
901
				break;
902
			case 'jetpack_network_admin_page':
903
			case 'jetpack_network_settings_page':
904
				$caps = array( 'manage_network_plugins' );
905
				break;
906
			case 'jetpack_network_sites_page':
907
				$caps = array( 'manage_sites' );
908
				break;
909
			case 'jetpack_admin_page' :
910
				if ( Jetpack::is_development_mode() ) {
911
					$caps = array( 'manage_options' );
912
					break;
913
				}
914
915
				// Don't ever show to subscribers, but allow access to the page if they're trying to unlink.
916
				if ( ! current_user_can( 'edit_posts' ) ) {
917
					if ( isset( $_GET['redirect'] ) && 'sub-unlink' == $_GET['redirect'] ) {
918
						// We need this in order to unlink the user.
919
						$this->admin_page_load();
920
					}
921
					if ( ! wp_verify_nonce( 'jetpack-unlink' ) ) {
922
						$caps = array( 'do_not_allow' );
923
						break;
924
					}
925
				}
926
927
				if ( ! self::is_active() && ! current_user_can( 'jetpack_connect' ) ) {
928
					$caps = array( 'do_not_allow' );
929
					break;
930
				}
931
				/**
932
				 * Pass through. If it's not development mode, these should match the admin page.
933
				 * Let users disconnect if it's development mode, just in case things glitch.
934
				 */
935
			case 'jetpack_connect_user' :
936
				if ( Jetpack::is_development_mode() ) {
937
					$caps = array( 'do_not_allow' );
938
					break;
939
				}
940
				$caps = array( 'read' );
941
				break;
942
		}
943
		return $caps;
944
	}
945
946
	function require_jetpack_authentication() {
947
		// Don't let anyone authenticate
948
		$_COOKIE = array();
949
		remove_all_filters( 'authenticate' );
950
951
		/**
952
		 * For the moment, remove Limit Login Attempts if its xmlrpc for Jetpack.
953
		 * If Limit Login Attempts is installed as a mu-plugin, it can occasionally
954
		 * generate false-positives.
955
		 */
956
		remove_filter( 'wp_login_failed', 'limit_login_failed' );
957
958
		if ( Jetpack::is_active() ) {
959
			// Allow Jetpack authentication
960
			add_filter( 'authenticate', array( $this, 'authenticate_jetpack' ), 10, 3 );
961
		}
962
	}
963
964
	/**
965
	 * Load language files
966
	 */
967
	public static function plugin_textdomain() {
968
		// Note to self, the third argument must not be hardcoded, to account for relocated folders.
969
		load_plugin_textdomain( 'jetpack', false, dirname( plugin_basename( JETPACK__PLUGIN_FILE ) ) . '/languages/' );
970
	}
971
972
	/**
973
	 * Register assets for use in various modules and the Jetpack admin page.
974
	 *
975
	 * @uses wp_script_is, wp_register_script, plugins_url
976
	 * @action wp_loaded
977
	 * @return null
978
	 */
979
	public function register_assets() {
980
		if ( ! wp_script_is( 'spin', 'registered' ) ) {
981
			wp_register_script( 'spin', plugins_url( '_inc/spin.js', JETPACK__PLUGIN_FILE ), false, '1.3' );
982
		}
983
984 View Code Duplication
		if ( ! wp_script_is( 'jquery.spin', 'registered' ) ) {
985
			wp_register_script( 'jquery.spin', plugins_url( '_inc/jquery.spin.js', JETPACK__PLUGIN_FILE ) , array( 'jquery', 'spin' ), '1.3' );
986
		}
987
988 View Code Duplication
		if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) ) {
989
			wp_register_script( 'jetpack-gallery-settings', plugins_url( '_inc/gallery-settings.js', JETPACK__PLUGIN_FILE ), array( 'media-views' ), '20121225' );
990
		}
991
992 View Code Duplication
		if ( ! wp_script_is( 'jetpack-twitter-timeline', 'registered' ) ) {
993
			wp_register_script( 'jetpack-twitter-timeline', plugins_url( '_inc/twitter-timeline.js', JETPACK__PLUGIN_FILE ) , array( 'jquery' ), '4.0.0', true );
994
		}
995
996
		if ( ! wp_script_is( 'jetpack-facebook-embed', 'registered' ) ) {
997
			wp_register_script( 'jetpack-facebook-embed', plugins_url( '_inc/facebook-embed.js', __FILE__ ), array( 'jquery' ), null, true );
998
999
			/** This filter is documented in modules/sharedaddy/sharing-sources.php */
1000
			$fb_app_id = apply_filters( 'jetpack_sharing_facebook_app_id', '249643311490' );
1001
			if ( ! is_numeric( $fb_app_id ) ) {
1002
				$fb_app_id = '';
1003
			}
1004
			wp_localize_script(
1005
				'jetpack-facebook-embed',
1006
				'jpfbembed',
1007
				array(
1008
					'appid' => $fb_app_id,
1009
					'locale' => $this->get_locale(),
1010
				)
1011
			);
1012
		}
1013
1014
		/**
1015
		 * As jetpack_register_genericons is by default fired off a hook,
1016
		 * the hook may have already fired by this point.
1017
		 * So, let's just trigger it manually.
1018
		 */
1019
		require_once( JETPACK__PLUGIN_DIR . '_inc/genericons.php' );
1020
		jetpack_register_genericons();
1021
1022 View Code Duplication
		if ( ! wp_style_is( 'jetpack-icons', 'registered' ) )
1023
			wp_register_style( 'jetpack-icons', plugins_url( 'css/jetpack-icons.min.css', JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
1024
	}
1025
1026
	/**
1027
	 * Guess locale from language code.
1028
	 *
1029
	 * @param string $lang Language code.
1030
	 * @return string|bool
1031
	 */
1032 View Code Duplication
	function guess_locale_from_lang( $lang ) {
1033
		if ( 'en' === $lang || 'en_US' === $lang || ! $lang ) {
1034
			return 'en_US';
1035
		}
1036
1037
		if ( ! class_exists( 'GP_Locales' ) ) {
1038
			if ( ! defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || ! file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
1039
				return false;
1040
			}
1041
1042
			require JETPACK__GLOTPRESS_LOCALES_PATH;
1043
		}
1044
1045
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1046
			// WP.com: get_locale() returns 'it'
1047
			$locale = GP_Locales::by_slug( $lang );
1048
		} else {
1049
			// Jetpack: get_locale() returns 'it_IT';
1050
			$locale = GP_Locales::by_field( 'facebook_locale', $lang );
1051
		}
1052
1053
		if ( ! $locale ) {
1054
			return false;
1055
		}
1056
1057
		if ( empty( $locale->facebook_locale ) ) {
1058
			if ( empty( $locale->wp_locale ) ) {
1059
				return false;
1060
			} else {
1061
				// Facebook SDK is smart enough to fall back to en_US if a
1062
				// locale isn't supported. Since supported Facebook locales
1063
				// can fall out of sync, we'll attempt to use the known
1064
				// wp_locale value and rely on said fallback.
1065
				return $locale->wp_locale;
1066
			}
1067
		}
1068
1069
		return $locale->facebook_locale;
1070
	}
1071
1072
	/**
1073
	 * Get the locale.
1074
	 *
1075
	 * @return string|bool
1076
	 */
1077
	function get_locale() {
1078
		$locale = $this->guess_locale_from_lang( get_locale() );
1079
1080
		if ( ! $locale ) {
1081
			$locale = 'en_US';
1082
		}
1083
1084
		return $locale;
1085
	}
1086
1087
	/**
1088
	 * Device Pixels support
1089
	 * This improves the resolution of gravatars and wordpress.com uploads on hi-res and zoomed browsers.
1090
	 */
1091
	function devicepx() {
1092
		if ( Jetpack::is_active() ) {
1093
			wp_enqueue_script( 'devicepx', set_url_scheme( 'http://s0.wp.com/wp-content/js/devicepx-jetpack.js' ), array(), gmdate( 'oW' ), true );
1094
		}
1095
	}
1096
1097
	/*
1098
	 * Returns the location of Jetpack's lib directory. This filter is applied
1099
	 * in require_lib().
1100
	 *
1101
	 * @filter require_lib_dir
1102
	 */
1103
	static function require_lib_dir() {
1104
		return JETPACK__PLUGIN_DIR . '_inc/lib';
1105
	}
1106
1107
	/**
1108
	 * Return the network_site_url so that .com knows what network this site is a part of.
1109
	 * @param  bool $option
1110
	 * @return string
1111
	 */
1112
	public function jetpack_main_network_site_option( $option ) {
1113
		return network_site_url();
1114
	}
1115
	/**
1116
	 * Network Name.
1117
	 */
1118
	static function network_name( $option = null ) {
1119
		global $current_site;
1120
		return $current_site->site_name;
1121
	}
1122
	/**
1123
	 * Does the network allow new user and site registrations.
1124
	 * @return string
1125
	 */
1126
	static function network_allow_new_registrations( $option = null ) {
1127
		return ( in_array( get_site_option( 'registration' ), array('none', 'user', 'blog', 'all' ) ) ? get_site_option( 'registration') : 'none' );
1128
	}
1129
	/**
1130
	 * Does the network allow admins to add new users.
1131
	 * @return boolian
1132
	 */
1133
	static function network_add_new_users( $option = null ) {
1134
		return (bool) get_site_option( 'add_new_users' );
1135
	}
1136
	/**
1137
	 * File upload psace left per site in MB.
1138
	 *  -1 means NO LIMIT.
1139
	 * @return number
1140
	 */
1141
	static function network_site_upload_space( $option = null ) {
1142
		// value in MB
1143
		return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
1144
	}
1145
1146
	/**
1147
	 * Network allowed file types.
1148
	 * @return string
1149
	 */
1150
	static function network_upload_file_types( $option = null ) {
1151
		return get_site_option( 'upload_filetypes', 'jpg jpeg png gif' );
1152
	}
1153
1154
	/**
1155
	 * Maximum file upload size set by the network.
1156
	 * @return number
1157
	 */
1158
	static function network_max_upload_file_size( $option = null ) {
1159
		// value in KB
1160
		return get_site_option( 'fileupload_maxk', 300 );
1161
	}
1162
1163
	/**
1164
	 * Lets us know if a site allows admins to manage the network.
1165
	 * @return array
1166
	 */
1167
	static function network_enable_administration_menus( $option = null ) {
1168
		return get_site_option( 'menu_items' );
1169
	}
1170
1171
	/**
1172
	 * Return whether we are dealing with a multi network setup or not.
1173
	 * The reason we are type casting this is because we want to avoid the situation where
1174
	 * the result is false since when is_main_network_option return false it cases
1175
	 * the rest the get_option( 'jetpack_is_multi_network' ); to return the value that is set in the
1176
	 * database which could be set to anything as opposed to what this function returns.
1177
	 * @param  bool  $option
1178
	 *
1179
	 * @return boolean
1180
	 */
1181
	public function is_main_network_option( $option ) {
1182
		// return '1' or ''
1183
		return (string) (bool) Jetpack::is_multi_network();
1184
	}
1185
1186
	/**
1187
	 * Return true if we are with multi-site or multi-network false if we are dealing with single site.
1188
	 *
1189
	 * @param  string  $option
1190
	 * @return boolean
1191
	 */
1192
	public function is_multisite( $option ) {
1193
		return (string) (bool) is_multisite();
1194
	}
1195
1196
	/**
1197
	 * Implemented since there is no core is multi network function
1198
	 * Right now there is no way to tell if we which network is the dominant network on the system
1199
	 *
1200
	 * @since  3.3
1201
	 * @return boolean
1202
	 */
1203
	public static function is_multi_network() {
1204
		global  $wpdb;
1205
1206
		// if we don't have a multi site setup no need to do any more
1207
		if ( ! is_multisite() ) {
1208
			return false;
1209
		}
1210
1211
		$num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
1212
		if ( $num_sites > 1 ) {
1213
			return true;
1214
		} else {
1215
			return false;
1216
		}
1217
	}
1218
1219
	/**
1220
	 * Trigger an update to the main_network_site when we update the siteurl of a site.
1221
	 * @return null
1222
	 */
1223
	function update_jetpack_main_network_site_option() {
1224
		// do_action( 'add_option_$option', '$option', '$value-of-the-option' );
1225
		/**
1226
		 * Fires when the site URL is updated.
1227
		 * Determines if the site is the main site of a Mulitiste network.
1228
		 *
1229
		 * @since 3.3.0
1230
		 *
1231
		 * @param string jetpack_main_network_site.
1232
		 * @param string network_site_url() Site URL for the "main" site of the current Multisite network.
1233
		 */
1234
		do_action( 'add_option_jetpack_main_network_site', 'jetpack_main_network_site', network_site_url() );
1235
		/**
1236
		 * Fires when the site URL is updated.
1237
		 * Determines if the is part of a multi network.
1238
		 *
1239
		 * @since 3.3.0
1240
		 *
1241
		 * @param string jetpack_is_main_network.
1242
		 * @param bool Jetpack::is_multi_network() Is the site part of a multi network.
1243
		 */
1244
		do_action( 'add_option_jetpack_is_main_network', 'jetpack_is_main_network', (string) (bool) Jetpack::is_multi_network() );
1245
		/**
1246
		 * Fires when the site URL is updated.
1247
		 * Determines if the site is part of a multisite network.
1248
		 *
1249
		 * @since 3.4.0
1250
		 *
1251
		 * @param string jetpack_is_multi_site.
1252
		 * @param bool is_multisite() Is the site part of a mutlisite network.
1253
		 */
1254
		do_action( 'add_option_jetpack_is_multi_site', 'jetpack_is_multi_site', (string) (bool) is_multisite() );
1255
	}
1256
	/**
1257
	 * Triggered after a user updates the network settings via Network Settings Admin Page
1258
	 *
1259
	 */
1260
	function update_jetpack_network_settings() {
1261
		// Only sync this info for the main network site.
1262
		do_action( 'add_option_jetpack_network_name', 'jetpack_network_name', Jetpack::network_name() );
1263
		do_action( 'add_option_jetpack_network_allow_new_registrations', 'jetpack_network_allow_new_registrations', Jetpack::network_allow_new_registrations() );
1264
		do_action( 'add_option_jetpack_network_add_new_users', 'jetpack_network_add_new_users', Jetpack::network_add_new_users() );
1265
		do_action( 'add_option_jetpack_network_site_upload_space', 'jetpack_network_site_upload_space', Jetpack::network_site_upload_space() );
1266
		do_action( 'add_option_jetpack_network_upload_file_types', 'jetpack_network_upload_file_types', Jetpack::network_upload_file_types() );
1267
		do_action( 'add_option_jetpack_network_enable_administration_menus', 'jetpack_network_enable_administration_menus', Jetpack::network_enable_administration_menus() );
1268
1269
	}
1270
1271
	/**
1272
	 * Get back if the current site is single user site.
1273
	 *
1274
	 * @return bool
1275
	 */
1276
	public static function is_single_user_site() {
1277
1278
		$user_query = new WP_User_Query( array(
1279
			'blog_id' => get_current_blog_id(),
1280
			'fields'  => 'ID',
1281
			'number' => 2
1282
		) );
1283
		return 1 === (int) $user_query->get_total();
1284
	}
1285
1286
	/**
1287
	 * Returns true if the site has file write access false otherwise.
1288
	 * @return string ( '1' | '0' )
1289
	 **/
1290
	public static function file_system_write_access() {
1291
		if ( ! function_exists( 'get_filesystem_method' ) ) {
1292
			require_once( ABSPATH . 'wp-admin/includes/file.php' );
1293
		}
1294
1295
		require_once( ABSPATH . 'wp-admin/includes/template.php' );
1296
1297
		$filesystem_method = get_filesystem_method();
1298
		if ( $filesystem_method === 'direct' ) {
1299
			return 1;
1300
		}
1301
1302
		ob_start();
1303
		$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1304
		ob_end_clean();
1305
		if ( $filesystem_credentials_are_stored ) {
1306
			return 1;
1307
		}
1308
		return 0;
1309
	}
1310
1311
	/**
1312
	 * Finds out if a site is using a version control system.
1313
	 * @return string ( '1' | '0' )
1314
	 **/
1315
	public static function is_version_controlled() {
1316
1317
		if ( !class_exists( 'WP_Automatic_Updater' ) ) {
1318
			require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
1319
		}
1320
		$updater = new WP_Automatic_Updater();
1321
		$is_version_controlled = strval( $updater->is_vcs_checkout( $context = ABSPATH ) );
1322
		// transients should not be empty
1323
		if ( empty( $is_version_controlled ) ) {
1324
			$is_version_controlled = '0';
1325
		}
1326
		return $is_version_controlled;
1327
	}
1328
1329
	/**
1330
	 * Determines whether the current theme supports featured images or not.
1331
	 * @return string ( '1' | '0' )
1332
	 */
1333
	public static function featured_images_enabled() {
1334
		return current_theme_supports( 'post-thumbnails' ) ? '1' : '0';
1335
	}
1336
1337
	/*
1338
	 * Sync back wp_version
1339
	 */
1340
	public static function get_wp_version() {
1341
		global $wp_version;
1342
		return $wp_version;
1343
	}
1344
1345
	/**
1346
	 * Keeps wp_version in sync with .com when WordPress core updates
1347
	 **/
1348
	public static function update_get_wp_version( $update, $meta_data ) {
1349
		if ( 'update' === $meta_data['action'] && 'core' === $meta_data['type'] ) {
1350
			/** This action is documented in wp-includes/option.php */
1351
			/**
1352
			 * This triggers the sync for the jetpack version
1353
			 * See Jetpack_Sync options method for more info.
1354
			 */
1355
			do_action( 'add_option_jetpack_wp_version', 'jetpack_wp_version', (string) Jetpack::get_wp_version() );
1356
		}
1357
	}
1358
1359
	/**
1360
	 * Triggers a sync of update counts and update details
1361
	 */
1362
	function sync_update_data() {
1363
		// Anytime WordPress saves update data, we'll want to sync update data
1364
		add_action( 'set_site_transient_update_plugins', array( 'Jetpack', 'refresh_update_data' ) );
1365
		add_action( 'set_site_transient_update_themes', array( 'Jetpack', 'refresh_update_data' ) );
1366
		add_action( 'set_site_transient_update_core', array( 'Jetpack', 'refresh_update_data' ) );
1367
		// Anytime a connection to jetpack is made, sync the update data
1368
		add_action( 'jetpack_site_registered', array( 'Jetpack', 'refresh_update_data' ) );
1369
		// Anytime the Jetpack Version changes, sync the the update data
1370
		add_action( 'updating_jetpack_version', array( 'Jetpack', 'refresh_update_data' ) );
1371
1372
		if ( current_user_can( 'update_core' ) && current_user_can( 'update_plugins' ) && current_user_can( 'update_themes' ) ) {
1373
			$this->sync->mock_option( 'updates', array( 'Jetpack', 'get_updates' ) );
1374
		}
1375
1376
		$this->sync->mock_option( 'update_details', array( 'Jetpack', 'get_update_details' ) );
1377
	}
1378
1379
	/**
1380
	 * Triggers a sync of information specific to the current theme.
1381
	 */
1382
	function sync_theme_data() {
1383
		add_action( 'switch_theme', array( 'Jetpack', 'refresh_theme_data' ) );
1384
		$this->sync->mock_option( 'featured_images_enabled', array( 'Jetpack', 'featured_images_enabled' ) );
1385
	}
1386
1387
	/**
1388
	 * jetpack_updates is saved in the following schema:
1389
	 *
1390
	 * array (
1391
	 *      'plugins'                       => (int) Number of plugin updates available.
1392
	 *      'themes'                        => (int) Number of theme updates available.
1393
	 *      'wordpress'                     => (int) Number of WordPress core updates available.
1394
	 *      'translations'                  => (int) Number of translation updates available.
1395
	 *      'total'                         => (int) Total of all available updates.
1396
	 *      'wp_update_version'             => (string) The latest available version of WordPress, only present if a WordPress update is needed.
1397
	 * )
1398
	 * @return array
1399
	 */
1400
	public static function get_updates() {
1401
		$update_data = wp_get_update_data();
1402
1403
		// Stores the individual update counts as well as the total count.
1404
		if ( isset( $update_data['counts'] ) ) {
1405
			$updates = $update_data['counts'];
1406
		}
1407
1408
		// If we need to update WordPress core, let's find the latest version number.
1409 View Code Duplication
		if ( ! empty( $updates['wordpress'] ) ) {
1410
			$cur = get_preferred_from_update_core();
1411
			if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
1412
				$updates['wp_update_version'] = $cur->current;
1413
			}
1414
		}
1415
		return isset( $updates ) ? $updates : array();
1416
	}
1417
1418
	public static function get_update_details() {
1419
		$update_details = array(
1420
			'update_core' => get_site_transient( 'update_core' ),
1421
			'update_plugins' => get_site_transient( 'update_plugins' ),
1422
			'update_themes' => get_site_transient( 'update_themes' ),
1423
		);
1424
		return $update_details;
1425
	}
1426
1427
	public static function refresh_update_data() {
1428
		if ( current_user_can( 'update_core' ) && current_user_can( 'update_plugins' ) && current_user_can( 'update_themes' ) ) {
1429
			/**
1430
			 * Fires whenever the amount of updates needed for a site changes.
1431
			 * Syncs an array that includes the number of theme, plugin, and core updates available, as well as the latest core version available.
1432
			 *
1433
			 * @since 3.7.0
1434
			 *
1435
			 * @param string jetpack_updates
1436
			 * @param array Update counts calculated by Jetpack::get_updates
1437
			 */
1438
			do_action( 'add_option_jetpack_updates', 'jetpack_updates', Jetpack::get_updates() );
1439
		}
1440
		/**
1441
		 * Fires whenever the amount of updates needed for a site changes.
1442
		 * Syncs an array of core, theme, and plugin data, and which of each is out of date
1443
		 *
1444
		 * @since 3.7.0
1445
		 *
1446
		 * @param string jetpack_update_details
1447
		 * @param array Update details calculated by Jetpack::get_update_details
1448
		 */
1449
		do_action( 'add_option_jetpack_update_details', 'jetpack_update_details', Jetpack::get_update_details() );
1450
	}
1451
1452
	public static function refresh_theme_data() {
1453
		/**
1454
		 * Fires whenever a theme change is made.
1455
		 *
1456
		 * @since 3.8.1
1457
		 *
1458
		 * @param string featured_images_enabled
1459
		 * @param boolean Whether featured images are enabled or not
1460
		 */
1461
		do_action( 'add_option_jetpack_featured_images_enabled', 'jetpack_featured_images_enabled', Jetpack::featured_images_enabled() );
1462
	}
1463
1464
	/**
1465
	 * Invalides the transient as well as triggers the update of the mock option.
1466
	 *
1467
	 * @return null
1468
	 */
1469
	function is_single_user_site_invalidate() {
1470
		/**
1471
		 * Fires when a user is added or removed from a site.
1472
		 * Determines if the site is a single user site.
1473
		 *
1474
		 * @since 3.4.0
1475
		 *
1476
		 * @param string jetpack_single_user_site.
1477
		 * @param bool Jetpack::is_single_user_site() Is the current site a single user site.
1478
		 */
1479
		do_action( 'update_option_jetpack_single_user_site', 'jetpack_single_user_site', (bool) Jetpack::is_single_user_site() );
1480
	}
1481
1482
	/**
1483
	 * Is Jetpack active?
1484
	 */
1485
	public static function is_active() {
1486
		return (bool) Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1487
	}
1488
1489
	/**
1490
	 * Is Jetpack in development (offline) mode?
1491
	 */
1492
	public static function is_development_mode() {
1493
		$development_mode = false;
1494
1495
		if ( defined( 'JETPACK_DEV_DEBUG' ) ) {
1496
			$development_mode = JETPACK_DEV_DEBUG;
1497
		}
1498
1499
		elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1500
			$development_mode = true;
1501
		}
1502
		/**
1503
		 * Filters Jetpack's development mode.
1504
		 *
1505
		 * @see http://jetpack.com/support/development-mode/
1506
		 *
1507
		 * @since 2.2.1
1508
		 *
1509
		 * @param bool $development_mode Is Jetpack's development mode active.
1510
		 */
1511
		return apply_filters( 'jetpack_development_mode', $development_mode );
1512
	}
1513
1514
	/**
1515
	* Get Jetpack development mode notice text and notice class.
1516
	*
1517
	* Mirrors the checks made in Jetpack::is_development_mode
1518
	*
1519
	*/
1520
	public static function show_development_mode_notice() {
1521
		if ( Jetpack::is_development_mode() ) {
1522
			if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
1523
				$notice = sprintf(
1524
					/* translators: %s is a URL */
1525
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the JETPACK_DEV_DEBUG constant being defined in wp-config.php or elsewhere.', 'jetpack' ),
1526
					'http://jetpack.com/support/development-mode/'
1527
				);
1528
			} elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1529
				$notice = sprintf(
1530
					/* translators: %s is a URL */
1531
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via site URL lacking a dot (e.g. http://localhost).', 'jetpack' ),
1532
					'http://jetpack.com/support/development-mode/'
1533
				);
1534
			} else {
1535
				$notice = sprintf(
1536
					/* translators: %s is a URL */
1537
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the jetpack_development_mode filter.', 'jetpack' ),
1538
					'http://jetpack.com/support/development-mode/'
1539
				);
1540
			}
1541
1542
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1543
		}
1544
1545
		// Throw up a notice if using a development version and as for feedback.
1546
		if ( Jetpack::is_development_version() ) {
1547
			/* translators: %s is a URL */
1548
			$notice = sprintf( __( 'You are currently running a development version of Jetpack. <a href="%s" target="_blank">Submit your feedback</a>', 'jetpack' ), 'https://jetpack.com/contact-support/beta-group/' );
1549
1550
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1551
		}
1552
		// Throw up a notice if using staging mode
1553
		if ( Jetpack::is_staging_site() ) {
1554
			/* translators: %s is a URL */
1555
			$notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), 'https://jetpack.com/support/staging-sites/' );
1556
1557
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1558
		}
1559
	}
1560
1561
	/**
1562
	 * Whether Jetpack's version maps to a public release, or a development version.
1563
	 */
1564
	public static function is_development_version() {
1565
		return ! preg_match( '/^\d+(\.\d+)+$/', JETPACK__VERSION );
1566
	}
1567
1568
	/**
1569
	 * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1570
	 */
1571
	public static function is_user_connected( $user_id = false ) {
1572
		$user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
1573
		if ( ! $user_id ) {
1574
			return false;
1575
		}
1576
		return (bool) Jetpack_Data::get_access_token( $user_id );
1577
	}
1578
1579
	/**
1580
	 * Get the wpcom user data of the current|specified connected user.
1581
	 */
1582 View Code Duplication
	public static function get_connected_user_data( $user_id = null ) {
1583
		if ( ! $user_id ) {
1584
			$user_id = get_current_user_id();
1585
		}
1586
		Jetpack::load_xml_rpc_client();
1587
		$xml = new Jetpack_IXR_Client( array(
1588
			'user_id' => $user_id,
1589
		) );
1590
		$xml->query( 'wpcom.getUser' );
1591
		if ( ! $xml->isError() ) {
1592
			return $xml->getResponse();
1593
		}
1594
		return false;
1595
	}
1596
1597
	/**
1598
	 * Get the wpcom email of the current|specified connected user.
1599
	 */
1600 View Code Duplication
	public static function get_connected_user_email( $user_id = null ) {
1601
		if ( ! $user_id ) {
1602
			$user_id = get_current_user_id();
1603
		}
1604
		Jetpack::load_xml_rpc_client();
1605
		$xml = new Jetpack_IXR_Client( array(
1606
			'user_id' => $user_id,
1607
		) );
1608
		$xml->query( 'wpcom.getUserEmail' );
1609
		if ( ! $xml->isError() ) {
1610
			return $xml->getResponse();
1611
		}
1612
		return false;
1613
	}
1614
1615
	/**
1616
	 * Get the wpcom email of the master user.
1617
	 */
1618
	public static function get_master_user_email() {
1619
		$master_user_id = Jetpack_Options::get_option( 'master_user' );
1620
		if ( $master_user_id ) {
1621
			return self::get_connected_user_email( $master_user_id );
1622
		}
1623
		return '';
1624
	}
1625
1626
	function current_user_is_connection_owner() {
1627
		$user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1628
		return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && get_current_user_id() === $user_token->external_user_id;
1629
	}
1630
1631
	/**
1632
	 * Add any extra oEmbed providers that we know about and use on wpcom for feature parity.
1633
	 */
1634
	function extra_oembed_providers() {
1635
		// Cloudup: https://dev.cloudup.com/#oembed
1636
		wp_oembed_add_provider( 'https://cloudup.com/*' , 'https://cloudup.com/oembed' );
1637
		wp_oembed_add_provider( 'https://me.sh/*', 'https://me.sh/oembed?format=json' );
1638
		wp_oembed_add_provider( '#https?://(www\.)?gfycat\.com/.*#i', 'https://api.gfycat.com/v1/oembed', true );
1639
		wp_oembed_add_provider( '#https?://[^.]+\.(wistia\.com|wi\.st)/(medias|embed)/.*#', 'https://fast.wistia.com/oembed', true );
1640
		wp_oembed_add_provider( '#https?://sketchfab\.com/.*#i', 'https://sketchfab.com/oembed', true );
1641
	}
1642
1643
	/**
1644
	 * Synchronize connected user role changes
1645
	 */
1646
	function user_role_change( $user_id ) {
1647
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $user_id ) ) {
1648
			$current_user_id = get_current_user_id();
1649
			wp_set_current_user( $user_id );
1650
			$role = $this->translate_current_user_to_role();
1651
			$signed_role = $this->sign_role( $role );
1652
			wp_set_current_user( $current_user_id );
1653
1654
			$master_token   = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1655
			$master_user_id = absint( $master_token->external_user_id );
1656
1657
			if ( ! $master_user_id )
1658
				return; // this shouldn't happen
1659
1660
			Jetpack::xmlrpc_async_call( 'jetpack.updateRole', $user_id, $signed_role );
1661
			//@todo retry on failure
1662
1663
			//try to choose a new master if we're demoting the current one
1664
			if ( $user_id == $master_user_id && 'administrator' != $role ) {
1665
				$query = new WP_User_Query(
1666
					array(
1667
						'fields'  => array( 'id' ),
1668
						'role'    => 'administrator',
1669
						'orderby' => 'id',
1670
						'exclude' => array( $master_user_id ),
1671
					)
1672
				);
1673
				$new_master = false;
1674
				foreach ( $query->results as $result ) {
1675
					$uid = absint( $result->id );
1676
					if ( $uid && Jetpack::is_user_connected( $uid ) ) {
1677
						$new_master = $uid;
1678
						break;
1679
					}
1680
				}
1681
1682
				if ( $new_master ) {
1683
					Jetpack_Options::update_option( 'master_user', $new_master );
1684
				}
1685
				// else disconnect..?
1686
			}
1687
		}
1688
	}
1689
1690
	/**
1691
	 * Loads the currently active modules.
1692
	 */
1693
	public static function load_modules() {
1694
		if ( ! self::is_active() && !self::is_development_mode() ) {
1695
			if ( ! is_multisite() || ! get_site_option( 'jetpack_protect_active' ) ) {
1696
				return;
1697
			}
1698
		}
1699
1700
		/**
1701
		 * Making sure the proper library path is used. This was previously done in init,
1702
		 * but it turned out that some modules might require libraries even before init
1703
		 * happens.
1704
		 */
1705
		add_filter( 'jetpack_require_lib_dir', array( 'Jetpack', 'require_lib_dir' ) );
1706
1707
		$version = Jetpack_Options::get_option( 'version' );
1708 View Code Duplication
		if ( ! $version ) {
1709
			$version = $old_version = JETPACK__VERSION . ':' . time();
1710
			/** This action is documented in class.jetpack.php */
1711
			do_action( 'updating_jetpack_version', $version, false );
1712
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1713
		}
1714
		list( $version ) = explode( ':', $version );
1715
1716
		$modules = array_filter( Jetpack::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1717
1718
		$modules_data = array();
1719
1720
		// Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1721
		if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1722
			$updated_modules = array();
1723
			foreach ( $modules as $module ) {
1724
				$modules_data[ $module ] = Jetpack::get_module( $module );
1725
				if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1726
					continue;
1727
				}
1728
1729
				if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1730
					continue;
1731
				}
1732
1733
				$updated_modules[] = $module;
1734
			}
1735
1736
			$modules = array_diff( $modules, $updated_modules );
1737
		}
1738
1739
		$is_development_mode = Jetpack::is_development_mode();
1740
1741
		foreach ( $modules as $index => $module ) {
1742
			// If we're in dev mode, disable modules requiring a connection
1743
			if ( $is_development_mode ) {
1744
				// Prime the pump if we need to
1745
				if ( empty( $modules_data[ $module ] ) ) {
1746
					$modules_data[ $module ] = Jetpack::get_module( $module );
1747
				}
1748
				// If the module requires a connection, but we're in local mode, don't include it.
1749
				if ( $modules_data[ $module ]['requires_connection'] ) {
1750
					continue;
1751
				}
1752
			}
1753
1754
			if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1755
				continue;
1756
			}
1757
1758
			if ( ! @include( Jetpack::get_module_path( $module ) ) ) {
1759
				unset( $modules[ $index ] );
1760
				Jetpack_Options::update_option( 'active_modules', array_values( $modules ) );
1761
				continue;
1762
			}
1763
1764
			/**
1765
			 * Fires when a specific module is loaded.
1766
			 * The dynamic part of the hook, $module, is the module slug.
1767
			 *
1768
			 * @since 1.1.0
1769
			 */
1770
			do_action( 'jetpack_module_loaded_' . $module );
1771
		}
1772
1773
		/**
1774
		 * Fires when all the modules are loaded.
1775
		 *
1776
		 * @since 1.1.0
1777
		 */
1778
		do_action( 'jetpack_modules_loaded' );
1779
1780
		// 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.
1781
		if ( Jetpack::is_active() || Jetpack::is_development_mode() )
1782
			require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' );
1783
	}
1784
1785
	/**
1786
	 * Check if Jetpack's REST API compat file should be included
1787
	 * @action plugins_loaded
1788
	 * @return null
1789
	 */
1790
	public function check_rest_api_compat() {
1791
		/**
1792
		 * Filters the list of REST API compat files to be included.
1793
		 *
1794
		 * @since 2.2.5
1795
		 *
1796
		 * @param array $args Array of REST API compat files to include.
1797
		 */
1798
		$_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1799
1800
		if ( function_exists( 'bbpress' ) )
1801
			$_jetpack_rest_api_compat_includes[] = JETPACK__PLUGIN_DIR . 'class.jetpack-bbpress-json-api-compat.php';
1802
1803
		foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include )
1804
			require_once $_jetpack_rest_api_compat_include;
1805
	}
1806
1807
	/**
1808
	 * Gets all plugins currently active in values, regardless of whether they're
1809
	 * traditionally activated or network activated.
1810
	 *
1811
	 * @todo Store the result in core's object cache maybe?
1812
	 */
1813
	public static function get_active_plugins() {
1814
		$active_plugins = (array) get_option( 'active_plugins', array() );
1815
1816
		if ( is_multisite() ) {
1817
			// Due to legacy code, active_sitewide_plugins stores them in the keys,
1818
			// whereas active_plugins stores them in the values.
1819
			$network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1820
			if ( $network_plugins ) {
1821
				$active_plugins = array_merge( $active_plugins, $network_plugins );
1822
			}
1823
		}
1824
1825
		sort( $active_plugins );
1826
1827
		return array_unique( $active_plugins );
1828
	}
1829
1830
	/**
1831
	 * Gets and parses additional plugin data to send with the heartbeat data
1832
	 *
1833
	 * @since 3.8.1
1834
	 *
1835
	 * @return array Array of plugin data
1836
	 */
1837
	public static function get_parsed_plugin_data() {
1838
		if ( ! function_exists( 'get_plugins' ) ) {
1839
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
1840
		}
1841
		$all_plugins    = get_plugins();
1842
		$active_plugins = Jetpack::get_active_plugins();
1843
1844
		$plugins = array();
1845
		foreach ( $all_plugins as $path => $plugin_data ) {
1846
			$plugins[ $path ] = array(
1847
					'is_active' => in_array( $path, $active_plugins ),
1848
					'file'      => $path,
1849
					'name'      => $plugin_data['Name'],
1850
					'version'   => $plugin_data['Version'],
1851
					'author'    => $plugin_data['Author'],
1852
			);
1853
		}
1854
1855
		return $plugins;
1856
	}
1857
1858
	/**
1859
	 * Gets and parses theme data to send with the heartbeat data
1860
	 *
1861
	 * @since 3.8.1
1862
	 *
1863
	 * @return array Array of theme data
1864
	 */
1865
	public static function get_parsed_theme_data() {
1866
		$all_themes = wp_get_themes( array( 'allowed' => true ) );
1867
		$header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
1868
1869
		$themes = array();
1870
		foreach ( $all_themes as $slug => $theme_data ) {
1871
			$theme_headers = array();
1872
			foreach ( $header_keys as $header_key ) {
1873
				$theme_headers[ $header_key ] = $theme_data->get( $header_key );
1874
			}
1875
1876
			$themes[ $slug ] = array(
1877
					'is_active_theme' => $slug == wp_get_theme()->get_template(),
1878
					'slug' => $slug,
1879
					'theme_root' => $theme_data->get_theme_root_uri(),
1880
					'parent' => $theme_data->parent(),
1881
					'headers' => $theme_headers
1882
			);
1883
		}
1884
1885
		return $themes;
1886
	}
1887
1888
	/**
1889
	 * Checks whether a specific plugin is active.
1890
	 *
1891
	 * We don't want to store these in a static variable, in case
1892
	 * there are switch_to_blog() calls involved.
1893
	 */
1894
	public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
1895
		return in_array( $plugin, self::get_active_plugins() );
1896
	}
1897
1898
	/**
1899
	 * Check if Jetpack's Open Graph tags should be used.
1900
	 * If certain plugins are active, Jetpack's og 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_open_graph() {
1907
		if ( in_array( 'publicize', Jetpack::get_active_modules() ) || in_array( 'sharedaddy', Jetpack::get_active_modules() ) ) {
1908
			add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
1909
		}
1910
1911
		$active_plugins = self::get_active_plugins();
1912
1913
		if ( ! empty( $active_plugins ) ) {
1914
			foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
1915
				if ( in_array( $plugin, $active_plugins ) ) {
1916
					add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
1917
					break;
1918
				}
1919
			}
1920
		}
1921
1922
		/**
1923
		 * Allow the addition of Open Graph Meta Tags to all pages.
1924
		 *
1925
		 * @since 2.0.3
1926
		 *
1927
		 * @param bool false Should Open Graph Meta tags be added. Default to false.
1928
		 */
1929
		if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
1930
			require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
1931
		}
1932
	}
1933
1934
	/**
1935
	 * Check if Jetpack's Twitter tags should be used.
1936
	 * If certain plugins are active, Jetpack's twitter tags are suppressed.
1937
	 *
1938
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1939
	 * @action plugins_loaded
1940
	 * @return null
1941
	 */
1942
	public function check_twitter_tags() {
1943
1944
		$active_plugins = self::get_active_plugins();
1945
1946
		if ( ! empty( $active_plugins ) ) {
1947
			foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
1948
				if ( in_array( $plugin, $active_plugins ) ) {
1949
					add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
1950
					break;
1951
				}
1952
			}
1953
		}
1954
1955
		/**
1956
		 * Allow Twitter Card Meta tags to be disabled.
1957
		 *
1958
		 * @since 2.6.0
1959
		 *
1960
		 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
1961
		 */
1962
		if ( apply_filters( 'jetpack_disable_twitter_cards', true ) ) {
1963
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
1964
		}
1965
	}
1966
1967
1968
1969
1970
	/*
1971
	 *
1972
	 * Jetpack Security Reports
1973
	 *
1974
	 * Allowed types: login_form, backup, file_scanning, spam
1975
	 *
1976
	 * 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)
1977
	 *
1978
	 * 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)
1979
	 *
1980
	 *
1981
	 * Example code to submit a security report:
1982
	 *
1983
	 *  function akismet_submit_jetpack_security_report() {
1984
	 *  	Jetpack::submit_security_report( 'spam', __FILE__, $args = array( 'blocked' => 138284, status => 'ok' ) );
1985
	 *  }
1986
	 *  add_action( 'jetpack_security_report', 'akismet_submit_jetpack_security_report' );
1987
	 *
1988
	 */
1989
1990
1991
	/**
1992
	 * Calls for security report submissions.
1993
	 *
1994
	 * @return null
1995
	 */
1996
	public static function perform_security_reporting() {
1997
		$no_check_needed = get_site_transient( 'security_report_performed_recently' );
1998
1999
		if ( $no_check_needed ) {
2000
			return;
2001
		}
2002
2003
		/**
2004
		 * Fires before a security report is created.
2005
		 *
2006
		 * @since 3.4.0
2007
		 */
2008
		do_action( 'jetpack_security_report' );
2009
2010
		Jetpack_Options::update_option( 'security_report', self::$security_report );
2011
		set_site_transient( 'security_report_performed_recently', 1, 15 * MINUTE_IN_SECONDS );
2012
	}
2013
2014
	/**
2015
	 * Allows plugins to submit security reports.
2016
 	 *
2017
	 * @param string  $type         Report type (login_form, backup, file_scanning, spam)
2018
	 * @param string  $plugin_file  Plugin __FILE__, so that we can pull plugin data
2019
	 * @param array   $args         See definitions above
2020
	 */
2021
	public static function submit_security_report( $type = '', $plugin_file = '', $args = array() ) {
2022
2023
		if( !doing_action( 'jetpack_security_report' ) ) {
2024
			return new WP_Error( 'not_collecting_report', 'Not currently collecting security reports.  Please use the jetpack_security_report hook.' );
2025
		}
2026
2027
		if( !is_string( $type ) || !is_string( $plugin_file ) ) {
2028
			return new WP_Error( 'invalid_security_report', 'Invalid Security Report' );
2029
		}
2030
2031
		if( !function_exists( 'get_plugin_data' ) ) {
2032
			include( ABSPATH . 'wp-admin/includes/plugin.php' );
2033
		}
2034
2035
		//Get rid of any non-allowed args
2036
		$args = array_intersect_key( $args, array_flip( array( 'blocked', 'last', 'next', 'status', 'message' ) ) );
2037
2038
		$plugin = get_plugin_data( $plugin_file );
2039
2040
		if ( !$plugin['Name'] ) {
2041
			return new WP_Error( 'security_report_missing_plugin_name', 'Invalid Plugin File Provided' );
2042
		}
2043
2044
		// Sanitize everything to make sure we're not syncing something wonky
2045
		$type = sanitize_key( $type );
2046
2047
		$args['plugin'] = $plugin;
2048
2049
		// Cast blocked, last and next as integers.
2050
		// Last and next should be in unix timestamp format
2051
		if ( isset( $args['blocked'] ) ) {
2052
			$args['blocked'] = (int) $args['blocked'];
2053
		}
2054
		if ( isset( $args['last'] ) ) {
2055
			$args['last'] = (int) $args['last'];
2056
		}
2057
		if ( isset( $args['next'] ) ) {
2058
			$args['next'] = (int) $args['next'];
2059
		}
2060
		if ( !in_array( $args['status'], array( 'ok', 'warning', 'error' ) ) ) {
2061
			$args['status'] = 'ok';
2062
		}
2063
		if ( isset( $args['message'] ) ) {
2064
2065
			if( $args['status'] == 'ok' ) {
2066
				unset( $args['message'] );
2067
			}
2068
2069
			$allowed_html = array(
2070
			    'a' => array(
2071
			        'href' => array(),
2072
			        'title' => array()
2073
			    ),
2074
			    'em' => array(),
2075
			    'strong' => array(),
2076
			);
2077
2078
			$args['message'] = wp_kses( $args['message'], $allowed_html );
2079
		}
2080
2081
		$plugin_name = $plugin[ 'Name' ];
2082
2083
		self::$security_report[ $type ][ $plugin_name ] = $args;
2084
	}
2085
2086
	/**
2087
	 * Collects a new report if needed, then returns it.
2088
	 */
2089
	public function get_security_report() {
2090
		self::perform_security_reporting();
2091
		return Jetpack_Options::get_option( 'security_report' );
2092
	}
2093
2094
2095
/* Jetpack Options API */
2096
2097
	public static function get_option_names( $type = 'compact' ) {
2098
		return Jetpack_Options::get_option_names( $type );
2099
	}
2100
2101
	/**
2102
	 * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
2103
 	 *
2104
	 * @param string $name    Option name
2105
	 * @param mixed  $default (optional)
2106
	 */
2107
	public static function get_option( $name, $default = false ) {
2108
		return Jetpack_Options::get_option( $name, $default );
2109
	}
2110
2111
	/**
2112
	* Stores two secrets and a timestamp so WordPress.com can make a request back and verify an action
2113
	* Does some extra verification so urls (such as those to public-api, register, etc) can't just be crafted
2114
	* $name must be a registered option name.
2115
	*/
2116
	public static function create_nonce( $name ) {
2117
		$secret = wp_generate_password( 32, false ) . ':' . wp_generate_password( 32, false ) . ':' . ( time() + 600 );
2118
2119
		Jetpack_Options::update_option( $name, $secret );
2120
		@list( $secret_1, $secret_2, $eol ) = explode( ':', Jetpack_Options::get_option( $name ) );
2121
		if ( empty( $secret_1 ) || empty( $secret_2 ) || $eol < time() )
2122
			return new Jetpack_Error( 'missing_secrets' );
2123
2124
		return array(
2125
			'secret_1' => $secret_1,
2126
			'secret_2' => $secret_2,
2127
			'eol'      => $eol,
2128
		);
2129
	}
2130
2131
	/**
2132
	 * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
2133
 	 *
2134
	 * @deprecated 3.4 use Jetpack_Options::update_option() instead.
2135
	 * @param string $name  Option name
2136
	 * @param mixed  $value Option value
2137
	 */
2138
	public static function update_option( $name, $value ) {
2139
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_option()' );
2140
		return Jetpack_Options::update_option( $name, $value );
2141
	}
2142
2143
	/**
2144
	 * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
2145
 	 *
2146
	 * @deprecated 3.4 use Jetpack_Options::update_options() instead.
2147
	 * @param array $array array( option name => option value, ... )
2148
	 */
2149
	public static function update_options( $array ) {
2150
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_options()' );
2151
		return Jetpack_Options::update_options( $array );
2152
	}
2153
2154
	/**
2155
	 * Deletes the given option.  May be passed multiple option names as an array.
2156
	 * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
2157
	 *
2158
	 * @deprecated 3.4 use Jetpack_Options::delete_option() instead.
2159
	 * @param string|array $names
2160
	 */
2161
	public static function delete_option( $names ) {
2162
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::delete_option()' );
2163
		return Jetpack_Options::delete_option( $names );
2164
	}
2165
2166
	/**
2167
	 * Enters a user token into the user_tokens option
2168
	 *
2169
	 * @param int $user_id
2170
	 * @param string $token
2171
	 * return bool
2172
	 */
2173
	public static function update_user_token( $user_id, $token, $is_master_user ) {
2174
		// not designed for concurrent updates
2175
		$user_tokens = Jetpack_Options::get_option( 'user_tokens' );
2176
		if ( ! is_array( $user_tokens ) )
2177
			$user_tokens = array();
2178
		$user_tokens[$user_id] = $token;
2179
		if ( $is_master_user ) {
2180
			$master_user = $user_id;
2181
			$options     = compact( 'user_tokens', 'master_user' );
2182
		} else {
2183
			$options = compact( 'user_tokens' );
2184
		}
2185
		return Jetpack_Options::update_options( $options );
2186
	}
2187
2188
	/**
2189
	 * Returns an array of all PHP files in the specified absolute path.
2190
	 * Equivalent to glob( "$absolute_path/*.php" ).
2191
	 *
2192
	 * @param string $absolute_path The absolute path of the directory to search.
2193
	 * @return array Array of absolute paths to the PHP files.
2194
	 */
2195
	public static function glob_php( $absolute_path ) {
2196
		if ( function_exists( 'glob' ) ) {
2197
			return glob( "$absolute_path/*.php" );
2198
		}
2199
2200
		$absolute_path = untrailingslashit( $absolute_path );
2201
		$files = array();
2202
		if ( ! $dir = @opendir( $absolute_path ) ) {
2203
			return $files;
2204
		}
2205
2206
		while ( false !== $file = readdir( $dir ) ) {
2207
			if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
2208
				continue;
2209
			}
2210
2211
			$file = "$absolute_path/$file";
2212
2213
			if ( ! is_file( $file ) ) {
2214
				continue;
2215
			}
2216
2217
			$files[] = $file;
2218
		}
2219
2220
		closedir( $dir );
2221
2222
		return $files;
2223
	}
2224
2225
	public static function activate_new_modules( $redirect = false ) {
2226
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
2227
			return;
2228
		}
2229
2230
		$jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
2231 View Code Duplication
		if ( ! $jetpack_old_version ) {
2232
			$jetpack_old_version = $version = $old_version = '1.1:' . time();
2233
			/** This action is documented in class.jetpack.php */
2234
			do_action( 'updating_jetpack_version', $version, false );
2235
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2236
		}
2237
2238
		list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
2239
2240
		if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
2241
			return;
2242
		}
2243
2244
		$active_modules     = Jetpack::get_active_modules();
2245
		$reactivate_modules = array();
2246
		foreach ( $active_modules as $active_module ) {
2247
			$module = Jetpack::get_module( $active_module );
2248
			if ( ! isset( $module['changed'] ) ) {
2249
				continue;
2250
			}
2251
2252
			if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
2253
				continue;
2254
			}
2255
2256
			$reactivate_modules[] = $active_module;
2257
			Jetpack::deactivate_module( $active_module );
2258
		}
2259
2260
		$new_version = JETPACK__VERSION . ':' . time();
2261
		/** This action is documented in class.jetpack.php */
2262
		do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
2263
		Jetpack_Options::update_options(
2264
			array(
2265
				'version'     => $new_version,
2266
				'old_version' => $jetpack_old_version,
2267
			)
2268
		);
2269
2270
		Jetpack::state( 'message', 'modules_activated' );
2271
		Jetpack::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules );
2272
2273
		if ( $redirect ) {
2274
			$page = 'jetpack'; // make sure we redirect to either settings or the jetpack page
2275
			if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ) ) ) {
2276
				$page = $_GET['page'];
2277
			}
2278
2279
			wp_safe_redirect( Jetpack::admin_url( 'page=' . $page ) );
2280
			exit;
2281
		}
2282
	}
2283
2284
	/**
2285
	 * List available Jetpack modules. Simply lists .php files in /modules/.
2286
	 * Make sure to tuck away module "library" files in a sub-directory.
2287
	 */
2288
	public static function get_available_modules( $min_version = false, $max_version = false ) {
2289
		static $modules = null;
2290
2291
		if ( ! isset( $modules ) ) {
2292
			$available_modules_option = Jetpack_Options::get_option( 'available_modules', array() );
2293
			// Use the cache if we're on the front-end and it's available...
2294
			if ( ! is_admin() && ! empty( $available_modules_option[ JETPACK__VERSION ] ) ) {
2295
				$modules = $available_modules_option[ JETPACK__VERSION ];
2296
			} else {
2297
				$files = Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
2298
2299
				$modules = array();
2300
2301
				foreach ( $files as $file ) {
2302
					if ( ! $headers = Jetpack::get_module( $file ) ) {
2303
						continue;
2304
					}
2305
2306
					$modules[ Jetpack::get_module_slug( $file ) ] = $headers['introduced'];
2307
				}
2308
2309
				Jetpack_Options::update_option( 'available_modules', array(
2310
					JETPACK__VERSION => $modules,
2311
				) );
2312
			}
2313
		}
2314
2315
		/**
2316
		 * Filters the array of modules available to be activated.
2317
		 *
2318
		 * @since 2.4.0
2319
		 *
2320
		 * @param array $modules Array of available modules.
2321
		 * @param string $min_version Minimum version number required to use modules.
2322
		 * @param string $max_version Maximum version number required to use modules.
2323
		 */
2324
		$mods = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version );
2325
2326
		if ( ! $min_version && ! $max_version ) {
2327
			return array_keys( $mods );
2328
		}
2329
2330
		$r = array();
2331
		foreach ( $mods as $slug => $introduced ) {
2332
			if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
2333
				continue;
2334
			}
2335
2336
			if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
2337
				continue;
2338
			}
2339
2340
			$r[] = $slug;
2341
		}
2342
2343
		return $r;
2344
	}
2345
2346
	/**
2347
	 * Default modules loaded on activation.
2348
	 */
2349
	public static function get_default_modules( $min_version = false, $max_version = false ) {
2350
		$return = array();
2351
2352
		foreach ( Jetpack::get_available_modules( $min_version, $max_version ) as $module ) {
2353
			$module_data = Jetpack::get_module( $module );
2354
2355
			switch ( strtolower( $module_data['auto_activate'] ) ) {
2356
				case 'yes' :
2357
					$return[] = $module;
2358
					break;
2359
				case 'public' :
2360
					if ( Jetpack_Options::get_option( 'public' ) ) {
2361
						$return[] = $module;
2362
					}
2363
					break;
2364
				case 'no' :
2365
				default :
0 ignored issues
show
There must be no space before the colon in a DEFAULT statement

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

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

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

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

Loading history...
2366
					break;
2367
			}
2368
		}
2369
		/**
2370
		 * Filters the array of default modules.
2371
		 *
2372
		 * @since 2.5.0
2373
		 *
2374
		 * @param array $return Array of default modules.
2375
		 * @param string $min_version Minimum version number required to use modules.
2376
		 * @param string $max_version Maximum version number required to use modules.
2377
		 */
2378
		return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version );
2379
	}
2380
2381
	/**
2382
	 * Checks activated modules during auto-activation to determine
2383
	 * if any of those modules are being deprecated.  If so, close
2384
	 * them out, and add any replacement modules.
2385
	 *
2386
	 * Runs at priority 99 by default.
2387
	 *
2388
	 * This is run late, so that it can still activate a module if
2389
	 * the new module is a replacement for another that the user
2390
	 * currently has active, even if something at the normal priority
2391
	 * would kibosh everything.
2392
	 *
2393
	 * @since 2.6
2394
	 * @uses jetpack_get_default_modules filter
2395
	 * @param array $modules
2396
	 * @return array
2397
	 */
2398
	function handle_deprecated_modules( $modules ) {
2399
		$deprecated_modules = array(
2400
			'debug'            => null,  // Closed out and moved to ./class.jetpack-debugger.php
2401
			'wpcc'             => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2402
			'gplus-authorship' => null,  // Closed out in 3.2 -- Google dropped support.
2403
		);
2404
2405
		// Don't activate SSO if they never completed activating WPCC.
2406
		if ( Jetpack::is_module_active( 'wpcc' ) ) {
2407
			$wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2408
			if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2409
				$deprecated_modules['wpcc'] = null;
2410
			}
2411
		}
2412
2413
		foreach ( $deprecated_modules as $module => $replacement ) {
2414
			if ( Jetpack::is_module_active( $module ) ) {
2415
				self::deactivate_module( $module );
2416
				if ( $replacement ) {
2417
					$modules[] = $replacement;
2418
				}
2419
			}
2420
		}
2421
2422
		return array_unique( $modules );
2423
	}
2424
2425
	/**
2426
	 * Checks activated plugins during auto-activation to determine
2427
	 * if any of those plugins are in the list with a corresponding module
2428
	 * that is not compatible with the plugin. The module will not be allowed
2429
	 * to auto-activate.
2430
	 *
2431
	 * @since 2.6
2432
	 * @uses jetpack_get_default_modules filter
2433
	 * @param array $modules
2434
	 * @return array
2435
	 */
2436
	function filter_default_modules( $modules ) {
2437
2438
		$active_plugins = self::get_active_plugins();
2439
2440
		if ( ! empty( $active_plugins ) ) {
2441
2442
			// For each module we'd like to auto-activate...
2443
			foreach ( $modules as $key => $module ) {
2444
				// If there are potential conflicts for it...
2445
				if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2446
					// For each potential conflict...
2447
					foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
2448
						// If that conflicting plugin is active...
2449
						if ( in_array( $plugin, $active_plugins ) ) {
2450
							// Remove that item from being auto-activated.
2451
							unset( $modules[ $key ] );
2452
						}
2453
					}
2454
				}
2455
			}
2456
		}
2457
2458
		return $modules;
2459
	}
2460
2461
	/**
2462
	 * Extract a module's slug from its full path.
2463
	 */
2464
	public static function get_module_slug( $file ) {
2465
		return str_replace( '.php', '', basename( $file ) );
2466
	}
2467
2468
	/**
2469
	 * Generate a module's path from its slug.
2470
	 */
2471
	public static function get_module_path( $slug ) {
2472
		return JETPACK__PLUGIN_DIR . "modules/$slug.php";
2473
	}
2474
2475
	/**
2476
	 * Load module data from module file. Headers differ from WordPress
2477
	 * plugin headers to avoid them being identified as standalone
2478
	 * plugins on the WordPress plugins page.
2479
	 */
2480
	public static function get_module( $module ) {
2481
		$headers = array(
2482
			'name'                      => 'Module Name',
2483
			'description'               => 'Module Description',
2484
			'jumpstart_desc'            => 'Jumpstart Description',
2485
			'sort'                      => 'Sort Order',
2486
			'recommendation_order'      => 'Recommendation Order',
2487
			'introduced'                => 'First Introduced',
2488
			'changed'                   => 'Major Changes In',
2489
			'deactivate'                => 'Deactivate',
2490
			'free'                      => 'Free',
2491
			'requires_connection'       => 'Requires Connection',
2492
			'auto_activate'             => 'Auto Activate',
2493
			'module_tags'               => 'Module Tags',
2494
			'feature'                   => 'Feature',
2495
			'additional_search_queries' => 'Additional Search Queries',
2496
		);
2497
2498
		$file = Jetpack::get_module_path( Jetpack::get_module_slug( $module ) );
2499
2500
		$mod = Jetpack::get_file_data( $file, $headers );
2501
		if ( empty( $mod['name'] ) ) {
2502
			return false;
2503
		}
2504
2505
		$mod['sort']                    = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
2506
		$mod['recommendation_order']    = empty( $mod['recommendation_order'] ) ? 20 : (int) $mod['recommendation_order'];
2507
		$mod['deactivate']              = empty( $mod['deactivate'] );
2508
		$mod['free']                    = empty( $mod['free'] );
2509
		$mod['requires_connection']     = ( ! empty( $mod['requires_connection'] ) && 'No' == $mod['requires_connection'] ) ? false : true;
2510
2511
		if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ) ) ) {
2512
			$mod['auto_activate'] = 'No';
2513
		} else {
2514
			$mod['auto_activate'] = (string) $mod['auto_activate'];
2515
		}
2516
2517
		if ( $mod['module_tags'] ) {
2518
			$mod['module_tags'] = explode( ',', $mod['module_tags'] );
2519
			$mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
2520
			$mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
2521
		} else {
2522
			$mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
2523
		}
2524
2525
		if ( $mod['feature'] ) {
2526
			$mod['feature'] = explode( ',', $mod['feature'] );
2527
			$mod['feature'] = array_map( 'trim', $mod['feature'] );
2528
		} else {
2529
			$mod['feature'] = array( self::translate_module_tag( 'Other' ) );
2530
		}
2531
2532
		/**
2533
		 * Filters the feature array on a module.
2534
		 *
2535
		 * This filter allows you to control where each module is filtered: Recommended,
2536
		 * Jumpstart, and the default "Other" listing.
2537
		 *
2538
		 * @since 3.5.0
2539
		 *
2540
		 * @param array   $mod['feature'] The areas to feature this module:
2541
		 *     'Jumpstart' adds to the "Jumpstart" option to activate many modules at once.
2542
		 *     'Recommended' shows on the main Jetpack admin screen.
2543
		 *     'Other' should be the default if no other value is in the array.
2544
		 * @param string  $module The slug of the module, e.g. sharedaddy.
2545
		 * @param array   $mod All the currently assembled module data.
2546
		 */
2547
		$mod['feature'] = apply_filters( 'jetpack_module_feature', $mod['feature'], $module, $mod );
2548
2549
		/**
2550
		 * Filter the returned data about a module.
2551
		 *
2552
		 * This filter allows overriding any info about Jetpack modules. It is dangerous,
2553
		 * so please be careful.
2554
		 *
2555
		 * @since 3.6.0
2556
		 *
2557
		 * @param array   $mod    The details of the requested module.
2558
		 * @param string  $module The slug of the module, e.g. sharedaddy
2559
		 * @param string  $file   The path to the module source file.
2560
		 */
2561
		return apply_filters( 'jetpack_get_module', $mod, $module, $file );
2562
	}
2563
2564
	/**
2565
	 * Like core's get_file_data implementation, but caches the result.
2566
	 */
2567
	public static function get_file_data( $file, $headers ) {
2568
		//Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated
2569
		$file_name = basename( $file );
2570
		$file_data_option = Jetpack_Options::get_option( 'file_data', array() );
2571
		$key              = md5( $file_name . serialize( $headers ) );
2572
		$refresh_cache    = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 );
2573
2574
		// If we don't need to refresh the cache, and already have the value, short-circuit!
2575
		if ( ! $refresh_cache && isset( $file_data_option[ JETPACK__VERSION ][ $key ] ) ) {
2576
			return $file_data_option[ JETPACK__VERSION ][ $key ];
2577
		}
2578
2579
		$data = get_file_data( $file, $headers );
2580
2581
		// Strip out any old Jetpack versions that are cluttering the option.
2582
		$file_data_option = array_intersect_key( (array) $file_data_option, array( JETPACK__VERSION => null ) );
2583
		$file_data_option[ JETPACK__VERSION ][ $key ] = $data;
2584
		Jetpack_Options::update_option( 'file_data', $file_data_option );
2585
2586
		return $data;
2587
	}
2588
2589
	/**
2590
	 * Return translated module tag.
2591
	 *
2592
	 * @param string $tag Tag as it appears in each module heading.
2593
	 *
2594
	 * @return mixed
2595
	 */
2596
	public static function translate_module_tag( $tag ) {
2597
		return jetpack_get_module_i18n_tag( $tag );
2598
	}
2599
2600
	/**
2601
	 * Return module name translation. Uses matching string created in modules/module-headings.php.
2602
	 *
2603
	 * @since 3.9.2
2604
	 *
2605
	 * @param array $modules
2606
	 *
2607
	 * @return string|void
2608
	 */
2609
	public static function get_translated_modules( $modules ) {
2610
		foreach ( $modules as $index => $module ) {
2611
			$i18n_module = jetpack_get_module_i18n( $module['module'] );
2612
			if ( isset( $module['name'] ) ) {
2613
				$modules[ $index ]['name'] = $i18n_module['name'];
2614
			}
2615
			if ( isset( $module['description'] ) ) {
2616
				$modules[ $index ]['description'] = $i18n_module['description'];
2617
				$modules[ $index ]['short_description'] = $i18n_module['description'];
2618
			}
2619
		}
2620
		return $modules;
2621
	}
2622
2623
	/**
2624
	 * Get a list of activated modules as an array of module slugs.
2625
	 */
2626
	public static function get_active_modules() {
2627
		$active = Jetpack_Options::get_option( 'active_modules' );
2628
		if ( ! is_array( $active ) )
2629
			$active = array();
2630
		if ( is_admin() && ( class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ) ) ) {
2631
			$active[] = 'vaultpress';
2632
		} else {
2633
			$active = array_diff( $active, array( 'vaultpress' ) );
2634
		}
2635
2636
		//If protect is active on the main site of a multisite, it should be active on all sites.
2637
		if ( ! in_array( 'protect', $active ) && is_multisite() && get_site_option( 'jetpack_protect_active' ) ) {
2638
			$active[] = 'protect';
2639
		}
2640
2641
		return array_unique( $active );
2642
	}
2643
2644
	/**
2645
	 * Check whether or not a Jetpack module is active.
2646
	 *
2647
	 * @param string $module The slug of a Jetpack module.
2648
	 * @return bool
2649
	 *
2650
	 * @static
2651
	 */
2652
	public static function is_module_active( $module ) {
2653
		return in_array( $module, self::get_active_modules() );
2654
	}
2655
2656
	public static function is_module( $module ) {
2657
		return ! empty( $module ) && ! validate_file( $module, Jetpack::get_available_modules() );
2658
	}
2659
2660
	/**
2661
	 * Catches PHP errors.  Must be used in conjunction with output buffering.
2662
	 *
2663
	 * @param bool $catch True to start catching, False to stop.
2664
	 *
2665
	 * @static
2666
	 */
2667
	public static function catch_errors( $catch ) {
2668
		static $display_errors, $error_reporting;
2669
2670
		if ( $catch ) {
2671
			$display_errors  = @ini_set( 'display_errors', 1 );
2672
			$error_reporting = @error_reporting( E_ALL );
2673
			add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2674
		} else {
2675
			@ini_set( 'display_errors', $display_errors );
2676
			@error_reporting( $error_reporting );
2677
			remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2678
		}
2679
	}
2680
2681
	/**
2682
	 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2683
	 */
2684
	public static function catch_errors_on_shutdown() {
2685
		Jetpack::state( 'php_errors', ob_get_clean() );
2686
	}
2687
2688
	public static function activate_default_modules( $min_version = false, $max_version = false, $other_modules = array(), $redirect = true ) {
2689
		$jetpack = Jetpack::init();
2690
2691
		$modules = Jetpack::get_default_modules( $min_version, $max_version );
2692
		$modules = array_merge( $other_modules, $modules );
2693
2694
		// Look for standalone plugins and disable if active.
2695
2696
		$to_deactivate = array();
2697
		foreach ( $modules as $module ) {
2698
			if ( isset( $jetpack->plugins_to_deactivate[$module] ) ) {
2699
				$to_deactivate[$module] = $jetpack->plugins_to_deactivate[$module];
2700
			}
2701
		}
2702
2703
		$deactivated = array();
2704
		foreach ( $to_deactivate as $module => $deactivate_me ) {
2705
			list( $probable_file, $probable_title ) = $deactivate_me;
2706
			if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2707
				$deactivated[] = $module;
2708
			}
2709
		}
2710
2711
		if ( $deactivated && $redirect ) {
2712
			Jetpack::state( 'deactivated_plugins', join( ',', $deactivated ) );
2713
2714
			$url = add_query_arg(
2715
				array(
2716
					'action'   => 'activate_default_modules',
2717
					'_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2718
				),
2719
				add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), Jetpack::admin_url( 'page=jetpack' ) )
2720
			);
2721
			wp_safe_redirect( $url );
2722
			exit;
2723
		}
2724
2725
		/**
2726
		 * Fires before default modules are activated.
2727
		 *
2728
		 * @since 1.9.0
2729
		 *
2730
		 * @param string $min_version Minimum version number required to use modules.
2731
		 * @param string $max_version Maximum version number required to use modules.
2732
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2733
		 */
2734
		do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
2735
2736
		// Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
2737
		Jetpack::restate();
2738
		Jetpack::catch_errors( true );
2739
2740
		$active = Jetpack::get_active_modules();
2741
2742
		foreach ( $modules as $module ) {
2743
			if ( did_action( "jetpack_module_loaded_$module" ) ) {
2744
				$active[] = $module;
2745
				Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2746
				continue;
2747
			}
2748
2749
			if ( in_array( $module, $active ) ) {
2750
				$module_info = Jetpack::get_module( $module );
2751
				if ( ! $module_info['deactivate'] ) {
2752
					$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2753 View Code Duplication
					if ( $active_state = Jetpack::state( $state ) ) {
2754
						$active_state = explode( ',', $active_state );
2755
					} else {
2756
						$active_state = array();
2757
					}
2758
					$active_state[] = $module;
2759
					Jetpack::state( $state, implode( ',', $active_state ) );
2760
				}
2761
				continue;
2762
			}
2763
2764
			$file = Jetpack::get_module_path( $module );
2765
			if ( ! file_exists( $file ) ) {
2766
				continue;
2767
			}
2768
2769
			// we'll override this later if the plugin can be included without fatal error
2770
			if ( $redirect ) {
2771
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2772
			}
2773
			Jetpack::state( 'error', 'module_activation_failed' );
2774
			Jetpack::state( 'module', $module );
2775
			ob_start();
2776
			require $file;
2777
			/**
2778
			 * Fires when a specific module is activated.
2779
			 *
2780
			 * @since 1.9.0
2781
			 *
2782
			 * @param string $module Module slug.
2783
			 */
2784
			do_action( 'jetpack_activate_module', $module );
2785
			$active[] = $module;
2786
			$state    = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2787 View Code Duplication
			if ( $active_state = Jetpack::state( $state ) ) {
2788
				$active_state = explode( ',', $active_state );
2789
			} else {
2790
				$active_state = array();
2791
			}
2792
			$active_state[] = $module;
2793
			Jetpack::state( $state, implode( ',', $active_state ) );
2794
			Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2795
			ob_end_clean();
2796
		}
2797
		Jetpack::state( 'error', false );
2798
		Jetpack::state( 'module', false );
2799
		Jetpack::catch_errors( false );
2800
		/**
2801
		 * Fires when default modules are activated.
2802
		 *
2803
		 * @since 1.9.0
2804
		 *
2805
		 * @param string $min_version Minimum version number required to use modules.
2806
		 * @param string $max_version Maximum version number required to use modules.
2807
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2808
		 */
2809
		do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
2810
	}
2811
2812
	public static function activate_module( $module, $exit = true, $redirect = true ) {
2813
		/**
2814
		 * Fires before a module is activated.
2815
		 *
2816
		 * @since 2.6.0
2817
		 *
2818
		 * @param string $module Module slug.
2819
		 * @param bool $exit Should we exit after the module has been activated. Default to true.
2820
		 * @param bool $redirect Should the user be redirected after module activation? Default to true.
2821
		 */
2822
		do_action( 'jetpack_pre_activate_module', $module, $exit, $redirect );
2823
2824
		$jetpack = Jetpack::init();
2825
2826
		if ( ! strlen( $module ) )
2827
			return false;
2828
2829
		if ( ! Jetpack::is_module( $module ) )
2830
			return false;
2831
2832
		// If it's already active, then don't do it again
2833
		$active = Jetpack::get_active_modules();
2834
		foreach ( $active as $act ) {
2835
			if ( $act == $module )
2836
				return true;
2837
		}
2838
2839
		$module_data = Jetpack::get_module( $module );
2840
2841
		if ( ! Jetpack::is_active() ) {
2842
			if ( !Jetpack::is_development_mode() )
2843
				return false;
2844
2845
			// If we're not connected but in development mode, make sure the module doesn't require a connection
2846
			if ( Jetpack::is_development_mode() && $module_data['requires_connection'] )
2847
				return false;
2848
		}
2849
2850
		// Check and see if the old plugin is active
2851
		if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2852
			// Deactivate the old plugin
2853
			if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[ $module ][0], $jetpack->plugins_to_deactivate[ $module ][1] ) ) {
2854
				// If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
2855
				// We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
2856
				Jetpack::state( 'deactivated_plugins', $module );
2857
				wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
2858
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method activate_module() contains an exit expression.

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

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

Loading history...
2859
			}
2860
		}
2861
2862
		// Check the file for fatal errors, a la wp-admin/plugins.php::activate
2863
		Jetpack::state( 'module', $module );
2864
		Jetpack::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
2865
2866
		Jetpack::catch_errors( true );
2867
		ob_start();
2868
		require Jetpack::get_module_path( $module );
2869
		/** This action is documented in class.jetpack.php */
2870
		do_action( 'jetpack_activate_module', $module );
2871
		$active[] = $module;
2872
		Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2873
		Jetpack::state( 'error', false ); // the override
2874
		Jetpack::state( 'message', 'module_activated' );
2875
		Jetpack::state( 'module', $module );
2876
		ob_end_clean();
2877
		Jetpack::catch_errors( false );
2878
2879
		// A flag for Jump Start so it's not shown again. Only set if it hasn't been yet.
2880 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2881
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2882
2883
			//Jump start is being dismissed send data to MC Stats
2884
			$jetpack->stat( 'jumpstart', 'manual,'.$module );
2885
2886
			$jetpack->do_stats( 'server_side' );
2887
		}
2888
2889
		if ( $redirect ) {
2890
			wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2891
		}
2892
		if ( $exit ) {
2893
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method activate_module() contains an exit expression.

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

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

Loading history...
2894
		}
2895
		return true;
2896
	}
2897
2898
	function activate_module_actions( $module ) {
2899
		/**
2900
		 * Fires when a module is activated.
2901
		 * The dynamic part of the filter, $module, is the module slug.
2902
		 *
2903
		 * @since 1.9.0
2904
		 *
2905
		 * @param string $module Module slug.
2906
		 */
2907
		do_action( "jetpack_activate_module_$module", $module );
2908
2909
		$this->sync->sync_all_module_options( $module );
2910
	}
2911
2912
	public static function deactivate_module( $module ) {
2913
		/**
2914
		 * Fires when a module is deactivated.
2915
		 *
2916
		 * @since 1.9.0
2917
		 *
2918
		 * @param string $module Module slug.
2919
		 */
2920
		do_action( 'jetpack_pre_deactivate_module', $module );
2921
2922
		$jetpack = Jetpack::init();
2923
2924
		$active = Jetpack::get_active_modules();
2925
		$new    = array_filter( array_diff( $active, (array) $module ) );
2926
2927
		/**
2928
		 * Fires when a module is deactivated.
2929
		 * The dynamic part of the filter, $module, is the module slug.
2930
		 *
2931
		 * @since 1.9.0
2932
		 *
2933
		 * @param string $module Module slug.
2934
		 */
2935
		do_action( "jetpack_deactivate_module_$module", $module );
2936
2937
		// A flag for Jump Start so it's not shown again.
2938 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2939
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2940
2941
			//Jump start is being dismissed send data to MC Stats
2942
			$jetpack->stat( 'jumpstart', 'manual,deactivated-'.$module );
2943
2944
			$jetpack->do_stats( 'server_side' );
2945
		}
2946
2947
		return Jetpack_Options::update_option( 'active_modules', array_unique( $new ) );
2948
	}
2949
2950
	public static function enable_module_configurable( $module ) {
2951
		$module = Jetpack::get_module_slug( $module );
2952
		add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
2953
	}
2954
2955
	public static function module_configuration_url( $module ) {
2956
		$module = Jetpack::get_module_slug( $module );
2957
		return Jetpack::admin_url( array( 'page' => 'jetpack', 'configure' => $module ) );
2958
	}
2959
2960
	public static function module_configuration_load( $module, $method ) {
2961
		$module = Jetpack::get_module_slug( $module );
2962
		add_action( 'jetpack_module_configuration_load_' . $module, $method );
2963
	}
2964
2965
	public static function module_configuration_head( $module, $method ) {
2966
		$module = Jetpack::get_module_slug( $module );
2967
		add_action( 'jetpack_module_configuration_head_' . $module, $method );
2968
	}
2969
2970
	public static function module_configuration_screen( $module, $method ) {
2971
		$module = Jetpack::get_module_slug( $module );
2972
		add_action( 'jetpack_module_configuration_screen_' . $module, $method );
2973
	}
2974
2975
	public static function module_configuration_activation_screen( $module, $method ) {
2976
		$module = Jetpack::get_module_slug( $module );
2977
		add_action( 'display_activate_module_setting_' . $module, $method );
2978
	}
2979
2980
/* Installation */
2981
2982
	public static function bail_on_activation( $message, $deactivate = true ) {
2983
?>
2984
<!doctype html>
2985
<html>
2986
<head>
2987
<meta charset="<?php bloginfo( 'charset' ); ?>">
2988
<style>
2989
* {
2990
	text-align: center;
2991
	margin: 0;
2992
	padding: 0;
2993
	font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
2994
}
2995
p {
2996
	margin-top: 1em;
2997
	font-size: 18px;
2998
}
2999
</style>
3000
<body>
3001
<p><?php echo esc_html( $message ); ?></p>
3002
</body>
3003
</html>
3004
<?php
3005
		if ( $deactivate ) {
3006
			$plugins = get_option( 'active_plugins' );
3007
			$jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
3008
			$update  = false;
3009
			foreach ( $plugins as $i => $plugin ) {
3010
				if ( $plugin === $jetpack ) {
3011
					$plugins[$i] = false;
3012
					$update = true;
3013
				}
3014
			}
3015
3016
			if ( $update ) {
3017
				update_option( 'active_plugins', array_filter( $plugins ) );
3018
			}
3019
		}
3020
		exit;
3021
	}
3022
3023
	/**
3024
	 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
3025
	 * @static
3026
	 */
3027
	public static function plugin_activation( $network_wide ) {
3028
		Jetpack_Options::update_option( 'activated', 1 );
3029
3030
		if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
3031
			Jetpack::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
3032
		}
3033
3034
		if ( $network_wide )
3035
			Jetpack::state( 'network_nag', true );
3036
3037
		Jetpack::plugin_initialize();
3038
	}
3039
	/**
3040
	 * Runs before bumping version numbers up to a new version
3041
	 * @param  (string) $version    Version:timestamp
3042
	 * @param  (string) $old_version Old Version:timestamp or false if not set yet.
3043
	 * @return null              [description]
3044
	 */
3045
	public static function do_version_bump( $version, $old_version ) {
3046
3047
		if ( ! $old_version ) { // For new sites
3048
			// Setting up jetpack manage
3049
			Jetpack::activate_manage();
3050
		}
3051
	}
3052
3053
	/**
3054
	 * Sets the internal version number and activation state.
3055
	 * @static
3056
	 */
3057
	public static function plugin_initialize() {
3058
		if ( ! Jetpack_Options::get_option( 'activated' ) ) {
3059
			Jetpack_Options::update_option( 'activated', 2 );
3060
		}
3061
3062 View Code Duplication
		if ( ! Jetpack_Options::get_option( 'version' ) ) {
3063
			$version = $old_version = JETPACK__VERSION . ':' . time();
3064
			/** This action is documented in class.jetpack.php */
3065
			do_action( 'updating_jetpack_version', $version, false );
3066
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
3067
		}
3068
3069
		Jetpack::load_modules();
3070
3071
		Jetpack_Options::delete_option( 'do_activate' );
3072
	}
3073
3074
	/**
3075
	 * Removes all connection options
3076
	 * @static
3077
	 */
3078
	public static function plugin_deactivation( ) {
3079
		require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
3080
		if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
3081
			Jetpack_Network::init()->deactivate();
3082
		} else {
3083
			Jetpack::disconnect( false );
3084
			//Jetpack_Heartbeat::init()->deactivate();
3085
		}
3086
	}
3087
3088
	/**
3089
	 * Disconnects from the Jetpack servers.
3090
	 * Forgets all connection details and tells the Jetpack servers to do the same.
3091
	 * @static
3092
	 */
3093
	public static function disconnect( $update_activated_state = true ) {
3094
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
3095
		Jetpack::clean_nonces( true );
3096
3097
		Jetpack::load_xml_rpc_client();
3098
		$xml = new Jetpack_IXR_Client();
3099
		$xml->query( 'jetpack.deregister' );
3100
3101
		Jetpack_Options::delete_option(
3102
			array(
3103
				'register',
3104
				'blog_token',
3105
				'user_token',
3106
				'user_tokens',
3107
				'master_user',
3108
				'time_diff',
3109
				'fallback_no_verify_ssl_certs',
3110
			)
3111
		);
3112
3113
		if ( $update_activated_state ) {
3114
			Jetpack_Options::update_option( 'activated', 4 );
3115
		}
3116
3117
		$jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' );
3118
		// Check then record unique disconnection if site has never been disconnected previously
3119
		if ( -1 == $jetpack_unique_connection['disconnected'] ) {
3120
			$jetpack_unique_connection['disconnected'] = 1;
3121
		}
3122
		else {
3123
			if ( 0 == $jetpack_unique_connection['disconnected'] ) {
3124
				//track unique disconnect
3125
				$jetpack = Jetpack::init();
3126
3127
				$jetpack->stat( 'connections', 'unique-disconnect' );
3128
				$jetpack->do_stats( 'server_side' );
3129
			}
3130
			// increment number of times disconnected
3131
			$jetpack_unique_connection['disconnected'] += 1;
3132
		}
3133
3134
		Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
3135
3136
		// Disable the Heartbeat cron
3137
		Jetpack_Heartbeat::init()->deactivate();
3138
	}
3139
3140
	/**
3141
	 * Unlinks the current user from the linked WordPress.com user
3142
	 */
3143
	public static function unlink_user( $user_id = null ) {
3144
		if ( ! $tokens = Jetpack_Options::get_option( 'user_tokens' ) )
3145
			return false;
3146
3147
		$user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
3148
3149
		if ( Jetpack_Options::get_option( 'master_user' ) == $user_id )
3150
			return false;
3151
3152
		if ( ! isset( $tokens[ $user_id ] ) )
3153
			return false;
3154
3155
		Jetpack::load_xml_rpc_client();
3156
		$xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
3157
		$xml->query( 'jetpack.unlink_user', $user_id );
3158
3159
		unset( $tokens[ $user_id ] );
3160
3161
		Jetpack_Options::update_option( 'user_tokens', $tokens );
3162
3163
		return true;
3164
	}
3165
3166
	/**
3167
	 * Attempts Jetpack registration.  If it fail, a state flag is set: @see ::admin_page_load()
3168
	 */
3169
	public static function try_registration() {
3170
		// Let's get some testing in beta versions and such.
3171
		if ( self::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
3172
			// Before attempting to connect, let's make sure that the domains are viable.
3173
			$domains_to_check = array_unique( array(
3174
				'siteurl' => parse_url( get_site_url(), PHP_URL_HOST ),
3175
				'homeurl' => parse_url( get_home_url(), PHP_URL_HOST ),
3176
			) );
3177
			foreach ( $domains_to_check as $domain ) {
3178
				$result = Jetpack_Data::is_usable_domain( $domain );
3179
				if ( is_wp_error( $result ) ) {
3180
					return $result;
3181
				}
3182
			}
3183
		}
3184
3185
		$result = Jetpack::register();
3186
3187
		// If there was an error with registration and the site was not registered, record this so we can show a message.
3188
		if ( ! $result || is_wp_error( $result ) ) {
3189
			return $result;
3190
		} else {
3191
			return true;
3192
		}
3193
	}
3194
3195
	/**
3196
	 * Tracking an internal event log. Try not to put too much chaff in here.
3197
	 *
3198
	 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
3199
	 */
3200
	public static function log( $code, $data = null ) {
3201
		// only grab the latest 200 entries
3202
		$log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3203
3204
		// Append our event to the log
3205
		$log_entry = array(
3206
			'time'    => time(),
3207
			'user_id' => get_current_user_id(),
3208
			'blog_id' => Jetpack_Options::get_option( 'id' ),
3209
			'code'    => $code,
3210
		);
3211
		// Don't bother storing it unless we've got some.
3212
		if ( ! is_null( $data ) ) {
3213
			$log_entry['data'] = $data;
3214
		}
3215
		$log[] = $log_entry;
3216
3217
		// Try add_option first, to make sure it's not autoloaded.
3218
		// @todo: Add an add_option method to Jetpack_Options
3219
		if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
3220
			Jetpack_Options::update_option( 'log', $log );
3221
		}
3222
3223
		/**
3224
		 * Fires when Jetpack logs an internal event.
3225
		 *
3226
		 * @since 3.0.0
3227
		 *
3228
		 * @param array $log_entry {
3229
		 *	Array of details about the log entry.
3230
		 *
3231
		 *	@param string time Time of the event.
3232
		 *	@param int user_id ID of the user who trigerred the event.
3233
		 *	@param int blog_id Jetpack Blog ID.
3234
		 *	@param string code Unique name for the event.
3235
		 *	@param string data Data about the event.
3236
		 * }
3237
		 */
3238
		do_action( 'jetpack_log_entry', $log_entry );
3239
	}
3240
3241
	/**
3242
	 * Get the internal event log.
3243
	 *
3244
	 * @param $event (string) - only return the specific log events
3245
	 * @param $num   (int)    - get specific number of latest results, limited to 200
3246
	 *
3247
	 * @return array of log events || WP_Error for invalid params
3248
	 */
3249
	public static function get_log( $event = false, $num = false ) {
3250
		if ( $event && ! is_string( $event ) ) {
3251
			return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
3252
		}
3253
3254
		if ( $num && ! is_numeric( $num ) ) {
3255
			return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
3256
		}
3257
3258
		$entire_log = Jetpack_Options::get_option( 'log', array() );
3259
3260
		// If nothing set - act as it did before, otherwise let's start customizing the output
3261
		if ( ! $num && ! $event ) {
3262
			return $entire_log;
3263
		} else {
3264
			$entire_log = array_reverse( $entire_log );
3265
		}
3266
3267
		$custom_log_output = array();
3268
3269
		if ( $event ) {
3270
			foreach ( $entire_log as $log_event ) {
3271
				if ( $event == $log_event[ 'code' ] ) {
3272
					$custom_log_output[] = $log_event;
3273
				}
3274
			}
3275
		} else {
3276
			$custom_log_output = $entire_log;
3277
		}
3278
3279
		if ( $num ) {
3280
			$custom_log_output = array_slice( $custom_log_output, 0, $num );
3281
		}
3282
3283
		return $custom_log_output;
3284
	}
3285
3286
	/**
3287
	 * Log modification of important settings.
3288
	 */
3289
	public static function log_settings_change( $option, $old_value, $value ) {
3290
		switch( $option ) {
3291
			case 'jetpack_sync_non_public_post_stati':
3292
				self::log( $option, $value );
3293
				break;
3294
		}
3295
	}
3296
3297
	/**
3298
	 * Return stat data for WPCOM sync
3299
	 */
3300
	function get_stat_data() {
3301
		$heartbeat_data = Jetpack_Heartbeat::generate_stats_array();
3302
		$additional_data = $this->get_additional_stat_data();
3303
3304
		return json_encode( array_merge( $heartbeat_data, $additional_data ) );
3305
	}
3306
3307
	/**
3308
	 * Get additional stat data to sync to WPCOM
3309
	 */
3310
	function get_additional_stat_data( $prefix = '' ) {
3311
		$return["{$prefix}themes"]         = Jetpack::get_parsed_theme_data();
3312
		$return["{$prefix}plugins-extra"]  = Jetpack::get_parsed_plugin_data();
3313
		$return["{$prefix}users"]          = count_users();
3314
		$return["{$prefix}site-count"]     = 0;
3315
		if ( function_exists( 'get_blog_count' ) ) {
3316
			$return["{$prefix}site-count"] = get_blog_count();
3317
		}
3318
		return $return;
3319
	}
3320
3321
	/* Admin Pages */
3322
3323
	function admin_init() {
3324
		// If the plugin is not connected, display a connect message.
3325
		if (
3326
			// the plugin was auto-activated and needs its candy
3327
			Jetpack_Options::get_option( 'do_activate' )
3328
		||
3329
			// the plugin is active, but was never activated.  Probably came from a site-wide network activation
3330
			! Jetpack_Options::get_option( 'activated' )
3331
		) {
3332
			Jetpack::plugin_initialize();
3333
		}
3334
3335
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
3336
			if ( 4 != Jetpack_Options::get_option( 'activated' ) ) {
3337
				// Show connect notice on dashboard and plugins pages
3338
				add_action( 'load-index.php', array( $this, 'prepare_connect_notice' ) );
3339
				add_action( 'load-plugins.php', array( $this, 'prepare_connect_notice' ) );
3340
			}
3341
		} elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
3342
			// Upgrade: 1.1 -> 1.1.1
3343
			// Check and see if host can verify the Jetpack servers' SSL certificate
3344
			$args = array();
3345
			Jetpack_Client::_wp_remote_request(
3346
				Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'test' ) ),
3347
				$args,
3348
				true
3349
			);
3350
		} else {
3351
			// Show the notice on the Dashboard only for now
3352
3353
			add_action( 'load-index.php', array( $this, 'prepare_manage_jetpack_notice' ) );
3354
3355
			// Identity crisis notices
3356
			add_action( 'jetpack_notices', array( $this, 'alert_identity_crisis' ) );
3357
		}
3358
3359
		// If the plugin has just been disconnected from WP.com, show the survey notice
3360
		if ( isset( $_GET['disconnected'] ) && 'true' === $_GET['disconnected'] ) {
3361
			add_action( 'jetpack_notices', array( $this, 'disconnect_survey_notice' ) );
3362
		}
3363
3364
		if ( current_user_can( 'manage_options' ) && 'ALWAYS' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
3365
			add_action( 'admin_notices', array( $this, 'alert_required_ssl_fail' ) );
3366
		}
3367
3368
		add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3369
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3370
		add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3371
3372
		if ( Jetpack::is_active() || Jetpack::is_development_mode() ) {
3373
			// Artificially throw errors in certain whitelisted cases during plugin activation
3374
			add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3375
3376
			// Kick off synchronization of user role when it changes
3377
			add_action( 'set_user_role', array( $this, 'user_role_change' ) );
3378
		}
3379
3380
		// Jetpack Manage Activation Screen from .com
3381
		Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
3382
	}
3383
3384
	function admin_body_class( $admin_body_class = '' ) {
3385
		$classes = explode( ' ', trim( $admin_body_class ) );
3386
3387
		$classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
3388
3389
		$admin_body_class = implode( ' ', array_unique( $classes ) );
3390
		return " $admin_body_class ";
3391
	}
3392
3393
	static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3394
		return $admin_body_class . ' jetpack-pagestyles ';
3395
	}
3396
3397
	function prepare_connect_notice() {
3398
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3399
3400
		add_action( 'admin_notices', array( $this, 'admin_connect_notice' ) );
3401
3402
		if ( Jetpack::state( 'network_nag' ) )
3403
			add_action( 'network_admin_notices', array( $this, 'network_connect_notice' ) );
3404
	}
3405
	/**
3406
	 * Call this function if you want the Big Jetpack Manage Notice to show up.
3407
	 *
3408
	 * @return null
3409
	 */
3410
	function prepare_manage_jetpack_notice() {
3411
3412
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3413
		add_action( 'admin_notices', array( $this, 'admin_jetpack_manage_notice' ) );
3414
	}
3415
3416
	function manage_activate_screen() {
3417
		include ( JETPACK__PLUGIN_DIR . 'modules/manage/activate-admin.php' );
3418
	}
3419
	/**
3420
	 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3421
	 * This function artificially throws errors for such cases (whitelisted).
3422
	 *
3423
	 * @param string $plugin The activated plugin.
3424
	 */
3425
	function throw_error_on_activate_plugin( $plugin ) {
3426
		$active_modules = Jetpack::get_active_modules();
3427
3428
		// The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3429
		if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3430
			$throw = false;
3431
3432
			// Try and make sure it really was the stats plugin
3433
			if ( ! class_exists( 'ReflectionFunction' ) ) {
3434
				if ( 'stats.php' == basename( $plugin ) ) {
3435
					$throw = true;
3436
				}
3437
			} else {
3438
				$reflection = new ReflectionFunction( 'stats_get_api_key' );
3439
				if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3440
					$throw = true;
3441
				}
3442
			}
3443
3444
			if ( $throw ) {
3445
				trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3446
			}
3447
		}
3448
	}
3449
3450
	function intercept_plugin_error_scrape_init() {
3451
		add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3452
	}
3453
3454
	function intercept_plugin_error_scrape( $action, $result ) {
3455
		if ( ! $result ) {
3456
			return;
3457
		}
3458
3459
		foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3460
			if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3461
				Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3462
			}
3463
		}
3464
	}
3465
3466
	function add_remote_request_handlers() {
3467
		add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
3468
	}
3469
3470
	function remote_request_handlers() {
3471
		switch ( current_filter() ) {
3472
		case 'wp_ajax_nopriv_jetpack_upload_file' :
3473
			$response = $this->upload_handler();
3474
			break;
3475
		default :
0 ignored issues
show
There must be no space before the colon in a DEFAULT statement

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

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

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

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

Loading history...
3476
			$response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
3477
			break;
3478
		}
3479
3480
		if ( ! $response ) {
3481
			$response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
3482
		}
3483
3484
		if ( is_wp_error( $response ) ) {
3485
			$status_code       = $response->get_error_data();
3486
			$error             = $response->get_error_code();
3487
			$error_description = $response->get_error_message();
3488
3489
			if ( ! is_int( $status_code ) ) {
3490
				$status_code = 400;
3491
			}
3492
3493
			status_header( $status_code );
3494
			die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3495
		}
3496
3497
		status_header( 200 );
3498
		if ( true === $response ) {
3499
			exit;
3500
		}
3501
3502
		die( json_encode( (object) $response ) );
3503
	}
3504
3505
	function upload_handler() {
3506
		if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3507
			return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
3508
		}
3509
3510
		$user = wp_authenticate( '', '' );
3511
		if ( ! $user || is_wp_error( $user ) ) {
3512
			return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
3513
		}
3514
3515
		wp_set_current_user( $user->ID );
3516
3517
		if ( ! current_user_can( 'upload_files' ) ) {
3518
			return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3519
		}
3520
3521
		if ( empty( $_FILES ) ) {
3522
			return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3523
		}
3524
3525
		foreach ( array_keys( $_FILES ) as $files_key ) {
3526
			if ( ! isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
3527
				return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3528
			}
3529
		}
3530
3531
		$media_keys = array_keys( $_FILES['media'] );
3532
3533
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
3534
		if ( ! $token || is_wp_error( $token ) ) {
3535
			return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3536
		}
3537
3538
		$uploaded_files = array();
3539
		$global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3540
		unset( $GLOBALS['post'] );
3541
		foreach ( $_FILES['media']['name'] as $index => $name ) {
3542
			$file = array();
3543
			foreach ( $media_keys as $media_key ) {
3544
				$file[$media_key] = $_FILES['media'][$media_key][$index];
3545
			}
3546
3547
			list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
3548
3549
			$hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3550
			if ( $hmac_provided !== $hmac_file ) {
3551
				$uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
3552
				continue;
3553
			}
3554
3555
			$_FILES['.jetpack.upload.'] = $file;
3556
			$post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
3557
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
3558
				$post_id = 0;
3559
			}
3560
			$attachment_id = media_handle_upload(
3561
				'.jetpack.upload.',
3562
				$post_id,
3563
				array(),
3564
				array(
3565
					'action' => 'jetpack_upload_file',
3566
				)
3567
			);
3568
3569
			if ( ! $attachment_id ) {
3570
				$uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
3571
			} elseif ( is_wp_error( $attachment_id ) ) {
3572
				$uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
3573
			} else {
3574
				$attachment = get_post( $attachment_id );
3575
				$uploaded_files[$index] = (object) array(
3576
					'id'   => (string) $attachment_id,
3577
					'file' => $attachment->post_title,
3578
					'url'  => wp_get_attachment_url( $attachment_id ),
3579
					'type' => $attachment->post_mime_type,
3580
					'meta' => wp_get_attachment_metadata( $attachment_id ),
3581
				);
3582
			}
3583
		}
3584
		if ( ! is_null( $global_post ) ) {
3585
			$GLOBALS['post'] = $global_post;
3586
		}
3587
3588
		return $uploaded_files;
3589
	}
3590
3591
	/**
3592
	 * Add help to the Jetpack page
3593
	 *
3594
	 * @since Jetpack (1.2.3)
3595
	 * @return false if not the Jetpack page
3596
	 */
3597
	function admin_help() {
3598
		$current_screen = get_current_screen();
3599
3600
		// Overview
3601
		$current_screen->add_help_tab(
3602
			array(
3603
				'id'		=> 'home',
3604
				'title'		=> __( 'Home', 'jetpack' ),
3605
				'content'	=>
3606
					'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3607
					'<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3608
					'<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>',
3609
			)
3610
		);
3611
3612
		// Screen Content
3613
		if ( current_user_can( 'manage_options' ) ) {
3614
			$current_screen->add_help_tab(
3615
				array(
3616
					'id'		=> 'settings',
3617
					'title'		=> __( 'Settings', 'jetpack' ),
3618
					'content'	=>
3619
						'<p><strong>' . __( 'Jetpack by WordPress.com',                                              'jetpack' ) . '</strong></p>' .
3620
						'<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3621
						'<ol>' .
3622
							'<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.',														'jetpack' ) . '</li>' .
3623
							'<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>' .
3624
						'</ol>' .
3625
						'<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>'
3626
				)
3627
			);
3628
		}
3629
3630
		// Help Sidebar
3631
		$current_screen->set_help_sidebar(
3632
			'<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3633
			'<p><a href="http://jetpack.com/faq/" target="_blank">'     . __( 'Jetpack FAQ',     'jetpack' ) . '</a></p>' .
3634
			'<p><a href="http://jetpack.com/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3635
			'<p><a href="' . Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) .'">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3636
		);
3637
	}
3638
3639
	function admin_menu_css() {
3640
		wp_enqueue_style( 'jetpack-icons' );
3641
	}
3642
3643
	function admin_menu_order() {
3644
		return true;
3645
	}
3646
3647 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
3648
		$jp_menu_order = array();
3649
3650
		foreach ( $menu_order as $index => $item ) {
3651
			if ( $item != 'jetpack' ) {
3652
				$jp_menu_order[] = $item;
3653
			}
3654
3655
			if ( $index == 0 ) {
3656
				$jp_menu_order[] = 'jetpack';
3657
			}
3658
		}
3659
3660
		return $jp_menu_order;
3661
	}
3662
3663
	function admin_head() {
3664 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) )
3665
			/** This action is documented in class.jetpack-admin-page.php */
3666
			do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
3667
	}
3668
3669
	function admin_banner_styles() {
3670
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3671
3672
		wp_enqueue_style( 'jetpack', plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION . '-20121016' );
3673
		wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3674
		wp_style_add_data( 'jetpack', 'suffix', $min );
3675
	}
3676
3677
	function admin_scripts() {
3678
		wp_enqueue_script( 'jetpack-js', plugins_url( '_inc/jp.js', JETPACK__PLUGIN_FILE ), array( 'jquery', 'wp-util' ), JETPACK__VERSION . '-20121111' );
3679
		wp_localize_script(
3680
			'jetpack-js',
3681
			'jetpackL10n',
3682
			array(
3683
				'ays_disconnect' => "This will deactivate all Jetpack modules.\nAre you sure you want to disconnect?",
3684
				'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?",
3685
				'ays_dismiss'    => "This will deactivate Jetpack.\nAre you sure you want to deactivate Jetpack?",
3686
			)
3687
		);
3688
		add_action( 'admin_footer', array( $this, 'do_stats' ) );
3689
	}
3690
3691
	function plugin_action_links( $actions ) {
3692
3693
		$jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack' ), __( 'Jetpack', 'jetpack' ) ) );
3694
3695
		if( current_user_can( 'jetpack_manage_modules' ) && ( Jetpack::is_active() || Jetpack::is_development_mode() ) ) {
3696
			return array_merge(
3697
				$jetpack_home,
3698
				array( 'settings' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack_modules' ), __( 'Settings', 'jetpack' ) ) ),
3699
				array( 'support' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack-debugger '), __( 'Support', 'jetpack' ) ) ),
3700
				$actions
3701
				);
3702
			}
3703
3704
		return array_merge( $jetpack_home, $actions );
3705
	}
3706
3707
	function admin_connect_notice() {
3708
		// Don't show the connect notice anywhere but the plugins.php after activating
3709
		$current = get_current_screen();
3710
		if ( 'plugins' !== $current->parent_base )
3711
			return;
3712
3713
		if ( ! current_user_can( 'jetpack_connect' ) )
3714
			return;
3715
3716
		$dismiss_and_deactivate_url = wp_nonce_url( Jetpack::admin_url( '?page=jetpack&jetpack-notice=dismiss' ), 'jetpack-deactivate' );
3717
		?>
3718
		<div id="message" class="updated jetpack-message jp-banner" style="display:block !important;">
3719
			<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>
3720
			<?php if ( in_array( Jetpack_Options::get_option( 'activated' ) , array( 1, 2, 3 ) ) ) : ?>
3721
				<div class="jp-banner__content is-connection">
3722
					<h2><?php _e( 'Your Jetpack is almost ready!', 'jetpack' ); ?></h2>
3723
					<p><?php _e( 'Connect now to enable features like Stats, Likes, and Social Sharing.', 'jetpack' ); ?></p>
3724
				</div>
3725
				<div class="jp-banner__action-container is-connection">
3726
						<a href="<?php echo $this->build_connect_url( false, false, 'banner' ) ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'Connect to WordPress.com', 'jetpack' ); ?></a>
3727
				</div>
3728 View Code Duplication
			<?php else : ?>
3729
				<div class="jp-banner__content">
3730
					<h2><?php _e( 'Jetpack is installed!', 'jetpack' ) ?></h2>
3731
					<p><?php _e( 'It\'s ready to bring awesome, WordPress.com cloud-powered features to your site.', 'jetpack' ) ?></p>
3732
				</div>
3733
				<div class="jp-banner__action-container">
3734
					<a href="<?php echo Jetpack::admin_url() ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'Learn More', 'jetpack' ); ?></a>
3735
				</div>
3736
			<?php endif; ?>
3737
		</div>
3738
3739
		<?php
3740
	}
3741
3742
	/**
3743
	 * This is the first banner
3744
	 * It should be visible only to user that can update the option
3745
	 * Are not connected
3746
	 *
3747
	 * @return null
3748
	 */
3749
	function admin_jetpack_manage_notice() {
3750
		$screen = get_current_screen();
3751
3752
		// Don't show the connect notice on the jetpack settings page.
3753
		if ( ! in_array( $screen->base, array( 'dashboard' ) ) || $screen->is_network || $screen->action )
3754
			return;
3755
3756
		// Only show it if don't have the managment option set.
3757
		// And not dismissed it already.
3758
		if ( ! $this->can_display_jetpack_manage_notice() || Jetpack_Options::get_option( 'dismissed_manage_banner' ) ) {
3759
			return;
3760
		}
3761
3762
		$opt_out_url = $this->opt_out_jetpack_manage_url();
3763
		$opt_in_url  = $this->opt_in_jetpack_manage_url();
3764
		/**
3765
		 * I think it would be great to have different wordsing depending on where you are
3766
		 * for example if we show the notice on dashboard and a different one if we show it on Plugins screen
3767
		 * etc..
3768
		 */
3769
3770
		?>
3771
		<div id="message" class="updated jetpack-message jp-banner is-opt-in" style="display:block !important;">
3772
			<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>
3773
			<div class="jp-banner__content">
3774
				<h2><?php esc_html_e( 'New in Jetpack: Centralized Site Management', 'jetpack' ); ?></h2>
3775
				<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.com/support/site-management' ); ?></p>
3776
			</div>
3777
			<div class="jp-banner__action-container is-opt-in">
3778
				<a href="<?php echo esc_url( $opt_in_url ); ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'Activate now', 'jetpack' ); ?></a>
3779
			</div>
3780
		</div>
3781
		<?php
3782
	}
3783
3784
	/**
3785
	 * Returns the url that the user clicks to remove the notice for the big banner
3786
	 * @return (string)
3787
	 */
3788
	function opt_out_jetpack_manage_url() {
3789
		$referer = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
3790
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-out' . $referer ), 'jetpack_manage_banner_opt_out' );
3791
	}
3792
	/**
3793
	 * Returns the url that the user clicks to opt in to Jetpack Manage
3794
	 * @return (string)
3795
	 */
3796
	function opt_in_jetpack_manage_url() {
3797
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-in' ), 'jetpack_manage_banner_opt_in' );
3798
	}
3799
3800
	function opt_in_jetpack_manage_notice() {
3801
		?>
3802
		<div class="wrap">
3803
			<div id="message" class="jetpack-message is-opt-in">
3804
				<?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.com/support/site-management' ); ?>
3805
			</div>
3806
		</div>
3807
		<?php
3808
3809
	}
3810
	/**
3811
	 * Determines whether to show the notice of not true = display notice
3812
	 * @return (bool)
3813
	 */
3814
	function can_display_jetpack_manage_notice() {
3815
		// never display the notice to users that can't do anything about it anyways
3816
		if( ! current_user_can( 'jetpack_manage_modules' ) )
3817
			return false;
3818
3819
		// don't display if we are in development more
3820
		if( Jetpack::is_development_mode() ) {
3821
			return false;
3822
		}
3823
		// don't display if the site is private
3824
		if(  ! Jetpack_Options::get_option( 'public' ) )
3825
			return false;
3826
3827
		/**
3828
		 * Should the Jetpack Remote Site Management notice be displayed.
3829
		 *
3830
		 * @since 3.3.0
3831
		 *
3832
		 * @param bool ! self::is_module_active( 'manage' ) Is the Manage module inactive.
3833
		 */
3834
		return apply_filters( 'can_display_jetpack_manage_notice', ! self::is_module_active( 'manage' ) );
3835
	}
3836
3837
	function network_connect_notice() {
3838
		?>
3839
		<div id="message" class="updated jetpack-message">
3840
			<div class="squeezer">
3841
				<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>
3842
			</div>
3843
		</div>
3844
		<?php
3845
	}
3846
3847
	public static function jetpack_comment_notice() {
3848
		if ( in_array( 'comments', Jetpack::get_active_modules() ) ) {
3849
			return '';
3850
		}
3851
3852
		$jetpack_old_version = explode( ':', Jetpack_Options::get_option( 'old_version' ) );
3853
		$jetpack_new_version = explode( ':', Jetpack_Options::get_option( 'version' ) );
3854
3855
		if ( $jetpack_old_version ) {
3856
			if ( version_compare( $jetpack_old_version[0], '1.4', '>=' ) ) {
3857
				return '';
3858
			}
3859
		}
3860
3861
		if ( $jetpack_new_version ) {
3862
			if ( version_compare( $jetpack_new_version[0], '1.4-something', '<' ) ) {
3863
				return '';
3864
			}
3865
		}
3866
3867
		return '<br /><br />' . sprintf(
3868
			__( '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' ),
3869
			wp_nonce_url(
3870
				Jetpack::admin_url(
3871
					array(
3872
						'page'   => 'jetpack',
3873
						'action' => 'activate',
3874
						'module' => 'comments',
3875
					)
3876
				),
3877
				'jetpack_activate-comments'
3878
			),
3879
			__( 'click here', 'jetpack' )
3880
		);
3881
	}
3882
3883
	/**
3884
	 * Show the survey link when the user has just disconnected Jetpack.
3885
	 */
3886
	function disconnect_survey_notice() {
3887
		?>
3888
		<div class="wrap">
3889
			<div id="message" class="jetpack-message stay-visible">
3890
				<div class="squeezer">
3891
					<h2>
3892
						<?php _e( 'You have successfully disconnected Jetpack.', 'jetpack' ); ?>
3893
						<br />
3894
						<?php echo sprintf(
3895
							__( 'Would you tell us why? Just <a href="%1$s" target="%2$s">answering two simple questions</a> would help us improve Jetpack.', 'jetpack' ),
3896
							'https://jetpack.com/survey-disconnected/',
3897
							'_blank'
3898
						); ?>
3899
					</h2>
3900
				</div>
3901
			</div>
3902
		</div>
3903
		<?php
3904
	}
3905
3906
	/*
3907
	 * Registration flow:
3908
	 * 1 - ::admin_page_load() action=register
3909
	 * 2 - ::try_registration()
3910
	 * 3 - ::register()
3911
	 *     - Creates jetpack_register option containing two secrets and a timestamp
3912
	 *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3913
	 *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3914
	 *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
3915
	 *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
3916
	 *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
3917
	 *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
3918
	 *       jetpack_id, jetpack_secret, jetpack_public
3919
	 *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
3920
	 * 4 - redirect to https://wordpress.com/start/jetpack-connect
3921
	 * 5 - user logs in with WP.com account
3922
	 * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
3923
	 *		- Jetpack_Client_Server::authorize()
3924
	 *		- Jetpack_Client_Server::get_token()
3925
	 *		- GET https://jetpack.wordpress.com/jetpack.token/1/ with
3926
	 *        client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
3927
	 *			- which responds with access_token, token_type, scope
3928
	 *		- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
3929
	 *		- Jetpack::activate_default_modules()
3930
	 *     		- Deactivates deprecated plugins
3931
	 *     		- Activates all default modules
3932
	 *		- Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
3933
	 * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
3934
	 * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
3935
	 *     Done!
3936
	 */
3937
3938
	/**
3939
	 * Handles the page load events for the Jetpack admin page
3940
	 */
3941
	function admin_page_load() {
3942
		$error = false;
3943
3944
		// Make sure we have the right body class to hook stylings for subpages off of.
3945
		add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ) );
3946
3947
		if ( ! empty( $_GET['jetpack_restate'] ) ) {
3948
			// Should only be used in intermediate redirects to preserve state across redirects
3949
			Jetpack::restate();
3950
		}
3951
3952
		if ( isset( $_GET['connect_url_redirect'] ) ) {
3953
			// User clicked in the iframe to link their accounts
3954
			if ( ! Jetpack::is_user_connected() ) {
3955
				$connect_url = $this->build_connect_url( true, false, 'iframe' );
3956
				if ( isset( $_GET['notes_iframe'] ) )
3957
					$connect_url .= '&notes_iframe';
3958
				wp_redirect( $connect_url );
3959
				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...
3960
			} else {
3961
				Jetpack::state( 'message', 'already_authorized' );
3962
				wp_safe_redirect( Jetpack::admin_url() );
3963
				exit;
3964
			}
3965
		}
3966
3967
3968
		if ( isset( $_GET['action'] ) ) {
3969
			switch ( $_GET['action'] ) {
3970
			case 'authorize':
3971
				if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
3972
					Jetpack::state( 'message', 'already_authorized' );
3973
					wp_safe_redirect( Jetpack::admin_url() );
3974
					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...
3975
				}
3976
				Jetpack::log( 'authorize' );
3977
				$client_server = new Jetpack_Client_Server;
3978
				$client_server->client_authorize();
3979
				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...
3980
			case 'register' :
3981
				if ( ! current_user_can( 'jetpack_connect' ) ) {
3982
					$error = 'cheatin';
3983
					break;
3984
				}
3985
				check_admin_referer( 'jetpack-register' );
3986
				Jetpack::log( 'register' );
3987
				Jetpack::maybe_set_version_option();
3988
				$registered = Jetpack::try_registration();
3989
				if ( is_wp_error( $registered ) ) {
3990
					$error = $registered->get_error_code();
3991
					Jetpack::state( 'error_description', $registered->get_error_message() );
3992
					break;
3993
				}
3994
				
3995
				$from = isset( $_GET['from'] ) ? $_GET['from'] : false;
3996
3997
				wp_redirect( $this->build_connect_url( true, false, $from ) );
3998
				exit;
3999
			case 'activate' :
4000
				if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
4001
					$error = 'cheatin';
4002
					break;
4003
				}
4004
4005
				$module = stripslashes( $_GET['module'] );
4006
				check_admin_referer( "jetpack_activate-$module" );
4007
				Jetpack::log( 'activate', $module );
4008
				Jetpack::activate_module( $module );
4009
				// The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
4010
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4011
				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...
4012
			case 'activate_default_modules' :
4013
				check_admin_referer( 'activate_default_modules' );
4014
				Jetpack::log( 'activate_default_modules' );
4015
				Jetpack::restate();
4016
				$min_version   = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
4017
				$max_version   = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
4018
				$other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
4019
				Jetpack::activate_default_modules( $min_version, $max_version, $other_modules );
4020
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4021
				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...
4022
			case 'disconnect' :
4023
				if ( ! current_user_can( 'jetpack_disconnect' ) ) {
4024
					$error = 'cheatin';
4025
					break;
4026
				}
4027
4028
				check_admin_referer( 'jetpack-disconnect' );
4029
				Jetpack::log( 'disconnect' );
4030
				Jetpack::disconnect();
4031
				wp_safe_redirect( Jetpack::admin_url( 'disconnected=true' ) );
4032
				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...
4033
			case 'reconnect' :
4034
				if ( ! current_user_can( 'jetpack_reconnect' ) ) {
4035
					$error = 'cheatin';
4036
					break;
4037
				}
4038
4039
				check_admin_referer( 'jetpack-reconnect' );
4040
				Jetpack::log( 'reconnect' );
4041
				$this->disconnect();
4042
				wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
4043
				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...
4044 View Code Duplication
			case 'deactivate' :
4045
				if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
4046
					$error = 'cheatin';
4047
					break;
4048
				}
4049
4050
				$modules = stripslashes( $_GET['module'] );
4051
				check_admin_referer( "jetpack_deactivate-$modules" );
4052
				foreach ( explode( ',', $modules ) as $module ) {
4053
					Jetpack::log( 'deactivate', $module );
4054
					Jetpack::deactivate_module( $module );
4055
					Jetpack::state( 'message', 'module_deactivated' );
4056
				}
4057
				Jetpack::state( 'module', $modules );
4058
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4059
				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...
4060
			case 'unlink' :
4061
				$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
4062
				check_admin_referer( 'jetpack-unlink' );
4063
				Jetpack::log( 'unlink' );
4064
				$this->unlink_user();
4065
				Jetpack::state( 'message', 'unlinked' );
4066
				if ( 'sub-unlink' == $redirect ) {
4067
					wp_safe_redirect( admin_url() );
4068
				} else {
4069
					wp_safe_redirect( Jetpack::admin_url( array( 'page' => $redirect ) ) );
4070
				}
4071
				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...
4072
			default:
4073
				/**
4074
				 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
4075
				 *
4076
				 * @since 2.6.0
4077
				 *
4078
				 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
4079
				 */
4080
				do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
4081
			}
4082
		}
4083
4084
		if ( ! $error = $error ? $error : Jetpack::state( 'error' ) ) {
4085
			self::activate_new_modules( true );
4086
		}
4087
4088
		switch ( $error ) {
4089
		case 'cheatin' :
4090
			$this->error = __( 'Cheatin&#8217; uh?', 'jetpack' );
4091
			break;
4092
		case 'access_denied' :
4093
			$this->error = sprintf( __( 'Would you mind telling us why you did not complete the Jetpack connection in this <a href="%s">1 question survey</a>?', 'jetpack' ), 'http://jetpack.com/cancelled-connection/' ) . '<br /><small>' . __( 'A Jetpack connection is required for our free security and traffic features to work.', 'jetpack' ) . '</small>';
4094
			break;
4095
		case 'wrong_state' :
4096
			$this->error = __( 'You need to stay logged in to your WordPress blog while you authorize Jetpack.', 'jetpack' );
4097
			break;
4098
		case 'invalid_client' :
4099
			// @todo re-register instead of deactivate/reactivate
4100
			$this->error = __( 'We had an issue connecting Jetpack; deactivate then reactivate the Jetpack plugin, then connect again.', 'jetpack' );
4101
			break;
4102
		case 'invalid_grant' :
4103
			$this->error = __( 'There was an issue connecting your Jetpack. Please click &#8220;Connect to WordPress.com&#8221; again.', 'jetpack' );
4104
			break;
4105
		case 'site_inaccessible' :
4106
		case 'site_requires_authorization' :
4107
			$this->error = sprintf( __( 'Your website needs to be publicly accessible to use Jetpack: %s', 'jetpack' ), "<code>$error</code>" );
4108
			break;
4109
		case 'module_activation_failed' :
4110
			$module = Jetpack::state( 'module' );
4111
			if ( ! empty( $module ) && $mod = Jetpack::get_module( $module ) ) {
4112
				$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'] );
4113
				if ( isset( $this->plugins_to_deactivate[$module] ) ) {
4114
					$this->error .= ' ' . sprintf( __( 'Do you still have the %s plugin installed?', 'jetpack' ), $this->plugins_to_deactivate[$module][1] );
4115
				}
4116
			} else {
4117
				$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' );
4118
			}
4119
			if ( $php_errors = Jetpack::state( 'php_errors' ) ) {
4120
				$this->error .= "<br />\n";
4121
				$this->error .= $php_errors;
4122
			}
4123
			break;
4124
		case 'master_user_required' :
4125
			$module = Jetpack::state( 'module' );
4126
			$module_name = '';
4127
			if ( ! empty( $module ) && $mod = Jetpack::get_module( $module ) ) {
4128
				$module_name = $mod['name'];
4129
			}
4130
4131
			$master_user = Jetpack_Options::get_option( 'master_user' );
4132
			$master_userdata = get_userdata( $master_user ) ;
4133
			if ( $master_userdata ) {
4134
				if ( ! in_array( $module, Jetpack::get_active_modules() ) ) {
4135
					$this->error = sprintf( __( '%s was not activated.' , 'jetpack' ), $module_name );
4136
				} else {
4137
					$this->error = sprintf( __( '%s was not deactivated.' , 'jetpack' ), $module_name );
4138
				}
4139
				$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 ) );
4140
4141
			} else {
4142
				$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 );
4143
			}
4144
			break;
4145
		case 'not_public' :
4146
			$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' );
4147
			break;
4148
		case 'wpcom_408' :
4149
		case 'wpcom_5??' :
4150
		case 'wpcom_bad_response' :
4151
		case 'wpcom_outage' :
4152
			$this->error = __( 'WordPress.com is currently having problems and is unable to fuel up your Jetpack.  Please try again later.', 'jetpack' );
4153
			break;
4154
		case 'register_http_request_failed' :
4155
		case 'token_http_request_failed' :
4156
			$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>" );
4157
			break;
4158
		default :
0 ignored issues
show
There must be no space before the colon in a DEFAULT statement

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

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

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

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

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