Completed
Push — deprecate/omnisearch ( b6b013 )
by George
08:13
created

class.jetpack.php (22 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
		'gravatar-hovercards' => array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
70
		'latex'               => array( 'wp-latex/wp-latex.php', 'WP LaTeX' )
71
	);
72
73
	public $capability_translations = array(
74
		'administrator' => 'manage_options',
75
		'editor'        => 'edit_others_posts',
76
		'author'        => 'publish_posts',
77
		'contributor'   => 'edit_posts',
78
		'subscriber'    => 'read',
79
	);
80
81
	/**
82
	 * Map of modules that have conflicts with plugins and should not be auto-activated
83
	 * if the plugins are active.  Used by filter_default_modules
84
	 *
85
	 * Plugin Authors: If you'd like to prevent a single module from auto-activating,
86
	 * change `module-slug` and add this to your plugin:
87
	 *
88
	 * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
89
	 * function my_jetpack_get_default_modules( $modules ) {
90
	 *     return array_diff( $modules, array( 'module-slug' ) );
91
	 * }
92
	 *
93
	 * @var array
94
	 */
95
	private $conflicting_plugins = array(
96
		'comments'          => array(
97
			'Intense Debate'                       => 'intensedebate/intensedebate.php',
98
			'Disqus'                               => 'disqus-comment-system/disqus.php',
99
			'Livefyre'                             => 'livefyre-comments/livefyre.php',
100
			'Comments Evolved for WordPress'       => 'gplus-comments/comments-evolved.php',
101
			'Google+ Comments'                     => 'google-plus-comments/google-plus-comments.php',
102
			'WP-SpamShield Anti-Spam'              => 'wp-spamshield/wp-spamshield.php',
103
		),
104
		'contact-form'      => array(
105
			'Contact Form 7'                       => 'contact-form-7/wp-contact-form-7.php',
106
			'Gravity Forms'                        => 'gravityforms/gravityforms.php',
107
			'Contact Form Plugin'                  => 'contact-form-plugin/contact_form.php',
108
			'Easy Contact Forms'                   => 'easy-contact-forms/easy-contact-forms.php',
109
			'Fast Secure Contact Form'             => 'si-contact-form/si-contact-form.php',
110
		),
111
		'minileven'         => array(
112
			'WPtouch'                              => 'wptouch/wptouch.php',
113
		),
114
		'latex'             => array(
115
			'LaTeX for WordPress'                  => 'latex/latex.php',
116
			'Youngwhans Simple Latex'              => 'youngwhans-simple-latex/yw-latex.php',
117
			'Easy WP LaTeX'                        => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
118
			'MathJax-LaTeX'                        => 'mathjax-latex/mathjax-latex.php',
119
			'Enable Latex'                         => 'enable-latex/enable-latex.php',
120
			'WP QuickLaTeX'                        => 'wp-quicklatex/wp-quicklatex.php',
121
		),
122
		'protect'           => array(
123
			'Limit Login Attempts'                 => 'limit-login-attempts/limit-login-attempts.php',
124
			'Captcha'                              => 'captcha/captcha.php',
125
			'Brute Force Login Protection'         => 'brute-force-login-protection/brute-force-login-protection.php',
126
			'Login Security Solution'              => 'login-security-solution/login-security-solution.php',
127
			'WPSecureOps Brute Force Protect'      => 'wpsecureops-bruteforce-protect/wpsecureops-bruteforce-protect.php',
128
			'BulletProof Security'                 => 'bulletproof-security/bulletproof-security.php',
129
			'SiteGuard WP Plugin'                  => 'siteguard/siteguard.php',
130
			'Security-protection'                  => 'security-protection/security-protection.php',
131
			'Login Security'                       => 'login-security/login-security.php',
132
			'Botnet Attack Blocker'                => 'botnet-attack-blocker/botnet-attack-blocker.php',
133
			'Wordfence Security'                   => 'wordfence/wordfence.php',
134
			'All In One WP Security & Firewall'    => 'all-in-one-wp-security-and-firewall/wp-security.php',
135
			'iThemes Security'                     => 'better-wp-security/better-wp-security.php',
136
		),
137
		'random-redirect'   => array(
138
			'Random Redirect 2'                    => 'random-redirect-2/random-redirect.php',
139
		),
140
		'related-posts'     => array(
141
			'YARPP'                                => 'yet-another-related-posts-plugin/yarpp.php',
142
			'WordPress Related Posts'              => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
143
			'nrelate Related Content'              => 'nrelate-related-content/nrelate-related.php',
144
			'Contextual Related Posts'             => 'contextual-related-posts/contextual-related-posts.php',
145
			'Related Posts for WordPress'          => 'microkids-related-posts/microkids-related-posts.php',
146
			'outbrain'                             => 'outbrain/outbrain.php',
147
			'Shareaholic'                          => 'shareaholic/shareaholic.php',
148
			'Sexybookmarks'                        => 'sexybookmarks/shareaholic.php',
149
		),
150
		'sharedaddy'        => array(
151
			'AddThis'                              => 'addthis/addthis_social_widget.php',
152
			'Add To Any'                           => 'add-to-any/add-to-any.php',
153
			'ShareThis'                            => 'share-this/sharethis.php',
154
			'Shareaholic'                          => 'shareaholic/shareaholic.php',
155
		),
156
		'verification-tools' => array(
157
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
158
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
159
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
160
		),
161
		'widget-visibility' => array(
162
			'Widget Logic'                         => 'widget-logic/widget_logic.php',
163
			'Dynamic Widgets'                      => 'dynamic-widgets/dynamic-widgets.php',
164
		),
165
		'sitemaps' => array(
166
			'Google XML Sitemaps'                  => 'google-sitemap-generator/sitemap.php',
167
			'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
168
			'Google XML Sitemaps for qTranslate'   => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php',
169
			'XML Sitemap & Google News feeds'      => 'xml-sitemap-feed/xml-sitemap.php',
170
			'Google Sitemap by BestWebSoft'        => 'google-sitemap-plugin/google-sitemap-plugin.php',
171
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
172
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
173
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
174
			'Sitemap'                              => 'sitemap/sitemap.php',
175
			'Simple Wp Sitemap'                    => 'simple-wp-sitemap/simple-wp-sitemap.php',
176
			'Simple Sitemap'                       => 'simple-sitemap/simple-sitemap.php',
177
			'XML Sitemaps'                         => 'xml-sitemaps/xml-sitemaps.php',
178
			'MSM Sitemaps'                         => 'msm-sitemap/msm-sitemap.php',
179
		),
180
	);
181
182
	/**
183
	 * Plugins for which we turn off our Facebook OG Tags implementation.
184
	 *
185
	 * Note: WordPress SEO by Yoast and WordPress SEO Premium by Yoast automatically deactivate
186
	 * Jetpack's Open Graph tags via filter when their Social Meta modules are active.
187
	 *
188
	 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
189
	 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
190
	 */
191
	private $open_graph_conflicting_plugins = array(
192
		'2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
193
		                                                         // 2 Click Social Media Buttons
194
		'add-link-to-facebook/add-link-to-facebook.php',         // Add Link to Facebook
195
		'add-meta-tags/add-meta-tags.php',                       // Add Meta Tags
196
		'easy-facebook-share-thumbnails/esft.php',               // Easy Facebook Share Thumbnail
197
		'facebook/facebook.php',                                 // Facebook (official plugin)
198
		'facebook-awd/AWD_facebook.php',                         // Facebook AWD All in one
199
		'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
200
		                                                         // Facebook Featured Image & OG Meta Tags
201
		'facebook-meta-tags/facebook-metatags.php',              // Facebook Meta Tags
202
		'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
203
		                                                         // Facebook Open Graph Meta Tags for WordPress
204
		'facebook-revised-open-graph-meta-tag/index.php',        // Facebook Revised Open Graph Meta Tag
205
		'facebook-thumb-fixer/_facebook-thumb-fixer.php',        // Facebook Thumb Fixer
206
		'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
207
		                                                         // Fedmich's Facebook Open Graph Meta
208
		'header-footer/plugin.php',                              // Header and Footer
209
		'network-publisher/networkpub.php',                      // Network Publisher
210
		'nextgen-facebook/nextgen-facebook.php',                 // NextGEN Facebook OG
211
		'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php',
212
		                                                         // NextScripts SNAP
213
		'opengraph/opengraph.php',                               // Open Graph
214
		'open-graph-protocol-framework/open-graph-protocol-framework.php',
215
		                                                         // Open Graph Protocol Framework
216
		'seo-facebook-comments/seofacebook.php',                 // SEO Facebook Comments
217
		'seo-ultimate/seo-ultimate.php',                         // SEO Ultimate
218
		'sexybookmarks/sexy-bookmarks.php',                      // Shareaholic
219
		'shareaholic/sexy-bookmarks.php',                        // Shareaholic
220
		'sharepress/sharepress.php',                             // SharePress
221
		'simple-facebook-connect/sfc.php',                       // Simple Facebook Connect
222
		'social-discussions/social-discussions.php',             // Social Discussions
223
		'social-sharing-toolkit/social_sharing_toolkit.php',     // Social Sharing Toolkit
224
		'socialize/socialize.php',                               // Socialize
225
		'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
226
		                                                         // Tweet, Like, Google +1 and Share
227
		'wordbooker/wordbooker.php',                             // Wordbooker
228
		'wpsso/wpsso.php',                                       // WordPress Social Sharing Optimization
229
		'wp-caregiver/wp-caregiver.php',                         // WP Caregiver
230
		'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
231
		                                                         // WP Facebook Like Send & Open Graph Meta
232
		'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',   // WP Facebook Open Graph protocol
233
		'wp-ogp/wp-ogp.php',                                     // WP-OGP
234
		'zoltonorg-social-plugin/zosp.php',                      // Zolton.org Social Plugin
235
		'wp-fb-share-like-button/wp_fb_share-like_widget.php'    // WP Facebook Like Button
236
	);
237
238
	/**
239
	 * Plugins for which we turn off our Twitter Cards Tags implementation.
240
	 */
241
	private $twitter_cards_conflicting_plugins = array(
242
	//	'twitter/twitter.php',                       // The official one handles this on its own.
243
	//	                                             // https://github.com/twitter/wordpress/blob/master/src/Twitter/WordPress/Cards/Compatibility.php
244
		'eewee-twitter-card/index.php',              // Eewee Twitter Card
245
		'ig-twitter-cards/ig-twitter-cards.php',     // IG:Twitter Cards
246
		'jm-twitter-cards/jm-twitter-cards.php',     // JM Twitter Cards
247
		'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',
248
		                                             // Pure Web Brilliant's Social Graph Twitter Cards Extension
249
		'twitter-cards/twitter-cards.php',           // Twitter Cards
250
		'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta
251
		'wp-twitter-cards/twitter_cards.php',        // WP Twitter Cards
252
	);
253
254
	/**
255
	 * Message to display in admin_notice
256
	 * @var string
257
	 */
258
	public $message = '';
259
260
	/**
261
	 * Error to display in admin_notice
262
	 * @var string
263
	 */
264
	public $error = '';
265
266
	/**
267
	 * Modules that need more privacy description.
268
	 * @var string
269
	 */
270
	public $privacy_checks = '';
271
272
	/**
273
	 * Stats to record once the page loads
274
	 *
275
	 * @var array
276
	 */
277
	public $stats = array();
278
279
	/**
280
	 * Allows us to build a temporary security report
281
	 *
282
	 * @var array
283
	 */
284
	static $security_report = array();
285
286
	/**
287
	 * Jetpack_Sync object
288
	 */
289
	public $sync;
290
291
	/**
292
	 * Verified data for JSON authorization request
293
	 */
294
	public $json_api_authorization_request = array();
295
296
	/**
297
	 * Holds the singleton instance of this class
298
	 * @since 2.3.3
299
	 * @var Jetpack
300
	 */
301
	static $instance = false;
302
303
	/**
304
	 * Singleton
305
	 * @static
306
	 */
307
	public static function init() {
308
		if ( ! self::$instance ) {
309
			if ( did_action( 'plugins_loaded' ) )
310
				self::plugin_textdomain();
311
			else
312
				add_action( 'plugins_loaded', array( __CLASS__, 'plugin_textdomain' ), 99 );
313
314
			self::$instance = new Jetpack;
315
316
			self::$instance->plugin_upgrade();
317
318
			add_action( 'init', array( __CLASS__, 'perform_security_reporting' ) );
319
320
		}
321
322
		return self::$instance;
323
	}
324
325
	/**
326
	 * Must never be called statically
327
	 */
328
	function plugin_upgrade() {
329
		if ( Jetpack::is_active() ) {
330
			list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
331
			if ( JETPACK__VERSION != $version ) {
332
333
				// Check which active modules actually exist and remove others from active_modules list
334
				$unfiltered_modules = Jetpack::get_active_modules();
335
				$modules = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) );
336
				if ( array_diff( $unfiltered_modules, $modules ) ) {
337
					Jetpack_Options::update_option( 'active_modules', $modules );
338
				}
339
340
				add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
341
				/**
342
				 * Fires when synchronizing all registered options and constants.
343
				 *
344
				 * @since 3.3.0
345
				 */
346
				do_action( 'jetpack_sync_all_registered_options' );
347
			}
348
		}
349
	}
350
351
	static function activate_manage( ) {
352
353
		if ( did_action( 'init' ) || current_filter() == 'init' ) {
354
			self::activate_module( 'manage', false, false );
355
		} else if ( !  has_action( 'init' , array( __CLASS__, 'activate_manage' ) ) ) {
356
			add_action( 'init', array( __CLASS__, 'activate_manage' ) );
357
		}
358
359
	}
360
361
	/**
362
	 * Constructor.  Initializes WordPress hooks
363
	 */
364
	private function __construct() {
365
		/*
366
		 * Check for and alert any deprecated hooks
367
		 */
368
		add_action( 'init', array( $this, 'deprecated_hooks' ) );
369
370
		/*
371
		 * Do things that should run even in the network admin
372
		 * here, before we potentially fail out.
373
		 */
374
		add_filter( 'jetpack_require_lib_dir', array( $this, 'require_lib_dir' ) );
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_true' );
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' ), '3.10', 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
	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
		$version = Jetpack_Options::get_option( 'version' );
1701 View Code Duplication
		if ( ! $version ) {
1702
			$version = $old_version = JETPACK__VERSION . ':' . time();
1703
			/** This action is documented in class.jetpack.php */
1704
			do_action( 'updating_jetpack_version', $version, false );
1705
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1706
		}
1707
		list( $version ) = explode( ':', $version );
1708
1709
		$modules = array_filter( Jetpack::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1710
1711
		$modules_data = array();
1712
1713
		// Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1714
		if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1715
			$updated_modules = array();
1716
			foreach ( $modules as $module ) {
1717
				$modules_data[ $module ] = Jetpack::get_module( $module );
1718
				if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1719
					continue;
1720
				}
1721
1722
				if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1723
					continue;
1724
				}
1725
1726
				$updated_modules[] = $module;
1727
			}
1728
1729
			$modules = array_diff( $modules, $updated_modules );
1730
		}
1731
1732
		$is_development_mode = Jetpack::is_development_mode();
1733
1734
		foreach ( $modules as $index => $module ) {
1735
			// If we're in dev mode, disable modules requiring a connection
1736
			if ( $is_development_mode ) {
1737
				// Prime the pump if we need to
1738
				if ( empty( $modules_data[ $module ] ) ) {
1739
					$modules_data[ $module ] = Jetpack::get_module( $module );
1740
				}
1741
				// If the module requires a connection, but we're in local mode, don't include it.
1742
				if ( $modules_data[ $module ]['requires_connection'] ) {
1743
					continue;
1744
				}
1745
			}
1746
1747
			if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1748
				continue;
1749
			}
1750
1751
			if ( ! @include( Jetpack::get_module_path( $module ) ) ) {
1752
				unset( $modules[ $index ] );
1753
				Jetpack_Options::update_option( 'active_modules', array_values( $modules ) );
1754
				continue;
1755
			}
1756
1757
			/**
1758
			 * Fires when a specific module is loaded.
1759
			 * The dynamic part of the hook, $module, is the module slug.
1760
			 *
1761
			 * @since 1.1.0
1762
			 */
1763
			do_action( 'jetpack_module_loaded_' . $module );
1764
		}
1765
1766
		/**
1767
		 * Fires when all the modules are loaded.
1768
		 *
1769
		 * @since 1.1.0
1770
		 */
1771
		do_action( 'jetpack_modules_loaded' );
1772
1773
		// 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.
1774
		if ( Jetpack::is_active() || Jetpack::is_development_mode() )
1775
			require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' );
1776
	}
1777
1778
	/**
1779
	 * Check if Jetpack's REST API compat file should be included
1780
	 * @action plugins_loaded
1781
	 * @return null
1782
	 */
1783
	public function check_rest_api_compat() {
1784
		/**
1785
		 * Filters the list of REST API compat files to be included.
1786
		 *
1787
		 * @since 2.2.5
1788
		 *
1789
		 * @param array $args Array of REST API compat files to include.
1790
		 */
1791
		$_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1792
1793
		if ( function_exists( 'bbpress' ) )
1794
			$_jetpack_rest_api_compat_includes[] = JETPACK__PLUGIN_DIR . 'class.jetpack-bbpress-json-api-compat.php';
1795
1796
		foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include )
1797
			require_once $_jetpack_rest_api_compat_include;
1798
	}
1799
1800
	/**
1801
	 * Gets all plugins currently active in values, regardless of whether they're
1802
	 * traditionally activated or network activated.
1803
	 *
1804
	 * @todo Store the result in core's object cache maybe?
1805
	 */
1806
	public static function get_active_plugins() {
1807
		$active_plugins = (array) get_option( 'active_plugins', array() );
1808
1809
		if ( is_multisite() ) {
1810
			// Due to legacy code, active_sitewide_plugins stores them in the keys,
1811
			// whereas active_plugins stores them in the values.
1812
			$network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1813
			if ( $network_plugins ) {
1814
				$active_plugins = array_merge( $active_plugins, $network_plugins );
1815
			}
1816
		}
1817
1818
		sort( $active_plugins );
1819
1820
		return array_unique( $active_plugins );
1821
	}
1822
1823
	/**
1824
	 * Gets and parses additional plugin data to send with the heartbeat data
1825
	 *
1826
	 * @since 3.8.1
1827
	 *
1828
	 * @return array Array of plugin data
1829
	 */
1830
	public static function get_parsed_plugin_data() {
1831
		if ( ! function_exists( 'get_plugins' ) ) {
1832
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
1833
		}
1834
		$all_plugins    = get_plugins();
1835
		$active_plugins = Jetpack::get_active_plugins();
1836
1837
		$plugins = array();
1838
		foreach ( $all_plugins as $path => $plugin_data ) {
1839
			$plugins[ $path ] = array(
1840
					'is_active' => in_array( $path, $active_plugins ),
1841
					'file'      => $path,
1842
					'name'      => $plugin_data['Name'],
1843
					'version'   => $plugin_data['Version'],
1844
					'author'    => $plugin_data['Author'],
1845
			);
1846
		}
1847
1848
		return $plugins;
1849
	}
1850
1851
	/**
1852
	 * Gets and parses theme data to send with the heartbeat data
1853
	 *
1854
	 * @since 3.8.1
1855
	 *
1856
	 * @return array Array of theme data
1857
	 */
1858
	public static function get_parsed_theme_data() {
1859
		$all_themes = wp_get_themes( array( 'allowed' => true ) );
1860
		$header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
1861
1862
		$themes = array();
1863
		foreach ( $all_themes as $slug => $theme_data ) {
1864
			$theme_headers = array();
1865
			foreach ( $header_keys as $header_key ) {
1866
				$theme_headers[ $header_key ] = $theme_data->get( $header_key );
1867
			}
1868
1869
			$themes[ $slug ] = array(
1870
					'is_active_theme' => $slug == wp_get_theme()->get_template(),
1871
					'slug' => $slug,
1872
					'theme_root' => $theme_data->get_theme_root_uri(),
1873
					'parent' => $theme_data->parent(),
1874
					'headers' => $theme_headers
1875
			);
1876
		}
1877
1878
		return $themes;
1879
	}
1880
1881
	/**
1882
	 * Checks whether a specific plugin is active.
1883
	 *
1884
	 * We don't want to store these in a static variable, in case
1885
	 * there are switch_to_blog() calls involved.
1886
	 */
1887
	public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
1888
		return in_array( $plugin, self::get_active_plugins() );
1889
	}
1890
1891
	/**
1892
	 * Check if Jetpack's Open Graph tags should be used.
1893
	 * If certain plugins are active, Jetpack's og tags are suppressed.
1894
	 *
1895
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1896
	 * @action plugins_loaded
1897
	 * @return null
1898
	 */
1899
	public function check_open_graph() {
1900
		if ( in_array( 'publicize', Jetpack::get_active_modules() ) || in_array( 'sharedaddy', Jetpack::get_active_modules() ) ) {
1901
			add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
1902
		}
1903
1904
		$active_plugins = self::get_active_plugins();
1905
1906
		if ( ! empty( $active_plugins ) ) {
1907
			foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
1908
				if ( in_array( $plugin, $active_plugins ) ) {
1909
					add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
1910
					break;
1911
				}
1912
			}
1913
		}
1914
1915
		/**
1916
		 * Allow the addition of Open Graph Meta Tags to all pages.
1917
		 *
1918
		 * @since 2.0.3
1919
		 *
1920
		 * @param bool false Should Open Graph Meta tags be added. Default to false.
1921
		 */
1922
		if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
1923
			require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
1924
		}
1925
	}
1926
1927
	/**
1928
	 * Check if Jetpack's Twitter tags should be used.
1929
	 * If certain plugins are active, Jetpack's twitter tags are suppressed.
1930
	 *
1931
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1932
	 * @action plugins_loaded
1933
	 * @return null
1934
	 */
1935
	public function check_twitter_tags() {
1936
1937
		$active_plugins = self::get_active_plugins();
1938
1939
		if ( ! empty( $active_plugins ) ) {
1940
			foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
1941
				if ( in_array( $plugin, $active_plugins ) ) {
1942
					add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
1943
					break;
1944
				}
1945
			}
1946
		}
1947
1948
		/**
1949
		 * Allow Twitter Card Meta tags to be disabled.
1950
		 *
1951
		 * @since 2.6.0
1952
		 *
1953
		 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
1954
		 */
1955
		if ( apply_filters( 'jetpack_disable_twitter_cards', true ) ) {
1956
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
1957
		}
1958
	}
1959
1960
1961
1962
1963
	/*
1964
	 *
1965
	 * Jetpack Security Reports
1966
	 *
1967
	 * Allowed types: login_form, backup, file_scanning, spam
1968
	 *
1969
	 * 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)
1970
	 *
1971
	 * 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)
1972
	 *
1973
	 *
1974
	 * Example code to submit a security report:
1975
	 *
1976
	 *  function akismet_submit_jetpack_security_report() {
1977
	 *  	Jetpack::submit_security_report( 'spam', __FILE__, $args = array( 'blocked' => 138284, status => 'ok' ) );
1978
	 *  }
1979
	 *  add_action( 'jetpack_security_report', 'akismet_submit_jetpack_security_report' );
1980
	 *
1981
	 */
1982
1983
1984
	/**
1985
	 * Calls for security report submissions.
1986
	 *
1987
	 * @return null
1988
	 */
1989
	public static function perform_security_reporting() {
1990
		$no_check_needed = get_site_transient( 'security_report_performed_recently' );
1991
1992
		if ( $no_check_needed ) {
1993
			return;
1994
		}
1995
1996
		/**
1997
		 * Fires before a security report is created.
1998
		 *
1999
		 * @since 3.4.0
2000
		 */
2001
		do_action( 'jetpack_security_report' );
2002
2003
		Jetpack_Options::update_option( 'security_report', self::$security_report );
2004
		set_site_transient( 'security_report_performed_recently', 1, 15 * MINUTE_IN_SECONDS );
2005
	}
2006
2007
	/**
2008
	 * Allows plugins to submit security reports.
2009
 	 *
2010
	 * @param string  $type         Report type (login_form, backup, file_scanning, spam)
2011
	 * @param string  $plugin_file  Plugin __FILE__, so that we can pull plugin data
2012
	 * @param array   $args         See definitions above
2013
	 */
2014
	public static function submit_security_report( $type = '', $plugin_file = '', $args = array() ) {
2015
2016
		if( !doing_action( 'jetpack_security_report' ) ) {
2017
			return new WP_Error( 'not_collecting_report', 'Not currently collecting security reports.  Please use the jetpack_security_report hook.' );
2018
		}
2019
2020
		if( !is_string( $type ) || !is_string( $plugin_file ) ) {
2021
			return new WP_Error( 'invalid_security_report', 'Invalid Security Report' );
2022
		}
2023
2024
		if( !function_exists( 'get_plugin_data' ) ) {
2025
			include( ABSPATH . 'wp-admin/includes/plugin.php' );
2026
		}
2027
2028
		//Get rid of any non-allowed args
2029
		$args = array_intersect_key( $args, array_flip( array( 'blocked', 'last', 'next', 'status', 'message' ) ) );
2030
2031
		$plugin = get_plugin_data( $plugin_file );
2032
2033
		if ( !$plugin['Name'] ) {
2034
			return new WP_Error( 'security_report_missing_plugin_name', 'Invalid Plugin File Provided' );
2035
		}
2036
2037
		// Sanitize everything to make sure we're not syncing something wonky
2038
		$type = sanitize_key( $type );
2039
2040
		$args['plugin'] = $plugin;
2041
2042
		// Cast blocked, last and next as integers.
2043
		// Last and next should be in unix timestamp format
2044
		if ( isset( $args['blocked'] ) ) {
2045
			$args['blocked'] = (int) $args['blocked'];
2046
		}
2047
		if ( isset( $args['last'] ) ) {
2048
			$args['last'] = (int) $args['last'];
2049
		}
2050
		if ( isset( $args['next'] ) ) {
2051
			$args['next'] = (int) $args['next'];
2052
		}
2053
		if ( !in_array( $args['status'], array( 'ok', 'warning', 'error' ) ) ) {
2054
			$args['status'] = 'ok';
2055
		}
2056
		if ( isset( $args['message'] ) ) {
2057
2058
			if( $args['status'] == 'ok' ) {
2059
				unset( $args['message'] );
2060
			}
2061
2062
			$allowed_html = array(
2063
			    'a' => array(
2064
			        'href' => array(),
2065
			        'title' => array()
2066
			    ),
2067
			    'em' => array(),
2068
			    'strong' => array(),
2069
			);
2070
2071
			$args['message'] = wp_kses( $args['message'], $allowed_html );
2072
		}
2073
2074
		$plugin_name = $plugin[ 'Name' ];
2075
2076
		self::$security_report[ $type ][ $plugin_name ] = $args;
2077
	}
2078
2079
	/**
2080
	 * Collects a new report if needed, then returns it.
2081
	 */
2082
	public function get_security_report() {
2083
		self::perform_security_reporting();
2084
		return Jetpack_Options::get_option( 'security_report' );
2085
	}
2086
2087
2088
/* Jetpack Options API */
2089
2090
	public static function get_option_names( $type = 'compact' ) {
2091
		return Jetpack_Options::get_option_names( $type );
2092
	}
2093
2094
	/**
2095
	 * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
2096
 	 *
2097
	 * @param string $name    Option name
2098
	 * @param mixed  $default (optional)
2099
	 */
2100
	public static function get_option( $name, $default = false ) {
2101
		return Jetpack_Options::get_option( $name, $default );
2102
	}
2103
2104
	/**
2105
	* Stores two secrets and a timestamp so WordPress.com can make a request back and verify an action
2106
	* Does some extra verification so urls (such as those to public-api, register, etc) can't just be crafted
2107
	* $name must be a registered option name.
2108
	*/
2109
	public static function create_nonce( $name ) {
2110
		$secret = wp_generate_password( 32, false ) . ':' . wp_generate_password( 32, false ) . ':' . ( time() + 600 );
2111
2112
		Jetpack_Options::update_option( $name, $secret );
2113
		@list( $secret_1, $secret_2, $eol ) = explode( ':', Jetpack_Options::get_option( $name ) );
2114
		if ( empty( $secret_1 ) || empty( $secret_2 ) || $eol < time() )
2115
			return new Jetpack_Error( 'missing_secrets' );
2116
2117
		return array(
2118
			'secret_1' => $secret_1,
2119
			'secret_2' => $secret_2,
2120
			'eol'      => $eol,
2121
		);
2122
	}
2123
2124
	/**
2125
	 * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
2126
 	 *
2127
	 * @deprecated 3.4 use Jetpack_Options::update_option() instead.
2128
	 * @param string $name  Option name
2129
	 * @param mixed  $value Option value
2130
	 */
2131
	public static function update_option( $name, $value ) {
2132
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_option()' );
2133
		return Jetpack_Options::update_option( $name, $value );
2134
	}
2135
2136
	/**
2137
	 * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
2138
 	 *
2139
	 * @deprecated 3.4 use Jetpack_Options::update_options() instead.
2140
	 * @param array $array array( option name => option value, ... )
2141
	 */
2142
	public static function update_options( $array ) {
2143
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_options()' );
2144
		return Jetpack_Options::update_options( $array );
2145
	}
2146
2147
	/**
2148
	 * Deletes the given option.  May be passed multiple option names as an array.
2149
	 * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
2150
	 *
2151
	 * @deprecated 3.4 use Jetpack_Options::delete_option() instead.
2152
	 * @param string|array $names
2153
	 */
2154
	public static function delete_option( $names ) {
2155
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::delete_option()' );
2156
		return Jetpack_Options::delete_option( $names );
2157
	}
2158
2159
	/**
2160
	 * Enters a user token into the user_tokens option
2161
	 *
2162
	 * @param int $user_id
2163
	 * @param string $token
2164
	 * return bool
2165
	 */
2166
	public static function update_user_token( $user_id, $token, $is_master_user ) {
2167
		// not designed for concurrent updates
2168
		$user_tokens = Jetpack_Options::get_option( 'user_tokens' );
2169
		if ( ! is_array( $user_tokens ) )
2170
			$user_tokens = array();
2171
		$user_tokens[$user_id] = $token;
2172
		if ( $is_master_user ) {
2173
			$master_user = $user_id;
2174
			$options     = compact( 'user_tokens', 'master_user' );
2175
		} else {
2176
			$options = compact( 'user_tokens' );
2177
		}
2178
		return Jetpack_Options::update_options( $options );
2179
	}
2180
2181
	/**
2182
	 * Returns an array of all PHP files in the specified absolute path.
2183
	 * Equivalent to glob( "$absolute_path/*.php" ).
2184
	 *
2185
	 * @param string $absolute_path The absolute path of the directory to search.
2186
	 * @return array Array of absolute paths to the PHP files.
2187
	 */
2188
	public static function glob_php( $absolute_path ) {
2189
		if ( function_exists( 'glob' ) ) {
2190
			return glob( "$absolute_path/*.php" );
2191
		}
2192
2193
		$absolute_path = untrailingslashit( $absolute_path );
2194
		$files = array();
2195
		if ( ! $dir = @opendir( $absolute_path ) ) {
2196
			return $files;
2197
		}
2198
2199
		while ( false !== $file = readdir( $dir ) ) {
2200
			if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
2201
				continue;
2202
			}
2203
2204
			$file = "$absolute_path/$file";
2205
2206
			if ( ! is_file( $file ) ) {
2207
				continue;
2208
			}
2209
2210
			$files[] = $file;
2211
		}
2212
2213
		closedir( $dir );
2214
2215
		return $files;
2216
	}
2217
2218
	public static function activate_new_modules( $redirect = false ) {
2219
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
2220
			return;
2221
		}
2222
2223
		$jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
2224 View Code Duplication
		if ( ! $jetpack_old_version ) {
2225
			$jetpack_old_version = $version = $old_version = '1.1:' . time();
2226
			/** This action is documented in class.jetpack.php */
2227
			do_action( 'updating_jetpack_version', $version, false );
2228
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2229
		}
2230
2231
		list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
2232
2233
		if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
2234
			return;
2235
		}
2236
2237
		$active_modules     = Jetpack::get_active_modules();
2238
		$reactivate_modules = array();
2239
		foreach ( $active_modules as $active_module ) {
2240
			$module = Jetpack::get_module( $active_module );
2241
			if ( ! isset( $module['changed'] ) ) {
2242
				continue;
2243
			}
2244
2245
			if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
2246
				continue;
2247
			}
2248
2249
			$reactivate_modules[] = $active_module;
2250
			Jetpack::deactivate_module( $active_module );
2251
		}
2252
2253
		$new_version = JETPACK__VERSION . ':' . time();
2254
		/** This action is documented in class.jetpack.php */
2255
		do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
2256
		Jetpack_Options::update_options(
2257
			array(
2258
				'version'     => $new_version,
2259
				'old_version' => $jetpack_old_version,
2260
			)
2261
		);
2262
2263
		Jetpack::state( 'message', 'modules_activated' );
2264
		Jetpack::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules );
2265
2266
		if ( $redirect ) {
2267
			$page = 'jetpack'; // make sure we redirect to either settings or the jetpack page
2268
			if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ) ) ) {
2269
				$page = $_GET['page'];
2270
			}
2271
2272
			wp_safe_redirect( Jetpack::admin_url( 'page=' . $page ) );
2273
			exit;
2274
		}
2275
	}
2276
2277
	/**
2278
	 * List available Jetpack modules. Simply lists .php files in /modules/.
2279
	 * Make sure to tuck away module "library" files in a sub-directory.
2280
	 */
2281
	public static function get_available_modules( $min_version = false, $max_version = false ) {
2282
		static $modules = null;
2283
2284
		if ( ! isset( $modules ) ) {
2285
			$available_modules_option = Jetpack_Options::get_option( 'available_modules', array() );
2286
			// Use the cache if we're on the front-end and it's available...
2287
			if ( ! is_admin() && ! empty( $available_modules_option[ JETPACK__VERSION ] ) ) {
2288
				$modules = $available_modules_option[ JETPACK__VERSION ];
2289
			} else {
2290
				$files = Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
2291
2292
				$modules = array();
2293
2294
				foreach ( $files as $file ) {
2295
					if ( ! $headers = Jetpack::get_module( $file ) ) {
2296
						continue;
2297
					}
2298
2299
					$modules[ Jetpack::get_module_slug( $file ) ] = $headers['introduced'];
2300
				}
2301
2302
				Jetpack_Options::update_option( 'available_modules', array(
2303
					JETPACK__VERSION => $modules,
2304
				) );
2305
			}
2306
		}
2307
2308
		/**
2309
		 * Filters the array of modules available to be activated.
2310
		 *
2311
		 * @since 2.4.0
2312
		 *
2313
		 * @param array $modules Array of available modules.
2314
		 * @param string $min_version Minimum version number required to use modules.
2315
		 * @param string $max_version Maximum version number required to use modules.
2316
		 */
2317
		$mods = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version );
2318
2319
		if ( ! $min_version && ! $max_version ) {
2320
			return array_keys( $mods );
2321
		}
2322
2323
		$r = array();
2324
		foreach ( $mods as $slug => $introduced ) {
2325
			if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
2326
				continue;
2327
			}
2328
2329
			if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
2330
				continue;
2331
			}
2332
2333
			$r[] = $slug;
2334
		}
2335
2336
		return $r;
2337
	}
2338
2339
	/**
2340
	 * Default modules loaded on activation.
2341
	 */
2342
	public static function get_default_modules( $min_version = false, $max_version = false ) {
2343
		$return = array();
2344
2345
		foreach ( Jetpack::get_available_modules( $min_version, $max_version ) as $module ) {
2346
			$module_data = Jetpack::get_module( $module );
2347
2348
			switch ( strtolower( $module_data['auto_activate'] ) ) {
2349
				case 'yes' :
2350
					$return[] = $module;
2351
					break;
2352
				case 'public' :
2353
					if ( Jetpack_Options::get_option( 'public' ) ) {
2354
						$return[] = $module;
2355
					}
2356
					break;
2357
				case 'no' :
2358
				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...
2359
					break;
2360
			}
2361
		}
2362
		/**
2363
		 * Filters the array of default modules.
2364
		 *
2365
		 * @since 2.5.0
2366
		 *
2367
		 * @param array $return Array of default modules.
2368
		 * @param string $min_version Minimum version number required to use modules.
2369
		 * @param string $max_version Maximum version number required to use modules.
2370
		 */
2371
		return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version );
2372
	}
2373
2374
	/**
2375
	 * Checks activated modules during auto-activation to determine
2376
	 * if any of those modules are being deprecated.  If so, close
2377
	 * them out, and add any replacement modules.
2378
	 *
2379
	 * Runs at priority 99 by default.
2380
	 *
2381
	 * This is run late, so that it can still activate a module if
2382
	 * the new module is a replacement for another that the user
2383
	 * currently has active, even if something at the normal priority
2384
	 * would kibosh everything.
2385
	 *
2386
	 * @since 2.6
2387
	 * @uses jetpack_get_default_modules filter
2388
	 * @param array $modules
2389
	 * @return array
2390
	 */
2391
	function handle_deprecated_modules( $modules ) {
2392
		$deprecated_modules = array(
2393
			'debug'            => null,  // Closed out and moved to ./class.jetpack-debugger.php
2394
			'wpcc'             => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2395
			'gplus-authorship' => null,  // Closed out in 3.2 -- Google dropped support.
2396
		);
2397
2398
		// Don't activate SSO if they never completed activating WPCC.
2399
		if ( Jetpack::is_module_active( 'wpcc' ) ) {
2400
			$wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2401
			if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2402
				$deprecated_modules['wpcc'] = null;
2403
			}
2404
		}
2405
2406
		foreach ( $deprecated_modules as $module => $replacement ) {
2407
			if ( Jetpack::is_module_active( $module ) ) {
2408
				self::deactivate_module( $module );
2409
				if ( $replacement ) {
2410
					$modules[] = $replacement;
2411
				}
2412
			}
2413
		}
2414
2415
		return array_unique( $modules );
2416
	}
2417
2418
	/**
2419
	 * Checks activated plugins during auto-activation to determine
2420
	 * if any of those plugins are in the list with a corresponding module
2421
	 * that is not compatible with the plugin. The module will not be allowed
2422
	 * to auto-activate.
2423
	 *
2424
	 * @since 2.6
2425
	 * @uses jetpack_get_default_modules filter
2426
	 * @param array $modules
2427
	 * @return array
2428
	 */
2429
	function filter_default_modules( $modules ) {
2430
2431
		$active_plugins = self::get_active_plugins();
2432
2433
		if ( ! empty( $active_plugins ) ) {
2434
2435
			// For each module we'd like to auto-activate...
2436
			foreach ( $modules as $key => $module ) {
2437
				// If there are potential conflicts for it...
2438
				if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2439
					// For each potential conflict...
2440
					foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
2441
						// If that conflicting plugin is active...
2442
						if ( in_array( $plugin, $active_plugins ) ) {
2443
							// Remove that item from being auto-activated.
2444
							unset( $modules[ $key ] );
2445
						}
2446
					}
2447
				}
2448
			}
2449
		}
2450
2451
		return $modules;
2452
	}
2453
2454
	/**
2455
	 * Extract a module's slug from its full path.
2456
	 */
2457
	public static function get_module_slug( $file ) {
2458
		return str_replace( '.php', '', basename( $file ) );
2459
	}
2460
2461
	/**
2462
	 * Generate a module's path from its slug.
2463
	 */
2464
	public static function get_module_path( $slug ) {
2465
		return JETPACK__PLUGIN_DIR . "modules/$slug.php";
2466
	}
2467
2468
	/**
2469
	 * Load module data from module file. Headers differ from WordPress
2470
	 * plugin headers to avoid them being identified as standalone
2471
	 * plugins on the WordPress plugins page.
2472
	 */
2473
	public static function get_module( $module ) {
2474
		$headers = array(
2475
			'name'                      => 'Module Name',
2476
			'description'               => 'Module Description',
2477
			'jumpstart_desc'            => 'Jumpstart Description',
2478
			'sort'                      => 'Sort Order',
2479
			'recommendation_order'      => 'Recommendation Order',
2480
			'introduced'                => 'First Introduced',
2481
			'changed'                   => 'Major Changes In',
2482
			'deactivate'                => 'Deactivate',
2483
			'free'                      => 'Free',
2484
			'requires_connection'       => 'Requires Connection',
2485
			'auto_activate'             => 'Auto Activate',
2486
			'module_tags'               => 'Module Tags',
2487
			'feature'                   => 'Feature',
2488
			'additional_search_queries' => 'Additional Search Queries',
2489
		);
2490
2491
		$file = Jetpack::get_module_path( Jetpack::get_module_slug( $module ) );
2492
2493
		$mod = Jetpack::get_file_data( $file, $headers );
2494
		if ( empty( $mod['name'] ) ) {
2495
			return false;
2496
		}
2497
2498
		$mod['sort']                    = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
2499
		$mod['recommendation_order']    = empty( $mod['recommendation_order'] ) ? 20 : (int) $mod['recommendation_order'];
2500
		$mod['deactivate']              = empty( $mod['deactivate'] );
2501
		$mod['free']                    = empty( $mod['free'] );
2502
		$mod['requires_connection']     = ( ! empty( $mod['requires_connection'] ) && 'No' == $mod['requires_connection'] ) ? false : true;
2503
2504
		if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ) ) ) {
2505
			$mod['auto_activate'] = 'No';
2506
		} else {
2507
			$mod['auto_activate'] = (string) $mod['auto_activate'];
2508
		}
2509
2510
		if ( $mod['module_tags'] ) {
2511
			$mod['module_tags'] = explode( ',', $mod['module_tags'] );
2512
			$mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
2513
			$mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
2514
		} else {
2515
			$mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
2516
		}
2517
2518
		if ( $mod['feature'] ) {
2519
			$mod['feature'] = explode( ',', $mod['feature'] );
2520
			$mod['feature'] = array_map( 'trim', $mod['feature'] );
2521
		} else {
2522
			$mod['feature'] = array( self::translate_module_tag( 'Other' ) );
2523
		}
2524
2525
		/**
2526
		 * Filters the feature array on a module.
2527
		 *
2528
		 * This filter allows you to control where each module is filtered: Recommended,
2529
		 * Jumpstart, and the default "Other" listing.
2530
		 *
2531
		 * @since 3.5.0
2532
		 *
2533
		 * @param array   $mod['feature'] The areas to feature this module:
2534
		 *     'Jumpstart' adds to the "Jumpstart" option to activate many modules at once.
2535
		 *     'Recommended' shows on the main Jetpack admin screen.
2536
		 *     'Other' should be the default if no other value is in the array.
2537
		 * @param string  $module The slug of the module, e.g. sharedaddy.
2538
		 * @param array   $mod All the currently assembled module data.
2539
		 */
2540
		$mod['feature'] = apply_filters( 'jetpack_module_feature', $mod['feature'], $module, $mod );
2541
2542
		/**
2543
		 * Filter the returned data about a module.
2544
		 *
2545
		 * This filter allows overriding any info about Jetpack modules. It is dangerous,
2546
		 * so please be careful.
2547
		 *
2548
		 * @since 3.6.0
2549
		 *
2550
		 * @param array   $mod    The details of the requested module.
2551
		 * @param string  $module The slug of the module, e.g. sharedaddy
2552
		 * @param string  $file   The path to the module source file.
2553
		 */
2554
		return apply_filters( 'jetpack_get_module', $mod, $module, $file );
2555
	}
2556
2557
	/**
2558
	 * Like core's get_file_data implementation, but caches the result.
2559
	 */
2560
	public static function get_file_data( $file, $headers ) {
2561
		//Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated
2562
		$file_name = basename( $file );
2563
		$file_data_option = Jetpack_Options::get_option( 'file_data', array() );
2564
		$key              = md5( $file_name . serialize( $headers ) );
2565
		$refresh_cache    = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 );
2566
2567
		// If we don't need to refresh the cache, and already have the value, short-circuit!
2568
		if ( ! $refresh_cache && isset( $file_data_option[ JETPACK__VERSION ][ $key ] ) ) {
2569
			return $file_data_option[ JETPACK__VERSION ][ $key ];
2570
		}
2571
2572
		$data = get_file_data( $file, $headers );
2573
2574
		// Strip out any old Jetpack versions that are cluttering the option.
2575
		$file_data_option = array_intersect_key( (array) $file_data_option, array( JETPACK__VERSION => null ) );
2576
		$file_data_option[ JETPACK__VERSION ][ $key ] = $data;
2577
		Jetpack_Options::update_option( 'file_data', $file_data_option );
2578
2579
		return $data;
2580
	}
2581
2582
	/**
2583
	 * Return translated module tag.
2584
	 *
2585
	 * @param string $tag Tag as it appears in each module heading.
2586
	 *
2587
	 * @return mixed
2588
	 */
2589
	public static function translate_module_tag( $tag ) {
2590
		return jetpack_get_module_i18n_tag( $tag );
2591
	}
2592
2593
	/**
2594
	 * Return module name translation. Uses matching string created in modules/module-headings.php.
2595
	 *
2596
	 * @since 3.9.2
2597
	 *
2598
	 * @param array $modules
2599
	 *
2600
	 * @return string|void
2601
	 */
2602
	public static function get_translated_modules( $modules ) {
2603
		foreach ( $modules as $index => $module ) {
2604
			$i18n_module = jetpack_get_module_i18n( $module['module'] );
2605
			if ( isset( $module['name'] ) ) {
2606
				$modules[ $index ]['name'] = $i18n_module['name'];
2607
			}
2608
			if ( isset( $module['description'] ) ) {
2609
				$modules[ $index ]['description'] = $i18n_module['description'];
2610
				$modules[ $index ]['short_description'] = $i18n_module['description'];
2611
			}
2612
		}
2613
		return $modules;
2614
	}
2615
2616
	/**
2617
	 * Get a list of activated modules as an array of module slugs.
2618
	 */
2619
	public static function get_active_modules() {
2620
		$active = Jetpack_Options::get_option( 'active_modules' );
2621
		if ( ! is_array( $active ) )
2622
			$active = array();
2623
		if ( is_admin() && ( class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ) ) ) {
2624
			$active[] = 'vaultpress';
2625
		} else {
2626
			$active = array_diff( $active, array( 'vaultpress' ) );
2627
		}
2628
2629
		//If protect is active on the main site of a multisite, it should be active on all sites.
2630
		if ( ! in_array( 'protect', $active ) && is_multisite() && get_site_option( 'jetpack_protect_active' ) ) {
2631
			$active[] = 'protect';
2632
		}
2633
2634
		return array_unique( $active );
2635
	}
2636
2637
	/**
2638
	 * Check whether or not a Jetpack module is active.
2639
	 *
2640
	 * @param string $module The slug of a Jetpack module.
2641
	 * @return bool
2642
	 *
2643
	 * @static
2644
	 */
2645
	public static function is_module_active( $module ) {
2646
		return in_array( $module, self::get_active_modules() );
2647
	}
2648
2649
	public static function is_module( $module ) {
2650
		return ! empty( $module ) && ! validate_file( $module, Jetpack::get_available_modules() );
2651
	}
2652
2653
	/**
2654
	 * Catches PHP errors.  Must be used in conjunction with output buffering.
2655
	 *
2656
	 * @param bool $catch True to start catching, False to stop.
2657
	 *
2658
	 * @static
2659
	 */
2660
	public static function catch_errors( $catch ) {
2661
		static $display_errors, $error_reporting;
2662
2663
		if ( $catch ) {
2664
			$display_errors  = @ini_set( 'display_errors', 1 );
2665
			$error_reporting = @error_reporting( E_ALL );
2666
			add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2667
		} else {
2668
			@ini_set( 'display_errors', $display_errors );
2669
			@error_reporting( $error_reporting );
2670
			remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2671
		}
2672
	}
2673
2674
	/**
2675
	 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2676
	 */
2677
	public static function catch_errors_on_shutdown() {
2678
		Jetpack::state( 'php_errors', ob_get_clean() );
2679
	}
2680
2681
	public static function activate_default_modules( $min_version = false, $max_version = false, $other_modules = array(), $redirect = true ) {
2682
		$jetpack = Jetpack::init();
2683
2684
		$modules = Jetpack::get_default_modules( $min_version, $max_version );
2685
		$modules = array_merge( $other_modules, $modules );
2686
2687
		// Look for standalone plugins and disable if active.
2688
2689
		$to_deactivate = array();
2690
		foreach ( $modules as $module ) {
2691
			if ( isset( $jetpack->plugins_to_deactivate[$module] ) ) {
2692
				$to_deactivate[$module] = $jetpack->plugins_to_deactivate[$module];
2693
			}
2694
		}
2695
2696
		$deactivated = array();
2697
		foreach ( $to_deactivate as $module => $deactivate_me ) {
2698
			list( $probable_file, $probable_title ) = $deactivate_me;
2699
			if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2700
				$deactivated[] = $module;
2701
			}
2702
		}
2703
2704
		if ( $deactivated && $redirect ) {
2705
			Jetpack::state( 'deactivated_plugins', join( ',', $deactivated ) );
2706
2707
			$url = add_query_arg(
2708
				array(
2709
					'action'   => 'activate_default_modules',
2710
					'_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2711
				),
2712
				add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), Jetpack::admin_url( 'page=jetpack' ) )
2713
			);
2714
			wp_safe_redirect( $url );
2715
			exit;
2716
		}
2717
2718
		/**
2719
		 * Fires before default modules are activated.
2720
		 *
2721
		 * @since 1.9.0
2722
		 *
2723
		 * @param string $min_version Minimum version number required to use modules.
2724
		 * @param string $max_version Maximum version number required to use modules.
2725
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2726
		 */
2727
		do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
2728
2729
		// Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
2730
		Jetpack::restate();
2731
		Jetpack::catch_errors( true );
2732
2733
		$active = Jetpack::get_active_modules();
2734
2735
		foreach ( $modules as $module ) {
2736
			if ( did_action( "jetpack_module_loaded_$module" ) ) {
2737
				$active[] = $module;
2738
				Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2739
				continue;
2740
			}
2741
2742
			if ( in_array( $module, $active ) ) {
2743
				$module_info = Jetpack::get_module( $module );
2744
				if ( ! $module_info['deactivate'] ) {
2745
					$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2746 View Code Duplication
					if ( $active_state = Jetpack::state( $state ) ) {
2747
						$active_state = explode( ',', $active_state );
2748
					} else {
2749
						$active_state = array();
2750
					}
2751
					$active_state[] = $module;
2752
					Jetpack::state( $state, implode( ',', $active_state ) );
2753
				}
2754
				continue;
2755
			}
2756
2757
			$file = Jetpack::get_module_path( $module );
2758
			if ( ! file_exists( $file ) ) {
2759
				continue;
2760
			}
2761
2762
			// we'll override this later if the plugin can be included without fatal error
2763
			if ( $redirect ) {
2764
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2765
			}
2766
			Jetpack::state( 'error', 'module_activation_failed' );
2767
			Jetpack::state( 'module', $module );
2768
			ob_start();
2769
			require $file;
2770
			/**
2771
			 * Fires when a specific module is activated.
2772
			 *
2773
			 * @since 1.9.0
2774
			 *
2775
			 * @param string $module Module slug.
2776
			 */
2777
			do_action( 'jetpack_activate_module', $module );
2778
			$active[] = $module;
2779
			$state    = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2780 View Code Duplication
			if ( $active_state = Jetpack::state( $state ) ) {
2781
				$active_state = explode( ',', $active_state );
2782
			} else {
2783
				$active_state = array();
2784
			}
2785
			$active_state[] = $module;
2786
			Jetpack::state( $state, implode( ',', $active_state ) );
2787
			Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2788
			ob_end_clean();
2789
		}
2790
		Jetpack::state( 'error', false );
2791
		Jetpack::state( 'module', false );
2792
		Jetpack::catch_errors( false );
2793
		/**
2794
		 * Fires when default modules are activated.
2795
		 *
2796
		 * @since 1.9.0
2797
		 *
2798
		 * @param string $min_version Minimum version number required to use modules.
2799
		 * @param string $max_version Maximum version number required to use modules.
2800
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2801
		 */
2802
		do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
2803
	}
2804
2805
	public static function activate_module( $module, $exit = true, $redirect = true ) {
2806
		/**
2807
		 * Fires before a module is activated.
2808
		 *
2809
		 * @since 2.6.0
2810
		 *
2811
		 * @param string $module Module slug.
2812
		 * @param bool $exit Should we exit after the module has been activated. Default to true.
2813
		 * @param bool $redirect Should the user be redirected after module activation? Default to true.
2814
		 */
2815
		do_action( 'jetpack_pre_activate_module', $module, $exit, $redirect );
2816
2817
		$jetpack = Jetpack::init();
2818
2819
		if ( ! strlen( $module ) )
2820
			return false;
2821
2822
		if ( ! Jetpack::is_module( $module ) )
2823
			return false;
2824
2825
		// If it's already active, then don't do it again
2826
		$active = Jetpack::get_active_modules();
2827
		foreach ( $active as $act ) {
2828
			if ( $act == $module )
2829
				return true;
2830
		}
2831
2832
		$module_data = Jetpack::get_module( $module );
2833
2834
		if ( ! Jetpack::is_active() ) {
2835
			if ( !Jetpack::is_development_mode() )
2836
				return false;
2837
2838
			// If we're not connected but in development mode, make sure the module doesn't require a connection
2839
			if ( Jetpack::is_development_mode() && $module_data['requires_connection'] )
2840
				return false;
2841
		}
2842
2843
		// Check and see if the old plugin is active
2844
		if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2845
			// Deactivate the old plugin
2846
			if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[ $module ][0], $jetpack->plugins_to_deactivate[ $module ][1] ) ) {
2847
				// If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
2848
				// We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
2849
				Jetpack::state( 'deactivated_plugins', $module );
2850
				wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
2851
				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...
2852
			}
2853
		}
2854
2855
		// Check the file for fatal errors, a la wp-admin/plugins.php::activate
2856
		Jetpack::state( 'module', $module );
2857
		Jetpack::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
2858
2859
		Jetpack::catch_errors( true );
2860
		ob_start();
2861
		require Jetpack::get_module_path( $module );
2862
		/** This action is documented in class.jetpack.php */
2863
		do_action( 'jetpack_activate_module', $module );
2864
		$active[] = $module;
2865
		Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
2866
		Jetpack::state( 'error', false ); // the override
2867
		Jetpack::state( 'message', 'module_activated' );
2868
		Jetpack::state( 'module', $module );
2869
		ob_end_clean();
2870
		Jetpack::catch_errors( false );
2871
2872
		// A flag for Jump Start so it's not shown again. Only set if it hasn't been yet.
2873 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2874
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2875
2876
			//Jump start is being dismissed send data to MC Stats
2877
			$jetpack->stat( 'jumpstart', 'manual,'.$module );
2878
2879
			$jetpack->do_stats( 'server_side' );
2880
		}
2881
2882
		if ( $redirect ) {
2883
			wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2884
		}
2885
		if ( $exit ) {
2886
			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...
2887
		}
2888
		return true;
2889
	}
2890
2891
	function activate_module_actions( $module ) {
2892
		/**
2893
		 * Fires when a module is activated.
2894
		 * The dynamic part of the filter, $module, is the module slug.
2895
		 *
2896
		 * @since 1.9.0
2897
		 *
2898
		 * @param string $module Module slug.
2899
		 */
2900
		do_action( "jetpack_activate_module_$module", $module );
2901
2902
		$this->sync->sync_all_module_options( $module );
2903
	}
2904
2905
	public static function deactivate_module( $module ) {
2906
		/**
2907
		 * Fires when a module is deactivated.
2908
		 *
2909
		 * @since 1.9.0
2910
		 *
2911
		 * @param string $module Module slug.
2912
		 */
2913
		do_action( 'jetpack_pre_deactivate_module', $module );
2914
2915
		$jetpack = Jetpack::init();
2916
2917
		$active = Jetpack::get_active_modules();
2918
		$new    = array_filter( array_diff( $active, (array) $module ) );
2919
2920
		/**
2921
		 * Fires when a module is deactivated.
2922
		 * The dynamic part of the filter, $module, is the module slug.
2923
		 *
2924
		 * @since 1.9.0
2925
		 *
2926
		 * @param string $module Module slug.
2927
		 */
2928
		do_action( "jetpack_deactivate_module_$module", $module );
2929
2930
		// A flag for Jump Start so it's not shown again.
2931 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2932
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2933
2934
			//Jump start is being dismissed send data to MC Stats
2935
			$jetpack->stat( 'jumpstart', 'manual,deactivated-'.$module );
2936
2937
			$jetpack->do_stats( 'server_side' );
2938
		}
2939
2940
		return Jetpack_Options::update_option( 'active_modules', array_unique( $new ) );
2941
	}
2942
2943
	public static function enable_module_configurable( $module ) {
2944
		$module = Jetpack::get_module_slug( $module );
2945
		add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
2946
	}
2947
2948
	public static function module_configuration_url( $module ) {
2949
		$module = Jetpack::get_module_slug( $module );
2950
		return Jetpack::admin_url( array( 'page' => 'jetpack', 'configure' => $module ) );
2951
	}
2952
2953
	public static function module_configuration_load( $module, $method ) {
2954
		$module = Jetpack::get_module_slug( $module );
2955
		add_action( 'jetpack_module_configuration_load_' . $module, $method );
2956
	}
2957
2958
	public static function module_configuration_head( $module, $method ) {
2959
		$module = Jetpack::get_module_slug( $module );
2960
		add_action( 'jetpack_module_configuration_head_' . $module, $method );
2961
	}
2962
2963
	public static function module_configuration_screen( $module, $method ) {
2964
		$module = Jetpack::get_module_slug( $module );
2965
		add_action( 'jetpack_module_configuration_screen_' . $module, $method );
2966
	}
2967
2968
	public static function module_configuration_activation_screen( $module, $method ) {
2969
		$module = Jetpack::get_module_slug( $module );
2970
		add_action( 'display_activate_module_setting_' . $module, $method );
2971
	}
2972
2973
/* Installation */
2974
2975
	public static function bail_on_activation( $message, $deactivate = true ) {
2976
?>
2977
<!doctype html>
2978
<html>
2979
<head>
2980
<meta charset="<?php bloginfo( 'charset' ); ?>">
2981
<style>
2982
* {
2983
	text-align: center;
2984
	margin: 0;
2985
	padding: 0;
2986
	font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
2987
}
2988
p {
2989
	margin-top: 1em;
2990
	font-size: 18px;
2991
}
2992
</style>
2993
<body>
2994
<p><?php echo esc_html( $message ); ?></p>
2995
</body>
2996
</html>
2997
<?php
2998
		if ( $deactivate ) {
2999
			$plugins = get_option( 'active_plugins' );
3000
			$jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
3001
			$update  = false;
3002
			foreach ( $plugins as $i => $plugin ) {
3003
				if ( $plugin === $jetpack ) {
3004
					$plugins[$i] = false;
3005
					$update = true;
3006
				}
3007
			}
3008
3009
			if ( $update ) {
3010
				update_option( 'active_plugins', array_filter( $plugins ) );
3011
			}
3012
		}
3013
		exit;
3014
	}
3015
3016
	/**
3017
	 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
3018
	 * @static
3019
	 */
3020
	public static function plugin_activation( $network_wide ) {
3021
		Jetpack_Options::update_option( 'activated', 1 );
3022
3023
		if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
3024
			Jetpack::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
3025
		}
3026
3027
		if ( $network_wide )
3028
			Jetpack::state( 'network_nag', true );
3029
3030
		Jetpack::plugin_initialize();
3031
	}
3032
	/**
3033
	 * Runs before bumping version numbers up to a new version
3034
	 * @param  (string) $version    Version:timestamp
3035
	 * @param  (string) $old_version Old Version:timestamp or false if not set yet.
3036
	 * @return null              [description]
3037
	 */
3038
	public static function do_version_bump( $version, $old_version ) {
3039
3040
		if ( ! $old_version ) { // For new sites
3041
			// Setting up jetpack manage
3042
			Jetpack::activate_manage();
3043
		}
3044
	}
3045
3046
	/**
3047
	 * Sets the internal version number and activation state.
3048
	 * @static
3049
	 */
3050
	public static function plugin_initialize() {
3051
		if ( ! Jetpack_Options::get_option( 'activated' ) ) {
3052
			Jetpack_Options::update_option( 'activated', 2 );
3053
		}
3054
3055 View Code Duplication
		if ( ! Jetpack_Options::get_option( 'version' ) ) {
3056
			$version = $old_version = JETPACK__VERSION . ':' . time();
3057
			/** This action is documented in class.jetpack.php */
3058
			do_action( 'updating_jetpack_version', $version, false );
3059
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
3060
		}
3061
3062
		Jetpack::load_modules();
3063
3064
		Jetpack_Options::delete_option( 'do_activate' );
3065
	}
3066
3067
	/**
3068
	 * Removes all connection options
3069
	 * @static
3070
	 */
3071
	public static function plugin_deactivation( ) {
3072
		require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
3073
		if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
3074
			Jetpack_Network::init()->deactivate();
3075
		} else {
3076
			Jetpack::disconnect( false );
3077
			//Jetpack_Heartbeat::init()->deactivate();
3078
		}
3079
	}
3080
3081
	/**
3082
	 * Disconnects from the Jetpack servers.
3083
	 * Forgets all connection details and tells the Jetpack servers to do the same.
3084
	 * @static
3085
	 */
3086
	public static function disconnect( $update_activated_state = true ) {
3087
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
3088
		Jetpack::clean_nonces( true );
3089
3090
		Jetpack::load_xml_rpc_client();
3091
		$xml = new Jetpack_IXR_Client();
3092
		$xml->query( 'jetpack.deregister' );
3093
3094
		Jetpack_Options::delete_option(
3095
			array(
3096
				'register',
3097
				'blog_token',
3098
				'user_token',
3099
				'user_tokens',
3100
				'master_user',
3101
				'time_diff',
3102
				'fallback_no_verify_ssl_certs',
3103
			)
3104
		);
3105
3106
		if ( $update_activated_state ) {
3107
			Jetpack_Options::update_option( 'activated', 4 );
3108
		}
3109
3110
		$jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' );
3111
		// Check then record unique disconnection if site has never been disconnected previously
3112
		if ( -1 == $jetpack_unique_connection['disconnected'] ) {
3113
			$jetpack_unique_connection['disconnected'] = 1;
3114
		}
3115
		else {
3116
			if ( 0 == $jetpack_unique_connection['disconnected'] ) {
3117
				//track unique disconnect
3118
				$jetpack = Jetpack::init();
3119
3120
				$jetpack->stat( 'connections', 'unique-disconnect' );
3121
				$jetpack->do_stats( 'server_side' );
3122
			}
3123
			// increment number of times disconnected
3124
			$jetpack_unique_connection['disconnected'] += 1;
3125
		}
3126
3127
		Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
3128
3129
		// Disable the Heartbeat cron
3130
		Jetpack_Heartbeat::init()->deactivate();
3131
	}
3132
3133
	/**
3134
	 * Unlinks the current user from the linked WordPress.com user
3135
	 */
3136
	public static function unlink_user( $user_id = null ) {
3137
		if ( ! $tokens = Jetpack_Options::get_option( 'user_tokens' ) )
3138
			return false;
3139
3140
		$user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
3141
3142
		if ( Jetpack_Options::get_option( 'master_user' ) == $user_id )
3143
			return false;
3144
3145
		if ( ! isset( $tokens[ $user_id ] ) )
3146
			return false;
3147
3148
		Jetpack::load_xml_rpc_client();
3149
		$xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
3150
		$xml->query( 'jetpack.unlink_user', $user_id );
3151
3152
		unset( $tokens[ $user_id ] );
3153
3154
		Jetpack_Options::update_option( 'user_tokens', $tokens );
3155
3156
		return true;
3157
	}
3158
3159
	/**
3160
	 * Attempts Jetpack registration.  If it fail, a state flag is set: @see ::admin_page_load()
3161
	 */
3162
	public static function try_registration() {
3163
		// Let's get some testing in beta versions and such.
3164
		if ( self::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
3165
			// Before attempting to connect, let's make sure that the domains are viable.
3166
			$domains_to_check = array_unique( array(
3167
				'siteurl' => parse_url( get_site_url(), PHP_URL_HOST ),
3168
				'homeurl' => parse_url( get_home_url(), PHP_URL_HOST ),
3169
			) );
3170
			foreach ( $domains_to_check as $domain ) {
3171
				$result = Jetpack_Data::is_usable_domain( $domain );
3172
				if ( is_wp_error( $result ) ) {
3173
					return $result;
3174
				}
3175
			}
3176
		}
3177
3178
		$result = Jetpack::register();
3179
3180
		// If there was an error with registration and the site was not registered, record this so we can show a message.
3181
		if ( ! $result || is_wp_error( $result ) ) {
3182
			return $result;
3183
		} else {
3184
			return true;
3185
		}
3186
	}
3187
3188
	/**
3189
	 * Tracking an internal event log. Try not to put too much chaff in here.
3190
	 *
3191
	 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
3192
	 */
3193
	public static function log( $code, $data = null ) {
3194
		// only grab the latest 200 entries
3195
		$log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3196
3197
		// Append our event to the log
3198
		$log_entry = array(
3199
			'time'    => time(),
3200
			'user_id' => get_current_user_id(),
3201
			'blog_id' => Jetpack_Options::get_option( 'id' ),
3202
			'code'    => $code,
3203
		);
3204
		// Don't bother storing it unless we've got some.
3205
		if ( ! is_null( $data ) ) {
3206
			$log_entry['data'] = $data;
3207
		}
3208
		$log[] = $log_entry;
3209
3210
		// Try add_option first, to make sure it's not autoloaded.
3211
		// @todo: Add an add_option method to Jetpack_Options
3212
		if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
3213
			Jetpack_Options::update_option( 'log', $log );
3214
		}
3215
3216
		/**
3217
		 * Fires when Jetpack logs an internal event.
3218
		 *
3219
		 * @since 3.0.0
3220
		 *
3221
		 * @param array $log_entry {
3222
		 *	Array of details about the log entry.
3223
		 *
3224
		 *	@param string time Time of the event.
3225
		 *	@param int user_id ID of the user who trigerred the event.
3226
		 *	@param int blog_id Jetpack Blog ID.
3227
		 *	@param string code Unique name for the event.
3228
		 *	@param string data Data about the event.
3229
		 * }
3230
		 */
3231
		do_action( 'jetpack_log_entry', $log_entry );
3232
	}
3233
3234
	/**
3235
	 * Get the internal event log.
3236
	 *
3237
	 * @param $event (string) - only return the specific log events
3238
	 * @param $num   (int)    - get specific number of latest results, limited to 200
3239
	 *
3240
	 * @return array of log events || WP_Error for invalid params
3241
	 */
3242
	public static function get_log( $event = false, $num = false ) {
3243
		if ( $event && ! is_string( $event ) ) {
3244
			return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
3245
		}
3246
3247
		if ( $num && ! is_numeric( $num ) ) {
3248
			return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
3249
		}
3250
3251
		$entire_log = Jetpack_Options::get_option( 'log', array() );
3252
3253
		// If nothing set - act as it did before, otherwise let's start customizing the output
3254
		if ( ! $num && ! $event ) {
3255
			return $entire_log;
3256
		} else {
3257
			$entire_log = array_reverse( $entire_log );
3258
		}
3259
3260
		$custom_log_output = array();
3261
3262
		if ( $event ) {
3263
			foreach ( $entire_log as $log_event ) {
3264
				if ( $event == $log_event[ 'code' ] ) {
3265
					$custom_log_output[] = $log_event;
3266
				}
3267
			}
3268
		} else {
3269
			$custom_log_output = $entire_log;
3270
		}
3271
3272
		if ( $num ) {
3273
			$custom_log_output = array_slice( $custom_log_output, 0, $num );
3274
		}
3275
3276
		return $custom_log_output;
3277
	}
3278
3279
	/**
3280
	 * Log modification of important settings.
3281
	 */
3282
	public static function log_settings_change( $option, $old_value, $value ) {
3283
		switch( $option ) {
3284
			case 'jetpack_sync_non_public_post_stati':
3285
				self::log( $option, $value );
3286
				break;
3287
		}
3288
	}
3289
3290
	/**
3291
	 * Return stat data for WPCOM sync
3292
	 */
3293
	function get_stat_data() {
3294
		$heartbeat_data = Jetpack_Heartbeat::generate_stats_array();
3295
		$additional_data = $this->get_additional_stat_data();
3296
3297
		return json_encode( array_merge( $heartbeat_data, $additional_data ) );
3298
	}
3299
3300
	/**
3301
	 * Get additional stat data to sync to WPCOM
3302
	 */
3303
	function get_additional_stat_data( $prefix = '' ) {
3304
		$return["{$prefix}themes"]         = Jetpack::get_parsed_theme_data();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = array(); before regardless.

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

Let’s take a look at an example:

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

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

    // do something with $myArray
}

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

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

Loading history...
3305
		$return["{$prefix}plugins-extra"]  = Jetpack::get_parsed_plugin_data();
3306
		$return["{$prefix}users"]          = count_users();
3307
		$return["{$prefix}site-count"]     = 0;
3308
		if ( function_exists( 'get_blog_count' ) ) {
3309
			$return["{$prefix}site-count"] = get_blog_count();
3310
		}
3311
		return $return;
3312
	}
3313
3314
	/* Admin Pages */
3315
3316
	function admin_init() {
3317
		// If the plugin is not connected, display a connect message.
3318
		if (
3319
			// the plugin was auto-activated and needs its candy
3320
			Jetpack_Options::get_option( 'do_activate' )
3321
		||
3322
			// the plugin is active, but was never activated.  Probably came from a site-wide network activation
3323
			! Jetpack_Options::get_option( 'activated' )
3324
		) {
3325
			Jetpack::plugin_initialize();
3326
		}
3327
3328
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
3329
			if ( 4 != Jetpack_Options::get_option( 'activated' ) ) {
3330
				// Show connect notice on dashboard and plugins pages
3331
				add_action( 'load-index.php', array( $this, 'prepare_connect_notice' ) );
3332
				add_action( 'load-plugins.php', array( $this, 'prepare_connect_notice' ) );
3333
			}
3334
		} elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
3335
			// Upgrade: 1.1 -> 1.1.1
3336
			// Check and see if host can verify the Jetpack servers' SSL certificate
3337
			$args = array();
3338
			Jetpack_Client::_wp_remote_request(
3339
				Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'test' ) ),
3340
				$args,
3341
				true
3342
			);
3343
		} else {
3344
			// Show the notice on the Dashboard only for now
3345
3346
			add_action( 'load-index.php', array( $this, 'prepare_manage_jetpack_notice' ) );
3347
3348
			// Identity crisis notices
3349
			add_action( 'jetpack_notices', array( $this, 'alert_identity_crisis' ) );
3350
		}
3351
3352
		// If the plugin has just been disconnected from WP.com, show the survey notice
3353
		if ( isset( $_GET['disconnected'] ) && 'true' === $_GET['disconnected'] ) {
3354
			add_action( 'jetpack_notices', array( $this, 'disconnect_survey_notice' ) );
3355
		}
3356
3357
		if ( current_user_can( 'manage_options' ) && 'ALWAYS' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
3358
			add_action( 'admin_notices', array( $this, 'alert_required_ssl_fail' ) );
3359
		}
3360
3361
		add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3362
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3363
		add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3364
3365
		if ( Jetpack::is_active() || Jetpack::is_development_mode() ) {
3366
			// Artificially throw errors in certain whitelisted cases during plugin activation
3367
			add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3368
3369
			// Kick off synchronization of user role when it changes
3370
			add_action( 'set_user_role', array( $this, 'user_role_change' ) );
3371
		}
3372
3373
		// Jetpack Manage Activation Screen from .com
3374
		Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
3375
	}
3376
3377
	function admin_body_class( $admin_body_class = '' ) {
3378
		$classes = explode( ' ', trim( $admin_body_class ) );
3379
3380
		$classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
3381
3382
		$admin_body_class = implode( ' ', array_unique( $classes ) );
3383
		return " $admin_body_class ";
3384
	}
3385
3386
	static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3387
		return $admin_body_class . ' jetpack-pagestyles ';
3388
	}
3389
3390
	function prepare_connect_notice() {
3391
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3392
3393
		add_action( 'admin_notices', array( $this, 'admin_connect_notice' ) );
3394
3395
		if ( Jetpack::state( 'network_nag' ) )
3396
			add_action( 'network_admin_notices', array( $this, 'network_connect_notice' ) );
3397
	}
3398
	/**
3399
	 * Call this function if you want the Big Jetpack Manage Notice to show up.
3400
	 *
3401
	 * @return null
3402
	 */
3403
	function prepare_manage_jetpack_notice() {
3404
3405
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3406
		add_action( 'admin_notices', array( $this, 'admin_jetpack_manage_notice' ) );
3407
	}
3408
3409
	function manage_activate_screen() {
3410
		include ( JETPACK__PLUGIN_DIR . 'modules/manage/activate-admin.php' );
3411
	}
3412
	/**
3413
	 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3414
	 * This function artificially throws errors for such cases (whitelisted).
3415
	 *
3416
	 * @param string $plugin The activated plugin.
3417
	 */
3418
	function throw_error_on_activate_plugin( $plugin ) {
3419
		$active_modules = Jetpack::get_active_modules();
3420
3421
		// The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3422
		if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3423
			$throw = false;
3424
3425
			// Try and make sure it really was the stats plugin
3426
			if ( ! class_exists( 'ReflectionFunction' ) ) {
3427
				if ( 'stats.php' == basename( $plugin ) ) {
3428
					$throw = true;
3429
				}
3430
			} else {
3431
				$reflection = new ReflectionFunction( 'stats_get_api_key' );
3432
				if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3433
					$throw = true;
3434
				}
3435
			}
3436
3437
			if ( $throw ) {
3438
				trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3439
			}
3440
		}
3441
	}
3442
3443
	function intercept_plugin_error_scrape_init() {
3444
		add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3445
	}
3446
3447
	function intercept_plugin_error_scrape( $action, $result ) {
3448
		if ( ! $result ) {
3449
			return;
3450
		}
3451
3452
		foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3453
			if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3454
				Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3455
			}
3456
		}
3457
	}
3458
3459
	function add_remote_request_handlers() {
3460
		add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
3461
	}
3462
3463
	function remote_request_handlers() {
3464
		switch ( current_filter() ) {
3465
		case 'wp_ajax_nopriv_jetpack_upload_file' :
3466
			$response = $this->upload_handler();
3467
			break;
3468
		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...
3469
			$response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
3470
			break;
3471
		}
3472
3473
		if ( ! $response ) {
3474
			$response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
3475
		}
3476
3477
		if ( is_wp_error( $response ) ) {
3478
			$status_code       = $response->get_error_data();
3479
			$error             = $response->get_error_code();
3480
			$error_description = $response->get_error_message();
3481
3482
			if ( ! is_int( $status_code ) ) {
3483
				$status_code = 400;
3484
			}
3485
3486
			status_header( $status_code );
3487
			die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3488
		}
3489
3490
		status_header( 200 );
3491
		if ( true === $response ) {
3492
			exit;
3493
		}
3494
3495
		die( json_encode( (object) $response ) );
3496
	}
3497
3498
	function upload_handler() {
3499
		if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3500
			return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
3501
		}
3502
3503
		$user = wp_authenticate( '', '' );
3504
		if ( ! $user || is_wp_error( $user ) ) {
3505
			return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
3506
		}
3507
3508
		wp_set_current_user( $user->ID );
3509
3510
		if ( ! current_user_can( 'upload_files' ) ) {
3511
			return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3512
		}
3513
3514
		if ( empty( $_FILES ) ) {
3515
			return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3516
		}
3517
3518
		foreach ( array_keys( $_FILES ) as $files_key ) {
3519
			if ( ! isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
3520
				return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3521
			}
3522
		}
3523
3524
		$media_keys = array_keys( $_FILES['media'] );
3525
3526
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
3527
		if ( ! $token || is_wp_error( $token ) ) {
3528
			return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3529
		}
3530
3531
		$uploaded_files = array();
3532
		$global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3533
		unset( $GLOBALS['post'] );
3534
		foreach ( $_FILES['media']['name'] as $index => $name ) {
3535
			$file = array();
3536
			foreach ( $media_keys as $media_key ) {
3537
				$file[$media_key] = $_FILES['media'][$media_key][$index];
3538
			}
3539
3540
			list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
3541
3542
			$hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3543
			if ( $hmac_provided !== $hmac_file ) {
3544
				$uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
3545
				continue;
3546
			}
3547
3548
			$_FILES['.jetpack.upload.'] = $file;
3549
			$post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
3550
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
3551
				$post_id = 0;
3552
			}
3553
			$attachment_id = media_handle_upload(
3554
				'.jetpack.upload.',
3555
				$post_id,
3556
				array(),
3557
				array(
3558
					'action' => 'jetpack_upload_file',
3559
				)
3560
			);
3561
3562
			if ( ! $attachment_id ) {
3563
				$uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
3564
			} elseif ( is_wp_error( $attachment_id ) ) {
3565
				$uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
3566
			} else {
3567
				$attachment = get_post( $attachment_id );
3568
				$uploaded_files[$index] = (object) array(
3569
					'id'   => (string) $attachment_id,
3570
					'file' => $attachment->post_title,
3571
					'url'  => wp_get_attachment_url( $attachment_id ),
3572
					'type' => $attachment->post_mime_type,
3573
					'meta' => wp_get_attachment_metadata( $attachment_id ),
3574
				);
3575
			}
3576
		}
3577
		if ( ! is_null( $global_post ) ) {
3578
			$GLOBALS['post'] = $global_post;
3579
		}
3580
3581
		return $uploaded_files;
3582
	}
3583
3584
	/**
3585
	 * Add help to the Jetpack page
3586
	 *
3587
	 * @since Jetpack (1.2.3)
3588
	 * @return false if not the Jetpack page
3589
	 */
3590
	function admin_help() {
3591
		$current_screen = get_current_screen();
3592
3593
		// Overview
3594
		$current_screen->add_help_tab(
3595
			array(
3596
				'id'		=> 'home',
3597
				'title'		=> __( 'Home', 'jetpack' ),
3598
				'content'	=>
3599
					'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3600
					'<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3601
					'<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>',
3602
			)
3603
		);
3604
3605
		// Screen Content
3606
		if ( current_user_can( 'manage_options' ) ) {
3607
			$current_screen->add_help_tab(
3608
				array(
3609
					'id'		=> 'settings',
3610
					'title'		=> __( 'Settings', 'jetpack' ),
3611
					'content'	=>
3612
						'<p><strong>' . __( 'Jetpack by WordPress.com',                                              'jetpack' ) . '</strong></p>' .
3613
						'<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3614
						'<ol>' .
3615
							'<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.',														'jetpack' ) . '</li>' .
3616
							'<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>' .
3617
						'</ol>' .
3618
						'<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>'
3619
				)
3620
			);
3621
		}
3622
3623
		// Help Sidebar
3624
		$current_screen->set_help_sidebar(
3625
			'<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3626
			'<p><a href="http://jetpack.com/faq/" target="_blank">'     . __( 'Jetpack FAQ',     'jetpack' ) . '</a></p>' .
3627
			'<p><a href="http://jetpack.com/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3628
			'<p><a href="' . Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) .'">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3629
		);
3630
	}
3631
3632
	function admin_menu_css() {
3633
		wp_enqueue_style( 'jetpack-icons' );
3634
	}
3635
3636
	function admin_menu_order() {
3637
		return true;
3638
	}
3639
3640 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
3641
		$jp_menu_order = array();
3642
3643
		foreach ( $menu_order as $index => $item ) {
3644
			if ( $item != 'jetpack' ) {
3645
				$jp_menu_order[] = $item;
3646
			}
3647
3648
			if ( $index == 0 ) {
3649
				$jp_menu_order[] = 'jetpack';
3650
			}
3651
		}
3652
3653
		return $jp_menu_order;
3654
	}
3655
3656
	function admin_head() {
3657 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) )
3658
			/** This action is documented in class.jetpack-admin-page.php */
3659
			do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
3660
	}
3661
3662
	function admin_banner_styles() {
3663
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3664
3665
		wp_enqueue_style( 'jetpack', plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION . '-20121016' );
3666
		wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3667
		wp_style_add_data( 'jetpack', 'suffix', $min );
3668
	}
3669
3670
	function admin_scripts() {
3671
		wp_enqueue_script( 'jetpack-js', plugins_url( '_inc/jp.js', JETPACK__PLUGIN_FILE ), array( 'jquery', 'wp-util' ), JETPACK__VERSION . '-20121111' );
3672
		wp_localize_script(
3673
			'jetpack-js',
3674
			'jetpackL10n',
3675
			array(
3676
				'ays_disconnect' => "This will deactivate all Jetpack modules.\nAre you sure you want to disconnect?",
3677
				'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?",
3678
				'ays_dismiss'    => "This will deactivate Jetpack.\nAre you sure you want to deactivate Jetpack?",
3679
			)
3680
		);
3681
		add_action( 'admin_footer', array( $this, 'do_stats' ) );
3682
	}
3683
3684
	function plugin_action_links( $actions ) {
3685
3686
		$jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack' ), __( 'Jetpack', 'jetpack' ) ) );
3687
3688
		if( current_user_can( 'jetpack_manage_modules' ) && ( Jetpack::is_active() || Jetpack::is_development_mode() ) ) {
3689
			return array_merge(
3690
				$jetpack_home,
3691
				array( 'settings' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack_modules' ), __( 'Settings', 'jetpack' ) ) ),
3692
				array( 'support' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack-debugger '), __( 'Support', 'jetpack' ) ) ),
3693
				$actions
3694
				);
3695
			}
3696
3697
		return array_merge( $jetpack_home, $actions );
3698
	}
3699
3700
	function admin_connect_notice() {
3701
		// Don't show the connect notice anywhere but the plugins.php after activating
3702
		$current = get_current_screen();
3703
		if ( 'plugins' !== $current->parent_base )
3704
			return;
3705
3706
		if ( ! current_user_can( 'jetpack_connect' ) )
3707
			return;
3708
3709
		$dismiss_and_deactivate_url = wp_nonce_url( Jetpack::admin_url( '?page=jetpack&jetpack-notice=dismiss' ), 'jetpack-deactivate' );
3710
		?>
3711
		<div id="message" class="updated jetpack-message jp-banner" style="display:block !important;">
3712
			<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>
3713
			<?php if ( in_array( Jetpack_Options::get_option( 'activated' ) , array( 1, 2, 3 ) ) ) : ?>
3714
				<div class="jp-banner__content is-connection">
3715
					<h2><?php _e( 'Your Jetpack is almost ready!', 'jetpack' ); ?></h2>
3716
					<p><?php _e( 'Connect now to enable features like Stats, Likes, and Social Sharing.', 'jetpack' ); ?></p>
3717
				</div>
3718
				<div class="jp-banner__action-container is-connection">
3719
						<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>
3720
				</div>
3721 View Code Duplication
			<?php else : ?>
3722
				<div class="jp-banner__content">
3723
					<h2><?php _e( 'Jetpack is installed!', 'jetpack' ) ?></h2>
3724
					<p><?php _e( 'It\'s ready to bring awesome, WordPress.com cloud-powered features to your site.', 'jetpack' ) ?></p>
3725
				</div>
3726
				<div class="jp-banner__action-container">
3727
					<a href="<?php echo Jetpack::admin_url() ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'Learn More', 'jetpack' ); ?></a>
3728
				</div>
3729
			<?php endif; ?>
3730
		</div>
3731
3732
		<?php
3733
	}
3734
3735
	/**
3736
	 * This is the first banner
3737
	 * It should be visible only to user that can update the option
3738
	 * Are not connected
3739
	 *
3740
	 * @return null
3741
	 */
3742
	function admin_jetpack_manage_notice() {
3743
		$screen = get_current_screen();
3744
3745
		// Don't show the connect notice on the jetpack settings page.
3746
		if ( ! in_array( $screen->base, array( 'dashboard' ) ) || $screen->is_network || $screen->action )
3747
			return;
3748
3749
		// Only show it if don't have the managment option set.
3750
		// And not dismissed it already.
3751
		if ( ! $this->can_display_jetpack_manage_notice() || Jetpack_Options::get_option( 'dismissed_manage_banner' ) ) {
3752
			return;
3753
		}
3754
3755
		$opt_out_url = $this->opt_out_jetpack_manage_url();
3756
		$opt_in_url  = $this->opt_in_jetpack_manage_url();
3757
		/**
3758
		 * I think it would be great to have different wordsing depending on where you are
3759
		 * for example if we show the notice on dashboard and a different one if we show it on Plugins screen
3760
		 * etc..
3761
		 */
3762
3763
		?>
3764
		<div id="message" class="updated jetpack-message jp-banner is-opt-in" style="display:block !important;">
3765
			<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>
3766
			<div class="jp-banner__content">
3767
				<h2><?php esc_html_e( 'New in Jetpack: Centralized Site Management', 'jetpack' ); ?></h2>
3768
				<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>
3769
			</div>
3770
			<div class="jp-banner__action-container is-opt-in">
3771
				<a href="<?php echo esc_url( $opt_in_url ); ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'Activate now', 'jetpack' ); ?></a>
3772
			</div>
3773
		</div>
3774
		<?php
3775
	}
3776
3777
	/**
3778
	 * Returns the url that the user clicks to remove the notice for the big banner
3779
	 * @return (string)
3780
	 */
3781
	function opt_out_jetpack_manage_url() {
3782
		$referer = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
3783
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-out' . $referer ), 'jetpack_manage_banner_opt_out' );
3784
	}
3785
	/**
3786
	 * Returns the url that the user clicks to opt in to Jetpack Manage
3787
	 * @return (string)
3788
	 */
3789
	function opt_in_jetpack_manage_url() {
3790
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-in' ), 'jetpack_manage_banner_opt_in' );
3791
	}
3792
3793
	function opt_in_jetpack_manage_notice() {
3794
		?>
3795
		<div class="wrap">
3796
			<div id="message" class="jetpack-message is-opt-in">
3797
				<?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' ); ?>
3798
			</div>
3799
		</div>
3800
		<?php
3801
3802
	}
3803
	/**
3804
	 * Determines whether to show the notice of not true = display notice
3805
	 * @return (bool)
3806
	 */
3807
	function can_display_jetpack_manage_notice() {
3808
		// never display the notice to users that can't do anything about it anyways
3809
		if( ! current_user_can( 'jetpack_manage_modules' ) )
3810
			return false;
3811
3812
		// don't display if we are in development more
3813
		if( Jetpack::is_development_mode() ) {
3814
			return false;
3815
		}
3816
		// don't display if the site is private
3817
		if(  ! Jetpack_Options::get_option( 'public' ) )
3818
			return false;
3819
3820
		/**
3821
		 * Should the Jetpack Remote Site Management notice be displayed.
3822
		 *
3823
		 * @since 3.3.0
3824
		 *
3825
		 * @param bool ! self::is_module_active( 'manage' ) Is the Manage module inactive.
3826
		 */
3827
		return apply_filters( 'can_display_jetpack_manage_notice', ! self::is_module_active( 'manage' ) );
3828
	}
3829
3830
	function network_connect_notice() {
3831
		?>
3832
		<div id="message" class="updated jetpack-message">
3833
			<div class="squeezer">
3834
				<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>
3835
			</div>
3836
		</div>
3837
		<?php
3838
	}
3839
3840
	public static function jetpack_comment_notice() {
3841
		if ( in_array( 'comments', Jetpack::get_active_modules() ) ) {
3842
			return '';
3843
		}
3844
3845
		$jetpack_old_version = explode( ':', Jetpack_Options::get_option( 'old_version' ) );
3846
		$jetpack_new_version = explode( ':', Jetpack_Options::get_option( 'version' ) );
3847
3848
		if ( $jetpack_old_version ) {
3849
			if ( version_compare( $jetpack_old_version[0], '1.4', '>=' ) ) {
3850
				return '';
3851
			}
3852
		}
3853
3854
		if ( $jetpack_new_version ) {
3855
			if ( version_compare( $jetpack_new_version[0], '1.4-something', '<' ) ) {
3856
				return '';
3857
			}
3858
		}
3859
3860
		return '<br /><br />' . sprintf(
3861
			__( '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' ),
3862
			wp_nonce_url(
3863
				Jetpack::admin_url(
3864
					array(
3865
						'page'   => 'jetpack',
3866
						'action' => 'activate',
3867
						'module' => 'comments',
3868
					)
3869
				),
3870
				'jetpack_activate-comments'
3871
			),
3872
			__( 'click here', 'jetpack' )
3873
		);
3874
	}
3875
3876
	/**
3877
	 * Show the survey link when the user has just disconnected Jetpack.
3878
	 */
3879
	function disconnect_survey_notice() {
3880
		?>
3881
		<div class="wrap">
3882
			<div id="message" class="jetpack-message stay-visible">
3883
				<div class="squeezer">
3884
					<h2>
3885
						<?php _e( 'You have successfully disconnected Jetpack.', 'jetpack' ); ?>
3886
						<br />
3887
						<?php echo sprintf(
3888
							__( 'Would you tell us why? Just <a href="%1$s" target="%2$s">answering two simple questions</a> would help us improve Jetpack.', 'jetpack' ),
3889
							'https://jetpack.com/survey-disconnected/',
3890
							'_blank'
3891
						); ?>
3892
					</h2>
3893
				</div>
3894
			</div>
3895
		</div>
3896
		<?php
3897
	}
3898
3899
	/*
3900
	 * Registration flow:
3901
	 * 1 - ::admin_page_load() action=register
3902
	 * 2 - ::try_registration()
3903
	 * 3 - ::register()
3904
	 *     - Creates jetpack_register option containing two secrets and a timestamp
3905
	 *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3906
	 *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3907
	 *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
3908
	 *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
3909
	 *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
3910
	 *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
3911
	 *       jetpack_id, jetpack_secret, jetpack_public
3912
	 *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
3913
	 * 4 - redirect to https://wordpress.com/start/jetpack-connect
3914
	 * 5 - user logs in with WP.com account
3915
	 * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
3916
	 *		- Jetpack_Client_Server::authorize()
3917
	 *		- Jetpack_Client_Server::get_token()
3918
	 *		- GET https://jetpack.wordpress.com/jetpack.token/1/ with
3919
	 *        client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
3920
	 *			- which responds with access_token, token_type, scope
3921
	 *		- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
3922
	 *		- Jetpack::activate_default_modules()
3923
	 *     		- Deactivates deprecated plugins
3924
	 *     		- Activates all default modules
3925
	 *		- Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
3926
	 * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
3927
	 * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
3928
	 *     Done!
3929
	 */
3930
3931
	/**
3932
	 * Handles the page load events for the Jetpack admin page
3933
	 */
3934
	function admin_page_load() {
3935
		$error = false;
3936
3937
		// Make sure we have the right body class to hook stylings for subpages off of.
3938
		add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ) );
3939
3940
		if ( ! empty( $_GET['jetpack_restate'] ) ) {
3941
			// Should only be used in intermediate redirects to preserve state across redirects
3942
			Jetpack::restate();
3943
		}
3944
3945
		if ( isset( $_GET['connect_url_redirect'] ) ) {
3946
			// User clicked in the iframe to link their accounts
3947
			if ( ! Jetpack::is_user_connected() ) {
3948
				$connect_url = $this->build_connect_url( true, false, 'iframe' );
3949
				if ( isset( $_GET['notes_iframe'] ) )
3950
					$connect_url .= '&notes_iframe';
3951
				wp_redirect( $connect_url );
3952
				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...
3953
			} else {
3954
				Jetpack::state( 'message', 'already_authorized' );
3955
				wp_safe_redirect( Jetpack::admin_url() );
3956
				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...
3957
			}
3958
		}
3959
3960
3961
		if ( isset( $_GET['action'] ) ) {
3962
			switch ( $_GET['action'] ) {
3963
			case 'authorize':
3964
				if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
3965
					Jetpack::state( 'message', 'already_authorized' );
3966
					wp_safe_redirect( Jetpack::admin_url() );
3967
					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...
3968
				}
3969
				Jetpack::log( 'authorize' );
3970
				$client_server = new Jetpack_Client_Server;
3971
				$client_server->client_authorize();
3972
				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...
3973
			case 'register' :
3974
				if ( ! current_user_can( 'jetpack_connect' ) ) {
3975
					$error = 'cheatin';
3976
					break;
3977
				}
3978
				check_admin_referer( 'jetpack-register' );
3979
				Jetpack::log( 'register' );
3980
				Jetpack::maybe_set_version_option();
3981
				$registered = Jetpack::try_registration();
3982
				if ( is_wp_error( $registered ) ) {
3983
					$error = $registered->get_error_code();
3984
					Jetpack::state( 'error_description', $registered->get_error_message() );
3985
					break;
3986
				}
3987
				
3988
				$from = isset( $_GET['from'] ) ? $_GET['from'] : false;
3989
3990
				wp_redirect( $this->build_connect_url( true, false, $from ) );
3991
				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...
3992
			case 'activate' :
3993
				if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3994
					$error = 'cheatin';
3995
					break;
3996
				}
3997
3998
				$module = stripslashes( $_GET['module'] );
3999
				check_admin_referer( "jetpack_activate-$module" );
4000
				Jetpack::log( 'activate', $module );
4001
				Jetpack::activate_module( $module );
4002
				// The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
4003
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4004
				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...
4005
			case 'activate_default_modules' :
4006
				check_admin_referer( 'activate_default_modules' );
4007
				Jetpack::log( 'activate_default_modules' );
4008
				Jetpack::restate();
4009
				$min_version   = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
4010
				$max_version   = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
4011
				$other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
4012
				Jetpack::activate_default_modules( $min_version, $max_version, $other_modules );
4013
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4014
				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...
4015
			case 'disconnect' :
4016
				if ( ! current_user_can( 'jetpack_disconnect' ) ) {
4017
					$error = 'cheatin';
4018
					break;
4019
				}
4020
4021
				check_admin_referer( 'jetpack-disconnect' );
4022
				Jetpack::log( 'disconnect' );
4023
				Jetpack::disconnect();
4024
				wp_safe_redirect( Jetpack::admin_url( 'disconnected=true' ) );
4025
				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...
4026
			case 'reconnect' :
4027
				if ( ! current_user_can( 'jetpack_reconnect' ) ) {
4028
					$error = 'cheatin';
4029
					break;
4030
				}
4031
4032
				check_admin_referer( 'jetpack-reconnect' );
4033
				Jetpack::log( 'reconnect' );
4034
				$this->disconnect();
4035
				wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
4036
				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...
4037 View Code Duplication
			case 'deactivate' :
4038
				if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
4039
					$error = 'cheatin';
4040
					break;
4041
				}
4042
4043
				$modules = stripslashes( $_GET['module'] );
4044
				check_admin_referer( "jetpack_deactivate-$modules" );
4045
				foreach ( explode( ',', $modules ) as $module ) {
4046
					Jetpack::log( 'deactivate', $module );
4047
					Jetpack::deactivate_module( $module );
4048
					Jetpack::state( 'message', 'module_deactivated' );
4049
				}
4050
				Jetpack::state( 'module', $modules );
4051
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
4052
				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...
4053
			case 'unlink' :
4054
				$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
4055
				check_admin_referer( 'jetpack-unlink' );
4056
				Jetpack::log( 'unlink' );
4057
				$this->unlink_user();
4058
				Jetpack::state( 'message', 'unlinked' );
4059
				if ( 'sub-unlink' == $redirect ) {
4060
					wp_safe_redirect( admin_url() );
4061
				} else {
4062
					wp_safe_redirect( Jetpack::admin_url( array( 'page' => $redirect ) ) );
4063
				}
4064
				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...
4065
			default:
4066
				/**
4067
				 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
4068
				 *
4069
				 * @since 2.6.0
4070
				 *
4071
				 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
4072
				 */
4073
				do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
4074
			}
4075
		}
4076
4077
		if ( ! $error = $error ? $error : Jetpack::state( 'error' ) ) {
4078
			self::activate_new_modules( true );
4079
		}
4080
4081
		switch ( $error ) {
4082
		case 'cheatin' :
4083
			$this->error = __( 'Cheatin&#8217; uh?', 'jetpack' );
4084
			break;
4085
		case 'access_denied' :
4086
			$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>';
4087
			break;
4088
		case 'wrong_state' :
4089
			$this->error = __( 'You need to stay logged in to your WordPress blog while you authorize Jetpack.', 'jetpack' );
4090
			break;
4091
		case 'invalid_client' :
4092
			// @todo re-register instead of deactivate/reactivate
4093
			$this->error = __( 'We had an issue connecting Jetpack; deactivate then reactivate the Jetpack plugin, then connect again.', 'jetpack' );
4094
			break;
4095
		case 'invalid_grant' :
4096
			$this->error = __( 'There was an issue connecting your Jetpack. Please click &#8220;Connect to WordPress.com&#8221; again.', 'jetpack' );
4097
			break;
4098
		case 'site_inaccessible' :
4099
		case 'site_requires_authorization' :
4100
			$this->error = sprintf( __( 'Your website needs to be publicly accessible to use Jetpack: %s', 'jetpack' ), "<code>$error</code>" );
4101
			break;
4102
		case 'module_activation_failed' :
4103
			$module = Jetpack::state( 'module' );
4104
			if ( ! empty( $module ) && $mod = Jetpack::get_module( $module ) ) {
4105
				$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'] );
4106
				if ( isset( $this->plugins_to_deactivate[$module] ) ) {
4107
					$this->error .= ' ' . sprintf( __( 'Do you still have the %s plugin installed?', 'jetpack' ), $this->plugins_to_deactivate[$module][1] );
4108
				}
4109
			} else {
4110
				$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' );
4111
			}
4112
			if ( $php_errors = Jetpack::state( 'php_errors' ) ) {
4113
				$this->error .= "<br />\n";
4114
				$this->error .= $php_errors;
4115
			}
4116
			break;
4117
		case 'master_user_required' :
4118
			$module = Jetpack::state( 'module' );
4119
			$module_name = '';
4120
			if ( ! empty( $module ) && $mod = Jetpack::get_module( $module ) ) {
4121
				$module_name = $mod['name'];
4122
			}
4123
4124
			$master_user = Jetpack_Options::get_option( 'master_user' );
4125
			$master_userdata = get_userdata( $master_user ) ;
4126
			if ( $master_userdata ) {
4127
				if ( ! in_array( $module, Jetpack::get_active_modules() ) ) {
4128
					$this->error = sprintf( __( '%s was not activated.' , 'jetpack' ), $module_name );
4129
				} else {
4130
					$this->error = sprintf( __( '%s was not deactivated.' , 'jetpack' ), $module_name );
4131
				}
4132
				$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 ) );
4133
4134
			} else {
4135
				$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 );
4136
			}
4137
			break;
4138
		case 'not_public' :
4139
			$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' );
4140
			break;
4141
		case 'wpcom_408' :
4142
		case 'wpcom_5??' :
4143
		case 'wpcom_bad_response' :
4144
		case 'wpcom_outage' :
4145
			$this->error = __( 'WordPress.com is currently having problems and is unable to fuel up your Jetpack.  Please try again later.', 'jetpack' );
4146
			break;
4147
		case 'register_http_request_failed' :
4148
		case 'token_http_request_failed' :
4149
			$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>" );
4150
			break;
4151
		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...
4152
			if ( empty( $error ) ) {
4153
				break;
4154
			}
4155
			$error = trim( substr( strip_tags( $error ), 0, 20 ) );
4156
			// no break: fall through
4157
		case 'no_role' :
4158
		case 'no_cap' :
4159
		case 'no_code' :
4160
		case 'no_state' :
4161
		case 'invalid_state' :
4162
		case 'invalid_request' :
4163
		case 'invalid_scope' :
4164
		case 'unsupported_response_type' :
4165
		case 'invalid_token' :
4166
		case 'no_token' :
4167
		case 'missing_secrets' :
4168
		case 'home_missing' :
4169
		case 'siteurl_missing' :
4170
		case 'gmt_offset_missing' :
4171
		case 'site_name_missing' :
4172
		case 'secret_1_missing' :
4173
		case 'secret_2_missing' :
4174
		case 'site_lang_missing' :
4175
		case 'home_malformed' :
4176
		case 'siteurl_malformed' :
4177
		case 'gmt_offset_malformed' :
4178
		case 'timezone_string_malformed' :
4179
		case 'site_name_malformed' :
4180
		case 'secret_1_malformed' :
4181
		case 'secret_2_malformed' :
4182
		case 'site_lang_malformed' :
4183
		case 'secrets_mismatch' :
4184
		case 'verify_secret_1_missing' :
4185
		case 'verify_secret_1_malformed' :
4186
		case 'verify_secrets_missing' :
4187
		case 'verify_secrets_mismatch' :
4188
			$error = esc_html( $error );
4189
			$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>" );
4190
			if ( ! Jetpack::is_active() ) {
4191
				$this->error .= '<br />';
4192
				$this->error .= sprintf( __( 'Try connecting again.', 'jetpack' ) );
4193
			}
4194
			break;
4195
		}
4196
4197
		$message_code = Jetpack::state( 'message' );
4198
4199
		$active_state = Jetpack::state( 'activated_modules' );
4200
		if ( ! empty( $active_state ) ) {
4201
			$available    = Jetpack::get_available_modules();
4202
			$active_state = explode( ',', $active_state );
4203
			$active_state = array_intersect( $active_state, $available );
4204
			if ( count( $active_state ) ) {
4205
				foreach ( $active_state as $mod ) {
4206
					$this->stat( 'module-activated', $mod );
4207
				}
4208
			} else {
4209
				$active_state = false;
4210
			}
4211
		}
4212
		if( Jetpack::state( 'optin-manage' ) ) {
4213
			$activated_manage = $message_code;
4214
			$message_code = 'jetpack-manage';
4215
4216
		}
4217
		switch ( $message_code ) {
4218
		case 'modules_activated' :
4219
			$this->message = sprintf(
4220
				__( 'Welcome to <strong>Jetpack %s</strong>!', 'jetpack' ),
4221
				JETPACK__VERSION
4222
			);
4223
4224
			if ( $active_state ) {
4225
				$titles = array();
4226 View Code Duplication
				foreach ( $active_state as $mod ) {
4227
					if ( $mod_headers = Jetpack::get_module( $mod ) ) {
4228
						$titles[] = '<strong>' . preg_replace( '/\s+(?![^<>]++>)/', '&nbsp;', $mod_headers['name'] ) . '</strong>';
4229
					}
4230
				}
4231
				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...
4232
					$this->message .= '<br /><br />' . wp_sprintf( __( 'The following new modules have been activated: %l.', 'jetpack' ), $titles );
4233
				}
4234
			}
4235
4236
			if ( $reactive_state = Jetpack::state( 'reactivated_modules' ) ) {
4237
				$titles = array();
4238 View Code Duplication
				foreach ( explode( ',',  $reactive_state ) as $mod ) {
4239
					if ( $mod_headers = Jetpack::get_module( $mod ) ) {
4240
						$titles[] = '<strong>' . preg_replace( '/\s+(?![^<>]++>)/', '&nbsp;', $mod_headers['name'] ) . '</strong>';
4241
					}
4242
				}
4243
				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...
4244
					$this->message .= '<br /><br />' . wp_sprintf( __( 'The following modules have been updated: %l.', 'jetpack' ), $titles );
4245
				}
4246
			}
4247
4248
			$this->message .= Jetpack::jetpack_comment_notice();
4249
			break;
4250
		case 'jetpack-manage':
4251
			$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>';
4252
			if ( $activated_manage ) {
4253
				$this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack'  ) . '</strong>';
4254
			}
4255
			break;
4256
		case 'module_activated' :
4257
			if ( $module = Jetpack::get_module( Jetpack::state( 'module' ) ) ) {
4258
				$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'] );
4259
				$this->stat( 'module-activated', Jetpack::state( 'module' ) );
4260
			}
4261
			break;
4262
4263
		case 'module_deactivated' :
4264
			$modules = Jetpack::state( 'module' );
4265
			if ( ! $modules ) {
4266
				break;
4267
			}
4268
4269
			$module_names = array();
4270
			foreach ( explode( ',', $modules ) as $module_slug ) {
4271
				$module = Jetpack::get_module( $module_slug );
4272
				if ( $module ) {
4273
					$module_names[] = $module['name'];
4274
				}
4275
4276
				$this->stat( 'module-deactivated', $module_slug );
4277
			}
4278
4279
			if ( ! $module_names ) {
4280
				break;
4281
			}
4282
4283
			$this->message = wp_sprintf(
4284
				_nx(
4285
					'<strong>%l Deactivated!</strong> You can activate it again at any time using the activate link next to each module.',
4286
					'<strong>%l Deactivated!</strong> You can activate them again at any time using the activate links next to each module.',
4287
					count( $module_names ),
4288
					'%l = list of Jetpack module/feature names',
4289
					'jetpack'
4290
				),
4291
				$module_names
4292
			);
4293
			break;
4294
4295
		case 'module_configured' :
4296
			$this->message = __( '<strong>Module settings were saved.</strong> ', 'jetpack' );
4297
			break;
4298
4299
		case 'already_authorized' :
4300
			$this->message = __( '<strong>Your Jetpack is already connected.</strong> ', 'jetpack' );
4301
			break;
4302
4303
		case 'authorized' :
4304
			$this->message  = __( '<strong>You&#8217;re fueled up and ready to go, Jetpack is now active.</strong> ', 'jetpack' );
4305
			$this->message .= Jetpack::jetpack_comment_notice();
4306
			break;
4307
4308
		case 'linked' :
4309
			$this->message  = __( '<strong>You&#8217;re fueled up and ready to go.</strong> ', 'jetpack' );
4310
			$this->message .= Jetpack::jetpack_comment_notice();
4311
			break;
4312
4313
		case 'unlinked' :
4314
			$user = wp_get_current_user();
4315
			$this->message = sprintf( __( '<strong>You have unlinked your account (%s) from WordPress.com.</strong>', 'jetpack' ), $user->user_login );
4316
			break;
4317
4318
		case 'switch_master' :
4319
			global $current_user;
4320
			$is_master_user = $current_user->ID == Jetpack_Options::get_option( 'master_user' );
4321
			$master_userdata = get_userdata( Jetpack_Options::get_option( 'master_user' ) );
4322
			if ( $is_master_user ) {
4323
				$this->message = __( 'You have successfully set yourself as Jetpack’s primary user.', 'jetpack' );
4324
			} else {
4325
				$this->message = sprintf( _x( 'You have successfully set %s as Jetpack’s primary user.', '%s is a username', 'jetpack' ), $master_userdata->user_login );
4326
			}
4327
			break;
4328
		}
4329
4330
		$deactivated_plugins = Jetpack::state( 'deactivated_plugins' );
4331
4332
		if ( ! empty( $deactivated_plugins ) ) {
4333
			$deactivated_plugins = explode( ',', $deactivated_plugins );
4334
			$deactivated_titles  = array();
4335
			foreach ( $deactivated_plugins as $deactivated_plugin ) {
4336
				if ( ! isset( $this->plugins_to_deactivate[$deactivated_plugin] ) ) {
4337
					continue;
4338
				}
4339
4340
				$deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[$deactivated_plugin][1] ) . '</strong>';
4341
			}
4342
4343
			if ( $deactivated_titles ) {
4344
				if ( $this->message ) {
4345
					$this->message .= "<br /><br />\n";
4346
				}
4347
4348
				$this->message .= wp_sprintf(
4349
					_n(
4350
						'Jetpack contains the most recent version of the old %l plugin.',
4351
						'Jetpack contains the most recent versions of the old %l plugins.',
4352
						count( $deactivated_titles ),
4353
						'jetpack'
4354
					),
4355
					$deactivated_titles
4356
				);
4357
4358
				$this->message .= "<br />\n";
4359
4360
				$this->message .= _n(
4361
					'The old version has been deactivated and can be removed from your site.',
4362
					'The old versions have been deactivated and can be removed from your site.',
4363
					count( $deactivated_titles ),
4364
					'jetpack'
4365
				);
4366
			}
4367
		}
4368
4369
		$this->privacy_checks = Jetpack::state( 'privacy_checks' );
4370
4371
		if ( $this->message || $this->error || $this->privacy_checks || $this->can_display_jetpack_manage_notice() ) {
4372
			add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
4373
		}
4374
4375 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
4376
			/**
4377
			 * Fires when a module configuration page is loaded.
4378
			 * The dynamic part of the hook is the configure parameter from the URL.
4379
			 *
4380
			 * @since 1.1.0
4381
			 */
4382
			do_action( 'jetpack_module_configuration_load_' . $_GET['configure'] );
4383
		}
4384
4385
		add_filter( 'jetpack_short_module_description', 'wptexturize' );
4386
	}
4387
4388
	function admin_notices() {
4389
4390
		if ( $this->error ) {
4391
?>
4392
<div id="message" class="jetpack-message jetpack-err">
4393
	<div class="squeezer">
4394
		<h2><?php echo wp_kses( $this->error, array( 'a' => array( 'href' => array() ), 'small' => true, 'code' => true, 'strong' => true, 'br' => true, 'b' => true ) ); ?></h2>
4395
<?php	if ( $desc = Jetpack::state( 'error_description' ) ) : ?>
4396
		<p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
4397
<?php	endif; ?>
4398
	</div>
4399
</div>
4400
<?php
4401
		}
4402
4403
		if ( $this->message ) {
4404
?>
4405
<div id="message" class="jetpack-message">
4406
	<div class="squeezer">
4407
		<h2><?php echo wp_kses( $this->message, array( 'strong' => array(), 'a' => array( 'href' => true ), 'br' => true ) ); ?></h2>
4408
	</div>
4409
</div>
4410
<?php
4411
		}
4412
4413
		if ( $this->privacy_checks ) :
4414
			$module_names = $module_slugs = array();
4415
4416
			$privacy_checks = explode( ',', $this->privacy_checks );
4417
			$privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
4418
			foreach ( $privacy_checks as $module_slug ) {
4419
				$module = Jetpack::get_module( $module_slug );
4420
				if ( ! $module ) {
4421
					continue;
4422
				}
4423
4424
				$module_slugs[] = $module_slug;
4425
				$module_names[] = "<strong>{$module['name']}</strong>";
4426
			}
4427
4428
			$module_slugs = join( ',', $module_slugs );
4429
?>
4430
<div id="message" class="jetpack-message jetpack-err">
4431
	<div class="squeezer">
4432
		<h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
4433
		<p><?php
4434
			echo wp_kses(
4435
				wptexturize(
4436
					wp_sprintf(
4437
						_nx(
4438
							"Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
4439
							"Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
4440
							count( $privacy_checks ),
4441
							'%l = list of Jetpack module/feature names',
4442
							'jetpack'
4443
						),
4444
						$module_names
4445
					)
4446
				),
4447
				array( 'strong' => true )
4448
			);
4449
4450
			echo "\n<br />\n";
4451
4452
			echo wp_kses(
4453
				sprintf(
4454
					_nx(
4455
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
4456
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
4457
						count( $privacy_checks ),
4458
						'%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
4459
						'jetpack'
4460
					),
4461
					wp_nonce_url(
4462
						Jetpack::admin_url(
4463
							array(
4464
								'page'   => 'jetpack',
4465
								'action' => 'deactivate',
4466
								'module' => urlencode( $module_slugs ),
4467
							)
4468
						),
4469
						"jetpack_deactivate-$module_slugs"
4470
					),
4471
					esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
4472
				),
4473
				array( 'a' => array( 'href' => true, 'title' => true ) )
4474
			);
4475
		?></p>
4476
	</div>
4477
</div>
4478
<?php endif;
4479
	// only display the notice if the other stuff is not there
4480
	if( $this->can_display_jetpack_manage_notice() && !  $this->error && ! $this->message && ! $this->privacy_checks ) {
4481
		if( isset( $_GET['page'] ) && 'jetpack' != $_GET['page'] )
4482
			$this->opt_in_jetpack_manage_notice();
4483
		}
4484
	}
4485
4486
	/**
4487
	 * Record a stat for later output.  This will only currently output in the admin_footer.
4488
	 */
4489
	function stat( $group, $detail ) {
4490
		if ( ! isset( $this->stats[ $group ] ) )
4491
			$this->stats[ $group ] = array();
4492
		$this->stats[ $group ][] = $detail;
4493
	}
4494
4495
	/**
4496
	 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
4497
	 */
4498
	function do_stats( $method = '' ) {
4499
		if ( is_array( $this->stats ) && count( $this->stats ) ) {
4500
			foreach ( $this->stats as $group => $stats ) {
4501
				if ( is_array( $stats ) && count( $stats ) ) {
4502
					$args = array( "x_jetpack-{$group}" => implode( ',', $stats ) );
4503
					if ( 'server_side' === $method ) {
4504
						self::do_server_side_stat( $args );
4505
					} else {
4506
						echo '<img src="' . esc_url( self::build_stats_url( $args ) ) . '" width="1" height="1" style="display:none;" />';
4507
					}
4508
				}
4509
				unset( $this->stats[ $group ] );
4510
			}
4511
		}
4512
	}
4513
4514
	/**
4515
	 * Runs stats code for a one-off, server-side.
4516
	 *
4517
	 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
4518
	 *
4519
	 * @return bool If it worked.
4520
	 */
4521
	static function do_server_side_stat( $args ) {
4522
		$response = wp_remote_get( esc_url_raw( self::build_stats_url( $args ) ) );
4523
		if ( is_wp_error( $response ) )
4524
			return false;
4525
4526
		if ( 200 !== wp_remote_retrieve_response_code( $response ) )
4527
			return false;
4528
4529
		return true;
4530
	}
4531
4532
	/**
4533
	 * Builds the stats url.
4534
	 *
4535
	 * @param $args array|string The arguments to append to the URL.
4536
	 *
4537
	 * @return string The URL to be pinged.
4538
	 */
4539
	static function build_stats_url( $args ) {
4540
		$defaults = array(
4541
			'v'    => 'wpcom2',
4542
			'rand' => md5( mt_rand( 0, 999 ) . time() ),
4543
		);
4544
		$args     = wp_parse_args( $args, $defaults );
4545
		/**
4546
		 * Filter the URL used as the Stats tracking pixel.
4547
		 *
4548
		 * @since 2.3.2
4549
		 *
4550
		 * @param string $url Base URL used as the Stats tracking pixel.
4551
		 */
4552
		$base_url = apply_filters(
4553
			'jetpack_stats_base_url',
4554
			set_url_scheme( 'http://pixel.wp.com/g.gif' )
4555
		);
4556
		$url      = add_query_arg( $args, $base_url );
4557
		return $url;
4558
	}
4559
4560
	function translate_current_user_to_role() {
4561
		foreach ( $this->capability_translations as $role => $cap ) {
4562
			if ( current_user_can( $role ) || current_user_can( $cap ) ) {
4563
				return $role;
4564
			}
4565
		}
4566
4567
		return false;
4568
	}
4569
4570
	function translate_role_to_cap( $role ) {
4571
		if ( ! isset( $this->capability_translations[$role] ) ) {
4572
			return false;
4573
		}
4574
4575
		return $this->capability_translations[$role];
4576
	}
4577
4578
	function sign_role( $role ) {
4579
		if ( ! $user_id = (int) get_current_user_id() ) {
4580
			return false;
4581
		}
4582
4583
		$token = Jetpack_Data::get_access_token();
4584
		if ( ! $token || is_wp_error( $token ) ) {
4585
			return false;
4586
		}
4587
4588
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
4589
	}
4590
4591
4592
	/**
4593
	 * Builds a URL to the Jetpack connection auth page
4594
	 *
4595
	 * @since 3.9.5
4596
	 *
4597
	 * @param bool $raw If true, URL will not be escaped.
4598
	 * @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
4599
	 *                              If string, will be a custom redirect.
4600
	 * @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
4601
	 *
4602
	 * @return string Connect URL
4603
	 */
4604
	function build_connect_url( $raw = false, $redirect = false, $from = false ) {
4605
		if ( ! Jetpack_Options::get_option( 'blog_token' ) || ! Jetpack_Options::get_option( 'id' ) ) {
4606
			$url = Jetpack::nonce_url_no_esc( Jetpack::admin_url( 'action=register' ), 'jetpack-register' );
4607
			if( is_network_admin() ) {
4608
			    $url = add_query_arg( 'is_multisite', network_admin_url(
4609
			    'admin.php?page=jetpack-settings' ), $url );
4610
			}
4611
		} else {
4612
			$role = $this->translate_current_user_to_role();
4613
			$signed_role = $this->sign_role( $role );
4614
4615
			$user = wp_get_current_user();
4616
4617
			$redirect = $redirect ? esc_url_raw( $redirect ) : esc_url_raw( menu_page_url( 'jetpack', false ) );
4618
4619
			if( isset( $_REQUEST['is_multisite'] ) ) {
4620
				$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4621
			}
4622
4623
			$secrets = Jetpack::init()->generate_secrets();
4624
			Jetpack_Options::update_option( 'authorize', $secrets[0] . ':' . $secrets[1] . ':' . $secrets[2] . ':' . $secrets[3] );
4625
4626
			@list( $secret ) = explode( ':', Jetpack_Options::get_option( 'authorize' ) );
4627
4628
			$args = urlencode_deep(
4629
				array(
4630
					'response_type' => 'code',
4631
					'client_id'     => Jetpack_Options::get_option( 'id' ),
4632
					'redirect_uri'  => add_query_arg(
4633
						array(
4634
							'action'   => 'authorize',
4635
							'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
4636
							'redirect' => urlencode( $redirect ),
4637
						),
4638
						menu_page_url( 'jetpack', false )
4639
					),
4640
					'state'         => $user->ID,
4641
					'scope'         => $signed_role,
4642
					'user_email'    => $user->user_email,
4643
					'user_login'    => $user->user_login,
4644
					'is_active'     => Jetpack::is_active(),
4645
					'jp_version'    => JETPACK__VERSION,
4646
					'auth_type'     => 'calypso',
4647
					'secret'		=> $secret,
4648
				)
4649
			);
4650
4651
			$url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) );
4652
		}
4653
4654
		if ( $from ) {
4655
			$url = add_query_arg( 'from', $from, $url );
4656
		}
4657
		return $raw ? $url : esc_url( $url );
4658
	}
4659
4660
	function build_reconnect_url( $raw = false ) {
4661
		$url = wp_nonce_url( Jetpack::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4662
		return $raw ? $url : esc_url( $url );
4663
	}
4664
4665
	public static function admin_url( $args = null ) {
4666
		$args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
4667
		$url = add_query_arg( $args, admin_url( 'admin.php' ) );
4668
		return $url;
4669
	}
4670
4671
	public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4672
		$actionurl = str_replace( '&amp;', '&', $actionurl );
4673
		return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4674
	}
4675
4676
	function dismiss_jetpack_notice() {
4677
4678
		if ( ! isset( $_GET['jetpack-notice'] ) ) {
4679
			return;
4680
		}
4681
4682
		switch( $_GET['jetpack-notice'] ) {
4683
			case 'dismiss':
4684
				if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4685
4686
					require_once ABSPATH . 'wp-admin/includes/plugin.php';
4687
					deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4688
					wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4689
				}
4690
				break;
4691 View Code Duplication
			case 'jetpack-manage-opt-out':
4692
4693
				if ( check_admin_referer( 'jetpack_manage_banner_opt_out' ) ) {
4694
					// Don't show the banner again
4695
4696
					Jetpack_Options::update_option( 'dismissed_manage_banner', true );
4697
					// redirect back to the page that had the notice
4698
					if ( wp_get_referer() ) {
4699
						wp_safe_redirect( wp_get_referer() );
4700
					} else {
4701
						// Take me to Jetpack
4702
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4703
					}
4704
				}
4705
				break;
4706 View Code Duplication
			case 'jetpack-protect-multisite-opt-out':
4707
4708
				if ( check_admin_referer( 'jetpack_protect_multisite_banner_opt_out' ) ) {
4709
					// Don't show the banner again
4710
4711
					update_site_option( 'jetpack_dismissed_protect_multisite_banner', true );
4712
					// redirect back to the page that had the notice
4713
					if ( wp_get_referer() ) {
4714
						wp_safe_redirect( wp_get_referer() );
4715
					} else {
4716
						// Take me to Jetpack
4717
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4718
					}
4719
				}
4720
				break;
4721
			case 'jetpack-manage-opt-in':
4722
				if ( check_admin_referer( 'jetpack_manage_banner_opt_in' ) ) {
4723
					// This makes sure that we are redirect to jetpack home so that we can see the Success Message.
4724
4725
					$redirection_url = Jetpack::admin_url();
4726
					remove_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4727
4728
					// Don't redirect form the Jetpack Setting Page
4729
					$referer_parsed = parse_url ( wp_get_referer() );
4730
					// check that we do have a wp_get_referer and the query paramater is set orderwise go to the Jetpack Home
4731
					if ( isset( $referer_parsed['query'] ) && false !== strpos( $referer_parsed['query'], 'page=jetpack_modules' ) ) {
4732
						// Take the user to Jetpack home except when on the setting page
4733
						$redirection_url = wp_get_referer();
4734
						add_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4735
					}
4736
					// Also update the JSON API FULL MANAGEMENT Option
4737
					Jetpack::activate_module( 'manage', false, false );
4738
4739
					// Special Message when option in.
4740
					Jetpack::state( 'optin-manage', 'true' );
4741
					// Activate the Module if not activated already
4742
4743
					// Redirect properly
4744
					wp_safe_redirect( $redirection_url );
4745
4746
				}
4747
				break;
4748
		}
4749
	}
4750
4751
	function debugger_page() {
4752
		nocache_headers();
4753
		if ( ! current_user_can( 'manage_options' ) ) {
4754
			die( '-1' );
4755
		}
4756
		Jetpack_Debugger::jetpack_debug_display_handler();
4757
		exit;
4758
	}
4759
4760
	public static function admin_screen_configure_module( $module_id ) {
4761
4762
		// User that doesn't have 'jetpack_configure_modules' will never end up here since Jetpack Landing Page woun't let them.
4763
		if ( ! in_array( $module_id, Jetpack::get_active_modules() ) && current_user_can( 'manage_options' ) ) {
4764
			if ( has_action( 'display_activate_module_setting_' . $module_id ) ) {
4765
				/**
4766
				 * Fires to diplay a custom module activation screen.
4767
				 *
4768
				 * To add a module actionation screen use Jetpack::module_configuration_activation_screen method.
4769
				 * Example: Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
4770
				 *
4771
				 * @module manage
4772
				 *
4773
				 * @since 3.8.0
4774
				 *
4775
				 * @param int $module_id Module ID.
4776
				 */
4777
				do_action( 'display_activate_module_setting_' . $module_id );
4778
			} else {
4779
				self::display_activate_module_link( $module_id );
4780
			}
4781
4782
			return false;
4783
		} ?>
4784
4785
		<div id="jp-settings-screen" style="position: relative">
4786
			<h3>
4787
			<?php
4788
				$module = Jetpack::get_module( $module_id );
4789
				echo '<a href="' . Jetpack::admin_url( 'page=jetpack_modules' ) . '">' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</a> &rarr; ';
4790
				printf( __( 'Configure %s', 'jetpack' ), $module['name'] );
4791
			?>
4792
			</h3>
4793
			<?php
4794
				/**
4795
				 * Fires within the displayed message when a feature configuation is updated.
4796
				 *
4797
				 * @since 3.4.0
4798
				 *
4799
				 * @param int $module_id Module ID.
4800
				 */
4801
				do_action( 'jetpack_notices_update_settings', $module_id );
4802
				/**
4803
				 * Fires when a feature configuation screen is loaded.
4804
				 * The dynamic part of the hook, $module_id, is the module ID.
4805
				 *
4806
				 * @since 1.1.0
4807
				 */
4808
				do_action( 'jetpack_module_configuration_screen_' . $module_id );
4809
			?>
4810
		</div><?php
4811
	}
4812
4813
	/**
4814
	 * Display link to activate the module to see the settings screen.
4815
	 * @param  string $module_id
4816
	 * @return null
4817
	 */
4818
	public static function display_activate_module_link( $module_id ) {
4819
4820
		$info =  Jetpack::get_module( $module_id );
4821
		$extra = '';
4822
		$activate_url = wp_nonce_url(
4823
				Jetpack::admin_url(
4824
					array(
4825
						'page'   => 'jetpack',
4826
						'action' => 'activate',
4827
						'module' => $module_id,
4828
					)
4829
				),
4830
				"jetpack_activate-$module_id"
4831
			);
4832
4833
		?>
4834
4835
		<div class="wrap configure-module">
4836
			<div id="jp-settings-screen">
4837
				<?php
4838
				if ( $module_id == 'json-api' ) {
4839
4840
					$info['name'] = esc_html__( 'Activate Site Management and JSON API', 'jetpack' );
4841
4842
					$activate_url = Jetpack::init()->opt_in_jetpack_manage_url();
4843
4844
					$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' );
4845
4846
					// $extra = __( 'To use Site Management, you need to first activate JSON API to allow remote management of your site. ', 'jetpack' );
4847
				} ?>
4848
4849
				<h3><?php echo esc_html( $info['name'] ); ?></h3>
4850
				<div class="narrow">
4851
					<p><?php echo  $info['description']; ?></p>
4852
					<?php if( $extra ) { ?>
4853
					<p><?php echo esc_html( $extra ); ?></p>
4854
					<?php } ?>
4855
					<p>
4856
						<?php
4857
						if( wp_get_referer() ) {
4858
							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() );
4859
						} else {
4860
							printf( __( '<a class="button-primary" href="%s">Activate Now</a>', 'jetpack' ) , $activate_url  );
4861
						} ?>
4862
					</p>
4863
				</div>
4864
4865
			</div>
4866
		</div>
4867
4868
		<?php
4869
	}
4870
4871
	public static function sort_modules( $a, $b ) {
4872
		if ( $a['sort'] == $b['sort'] )
4873
			return 0;
4874
4875
		return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
4876
	}
4877
4878 View Code Duplication
	function sync_reindex_trigger() {
4879
		if ( $this->current_user_is_connection_owner() && current_user_can( 'manage_options' ) ) {
4880
			echo json_encode( $this->sync->reindex_trigger() );
4881
		} else {
4882
			echo '{"status":"ERROR"}';
4883
		}
4884
		exit;
4885
	}
4886
4887 View Code Duplication
	function sync_reindex_status(){
4888
		if ( $this->current_user_is_connection_owner() && current_user_can( 'manage_options' ) ) {
4889
			echo json_encode( $this->sync->reindex_status() );
4890
		} else {
4891
			echo '{"status":"ERROR"}';
4892
		}
4893
		exit;
4894
	}
4895
4896
/* Client API */
4897
4898
	/**
4899
	 * Returns the requested Jetpack API URL
4900
	 *
4901
	 * @return string
4902
	 */
4903
	public static function api_url( $relative_url ) {
4904
		return trailingslashit( JETPACK__API_BASE . $relative_url  ) . JETPACK__API_VERSION . '/';
4905
	}
4906
4907
	/**
4908
	 * Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
4909
	 */
4910
	public static function fix_url_for_bad_hosts( $url ) {
4911
		if ( 0 !== strpos( $url, 'https://' ) ) {
4912
			return $url;
4913
		}
4914
4915
		switch ( JETPACK_CLIENT__HTTPS ) {
4916
			case 'ALWAYS' :
4917
				return $url;
4918
			case 'NEVER' :
4919
				return set_url_scheme( $url, 'http' );
4920
			// default : case 'AUTO' :
4921
		}
4922
4923
		// Yay! Your host is good!
4924
		if ( self::permit_ssl() && wp_http_supports( array( 'ssl' => true ) ) ) {
4925
			return $url;
4926
		}
4927
4928
		// Boo! Your host is bad and makes Jetpack cry!
4929
		return set_url_scheme( $url, 'http' );
4930
	}
4931
4932
	/**
4933
	 * Checks to see if the URL is using SSL to connect with Jetpack
4934
	 *
4935
	 * @since 2.3.3
4936
	 * @return boolean
4937
	 */
4938
	public static function permit_ssl( $force_recheck = false ) {
4939
		// Do some fancy tests to see if ssl is being supported
4940
		if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
4941
			if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
4942
				$ssl = 0;
4943
			} else {
4944
				switch ( JETPACK_CLIENT__HTTPS ) {
4945
					case 'NEVER':
4946
						$ssl = 0;
4947
						break;
4948
					case 'ALWAYS':
4949
					case 'AUTO':
4950
					default:
4951
						$ssl = 1;
4952
						break;
4953
				}
4954
4955
				// If it's not 'NEVER', test to see
4956
				if ( $ssl ) {
4957
					$response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4958
					if ( is_wp_error( $response ) || ( 'OK' !== wp_remote_retrieve_body( $response ) ) ) {
4959
						$ssl = 0;
4960
					}
4961
				}
4962
			}
4963
			set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4964
		}
4965
4966
		return (bool) $ssl;
4967
	}
4968
4969
	/*
4970
	 * Displays an admin_notice, alerting the user to their JETPACK_CLIENT__HTTPS constant being 'ALWAYS' but SSL isn't working.
4971
	 */
4972
	public function alert_required_ssl_fail() {
4973
		if ( ! current_user_can( 'manage_options' ) )
4974
			return;
4975
		?>
4976
4977
		<div id="message" class="error jetpack-message jp-identity-crisis">
4978
			<div class="jp-banner__content">
4979
				<h2><?php _e( 'Something is being cranky!', 'jetpack' ); ?></h2>
4980
				<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>
4981
			</div>
4982
		</div>
4983
4984
		<?php
4985
	}
4986
4987
	/**
4988
	 * Returns the Jetpack XML-RPC API
4989
	 *
4990
	 * @return string
4991
	 */
4992
	public static function xmlrpc_api_url() {
4993
		$base = preg_replace( '#(https?://[^?/]+)(/?.*)?$#', '\\1', JETPACK__API_BASE );
4994
		return untrailingslashit( $base ) . '/xmlrpc.php';
4995
	}
4996
4997
	/**
4998
	 * Creates two secret tokens and the end of life timestamp for them.
4999
	 *
5000
	 * Note these tokens are unique per call, NOT static per site for connecting.
5001
	 *
5002
	 * @since 2.6
5003
	 * @return array
5004
	 */
5005
	public function generate_secrets() {
5006
	    $secrets = array(
5007
			wp_generate_password( 32, false ), // secret_1
5008
			wp_generate_password( 32, false ), // secret_2
5009
			( time() + 600 ), // eol ( End of Life )
5010
			get_current_user_id(), // ties the secrets to the current user
5011
	    );
5012
5013
	    return $secrets;
5014
	}
5015
5016
	/**
5017
	 * Builds the timeout limit for queries talking with the wpcom servers.
5018
	 *
5019
	 * Based on local php max_execution_time in php.ini
5020
	 *
5021
	 * @since 2.6
5022
	 * @return int
5023
	 **/
5024
	public function get_remote_query_timeout_limit() {
5025
	    $timeout = (int) ini_get( 'max_execution_time' );
5026
	    if ( ! $timeout ) // Ensure exec time set in php.ini
5027
		$timeout = 30;
5028
	    return intval( $timeout / 2 );
5029
	}
5030
5031
5032
	/**
5033
	 * Takes the response from the Jetpack register new site endpoint and
5034
	 * verifies it worked properly.
5035
	 *
5036
	 * @since 2.6
5037
	 * @return true or Jetpack_Error
5038
	 **/
5039
	public function validate_remote_register_response( $response ) {
5040
	    	if ( is_wp_error( $response ) ) {
5041
			return new Jetpack_Error( 'register_http_request_failed', $response->get_error_message() );
5042
		}
5043
5044
		$code   = wp_remote_retrieve_response_code( $response );
5045
		$entity = wp_remote_retrieve_body( $response );
5046
		if ( $entity )
5047
			$json = json_decode( $entity );
5048
		else
5049
			$json = false;
5050
5051
		$code_type = intval( $code / 100 );
5052
		if ( 5 == $code_type ) {
5053
			return new Jetpack_Error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
5054
		} elseif ( 408 == $code ) {
5055
			return new Jetpack_Error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
5056
		} elseif ( ! empty( $json->error ) ) {
5057
			$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
5058
			return new Jetpack_Error( (string) $json->error, $error_description, $code );
5059
		} elseif ( 200 != $code ) {
5060
			return new Jetpack_Error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
5061
		}
5062
5063
		// Jetpack ID error block
5064
		if ( empty( $json->jetpack_id ) ) {
5065
			return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ), $entity );
5066
		} elseif ( ! is_scalar( $json->jetpack_id ) ) {
5067
			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 );
5068
		} elseif ( preg_match( '/[^0-9]/', $json->jetpack_id ) ) {
5069
			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 );
5070
		}
5071
5072
	    return true;
5073
	}
5074
	/**
5075
	 * @return bool|WP_Error
5076
	 */
5077
	public static function register() {
5078
		add_action( 'pre_update_jetpack_option_register', array( 'Jetpack_Options', 'delete_option' ) );
5079
		$secrets = Jetpack::init()->generate_secrets();
5080
5081
		Jetpack_Options::update_option( 'register', $secrets[0] . ':' . $secrets[1] . ':' . $secrets[2] . ':' . $secrets[3] );
5082
5083
		@list( $secret_1, $secret_2, $secret_eol ) = explode( ':', Jetpack_Options::get_option( 'register' ) );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
5084
		if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) {
5085
			return new Jetpack_Error( 'missing_secrets' );
5086
		}
5087
5088
		$timeout = Jetpack::init()->get_remote_query_timeout_limit();
5089
5090
		$gmt_offset = get_option( 'gmt_offset' );
5091
		if ( ! $gmt_offset ) {
5092
			$gmt_offset = 0;
5093
		}
5094
5095
		$stats_options = get_option( 'stats_options' );
5096
		$stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null;
5097
5098
		$args = array(
5099
			'method'  => 'POST',
5100
			'body'    => array(
5101
				'siteurl'         => site_url(),
5102
				'home'            => home_url(),
5103
				'gmt_offset'      => $gmt_offset,
5104
				'timezone_string' => (string) get_option( 'timezone_string' ),
5105
				'site_name'       => (string) get_option( 'blogname' ),
5106
				'secret_1'        => $secret_1,
5107
				'secret_2'        => $secret_2,
5108
				'site_lang'       => get_locale(),
5109
				'timeout'         => $timeout,
5110
				'stats_id'        => $stats_id,
5111
				'state'           => get_current_user_id(),
5112
			),
5113
			'headers' => array(
5114
				'Accept' => 'application/json',
5115
			),
5116
			'timeout' => $timeout,
5117
		);
5118
		$response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'register' ) ), $args, true );
5119
5120
5121
		// Make sure the response is valid and does not contain any Jetpack errors
5122
		$valid_response = Jetpack::init()->validate_remote_register_response( $response );
5123
		if( is_wp_error( $valid_response ) || !$valid_response ) {
5124
		    return $valid_response;
5125
		}
5126
5127
		// Grab the response values to work with
5128
		$code   = wp_remote_retrieve_response_code( $response );
5129
		$entity = wp_remote_retrieve_body( $response );
5130
5131
		if ( $entity )
5132
			$json = json_decode( $entity );
5133
		else
5134
			$json = false;
5135
5136 View Code Duplication
		if ( empty( $json->jetpack_secret ) || ! is_string( $json->jetpack_secret ) )
5137
			return new Jetpack_Error( 'jetpack_secret', '', $code );
5138
5139
		if ( isset( $json->jetpack_public ) ) {
5140
			$jetpack_public = (int) $json->jetpack_public;
5141
		} else {
5142
			$jetpack_public = false;
5143
		}
5144
5145
		Jetpack_Options::update_options(
5146
			array(
5147
				'id'         => (int)    $json->jetpack_id,
5148
				'blog_token' => (string) $json->jetpack_secret,
5149
				'public'     => $jetpack_public,
5150
			)
5151
		);
5152
5153
		/**
5154
		 * Fires when a site is registered on WordPress.com.
5155
		 *
5156
		 * @since 3.7.0
5157
		 *
5158
		 * @param int $json->jetpack_id Jetpack Blog ID.
5159
		 * @param string $json->jetpack_secret Jetpack Blog Token.
5160
		 * @param int|bool $jetpack_public Is the site public.
5161
		 */
5162
		do_action( 'jetpack_site_registered', $json->jetpack_id, $json->jetpack_secret, $jetpack_public );
5163
5164
		// Initialize Jump Start for the first and only time.
5165
		if ( ! Jetpack_Options::get_option( 'jumpstart' ) ) {
5166
			Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
5167
5168
			$jetpack = Jetpack::init();
5169
5170
			$jetpack->stat( 'jumpstart', 'unique-views' );
5171
			$jetpack->do_stats( 'server_side' );
5172
		};
5173
5174
		return true;
5175
	}
5176
5177
	/**
5178
	 * If the db version is showing something other that what we've got now, bump it to current.
5179
	 *
5180
	 * @return bool: True if the option was incorrect and updated, false if nothing happened.
5181
	 */
5182
	public static function maybe_set_version_option() {
5183
		list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
5184
		if ( JETPACK__VERSION != $version ) {
5185
			Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
5186
			return true;
5187
		}
5188
		return false;
5189
	}
5190
5191
/* Client Server API */
5192
5193
	/**
5194
	 * Loads the Jetpack XML-RPC client
5195
	 */
5196
	public static function load_xml_rpc_client() {
5197
		require_once ABSPATH . WPINC . '/class-IXR.php';
5198
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-ixr-client.php';
5199
	}
5200
5201
	function verify_xml_rpc_signature() {
5202
		if ( $this->xmlrpc_verification ) {
5203
			return $this->xmlrpc_verification;
5204
		}
5205
5206
		// It's not for us
5207
		if ( ! isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
5208
			return false;
5209
		}
5210
5211
		@list( $token_key, $version, $user_id ) = explode( ':', $_GET['token'] );
5212
		if (
5213
			empty( $token_key )
5214
		||
5215
			empty( $version ) || strval( JETPACK__API_VERSION ) !== $version
5216
		) {
5217
			return false;
5218
		}
5219
5220
		if ( '0' === $user_id ) {
5221
			$token_type = 'blog';
5222
			$user_id = 0;
5223
		} else {
5224
			$token_type = 'user';
5225
			if ( empty( $user_id ) || ! ctype_digit( $user_id ) ) {
5226
				return false;
5227
			}
5228
			$user_id = (int) $user_id;
5229
5230
			$user = new WP_User( $user_id );
5231
			if ( ! $user || ! $user->exists() ) {
5232
				return false;
5233
			}
5234
		}
5235
5236
		$token = Jetpack_Data::get_access_token( $user_id );
5237
		if ( ! $token ) {
5238
			return false;
5239
		}
5240
5241
		$token_check = "$token_key.";
5242
		if ( ! hash_equals( substr( $token->secret, 0, strlen( $token_check ) ), $token_check ) ) {
5243
			return false;
5244
		}
5245
5246
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
5247
5248
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5249
		if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
5250
			$post_data   = $_POST;
5251
			$file_hashes = array();
5252
			foreach ( $post_data as $post_data_key => $post_data_value ) {
5253
				if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
5254
					continue;
5255
				}
5256
				$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
5257
				$file_hashes[$post_data_key] = $post_data_value;
5258
			}
5259
5260
			foreach ( $file_hashes as $post_data_key => $post_data_value ) {
5261
				unset( $post_data["_jetpack_file_hmac_{$post_data_key}"] );
5262
				$post_data[$post_data_key] = $post_data_value;
5263
			}
5264
5265
			ksort( $post_data );
5266
5267
			$body = http_build_query( stripslashes_deep( $post_data ) );
5268
		} elseif ( is_null( $this->HTTP_RAW_POST_DATA ) ) {
5269
			$body = file_get_contents( 'php://input' );
5270
		} else {
5271
			$body = null;
5272
		}
5273
		$signature = $jetpack_signature->sign_current_request(
5274
			array( 'body' => is_null( $body ) ? $this->HTTP_RAW_POST_DATA : $body, )
5275
		);
5276
5277
		if ( ! $signature ) {
5278
			return false;
5279
		} else if ( is_wp_error( $signature ) ) {
5280
			return $signature;
5281
		} else if ( ! hash_equals( $signature, $_GET['signature'] ) ) {
5282
			return false;
5283
		}
5284
5285
		$timestamp = (int) $_GET['timestamp'];
5286
		$nonce     = stripslashes( (string) $_GET['nonce'] );
5287
5288
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
5289
			return false;
5290
		}
5291
5292
		$this->xmlrpc_verification = array(
5293
			'type'    => $token_type,
5294
			'user_id' => $token->external_user_id,
5295
		);
5296
5297
		return $this->xmlrpc_verification;
5298
	}
5299
5300
	/**
5301
	 * Authenticates XML-RPC and other requests from the Jetpack Server
5302
	 */
5303
	function authenticate_jetpack( $user, $username, $password ) {
5304
		if ( is_a( $user, 'WP_User' ) ) {
5305
			return $user;
5306
		}
5307
5308
		$token_details = $this->verify_xml_rpc_signature();
5309
5310
		if ( ! $token_details || is_wp_error( $token_details ) ) {
5311
			return $user;
5312
		}
5313
5314
		if ( 'user' !== $token_details['type'] ) {
5315
			return $user;
5316
		}
5317
5318
		if ( ! $token_details['user_id'] ) {
5319
			return $user;
5320
		}
5321
5322
		nocache_headers();
5323
5324
		return new WP_User( $token_details['user_id'] );
5325
	}
5326
5327
	function add_nonce( $timestamp, $nonce ) {
5328
		global $wpdb;
5329
		static $nonces_used_this_request = array();
5330
5331
		if ( isset( $nonces_used_this_request["$timestamp:$nonce"] ) ) {
5332
			return $nonces_used_this_request["$timestamp:$nonce"];
5333
		}
5334
5335
		// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce
5336
		$timestamp = (int) $timestamp;
5337
		$nonce     = esc_sql( $nonce );
5338
5339
		// Raw query so we can avoid races: add_option will also update
5340
		$show_errors = $wpdb->show_errors( false );
5341
5342
		$old_nonce = $wpdb->get_row(
5343
			$wpdb->prepare( "SELECT * FROM `$wpdb->options` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
5344
		);
5345
5346
		if ( is_null( $old_nonce ) ) {
5347
			$return = $wpdb->query(
5348
				$wpdb->prepare(
5349
					"INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
5350
					"jetpack_nonce_{$timestamp}_{$nonce}",
5351
					time(),
5352
					'no'
5353
				)
5354
			);
5355
		} else {
5356
			$return = false;
5357
		}
5358
5359
		$wpdb->show_errors( $show_errors );
5360
5361
		$nonces_used_this_request["$timestamp:$nonce"] = $return;
5362
5363
		return $return;
5364
	}
5365
5366
	/**
5367
	 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths since it is passed by reference to various methods.
5368
	 * Capture it here so we can verify the signature later.
5369
	 */
5370
	function xmlrpc_methods( $methods ) {
5371
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
5372
		return $methods;
5373
	}
5374
5375
	function public_xmlrpc_methods( $methods ) {
5376
		if ( array_key_exists( 'wp.getOptions', $methods ) ) {
5377
			$methods['wp.getOptions'] = array( $this, 'jetpack_getOptions' );
5378
		}
5379
		return $methods;
5380
	}
5381
5382
	function jetpack_getOptions( $args ) {
5383
		global $wp_xmlrpc_server;
5384
5385
		$wp_xmlrpc_server->escape( $args );
5386
5387
		$username	= $args[1];
5388
		$password	= $args[2];
5389
5390
		if ( !$user = $wp_xmlrpc_server->login($username, $password) ) {
5391
			return $wp_xmlrpc_server->error;
5392
		}
5393
5394
		$options = array();
5395
		$user_data = $this->get_connected_user_data();
5396
		if ( is_array( $user_data ) ) {
5397
			$options['jetpack_user_id'] = array(
5398
				'desc'          => __( 'The WP.com user ID of the connected user', 'jetpack' ),
5399
				'readonly'      => true,
5400
				'value'         => $user_data['ID'],
5401
			);
5402
			$options['jetpack_user_login'] = array(
5403
				'desc'          => __( 'The WP.com username of the connected user', 'jetpack' ),
5404
				'readonly'      => true,
5405
				'value'         => $user_data['login'],
5406
			);
5407
			$options['jetpack_user_email'] = array(
5408
				'desc'          => __( 'The WP.com user email of the connected user', 'jetpack' ),
5409
				'readonly'      => true,
5410
				'value'         => $user_data['email'],
5411
			);
5412
			$options['jetpack_user_site_count'] = array(
5413
				'desc'          => __( 'The number of sites of the connected WP.com user', 'jetpack' ),
5414
				'readonly'      => true,
5415
				'value'         => $user_data['site_count'],
5416
			);
5417
		}
5418
		$wp_xmlrpc_server->blog_options = array_merge( $wp_xmlrpc_server->blog_options, $options );
5419
		$args = stripslashes_deep( $args );
5420
		return $wp_xmlrpc_server->wp_getOptions( $args );
5421
	}
5422
5423
	function xmlrpc_options( $options ) {
5424
		$jetpack_client_id = false;
5425
		if ( self::is_active() ) {
5426
			$jetpack_client_id = Jetpack_Options::get_option( 'id' );
5427
		}
5428
		$options['jetpack_version'] = array(
5429
				'desc'          => __( 'Jetpack Plugin Version', 'jetpack' ),
5430
				'readonly'      => true,
5431
				'value'         => JETPACK__VERSION,
5432
		);
5433
5434
		$options['jetpack_client_id'] = array(
5435
				'desc'          => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack' ),
5436
				'readonly'      => true,
5437
				'value'         => $jetpack_client_id,
5438
		);
5439
		return $options;
5440
	}
5441
5442
	public static function clean_nonces( $all = false ) {
5443
		global $wpdb;
5444
5445
		$sql = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
5446
		if ( method_exists ( $wpdb , 'esc_like' ) ) {
5447
			$sql_args = array( $wpdb->esc_like( 'jetpack_nonce_' ) . '%' );
5448
		} else {
5449
			$sql_args = array( like_escape( 'jetpack_nonce_' ) . '%' );
5450
		}
5451
5452
		if ( true !== $all ) {
5453
			$sql .= ' AND CAST( `option_value` AS UNSIGNED ) < %d';
5454
			$sql_args[] = time() - 3600;
5455
		}
5456
5457
		$sql .= ' ORDER BY `option_id` LIMIT 100';
5458
5459
		$sql = $wpdb->prepare( $sql, $sql_args );
5460
5461
		for ( $i = 0; $i < 1000; $i++ ) {
5462
			if ( ! $wpdb->query( $sql ) ) {
5463
				break;
5464
			}
5465
		}
5466
	}
5467
5468
	/**
5469
	 * State is passed via cookies from one request to the next, but never to subsequent requests.
5470
	 * SET: state( $key, $value );
5471
	 * GET: $value = state( $key );
5472
	 *
5473
	 * @param string $key
5474
	 * @param string $value
5475
	 * @param bool $restate private
5476
	 */
5477
	public static function state( $key = null, $value = null, $restate = false ) {
5478
		static $state = array();
5479
		static $path, $domain;
5480
		if ( ! isset( $path ) ) {
5481
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
5482
			$admin_url = Jetpack::admin_url();
5483
			$bits      = parse_url( $admin_url );
5484
5485
			if ( is_array( $bits ) ) {
5486
				$path   = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
5487
				$domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
5488
			} else {
5489
				$path = $domain = null;
5490
			}
5491
		}
5492
5493
		// Extract state from cookies and delete cookies
5494
		if ( isset( $_COOKIE[ 'jetpackState' ] ) && is_array( $_COOKIE[ 'jetpackState' ] ) ) {
5495
			$yum = $_COOKIE[ 'jetpackState' ];
5496
			unset( $_COOKIE[ 'jetpackState' ] );
5497
			foreach ( $yum as $k => $v ) {
5498
				if ( strlen( $v ) )
5499
					$state[ $k ] = $v;
5500
				setcookie( "jetpackState[$k]", false, 0, $path, $domain );
5501
			}
5502
		}
5503
5504
		if ( $restate ) {
5505
			foreach ( $state as $k => $v ) {
5506
				setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
5507
			}
5508
			return;
5509
		}
5510
5511
		// Get a state variable
5512
		if ( isset( $key ) && ! isset( $value ) ) {
5513
			if ( array_key_exists( $key, $state ) )
5514
				return $state[ $key ];
5515
			return null;
5516
		}
5517
5518
		// Set a state variable
5519
		if ( isset ( $key ) && isset( $value ) ) {
5520
			if( is_array( $value ) && isset( $value[0] ) ) {
5521
				$value = $value[0];
5522
			}
5523
			$state[ $key ] = $value;
5524
			setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
5525
		}
5526
	}
5527
5528
	public static function restate() {
5529
		Jetpack::state( null, null, true );
5530
	}
5531
5532
	public static function check_privacy( $file ) {
5533
		static $is_site_publicly_accessible = null;
5534
5535
		if ( is_null( $is_site_publicly_accessible ) ) {
5536
			$is_site_publicly_accessible = false;
5537
5538
			Jetpack::load_xml_rpc_client();
5539
			$rpc = new Jetpack_IXR_Client();
5540
5541
			$success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
5542
			if ( $success ) {
5543
				$response = $rpc->getResponse();
5544
				if ( $response ) {
5545
					$is_site_publicly_accessible = true;
5546
				}
5547
			}
5548
5549
			Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
5550
		}
5551
5552
		if ( $is_site_publicly_accessible ) {
5553
			return;
5554
		}
5555
5556
		$module_slug = self::get_module_slug( $file );
5557
5558
		$privacy_checks = Jetpack::state( 'privacy_checks' );
5559
		if ( ! $privacy_checks ) {
5560
			$privacy_checks = $module_slug;
5561
		} else {
5562
			$privacy_checks .= ",$module_slug";
5563
		}
5564
5565
		Jetpack::state( 'privacy_checks', $privacy_checks );
5566
	}
5567
5568
	/**
5569
	 * Helper method for multicall XMLRPC.
5570
	 */
5571
	public static function xmlrpc_async_call() {
5572
		global $blog_id;
5573
		static $clients = array();
5574
5575
		$client_blog_id = is_multisite() ? $blog_id : 0;
5576
5577
		if ( ! isset( $clients[$client_blog_id] ) ) {
5578
			Jetpack::load_xml_rpc_client();
5579
			$clients[$client_blog_id] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => JETPACK_MASTER_USER, ) );
5580
			if ( function_exists( 'ignore_user_abort' ) ) {
5581
				ignore_user_abort( true );
5582
			}
5583
			add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
5584
		}
5585
5586
		$args = func_get_args();
5587
5588
		if ( ! empty( $args[0] ) ) {
5589
			call_user_func_array( array( $clients[$client_blog_id], 'addCall' ), $args );
5590
		} elseif ( is_multisite() ) {
5591
			foreach ( $clients as $client_blog_id => $client ) {
5592
				if ( ! $client_blog_id || empty( $client->calls ) ) {
5593
					continue;
5594
				}
5595
5596
				$switch_success = switch_to_blog( $client_blog_id, true );
5597
				if ( ! $switch_success ) {
5598
					continue;
5599
				}
5600
5601
				flush();
5602
				$client->query();
5603
5604
				restore_current_blog();
5605
			}
5606
		} else {
5607
			if ( isset( $clients[0] ) && ! empty( $clients[0]->calls ) ) {
5608
				flush();
5609
				$clients[0]->query();
5610
			}
5611
		}
5612
	}
5613
5614
	public static function staticize_subdomain( $url ) {
5615
5616
		// Extract hostname from URL
5617
		$host = parse_url( $url, PHP_URL_HOST );
5618
5619
		// Explode hostname on '.'
5620
		$exploded_host = explode( '.', $host );
5621
5622
		// Retrieve the name and TLD
5623
		if ( count( $exploded_host ) > 1 ) {
5624
			$name = $exploded_host[ count( $exploded_host ) - 2 ];
5625
			$tld = $exploded_host[ count( $exploded_host ) - 1 ];
5626
			// Rebuild domain excluding subdomains
5627
			$domain = $name . '.' . $tld;
5628
		} else {
5629
			$domain = $host;
5630
		}
5631
		// Array of Automattic domains
5632
		$domain_whitelist = array( 'wordpress.com', 'wp.com' );
5633
5634
		// Return $url if not an Automattic domain
5635
		if ( ! in_array( $domain, $domain_whitelist ) ) {
5636
			return $url;
5637
		}
5638
5639
		if ( is_ssl() ) {
5640
			return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
5641
		}
5642
5643
		srand( crc32( basename( $url ) ) );
5644
		$static_counter = rand( 0, 2 );
5645
		srand(); // this resets everything that relies on this, like array_rand() and shuffle()
5646
5647
		return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
5648
	}
5649
5650
/* JSON API Authorization */
5651
5652
	/**
5653
	 * Handles the login action for Authorizing the JSON API
5654
	 */
5655
	function login_form_json_api_authorization() {
5656
		$this->verify_json_api_authorization_request();
5657
5658
		add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
5659
5660
		add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
5661
		add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5662
		add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
5663
	}
5664
5665
	// Make sure the login form is POSTed to the signed URL so we can reverify the request
5666
	function post_login_form_to_signed_url( $url, $path, $scheme ) {
5667
		if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
5668
			return $url;
5669
		}
5670
5671
		$parsed_url = parse_url( $url );
5672
		$url = strtok( $url, '?' );
5673
		$url = "$url?{$_SERVER['QUERY_STRING']}";
5674
		if ( ! empty( $parsed_url['query'] ) )
5675
			$url .= "&{$parsed_url['query']}";
5676
5677
		return $url;
5678
	}
5679
5680
	// Make sure the POSTed request is handled by the same action
5681
	function preserve_action_in_login_form_for_json_api_authorization() {
5682
		echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
5683
		echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) . "' />\n";
5684
	}
5685
5686
	// If someone logs in to approve API access, store the Access Code in usermeta
5687
	function store_json_api_authorization_token( $user_login, $user ) {
5688
		add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5689
		add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
5690
		$token = wp_generate_password( 32, false );
5691
		update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5692
	}
5693
5694
	// Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
5695
	function allow_wpcom_public_api_domain( $domains ) {
5696
		$domains[] = 'public-api.wordpress.com';
5697
		return $domains;
5698
	}
5699
5700
	// Add the Access Code details to the public-api.wordpress.com redirect
5701
	function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5702
		return add_query_arg(
5703
			urlencode_deep(
5704
				array(
5705
					'jetpack-code'    => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5706
					'jetpack-user-id' => (int) $user->ID,
5707
					'jetpack-state'   => $this->json_api_authorization_request['state'],
5708
				)
5709
			),
5710
			$redirect_to
5711
		);
5712
	}
5713
5714
	// Verifies the request by checking the signature
5715
	function verify_json_api_authorization_request() {
5716
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
5717
5718
		$token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
5719
		if ( ! $token || empty( $token->secret ) ) {
5720
			wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.' , 'jetpack' ) );
5721
		}
5722
5723
		$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' );
5724
5725
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5726
5727
		if ( isset( $_POST['jetpack_json_api_original_query'] ) ) {
5728
			$signature = $jetpack_signature->sign_request( $_GET['token'], $_GET['timestamp'], $_GET['nonce'], '', 'GET', $_POST['jetpack_json_api_original_query'], null, true );
5729
		} else {
5730
			$signature = $jetpack_signature->sign_current_request( array( 'body' => null, 'method' => 'GET' ) );
5731
		}
5732
5733
		if ( ! $signature ) {
5734
			wp_die( $die_error );
5735
		} else if ( is_wp_error( $signature ) ) {
5736
			wp_die( $die_error );
5737
		} else if ( $signature !== $_GET['signature'] ) {
5738
			if ( is_ssl() ) {
5739
				// 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
5740
				$signature = $jetpack_signature->sign_current_request( array( 'scheme' => 'http', 'body' => null, 'method' => 'GET' ) );
5741
				if ( ! $signature || is_wp_error( $signature ) || $signature !== $_GET['signature'] ) {
5742
					wp_die( $die_error );
5743
				}
5744
			} else {
5745
				wp_die( $die_error );
5746
			}
5747
		}
5748
5749
		$timestamp = (int) $_GET['timestamp'];
5750
		$nonce     = stripslashes( (string) $_GET['nonce'] );
5751
5752
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
5753
			// De-nonce the nonce, at least for 5 minutes.
5754
			// 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)
5755
			$old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5756
			if ( $old_nonce_time < time() - 300 ) {
5757
				wp_die( __( 'The authorization process expired.  Please go back and try again.' , 'jetpack' ) );
5758
			}
5759
		}
5760
5761
		$data = json_decode( base64_decode( stripslashes( $_GET['data'] ) ) );
5762
		$data_filters = array(
5763
			'state'        => 'opaque',
5764
			'client_id'    => 'int',
5765
			'client_title' => 'string',
5766
			'client_image' => 'url',
5767
		);
5768
5769
		foreach ( $data_filters as $key => $sanitation ) {
5770
			if ( ! isset( $data->$key ) ) {
5771
				wp_die( $die_error );
5772
			}
5773
5774
			switch ( $sanitation ) {
5775
			case 'int' :
5776
				$this->json_api_authorization_request[$key] = (int) $data->$key;
5777
				break;
5778
			case 'opaque' :
5779
				$this->json_api_authorization_request[$key] = (string) $data->$key;
5780
				break;
5781
			case 'string' :
5782
				$this->json_api_authorization_request[$key] = wp_kses( (string) $data->$key, array() );
5783
				break;
5784
			case 'url' :
5785
				$this->json_api_authorization_request[$key] = esc_url_raw( (string) $data->$key );
5786
				break;
5787
			}
5788
		}
5789
5790
		if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5791
			wp_die( $die_error );
5792
		}
5793
	}
5794
5795
	function login_message_json_api_authorization( $message ) {
5796
		return '<p class="message">' . sprintf(
5797
			esc_html__( '%s wants to access your site&#8217;s data.  Log in to authorize that access.' , 'jetpack' ),
5798
			'<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5799
		) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5800
	}
5801
5802
	/**
5803
	 * Get $content_width, but with a <s>twist</s> filter.
5804
	 */
5805
	public static function get_content_width() {
5806
		$content_width = isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : false;
5807
		/**
5808
		 * Filter the Content Width value.
5809
		 *
5810
		 * @since 2.2.3
5811
		 *
5812
		 * @param string $content_width Content Width value.
5813
		 */
5814
		return apply_filters( 'jetpack_content_width', $content_width );
5815
	}
5816
5817
	/**
5818
	 * Centralize the function here until it gets added to core.
5819
	 *
5820
	 * @param int|string|object $id_or_email A user ID,  email address, or comment object
5821
	 * @param int $size Size of the avatar image
5822
	 * @param string $default URL to a default image to use if no avatar is available
5823
	 * @param bool $force_display Whether to force it to return an avatar even if show_avatars is disabled
5824
	 *
5825
	 * @return array First element is the URL, second is the class.
5826
	 */
5827
	public static function get_avatar_url( $id_or_email, $size = 96, $default = '', $force_display = false ) {
5828
		// Don't bother adding the __return_true filter if it's already there.
5829
		$has_filter = has_filter( 'pre_option_show_avatars', '__return_true' );
5830
5831
		if ( $force_display && ! $has_filter )
5832
			add_filter( 'pre_option_show_avatars', '__return_true' );
5833
5834
		$avatar = get_avatar( $id_or_email, $size, $default );
5835
5836
		if ( $force_display && ! $has_filter )
5837
			remove_filter( 'pre_option_show_avatars', '__return_true' );
5838
5839
		// If no data, fail out.
5840
		if ( is_wp_error( $avatar ) || ! $avatar )
5841
			return array( null, null );
5842
5843
		// Pull out the URL.  If it's not there, fail out.
5844
		if ( ! preg_match( '/src=["\']([^"\']+)["\']/', $avatar, $url_matches ) )
5845
			return array( null, null );
5846
		$url = wp_specialchars_decode( $url_matches[1], ENT_QUOTES );
5847
5848
		// Pull out the class, but it's not a big deal if it's missing.
5849
		$class = '';
5850
		if ( preg_match( '/class=["\']([^"\']+)["\']/', $avatar, $class_matches ) )
5851
			$class = wp_specialchars_decode( $class_matches[1], ENT_QUOTES );
5852
5853
		return array( $url, $class );
5854
	}
5855
5856
	/**
5857
	 * Pings the WordPress.com Mirror Site for the specified options.
5858
	 *
5859
	 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
5860
	 *
5861
	 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
5862
	 */
5863
	public function get_cloud_site_options( $option_names ) {
5864
		$option_names = array_filter( (array) $option_names, 'is_string' );
5865
5866
		Jetpack::load_xml_rpc_client();
5867
		$xml = new Jetpack_IXR_Client( array( 'user_id' => JETPACK_MASTER_USER, ) );
5868
		$xml->query( 'jetpack.fetchSiteOptions', $option_names );
5869
		if ( $xml->isError() ) {
5870
			return array(
5871
				'error_code' => $xml->getErrorCode(),
5872
				'error_msg'  => $xml->getErrorMessage(),
5873
			);
5874
		}
5875
		$cloud_site_options = $xml->getResponse();
5876
5877
		return $cloud_site_options;
5878
	}
5879
5880
	/**
5881
	 * Fetch the filtered array of options that we should compare to determine an identity crisis.
5882
	 *
5883
	 * @return array An array of options to check.
5884
	 */
5885
	public static function identity_crisis_options_to_check() {
5886
		return array(
5887
			'siteurl',
5888
			'home',
5889
		);
5890
	}
5891
5892
	/**
5893
	 * Checks to make sure that local options have the same values as remote options.  Will cache the results for up to 24 hours.
5894
	 *
5895
	 * @param bool $force_recheck Whether to ignore any cached transient and manually re-check.
5896
	 *
5897
	 * @return array An array of options that do not match.  If everything is good, it will evaluate to false.
5898
	 */
5899
	public static function check_identity_crisis( $force_recheck = false ) {
5900
		if ( ! Jetpack::is_active() || Jetpack::is_development_mode() || Jetpack::is_staging_site() )
5901
			return false;
5902
5903
		if ( $force_recheck || false === ( $errors = get_transient( 'jetpack_has_identity_crisis' ) ) ) {
5904
			$options_to_check = self::identity_crisis_options_to_check();
5905
			$cloud_options = Jetpack::init()->get_cloud_site_options( $options_to_check );
5906
			$errors        = array();
5907
5908
			foreach ( $cloud_options as $cloud_key => $cloud_value ) {
5909
5910
				// If it's not the same as the local value...
5911
				if ( $cloud_value !== get_option( $cloud_key ) ) {
5912
5913
					// Break out if we're getting errors.  We are going to check the error keys later when we alert.
5914
					if ( 'error_code' == $cloud_key ) {
5915
						$errors[ $cloud_key ] = $cloud_value;
5916
						break;
5917
					}
5918
5919
					$parsed_cloud_value = parse_url( $cloud_value );
5920
					// If the current options is an IP address
5921
					if ( filter_var( $parsed_cloud_value['host'], FILTER_VALIDATE_IP ) ) {
5922
						// Give the new value a Jetpack to fly in to the clouds
5923
						Jetpack::resolve_identity_crisis( $cloud_key );
5924
						continue;
5925
					}
5926
5927
					// And it's not been added to the whitelist...
5928
					if ( ! self::is_identity_crisis_value_whitelisted( $cloud_key, $cloud_value ) ) {
5929
						/*
5930
						 * This should be a temporary hack until a cleaner solution is found.
5931
						 *
5932
						 * The siteurl and home can be set to use http in General > Settings
5933
						 * however some constants can be defined that can force https in wp-admin
5934
						 * when this happens wpcom can confuse wporg with a fake identity
5935
						 * crisis with a mismatch of http vs https when it should be allowed.
5936
						 * we need to check that here.
5937
						 *
5938
						 * @see https://github.com/Automattic/jetpack/issues/1006
5939
						 */
5940
						if ( ( 'home' == $cloud_key || 'siteurl' == $cloud_key )
5941
							&& ( substr( $cloud_value, 0, 8 ) == "https://" )
5942
							&& Jetpack::init()->is_ssl_required_to_visit_site() ) {
5943
							// Ok, we found a mismatch of http and https because of wp-config, not an invalid url
5944
							continue;
5945
						}
5946
5947
5948
						// Then kick an error!
5949
						$errors[ $cloud_key ] = $cloud_value;
5950
					}
5951
				}
5952
			}
5953
		}
5954
5955
		/**
5956
		 * Filters the errors returned when checking for an Identity Crisis.
5957
		 *
5958
		 * @since 2.3.2
5959
		 *
5960
		 * @param array $errors Array of Identity Crisis errors.
5961
		 * @param bool $force_recheck Ignore any cached transient and manually re-check. Default to false.
5962
		 */
5963
		return apply_filters( 'jetpack_has_identity_crisis', $errors, $force_recheck );
5964
	}
5965
5966
	/*
5967
	 * Resolve ID crisis
5968
	 *
5969
	 * If the URL has changed, but the rest of the options are the same (i.e. blog/user tokens)
5970
	 * The user has the option to update the shadow site with the new URL before a new
5971
	 * token is created.
5972
	 *
5973
	 * @param $key : Which option to sync.  null defautlts to home and siteurl
5974
	 */
5975
	public static function resolve_identity_crisis( $key = null ) {
5976
		if ( $key ) {
5977
			$identity_options = array( $key );
5978
		} else {
5979
			$identity_options = self::identity_crisis_options_to_check();
5980
		}
5981
5982
		if ( is_array( $identity_options ) ) {
5983
			foreach( $identity_options as $identity_option ) {
5984
				Jetpack_Sync::sync_options( __FILE__, $identity_option );
5985
5986
				/**
5987
				 * Fires when a shadow site option is updated.
5988
				 * These options are updated via the Identity Crisis UI.
5989
				 * $identity_option is the option that gets updated.
5990
				 *
5991
				 * @since 3.7.0
5992
				 */
5993
				do_action( "update_option_{$identity_option}" );
5994
			}
5995
		}
5996
	}
5997
5998
	/*
5999
	 * Whitelist URL
6000
	 *
6001
	 * Ignore the URL differences between the blog and the shadow site.
6002
	 */
6003
	public static function whitelist_current_url() {
6004
		$options_to_check = Jetpack::identity_crisis_options_to_check();
6005
		$cloud_options = Jetpack::init()->get_cloud_site_options( $options_to_check );
6006
6007
		foreach ( $cloud_options as $cloud_key => $cloud_value ) {
6008
			Jetpack::whitelist_identity_crisis_value( $cloud_key, $cloud_value );
6009
		}
6010
	}
6011
6012
	/*
6013
	 * Ajax callbacks for ID crisis resolutions
6014
	 *
6015
	 * Things that could happen here:
6016
	 *  - site_migrated : Update the URL on the shadow blog to match new domain
6017
	 *  - whitelist     : Ignore the URL difference
6018
	 *  - default       : Error message
6019
	 */
6020
	public static function resolve_identity_crisis_ajax_callback() {
6021
		check_ajax_referer( 'resolve-identity-crisis', 'ajax-nonce' );
6022
6023
		switch ( $_POST[ 'crisis_resolution_action' ] ) {
6024
			case 'site_migrated':
6025
				Jetpack::resolve_identity_crisis();
6026
				echo 'resolved';
6027
				break;
6028
6029
			case 'whitelist':
6030
				Jetpack::whitelist_current_url();
6031
				echo 'whitelisted';
6032
				break;
6033
6034
			case 'reset_connection':
6035
				// Delete the options first so it doesn't get confused which site to disconnect dotcom-side
6036
				Jetpack_Options::delete_option(
6037
					array(
6038
						'register',
6039
						'blog_token',
6040
						'user_token',
6041
						'user_tokens',
6042
						'master_user',
6043
						'time_diff',
6044
						'fallback_no_verify_ssl_certs',
6045
						'id',
6046
					)
6047
				);
6048
				delete_transient( 'jetpack_has_identity_crisis' );
6049
6050
				echo 'reset-connection-success';
6051
				break;
6052
6053
			default:
6054
				echo 'missing action';
6055
				break;
6056
		}
6057
6058
		wp_die();
6059
	}
6060
6061
	/**
6062
	 * Adds a value to the whitelist for the specified key.
6063
	 *
6064
	 * @param string $key The option name that we're whitelisting the value for.
6065
	 * @param string $value The value that we're intending to add to the whitelist.
6066
	 *
6067
	 * @return bool Whether the value was added to the whitelist, or false if it was already there.
6068
	 */
6069
	public static function whitelist_identity_crisis_value( $key, $value ) {
6070
		if ( Jetpack::is_identity_crisis_value_whitelisted( $key, $value ) ) {
6071
			return false;
6072
		}
6073
6074
		$whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist', array() );
6075
		if ( empty( $whitelist[ $key ] ) || ! is_array( $whitelist[ $key ] ) ) {
6076
			$whitelist[ $key ] = array();
6077
		}
6078
		array_push( $whitelist[ $key ], $value );
6079
6080
		Jetpack_Options::update_option( 'identity_crisis_whitelist', $whitelist );
6081
		return true;
6082
	}
6083
6084
	/**
6085
	 * Checks whether a value is already whitelisted.
6086
	 *
6087
	 * @param string $key The option name that we're checking the value for.
6088
	 * @param string $value The value that we're curious to see if it's on the whitelist.
6089
	 *
6090
	 * @return bool Whether the value is whitelisted.
6091
	 */
6092
	public static function is_identity_crisis_value_whitelisted( $key, $value ) {
6093
		$whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist', array() );
6094
		if ( ! empty( $whitelist[ $key ] ) && is_array( $whitelist[ $key ] ) && in_array( $value, $whitelist[ $key ] ) ) {
6095
			return true;
6096
		}
6097
		return false;
6098
	}
6099
6100
	/**
6101
	 * Checks whether the home and siteurl specifically are whitelisted
6102
	 * Written so that we don't have re-check $key and $value params every time
6103
	 * we want to check if this site is whitelisted, for example in footer.php
6104
	 *
6105
	 * @return bool True = already whitelsisted False = not whitelisted
6106
	 */
6107
	public static function is_staging_site() {
6108
		$is_staging = false;
6109
6110
		$current_whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist' );
6111
		if ( $current_whitelist ) {
6112
			$options_to_check  = Jetpack::identity_crisis_options_to_check();
6113
			$cloud_options     = Jetpack::init()->get_cloud_site_options( $options_to_check );
6114
6115
			foreach ( $cloud_options as $cloud_key => $cloud_value ) {
6116
				if ( self::is_identity_crisis_value_whitelisted( $cloud_key, $cloud_value ) ) {
6117
					$is_staging = true;
6118
					break;
6119
				}
6120
			}
6121
		}
6122
		$known_staging = array(
6123
			'urls' => array(
6124
				'#\.staging\.wpengine\.com$#i',
6125
				),
6126
			'constants' => array(
6127
				'IS_WPE_SNAPSHOT',
6128
				'JETPACK_STAGING_MODE',
6129
				)
6130
			);
6131
		/**
6132
		 * Filters the flags of known staging sites.
6133
		 *
6134
		 * @since 3.9.0
6135
		 *
6136
		 * @param array $known_staging {
6137
		 *     An array of arrays that each are used to check if the current site is staging.
6138
		 *     @type array $urls      URLs of staging sites in regex to check against site_url.
6139
		 *     @type array $cosntants PHP constants of known staging/developement environments.
6140
		 *  }
6141
		 */
6142
		$known_staging = apply_filters( 'jetpack_known_staging', $known_staging );
6143
6144
		if ( isset( $known_staging['urls'] ) ) {
6145
			foreach ( $known_staging['urls'] as $url ){
6146
				if ( preg_match( $url, site_url() ) ) {
6147
					$is_staging = true;
6148
					break;
6149
				}
6150
			}
6151
		}
6152
6153
		if ( isset( $known_staging['constants'] ) ) {
6154
			foreach ( $known_staging['constants'] as $constant ) {
6155
				if ( defined( $constant ) && constant( $constant ) ) {
6156
					$is_staging = true;
6157
				}
6158
			}
6159
		}
6160
6161
		/**
6162
		 * Filters is_staging_site check.
6163
		 *
6164
		 * @since 3.9.0
6165
		 *
6166
		 * @param bool $is_staging If the current site is a staging site.
6167
		 */
6168
		return apply_filters( 'jetpack_is_staging_site', $is_staging );
6169
	}
6170
6171
	public function identity_crisis_js( $nonce ) {
6172
?>
6173
<script>
6174
(function( $ ) {
6175
	var SECOND_IN_MS = 1000;
6176
6177
	function contactSupport( e ) {
6178
		e.preventDefault();
6179
		$( '.jp-id-crisis-question' ).hide();
6180
		$( '#jp-id-crisis-contact-support' ).show();
6181
	}
6182
6183
	function autodismissSuccessBanner() {
6184
		$( '.jp-identity-crisis' ).fadeOut(600); //.addClass( 'dismiss' );
6185
	}
6186
6187
	var data = { action: 'jetpack_resolve_identity_crisis', 'ajax-nonce': '<?php echo $nonce; ?>' };
6188
6189
	$( document ).ready(function() {
6190
6191
		// Site moved: Update the URL on the shadow blog
6192
		$( '.site-moved' ).click(function( e ) {
6193
			e.preventDefault();
6194
			data.crisis_resolution_action = 'site_migrated';
6195
			$( '#jp-id-crisis-question-1 .spinner' ).show();
6196
			$.post( ajaxurl, data, function() {
6197
				$( '.jp-id-crisis-question' ).hide();
6198
				$( '.banner-title' ).hide();
6199
				$( '#jp-id-crisis-success' ).show();
6200
				setTimeout( autodismissSuccessBanner, 6 * SECOND_IN_MS );
6201
			});
6202
6203
		});
6204
6205
		// URL hasn't changed, next question please.
6206
		$( '.site-not-moved' ).click(function( e ) {
6207
			e.preventDefault();
6208
			$( '.jp-id-crisis-question' ).hide();
6209
			$( '#jp-id-crisis-question-2' ).show();
6210
		});
6211
6212
		// Reset connection: two separate sites.
6213
		$( '.reset-connection' ).click(function( e ) {
6214
			data.crisis_resolution_action = 'reset_connection';
6215
			$.post( ajaxurl, data, function( response ) {
6216
				if ( 'reset-connection-success' === response ) {
6217
					window.location.replace( '<?php echo Jetpack::admin_url(); ?>' );
6218
				}
6219
			});
6220
		});
6221
6222
		// It's a dev environment.  Ignore.
6223
		$( '.is-dev-env' ).click(function( e ) {
6224
			data.crisis_resolution_action = 'whitelist';
6225
			$( '#jp-id-crisis-question-2 .spinner' ).show();
6226
			$.post( ajaxurl, data, function() {
6227
				$( '.jp-id-crisis-question' ).hide();
6228
				$( '.banner-title' ).hide();
6229
				$( '#jp-id-crisis-success' ).show();
6230
				setTimeout( autodismissSuccessBanner, 4 * SECOND_IN_MS );
6231
			});
6232
		});
6233
6234
		$( '.not-reconnecting' ).click(contactSupport);
6235
		$( '.not-staging-or-dev' ).click(contactSupport);
6236
	});
6237
})( jQuery );
6238
</script>
6239
<?php
6240
	}
6241
6242
	/**
6243
	 * Displays an admin_notice, alerting the user to an identity crisis.
6244
	 */
6245
	public function alert_identity_crisis() {
6246
		// @todo temporary killing of feature in 3.8.1 as it revealed a number of scenarios not foreseen.
6247
		if ( ! Jetpack::is_development_version() ) {
6248
			return;
6249
		}
6250
6251
		// @todo temporary copout for dealing with domain mapping
6252
		// @see https://github.com/Automattic/jetpack/issues/2702
6253
		if ( is_multisite() && defined( 'SUNRISE' ) && ! Jetpack::is_development_version() ) {
6254
			return;
6255
		}
6256
6257
		if ( ! current_user_can( 'jetpack_disconnect' ) ) {
6258
			return;
6259
		}
6260
6261
		if ( ! $errors = self::check_identity_crisis() ) {
6262
			return;
6263
		}
6264
6265
		// Only show on dashboard and jetpack pages
6266
		$screen = get_current_screen();
6267
		if ( 'dashboard' !== $screen->base && ! did_action( 'jetpack_notices' ) ) {
6268
			return;
6269
		}
6270
6271
		// Include the js!
6272
		$ajax_nonce = wp_create_nonce( 'resolve-identity-crisis' );
6273
		$this->identity_crisis_js( $ajax_nonce );
6274
6275
		// Include the CSS!
6276
		if ( ! wp_script_is( 'jetpack', 'done' ) ) {
6277
			$this->admin_banner_styles();
6278
		}
6279
6280
		if ( ! array_key_exists( 'error_code', $errors ) ) {
6281
			$key = 'siteurl';
6282
			if ( ! $errors[ $key ] ) {
6283
				$key = 'home';
6284
			}
6285
		} else {
6286
			$key = 'error_code';
6287
			// 401 is the only error we care about.  Any other errors should not trigger the alert.
6288
			if ( 401 !== $errors[ $key ] ) {
6289
				return;
6290
			}
6291
		}
6292
6293
		?>
6294
6295
		<style>
6296
			.jp-identity-crisis .jp-btn-group {
6297
					margin: 15px 0;
6298
				}
6299
			.jp-identity-crisis strong {
6300
					color: #518d2a;
6301
				}
6302
			.jp-identity-crisis.dismiss {
6303
				display: none;
6304
			}
6305
			.jp-identity-crisis .button {
6306
				margin-right: 4px;
6307
			}
6308
		</style>
6309
6310
		<div id="message" class="error jetpack-message jp-identity-crisis stay-visible">
6311
			<div class="service-mark"></div>
6312
			<div class="jp-id-banner__content">
6313
				<!-- <h3 class="banner-title"><?php _e( 'Something\'s not quite right with your Jetpack connection! Let\'s fix that.', 'jetpack' ); ?></h3> -->
6314
6315
				<div class="jp-id-crisis-question" id="jp-id-crisis-question-1">
6316
					<?php
6317
					// 401 means that this site has been disconnected from wpcom, but the remote site still thinks it's connected.
6318
					if ( 'error_code' == $key && '401' == $errors[ $key ] ) : ?>
6319
						<div class="banner-content">
6320
							<p><?php
6321
								/* translators: %s is a URL */
6322
								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/' );
6323
							?></p>
6324
						</div>
6325
						<div class="jp-btn-group">
6326
							<a href="#" class="reset-connection"><?php _e( 'Reset the connection', 'jetpack' ); ?></a>
6327
							<span class="idc-separator">|</span>
6328
							<a href="<?php echo esc_url( wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=dismiss' ), 'jetpack-deactivate' ) ); ?>"><?php _e( 'Deactivate Jetpack', 'jetpack' ); ?></a>
6329
						</div>
6330
					<?php else : ?>
6331
							<div class="banner-content">
6332
							<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>
6333
							</div>
6334
						<div class="jp-btn-group">
6335
							<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>
6336
							<span class="spinner"></span>
6337
						</div>
6338
					<?php endif ; ?>
6339
				</div>
6340
6341
				<div class="jp-id-crisis-question" id="jp-id-crisis-question-2" style="display: none;">
6342
					<div class="banner-content">
6343
						<p><?php printf(
6344
							/* translators: %1$s, %2$s and %3$s are URLs */
6345
							__(
6346
								'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>',
6347
								'jetpack'
6348
							),
6349
							$errors[ $key ],
6350
							(string) get_option( $key ),
6351
							'https://jetpack.com/support/what-does-resetting-the-connection-mean/'
6352
						); ?></p>
6353
					</div>
6354
					<div class="jp-btn-group">
6355
						<a href="#" class="reset-connection"><?php _e( 'Reset the connection', 'jetpack' ); ?></a> <span class="idc-separator">|</span>
6356
						<a href="#" class="is-dev-env"><?php _e( 'This is a development environment', 'jetpack' ); ?></a> <span class="idc-separator">|</span>
6357
						<a href="https://jetpack.com/contact-support/" class="contact-support"><?php _e( 'Submit a support ticket', 'jetpack' ); ?></a>
6358
						<span class="spinner"></span>
6359
					</div>
6360
				</div>
6361
6362
				<div class="jp-id-crisis-success" id="jp-id-crisis-success" style="display: none;">
6363
					<h3 class="success-notice"><?php printf( __( 'Thanks for taking the time to sort things out. We&#039;ve updated our records accordingly!', 'jetpack' ) ); ?></h3>
6364
				</div>
6365
			</div>
6366
		</div>
6367
6368
		<?php
6369
	}
6370
6371
	/**
6372
	 * Maybe Use a .min.css stylesheet, maybe not.
6373
	 *
6374
	 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
6375
	 */
6376
	public static function maybe_min_asset( $url, $path, $plugin ) {
6377
		// Short out on things trying to find actual paths.
6378
		if ( ! $path || empty( $plugin ) ) {
6379
			return $url;
6380
		}
6381
6382
		// Strip out the abspath.
6383
		$base = dirname( plugin_basename( $plugin ) );
6384
6385
		// Short out on non-Jetpack assets.
6386
		if ( 'jetpack/' !== substr( $base, 0, 8 ) ) {
6387
			return $url;
6388
		}
6389
6390
		// File name parsing.
6391
		$file              = "{$base}/{$path}";
6392
		$full_path         = JETPACK__PLUGIN_DIR . substr( $file, 8 );
6393
		$file_name         = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
6394
		$file_name_parts_r = array_reverse( explode( '.', $file_name ) );
6395
		$extension         = array_shift( $file_name_parts_r );
6396
6397
		if ( in_array( strtolower( $extension ), array( 'css', 'js' ) ) ) {
6398
			// Already pointing at the minified version.
6399
			if ( 'min' === $file_name_parts_r[0] ) {
6400
				return $url;
6401
			}
6402
6403
			$min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
6404
			if ( file_exists( $min_full_path ) ) {
6405
				$url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
6406
			}
6407
		}
6408
6409
		return $url;
6410
	}
6411
6412
	/**
6413
	 * Maybe inlines a stylesheet.
6414
	 *
6415
	 * If you'd like to inline a stylesheet instead of printing a link to it,
6416
	 * wp_style_add_data( 'handle', 'jetpack-inline', true );
6417
	 *
6418
	 * Attached to `style_loader_tag` filter.
6419
	 *
6420
	 * @param string $tag The tag that would link to the external asset.
6421
	 * @param string $handle The registered handle of the script in question.
6422
	 *
6423
	 * @return string
6424
	 */
6425
	public static function maybe_inline_style( $tag, $handle ) {
6426
		global $wp_styles;
6427
		$item = $wp_styles->registered[ $handle ];
6428
6429
		if ( ! isset( $item->extra['jetpack-inline'] ) || ! $item->extra['jetpack-inline'] ) {
6430
			return $tag;
6431
		}
6432
6433
		if ( preg_match( '# href=\'([^\']+)\' #i', $tag, $matches ) ) {
6434
			$href = $matches[1];
6435
			// Strip off query string
6436
			if ( $pos = strpos( $href, '?' ) ) {
6437
				$href = substr( $href, 0, $pos );
6438
			}
6439
			// Strip off fragment
6440
			if ( $pos = strpos( $href, '#' ) ) {
6441
				$href = substr( $href, 0, $pos );
6442
			}
6443
		} else {
6444
			return $tag;
6445
		}
6446
6447
		$plugins_dir = plugin_dir_url( JETPACK__PLUGIN_FILE );
6448
		if ( $plugins_dir !== substr( $href, 0, strlen( $plugins_dir ) ) ) {
6449
			return $tag;
6450
		}
6451
6452
		// If this stylesheet has a RTL version, and the RTL version replaces normal...
6453
		if ( isset( $item->extra['rtl'] ) && 'replace' === $item->extra['rtl'] && is_rtl() ) {
6454
			// And this isn't the pass that actually deals with the RTL version...
6455
			if ( false === strpos( $tag, " id='$handle-rtl-css' " ) ) {
6456
				// Short out, as the RTL version will deal with it in a moment.
6457
				return $tag;
6458
			}
6459
		}
6460
6461
		$file = JETPACK__PLUGIN_DIR . substr( $href, strlen( $plugins_dir ) );
6462
		$css  = Jetpack::absolutize_css_urls( file_get_contents( $file ), $href );
6463
		if ( $css ) {
6464
			$tag = "<!-- Inline {$item->handle} -->\r\n";
6465
			if ( empty( $item->extra['after'] ) ) {
6466
				wp_add_inline_style( $handle, $css );
6467
			} else {
6468
				array_unshift( $item->extra['after'], $css );
6469
				wp_style_add_data( $handle, 'after', $item->extra['after'] );
6470
			}
6471
		}
6472
6473
		return $tag;
6474
	}
6475
6476
	/**
6477
	 * Loads a view file from the views
6478
	 *
6479
	 * Data passed in with the $data parameter will be available in the
6480
	 * template file as $data['value']
6481
	 *
6482
	 * @param string $template - Template file to load
6483
	 * @param array $data - Any data to pass along to the template
6484
	 * @return boolean - If template file was found
6485
	 **/
6486
	public function load_view( $template, $data = array() ) {
6487
		$views_dir = JETPACK__PLUGIN_DIR . 'views/';
6488
6489
		if( file_exists( $views_dir . $template ) ) {
6490
			require_once( $views_dir . $template );
6491
			return true;
6492
		}
6493
6494
		error_log( "Jetpack: Unable to find view file $views_dir$template" );
6495
		return false;
6496
	}
6497
6498
	/**
6499
	 * Sends a ping to the Jetpack servers to toggle on/off remote portions
6500
	 * required by some modules.
6501
	 *
6502
	 * @param string $module_slug
6503
	 */
6504
	public function toggle_module_on_wpcom( $module_slug ) {
6505
		Jetpack::init()->sync->register( 'noop' );
6506
6507
		if ( false !== strpos( current_filter(), 'jetpack_activate_module_' ) ) {
6508
			self::check_privacy( $module_slug );
6509
		}
6510
6511
	}
6512
6513
	/**
6514
	 * Throws warnings for deprecated hooks to be removed from Jetpack
6515
	 */
6516
	public function deprecated_hooks() {
6517
		global $wp_filter;
6518
6519
		/*
6520
		 * Format:
6521
		 * deprecated_filter_name => replacement_name
6522
		 *
6523
		 * If there is no replacement us null for replacement_name
6524
		 */
6525
		$deprecated_list = array(
6526
			'jetpack_bail_on_shortcode'                => 'jetpack_shortcodes_to_include',
6527
			'wpl_sharing_2014_1'                       => null,
6528
			'jetpack-tools-to-include'                 => 'jetpack_tools_to_include',
6529
			'jetpack_identity_crisis_options_to_check' => null,
6530
		);
6531
6532
		// This is a silly loop depth. Better way?
6533
		foreach( $deprecated_list AS $hook => $hook_alt ) {
6534
			if( isset( $wp_filter[ $hook ] ) && is_array( $wp_filter[ $hook ] ) ) {
6535
				foreach( $wp_filter[$hook] AS $func => $values ) {
6536
					foreach( $values AS $hooked ) {
6537
						_deprecated_function( $hook . ' used for ' . $hooked['function'], null, $hook_alt );
6538
					}
6539
				}
6540
			}
6541
		}
6542
	}
6543
6544
	/**
6545
	 * Converts any url in a stylesheet, to the correct absolute url.
6546
	 *
6547
	 * Considerations:
6548
	 *  - Normal, relative URLs     `feh.png`
6549
	 *  - Data URLs                 `data:image/gif;base64,eh129ehiuehjdhsa==`
6550
	 *  - Schema-agnostic URLs      `//domain.com/feh.png`
6551
	 *  - Absolute URLs             `http://domain.com/feh.png`
6552
	 *  - Domain root relative URLs `/feh.png`
6553
	 *
6554
	 * @param $css string: The raw CSS -- should be read in directly from the file.
6555
	 * @param $css_file_url : The URL that the file can be accessed at, for calculating paths from.
6556
	 *
6557
	 * @return mixed|string
6558
	 */
6559
	public static function absolutize_css_urls( $css, $css_file_url ) {
6560
		$pattern = '#url\((?P<path>[^)]*)\)#i';
6561
		$css_dir = dirname( $css_file_url );
6562
		$p       = parse_url( $css_dir );
6563
		$domain  = sprintf(
6564
					'%1$s//%2$s%3$s%4$s',
6565
					isset( $p['scheme'] )           ? "{$p['scheme']}:" : '',
6566
					isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
6567
					$p['host'],
6568
					isset( $p['port'] )             ? ":{$p['port']}" : ''
6569
				);
6570
6571
		if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
6572
			$find = $replace = array();
6573
			foreach ( $matches as $match ) {
6574
				$url = trim( $match['path'], "'\" \t" );
6575
6576
				// If this is a data url, we don't want to mess with it.
6577
				if ( 'data:' === substr( $url, 0, 5 ) ) {
6578
					continue;
6579
				}
6580
6581
				// If this is an absolute or protocol-agnostic url,
6582
				// we don't want to mess with it.
6583
				if ( preg_match( '#^(https?:)?//#i', $url ) ) {
6584
					continue;
6585
				}
6586
6587
				switch ( substr( $url, 0, 1 ) ) {
6588
					case '/':
6589
						$absolute = $domain . $url;
6590
						break;
6591
					default:
6592
						$absolute = $css_dir . '/' . $url;
6593
				}
6594
6595
				$find[]    = $match[0];
6596
				$replace[] = sprintf( 'url("%s")', $absolute );
6597
			}
6598
			$css = str_replace( $find, $replace, $css );
6599
		}
6600
6601
		return $css;
6602
	}
6603
6604
	/**
6605
	 * This method checks to see if SSL is required by the site in
6606
	 * order to visit it in some way other than only setting the
6607
	 * https value in the home or siteurl values.
6608
	 *
6609
	 * @since 3.2
6610
	 * @return boolean
6611
	 **/
6612
	private function is_ssl_required_to_visit_site() {
6613
		global $wp_version;
6614
		$ssl = is_ssl();
6615
6616
		if ( force_ssl_admin() ) {
6617
			$ssl = true;
6618
		}
6619
		return $ssl;
6620
	}
6621
6622
	/**
6623
	 * This methods removes all of the registered css files on the frontend
6624
	 * from Jetpack in favor of using a single file. In effect "imploding"
6625
	 * all the files into one file.
6626
	 *
6627
	 * Pros:
6628
	 * - Uses only ONE css asset connection instead of 15
6629
	 * - Saves a minimum of 56k
6630
	 * - Reduces server load
6631
	 * - Reduces time to first painted byte
6632
	 *
6633
	 * Cons:
6634
	 * - Loads css for ALL modules. However all selectors are prefixed so it
6635
	 *		should not cause any issues with themes.
6636
	 * - Plugins/themes dequeuing styles no longer do anything. See
6637
	 *		jetpack_implode_frontend_css filter for a workaround
6638
	 *
6639
	 * For some situations developers may wish to disable css imploding and
6640
	 * instead operate in legacy mode where each file loads seperately and
6641
	 * can be edited individually or dequeued. This can be accomplished with
6642
	 * the following line:
6643
	 *
6644
	 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6645
	 *
6646
	 * @since 3.2
6647
	 **/
6648
	public function implode_frontend_css( $travis_test = false ) {
6649
		$do_implode = true;
6650
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6651
			$do_implode = false;
6652
		}
6653
6654
		/**
6655
		 * Allow CSS to be concatenated into a single jetpack.css file.
6656
		 *
6657
		 * @since 3.2.0
6658
		 *
6659
		 * @param bool $do_implode Should CSS be concatenated? Default to true.
6660
		 */
6661
		$do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6662
6663
		// Do not use the imploded file when default behaviour was altered through the filter
6664
		if ( ! $do_implode ) {
6665
			return;
6666
		}
6667
6668
		// We do not want to use the imploded file in dev mode, or if not connected
6669
		if ( Jetpack::is_development_mode() || ! self::is_active() ) {
6670
			if ( ! $travis_test ) {
6671
				return;
6672
			}
6673
		}
6674
6675
		// Do not use the imploded file if sharing css was dequeued via the sharing settings screen
6676
		if ( get_option( 'sharedaddy_disable_resources' ) ) {
6677
			return;
6678
		}
6679
6680
		/*
6681
		 * Now we assume Jetpack is connected and able to serve the single
6682
		 * file.
6683
		 *
6684
		 * In the future there will be a check here to serve the file locally
6685
		 * or potentially from the Jetpack CDN
6686
		 *
6687
		 * For now:
6688
		 * - Enqueue a single imploded css file
6689
		 * - Zero out the style_loader_tag for the bundled ones
6690
		 * - Be happy, drink scotch
6691
		 */
6692
6693
		add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6694
6695
		$version = Jetpack::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6696
6697
		wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6698
		wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6699
	}
6700
6701
	function concat_remove_style_loader_tag( $tag, $handle ) {
6702
		if ( in_array( $handle, $this->concatenated_style_handles ) ) {
6703
			$tag = '';
6704
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6705
				$tag = "<!-- `" . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6706
			}
6707
		}
6708
6709
		return $tag;
6710
	}
6711
6712
	/*
6713
	 * Check the heartbeat data
6714
	 *
6715
	 * Organizes the heartbeat data by severity.  For example, if the site
6716
	 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
6717
	 *
6718
	 * Data will be added to "caution" array, if it either:
6719
	 *  - Out of date Jetpack version
6720
	 *  - Out of date WP version
6721
	 *  - Out of date PHP version
6722
	 *
6723
	 * $return array $filtered_data
6724
	 */
6725
	public static function jetpack_check_heartbeat_data() {
6726
		$raw_data = Jetpack_Heartbeat::generate_stats_array();
6727
6728
		$good    = array();
6729
		$caution = array();
6730
		$bad     = array();
6731
6732
		foreach ( $raw_data as $stat => $value ) {
6733
6734
			// Check jetpack version
6735
			if ( 'version' == $stat ) {
6736
				if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
6737
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__VERSION;
6738
					continue;
6739
				}
6740
			}
6741
6742
			// Check WP version
6743
			if ( 'wp-version' == $stat ) {
6744
				if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
6745
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__MINIMUM_WP_VERSION;
6746
					continue;
6747
				}
6748
			}
6749
6750
			// Check PHP version
6751
			if ( 'php-version' == $stat ) {
6752
				if ( version_compare( PHP_VERSION, '5.2.4', '<' ) ) {
6753
					$caution[ $stat ] = $value . " - min supported is 5.2.4";
6754
					continue;
6755
				}
6756
			}
6757
6758
			// Check ID crisis
6759
			if ( 'identitycrisis' == $stat ) {
6760
				if ( 'yes' == $value ) {
6761
					$bad[ $stat ] = $value;
6762
					continue;
6763
				}
6764
			}
6765
6766
			// The rest are good :)
6767
			$good[ $stat ] = $value;
6768
		}
6769
6770
		$filtered_data = array(
6771
			'good'    => $good,
6772
			'caution' => $caution,
6773
			'bad'     => $bad
6774
		);
6775
6776
		return $filtered_data;
6777
	}
6778
6779
6780
	/*
6781
	 * This method is used to organize all options that can be reset
6782
	 * without disconnecting Jetpack.
6783
	 *
6784
	 * It is used in class.jetpack-cli.php to reset options
6785
	 *
6786
	 * @return array of options to delete.
6787
	 */
6788
	public static function get_jetpack_options_for_reset() {
6789
		$jetpack_options            = Jetpack_Options::get_option_names();
6790
		$jetpack_options_non_compat = Jetpack_Options::get_option_names( 'non_compact' );
6791
		$jetpack_options_private    = Jetpack_Options::get_option_names( 'private' );
6792
6793
		$all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
6794
6795
		// A manual build of the wp options
6796
		$wp_options = array(
6797
			'sharing-options',
6798
			'disabled_likes',
6799
			'disabled_reblogs',
6800
			'jetpack_comments_likes_enabled',
6801
			'wp_mobile_excerpt',
6802
			'wp_mobile_featured_images',
6803
			'wp_mobile_app_promos',
6804
			'stats_options',
6805
			'stats_dashboard_widget',
6806
			'safecss_preview_rev',
6807
			'safecss_rev',
6808
			'safecss_revision_migrated',
6809
			'nova_menu_order',
6810
			'jetpack_portfolio',
6811
			'jetpack_portfolio_posts_per_page',
6812
			'jetpack_testimonial',
6813
			'jetpack_testimonial_posts_per_page',
6814
			'wp_mobile_custom_css',
6815
			'sharedaddy_disable_resources',
6816
			'sharing-options',
6817
			'sharing-services',
6818
			'site_icon_temp_data',
6819
			'featured-content',
6820
			'site_logo',
6821
		);
6822
6823
		// Flag some Jetpack options as unsafe
6824
		$unsafe_options = array(
6825
			'id',                           // (int)    The Client ID/WP.com Blog ID of this site.
6826
			'master_user',                  // (int)    The local User ID of the user who connected this site to jetpack.wordpress.com.
6827
			'version',                      // (string) Used during upgrade procedure to auto-activate new modules. version:time
6828
			'jumpstart',                    // (string) A flag for whether or not to show the Jump Start.  Accepts: new_connection, jumpstart_activated, jetpack_action_taken, jumpstart_dismissed.
6829
6830
			// non_compact
6831
			'activated',
6832
6833
			// private
6834
			'register',
6835
			'blog_token',                  // (string) The Client Secret/Blog Token of this site.
6836
			'user_token',                  // (string) The User Token of this site. (deprecated)
6837
			'user_tokens'
6838
		);
6839
6840
		// Remove the unsafe Jetpack options
6841
		foreach ( $unsafe_options as $unsafe_option ) {
6842
			if ( false !== ( $key = array_search( $unsafe_option, $all_jp_options ) ) ) {
6843
				unset( $all_jp_options[ $key ] );
6844
			}
6845
		}
6846
6847
		$options = array(
6848
			'jp_options' => $all_jp_options,
6849
			'wp_options' => $wp_options
6850
		);
6851
6852
		return $options;
6853
	}
6854
6855
	/*
6856
	 * Check if an option of a Jetpack module has been updated.
6857
	 *
6858
	 * If any module option has been updated before Jump Start has been dismissed,
6859
	 * update the 'jumpstart' option so we can hide Jump Start.
6860
	 */
6861
	public static function jumpstart_has_updated_module_option( $option_name = '' ) {
6862
		// Bail if Jump Start has already been dismissed
6863
		if ( 'new_connection' !== Jetpack::get_option( 'jumpstart' ) ) {
6864
			return false;
6865
		}
6866
6867
		$jetpack = Jetpack::init();
6868
6869
6870
		// Manual build of module options
6871
		$option_names = self::get_jetpack_options_for_reset();
6872
6873
		if ( in_array( $option_name, $option_names['wp_options'] ) ) {
6874
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
6875
6876
			//Jump start is being dismissed send data to MC Stats
6877
			$jetpack->stat( 'jumpstart', 'manual,'.$option_name );
6878
6879
			$jetpack->do_stats( 'server_side' );
6880
		}
6881
6882
	}
6883
6884
	/*
6885
	 * Strip http:// or https:// from a url, replaces forward slash with ::,
6886
	 * so we can bring them directly to their site in calypso.
6887
	 *
6888
	 * @param string | url
6889
	 * @return string | url without the guff
6890
	 */
6891
	public static function build_raw_urls( $url ) {
6892
		$strip_http = '/.*?:\/\//i';
6893
		$url = preg_replace( $strip_http, '', $url  );
6894
		$url = str_replace( '/', '::', $url );
6895
		return $url;
6896
	}
6897
6898
	/**
6899
	 * Stores and prints out domains to prefetch for page speed optimization.
6900
	 *
6901
	 * @param mixed $new_urls
6902
	 */
6903
	public static function dns_prefetch( $new_urls = null ) {
6904
		static $prefetch_urls = array();
6905
		if ( empty( $new_urls ) && ! empty( $prefetch_urls ) ) {
6906
			echo "\r\n";
6907
			foreach ( $prefetch_urls as $this_prefetch_url ) {
6908
				printf( "<link rel='dns-prefetch' href='%s'>\r\n", esc_attr( $this_prefetch_url ) );
6909
			}
6910
		} elseif ( ! empty( $new_urls ) ) {
6911
			if ( ! has_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) ) ) {
6912
				add_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) );
6913
			}
6914
			foreach ( (array) $new_urls as $this_new_url ) {
6915
				$prefetch_urls[] = strtolower( untrailingslashit( preg_replace( '#^https?://#i', '//', $this_new_url ) ) );
6916
			}
6917
			$prefetch_urls = array_unique( $prefetch_urls );
6918
		}
6919
	}
6920
6921
	public function wp_dashboard_setup() {
6922
		if ( self::is_active() ) {
6923
			add_action( 'jetpack_dashboard_widget', array( __CLASS__, 'dashboard_widget_footer' ), 999 );
6924
			$widget_title = __( 'Site Stats', 'jetpack' );
6925
		} elseif ( ! self::is_development_mode() && current_user_can( 'jetpack_connect' ) ) {
6926
			add_action( 'jetpack_dashboard_widget', array( $this, 'dashboard_widget_connect_to_wpcom' ) );
6927
			$widget_title = __( 'Please Connect Jetpack', 'jetpack' );
6928
		}
6929
6930
		if ( has_action( 'jetpack_dashboard_widget' ) ) {
6931
			wp_add_dashboard_widget(
6932
				'jetpack_summary_widget',
6933
				$widget_title,
6934
				array( __CLASS__, 'dashboard_widget' )
6935
			);
6936
			wp_enqueue_style( 'jetpack-dashboard-widget', plugins_url( 'css/dashboard-widget.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
6937
6938
			// If we're inactive and not in development mode, sort our box to the top.
6939
			if ( ! self::is_active() && ! self::is_development_mode() ) {
6940
				global $wp_meta_boxes;
6941
6942
				$dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
6943
				$ours      = array( 'jetpack_summary_widget' => $dashboard['jetpack_summary_widget'] );
6944
6945
				$wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard );
6946
			}
6947
		}
6948
	}
6949
6950
	/**
6951
	 * @param mixed $result Value for the user's option
6952
	 * @return mixed
6953
	 */
6954
	function get_user_option_meta_box_order_dashboard( $sorted ) {
6955
		if ( ! is_array( $sorted ) ) {
6956
			return $sorted;
6957
		}
6958
6959
		foreach ( $sorted as $box_context => $ids ) {
6960
			if ( false === strpos( $ids, 'dashboard_stats' ) ) {
6961
				// If the old id isn't anywhere in the ids, don't bother exploding and fail out.
6962
				continue;
6963
			}
6964
6965
			$ids_array = explode( ',', $ids );
6966
			$key = array_search( 'dashboard_stats', $ids_array );
6967
6968
			if ( false !== $key ) {
6969
				// If we've found that exact value in the option (and not `google_dashboard_stats` for example)
6970
				$ids_array[ $key ] = 'jetpack_summary_widget';
6971
				$sorted[ $box_context ] = implode( ',', $ids_array );
6972
				// We've found it, stop searching, and just return.
6973
				break;
6974
			}
6975
		}
6976
6977
		return $sorted;
6978
	}
6979
6980
	public static function dashboard_widget() {
6981
		/**
6982
		 * Fires when the dashboard is loaded.
6983
		 *
6984
		 * @since 3.4.0
6985
		 */
6986
		do_action( 'jetpack_dashboard_widget' );
6987
	}
6988
6989
	public static function dashboard_widget_footer() {
6990
		?>
6991
		<footer>
6992
6993
		<div class="protect">
6994
			<?php if ( Jetpack::is_module_active( 'protect' ) ) : ?>
6995
				<h3><?php echo number_format_i18n( get_site_option( 'jetpack_protect_blocked_attempts', 0 ) ); ?></h3>
6996
				<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>
6997
			<?php elseif ( current_user_can( 'jetpack_activate_modules' ) && ! self::is_development_mode() ) : ?>
6998
				<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' ); ?>">
6999
					<?php esc_html_e( 'Activate Protect', 'jetpack' ); ?>
7000
				</a>
7001
			<?php else : ?>
7002
				<?php esc_html_e( 'Protect is inactive.', 'jetpack' ); ?>
7003
			<?php endif; ?>
7004
		</div>
7005
7006
		<div class="akismet">
7007
			<?php if ( is_plugin_active( 'akismet/akismet.php' ) ) : ?>
7008
				<h3><?php echo number_format_i18n( get_option( 'akismet_spam_count', 0 ) ); ?></h3>
7009
				<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>
7010 View Code Duplication
			<?php elseif ( current_user_can( 'activate_plugins' ) && ! is_wp_error( validate_plugin( 'akismet/akismet.php' ) ) ) : ?>
7011
				<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">
7012
					<?php esc_html_e( 'Activate Akismet', 'jetpack' ); ?>
7013
				</a>
7014
			<?php else : ?>
7015
				<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>
7016
			<?php endif; ?>
7017
		</div>
7018
7019
7020 View Code Duplication
		<?php if ( ! current_user_can( 'edit_posts' ) && self::is_user_connected() ) : ?>
7021
			<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>
7022
		<?php endif; ?>
7023
7024
		</footer>
7025
		<?php
7026
	}
7027
7028
	public function dashboard_widget_connect_to_wpcom() {
7029
		if ( Jetpack::is_active() || Jetpack::is_development_mode() || ! current_user_can( 'jetpack_connect' ) ) {
7030
			return;
7031
		}
7032
		?>
7033
		<div class="wpcom-connect">
7034
			<div class="jp-emblem">
7035
			<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">
7036
				<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"/>
7037
			</svg>
7038
			</div>
7039
			<h3><?php esc_html_e( 'Please Connect Jetpack', 'jetpack' ); ?></h3>
7040
			<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>
7041
7042
			<div class="actions">
7043
				<a href="<?php echo $this->build_connect_url( false, false, 'widget-btn' ); ?>" class="button button-primary">
7044
					<?php esc_html_e( 'Connect Jetpack', 'jetpack' ); ?>
7045
				</a>
7046
			</div>
7047
		</div>
7048
		<?php
7049
	}
7050
7051
	/*
7052
	 * A graceful transition to using Core's site icon.
7053
	 *
7054
	 * All of the hard work has already been done with the image
7055
	 * in all_done_page(). All that needs to be done now is update
7056
	 * the option and display proper messaging.
7057
	 *
7058
	 * @todo remove when WP 4.3 is minimum
7059
	 *
7060
	 * @since 3.6.1
7061
	 *
7062
	 * @return bool false = Core's icon not available || true = Core's icon is available
7063
	 */
7064
	public static function jetpack_site_icon_available_in_core() {
7065
		global $wp_version;
7066
		$core_icon_available = function_exists( 'has_site_icon' ) && version_compare( $wp_version, '4.3-beta' ) >= 0;
7067
7068
		if ( ! $core_icon_available ) {
7069
			return false;
7070
		}
7071
7072
		// No need for Jetpack's site icon anymore if core's is already set
7073
		if ( has_site_icon() ) {
7074
			if ( Jetpack::is_module_active( 'site-icon' ) ) {
7075
				Jetpack::log( 'deactivate', 'site-icon' );
7076
				Jetpack::deactivate_module( 'site-icon' );
7077
			}
7078
			return true;
7079
		}
7080
7081
		// Transfer Jetpack's site icon to use core.
7082
		$site_icon_id = Jetpack::get_option( 'site_icon_id' );
7083
		if ( $site_icon_id ) {
7084
			// Update core's site icon
7085
			update_option( 'site_icon', $site_icon_id );
7086
7087
			// Delete Jetpack's icon option. We still want the blavatar and attached data though.
7088
			delete_option( 'site_icon_id' );
7089
		}
7090
7091
		// No need for Jetpack's site icon anymore
7092
		if ( Jetpack::is_module_active( 'site-icon' ) ) {
7093
			Jetpack::log( 'deactivate', 'site-icon' );
7094
			Jetpack::deactivate_module( 'site-icon' );
7095
		}
7096
7097
		return true;
7098
	}
7099
7100
}
7101