Completed
Push — update/improve-performance-oth... ( 0d75a2...f085f7 )
by
unknown
144:51 queued 135:15
created

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