Completed
Push — branch-4.5-built ( 997ccf...3ed81f )
by
unknown
68:20 queued 60:23
created

class.jetpack.php (12 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
		'jetpack-my-community-widget',
55
		'wordads',
56
	);
57
58
	public $plugins_to_deactivate = array(
59
		'stats'               => array( 'stats/stats.php', 'WordPress.com Stats' ),
60
		'shortlinks'          => array( 'stats/stats.php', 'WordPress.com Stats' ),
61
		'sharedaddy'          => array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
62
		'twitter-widget'      => array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
63
		'after-the-deadline'  => array( 'after-the-deadline/after-the-deadline.php', 'After The Deadline' ),
64
		'contact-form'        => array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
65
		'contact-form'        => array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
66
		'custom-css'          => array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
67
		'random-redirect'     => array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
68
		'videopress'          => array( 'video/video.php', 'VideoPress' ),
69
		'widget-visibility'   => array( 'jetpack-widget-visibility/widget-visibility.php', 'Jetpack Widget Visibility' ),
70
		'widget-visibility'   => array( 'widget-visibility-without-jetpack/widget-visibility-without-jetpack.php', 'Widget Visibility Without Jetpack' ),
71
		'sharedaddy'          => array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
72
		'omnisearch'          => array( 'jetpack-omnisearch/omnisearch.php', 'Jetpack Omnisearch' ),
73
		'gravatar-hovercards' => array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
74
		'latex'               => array( 'wp-latex/wp-latex.php', 'WP LaTeX' )
75
	);
76
77
	static $capability_translations = array(
78
		'administrator' => 'manage_options',
79
		'editor'        => 'edit_others_posts',
80
		'author'        => 'publish_posts',
81
		'contributor'   => 'edit_posts',
82
		'subscriber'    => 'read',
83
	);
84
85
	/**
86
	 * Map of modules that have conflicts with plugins and should not be auto-activated
87
	 * if the plugins are active.  Used by filter_default_modules
88
	 *
89
	 * Plugin Authors: If you'd like to prevent a single module from auto-activating,
90
	 * change `module-slug` and add this to your plugin:
91
	 *
92
	 * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
93
	 * function my_jetpack_get_default_modules( $modules ) {
94
	 *     return array_diff( $modules, array( 'module-slug' ) );
95
	 * }
96
	 *
97
	 * @var array
98
	 */
99
	private $conflicting_plugins = array(
100
		'comments'          => array(
101
			'Intense Debate'                       => 'intensedebate/intensedebate.php',
102
			'Disqus'                               => 'disqus-comment-system/disqus.php',
103
			'Livefyre'                             => 'livefyre-comments/livefyre.php',
104
			'Comments Evolved for WordPress'       => 'gplus-comments/comments-evolved.php',
105
			'Google+ Comments'                     => 'google-plus-comments/google-plus-comments.php',
106
			'WP-SpamShield Anti-Spam'              => 'wp-spamshield/wp-spamshield.php',
107
		),
108
		'contact-form'      => array(
109
			'Contact Form 7'                       => 'contact-form-7/wp-contact-form-7.php',
110
			'Gravity Forms'                        => 'gravityforms/gravityforms.php',
111
			'Contact Form Plugin'                  => 'contact-form-plugin/contact_form.php',
112
			'Easy Contact Forms'                   => 'easy-contact-forms/easy-contact-forms.php',
113
			'Fast Secure Contact Form'             => 'si-contact-form/si-contact-form.php',
114
		),
115
		'minileven'         => array(
116
			'WPtouch'                              => 'wptouch/wptouch.php',
117
		),
118
		'latex'             => array(
119
			'LaTeX for WordPress'                  => 'latex/latex.php',
120
			'Youngwhans Simple Latex'              => 'youngwhans-simple-latex/yw-latex.php',
121
			'Easy WP LaTeX'                        => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
122
			'MathJax-LaTeX'                        => 'mathjax-latex/mathjax-latex.php',
123
			'Enable Latex'                         => 'enable-latex/enable-latex.php',
124
			'WP QuickLaTeX'                        => 'wp-quicklatex/wp-quicklatex.php',
125
		),
126
		'protect'           => array(
127
			'Limit Login Attempts'                 => 'limit-login-attempts/limit-login-attempts.php',
128
			'Captcha'                              => 'captcha/captcha.php',
129
			'Brute Force Login Protection'         => 'brute-force-login-protection/brute-force-login-protection.php',
130
			'Login Security Solution'              => 'login-security-solution/login-security-solution.php',
131
			'WPSecureOps Brute Force Protect'      => 'wpsecureops-bruteforce-protect/wpsecureops-bruteforce-protect.php',
132
			'BulletProof Security'                 => 'bulletproof-security/bulletproof-security.php',
133
			'SiteGuard WP Plugin'                  => 'siteguard/siteguard.php',
134
			'Security-protection'                  => 'security-protection/security-protection.php',
135
			'Login Security'                       => 'login-security/login-security.php',
136
			'Botnet Attack Blocker'                => 'botnet-attack-blocker/botnet-attack-blocker.php',
137
			'Wordfence Security'                   => 'wordfence/wordfence.php',
138
			'All In One WP Security & Firewall'    => 'all-in-one-wp-security-and-firewall/wp-security.php',
139
			'iThemes Security'                     => 'better-wp-security/better-wp-security.php',
140
		),
141
		'random-redirect'   => array(
142
			'Random Redirect 2'                    => 'random-redirect-2/random-redirect.php',
143
		),
144
		'related-posts'     => array(
145
			'YARPP'                                => 'yet-another-related-posts-plugin/yarpp.php',
146
			'WordPress Related Posts'              => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
147
			'nrelate Related Content'              => 'nrelate-related-content/nrelate-related.php',
148
			'Contextual Related Posts'             => 'contextual-related-posts/contextual-related-posts.php',
149
			'Related Posts for WordPress'          => 'microkids-related-posts/microkids-related-posts.php',
150
			'outbrain'                             => 'outbrain/outbrain.php',
151
			'Shareaholic'                          => 'shareaholic/shareaholic.php',
152
			'Sexybookmarks'                        => 'sexybookmarks/shareaholic.php',
153
		),
154
		'sharedaddy'        => array(
155
			'AddThis'                              => 'addthis/addthis_social_widget.php',
156
			'Add To Any'                           => 'add-to-any/add-to-any.php',
157
			'ShareThis'                            => 'share-this/sharethis.php',
158
			'Shareaholic'                          => 'shareaholic/shareaholic.php',
159
		),
160
		'seo-tools' => array(
161
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
162
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
163
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
164
			'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
165
		),
166
		'verification-tools' => array(
167
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
168
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
169
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
170
			'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
171
		),
172
		'widget-visibility' => array(
173
			'Widget Logic'                         => 'widget-logic/widget_logic.php',
174
			'Dynamic Widgets'                      => 'dynamic-widgets/dynamic-widgets.php',
175
		),
176
		'sitemaps' => array(
177
			'Google XML Sitemaps'                  => 'google-sitemap-generator/sitemap.php',
178
			'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
179
			'Google XML Sitemaps for qTranslate'   => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php',
180
			'XML Sitemap & Google News feeds'      => 'xml-sitemap-feed/xml-sitemap.php',
181
			'Google Sitemap by BestWebSoft'        => 'google-sitemap-plugin/google-sitemap-plugin.php',
182
			'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
183
			'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
184
			'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
185
			'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
186
			'Sitemap'                              => 'sitemap/sitemap.php',
187
			'Simple Wp Sitemap'                    => 'simple-wp-sitemap/simple-wp-sitemap.php',
188
			'Simple Sitemap'                       => 'simple-sitemap/simple-sitemap.php',
189
			'XML Sitemaps'                         => 'xml-sitemaps/xml-sitemaps.php',
190
			'MSM Sitemaps'                         => 'msm-sitemap/msm-sitemap.php',
191
		),
192
	);
193
194
	/**
195
	 * Plugins for which we turn off our Facebook OG Tags implementation.
196
	 *
197
	 * Note: All in One SEO Pack, All in one SEO Pack Pro, WordPress SEO by Yoast, and WordPress SEO Premium by Yoast automatically deactivate
198
	 * Jetpack's Open Graph tags via filter when their Social Meta modules are active.
199
	 *
200
	 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
201
	 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
202
	 */
203
	private $open_graph_conflicting_plugins = array(
204
		'2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
205
		                                                         // 2 Click Social Media Buttons
206
		'add-link-to-facebook/add-link-to-facebook.php',         // Add Link to Facebook
207
		'add-meta-tags/add-meta-tags.php',                       // Add Meta Tags
208
		'autodescription/autodescription.php',                   // The SEO Framework
209
		'easy-facebook-share-thumbnails/esft.php',               // Easy Facebook Share Thumbnail
210
		'heateor-open-graph-meta-tags/heateor-open-graph-meta-tags.php',
211
		                                                         // Open Graph Meta Tags by Heateor
212
		'facebook/facebook.php',                                 // Facebook (official plugin)
213
		'facebook-awd/AWD_facebook.php',                         // Facebook AWD All in one
214
		'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
215
		                                                         // Facebook Featured Image & OG Meta Tags
216
		'facebook-meta-tags/facebook-metatags.php',              // Facebook Meta Tags
217
		'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
218
		                                                         // Facebook Open Graph Meta Tags for WordPress
219
		'facebook-revised-open-graph-meta-tag/index.php',        // Facebook Revised Open Graph Meta Tag
220
		'facebook-thumb-fixer/_facebook-thumb-fixer.php',        // Facebook Thumb Fixer
221
		'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
222
		                                                         // Fedmich's Facebook Open Graph Meta
223
		'header-footer/plugin.php',                              // Header and Footer
224
		'network-publisher/networkpub.php',                      // Network Publisher
225
		'nextgen-facebook/nextgen-facebook.php',                 // NextGEN Facebook OG
226
		'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php',
227
		                                                         // NextScripts SNAP
228
		'og-tags/og-tags.php',                                   // OG Tags
229
		'opengraph/opengraph.php',                               // Open Graph
230
		'open-graph-protocol-framework/open-graph-protocol-framework.php',
231
		                                                         // Open Graph Protocol Framework
232
		'seo-facebook-comments/seofacebook.php',                 // SEO Facebook Comments
233
		'seo-ultimate/seo-ultimate.php',                         // SEO Ultimate
234
		'sexybookmarks/sexy-bookmarks.php',                      // Shareaholic
235
		'shareaholic/sexy-bookmarks.php',                        // Shareaholic
236
		'sharepress/sharepress.php',                             // SharePress
237
		'simple-facebook-connect/sfc.php',                       // Simple Facebook Connect
238
		'social-discussions/social-discussions.php',             // Social Discussions
239
		'social-sharing-toolkit/social_sharing_toolkit.php',     // Social Sharing Toolkit
240
		'socialize/socialize.php',                               // Socialize
241
		'squirrly-seo/squirrly.php',                             // SEO by SQUIRRLY™
242
		'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
243
		                                                         // Tweet, Like, Google +1 and Share
244
		'wordbooker/wordbooker.php',                             // Wordbooker
245
		'wpsso/wpsso.php',                                       // WordPress Social Sharing Optimization
246
		'wp-caregiver/wp-caregiver.php',                         // WP Caregiver
247
		'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
248
		                                                         // WP Facebook Like Send & Open Graph Meta
249
		'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',   // WP Facebook Open Graph protocol
250
		'wp-ogp/wp-ogp.php',                                     // WP-OGP
251
		'zoltonorg-social-plugin/zosp.php',                      // Zolton.org Social Plugin
252
		'wp-fb-share-like-button/wp_fb_share-like_widget.php'    // WP Facebook Like Button
253
	);
254
255
	/**
256
	 * Plugins for which we turn off our Twitter Cards Tags implementation.
257
	 */
258
	private $twitter_cards_conflicting_plugins = array(
259
	//	'twitter/twitter.php',                       // The official one handles this on its own.
260
	//	                                             // https://github.com/twitter/wordpress/blob/master/src/Twitter/WordPress/Cards/Compatibility.php
261
		'eewee-twitter-card/index.php',              // Eewee Twitter Card
262
		'ig-twitter-cards/ig-twitter-cards.php',     // IG:Twitter Cards
263
		'jm-twitter-cards/jm-twitter-cards.php',     // JM Twitter Cards
264
		'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',
265
		                                             // Pure Web Brilliant's Social Graph Twitter Cards Extension
266
		'twitter-cards/twitter-cards.php',           // Twitter Cards
267
		'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta
268
		'wp-twitter-cards/twitter_cards.php',        // WP Twitter Cards
269
	);
270
271
	/**
272
	 * Message to display in admin_notice
273
	 * @var string
274
	 */
275
	public $message = '';
276
277
	/**
278
	 * Error to display in admin_notice
279
	 * @var string
280
	 */
281
	public $error = '';
282
283
	/**
284
	 * Modules that need more privacy description.
285
	 * @var string
286
	 */
287
	public $privacy_checks = '';
288
289
	/**
290
	 * Stats to record once the page loads
291
	 *
292
	 * @var array
293
	 */
294
	public $stats = array();
295
296
	/**
297
	 * Jetpack_Sync object
298
	 */
299
	public $sync;
300
301
	/**
302
	 * Verified data for JSON authorization request
303
	 */
304
	public $json_api_authorization_request = array();
305
306
	/**
307
	 * Holds the singleton instance of this class
308
	 * @since 2.3.3
309
	 * @var Jetpack
310
	 */
311
	static $instance = false;
312
313
	/**
314
	 * Singleton
315
	 * @static
316
	 */
317
	public static function init() {
318
		if ( ! self::$instance ) {
319
			self::$instance = new Jetpack;
320
321
			self::$instance->plugin_upgrade();
322
		}
323
324
		return self::$instance;
325
	}
326
327
	/**
328
	 * Must never be called statically
329
	 */
330
	function plugin_upgrade() {
331
		if ( Jetpack::is_active() ) {
332
			list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
333
			if ( JETPACK__VERSION != $version ) {
334
335
				// Check which active modules actually exist and remove others from active_modules list
336
				$unfiltered_modules = Jetpack::get_active_modules();
337
				$modules = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) );
338
				if ( array_diff( $unfiltered_modules, $modules ) ) {
339
					Jetpack::update_active_modules( $modules );
340
				}
341
342
				add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
343
344
				// Upgrade to 4.3.0
345
				if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
346
					Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
347
				}
348
349
				Jetpack::maybe_set_version_option();
350
			}
351
		}
352
	}
353
354
	static function activate_manage( ) {
355
		if ( did_action( 'init' ) || current_filter() == 'init' ) {
356
			self::activate_module( 'manage', false, false );
357
		} else if ( !  has_action( 'init' , array( __CLASS__, 'activate_manage' ) ) ) {
358
			add_action( 'init', array( __CLASS__, 'activate_manage' ) );
359
		}
360
	}
361
362
	static function update_active_modules( $modules ) {
363
		$current_modules = Jetpack_Options::get_option( 'active_modules', array() );
364
365
		$success = Jetpack_Options::update_option( 'active_modules', array_unique( $modules ) );
366
367
		if ( is_array( $modules ) && is_array( $current_modules ) ) {
368
			$new_active_modules = array_diff( $modules, $current_modules );
369
			foreach( $new_active_modules as $module ) {
370
				/**
371
				 * Fires when a specific module is activated.
372
				 *
373
				 * @since 1.9.0
374
				 *
375
				 * @param string $module Module slug.
376
				 * @param boolean $success whether the module was activated. @since 4.2
377
				 */
378
				do_action( 'jetpack_activate_module', $module, $success );
379
380
				/**
381
				 * Fires when a module is activated.
382
				 * The dynamic part of the filter, $module, is the module slug.
383
				 *
384
				 * @since 1.9.0
385
				 *
386
				 * @param string $module Module slug.
387
				 */
388
				do_action( "jetpack_activate_module_$module", $module );
389
			}
390
391
			$new_deactive_modules = array_diff( $current_modules, $modules );
392
			foreach( $new_deactive_modules as $module ) {
393
				/**
394
				 * Fired after a module has been deactivated.
395
				 *
396
				 * @since 4.2.0
397
				 *
398
				 * @param string $module Module slug.
399
				 * @param boolean $success whether the module was deactivated.
400
				 */
401
				do_action( 'jetpack_deactivate_module', $module, $success );
402
				/**
403
				 * Fires when a module is deactivated.
404
				 * The dynamic part of the filter, $module, is the module slug.
405
				 *
406
				 * @since 1.9.0
407
				 *
408
				 * @param string $module Module slug.
409
				 */
410
				do_action( "jetpack_deactivate_module_$module", $module );
411
			}
412
		}
413
414
		return $success;
415
	}
416
417
	static function delete_active_modules() {
418
		self::update_active_modules( array() );
419
	}
420
421
	/**
422
	 * Constructor.  Initializes WordPress hooks
423
	 */
424
	private function __construct() {
425
		/*
426
		 * Check for and alert any deprecated hooks
427
		 */
428
		add_action( 'init', array( $this, 'deprecated_hooks' ) );
429
430
431
		/*
432
		 * Load things that should only be in Network Admin.
433
		 *
434
		 * For now blow away everything else until a more full
435
		 * understanding of what is needed at the network level is
436
		 * available
437
		 */
438
		if( is_multisite() ) {
439
			Jetpack_Network::init();
440
		}
441
442
		add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
443
444
		// Unlink user before deleting the user from .com
445
		add_action( 'deleted_user', array( $this, 'unlink_user' ), 10, 1 );
446
		add_action( 'remove_user_from_blog', array( $this, 'unlink_user' ), 10, 1 );
447
448
		if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && isset( $_GET['for'] ) && 'jetpack' == $_GET['for'] ) {
449
			@ini_set( 'display_errors', false ); // Display errors can cause the XML to be not well formed.
450
451
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
452
			$this->xmlrpc_server = new Jetpack_XMLRPC_Server();
453
454
			$this->require_jetpack_authentication();
455
456
			if ( Jetpack::is_active() ) {
457
				// Hack to preserve $HTTP_RAW_POST_DATA
458
				add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
459
460
				$signed = $this->verify_xml_rpc_signature();
461
				if ( $signed && ! is_wp_error( $signed ) ) {
462
					// The actual API methods.
463
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'xmlrpc_methods' ) );
464
				} else {
465
					// The jetpack.authorize method should be available for unauthenticated users on a site with an
466
					// active Jetpack connection, so that additional users can link their account.
467
					add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'authorize_xmlrpc_methods' ) );
468
				}
469
			} else {
470
				// The bootstrap API methods.
471
				add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'bootstrap_xmlrpc_methods' ) );
472
			}
473
474
			// Now that no one can authenticate, and we're whitelisting all XML-RPC methods, force enable_xmlrpc on.
475
			add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
476
		} elseif ( is_admin() && isset( $_POST['action'] ) && 'jetpack_upload_file' == $_POST['action'] ) {
477
			$this->require_jetpack_authentication();
478
			$this->add_remote_request_handlers();
479
		} else {
480
			if ( Jetpack::is_active() ) {
481
				add_action( 'login_form_jetpack_json_api_authorization', array( &$this, 'login_form_json_api_authorization' ) );
482
				add_filter( 'xmlrpc_methods', array( $this, 'public_xmlrpc_methods' ) );
483
			}
484
		}
485
486
		if ( Jetpack::is_active() ) {
487
			Jetpack_Heartbeat::init();
488
		}
489
490
		add_action( 'jetpack_clean_nonces', array( 'Jetpack', 'clean_nonces' ) );
491
		if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
492
			wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
493
		}
494
495
		add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ) );
496
497
		add_action( 'admin_init', array( $this, 'admin_init' ) );
498
		add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
499
500
		add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
501
502
		add_action( 'wp_dashboard_setup', array( $this, 'wp_dashboard_setup' ) );
503
		// Filter the dashboard meta box order to swap the new one in in place of the old one.
504
		add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) );
505
506
		// returns HTTPS support status
507
		add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) );
508
509
		// If any module option is updated before Jump Start is dismissed, hide Jump Start.
510
		add_action( 'update_option', array( $this, 'jumpstart_has_updated_module_option' ) );
511
512
		// JITM AJAX callback function
513
		add_action( 'wp_ajax_jitm_ajax',  array( $this, 'jetpack_jitm_ajax_callback' ) );
514
515
		// Universal ajax callback for all tracking events triggered via js
516
		add_action( 'wp_ajax_jetpack_tracks', array( $this, 'jetpack_admin_ajax_tracks_callback' ) );
517
518
		add_action( 'wp_loaded', array( $this, 'register_assets' ) );
519
		add_action( 'wp_enqueue_scripts', array( $this, 'devicepx' ) );
520
		add_action( 'customize_controls_enqueue_scripts', array( $this, 'devicepx' ) );
521
		add_action( 'admin_enqueue_scripts', array( $this, 'devicepx' ) );
522
523
		add_action( 'plugins_loaded', array( $this, 'extra_oembed_providers' ), 100 );
524
525
		/**
526
		 * These actions run checks to load additional files.
527
		 * They check for external files or plugins, so they need to run as late as possible.
528
		 */
529
		add_action( 'wp_head', array( $this, 'check_open_graph' ),       1 );
530
		add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ),     999 );
531
		add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
532
533
		add_filter( 'plugins_url',      array( 'Jetpack', 'maybe_min_asset' ),     1, 3 );
534
		add_filter( 'style_loader_tag', array( 'Jetpack', 'maybe_inline_style' ), 10, 2 );
535
536
		add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 4 );
537
538
		add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
539
		add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
540
541
		// A filter to control all just in time messages
542
		add_filter( 'jetpack_just_in_time_msgs', '__return_true' );
543
544
		// Update the Jetpack plan from API on heartbeats
545
		add_action( 'jetpack_heartbeat', array( $this, 'refresh_active_plan_from_wpcom' ) );
546
547
		/**
548
		 * This is the hack to concatinate all css files into one.
549
		 * For description and reasoning see the implode_frontend_css method
550
		 *
551
		 * Super late priority so we catch all the registered styles
552
		 */
553
		if( !is_admin() ) {
554
			add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first
555
			add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`
556
		}
557
	}
558
559
	function jetpack_admin_ajax_tracks_callback() {
560
		// Check for nonce
561
		if ( ! isset( $_REQUEST['tracksNonce'] ) || ! wp_verify_nonce( $_REQUEST['tracksNonce'], 'jp-tracks-ajax-nonce' ) ) {
562
			wp_die( 'Permissions check failed.' );
563
		}
564
565
		if ( ! isset( $_REQUEST['tracksEventName'] ) || ! isset( $_REQUEST['tracksEventType'] )  ) {
566
			wp_die( 'No valid event name or type.' );
567
		}
568
569
		$tracks_data = array();
570
		if ( 'click' === $_REQUEST['tracksEventType'] && isset( $_REQUEST['tracksEventProp'] ) ) {
571
			$tracks_data = array( 'clicked' => $_REQUEST['tracksEventProp'] );
572
		}
573
574
		JetpackTracking::record_user_event( $_REQUEST['tracksEventName'], $tracks_data );
575
		wp_send_json_success();
576
		wp_die();
577
	}
578
579
	/**
580
	 * The callback for the JITM ajax requests.
581
	 */
582
	function jetpack_jitm_ajax_callback() {
583
		// Check for nonce
584
		if ( ! isset( $_REQUEST['jitmNonce'] ) || ! wp_verify_nonce( $_REQUEST['jitmNonce'], 'jetpack-jitm-nonce' ) ) {
585
			wp_die( 'Module activation failed due to lack of appropriate permissions' );
586
		}
587
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'activate' == $_REQUEST['jitmActionToTake'] ) {
588
			$module_slug = $_REQUEST['jitmModule'];
589
			Jetpack::log( 'activate', $module_slug );
590
			Jetpack::activate_module( $module_slug, false, false );
591
			Jetpack::state( 'message', 'no_message' );
592
593
			//A Jetpack module is being activated through a JITM, track it
594
			$this->stat( 'jitm', $module_slug.'-activated-' . JETPACK__VERSION );
595
			$this->do_stats( 'server_side' );
596
597
			wp_send_json_success();
598
		}
599
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'dismiss' == $_REQUEST['jitmActionToTake'] ) {
600
			// get the hide_jitm options array
601
			$jetpack_hide_jitm = Jetpack_Options::get_option( 'hide_jitm' );
602
			$module_slug = $_REQUEST['jitmModule'];
603
604
			if( ! $jetpack_hide_jitm ) {
605
				$jetpack_hide_jitm = array(
606
					$module_slug => 'hide'
607
				);
608
			} else {
609
				$jetpack_hide_jitm[$module_slug] = 'hide';
610
			}
611
612
			Jetpack_Options::update_option( 'hide_jitm', $jetpack_hide_jitm );
613
614
			//jitm is being dismissed forever, track it
615
			$this->stat( 'jitm', $module_slug.'-dismissed-' . JETPACK__VERSION );
616
			$this->do_stats( 'server_side' );
617
618
			wp_send_json_success();
619
		}
620 View Code Duplication
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'launch' == $_REQUEST['jitmActionToTake'] ) {
621
			$module_slug = $_REQUEST['jitmModule'];
622
623
			// User went to WordPress.com, track this
624
			$this->stat( 'jitm', $module_slug.'-wordpress-tools-' . JETPACK__VERSION );
625
			$this->do_stats( 'server_side' );
626
627
			wp_send_json_success();
628
		}
629 View Code Duplication
		if ( isset( $_REQUEST['jitmActionToTake'] ) && 'viewed' == $_REQUEST['jitmActionToTake'] ) {
630
			$track = $_REQUEST['jitmModule'];
631
632
			// User is viewing JITM, track it.
633
			$this->stat( 'jitm', $track . '-viewed-' . JETPACK__VERSION );
634
			$this->do_stats( 'server_side' );
635
636
			wp_send_json_success();
637
		}
638
	}
639
640
	/**
641
	 * If there are any stats that need to be pushed, but haven't been, push them now.
642
	 */
643
	function __destruct() {
644
		if ( ! empty( $this->stats ) ) {
645
			$this->do_stats( 'server_side' );
646
		}
647
	}
648
649
	function jetpack_custom_caps( $caps, $cap, $user_id, $args ) {
650
		switch( $cap ) {
651
			case 'jetpack_connect' :
652
			case 'jetpack_reconnect' :
653
				if ( Jetpack::is_development_mode() ) {
654
					$caps = array( 'do_not_allow' );
655
					break;
656
				}
657
				/**
658
				 * Pass through. If it's not development mode, these should match disconnect.
659
				 * Let users disconnect if it's development mode, just in case things glitch.
660
				 */
661
			case 'jetpack_disconnect' :
662
				/**
663
				 * In multisite, can individual site admins manage their own connection?
664
				 *
665
				 * Ideally, this should be extracted out to a separate filter in the Jetpack_Network class.
666
				 */
667
				if ( is_multisite() && ! is_super_admin() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
668
					if ( ! Jetpack_Network::init()->get_option( 'sub-site-connection-override' ) ) {
669
						/**
670
						 * We need to update the option name -- it's terribly unclear which
671
						 * direction the override goes.
672
						 *
673
						 * @todo: Update the option name to `sub-sites-can-manage-own-connections`
674
						 */
675
						$caps = array( 'do_not_allow' );
676
						break;
677
					}
678
				}
679
680
				$caps = array( 'manage_options' );
681
				break;
682
			case 'jetpack_manage_modules' :
683
			case 'jetpack_activate_modules' :
684
			case 'jetpack_deactivate_modules' :
685
				$caps = array( 'manage_options' );
686
				break;
687
			case 'jetpack_configure_modules' :
688
				$caps = array( 'manage_options' );
689
				break;
690
			case 'jetpack_network_admin_page':
691
			case 'jetpack_network_settings_page':
692
				$caps = array( 'manage_network_plugins' );
693
				break;
694
			case 'jetpack_network_sites_page':
695
				$caps = array( 'manage_sites' );
696
				break;
697
			case 'jetpack_admin_page' :
698
				if ( Jetpack::is_development_mode() ) {
699
					$caps = array( 'manage_options' );
700
					break;
701
				} else {
702
					$caps = array( 'read' );
703
				}
704
				break;
705
			case 'jetpack_connect_user' :
706
				if ( Jetpack::is_development_mode() ) {
707
					$caps = array( 'do_not_allow' );
708
					break;
709
				}
710
				$caps = array( 'read' );
711
				break;
712
		}
713
		return $caps;
714
	}
715
716
	function require_jetpack_authentication() {
717
		// Don't let anyone authenticate
718
		$_COOKIE = array();
719
		remove_all_filters( 'authenticate' );
720
		remove_all_actions( 'wp_login_failed' );
721
722
		if ( Jetpack::is_active() ) {
723
			// Allow Jetpack authentication
724
			add_filter( 'authenticate', array( $this, 'authenticate_jetpack' ), 10, 3 );
725
		}
726
	}
727
728
	/**
729
	 * Load language files
730
	 * @action plugins_loaded
731
	 */
732
	public static function plugin_textdomain() {
733
		// Note to self, the third argument must not be hardcoded, to account for relocated folders.
734
		load_plugin_textdomain( 'jetpack', false, dirname( plugin_basename( JETPACK__PLUGIN_FILE ) ) . '/languages/' );
735
	}
736
737
	/**
738
	 * Register assets for use in various modules and the Jetpack admin page.
739
	 *
740
	 * @uses wp_script_is, wp_register_script, plugins_url
741
	 * @action wp_loaded
742
	 * @return null
743
	 */
744
	public function register_assets() {
745
		if ( ! wp_script_is( 'spin', 'registered' ) ) {
746
			wp_register_script( 'spin', plugins_url( '_inc/spin.js', JETPACK__PLUGIN_FILE ), false, '1.3' );
747
		}
748
749 View Code Duplication
		if ( ! wp_script_is( 'jquery.spin', 'registered' ) ) {
750
			wp_register_script( 'jquery.spin', plugins_url( '_inc/jquery.spin.js', JETPACK__PLUGIN_FILE ) , array( 'jquery', 'spin' ), '1.3' );
751
		}
752
753 View Code Duplication
		if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) ) {
754
			wp_register_script( 'jetpack-gallery-settings', plugins_url( '_inc/gallery-settings.js', JETPACK__PLUGIN_FILE ), array( 'media-views' ), '20121225' );
755
		}
756
757 View Code Duplication
		if ( ! wp_script_is( 'jetpack-twitter-timeline', 'registered' ) ) {
758
			wp_register_script( 'jetpack-twitter-timeline', plugins_url( '_inc/twitter-timeline.js', JETPACK__PLUGIN_FILE ) , array( 'jquery' ), '4.0.0', true );
759
		}
760
761
		if ( ! wp_script_is( 'jetpack-facebook-embed', 'registered' ) ) {
762
			wp_register_script( 'jetpack-facebook-embed', plugins_url( '_inc/facebook-embed.js', __FILE__ ), array( 'jquery' ), null, true );
763
764
			/** This filter is documented in modules/sharedaddy/sharing-sources.php */
765
			$fb_app_id = apply_filters( 'jetpack_sharing_facebook_app_id', '249643311490' );
766
			if ( ! is_numeric( $fb_app_id ) ) {
767
				$fb_app_id = '';
768
			}
769
			wp_localize_script(
770
				'jetpack-facebook-embed',
771
				'jpfbembed',
772
				array(
773
					'appid' => $fb_app_id,
774
					'locale' => $this->get_locale(),
775
				)
776
			);
777
		}
778
779
		/**
780
		 * As jetpack_register_genericons is by default fired off a hook,
781
		 * the hook may have already fired by this point.
782
		 * So, let's just trigger it manually.
783
		 */
784
		require_once( JETPACK__PLUGIN_DIR . '_inc/genericons.php' );
785
		jetpack_register_genericons();
786
787
		/**
788
		 * Register the social logos
789
		 */
790
		require_once( JETPACK__PLUGIN_DIR . '_inc/social-logos.php' );
791
		jetpack_register_social_logos();
792
793 View Code Duplication
		if ( ! wp_style_is( 'jetpack-icons', 'registered' ) )
794
			wp_register_style( 'jetpack-icons', plugins_url( 'css/jetpack-icons.min.css', JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
795
	}
796
797
	/**
798
	 * Guess locale from language code.
799
	 *
800
	 * @param string $lang Language code.
801
	 * @return string|bool
802
	 */
803 View Code Duplication
	function guess_locale_from_lang( $lang ) {
804
		if ( 'en' === $lang || 'en_US' === $lang || ! $lang ) {
805
			return 'en_US';
806
		}
807
808
		if ( ! class_exists( 'GP_Locales' ) ) {
809
			if ( ! defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || ! file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
810
				return false;
811
			}
812
813
			require JETPACK__GLOTPRESS_LOCALES_PATH;
814
		}
815
816
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
817
			// WP.com: get_locale() returns 'it'
818
			$locale = GP_Locales::by_slug( $lang );
819
		} else {
820
			// Jetpack: get_locale() returns 'it_IT';
821
			$locale = GP_Locales::by_field( 'facebook_locale', $lang );
822
		}
823
824
		if ( ! $locale ) {
825
			return false;
826
		}
827
828
		if ( empty( $locale->facebook_locale ) ) {
829
			if ( empty( $locale->wp_locale ) ) {
830
				return false;
831
			} else {
832
				// Facebook SDK is smart enough to fall back to en_US if a
833
				// locale isn't supported. Since supported Facebook locales
834
				// can fall out of sync, we'll attempt to use the known
835
				// wp_locale value and rely on said fallback.
836
				return $locale->wp_locale;
837
			}
838
		}
839
840
		return $locale->facebook_locale;
841
	}
842
843
	/**
844
	 * Get the locale.
845
	 *
846
	 * @return string|bool
847
	 */
848
	function get_locale() {
849
		$locale = $this->guess_locale_from_lang( get_locale() );
850
851
		if ( ! $locale ) {
852
			$locale = 'en_US';
853
		}
854
855
		return $locale;
856
	}
857
858
	/**
859
	 * Device Pixels support
860
	 * This improves the resolution of gravatars and wordpress.com uploads on hi-res and zoomed browsers.
861
	 */
862
	function devicepx() {
863
		if ( Jetpack::is_active() ) {
864
			wp_enqueue_script( 'devicepx', set_url_scheme( 'http://s0.wp.com/wp-content/js/devicepx-jetpack.js' ), array(), gmdate( 'oW' ), true );
865
		}
866
	}
867
868
	/**
869
	 * Return the network_site_url so that .com knows what network this site is a part of.
870
	 * @param  bool $option
871
	 * @return string
872
	 */
873
	public function jetpack_main_network_site_option( $option ) {
874
		return network_site_url();
875
	}
876
	/**
877
	 * Network Name.
878
	 */
879
	static function network_name( $option = null ) {
880
		global $current_site;
881
		return $current_site->site_name;
882
	}
883
	/**
884
	 * Does the network allow new user and site registrations.
885
	 * @return string
886
	 */
887
	static function network_allow_new_registrations( $option = null ) {
888
		return ( in_array( get_site_option( 'registration' ), array('none', 'user', 'blog', 'all' ) ) ? get_site_option( 'registration') : 'none' );
889
	}
890
	/**
891
	 * Does the network allow admins to add new users.
892
	 * @return boolian
893
	 */
894
	static function network_add_new_users( $option = null ) {
895
		return (bool) get_site_option( 'add_new_users' );
896
	}
897
	/**
898
	 * File upload psace left per site in MB.
899
	 *  -1 means NO LIMIT.
900
	 * @return number
901
	 */
902
	static function network_site_upload_space( $option = null ) {
903
		// value in MB
904
		return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
905
	}
906
907
	/**
908
	 * Network allowed file types.
909
	 * @return string
910
	 */
911
	static function network_upload_file_types( $option = null ) {
912
		return get_site_option( 'upload_filetypes', 'jpg jpeg png gif' );
913
	}
914
915
	/**
916
	 * Maximum file upload size set by the network.
917
	 * @return number
918
	 */
919
	static function network_max_upload_file_size( $option = null ) {
920
		// value in KB
921
		return get_site_option( 'fileupload_maxk', 300 );
922
	}
923
924
	/**
925
	 * Lets us know if a site allows admins to manage the network.
926
	 * @return array
927
	 */
928
	static function network_enable_administration_menus( $option = null ) {
929
		return get_site_option( 'menu_items' );
930
	}
931
932
	/**
933
	 * If a user has been promoted to or demoted from admin, we need to clear the
934
	 * jetpack_other_linked_admins transient.
935
	 *
936
	 * @since 4.3.2
937
	 * @since 4.4.0  $old_roles is null by default and if it's not passed, the transient is cleared.
938
	 *
939
	 * @param int    $user_id   The user ID whose role changed.
940
	 * @param string $role      The new role.
941
	 * @param array  $old_roles An array of the user's previous roles.
942
	 */
943
	function maybe_clear_other_linked_admins_transient( $user_id, $role, $old_roles = null ) {
944
		if ( 'administrator' == $role
945
			|| ( is_array( $old_roles ) && in_array( 'administrator', $old_roles ) )
946
			|| is_null( $old_roles )
947
		) {
948
			delete_transient( 'jetpack_other_linked_admins' );
949
		}
950
	}
951
952
	/**
953
	 * Checks to see if there are any other users available to become primary
954
	 * Users must both:
955
	 * - Be linked to wpcom
956
	 * - Be an admin
957
	 *
958
	 * @return mixed False if no other users are linked, Int if there are.
959
	 */
960
	static function get_other_linked_admins() {
961
		$other_linked_users = get_transient( 'jetpack_other_linked_admins' );
962
963
		if ( false === $other_linked_users ) {
964
			$admins = get_users( array( 'role' => 'administrator' ) );
965
			if ( count( $admins ) > 1 ) {
966
				$available = array();
967
				foreach ( $admins as $admin ) {
968
					if ( Jetpack::is_user_connected( $admin->ID ) ) {
969
						$available[] = $admin->ID;
970
					}
971
				}
972
973
				$count_connected_admins = count( $available );
974
				if ( count( $available ) > 1 ) {
975
					$other_linked_users = $count_connected_admins;
976
				} else {
977
					$other_linked_users = 0;
978
				}
979
			} else {
980
				$other_linked_users = 0;
981
			}
982
983
			set_transient( 'jetpack_other_linked_admins', $other_linked_users, HOUR_IN_SECONDS );
984
		}
985
986
		return ( 0 === $other_linked_users ) ? false : $other_linked_users;
987
	}
988
989
	/**
990
	 * Return whether we are dealing with a multi network setup or not.
991
	 * The reason we are type casting this is because we want to avoid the situation where
992
	 * the result is false since when is_main_network_option return false it cases
993
	 * the rest the get_option( 'jetpack_is_multi_network' ); to return the value that is set in the
994
	 * database which could be set to anything as opposed to what this function returns.
995
	 * @param  bool  $option
996
	 *
997
	 * @return boolean
998
	 */
999
	public function is_main_network_option( $option ) {
1000
		// return '1' or ''
1001
		return (string) (bool) Jetpack::is_multi_network();
1002
	}
1003
1004
	/**
1005
	 * Return true if we are with multi-site or multi-network false if we are dealing with single site.
1006
	 *
1007
	 * @param  string  $option
1008
	 * @return boolean
1009
	 */
1010
	public function is_multisite( $option ) {
1011
		return (string) (bool) is_multisite();
1012
	}
1013
1014
	/**
1015
	 * Implemented since there is no core is multi network function
1016
	 * Right now there is no way to tell if we which network is the dominant network on the system
1017
	 *
1018
	 * @since  3.3
1019
	 * @return boolean
1020
	 */
1021
	public static function is_multi_network() {
1022
		global  $wpdb;
1023
1024
		// if we don't have a multi site setup no need to do any more
1025
		if ( ! is_multisite() ) {
1026
			return false;
1027
		}
1028
1029
		$num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
1030
		if ( $num_sites > 1 ) {
1031
			return true;
1032
		} else {
1033
			return false;
1034
		}
1035
	}
1036
1037
	/**
1038
	 * Trigger an update to the main_network_site when we update the siteurl of a site.
1039
	 * @return null
1040
	 */
1041
	function update_jetpack_main_network_site_option() {
1042
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1043
	}
1044
	/**
1045
	 * Triggered after a user updates the network settings via Network Settings Admin Page
1046
	 *
1047
	 */
1048
	function update_jetpack_network_settings() {
1049
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1050
		// Only sync this info for the main network site.
1051
	}
1052
1053
	/**
1054
	 * Get back if the current site is single user site.
1055
	 *
1056
	 * @return bool
1057
	 */
1058
	public static function is_single_user_site() {
1059
		global $wpdb;
1060
1061 View Code Duplication
		if ( false === ( $some_users = get_transient( 'jetpack_is_single_user' ) ) ) {
1062
			$some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
1063
			set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
1064
		}
1065
		return 1 === (int) $some_users;
1066
	}
1067
1068
	/**
1069
	 * Returns true if the site has file write access false otherwise.
1070
	 * @return string ( '1' | '0' )
1071
	 **/
1072
	public static function file_system_write_access() {
1073
		if ( ! function_exists( 'get_filesystem_method' ) ) {
1074
			require_once( ABSPATH . 'wp-admin/includes/file.php' );
1075
		}
1076
1077
		require_once( ABSPATH . 'wp-admin/includes/template.php' );
1078
1079
		$filesystem_method = get_filesystem_method();
1080
		if ( $filesystem_method === 'direct' ) {
1081
			return 1;
1082
		}
1083
1084
		ob_start();
1085
		$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1086
		ob_end_clean();
1087
		if ( $filesystem_credentials_are_stored ) {
1088
			return 1;
1089
		}
1090
		return 0;
1091
	}
1092
1093
	/**
1094
	 * Finds out if a site is using a version control system.
1095
	 * @return string ( '1' | '0' )
1096
	 **/
1097
	public static function is_version_controlled() {
1098
		_deprecated_function( __METHOD__, 'jetpack-4.2', 'Jetpack_Sync_Functions::is_version_controlled' );
1099
		return (string) (int) Jetpack_Sync_Functions::is_version_controlled();
1100
	}
1101
1102
	/**
1103
	 * Determines whether the current theme supports featured images or not.
1104
	 * @return string ( '1' | '0' )
1105
	 */
1106
	public static function featured_images_enabled() {
1107
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1108
		return current_theme_supports( 'post-thumbnails' ) ? '1' : '0';
1109
	}
1110
1111
	/**
1112
	 * jetpack_updates is saved in the following schema:
1113
	 *
1114
	 * array (
1115
	 *      'plugins'                       => (int) Number of plugin updates available.
1116
	 *      'themes'                        => (int) Number of theme updates available.
1117
	 *      'wordpress'                     => (int) Number of WordPress core updates available.
1118
	 *      'translations'                  => (int) Number of translation updates available.
1119
	 *      'total'                         => (int) Total of all available updates.
1120
	 *      'wp_update_version'             => (string) The latest available version of WordPress, only present if a WordPress update is needed.
1121
	 * )
1122
	 * @return array
1123
	 */
1124
	public static function get_updates() {
1125
		$update_data = wp_get_update_data();
1126
1127
		// Stores the individual update counts as well as the total count.
1128
		if ( isset( $update_data['counts'] ) ) {
1129
			$updates = $update_data['counts'];
1130
		}
1131
1132
		// If we need to update WordPress core, let's find the latest version number.
1133 View Code Duplication
		if ( ! empty( $updates['wordpress'] ) ) {
1134
			$cur = get_preferred_from_update_core();
1135
			if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
1136
				$updates['wp_update_version'] = $cur->current;
1137
			}
1138
		}
1139
		return isset( $updates ) ? $updates : array();
1140
	}
1141
1142
	public static function get_update_details() {
1143
		$update_details = array(
1144
			'update_core' => get_site_transient( 'update_core' ),
1145
			'update_plugins' => get_site_transient( 'update_plugins' ),
1146
			'update_themes' => get_site_transient( 'update_themes' ),
1147
		);
1148
		return $update_details;
1149
	}
1150
1151
	public static function refresh_update_data() {
1152
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1153
1154
	}
1155
1156
	public static function refresh_theme_data() {
1157
		_deprecated_function( __METHOD__, 'jetpack-4.2' );
1158
	}
1159
1160
	/**
1161
	 * Is Jetpack active?
1162
	 */
1163
	public static function is_active() {
1164
		return (bool) Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1165
	}
1166
1167
	/**
1168
	 * Make an API call to WordPress.com for plan status
1169
	 *
1170
	 * @uses Jetpack_Options::get_option()
1171
	 * @uses Jetpack_Client::wpcom_json_api_request_as_blog()
1172
	 * @uses update_option()
1173
	 *
1174
	 * @access public
1175
	 * @static
1176
	 *
1177
	 * @return bool True if plan is updated, false if no update
1178
	 */
1179
	public static function refresh_active_plan_from_wpcom() {
1180
		// Make the API request
1181
		$request = sprintf( '/sites/%d', Jetpack_Options::get_option( 'id' ) );
1182
		$response = Jetpack_Client::wpcom_json_api_request_as_blog( $request, '1.1' );
1183
1184
		// Bail if there was an error or malformed response
1185
		if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
1186
			return false;
1187
		}
1188
1189
		// Decode the results
1190
		$results = json_decode( $response['body'], true );
1191
1192
		// Bail if there were no results or plan details returned
1193
		if ( ! is_array( $results ) || ! isset( $results['plan'] ) ) {
1194
			return false;
1195
		}
1196
1197
		// Store the option and return true if updated
1198
		return update_option( 'jetpack_active_plan', $results['plan'] );
1199
	}
1200
1201
	/**
1202
	 * Get the plan that this Jetpack site is currently using
1203
	 *
1204
	 * @uses get_option()
1205
	 *
1206
	 * @access public
1207
	 * @static
1208
	 *
1209
	 * @return array Active Jetpack plan details
1210
	 */
1211
	public static function get_active_plan() {
1212
		$plan = get_option( 'jetpack_active_plan' );
1213
1214
		// Set the default options
1215
		if ( ! $plan ) {
1216
			$plan = array( 
1217
				'product_slug' => 'jetpack_free', 
1218
				'supports' => array(), 
1219
			);
1220
		}
1221
1222
		// Define what paid modules are supported by personal plans
1223
		$personal_plans = array( 
1224
			'jetpack_personal',
1225
			'jetpack_personal_monthly',
1226
		);
1227
1228
		if ( in_array( $plan['product_slug'], $personal_plans ) ) {
1229
			$plan['supports'] = array(
1230
				'akismet',
1231
			);
1232
		}
1233
1234
		// Define what paid modules are supported by premium plans
1235
		$premium_plans = array(
1236
			'jetpack_premium',
1237
			'jetpack_premium_monthly',
1238
		);
1239
1240 View Code Duplication
		if ( in_array( $plan['product_slug'], $premium_plans ) ) {
1241
			$plan['supports'] = array(
1242
				'videopress',
1243
				'akismet',
1244
				'vaultpress',
1245
			);
1246
		}
1247
1248
		// Define what paid modules are supported by professional plans
1249
		$business_plans = array(
1250
			'jetpack_business',
1251
			'jetpack_business_monthly',
1252
		);
1253
1254 View Code Duplication
		if ( in_array( $plan['product_slug'], $business_plans ) ) {
1255
			$plan['supports'] = array(
1256
				'videopress',
1257
				'akismet',
1258
				'vaultpress',
1259
				'seo-tools',
1260
			);
1261
		}
1262
1263
		// Make sure we have an array here in the event database data is stale
1264
		if ( ! isset( $plan['supports'] ) ) {
1265
			$plan['supports'] = array();
1266
		}
1267
1268
		return $plan;
1269
	}
1270
1271
	/**
1272
	 * Determine whether the active plan supports a particular feature
1273
	 *
1274
	 * @uses Jetpack::get_active_plan()
1275
	 *
1276
	 * @access public
1277
	 * @static
1278
	 *
1279
	 * @return bool True if plan supports feature, false if not
1280
	 */
1281
	public static function active_plan_supports( $feature ) {
1282
		$plan = Jetpack::get_active_plan();
1283
1284
		if ( in_array( $feature, $plan['supports'] ) ) {
1285
			return true;
1286
		}
1287
1288
		return false;
1289
	}
1290
1291
	/**
1292
	 * Is Jetpack in development (offline) mode?
1293
	 */
1294
	public static function is_development_mode() {
1295
		$development_mode = false;
1296
1297
		if ( defined( 'JETPACK_DEV_DEBUG' ) ) {
1298
			$development_mode = JETPACK_DEV_DEBUG;
1299
		}
1300
1301
		elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1302
			$development_mode = true;
1303
		}
1304
		/**
1305
		 * Filters Jetpack's development mode.
1306
		 *
1307
		 * @see https://jetpack.com/support/development-mode/
1308
		 *
1309
		 * @since 2.2.1
1310
		 *
1311
		 * @param bool $development_mode Is Jetpack's development mode active.
1312
		 */
1313
		return apply_filters( 'jetpack_development_mode', $development_mode );
1314
	}
1315
1316
	/**
1317
	* Get Jetpack development mode notice text and notice class.
1318
	*
1319
	* Mirrors the checks made in Jetpack::is_development_mode
1320
	*
1321
	*/
1322
	public static function show_development_mode_notice() {
1323
		if ( Jetpack::is_development_mode() ) {
1324
			if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
1325
				$notice = sprintf(
1326
					/* translators: %s is a URL */
1327
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the JETPACK_DEV_DEBUG constant being defined in wp-config.php or elsewhere.', 'jetpack' ),
1328
					'https://jetpack.com/support/development-mode/'
1329
				);
1330
			} elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
1331
				$notice = sprintf(
1332
					/* translators: %s is a URL */
1333
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via site URL lacking a dot (e.g. http://localhost).', 'jetpack' ),
1334
					'https://jetpack.com/support/development-mode/'
1335
				);
1336
			} else {
1337
				$notice = sprintf(
1338
					/* translators: %s is a URL */
1339
					__( 'In <a href="%s" target="_blank">Development Mode</a>, via the jetpack_development_mode filter.', 'jetpack' ),
1340
					'https://jetpack.com/support/development-mode/'
1341
				);
1342
			}
1343
1344
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1345
		}
1346
1347
		// Throw up a notice if using a development version and as for feedback.
1348
		if ( Jetpack::is_development_version() ) {
1349
			/* translators: %s is a URL */
1350
			$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/' );
1351
1352
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1353
		}
1354
		// Throw up a notice if using staging mode
1355
		if ( Jetpack::is_staging_site() ) {
1356
			/* translators: %s is a URL */
1357
			$notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), 'https://jetpack.com/support/staging-sites/' );
1358
1359
			echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>';
1360
		}
1361
	}
1362
1363
	/**
1364
	 * Whether Jetpack's version maps to a public release, or a development version.
1365
	 */
1366
	public static function is_development_version() {
1367
		/**
1368
		 * Allows filtering whether this is a development version of Jetpack.
1369
		 *
1370
		 * This filter is especially useful for tests.
1371
		 *
1372
		 * @since 4.3.0
1373
		 *
1374
		 * @param bool $development_version Is this a develoment version of Jetpack?
1375
		 */
1376
		return (bool) apply_filters(
1377
			'jetpack_development_version',
1378
			! preg_match( '/^\d+(\.\d+)+$/', Jetpack_Constants::get_constant( 'JETPACK__VERSION' ) )
1379
		);
1380
	}
1381
1382
	/**
1383
	 * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1384
	 */
1385
	public static function is_user_connected( $user_id = false ) {
1386
		$user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
1387
		if ( ! $user_id ) {
1388
			return false;
1389
		}
1390
1391
		return (bool) Jetpack_Data::get_access_token( $user_id );
1392
	}
1393
1394
	/**
1395
	 * Get the wpcom user data of the current|specified connected user.
1396
	 */
1397
	public static function get_connected_user_data( $user_id = null ) {
1398
		if ( ! $user_id ) {
1399
			$user_id = get_current_user_id();
1400
		}
1401
1402
		$transient_key = "jetpack_connected_user_data_$user_id";
1403
1404
		if ( $cached_user_data = get_transient( $transient_key ) ) {
1405
			return $cached_user_data;
1406
		}
1407
1408
		Jetpack::load_xml_rpc_client();
1409
		$xml = new Jetpack_IXR_Client( array(
1410
			'user_id' => $user_id,
1411
		) );
1412
		$xml->query( 'wpcom.getUser' );
1413
		if ( ! $xml->isError() ) {
1414
			$user_data = $xml->getResponse();
1415
			set_transient( $transient_key, $xml->getResponse(), DAY_IN_SECONDS );
1416
			return $user_data;
1417
		}
1418
1419
		return false;
1420
	}
1421
1422
	/**
1423
	 * Get the wpcom email of the current|specified connected user.
1424
	 */
1425 View Code Duplication
	public static function get_connected_user_email( $user_id = null ) {
1426
		if ( ! $user_id ) {
1427
			$user_id = get_current_user_id();
1428
		}
1429
		Jetpack::load_xml_rpc_client();
1430
		$xml = new Jetpack_IXR_Client( array(
1431
			'user_id' => $user_id,
1432
		) );
1433
		$xml->query( 'wpcom.getUserEmail' );
1434
		if ( ! $xml->isError() ) {
1435
			return $xml->getResponse();
1436
		}
1437
		return false;
1438
	}
1439
1440
	/**
1441
	 * Get the wpcom email of the master user.
1442
	 */
1443
	public static function get_master_user_email() {
1444
		$master_user_id = Jetpack_Options::get_option( 'master_user' );
1445
		if ( $master_user_id ) {
1446
			return self::get_connected_user_email( $master_user_id );
1447
		}
1448
		return '';
1449
	}
1450
1451
	function current_user_is_connection_owner() {
1452
		$user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
1453
		return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && get_current_user_id() === $user_token->external_user_id;
1454
	}
1455
1456
	/**
1457
	 * Add any extra oEmbed providers that we know about and use on wpcom for feature parity.
1458
	 */
1459
	function extra_oembed_providers() {
1460
		// Cloudup: https://dev.cloudup.com/#oembed
1461
		wp_oembed_add_provider( 'https://cloudup.com/*' , 'https://cloudup.com/oembed' );
1462
		wp_oembed_add_provider( 'https://me.sh/*', 'https://me.sh/oembed?format=json' );
1463
		wp_oembed_add_provider( '#https?://(www\.)?gfycat\.com/.*#i', 'https://api.gfycat.com/v1/oembed', true );
1464
		wp_oembed_add_provider( '#https?://[^.]+\.(wistia\.com|wi\.st)/(medias|embed)/.*#', 'https://fast.wistia.com/oembed', true );
1465
		wp_oembed_add_provider( '#https?://sketchfab\.com/.*#i', 'https://sketchfab.com/oembed', true );
1466
	}
1467
1468
	/**
1469
	 * Synchronize connected user role changes
1470
	 */
1471
	function user_role_change( $user_id ) {
1472
		_deprecated_function( __METHOD__, 'jetpack-4.2', 'Jetpack_Sync_Users::user_role_change()' );
1473
		Jetpack_Sync_Users::user_role_change( $user_id );
1474
	}
1475
1476
	/**
1477
	 * Loads the currently active modules.
1478
	 */
1479
	public static function load_modules() {
1480
		if ( ! self::is_active() && !self::is_development_mode() ) {
1481
			if ( ! is_multisite() || ! get_site_option( 'jetpack_protect_active' ) ) {
1482
				return;
1483
			}
1484
		}
1485
1486
		$version = Jetpack_Options::get_option( 'version' );
1487 View Code Duplication
		if ( ! $version ) {
1488
			$version = $old_version = JETPACK__VERSION . ':' . time();
1489
			/** This action is documented in class.jetpack.php */
1490
			do_action( 'updating_jetpack_version', $version, false );
1491
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1492
		}
1493
		list( $version ) = explode( ':', $version );
1494
1495
		$modules = array_filter( Jetpack::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1496
1497
		$modules_data = array();
1498
1499
		// Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1500
		if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1501
			$updated_modules = array();
1502
			foreach ( $modules as $module ) {
1503
				$modules_data[ $module ] = Jetpack::get_module( $module );
1504
				if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1505
					continue;
1506
				}
1507
1508
				if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1509
					continue;
1510
				}
1511
1512
				$updated_modules[] = $module;
1513
			}
1514
1515
			$modules = array_diff( $modules, $updated_modules );
1516
		}
1517
1518
		$is_development_mode = Jetpack::is_development_mode();
1519
1520
		foreach ( $modules as $index => $module ) {
1521
			// If we're in dev mode, disable modules requiring a connection
1522
			if ( $is_development_mode ) {
1523
				// Prime the pump if we need to
1524
				if ( empty( $modules_data[ $module ] ) ) {
1525
					$modules_data[ $module ] = Jetpack::get_module( $module );
1526
				}
1527
				// If the module requires a connection, but we're in local mode, don't include it.
1528
				if ( $modules_data[ $module ]['requires_connection'] ) {
1529
					continue;
1530
				}
1531
			}
1532
1533
			if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1534
				continue;
1535
			}
1536
1537
			if ( ! @include( Jetpack::get_module_path( $module ) ) ) {
1538
				unset( $modules[ $index ] );
1539
				self::update_active_modules( array_values( $modules ) );
1540
				continue;
1541
			}
1542
1543
			/**
1544
			 * Fires when a specific module is loaded.
1545
			 * The dynamic part of the hook, $module, is the module slug.
1546
			 *
1547
			 * @since 1.1.0
1548
			 */
1549
			do_action( 'jetpack_module_loaded_' . $module );
1550
		}
1551
1552
		/**
1553
		 * Fires when all the modules are loaded.
1554
		 *
1555
		 * @since 1.1.0
1556
		 */
1557
		do_action( 'jetpack_modules_loaded' );
1558
1559
		// 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.
1560
		if ( Jetpack::is_active() || Jetpack::is_development_mode() )
1561
			require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' );
1562
	}
1563
1564
	/**
1565
	 * Check if Jetpack's REST API compat file should be included
1566
	 * @action plugins_loaded
1567
	 * @return null
1568
	 */
1569
	public function check_rest_api_compat() {
1570
		/**
1571
		 * Filters the list of REST API compat files to be included.
1572
		 *
1573
		 * @since 2.2.5
1574
		 *
1575
		 * @param array $args Array of REST API compat files to include.
1576
		 */
1577
		$_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1578
1579
		if ( function_exists( 'bbpress' ) )
1580
			$_jetpack_rest_api_compat_includes[] = JETPACK__PLUGIN_DIR . 'class.jetpack-bbpress-json-api-compat.php';
1581
1582
		foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include )
1583
			require_once $_jetpack_rest_api_compat_include;
1584
	}
1585
1586
	/**
1587
	 * Gets all plugins currently active in values, regardless of whether they're
1588
	 * traditionally activated or network activated.
1589
	 *
1590
	 * @todo Store the result in core's object cache maybe?
1591
	 */
1592
	public static function get_active_plugins() {
1593
		$active_plugins = (array) get_option( 'active_plugins', array() );
1594
1595
		if ( is_multisite() ) {
1596
			// Due to legacy code, active_sitewide_plugins stores them in the keys,
1597
			// whereas active_plugins stores them in the values.
1598
			$network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1599
			if ( $network_plugins ) {
1600
				$active_plugins = array_merge( $active_plugins, $network_plugins );
1601
			}
1602
		}
1603
1604
		sort( $active_plugins );
1605
1606
		return array_unique( $active_plugins );
1607
	}
1608
1609
	/**
1610
	 * Gets and parses additional plugin data to send with the heartbeat data
1611
	 *
1612
	 * @since 3.8.1
1613
	 *
1614
	 * @return array Array of plugin data
1615
	 */
1616
	public static function get_parsed_plugin_data() {
1617
		if ( ! function_exists( 'get_plugins' ) ) {
1618
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
1619
		}
1620
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
1621
		$all_plugins    = apply_filters( 'all_plugins', get_plugins() );
1622
		$active_plugins = Jetpack::get_active_plugins();
1623
1624
		$plugins = array();
1625
		foreach ( $all_plugins as $path => $plugin_data ) {
1626
			$plugins[ $path ] = array(
1627
					'is_active' => in_array( $path, $active_plugins ),
1628
					'file'      => $path,
1629
					'name'      => $plugin_data['Name'],
1630
					'version'   => $plugin_data['Version'],
1631
					'author'    => $plugin_data['Author'],
1632
			);
1633
		}
1634
1635
		return $plugins;
1636
	}
1637
1638
	/**
1639
	 * Gets and parses theme data to send with the heartbeat data
1640
	 *
1641
	 * @since 3.8.1
1642
	 *
1643
	 * @return array Array of theme data
1644
	 */
1645
	public static function get_parsed_theme_data() {
1646
		$all_themes = wp_get_themes( array( 'allowed' => true ) );
1647
		$header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
1648
1649
		$themes = array();
1650
		foreach ( $all_themes as $slug => $theme_data ) {
1651
			$theme_headers = array();
1652
			foreach ( $header_keys as $header_key ) {
1653
				$theme_headers[ $header_key ] = $theme_data->get( $header_key );
1654
			}
1655
1656
			$themes[ $slug ] = array(
1657
					'is_active_theme' => $slug == wp_get_theme()->get_template(),
1658
					'slug' => $slug,
1659
					'theme_root' => $theme_data->get_theme_root_uri(),
1660
					'parent' => $theme_data->parent(),
1661
					'headers' => $theme_headers
1662
			);
1663
		}
1664
1665
		return $themes;
1666
	}
1667
1668
	/**
1669
	 * Checks whether a specific plugin is active.
1670
	 *
1671
	 * We don't want to store these in a static variable, in case
1672
	 * there are switch_to_blog() calls involved.
1673
	 */
1674
	public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
1675
		return in_array( $plugin, self::get_active_plugins() );
1676
	}
1677
1678
	/**
1679
	 * Check if Jetpack's Open Graph tags should be used.
1680
	 * If certain plugins are active, Jetpack's og tags are suppressed.
1681
	 *
1682
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1683
	 * @action plugins_loaded
1684
	 * @return null
1685
	 */
1686
	public function check_open_graph() {
1687
		if ( in_array( 'publicize', Jetpack::get_active_modules() ) || in_array( 'sharedaddy', Jetpack::get_active_modules() ) ) {
1688
			add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
1689
		}
1690
1691
		$active_plugins = self::get_active_plugins();
1692
1693
		if ( ! empty( $active_plugins ) ) {
1694
			foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
1695
				if ( in_array( $plugin, $active_plugins ) ) {
1696
					add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
1697
					break;
1698
				}
1699
			}
1700
		}
1701
1702
		/**
1703
		 * Allow the addition of Open Graph Meta Tags to all pages.
1704
		 *
1705
		 * @since 2.0.3
1706
		 *
1707
		 * @param bool false Should Open Graph Meta tags be added. Default to false.
1708
		 */
1709
		if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
1710
			require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
1711
		}
1712
	}
1713
1714
	/**
1715
	 * Check if Jetpack's Twitter tags should be used.
1716
	 * If certain plugins are active, Jetpack's twitter tags are suppressed.
1717
	 *
1718
	 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1719
	 * @action plugins_loaded
1720
	 * @return null
1721
	 */
1722
	public function check_twitter_tags() {
1723
1724
		$active_plugins = self::get_active_plugins();
1725
1726
		if ( ! empty( $active_plugins ) ) {
1727
			foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
1728
				if ( in_array( $plugin, $active_plugins ) ) {
1729
					add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
1730
					break;
1731
				}
1732
			}
1733
		}
1734
1735
		/**
1736
		 * Allow Twitter Card Meta tags to be disabled.
1737
		 *
1738
		 * @since 2.6.0
1739
		 *
1740
		 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
1741
		 */
1742
		if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
1743
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
1744
		}
1745
	}
1746
1747
	/**
1748
	 * Allows plugins to submit security reports.
1749
 	 *
1750
	 * @param string  $type         Report type (login_form, backup, file_scanning, spam)
1751
	 * @param string  $plugin_file  Plugin __FILE__, so that we can pull plugin data
1752
	 * @param array   $args         See definitions above
1753
	 */
1754
	public static function submit_security_report( $type = '', $plugin_file = '', $args = array() ) {
1755
		_deprecated_function( __FUNCTION__, 'jetpack-4.2', null );
1756
	}
1757
1758
/* Jetpack Options API */
1759
1760
	public static function get_option_names( $type = 'compact' ) {
1761
		return Jetpack_Options::get_option_names( $type );
1762
	}
1763
1764
	/**
1765
	 * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
1766
 	 *
1767
	 * @param string $name    Option name
1768
	 * @param mixed  $default (optional)
1769
	 */
1770
	public static function get_option( $name, $default = false ) {
1771
		return Jetpack_Options::get_option( $name, $default );
1772
	}
1773
1774
	/**
1775
	* Stores two secrets and a timestamp so WordPress.com can make a request back and verify an action
1776
	* Does some extra verification so urls (such as those to public-api, register, etc) can't just be crafted
1777
	* $name must be a registered option name.
1778
	*/
1779
	public static function create_nonce( $name ) {
1780
		$secret = wp_generate_password( 32, false ) . ':' . wp_generate_password( 32, false ) . ':' . ( time() + 600 );
1781
1782
		Jetpack_Options::update_option( $name, $secret );
1783
		@list( $secret_1, $secret_2, $eol ) = explode( ':', Jetpack_Options::get_option( $name ) );
1784
		if ( empty( $secret_1 ) || empty( $secret_2 ) || $eol < time() )
1785
			return new Jetpack_Error( 'missing_secrets' );
1786
1787
		return array(
1788
			'secret_1' => $secret_1,
1789
			'secret_2' => $secret_2,
1790
			'eol'      => $eol,
1791
		);
1792
	}
1793
1794
	/**
1795
	 * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
1796
 	 *
1797
	 * @deprecated 3.4 use Jetpack_Options::update_option() instead.
1798
	 * @param string $name  Option name
1799
	 * @param mixed  $value Option value
1800
	 */
1801
	public static function update_option( $name, $value ) {
1802
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_option()' );
1803
		return Jetpack_Options::update_option( $name, $value );
1804
	}
1805
1806
	/**
1807
	 * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
1808
 	 *
1809
	 * @deprecated 3.4 use Jetpack_Options::update_options() instead.
1810
	 * @param array $array array( option name => option value, ... )
1811
	 */
1812
	public static function update_options( $array ) {
1813
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::update_options()' );
1814
		return Jetpack_Options::update_options( $array );
1815
	}
1816
1817
	/**
1818
	 * Deletes the given option.  May be passed multiple option names as an array.
1819
	 * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
1820
	 *
1821
	 * @deprecated 3.4 use Jetpack_Options::delete_option() instead.
1822
	 * @param string|array $names
1823
	 */
1824
	public static function delete_option( $names ) {
1825
		_deprecated_function( __METHOD__, 'jetpack-3.4', 'Jetpack_Options::delete_option()' );
1826
		return Jetpack_Options::delete_option( $names );
1827
	}
1828
1829
	/**
1830
	 * Enters a user token into the user_tokens option
1831
	 *
1832
	 * @param int $user_id
1833
	 * @param string $token
1834
	 * return bool
1835
	 */
1836
	public static function update_user_token( $user_id, $token, $is_master_user ) {
1837
		// not designed for concurrent updates
1838
		$user_tokens = Jetpack_Options::get_option( 'user_tokens' );
1839
		if ( ! is_array( $user_tokens ) )
1840
			$user_tokens = array();
1841
		$user_tokens[$user_id] = $token;
1842
		if ( $is_master_user ) {
1843
			$master_user = $user_id;
1844
			$options     = compact( 'user_tokens', 'master_user' );
1845
		} else {
1846
			$options = compact( 'user_tokens' );
1847
		}
1848
		return Jetpack_Options::update_options( $options );
1849
	}
1850
1851
	/**
1852
	 * Returns an array of all PHP files in the specified absolute path.
1853
	 * Equivalent to glob( "$absolute_path/*.php" ).
1854
	 *
1855
	 * @param string $absolute_path The absolute path of the directory to search.
1856
	 * @return array Array of absolute paths to the PHP files.
1857
	 */
1858
	public static function glob_php( $absolute_path ) {
1859
		if ( function_exists( 'glob' ) ) {
1860
			return glob( "$absolute_path/*.php" );
1861
		}
1862
1863
		$absolute_path = untrailingslashit( $absolute_path );
1864
		$files = array();
1865
		if ( ! $dir = @opendir( $absolute_path ) ) {
1866
			return $files;
1867
		}
1868
1869
		while ( false !== $file = readdir( $dir ) ) {
1870
			if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
1871
				continue;
1872
			}
1873
1874
			$file = "$absolute_path/$file";
1875
1876
			if ( ! is_file( $file ) ) {
1877
				continue;
1878
			}
1879
1880
			$files[] = $file;
1881
		}
1882
1883
		closedir( $dir );
1884
1885
		return $files;
1886
	}
1887
1888
	public static function activate_new_modules( $redirect = false ) {
1889
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
1890
			return;
1891
		}
1892
1893
		$jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
1894 View Code Duplication
		if ( ! $jetpack_old_version ) {
1895
			$jetpack_old_version = $version = $old_version = '1.1:' . time();
1896
			/** This action is documented in class.jetpack.php */
1897
			do_action( 'updating_jetpack_version', $version, false );
1898
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1899
		}
1900
1901
		list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
1902
1903
		if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
1904
			return;
1905
		}
1906
1907
		$active_modules     = Jetpack::get_active_modules();
1908
		$reactivate_modules = array();
1909
		foreach ( $active_modules as $active_module ) {
1910
			$module = Jetpack::get_module( $active_module );
1911
			if ( ! isset( $module['changed'] ) ) {
1912
				continue;
1913
			}
1914
1915
			if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
1916
				continue;
1917
			}
1918
1919
			$reactivate_modules[] = $active_module;
1920
			Jetpack::deactivate_module( $active_module );
1921
		}
1922
1923
		$new_version = JETPACK__VERSION . ':' . time();
1924
		/** This action is documented in class.jetpack.php */
1925
		do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
1926
		Jetpack_Options::update_options(
1927
			array(
1928
				'version'     => $new_version,
1929
				'old_version' => $jetpack_old_version,
1930
			)
1931
		);
1932
1933
		Jetpack::state( 'message', 'modules_activated' );
1934
		Jetpack::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules );
1935
1936
		if ( $redirect ) {
1937
			$page = 'jetpack'; // make sure we redirect to either settings or the jetpack page
1938
			if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ) ) ) {
1939
				$page = $_GET['page'];
1940
			}
1941
1942
			wp_safe_redirect( Jetpack::admin_url( 'page=' . $page ) );
1943
			exit;
1944
		}
1945
	}
1946
1947
	/**
1948
	 * List available Jetpack modules. Simply lists .php files in /modules/.
1949
	 * Make sure to tuck away module "library" files in a sub-directory.
1950
	 */
1951
	public static function get_available_modules( $min_version = false, $max_version = false ) {
1952
		static $modules = null;
1953
1954
		if ( ! isset( $modules ) ) {
1955
			$available_modules_option = Jetpack_Options::get_option( 'available_modules', array() );
1956
			// Use the cache if we're on the front-end and it's available...
1957
			if ( ! is_admin() && ! empty( $available_modules_option[ JETPACK__VERSION ] ) ) {
1958
				$modules = $available_modules_option[ JETPACK__VERSION ];
1959
			} else {
1960
				$files = Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
1961
1962
				$modules = array();
1963
1964
				foreach ( $files as $file ) {
1965
					if ( ! $headers = Jetpack::get_module( $file ) ) {
1966
						continue;
1967
					}
1968
1969
					$modules[ Jetpack::get_module_slug( $file ) ] = $headers['introduced'];
1970
				}
1971
1972
				Jetpack_Options::update_option( 'available_modules', array(
1973
					JETPACK__VERSION => $modules,
1974
				) );
1975
			}
1976
		}
1977
1978
		/**
1979
		 * Filters the array of modules available to be activated.
1980
		 *
1981
		 * @since 2.4.0
1982
		 *
1983
		 * @param array $modules Array of available modules.
1984
		 * @param string $min_version Minimum version number required to use modules.
1985
		 * @param string $max_version Maximum version number required to use modules.
1986
		 */
1987
		$mods = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version );
1988
1989
		if ( ! $min_version && ! $max_version ) {
1990
			return array_keys( $mods );
1991
		}
1992
1993
		$r = array();
1994
		foreach ( $mods as $slug => $introduced ) {
1995
			if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
1996
				continue;
1997
			}
1998
1999
			if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
2000
				continue;
2001
			}
2002
2003
			$r[] = $slug;
2004
		}
2005
2006
		return $r;
2007
	}
2008
2009
	/**
2010
	 * Default modules loaded on activation.
2011
	 */
2012
	public static function get_default_modules( $min_version = false, $max_version = false ) {
2013
		$return = array();
2014
2015
		foreach ( Jetpack::get_available_modules( $min_version, $max_version ) as $module ) {
2016
			$module_data = Jetpack::get_module( $module );
2017
2018
			switch ( strtolower( $module_data['auto_activate'] ) ) {
2019
				case 'yes' :
2020
					$return[] = $module;
2021
					break;
2022
				case 'public' :
2023
					if ( Jetpack_Options::get_option( 'public' ) ) {
2024
						$return[] = $module;
2025
					}
2026
					break;
2027
				case 'no' :
2028
				default :
2029
					break;
2030
			}
2031
		}
2032
		/**
2033
		 * Filters the array of default modules.
2034
		 *
2035
		 * @since 2.5.0
2036
		 *
2037
		 * @param array $return Array of default modules.
2038
		 * @param string $min_version Minimum version number required to use modules.
2039
		 * @param string $max_version Maximum version number required to use modules.
2040
		 */
2041
		return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version );
2042
	}
2043
2044
	/**
2045
	 * Checks activated modules during auto-activation to determine
2046
	 * if any of those modules are being deprecated.  If so, close
2047
	 * them out, and add any replacement modules.
2048
	 *
2049
	 * Runs at priority 99 by default.
2050
	 *
2051
	 * This is run late, so that it can still activate a module if
2052
	 * the new module is a replacement for another that the user
2053
	 * currently has active, even if something at the normal priority
2054
	 * would kibosh everything.
2055
	 *
2056
	 * @since 2.6
2057
	 * @uses jetpack_get_default_modules filter
2058
	 * @param array $modules
2059
	 * @return array
2060
	 */
2061
	function handle_deprecated_modules( $modules ) {
2062
		$deprecated_modules = array(
2063
			'debug'            => null,  // Closed out and moved to ./class.jetpack-debugger.php
2064
			'wpcc'             => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2065
			'gplus-authorship' => null,  // Closed out in 3.2 -- Google dropped support.
2066
		);
2067
2068
		// Don't activate SSO if they never completed activating WPCC.
2069
		if ( Jetpack::is_module_active( 'wpcc' ) ) {
2070
			$wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2071
			if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2072
				$deprecated_modules['wpcc'] = null;
2073
			}
2074
		}
2075
2076
		foreach ( $deprecated_modules as $module => $replacement ) {
2077
			if ( Jetpack::is_module_active( $module ) ) {
2078
				self::deactivate_module( $module );
2079
				if ( $replacement ) {
2080
					$modules[] = $replacement;
2081
				}
2082
			}
2083
		}
2084
2085
		return array_unique( $modules );
2086
	}
2087
2088
	/**
2089
	 * Checks activated plugins during auto-activation to determine
2090
	 * if any of those plugins are in the list with a corresponding module
2091
	 * that is not compatible with the plugin. The module will not be allowed
2092
	 * to auto-activate.
2093
	 *
2094
	 * @since 2.6
2095
	 * @uses jetpack_get_default_modules filter
2096
	 * @param array $modules
2097
	 * @return array
2098
	 */
2099
	function filter_default_modules( $modules ) {
2100
2101
		$active_plugins = self::get_active_plugins();
2102
2103
		if ( ! empty( $active_plugins ) ) {
2104
2105
			// For each module we'd like to auto-activate...
2106
			foreach ( $modules as $key => $module ) {
2107
				// If there are potential conflicts for it...
2108
				if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2109
					// For each potential conflict...
2110
					foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
2111
						// If that conflicting plugin is active...
2112
						if ( in_array( $plugin, $active_plugins ) ) {
2113
							// Remove that item from being auto-activated.
2114
							unset( $modules[ $key ] );
2115
						}
2116
					}
2117
				}
2118
			}
2119
		}
2120
2121
		return $modules;
2122
	}
2123
2124
	/**
2125
	 * Extract a module's slug from its full path.
2126
	 */
2127
	public static function get_module_slug( $file ) {
2128
		return str_replace( '.php', '', basename( $file ) );
2129
	}
2130
2131
	/**
2132
	 * Generate a module's path from its slug.
2133
	 */
2134
	public static function get_module_path( $slug ) {
2135
		return JETPACK__PLUGIN_DIR . "modules/$slug.php";
2136
	}
2137
2138
	/**
2139
	 * Load module data from module file. Headers differ from WordPress
2140
	 * plugin headers to avoid them being identified as standalone
2141
	 * plugins on the WordPress plugins page.
2142
	 */
2143
	public static function get_module( $module ) {
2144
		$headers = array(
2145
			'name'                      => 'Module Name',
2146
			'description'               => 'Module Description',
2147
			'jumpstart_desc'            => 'Jumpstart Description',
2148
			'sort'                      => 'Sort Order',
2149
			'recommendation_order'      => 'Recommendation Order',
2150
			'introduced'                => 'First Introduced',
2151
			'changed'                   => 'Major Changes In',
2152
			'deactivate'                => 'Deactivate',
2153
			'free'                      => 'Free',
2154
			'requires_connection'       => 'Requires Connection',
2155
			'auto_activate'             => 'Auto Activate',
2156
			'module_tags'               => 'Module Tags',
2157
			'feature'                   => 'Feature',
2158
			'additional_search_queries' => 'Additional Search Queries',
2159
		);
2160
2161
		$file = Jetpack::get_module_path( Jetpack::get_module_slug( $module ) );
2162
2163
		$mod = Jetpack::get_file_data( $file, $headers );
2164
		if ( empty( $mod['name'] ) ) {
2165
			return false;
2166
		}
2167
2168
		$mod['sort']                    = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
2169
		$mod['recommendation_order']    = empty( $mod['recommendation_order'] ) ? 20 : (int) $mod['recommendation_order'];
2170
		$mod['deactivate']              = empty( $mod['deactivate'] );
2171
		$mod['free']                    = empty( $mod['free'] );
2172
		$mod['requires_connection']     = ( ! empty( $mod['requires_connection'] ) && 'No' == $mod['requires_connection'] ) ? false : true;
2173
2174
		if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ) ) ) {
2175
			$mod['auto_activate'] = 'No';
2176
		} else {
2177
			$mod['auto_activate'] = (string) $mod['auto_activate'];
2178
		}
2179
2180
		if ( $mod['module_tags'] ) {
2181
			$mod['module_tags'] = explode( ',', $mod['module_tags'] );
2182
			$mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
2183
			$mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
2184
		} else {
2185
			$mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
2186
		}
2187
2188
		if ( $mod['feature'] ) {
2189
			$mod['feature'] = explode( ',', $mod['feature'] );
2190
			$mod['feature'] = array_map( 'trim', $mod['feature'] );
2191
		} else {
2192
			$mod['feature'] = array( self::translate_module_tag( 'Other' ) );
2193
		}
2194
2195
		/**
2196
		 * Filters the feature array on a module.
2197
		 *
2198
		 * This filter allows you to control where each module is filtered: Recommended,
2199
		 * Jumpstart, and the default "Other" listing.
2200
		 *
2201
		 * @since 3.5.0
2202
		 *
2203
		 * @param array   $mod['feature'] The areas to feature this module:
2204
		 *     'Jumpstart' adds to the "Jumpstart" option to activate many modules at once.
2205
		 *     'Recommended' shows on the main Jetpack admin screen.
2206
		 *     'Other' should be the default if no other value is in the array.
2207
		 * @param string  $module The slug of the module, e.g. sharedaddy.
2208
		 * @param array   $mod All the currently assembled module data.
2209
		 */
2210
		$mod['feature'] = apply_filters( 'jetpack_module_feature', $mod['feature'], $module, $mod );
2211
2212
		/**
2213
		 * Filter the returned data about a module.
2214
		 *
2215
		 * This filter allows overriding any info about Jetpack modules. It is dangerous,
2216
		 * so please be careful.
2217
		 *
2218
		 * @since 3.6.0
2219
		 *
2220
		 * @param array   $mod    The details of the requested module.
2221
		 * @param string  $module The slug of the module, e.g. sharedaddy
2222
		 * @param string  $file   The path to the module source file.
2223
		 */
2224
		return apply_filters( 'jetpack_get_module', $mod, $module, $file );
2225
	}
2226
2227
	/**
2228
	 * Like core's get_file_data implementation, but caches the result.
2229
	 */
2230
	public static function get_file_data( $file, $headers ) {
2231
		//Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated
2232
		$file_name = basename( $file );
2233
		$file_data_option = Jetpack_Options::get_option( 'file_data', array() );
2234
		$key              = md5( $file_name . serialize( $headers ) );
2235
		$refresh_cache    = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 );
2236
2237
		// If we don't need to refresh the cache, and already have the value, short-circuit!
2238
		if ( ! $refresh_cache && isset( $file_data_option[ JETPACK__VERSION ][ $key ] ) ) {
2239
			return $file_data_option[ JETPACK__VERSION ][ $key ];
2240
		}
2241
2242
		$data = get_file_data( $file, $headers );
2243
2244
		// Strip out any old Jetpack versions that are cluttering the option.
2245
		$file_data_option = array_intersect_key( (array) $file_data_option, array( JETPACK__VERSION => null ) );
2246
		$file_data_option[ JETPACK__VERSION ][ $key ] = $data;
2247
		Jetpack_Options::update_option( 'file_data', $file_data_option );
2248
2249
		return $data;
2250
	}
2251
2252
	/**
2253
	 * Return translated module tag.
2254
	 *
2255
	 * @param string $tag Tag as it appears in each module heading.
2256
	 *
2257
	 * @return mixed
2258
	 */
2259
	public static function translate_module_tag( $tag ) {
2260
		return jetpack_get_module_i18n_tag( $tag );
2261
	}
2262
2263
	/**
2264
	 * Return module name translation. Uses matching string created in modules/module-headings.php.
2265
	 *
2266
	 * @since 3.9.2
2267
	 *
2268
	 * @param array $modules
2269
	 *
2270
	 * @return string|void
2271
	 */
2272
	public static function get_translated_modules( $modules ) {
2273
		foreach ( $modules as $index => $module ) {
2274
			$i18n_module = jetpack_get_module_i18n( $module['module'] );
2275
			if ( isset( $module['name'] ) ) {
2276
				$modules[ $index ]['name'] = $i18n_module['name'];
2277
			}
2278
			if ( isset( $module['description'] ) ) {
2279
				$modules[ $index ]['description'] = $i18n_module['description'];
2280
				$modules[ $index ]['short_description'] = $i18n_module['description'];
2281
			}
2282
		}
2283
		return $modules;
2284
	}
2285
2286
	/**
2287
	 * Get a list of activated modules as an array of module slugs.
2288
	 */
2289
	public static function get_active_modules() {
2290
		$active = Jetpack_Options::get_option( 'active_modules' );
2291
		
2292
		if ( ! is_array( $active ) ) {
2293
			$active = array();
2294
		}
2295
2296
		if ( class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' ) ) {
2297
			$active[] = 'vaultpress';
2298
		} else {
2299
			$active = array_diff( $active, array( 'vaultpress' ) );
2300
		}
2301
2302
		//If protect is active on the main site of a multisite, it should be active on all sites.
2303
		if ( ! in_array( 'protect', $active ) && is_multisite() && get_site_option( 'jetpack_protect_active' ) ) {
2304
			$active[] = 'protect';
2305
		}
2306
2307
		return array_unique( $active );
2308
	}
2309
2310
	/**
2311
	 * Check whether or not a Jetpack module is active.
2312
	 *
2313
	 * @param string $module The slug of a Jetpack module.
2314
	 * @return bool
2315
	 *
2316
	 * @static
2317
	 */
2318
	public static function is_module_active( $module ) {
2319
		return in_array( $module, self::get_active_modules() );
2320
	}
2321
2322
	public static function is_module( $module ) {
2323
		return ! empty( $module ) && ! validate_file( $module, Jetpack::get_available_modules() );
2324
	}
2325
2326
	/**
2327
	 * Catches PHP errors.  Must be used in conjunction with output buffering.
2328
	 *
2329
	 * @param bool $catch True to start catching, False to stop.
2330
	 *
2331
	 * @static
2332
	 */
2333
	public static function catch_errors( $catch ) {
2334
		static $display_errors, $error_reporting;
2335
2336
		if ( $catch ) {
2337
			$display_errors  = @ini_set( 'display_errors', 1 );
2338
			$error_reporting = @error_reporting( E_ALL );
2339
			add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2340
		} else {
2341
			@ini_set( 'display_errors', $display_errors );
2342
			@error_reporting( $error_reporting );
2343
			remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
2344
		}
2345
	}
2346
2347
	/**
2348
	 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2349
	 */
2350
	public static function catch_errors_on_shutdown() {
2351
		Jetpack::state( 'php_errors', ob_get_clean() );
2352
	}
2353
2354
	public static function activate_default_modules( $min_version = false, $max_version = false, $other_modules = array(), $redirect = true ) {
2355
		$jetpack = Jetpack::init();
2356
2357
		$modules = Jetpack::get_default_modules( $min_version, $max_version );
2358
		$modules = array_merge( $other_modules, $modules );
2359
2360
		// Look for standalone plugins and disable if active.
2361
2362
		$to_deactivate = array();
2363
		foreach ( $modules as $module ) {
2364
			if ( isset( $jetpack->plugins_to_deactivate[$module] ) ) {
2365
				$to_deactivate[$module] = $jetpack->plugins_to_deactivate[$module];
2366
			}
2367
		}
2368
2369
		$deactivated = array();
2370
		foreach ( $to_deactivate as $module => $deactivate_me ) {
2371
			list( $probable_file, $probable_title ) = $deactivate_me;
2372
			if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2373
				$deactivated[] = $module;
2374
			}
2375
		}
2376
2377
		if ( $deactivated && $redirect ) {
2378
			Jetpack::state( 'deactivated_plugins', join( ',', $deactivated ) );
2379
2380
			$url = add_query_arg(
2381
				array(
2382
					'action'   => 'activate_default_modules',
2383
					'_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2384
				),
2385
				add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), Jetpack::admin_url( 'page=jetpack' ) )
2386
			);
2387
			wp_safe_redirect( $url );
2388
			exit;
2389
		}
2390
2391
		/**
2392
		 * Fires before default modules are activated.
2393
		 *
2394
		 * @since 1.9.0
2395
		 *
2396
		 * @param string $min_version Minimum version number required to use modules.
2397
		 * @param string $max_version Maximum version number required to use modules.
2398
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2399
		 */
2400
		do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
2401
2402
		// Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
2403
		Jetpack::restate();
2404
		Jetpack::catch_errors( true );
2405
2406
		$active = Jetpack::get_active_modules();
2407
2408
		foreach ( $modules as $module ) {
2409
			if ( did_action( "jetpack_module_loaded_$module" ) ) {
2410
				$active[] = $module;
2411
				self::update_active_modules( $active );
2412
				continue;
2413
			}
2414
2415
			if ( in_array( $module, $active ) ) {
2416
				$module_info = Jetpack::get_module( $module );
2417
				if ( ! $module_info['deactivate'] ) {
2418
					$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2419 View Code Duplication
					if ( $active_state = Jetpack::state( $state ) ) {
2420
						$active_state = explode( ',', $active_state );
2421
					} else {
2422
						$active_state = array();
2423
					}
2424
					$active_state[] = $module;
2425
					Jetpack::state( $state, implode( ',', $active_state ) );
2426
				}
2427
				continue;
2428
			}
2429
2430
			$file = Jetpack::get_module_path( $module );
2431
			if ( ! file_exists( $file ) ) {
2432
				continue;
2433
			}
2434
2435
			// we'll override this later if the plugin can be included without fatal error
2436
			if ( $redirect ) {
2437
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2438
			}
2439
			Jetpack::state( 'error', 'module_activation_failed' );
2440
			Jetpack::state( 'module', $module );
2441
			ob_start();
2442
			require $file;
2443
2444
			$active[] = $module;
2445
			$state    = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
2446 View Code Duplication
			if ( $active_state = Jetpack::state( $state ) ) {
2447
				$active_state = explode( ',', $active_state );
2448
			} else {
2449
				$active_state = array();
2450
			}
2451
			$active_state[] = $module;
2452
			Jetpack::state( $state, implode( ',', $active_state ) );
2453
			Jetpack::update_active_modules( $active );
2454
2455
			ob_end_clean();
2456
		}
2457
		Jetpack::state( 'error', false );
2458
		Jetpack::state( 'module', false );
2459
		Jetpack::catch_errors( false );
2460
		/**
2461
		 * Fires when default modules are activated.
2462
		 *
2463
		 * @since 1.9.0
2464
		 *
2465
		 * @param string $min_version Minimum version number required to use modules.
2466
		 * @param string $max_version Maximum version number required to use modules.
2467
		 * @param array $other_modules Array of other modules to activate alongside the default modules.
2468
		 */
2469
		do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
2470
	}
2471
2472
	public static function activate_module( $module, $exit = true, $redirect = true ) {
2473
		/**
2474
		 * Fires before a module is activated.
2475
		 *
2476
		 * @since 2.6.0
2477
		 *
2478
		 * @param string $module Module slug.
2479
		 * @param bool $exit Should we exit after the module has been activated. Default to true.
2480
		 * @param bool $redirect Should the user be redirected after module activation? Default to true.
2481
		 */
2482
		do_action( 'jetpack_pre_activate_module', $module, $exit, $redirect );
2483
2484
		$jetpack = Jetpack::init();
2485
2486
		if ( ! strlen( $module ) )
2487
			return false;
2488
2489
		if ( ! Jetpack::is_module( $module ) )
2490
			return false;
2491
2492
		// If it's already active, then don't do it again
2493
		$active = Jetpack::get_active_modules();
2494
		foreach ( $active as $act ) {
2495
			if ( $act == $module )
2496
				return true;
2497
		}
2498
2499
		$module_data = Jetpack::get_module( $module );
2500
2501
		if ( ! Jetpack::is_active() ) {
2502
			if ( !Jetpack::is_development_mode() )
2503
				return false;
2504
2505
			// If we're not connected but in development mode, make sure the module doesn't require a connection
2506
			if ( Jetpack::is_development_mode() && $module_data['requires_connection'] )
2507
				return false;
2508
		}
2509
2510
		// Check and see if the old plugin is active
2511
		if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2512
			// Deactivate the old plugin
2513
			if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[ $module ][0], $jetpack->plugins_to_deactivate[ $module ][1] ) ) {
2514
				// If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
2515
				// We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
2516
				Jetpack::state( 'deactivated_plugins', $module );
2517
				wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
2518
				exit;
2519
			}
2520
		}
2521
2522
		// Check the file for fatal errors, a la wp-admin/plugins.php::activate
2523
		Jetpack::state( 'module', $module );
2524
		Jetpack::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
2525
2526
		Jetpack::catch_errors( true );
2527
		ob_start();
2528
		require Jetpack::get_module_path( $module );
2529
		/** This action is documented in class.jetpack.php */
2530
		do_action( 'jetpack_activate_module', $module );
2531
		$active[] = $module;
2532
		Jetpack::update_active_modules( $active );
2533
2534
		Jetpack::state( 'error', false ); // the override
2535
		ob_end_clean();
2536
		Jetpack::catch_errors( false );
2537
2538
		// A flag for Jump Start so it's not shown again. Only set if it hasn't been yet.
2539 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2540
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2541
2542
			//Jump start is being dismissed send data to MC Stats
2543
			$jetpack->stat( 'jumpstart', 'manual,'.$module );
2544
2545
			$jetpack->do_stats( 'server_side' );
2546
		}
2547
2548
		if ( $redirect ) {
2549
			wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2550
		}
2551
		if ( $exit ) {
2552
			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...
2553
		}
2554
		return true;
2555
	}
2556
2557
	function activate_module_actions( $module ) {
2558
		_deprecated_function( __METHOD__, 'jeptack-4.2' );
2559
	}
2560
2561
	public static function deactivate_module( $module ) {
2562
		/**
2563
		 * Fires when a module is deactivated.
2564
		 *
2565
		 * @since 1.9.0
2566
		 *
2567
		 * @param string $module Module slug.
2568
		 */
2569
		do_action( 'jetpack_pre_deactivate_module', $module );
2570
2571
		$jetpack = Jetpack::init();
2572
2573
		$active = Jetpack::get_active_modules();
2574
		$new    = array_filter( array_diff( $active, (array) $module ) );
2575
2576
		// A flag for Jump Start so it's not shown again.
2577 View Code Duplication
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
2578
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
2579
2580
			//Jump start is being dismissed send data to MC Stats
2581
			$jetpack->stat( 'jumpstart', 'manual,deactivated-'.$module );
2582
2583
			$jetpack->do_stats( 'server_side' );
2584
		}
2585
2586
		return self::update_active_modules( $new );
2587
	}
2588
2589
	public static function enable_module_configurable( $module ) {
2590
		$module = Jetpack::get_module_slug( $module );
2591
		add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
2592
	}
2593
2594
	public static function module_configuration_url( $module ) {
2595
		$module = Jetpack::get_module_slug( $module );
2596
		return Jetpack::admin_url( array( 'page' => 'jetpack', 'configure' => $module ) );
2597
	}
2598
2599
	public static function module_configuration_load( $module, $method ) {
2600
		$module = Jetpack::get_module_slug( $module );
2601
		add_action( 'jetpack_module_configuration_load_' . $module, $method );
2602
	}
2603
2604
	public static function module_configuration_head( $module, $method ) {
2605
		$module = Jetpack::get_module_slug( $module );
2606
		add_action( 'jetpack_module_configuration_head_' . $module, $method );
2607
	}
2608
2609
	public static function module_configuration_screen( $module, $method ) {
2610
		$module = Jetpack::get_module_slug( $module );
2611
		add_action( 'jetpack_module_configuration_screen_' . $module, $method );
2612
	}
2613
2614
	public static function module_configuration_activation_screen( $module, $method ) {
2615
		$module = Jetpack::get_module_slug( $module );
2616
		add_action( 'display_activate_module_setting_' . $module, $method );
2617
	}
2618
2619
/* Installation */
2620
2621
	public static function bail_on_activation( $message, $deactivate = true ) {
2622
?>
2623
<!doctype html>
2624
<html>
2625
<head>
2626
<meta charset="<?php bloginfo( 'charset' ); ?>">
2627
<style>
2628
* {
2629
	text-align: center;
2630
	margin: 0;
2631
	padding: 0;
2632
	font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
2633
}
2634
p {
2635
	margin-top: 1em;
2636
	font-size: 18px;
2637
}
2638
</style>
2639
<body>
2640
<p><?php echo esc_html( $message ); ?></p>
2641
</body>
2642
</html>
2643
<?php
2644
		if ( $deactivate ) {
2645
			$plugins = get_option( 'active_plugins' );
2646
			$jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
2647
			$update  = false;
2648
			foreach ( $plugins as $i => $plugin ) {
2649
				if ( $plugin === $jetpack ) {
2650
					$plugins[$i] = false;
2651
					$update = true;
2652
				}
2653
			}
2654
2655
			if ( $update ) {
2656
				update_option( 'active_plugins', array_filter( $plugins ) );
2657
			}
2658
		}
2659
		exit;
2660
	}
2661
2662
	/**
2663
	 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
2664
	 * @static
2665
	 */
2666
	public static function plugin_activation( $network_wide ) {
2667
		Jetpack_Options::update_option( 'activated', 1 );
2668
2669
		if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
2670
			Jetpack::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
2671
		}
2672
2673
		if ( $network_wide )
2674
			Jetpack::state( 'network_nag', true );
2675
2676
		Jetpack::plugin_initialize();
2677
	}
2678
	/**
2679
	 * Runs before bumping version numbers up to a new version
2680
	 * @param  (string) $version    Version:timestamp
2681
	 * @param  (string) $old_version Old Version:timestamp or false if not set yet.
2682
	 * @return null              [description]
2683
	 */
2684
	public static function do_version_bump( $version, $old_version ) {
2685
2686
		if ( ! $old_version ) { // For new sites
2687
			// Setting up jetpack manage
2688
			Jetpack::activate_manage();
2689
		}
2690
	}
2691
2692
	/**
2693
	 * Sets the internal version number and activation state.
2694
	 * @static
2695
	 */
2696
	public static function plugin_initialize() {
2697
		if ( ! Jetpack_Options::get_option( 'activated' ) ) {
2698
			Jetpack_Options::update_option( 'activated', 2 );
2699
		}
2700
2701 View Code Duplication
		if ( ! Jetpack_Options::get_option( 'version' ) ) {
2702
			$version = $old_version = JETPACK__VERSION . ':' . time();
2703
			/** This action is documented in class.jetpack.php */
2704
			do_action( 'updating_jetpack_version', $version, false );
2705
			Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2706
		}
2707
2708
		Jetpack::load_modules();
2709
2710
		Jetpack_Options::delete_option( 'do_activate' );
2711
	}
2712
2713
	/**
2714
	 * Removes all connection options
2715
	 * @static
2716
	 */
2717
	public static function plugin_deactivation( ) {
2718
		require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
2719
		if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
2720
			Jetpack_Network::init()->deactivate();
2721
		} else {
2722
			Jetpack::disconnect( false );
2723
			//Jetpack_Heartbeat::init()->deactivate();
2724
		}
2725
	}
2726
2727
	/**
2728
	 * Disconnects from the Jetpack servers.
2729
	 * Forgets all connection details and tells the Jetpack servers to do the same.
2730
	 * @static
2731
	 */
2732
	public static function disconnect( $update_activated_state = true ) {
2733
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
2734
		Jetpack::clean_nonces( true );
2735
2736
		// If the site is in an IDC because sync is not allowed,
2737
		// let's make sure to not disconnect the production site.
2738
		if ( ! self::validate_sync_error_idc_option() ) {
2739
			Jetpack::load_xml_rpc_client();
2740
			$xml = new Jetpack_IXR_Client();
2741
			$xml->query( 'jetpack.deregister' );
2742
		}
2743
2744
		Jetpack_Options::delete_option(
2745
			array(
2746
				'register',
2747
				'blog_token',
2748
				'user_token',
2749
				'user_tokens',
2750
				'master_user',
2751
				'time_diff',
2752
				'fallback_no_verify_ssl_certs',
2753
			)
2754
		);
2755
2756
		Jetpack_IDC::clear_all_idc_options();
2757
2758
		if ( $update_activated_state ) {
2759
			Jetpack_Options::update_option( 'activated', 4 );
2760
		}
2761
2762
		if ( $jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' ) ) {
2763
			// Check then record unique disconnection if site has never been disconnected previously
2764
			if ( - 1 == $jetpack_unique_connection['disconnected'] ) {
2765
				$jetpack_unique_connection['disconnected'] = 1;
2766
			} else {
2767
				if ( 0 == $jetpack_unique_connection['disconnected'] ) {
2768
					//track unique disconnect
2769
					$jetpack = Jetpack::init();
2770
2771
					$jetpack->stat( 'connections', 'unique-disconnect' );
2772
					$jetpack->do_stats( 'server_side' );
2773
				}
2774
				// increment number of times disconnected
2775
				$jetpack_unique_connection['disconnected'] += 1;
2776
			}
2777
2778
			Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
2779
		}
2780
2781
		// Delete cached connected user data
2782
		$transient_key = "jetpack_connected_user_data_" . get_current_user_id();
2783
		delete_transient( $transient_key );
2784
2785
		// Delete all the sync related data. Since it could be taking up space.
2786
		require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php';
2787
		Jetpack_Sync_Sender::get_instance()->uninstall();
2788
2789
		// Disable the Heartbeat cron
2790
		Jetpack_Heartbeat::init()->deactivate();
2791
	}
2792
2793
	/**
2794
	 * Unlinks the current user from the linked WordPress.com user
2795
	 */
2796
	public static function unlink_user( $user_id = null ) {
2797
		if ( ! $tokens = Jetpack_Options::get_option( 'user_tokens' ) )
2798
			return false;
2799
2800
		$user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
2801
2802
		if ( Jetpack_Options::get_option( 'master_user' ) == $user_id )
2803
			return false;
2804
2805
		if ( ! isset( $tokens[ $user_id ] ) )
2806
			return false;
2807
2808
		Jetpack::load_xml_rpc_client();
2809
		$xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
2810
		$xml->query( 'jetpack.unlink_user', $user_id );
2811
2812
		unset( $tokens[ $user_id ] );
2813
2814
		Jetpack_Options::update_option( 'user_tokens', $tokens );
2815
2816
		/**
2817
		 * Fires after the current user has been unlinked from WordPress.com.
2818
		 *
2819
		 * @since 4.1.0
2820
		 *
2821
		 * @param int $user_id The current user's ID.
2822
		 */
2823
		do_action( 'jetpack_unlinked_user', $user_id );
2824
2825
		return true;
2826
	}
2827
2828
	/**
2829
	 * Attempts Jetpack registration.  If it fail, a state flag is set: @see ::admin_page_load()
2830
	 */
2831
	public static function try_registration() {
2832
		// Let's get some testing in beta versions and such.
2833
		if ( self::is_development_version() && defined( 'PHP_URL_HOST' ) ) {
2834
			// Before attempting to connect, let's make sure that the domains are viable.
2835
			$domains_to_check = array_unique( array(
2836
				'siteurl' => parse_url( get_site_url(), PHP_URL_HOST ),
2837
				'homeurl' => parse_url( get_home_url(), PHP_URL_HOST ),
2838
			) );
2839
			foreach ( $domains_to_check as $domain ) {
2840
				$result = Jetpack_Data::is_usable_domain( $domain );
2841
				if ( is_wp_error( $result ) ) {
2842
					return $result;
2843
				}
2844
			}
2845
		}
2846
2847
		$result = Jetpack::register();
2848
2849
		// If there was an error with registration and the site was not registered, record this so we can show a message.
2850
		if ( ! $result || is_wp_error( $result ) ) {
2851
			return $result;
2852
		} else {
2853
			return true;
2854
		}
2855
	}
2856
2857
	/**
2858
	 * Tracking an internal event log. Try not to put too much chaff in here.
2859
	 *
2860
	 * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
2861
	 */
2862
	public static function log( $code, $data = null ) {
2863
		// only grab the latest 200 entries
2864
		$log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
2865
2866
		// Append our event to the log
2867
		$log_entry = array(
2868
			'time'    => time(),
2869
			'user_id' => get_current_user_id(),
2870
			'blog_id' => Jetpack_Options::get_option( 'id' ),
2871
			'code'    => $code,
2872
		);
2873
		// Don't bother storing it unless we've got some.
2874
		if ( ! is_null( $data ) ) {
2875
			$log_entry['data'] = $data;
2876
		}
2877
		$log[] = $log_entry;
2878
2879
		// Try add_option first, to make sure it's not autoloaded.
2880
		// @todo: Add an add_option method to Jetpack_Options
2881
		if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
2882
			Jetpack_Options::update_option( 'log', $log );
2883
		}
2884
2885
		/**
2886
		 * Fires when Jetpack logs an internal event.
2887
		 *
2888
		 * @since 3.0.0
2889
		 *
2890
		 * @param array $log_entry {
2891
		 *	Array of details about the log entry.
2892
		 *
2893
		 *	@param string time Time of the event.
2894
		 *	@param int user_id ID of the user who trigerred the event.
2895
		 *	@param int blog_id Jetpack Blog ID.
2896
		 *	@param string code Unique name for the event.
2897
		 *	@param string data Data about the event.
2898
		 * }
2899
		 */
2900
		do_action( 'jetpack_log_entry', $log_entry );
2901
	}
2902
2903
	/**
2904
	 * Get the internal event log.
2905
	 *
2906
	 * @param $event (string) - only return the specific log events
2907
	 * @param $num   (int)    - get specific number of latest results, limited to 200
2908
	 *
2909
	 * @return array of log events || WP_Error for invalid params
2910
	 */
2911
	public static function get_log( $event = false, $num = false ) {
2912
		if ( $event && ! is_string( $event ) ) {
2913
			return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
2914
		}
2915
2916
		if ( $num && ! is_numeric( $num ) ) {
2917
			return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
2918
		}
2919
2920
		$entire_log = Jetpack_Options::get_option( 'log', array() );
2921
2922
		// If nothing set - act as it did before, otherwise let's start customizing the output
2923
		if ( ! $num && ! $event ) {
2924
			return $entire_log;
2925
		} else {
2926
			$entire_log = array_reverse( $entire_log );
2927
		}
2928
2929
		$custom_log_output = array();
2930
2931
		if ( $event ) {
2932
			foreach ( $entire_log as $log_event ) {
2933
				if ( $event == $log_event[ 'code' ] ) {
2934
					$custom_log_output[] = $log_event;
2935
				}
2936
			}
2937
		} else {
2938
			$custom_log_output = $entire_log;
2939
		}
2940
2941
		if ( $num ) {
2942
			$custom_log_output = array_slice( $custom_log_output, 0, $num );
2943
		}
2944
2945
		return $custom_log_output;
2946
	}
2947
2948
	/**
2949
	 * Log modification of important settings.
2950
	 */
2951
	public static function log_settings_change( $option, $old_value, $value ) {
2952
		switch( $option ) {
2953
			case 'jetpack_sync_non_public_post_stati':
2954
				self::log( $option, $value );
2955
				break;
2956
		}
2957
	}
2958
2959
	/**
2960
	 * Return stat data for WPCOM sync
2961
	 */
2962
	public static function get_stat_data( $encode = true, $extended = true ) {
2963
		$data = Jetpack_Heartbeat::generate_stats_array();
2964
2965
		if ( $extended ) {
2966
			$additional_data = self::get_additional_stat_data();
2967
			$data = array_merge( $data, $additional_data );
2968
		}
2969
2970
		if ( $encode ) {
2971
			return json_encode( $data );
2972
		}
2973
2974
		return $data;
2975
	}
2976
2977
	/**
2978
	 * Get additional stat data to sync to WPCOM
2979
	 */
2980
	public static function get_additional_stat_data( $prefix = '' ) {
2981
		$return["{$prefix}themes"]         = Jetpack::get_parsed_theme_data();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = array(); before regardless.

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

Let’s take a look at an example:

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

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

    // do something with $myArray
}

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

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

Loading history...
2982
		$return["{$prefix}plugins-extra"]  = Jetpack::get_parsed_plugin_data();
2983
		$return["{$prefix}users"]          = (int) Jetpack::get_site_user_count();
2984
		$return["{$prefix}site-count"]     = 0;
2985
2986
		if ( function_exists( 'get_blog_count' ) ) {
2987
			$return["{$prefix}site-count"] = get_blog_count();
2988
		}
2989
		return $return;
2990
	}
2991
2992
	private static function get_site_user_count() {
2993
		global $wpdb;
2994
2995
		if ( function_exists( 'wp_is_large_network' ) ) {
2996
			if ( wp_is_large_network( 'users' ) ) {
2997
				return -1; // Not a real value but should tell us that we are dealing with a large network.
2998
			}
2999
		}
3000 View Code Duplication
		if ( false === ( $user_count = get_transient( 'jetpack_site_user_count' ) ) ) {
3001
			// It wasn't there, so regenerate the data and save the transient
3002
			$user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities'" );
3003
			set_transient( 'jetpack_site_user_count', $user_count, DAY_IN_SECONDS );
3004
		}
3005
		return $user_count;
3006
	}
3007
3008
	/* Admin Pages */
3009
3010
	function admin_init() {
3011
		// If the plugin is not connected, display a connect message.
3012
		if (
3013
			// the plugin was auto-activated and needs its candy
3014
			Jetpack_Options::get_option_and_ensure_autoload( 'do_activate', '0' )
3015
		||
3016
			// the plugin is active, but was never activated.  Probably came from a site-wide network activation
3017
			! Jetpack_Options::get_option( 'activated' )
3018
		) {
3019
			Jetpack::plugin_initialize();
3020
		}
3021
3022
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
3023
			Jetpack_Connection_Banner::init();
3024
		} elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
3025
			// Upgrade: 1.1 -> 1.1.1
3026
			// Check and see if host can verify the Jetpack servers' SSL certificate
3027
			$args = array();
3028
			Jetpack_Client::_wp_remote_request(
3029
				Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'test' ) ),
3030
				$args,
3031
				true
3032
			);
3033
		} else if ( $this->can_display_jetpack_manage_notice() && ! Jetpack_Options::get_option( 'dismissed_manage_banner' ) ) {
3034
			// Show the notice on the Dashboard only for now
3035
			add_action( 'load-index.php', array( $this, 'prepare_manage_jetpack_notice' ) );
3036
		}
3037
3038
		if ( current_user_can( 'manage_options' ) && 'AUTO' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
3039
			add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3040
		}
3041
3042
		add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3043
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
3044
		add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3045
3046
		if ( Jetpack::is_active() || Jetpack::is_development_mode() ) {
3047
			// Artificially throw errors in certain whitelisted cases during plugin activation
3048
			add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3049
		}
3050
3051
		// Jetpack Manage Activation Screen from .com
3052
		Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
3053
3054
		// Add custom column in wp-admin/users.php to show whether user is linked.
3055
		add_filter( 'manage_users_columns',       array( $this, 'jetpack_icon_user_connected' ) );
3056
		add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_user_connected_icon' ), 10, 3 );
3057
		add_action( 'admin_print_styles',         array( $this, 'jetpack_user_col_style' ) );
3058
	}
3059
3060
	function admin_body_class( $admin_body_class = '' ) {
3061
		$classes = explode( ' ', trim( $admin_body_class ) );
3062
3063
		$classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
3064
3065
		$admin_body_class = implode( ' ', array_unique( $classes ) );
3066
		return " $admin_body_class ";
3067
	}
3068
3069
	static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3070
		return $admin_body_class . ' jetpack-pagestyles ';
3071
	}
3072
3073
	/**
3074
	 * Call this function if you want the Big Jetpack Manage Notice to show up.
3075
	 *
3076
	 * @return null
3077
	 */
3078
	function prepare_manage_jetpack_notice() {
3079
3080
		add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
3081
		add_action( 'admin_notices', array( $this, 'admin_jetpack_manage_notice' ) );
3082
	}
3083
3084
	function manage_activate_screen() {
3085
		include ( JETPACK__PLUGIN_DIR . 'modules/manage/activate-admin.php' );
3086
	}
3087
	/**
3088
	 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3089
	 * This function artificially throws errors for such cases (whitelisted).
3090
	 *
3091
	 * @param string $plugin The activated plugin.
3092
	 */
3093
	function throw_error_on_activate_plugin( $plugin ) {
3094
		$active_modules = Jetpack::get_active_modules();
3095
3096
		// The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3097
		if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
3098
			$throw = false;
3099
3100
			// Try and make sure it really was the stats plugin
3101
			if ( ! class_exists( 'ReflectionFunction' ) ) {
3102
				if ( 'stats.php' == basename( $plugin ) ) {
3103
					$throw = true;
3104
				}
3105
			} else {
3106
				$reflection = new ReflectionFunction( 'stats_get_api_key' );
3107
				if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
3108
					$throw = true;
3109
				}
3110
			}
3111
3112
			if ( $throw ) {
3113
				trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
3114
			}
3115
		}
3116
	}
3117
3118
	function intercept_plugin_error_scrape_init() {
3119
		add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3120
	}
3121
3122
	function intercept_plugin_error_scrape( $action, $result ) {
3123
		if ( ! $result ) {
3124
			return;
3125
		}
3126
3127
		foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
3128
			if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
3129
				Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3130
			}
3131
		}
3132
	}
3133
3134
	function add_remote_request_handlers() {
3135
		add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
3136
	}
3137
3138
	function remote_request_handlers() {
3139
		switch ( current_filter() ) {
3140
		case 'wp_ajax_nopriv_jetpack_upload_file' :
3141
			$response = $this->upload_handler();
3142
			break;
3143
		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...
3144
			$response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
3145
			break;
3146
		}
3147
3148
		if ( ! $response ) {
3149
			$response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
3150
		}
3151
3152
		if ( is_wp_error( $response ) ) {
3153
			$status_code       = $response->get_error_data();
3154
			$error             = $response->get_error_code();
3155
			$error_description = $response->get_error_message();
3156
3157
			if ( ! is_int( $status_code ) ) {
3158
				$status_code = 400;
3159
			}
3160
3161
			status_header( $status_code );
3162
			die( json_encode( (object) compact( 'error', 'error_description' ) ) );
3163
		}
3164
3165
		status_header( 200 );
3166
		if ( true === $response ) {
3167
			exit;
3168
		}
3169
3170
		die( json_encode( (object) $response ) );
3171
	}
3172
3173
	function upload_handler() {
3174
		if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
3175
			return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
3176
		}
3177
3178
		$user = wp_authenticate( '', '' );
3179
		if ( ! $user || is_wp_error( $user ) ) {
3180
			return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
3181
		}
3182
3183
		wp_set_current_user( $user->ID );
3184
3185
		if ( ! current_user_can( 'upload_files' ) ) {
3186
			return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3187
		}
3188
3189
		if ( empty( $_FILES ) ) {
3190
			return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3191
		}
3192
3193
		foreach ( array_keys( $_FILES ) as $files_key ) {
3194
			if ( ! isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
3195
				return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3196
			}
3197
		}
3198
3199
		$media_keys = array_keys( $_FILES['media'] );
3200
3201
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
3202
		if ( ! $token || is_wp_error( $token ) ) {
3203
			return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3204
		}
3205
3206
		$uploaded_files = array();
3207
		$global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3208
		unset( $GLOBALS['post'] );
3209
		foreach ( $_FILES['media']['name'] as $index => $name ) {
3210
			$file = array();
3211
			foreach ( $media_keys as $media_key ) {
3212
				$file[$media_key] = $_FILES['media'][$media_key][$index];
3213
			}
3214
3215
			list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
3216
3217
			$hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3218
			if ( $hmac_provided !== $hmac_file ) {
3219
				$uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
3220
				continue;
3221
			}
3222
3223
			$_FILES['.jetpack.upload.'] = $file;
3224
			$post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
3225
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
3226
				$post_id = 0;
3227
			}
3228
			$attachment_id = media_handle_upload(
3229
				'.jetpack.upload.',
3230
				$post_id,
3231
				array(),
3232
				array(
3233
					'action' => 'jetpack_upload_file',
3234
				)
3235
			);
3236
3237
			if ( ! $attachment_id ) {
3238
				$uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
3239
			} elseif ( is_wp_error( $attachment_id ) ) {
3240
				$uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
3241
			} else {
3242
				$attachment = get_post( $attachment_id );
3243
				$uploaded_files[$index] = (object) array(
3244
					'id'   => (string) $attachment_id,
3245
					'file' => $attachment->post_title,
3246
					'url'  => wp_get_attachment_url( $attachment_id ),
3247
					'type' => $attachment->post_mime_type,
3248
					'meta' => wp_get_attachment_metadata( $attachment_id ),
3249
				);
3250
				// Zip files uploads are not supported unless they are done for installation purposed
3251
				// lets delete them in case something goes wrong in this whole process
3252
				if ( 'application/zip' === $attachment->post_mime_type ) {
3253
					// Schedule a cleanup for 2 hours from now in case of failed install.
3254
					wp_schedule_single_event( time() + 2 * HOUR_IN_SECONDS, 'upgrader_scheduled_cleanup', array( $attachment_id ) );
3255
				}
3256
			}
3257
		}
3258
		if ( ! is_null( $global_post ) ) {
3259
			$GLOBALS['post'] = $global_post;
3260
		}
3261
3262
		return $uploaded_files;
3263
	}
3264
3265
	/**
3266
	 * Add help to the Jetpack page
3267
	 *
3268
	 * @since Jetpack (1.2.3)
3269
	 * @return false if not the Jetpack page
3270
	 */
3271
	function admin_help() {
3272
		$current_screen = get_current_screen();
3273
3274
		// Overview
3275
		$current_screen->add_help_tab(
3276
			array(
3277
				'id'		=> 'home',
3278
				'title'		=> __( 'Home', 'jetpack' ),
3279
				'content'	=>
3280
					'<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
3281
					'<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3282
					'<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>',
3283
			)
3284
		);
3285
3286
		// Screen Content
3287
		if ( current_user_can( 'manage_options' ) ) {
3288
			$current_screen->add_help_tab(
3289
				array(
3290
					'id'		=> 'settings',
3291
					'title'		=> __( 'Settings', 'jetpack' ),
3292
					'content'	=>
3293
						'<p><strong>' . __( 'Jetpack by WordPress.com',                                              'jetpack' ) . '</strong></p>' .
3294
						'<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3295
						'<ol>' .
3296
							'<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.',														'jetpack' ) . '</li>' .
3297
							'<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>' .
3298
						'</ol>' .
3299
						'<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>'
3300
				)
3301
			);
3302
		}
3303
3304
		// Help Sidebar
3305
		$current_screen->set_help_sidebar(
3306
			'<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3307
			'<p><a href="https://jetpack.com/faq/" target="_blank">'     . __( 'Jetpack FAQ',     'jetpack' ) . '</a></p>' .
3308
			'<p><a href="https://jetpack.com/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3309
			'<p><a href="' . Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) .'">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3310
		);
3311
	}
3312
3313
	function admin_menu_css() {
3314
		wp_enqueue_style( 'jetpack-icons' );
3315
	}
3316
3317
	function admin_menu_order() {
3318
		return true;
3319
	}
3320
3321 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
3322
		$jp_menu_order = array();
3323
3324
		foreach ( $menu_order as $index => $item ) {
3325
			if ( $item != 'jetpack' ) {
3326
				$jp_menu_order[] = $item;
3327
			}
3328
3329
			if ( $index == 0 ) {
3330
				$jp_menu_order[] = 'jetpack';
3331
			}
3332
		}
3333
3334
		return $jp_menu_order;
3335
	}
3336
3337
	function admin_head() {
3338 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) )
3339
			/** This action is documented in class.jetpack-admin-page.php */
3340
			do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
3341
	}
3342
3343
	function admin_banner_styles() {
3344
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
3345
3346 View Code Duplication
		if ( ! wp_style_is( 'jetpack-dops-style' ) ) {
3347
			wp_register_style(
3348
				'jetpack-dops-style',
3349
				plugins_url( '_inc/build/admin.dops-style.css', JETPACK__PLUGIN_FILE ),
3350
				array(),
3351
				JETPACK__VERSION
3352
			);
3353
		}
3354
3355
		wp_enqueue_style(
3356
			'jetpack',
3357
			plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ),
3358
			array( 'jetpack-dops-style' ),
3359
			 JETPACK__VERSION . '-20121016'
3360
		);
3361
		wp_style_add_data( 'jetpack', 'rtl', 'replace' );
3362
		wp_style_add_data( 'jetpack', 'suffix', $min );
3363
	}
3364
3365
	function plugin_action_links( $actions ) {
3366
3367
		$jetpack_home = array( 'jetpack-home' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack' ), __( 'Jetpack', 'jetpack' ) ) );
3368
3369
		if( current_user_can( 'jetpack_manage_modules' ) && ( Jetpack::is_active() || Jetpack::is_development_mode() ) ) {
3370
			return array_merge(
3371
				$jetpack_home,
3372
				array( 'settings' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack#/settings' ), __( 'Settings', 'jetpack' ) ) ),
3373
				array( 'support' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url( 'page=jetpack-debugger '), __( 'Support', 'jetpack' ) ) ),
3374
				$actions
3375
				);
3376
			}
3377
3378
		return array_merge( $jetpack_home, $actions );
3379
	}
3380
3381
	/**
3382
	 * This is the first banner
3383
	 * It should be visible only to user that can update the option
3384
	 * Are not connected
3385
	 *
3386
	 * @return null
3387
	 */
3388
	function admin_jetpack_manage_notice() {
3389
		$screen = get_current_screen();
3390
3391
		// Don't show the connect notice on the jetpack settings page.
3392
		if ( ! in_array( $screen->base, array( 'dashboard' ) ) || $screen->is_network || $screen->action ) {
3393
			return;
3394
		}
3395
3396
		$opt_out_url = $this->opt_out_jetpack_manage_url();
3397
		$opt_in_url  = $this->opt_in_jetpack_manage_url();
3398
		/**
3399
		 * I think it would be great to have different wordsing depending on where you are
3400
		 * for example if we show the notice on dashboard and a different one if we show it on Plugins screen
3401
		 * etc..
3402
		 */
3403
3404
		?>
3405
		<div id="message" class="updated jp-banner">
3406
				<a href="<?php echo esc_url( $opt_out_url ); ?>" class="notice-dismiss" title="<?php esc_attr_e( 'Dismiss this notice', 'jetpack' ); ?>"></a>
3407
				<div class="jp-banner__description-container">
3408
					<h2 class="jp-banner__header"><?php esc_html_e( 'Jetpack Centralized Site Management', 'jetpack' ); ?></h2>
3409
					<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>
3410
					<p class="jp-banner__button-container">
3411
						<a href="<?php echo esc_url( $opt_in_url ); ?>" class="button button-primary" id="wpcom-connect"><?php _e( 'Activate Jetpack Manage', 'jetpack' ); ?></a>
3412
						<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>
3413
					</p>
3414
				</div>
3415
		</div>
3416
		<?php
3417
	}
3418
3419
	/**
3420
	 * Returns the url that the user clicks to remove the notice for the big banner
3421
	 * @return (string)
3422
	 */
3423
	function opt_out_jetpack_manage_url() {
3424
		$referer = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
3425
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-out' . $referer ), 'jetpack_manage_banner_opt_out' );
3426
	}
3427
	/**
3428
	 * Returns the url that the user clicks to opt in to Jetpack Manage
3429
	 * @return (string)
3430
	 */
3431
	function opt_in_jetpack_manage_url() {
3432
		return wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-manage-opt-in' ), 'jetpack_manage_banner_opt_in' );
3433
	}
3434
3435
	function opt_in_jetpack_manage_notice() {
3436
		?>
3437
		<div class="wrap">
3438
			<div id="message" class="jetpack-message is-opt-in">
3439
				<?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' ); ?>
3440
			</div>
3441
		</div>
3442
		<?php
3443
3444
	}
3445
	/**
3446
	 * Determines whether to show the notice of not true = display notice
3447
	 * @return (bool)
3448
	 */
3449
	function can_display_jetpack_manage_notice() {
3450
		// never display the notice to users that can't do anything about it anyways
3451
		if( ! current_user_can( 'jetpack_manage_modules' ) )
3452
			return false;
3453
3454
		// don't display if we are in development more
3455
		if( Jetpack::is_development_mode() ) {
3456
			return false;
3457
		}
3458
		// don't display if the site is private
3459
		if(  ! Jetpack_Options::get_option( 'public' ) )
3460
			return false;
3461
3462
		/**
3463
		 * Should the Jetpack Remote Site Management notice be displayed.
3464
		 *
3465
		 * @since 3.3.0
3466
		 *
3467
		 * @param bool ! self::is_module_active( 'manage' ) Is the Manage module inactive.
3468
		 */
3469
		return apply_filters( 'can_display_jetpack_manage_notice', ! self::is_module_active( 'manage' ) );
3470
	}
3471
3472
	/*
3473
	 * Registration flow:
3474
	 * 1 - ::admin_page_load() action=register
3475
	 * 2 - ::try_registration()
3476
	 * 3 - ::register()
3477
	 *     - Creates jetpack_register option containing two secrets and a timestamp
3478
	 *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3479
	 *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3480
	 *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
3481
	 *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
3482
	 *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
3483
	 *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
3484
	 *       jetpack_id, jetpack_secret, jetpack_public
3485
	 *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
3486
	 * 4 - redirect to https://wordpress.com/start/jetpack-connect
3487
	 * 5 - user logs in with WP.com account
3488
	 * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
3489
	 *		- Jetpack_Client_Server::authorize()
3490
	 *		- Jetpack_Client_Server::get_token()
3491
	 *		- GET https://jetpack.wordpress.com/jetpack.token/1/ with
3492
	 *        client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
3493
	 *			- which responds with access_token, token_type, scope
3494
	 *		- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
3495
	 *		- Jetpack::activate_default_modules()
3496
	 *     		- Deactivates deprecated plugins
3497
	 *     		- Activates all default modules
3498
	 *		- Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
3499
	 * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
3500
	 * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
3501
	 *     Done!
3502
	 */
3503
3504
	/**
3505
	 * Handles the page load events for the Jetpack admin page
3506
	 */
3507
	function admin_page_load() {
3508
		$error = false;
3509
3510
		// Make sure we have the right body class to hook stylings for subpages off of.
3511
		add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ) );
3512
3513
		if ( ! empty( $_GET['jetpack_restate'] ) ) {
3514
			// Should only be used in intermediate redirects to preserve state across redirects
3515
			Jetpack::restate();
3516
		}
3517
3518
		if ( isset( $_GET['connect_url_redirect'] ) ) {
3519
			// User clicked in the iframe to link their accounts
3520
			if ( ! Jetpack::is_user_connected() ) {
3521
				$connect_url = $this->build_connect_url( true, false, 'iframe' );
3522
				if ( isset( $_GET['notes_iframe'] ) )
3523
					$connect_url .= '&notes_iframe';
3524
				wp_redirect( $connect_url );
3525
				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...
3526
			} else {
3527
				if ( ! isset( $_GET['calypso_env'] ) ) {
3528
					Jetpack::state( 'message', 'already_authorized' );
3529
					wp_safe_redirect( Jetpack::admin_url() );
3530
				} else {
3531
					$connect_url = $this->build_connect_url( true, false, 'iframe' );
3532
					$connect_url .= '&already_authorized=true';
3533
					wp_redirect( $connect_url );
3534
				}
3535
			}
3536
		}
3537
3538
3539
		if ( isset( $_GET['action'] ) ) {
3540
			switch ( $_GET['action'] ) {
3541
			case 'authorize':
3542
				if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
3543
					Jetpack::state( 'message', 'already_authorized' );
3544
					wp_safe_redirect( Jetpack::admin_url() );
3545
					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...
3546
				}
3547
				Jetpack::log( 'authorize' );
3548
				$client_server = new Jetpack_Client_Server;
3549
				$client_server->client_authorize();
3550
				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...
3551
			case 'register' :
3552
				if ( ! current_user_can( 'jetpack_connect' ) ) {
3553
					$error = 'cheatin';
3554
					break;
3555
				}
3556
				check_admin_referer( 'jetpack-register' );
3557
				Jetpack::log( 'register' );
3558
				Jetpack::maybe_set_version_option();
3559
				$registered = Jetpack::try_registration();
3560
				if ( is_wp_error( $registered ) ) {
3561
					$error = $registered->get_error_code();
3562
					Jetpack::state( 'error', $error );
3563
					Jetpack::state( 'error', $registered->get_error_message() );
3564
					break;
3565
				}
3566
3567
				$from = isset( $_GET['from'] ) ? $_GET['from'] : false;
3568
3569
				wp_redirect( $this->build_connect_url( true, false, $from ) );
3570
				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...
3571
			case 'activate' :
3572
				if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3573
					$error = 'cheatin';
3574
					break;
3575
				}
3576
3577
				$module = stripslashes( $_GET['module'] );
3578
				check_admin_referer( "jetpack_activate-$module" );
3579
				Jetpack::log( 'activate', $module );
3580
				Jetpack::activate_module( $module );
3581
				// The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3582
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3583
				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...
3584
			case 'activate_default_modules' :
3585
				check_admin_referer( 'activate_default_modules' );
3586
				Jetpack::log( 'activate_default_modules' );
3587
				Jetpack::restate();
3588
				$min_version   = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
3589
				$max_version   = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
3590
				$other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
3591
				Jetpack::activate_default_modules( $min_version, $max_version, $other_modules );
3592
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3593
				exit;
3594
			case 'disconnect' :
3595
				if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3596
					$error = 'cheatin';
3597
					break;
3598
				}
3599
3600
				check_admin_referer( 'jetpack-disconnect' );
3601
				Jetpack::log( 'disconnect' );
3602
				Jetpack::disconnect();
3603
				wp_safe_redirect( Jetpack::admin_url( 'disconnected=true' ) );
3604
				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...
3605
			case 'reconnect' :
3606
				if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3607
					$error = 'cheatin';
3608
					break;
3609
				}
3610
3611
				check_admin_referer( 'jetpack-reconnect' );
3612
				Jetpack::log( 'reconnect' );
3613
				$this->disconnect();
3614
				wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3615
				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...
3616 View Code Duplication
			case 'deactivate' :
3617
				if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3618
					$error = 'cheatin';
3619
					break;
3620
				}
3621
3622
				$modules = stripslashes( $_GET['module'] );
3623
				check_admin_referer( "jetpack_deactivate-$modules" );
3624
				foreach ( explode( ',', $modules ) as $module ) {
3625
					Jetpack::log( 'deactivate', $module );
3626
					Jetpack::deactivate_module( $module );
3627
					Jetpack::state( 'message', 'module_deactivated' );
3628
				}
3629
				Jetpack::state( 'module', $modules );
3630
				wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
3631
				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...
3632
			case 'unlink' :
3633
				$redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : '';
3634
				check_admin_referer( 'jetpack-unlink' );
3635
				Jetpack::log( 'unlink' );
3636
				$this->unlink_user();
3637
				Jetpack::state( 'message', 'unlinked' );
3638
				if ( 'sub-unlink' == $redirect ) {
3639
					wp_safe_redirect( admin_url() );
3640
				} else {
3641
					wp_safe_redirect( Jetpack::admin_url( array( 'page' => $redirect ) ) );
3642
				}
3643
				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...
3644
			default:
3645
				/**
3646
				 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3647
				 *
3648
				 * @since 2.6.0
3649
				 *
3650
				 * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
3651
				 */
3652
				do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
3653
			}
3654
		}
3655
3656
		if ( ! $error = $error ? $error : Jetpack::state( 'error' ) ) {
3657
			self::activate_new_modules( true );
3658
		}
3659
3660
		$message_code = Jetpack::state( 'message' );
3661
		if ( Jetpack::state( 'optin-manage' ) ) {
3662
			$activated_manage = $message_code;
3663
			$message_code = 'jetpack-manage';
3664
		}
3665
3666
		switch ( $message_code ) {
3667
		case 'jetpack-manage':
3668
			$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>';
3669
			if ( $activated_manage ) {
3670
				$this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack'  ) . '</strong>';
3671
			}
3672
			break;
3673
3674
		}
3675
3676
		$deactivated_plugins = Jetpack::state( 'deactivated_plugins' );
3677
3678
		if ( ! empty( $deactivated_plugins ) ) {
3679
			$deactivated_plugins = explode( ',', $deactivated_plugins );
3680
			$deactivated_titles  = array();
3681
			foreach ( $deactivated_plugins as $deactivated_plugin ) {
3682
				if ( ! isset( $this->plugins_to_deactivate[$deactivated_plugin] ) ) {
3683
					continue;
3684
				}
3685
3686
				$deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[$deactivated_plugin][1] ) . '</strong>';
3687
			}
3688
3689
			if ( $deactivated_titles ) {
3690
				if ( $this->message ) {
3691
					$this->message .= "<br /><br />\n";
3692
				}
3693
3694
				$this->message .= wp_sprintf(
3695
					_n(
3696
						'Jetpack contains the most recent version of the old %l plugin.',
3697
						'Jetpack contains the most recent versions of the old %l plugins.',
3698
						count( $deactivated_titles ),
3699
						'jetpack'
3700
					),
3701
					$deactivated_titles
3702
				);
3703
3704
				$this->message .= "<br />\n";
3705
3706
				$this->message .= _n(
3707
					'The old version has been deactivated and can be removed from your site.',
3708
					'The old versions have been deactivated and can be removed from your site.',
3709
					count( $deactivated_titles ),
3710
					'jetpack'
3711
				);
3712
			}
3713
		}
3714
3715
		$this->privacy_checks = Jetpack::state( 'privacy_checks' );
3716
3717
		if ( $this->message || $this->error || $this->privacy_checks || $this->can_display_jetpack_manage_notice() ) {
3718
			add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
3719
		}
3720
3721 View Code Duplication
		if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
3722
			/**
3723
			 * Fires when a module configuration page is loaded.
3724
			 * The dynamic part of the hook is the configure parameter from the URL.
3725
			 *
3726
			 * @since 1.1.0
3727
			 */
3728
			do_action( 'jetpack_module_configuration_load_' . $_GET['configure'] );
3729
		}
3730
3731
		add_filter( 'jetpack_short_module_description', 'wptexturize' );
3732
	}
3733
3734
	function admin_notices() {
3735
3736
		if ( $this->error ) {
3737
?>
3738
<div id="message" class="jetpack-message jetpack-err">
3739
	<div class="squeezer">
3740
		<h2><?php echo wp_kses( $this->error, array( 'a' => array( 'href' => array() ), 'small' => true, 'code' => true, 'strong' => true, 'br' => true, 'b' => true ) ); ?></h2>
3741
<?php	if ( $desc = Jetpack::state( 'error_description' ) ) : ?>
3742
		<p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
3743
<?php	endif; ?>
3744
	</div>
3745
</div>
3746
<?php
3747
		}
3748
3749
		if ( $this->message ) {
3750
?>
3751
<div id="message" class="jetpack-message">
3752
	<div class="squeezer">
3753
		<h2><?php echo wp_kses( $this->message, array( 'strong' => array(), 'a' => array( 'href' => true ), 'br' => true ) ); ?></h2>
3754
	</div>
3755
</div>
3756
<?php
3757
		}
3758
3759
		if ( $this->privacy_checks ) :
3760
			$module_names = $module_slugs = array();
3761
3762
			$privacy_checks = explode( ',', $this->privacy_checks );
3763
			$privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
3764
			foreach ( $privacy_checks as $module_slug ) {
3765
				$module = Jetpack::get_module( $module_slug );
3766
				if ( ! $module ) {
3767
					continue;
3768
				}
3769
3770
				$module_slugs[] = $module_slug;
3771
				$module_names[] = "<strong>{$module['name']}</strong>";
3772
			}
3773
3774
			$module_slugs = join( ',', $module_slugs );
3775
?>
3776
<div id="message" class="jetpack-message jetpack-err">
3777
	<div class="squeezer">
3778
		<h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
3779
		<p><?php
3780
			echo wp_kses(
3781
				wptexturize(
3782
					wp_sprintf(
3783
						_nx(
3784
							"Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
3785
							"Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
3786
							count( $privacy_checks ),
3787
							'%l = list of Jetpack module/feature names',
3788
							'jetpack'
3789
						),
3790
						$module_names
3791
					)
3792
				),
3793
				array( 'strong' => true )
3794
			);
3795
3796
			echo "\n<br />\n";
3797
3798
			echo wp_kses(
3799
				sprintf(
3800
					_nx(
3801
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
3802
						'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
3803
						count( $privacy_checks ),
3804
						'%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
3805
						'jetpack'
3806
					),
3807
					wp_nonce_url(
3808
						Jetpack::admin_url(
3809
							array(
3810
								'page'   => 'jetpack',
3811
								'action' => 'deactivate',
3812
								'module' => urlencode( $module_slugs ),
3813
							)
3814
						),
3815
						"jetpack_deactivate-$module_slugs"
3816
					),
3817
					esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
3818
				),
3819
				array( 'a' => array( 'href' => true, 'title' => true ) )
3820
			);
3821
		?></p>
3822
	</div>
3823
</div>
3824
<?php endif;
3825
	// only display the notice if the other stuff is not there
3826
	if( $this->can_display_jetpack_manage_notice() && !  $this->error && ! $this->message && ! $this->privacy_checks ) {
3827
		if( isset( $_GET['page'] ) && 'jetpack' != $_GET['page'] )
3828
			$this->opt_in_jetpack_manage_notice();
3829
		}
3830
	}
3831
3832
	/**
3833
	 * Record a stat for later output.  This will only currently output in the admin_footer.
3834
	 */
3835
	function stat( $group, $detail ) {
3836
		if ( ! isset( $this->stats[ $group ] ) )
3837
			$this->stats[ $group ] = array();
3838
		$this->stats[ $group ][] = $detail;
3839
	}
3840
3841
	/**
3842
	 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
3843
	 */
3844
	function do_stats( $method = '' ) {
3845
		if ( is_array( $this->stats ) && count( $this->stats ) ) {
3846
			foreach ( $this->stats as $group => $stats ) {
3847
				if ( is_array( $stats ) && count( $stats ) ) {
3848
					$args = array( "x_jetpack-{$group}" => implode( ',', $stats ) );
3849
					if ( 'server_side' === $method ) {
3850
						self::do_server_side_stat( $args );
3851
					} else {
3852
						echo '<img src="' . esc_url( self::build_stats_url( $args ) ) . '" width="1" height="1" style="display:none;" />';
3853
					}
3854
				}
3855
				unset( $this->stats[ $group ] );
3856
			}
3857
		}
3858
	}
3859
3860
	/**
3861
	 * Runs stats code for a one-off, server-side.
3862
	 *
3863
	 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
3864
	 *
3865
	 * @return bool If it worked.
3866
	 */
3867
	static function do_server_side_stat( $args ) {
3868
		$response = wp_remote_get( esc_url_raw( self::build_stats_url( $args ) ) );
3869
		if ( is_wp_error( $response ) )
3870
			return false;
3871
3872
		if ( 200 !== wp_remote_retrieve_response_code( $response ) )
3873
			return false;
3874
3875
		return true;
3876
	}
3877
3878
	/**
3879
	 * Builds the stats url.
3880
	 *
3881
	 * @param $args array|string The arguments to append to the URL.
3882
	 *
3883
	 * @return string The URL to be pinged.
3884
	 */
3885
	static function build_stats_url( $args ) {
3886
		$defaults = array(
3887
			'v'    => 'wpcom2',
3888
			'rand' => md5( mt_rand( 0, 999 ) . time() ),
3889
		);
3890
		$args     = wp_parse_args( $args, $defaults );
3891
		/**
3892
		 * Filter the URL used as the Stats tracking pixel.
3893
		 *
3894
		 * @since 2.3.2
3895
		 *
3896
		 * @param string $url Base URL used as the Stats tracking pixel.
3897
		 */
3898
		$base_url = apply_filters(
3899
			'jetpack_stats_base_url',
3900
			'https://pixel.wp.com/g.gif'
3901
		);
3902
		$url      = add_query_arg( $args, $base_url );
3903
		return $url;
3904
	}
3905
3906
	static function translate_current_user_to_role() {
3907
		foreach ( self::$capability_translations as $role => $cap ) {
3908
			if ( current_user_can( $role ) || current_user_can( $cap ) ) {
3909
				return $role;
3910
			}
3911
		}
3912
3913
		return false;
3914
	}
3915
3916
	static function translate_role_to_cap( $role ) {
3917
		if ( ! isset( self::$capability_translations[$role] ) ) {
3918
			return false;
3919
		}
3920
3921
		return self::$capability_translations[$role];
3922
	}
3923
3924
	static function sign_role( $role ) {
3925
		if ( ! $user_id = (int) get_current_user_id() ) {
3926
			return false;
3927
		}
3928
3929
		$token = Jetpack_Data::get_access_token();
3930
		if ( ! $token || is_wp_error( $token ) ) {
3931
			return false;
3932
		}
3933
3934
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
3935
	}
3936
3937
3938
	/**
3939
	 * Builds a URL to the Jetpack connection auth page
3940
	 *
3941
	 * @since 3.9.5
3942
	 *
3943
	 * @param bool $raw If true, URL will not be escaped.
3944
	 * @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
3945
	 *                              If string, will be a custom redirect.
3946
	 * @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
3947
	 *
3948
	 * @return string Connect URL
3949
	 */
3950
	function build_connect_url( $raw = false, $redirect = false, $from = false ) {
3951
		if ( ! Jetpack_Options::get_option( 'blog_token' ) || ! Jetpack_Options::get_option( 'id' ) ) {
3952
			$url = Jetpack::nonce_url_no_esc( Jetpack::admin_url( 'action=register' ), 'jetpack-register' );
3953
			if( is_network_admin() ) {
3954
				$url = add_query_arg( 'is_multisite', network_admin_url( 'admin.php?page=jetpack-settings' ), $url );
3955
			}
3956
		} else {
3957
			if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && include_once JETPACK__GLOTPRESS_LOCALES_PATH ) {
3958
				$gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
3959
			}
3960
3961
			$role = self::translate_current_user_to_role();
3962
			$signed_role = self::sign_role( $role );
3963
3964
			$user = wp_get_current_user();
3965
3966
			$jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
3967
			$redirect = $redirect
3968
				? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
3969
				: $jetpack_admin_page;
3970
3971
			if( isset( $_REQUEST['is_multisite'] ) ) {
3972
				$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
3973
			}
3974
3975
			$secrets = Jetpack::init()->generate_secrets( 'authorize' );
3976
			@list( $secret ) = explode( ':', $secrets );
3977
3978
			$site_icon = ( function_exists( 'has_site_icon') && has_site_icon() )
3979
				? get_site_icon_url()
3980
				: false;
3981
3982
			/**
3983
			 * Filter the type of authorization.
3984
			 * 'calypso' completes authorization on wordpress.com/jetpack/connect
3985
			 * while 'jetpack' ( or any other value ) completes the authorization at jetpack.wordpress.com.
3986
			 *
3987
			 * @since 4.3.3
3988
			 *
3989
			 * @param string $auth_type Defaults to 'calypso', can also be 'jetpack'.
3990
			 */
3991
			$auth_type = apply_filters( 'jetpack_auth_type', 'calypso' );
3992
3993
			$args = urlencode_deep(
3994
				array(
3995
					'response_type' => 'code',
3996
					'client_id'     => Jetpack_Options::get_option( 'id' ),
3997
					'redirect_uri'  => add_query_arg(
3998
						array(
3999
							'action'   => 'authorize',
4000
							'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
4001
							'redirect' => urlencode( $redirect ),
4002
						),
4003
						esc_url( admin_url( 'admin.php?page=jetpack' ) )
4004
					),
4005
					'state'         => $user->ID,
4006
					'scope'         => $signed_role,
4007
					'user_email'    => $user->user_email,
4008
					'user_login'    => $user->user_login,
4009
					'is_active'     => Jetpack::is_active(),
4010
					'jp_version'    => JETPACK__VERSION,
4011
					'auth_type'     => $auth_type,
4012
					'secret'        => $secret,
4013
					'locale'        => ( isset( $gp_locale ) && isset( $gp_locale->slug ) ) ? $gp_locale->slug : '',
4014
					'blogname'      => get_option( 'blogname' ),
4015
					'site_url'      => site_url(),
4016
					'home_url'      => home_url(),
4017
					'site_icon'     => $site_icon,
4018
				)
4019
			);
4020
4021
			$url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) );
4022
		}
4023
4024
		if ( $from ) {
4025
			$url = add_query_arg( 'from', $from, $url );
4026
		}
4027
4028
		if ( isset( $_GET['calypso_env'] ) ) {
4029
			$url = add_query_arg( 'calypso_env', sanitize_key( $_GET['calypso_env'] ), $url );
4030
		}
4031
4032
		return $raw ? $url : esc_url( $url );
4033
	}
4034
4035
	function build_reconnect_url( $raw = false ) {
4036
		$url = wp_nonce_url( Jetpack::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4037
		return $raw ? $url : esc_url( $url );
4038
	}
4039
4040
	public static function admin_url( $args = null ) {
4041
		$args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
4042
		$url = add_query_arg( $args, admin_url( 'admin.php' ) );
4043
		return $url;
4044
	}
4045
4046
	public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4047
		$actionurl = str_replace( '&amp;', '&', $actionurl );
4048
		return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4049
	}
4050
4051
	function dismiss_jetpack_notice() {
4052
4053
		if ( ! isset( $_GET['jetpack-notice'] ) ) {
4054
			return;
4055
		}
4056
4057
		switch( $_GET['jetpack-notice'] ) {
4058
			case 'dismiss':
4059
				if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4060
4061
					require_once ABSPATH . 'wp-admin/includes/plugin.php';
4062
					deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4063
					wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4064
				}
4065
				break;
4066 View Code Duplication
			case 'jetpack-manage-opt-out':
4067
4068
				if ( check_admin_referer( 'jetpack_manage_banner_opt_out' ) ) {
4069
					// Don't show the banner again
4070
4071
					Jetpack_Options::update_option( 'dismissed_manage_banner', true );
4072
					// redirect back to the page that had the notice
4073
					if ( wp_get_referer() ) {
4074
						wp_safe_redirect( wp_get_referer() );
4075
					} else {
4076
						// Take me to Jetpack
4077
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4078
					}
4079
				}
4080
				break;
4081 View Code Duplication
			case 'jetpack-protect-multisite-opt-out':
4082
4083
				if ( check_admin_referer( 'jetpack_protect_multisite_banner_opt_out' ) ) {
4084
					// Don't show the banner again
4085
4086
					update_site_option( 'jetpack_dismissed_protect_multisite_banner', true );
4087
					// redirect back to the page that had the notice
4088
					if ( wp_get_referer() ) {
4089
						wp_safe_redirect( wp_get_referer() );
4090
					} else {
4091
						// Take me to Jetpack
4092
						wp_safe_redirect( admin_url( 'admin.php?page=jetpack' ) );
4093
					}
4094
				}
4095
				break;
4096
			case 'jetpack-manage-opt-in':
4097
				if ( check_admin_referer( 'jetpack_manage_banner_opt_in' ) ) {
4098
					// This makes sure that we are redirect to jetpack home so that we can see the Success Message.
4099
4100
					$redirection_url = Jetpack::admin_url();
4101
					remove_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4102
4103
					// Don't redirect form the Jetpack Setting Page
4104
					$referer_parsed = parse_url ( wp_get_referer() );
4105
					// check that we do have a wp_get_referer and the query paramater is set orderwise go to the Jetpack Home
4106
					if ( isset( $referer_parsed['query'] ) && false !== strpos( $referer_parsed['query'], 'page=jetpack_modules' ) ) {
4107
						// Take the user to Jetpack home except when on the setting page
4108
						$redirection_url = wp_get_referer();
4109
						add_action( 'jetpack_pre_activate_module',   array( Jetpack_Admin::init(), 'fix_redirect' ) );
4110
					}
4111
					// Also update the JSON API FULL MANAGEMENT Option
4112
					Jetpack::activate_module( 'manage', false, false );
4113
4114
					// Special Message when option in.
4115
					Jetpack::state( 'optin-manage', 'true' );
4116
					// Activate the Module if not activated already
4117
4118
					// Redirect properly
4119
					wp_safe_redirect( $redirection_url );
4120
4121
				}
4122
				break;
4123
		}
4124
	}
4125
4126
	function debugger_page() {
4127
		nocache_headers();
4128
		if ( ! current_user_can( 'manage_options' ) ) {
4129
			die( '-1' );
4130
		}
4131
		Jetpack_Debugger::jetpack_debug_display_handler();
4132
		exit;
4133
	}
4134
4135
	public static function admin_screen_configure_module( $module_id ) {
4136
4137
		// User that doesn't have 'jetpack_configure_modules' will never end up here since Jetpack Landing Page woun't let them.
4138
		if ( ! in_array( $module_id, Jetpack::get_active_modules() ) && current_user_can( 'manage_options' ) ) {
4139
			if ( has_action( 'display_activate_module_setting_' . $module_id ) ) {
4140
				/**
4141
				 * Fires to diplay a custom module activation screen.
4142
				 *
4143
				 * To add a module actionation screen use Jetpack::module_configuration_activation_screen method.
4144
				 * Example: Jetpack::module_configuration_activation_screen( 'manage', array( $this, 'manage_activate_screen' ) );
4145
				 *
4146
				 * @module manage
4147
				 *
4148
				 * @since 3.8.0
4149
				 *
4150
				 * @param int $module_id Module ID.
4151
				 */
4152
				do_action( 'display_activate_module_setting_' . $module_id );
4153
			} else {
4154
				self::display_activate_module_link( $module_id );
4155
			}
4156
4157
			return false;
4158
		} ?>
4159
4160
		<div id="jp-settings-screen" style="position: relative">
4161
			<h3>
4162
			<?php
4163
				$module = Jetpack::get_module( $module_id );
4164
				echo '<a href="' . Jetpack::admin_url( 'page=jetpack_modules' ) . '">' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</a> &rarr; ';
4165
				printf( __( 'Configure %s', 'jetpack' ), $module['name'] );
4166
			?>
4167
			</h3>
4168
			<?php
4169
				/**
4170
				 * Fires within the displayed message when a feature configuation is updated.
4171
				 *
4172
				 * @since 3.4.0
4173
				 *
4174
				 * @param int $module_id Module ID.
4175
				 */
4176
				do_action( 'jetpack_notices_update_settings', $module_id );
4177
				/**
4178
				 * Fires when a feature configuation screen is loaded.
4179
				 * The dynamic part of the hook, $module_id, is the module ID.
4180
				 *
4181
				 * @since 1.1.0
4182
				 */
4183
				do_action( 'jetpack_module_configuration_screen_' . $module_id );
4184
			?>
4185
		</div><?php
4186
	}
4187
4188
	/**
4189
	 * Display link to activate the module to see the settings screen.
4190
	 * @param  string $module_id
4191
	 * @return null
4192
	 */
4193
	public static function display_activate_module_link( $module_id ) {
4194
4195
		$info =  Jetpack::get_module( $module_id );
4196
		$extra = '';
4197
		$activate_url = wp_nonce_url(
4198
				Jetpack::admin_url(
4199
					array(
4200
						'page'   => 'jetpack',
4201
						'action' => 'activate',
4202
						'module' => $module_id,
4203
					)
4204
				),
4205
				"jetpack_activate-$module_id"
4206
			);
4207
4208
		?>
4209
4210
		<div class="wrap configure-module">
4211
			<div id="jp-settings-screen">
4212
				<?php
4213
				if ( $module_id == 'json-api' ) {
4214
4215
					$info['name'] = esc_html__( 'Activate Site Management and JSON API', 'jetpack' );
4216
4217
					$activate_url = Jetpack::init()->opt_in_jetpack_manage_url();
4218
4219
					$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' );
4220
4221
					// $extra = __( 'To use Site Management, you need to first activate JSON API to allow remote management of your site. ', 'jetpack' );
4222
				} ?>
4223
4224
				<h3><?php echo esc_html( $info['name'] ); ?></h3>
4225
				<div class="narrow">
4226
					<p><?php echo  $info['description']; ?></p>
4227
					<?php if( $extra ) { ?>
4228
					<p><?php echo esc_html( $extra ); ?></p>
4229
					<?php } ?>
4230
					<p>
4231
						<?php
4232
						if( wp_get_referer() ) {
4233
							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() );
4234
						} else {
4235
							printf( __( '<a class="button-primary" href="%s">Activate Now</a>', 'jetpack' ) , $activate_url  );
4236
						} ?>
4237
					</p>
4238
				</div>
4239
4240
			</div>
4241
		</div>
4242
4243
		<?php
4244
	}
4245
4246
	public static function sort_modules( $a, $b ) {
4247
		if ( $a['sort'] == $b['sort'] )
4248
			return 0;
4249
4250
		return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
4251
	}
4252
4253
	function ajax_recheck_ssl() {
4254
		check_ajax_referer( 'recheck-ssl', 'ajax-nonce' );
4255
		$result = Jetpack::permit_ssl( true );
4256
		wp_send_json( array(
4257
			'enabled' => $result,
4258
			'message' => get_transient( 'jetpack_https_test_message' )
4259
		) );
4260
	}
4261
4262
/* Client API */
4263
4264
	/**
4265
	 * Returns the requested Jetpack API URL
4266
	 *
4267
	 * @return string
4268
	 */
4269
	public static function api_url( $relative_url ) {
4270
		return trailingslashit( JETPACK__API_BASE . $relative_url  ) . JETPACK__API_VERSION . '/';
4271
	}
4272
4273
	/**
4274
	 * Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
4275
	 */
4276
	public static function fix_url_for_bad_hosts( $url ) {
4277
		if ( 0 !== strpos( $url, 'https://' ) ) {
4278
			return $url;
4279
		}
4280
4281
		switch ( JETPACK_CLIENT__HTTPS ) {
4282
			case 'ALWAYS' :
4283
				return $url;
4284
			case 'NEVER' :
4285
				return set_url_scheme( $url, 'http' );
4286
			// default : case 'AUTO' :
4287
		}
4288
4289
		// we now return the unmodified SSL URL by default, as a security precaution
4290
		return $url;
4291
	}
4292
4293
	/**
4294
	 * Checks to see if the URL is using SSL to connect with Jetpack
4295
	 *
4296
	 * @since 2.3.3
4297
	 * @return boolean
4298
	 */
4299
	public static function permit_ssl( $force_recheck = false ) {
4300
		// Do some fancy tests to see if ssl is being supported
4301
		if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
4302
			$message = '';
4303
			if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
4304
				$ssl = 0;
4305
			} else {
4306
				switch ( JETPACK_CLIENT__HTTPS ) {
4307
					case 'NEVER':
4308
						$ssl = 0;
4309
						$message = __( 'JETPACK_CLIENT__HTTPS is set to NEVER', 'jetpack' );
4310
						break;
4311
					case 'ALWAYS':
4312
					case 'AUTO':
4313
					default:
4314
						$ssl = 1;
4315
						break;
4316
				}
4317
4318
				// If it's not 'NEVER', test to see
4319
				if ( $ssl ) {
4320
					if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4321
						$ssl = 0;
4322
						$message = __( 'WordPress reports no SSL support', 'jetpack' );
4323
					} else {
4324
						$response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4325
						if ( is_wp_error( $response ) ) {
4326
							$ssl = 0;
4327
							$message = __( 'WordPress reports no SSL support', 'jetpack' );
4328
						} elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4329
							$ssl = 0;
4330
							$message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4331
						}
4332
					}
4333
				}
4334
			}
4335
			set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4336
			set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
4337
		}
4338
4339
		return (bool) $ssl;
4340
	}
4341
4342
	/*
4343
	 * Displays an admin_notice, alerting the user to their JETPACK_CLIENT__HTTPS constant being 'AUTO' but SSL isn't working.
4344
	 */
4345
	public function alert_auto_ssl_fail() {
4346
		if ( ! current_user_can( 'manage_options' ) )
4347
			return;
4348
4349
		$ajax_nonce = wp_create_nonce( 'recheck-ssl' );
4350
		?>
4351
4352
		<div id="jetpack-ssl-warning" class="error jp-identity-crisis">
4353
			<div class="jp-banner__content">
4354
				<h2><?php _e( 'Outbound HTTPS not working', 'jetpack' ); ?></h2>
4355
				<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>
4356
				<p>
4357
					<?php _e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
4358
					<a href="#" id="jetpack-recheck-ssl-button"><?php _e( 'Try again', 'jetpack' ); ?></a>
4359
					<span id="jetpack-recheck-ssl-output"><?php echo get_transient( 'jetpack_https_test_message' ); ?></span>
4360
				</p>
4361
				<p>
4362
					<?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' ),
4363
							esc_url( Jetpack::admin_url( array( 'page' => 'jetpack-debugger' )  ) ),
4364
							esc_url( 'https://jetpack.com/support/getting-started-with-jetpack/troubleshooting-tips/' ) ); ?>
4365
				</p>
4366
			</div>
4367
		</div>
4368
		<style>
4369
			#jetpack-recheck-ssl-output { margin-left: 5px; color: red; }
4370
		</style>
4371
		<script type="text/javascript">
4372
			jQuery( document ).ready( function( $ ) {
4373
				$( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4374
					var $this = $( this );
4375
					$this.html( <?php echo json_encode( __( 'Checking', 'jetpack' ) ); ?> );
4376
					$( '#jetpack-recheck-ssl-output' ).html( '' );
4377
					e.preventDefault();
4378
					var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': '<?php echo $ajax_nonce; ?>' };
4379
					$.post( ajaxurl, data )
4380
					  .done( function( response ) {
4381
					  	if ( response.enabled ) {
4382
					  		$( '#jetpack-ssl-warning' ).hide();
4383
					  	} else {
4384
					  		this.html( <?php echo json_encode( __( 'Try again', 'jetpack' ) ); ?> );
4385
					  		$( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4386
					  	}
4387
					  }.bind( $this ) );
4388
				} );
4389
			} );
4390
		</script>
4391
4392
		<?php
4393
	}
4394
4395
	/**
4396
	 * Returns the Jetpack XML-RPC API
4397
	 *
4398
	 * @return string
4399
	 */
4400
	public static function xmlrpc_api_url() {
4401
		$base = preg_replace( '#(https?://[^?/]+)(/?.*)?$#', '\\1', JETPACK__API_BASE );
4402
		return untrailingslashit( $base ) . '/xmlrpc.php';
4403
	}
4404
4405
	/**
4406
	 * Creates two secret tokens and the end of life timestamp for them.
4407
	 *
4408
	 * Note these tokens are unique per call, NOT static per site for connecting.
4409
	 *
4410
	 * @since 2.6
4411
	 * @return array
4412
	 */
4413
	public function generate_secrets( $action, $exp = 600 ) {
4414
	    $secret = wp_generate_password( 32, false ) // secret_1
4415
	    		. ':' . wp_generate_password( 32, false ) // secret_2
4416
	    		. ':' . ( time() + $exp ) // eol ( End of Life )
4417
	    		. ':' . get_current_user_id(); // ties the secrets to the current user
4418
		Jetpack_Options::update_option( $action, $secret );
4419
	    return Jetpack_Options::get_option( $action );
4420
	}
4421
4422
	/**
4423
	 * Builds the timeout limit for queries talking with the wpcom servers.
4424
	 *
4425
	 * Based on local php max_execution_time in php.ini
4426
	 *
4427
	 * @since 2.6
4428
	 * @return int
4429
	 **/
4430
	public function get_remote_query_timeout_limit() {
4431
	    $timeout = (int) ini_get( 'max_execution_time' );
4432
	    if ( ! $timeout ) // Ensure exec time set in php.ini
4433
		$timeout = 30;
4434
	    return intval( $timeout / 2 );
4435
	}
4436
4437
4438
	/**
4439
	 * Takes the response from the Jetpack register new site endpoint and
4440
	 * verifies it worked properly.
4441
	 *
4442
	 * @since 2.6
4443
	 * @return true or Jetpack_Error
4444
	 **/
4445
	public function validate_remote_register_response( $response ) {
4446
	    	if ( is_wp_error( $response ) ) {
4447
			return new Jetpack_Error( 'register_http_request_failed', $response->get_error_message() );
4448
		}
4449
4450
		$code   = wp_remote_retrieve_response_code( $response );
4451
		$entity = wp_remote_retrieve_body( $response );
4452
		if ( $entity )
4453
			$json = json_decode( $entity );
4454
		else
4455
			$json = false;
4456
4457
		$code_type = intval( $code / 100 );
4458
		if ( 5 == $code_type ) {
4459
			return new Jetpack_Error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4460
		} elseif ( 408 == $code ) {
4461
			return new Jetpack_Error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4462
		} elseif ( ! empty( $json->error ) ) {
4463
			$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
4464
			return new Jetpack_Error( (string) $json->error, $error_description, $code );
4465
		} elseif ( 200 != $code ) {
4466
			return new Jetpack_Error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
4467
		}
4468
4469
		// Jetpack ID error block
4470
		if ( empty( $json->jetpack_id ) ) {
4471
			return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ), $entity );
4472
		} elseif ( ! is_scalar( $json->jetpack_id ) ) {
4473
			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 );
4474
		} elseif ( preg_match( '/[^0-9]/', $json->jetpack_id ) ) {
4475
			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 );
4476
		}
4477
4478
	    return true;
4479
	}
4480
	/**
4481
	 * @return bool|WP_Error
4482
	 */
4483
	public static function register() {
4484
		add_action( 'pre_update_jetpack_option_register', array( 'Jetpack_Options', 'delete_option' ) );
4485
		$secrets = Jetpack::init()->generate_secrets( 'register' );
4486
4487
		@list( $secret_1, $secret_2, $secret_eol ) = explode( ':', $secrets );
4488 View Code Duplication
		if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) {
4489
			return new Jetpack_Error( 'missing_secrets' );
4490
		}
4491
4492
		$timeout = Jetpack::init()->get_remote_query_timeout_limit();
4493
4494
		$gmt_offset = get_option( 'gmt_offset' );
4495
		if ( ! $gmt_offset ) {
4496
			$gmt_offset = 0;
4497
		}
4498
4499
		$stats_options = get_option( 'stats_options' );
4500
		$stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null;
4501
4502
		$args = array(
4503
			'method'  => 'POST',
4504
			'body'    => array(
4505
				'siteurl'         => site_url(),
4506
				'home'            => home_url(),
4507
				'gmt_offset'      => $gmt_offset,
4508
				'timezone_string' => (string) get_option( 'timezone_string' ),
4509
				'site_name'       => (string) get_option( 'blogname' ),
4510
				'secret_1'        => $secret_1,
4511
				'secret_2'        => $secret_2,
4512
				'site_lang'       => get_locale(),
4513
				'timeout'         => $timeout,
4514
				'stats_id'        => $stats_id,
4515
				'state'           => get_current_user_id(),
4516
			),
4517
			'headers' => array(
4518
				'Accept' => 'application/json',
4519
			),
4520
			'timeout' => $timeout,
4521
		);
4522
		$response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'register' ) ), $args, true );
4523
4524
4525
		// Make sure the response is valid and does not contain any Jetpack errors
4526
		$valid_response = Jetpack::init()->validate_remote_register_response( $response );
4527
		if( is_wp_error( $valid_response ) || !$valid_response ) {
4528
		    return $valid_response;
4529
		}
4530
4531
		// Grab the response values to work with
4532
		$code   = wp_remote_retrieve_response_code( $response );
4533
		$entity = wp_remote_retrieve_body( $response );
4534
4535
		if ( $entity )
4536
			$json = json_decode( $entity );
4537
		else
4538
			$json = false;
4539
4540 View Code Duplication
		if ( empty( $json->jetpack_secret ) || ! is_string( $json->jetpack_secret ) )
4541
			return new Jetpack_Error( 'jetpack_secret', '', $code );
4542
4543
		if ( isset( $json->jetpack_public ) ) {
4544
			$jetpack_public = (int) $json->jetpack_public;
4545
		} else {
4546
			$jetpack_public = false;
4547
		}
4548
4549
		Jetpack_Options::update_options(
4550
			array(
4551
				'id'         => (int)    $json->jetpack_id,
4552
				'blog_token' => (string) $json->jetpack_secret,
4553
				'public'     => $jetpack_public,
4554
			)
4555
		);
4556
4557
		/**
4558
		 * Fires when a site is registered on WordPress.com.
4559
		 *
4560
		 * @since 3.7.0
4561
		 *
4562
		 * @param int $json->jetpack_id Jetpack Blog ID.
4563
		 * @param string $json->jetpack_secret Jetpack Blog Token.
4564
		 * @param int|bool $jetpack_public Is the site public.
4565
		 */
4566
		do_action( 'jetpack_site_registered', $json->jetpack_id, $json->jetpack_secret, $jetpack_public );
4567
4568
		// Initialize Jump Start for the first and only time.
4569
		if ( ! Jetpack_Options::get_option( 'jumpstart' ) ) {
4570
			Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
4571
4572
			$jetpack = Jetpack::init();
4573
4574
			$jetpack->stat( 'jumpstart', 'unique-views' );
4575
			$jetpack->do_stats( 'server_side' );
4576
		};
4577
4578
		return true;
4579
	}
4580
4581
	/**
4582
	 * If the db version is showing something other that what we've got now, bump it to current.
4583
	 *
4584
	 * @return bool: True if the option was incorrect and updated, false if nothing happened.
4585
	 */
4586
	public static function maybe_set_version_option() {
4587
		list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
4588
		if ( JETPACK__VERSION != $version ) {
4589
			Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
4590
4591
			if ( version_compare( JETPACK__VERSION, $version, '>' ) ) {
4592
				/** This action is documented in class.jetpack.php */
4593
				do_action( 'updating_jetpack_version', JETPACK__VERSION, $version );
4594
			}
4595
4596
			return true;
4597
		}
4598
		return false;
4599
	}
4600
4601
/* Client Server API */
4602
4603
	/**
4604
	 * Loads the Jetpack XML-RPC client
4605
	 */
4606
	public static function load_xml_rpc_client() {
4607
		require_once ABSPATH . WPINC . '/class-IXR.php';
4608
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-ixr-client.php';
4609
	}
4610
4611
	function verify_xml_rpc_signature() {
4612
		if ( $this->xmlrpc_verification ) {
4613
			return $this->xmlrpc_verification;
4614
		}
4615
4616
		// It's not for us
4617
		if ( ! isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
4618
			return false;
4619
		}
4620
4621
		@list( $token_key, $version, $user_id ) = explode( ':', $_GET['token'] );
4622
		if (
4623
			empty( $token_key )
4624
		||
4625
			empty( $version ) || strval( JETPACK__API_VERSION ) !== $version
4626
		) {
4627
			return false;
4628
		}
4629
4630
		if ( '0' === $user_id ) {
4631
			$token_type = 'blog';
4632
			$user_id = 0;
4633
		} else {
4634
			$token_type = 'user';
4635
			if ( empty( $user_id ) || ! ctype_digit( $user_id ) ) {
4636
				return false;
4637
			}
4638
			$user_id = (int) $user_id;
4639
4640
			$user = new WP_User( $user_id );
4641
			if ( ! $user || ! $user->exists() ) {
4642
				return false;
4643
			}
4644
		}
4645
4646
		$token = Jetpack_Data::get_access_token( $user_id );
4647
		if ( ! $token ) {
4648
			return false;
4649
		}
4650
4651
		$token_check = "$token_key.";
4652
		if ( ! hash_equals( substr( $token->secret, 0, strlen( $token_check ) ), $token_check ) ) {
4653
			return false;
4654
		}
4655
4656
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
4657
4658
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
4659
		if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
4660
			$post_data   = $_POST;
4661
			$file_hashes = array();
4662
			foreach ( $post_data as $post_data_key => $post_data_value ) {
4663
				if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
4664
					continue;
4665
				}
4666
				$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
4667
				$file_hashes[$post_data_key] = $post_data_value;
4668
			}
4669
4670
			foreach ( $file_hashes as $post_data_key => $post_data_value ) {
4671
				unset( $post_data["_jetpack_file_hmac_{$post_data_key}"] );
4672
				$post_data[$post_data_key] = $post_data_value;
4673
			}
4674
4675
			ksort( $post_data );
4676
4677
			$body = http_build_query( stripslashes_deep( $post_data ) );
4678
		} elseif ( is_null( $this->HTTP_RAW_POST_DATA ) ) {
4679
			$body = file_get_contents( 'php://input' );
4680
		} else {
4681
			$body = null;
4682
		}
4683
		$signature = $jetpack_signature->sign_current_request(
4684
			array( 'body' => is_null( $body ) ? $this->HTTP_RAW_POST_DATA : $body, )
4685
		);
4686
4687
		if ( ! $signature ) {
4688
			return false;
4689
		} else if ( is_wp_error( $signature ) ) {
4690
			return $signature;
4691
		} else if ( ! hash_equals( $signature, $_GET['signature'] ) ) {
4692
			return false;
4693
		}
4694
4695
		$timestamp = (int) $_GET['timestamp'];
4696
		$nonce     = stripslashes( (string) $_GET['nonce'] );
4697
4698
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
4699
			return false;
4700
		}
4701
4702
		$this->xmlrpc_verification = array(
4703
			'type'    => $token_type,
4704
			'user_id' => $token->external_user_id,
4705
		);
4706
4707
		return $this->xmlrpc_verification;
4708
	}
4709
4710
	/**
4711
	 * Authenticates XML-RPC and other requests from the Jetpack Server
4712
	 */
4713
	function authenticate_jetpack( $user, $username, $password ) {
4714
		if ( is_a( $user, 'WP_User' ) ) {
4715
			return $user;
4716
		}
4717
4718
		$token_details = $this->verify_xml_rpc_signature();
4719
4720
		if ( ! $token_details || is_wp_error( $token_details ) ) {
4721
			return $user;
4722
		}
4723
4724
		if ( 'user' !== $token_details['type'] ) {
4725
			return $user;
4726
		}
4727
4728
		if ( ! $token_details['user_id'] ) {
4729
			return $user;
4730
		}
4731
4732
		nocache_headers();
4733
4734
		return new WP_User( $token_details['user_id'] );
4735
	}
4736
4737
	function add_nonce( $timestamp, $nonce ) {
4738
		global $wpdb;
4739
		static $nonces_used_this_request = array();
4740
4741
		if ( isset( $nonces_used_this_request["$timestamp:$nonce"] ) ) {
4742
			return $nonces_used_this_request["$timestamp:$nonce"];
4743
		}
4744
4745
		// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce
4746
		$timestamp = (int) $timestamp;
4747
		$nonce     = esc_sql( $nonce );
4748
4749
		// Raw query so we can avoid races: add_option will also update
4750
		$show_errors = $wpdb->show_errors( false );
4751
4752
		$old_nonce = $wpdb->get_row(
4753
			$wpdb->prepare( "SELECT * FROM `$wpdb->options` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
4754
		);
4755
4756
		if ( is_null( $old_nonce ) ) {
4757
			$return = $wpdb->query(
4758
				$wpdb->prepare(
4759
					"INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
4760
					"jetpack_nonce_{$timestamp}_{$nonce}",
4761
					time(),
4762
					'no'
4763
				)
4764
			);
4765
		} else {
4766
			$return = false;
4767
		}
4768
4769
		$wpdb->show_errors( $show_errors );
4770
4771
		$nonces_used_this_request["$timestamp:$nonce"] = $return;
4772
4773
		return $return;
4774
	}
4775
4776
	/**
4777
	 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths since it is passed by reference to various methods.
4778
	 * Capture it here so we can verify the signature later.
4779
	 */
4780
	function xmlrpc_methods( $methods ) {
4781
		$this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
4782
		return $methods;
4783
	}
4784
4785
	function public_xmlrpc_methods( $methods ) {
4786
		if ( array_key_exists( 'wp.getOptions', $methods ) ) {
4787
			$methods['wp.getOptions'] = array( $this, 'jetpack_getOptions' );
4788
		}
4789
		return $methods;
4790
	}
4791
4792
	function jetpack_getOptions( $args ) {
4793
		global $wp_xmlrpc_server;
4794
4795
		$wp_xmlrpc_server->escape( $args );
4796
4797
		$username	= $args[1];
4798
		$password	= $args[2];
4799
4800
		if ( !$user = $wp_xmlrpc_server->login($username, $password) ) {
4801
			return $wp_xmlrpc_server->error;
4802
		}
4803
4804
		$options = array();
4805
		$user_data = $this->get_connected_user_data();
4806
		if ( is_array( $user_data ) ) {
4807
			$options['jetpack_user_id'] = array(
4808
				'desc'          => __( 'The WP.com user ID of the connected user', 'jetpack' ),
4809
				'readonly'      => true,
4810
				'value'         => $user_data['ID'],
4811
			);
4812
			$options['jetpack_user_login'] = array(
4813
				'desc'          => __( 'The WP.com username of the connected user', 'jetpack' ),
4814
				'readonly'      => true,
4815
				'value'         => $user_data['login'],
4816
			);
4817
			$options['jetpack_user_email'] = array(
4818
				'desc'          => __( 'The WP.com user email of the connected user', 'jetpack' ),
4819
				'readonly'      => true,
4820
				'value'         => $user_data['email'],
4821
			);
4822
			$options['jetpack_user_site_count'] = array(
4823
				'desc'          => __( 'The number of sites of the connected WP.com user', 'jetpack' ),
4824
				'readonly'      => true,
4825
				'value'         => $user_data['site_count'],
4826
			);
4827
		}
4828
		$wp_xmlrpc_server->blog_options = array_merge( $wp_xmlrpc_server->blog_options, $options );
4829
		$args = stripslashes_deep( $args );
4830
		return $wp_xmlrpc_server->wp_getOptions( $args );
4831
	}
4832
4833
	function xmlrpc_options( $options ) {
4834
		$jetpack_client_id = false;
4835
		if ( self::is_active() ) {
4836
			$jetpack_client_id = Jetpack_Options::get_option( 'id' );
4837
		}
4838
		$options['jetpack_version'] = array(
4839
				'desc'          => __( 'Jetpack Plugin Version', 'jetpack' ),
4840
				'readonly'      => true,
4841
				'value'         => JETPACK__VERSION,
4842
		);
4843
4844
		$options['jetpack_client_id'] = array(
4845
				'desc'          => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack' ),
4846
				'readonly'      => true,
4847
				'value'         => $jetpack_client_id,
4848
		);
4849
		return $options;
4850
	}
4851
4852
	public static function clean_nonces( $all = false ) {
4853
		global $wpdb;
4854
4855
		$sql = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
4856
		$sql_args = array( $wpdb->esc_like( 'jetpack_nonce_' ) . '%' );
4857
4858
		if ( true !== $all ) {
4859
			$sql .= ' AND CAST( `option_value` AS UNSIGNED ) < %d';
4860
			$sql_args[] = time() - 3600;
4861
		}
4862
4863
		$sql .= ' ORDER BY `option_id` LIMIT 100';
4864
4865
		$sql = $wpdb->prepare( $sql, $sql_args );
4866
4867
		for ( $i = 0; $i < 1000; $i++ ) {
4868
			if ( ! $wpdb->query( $sql ) ) {
4869
				break;
4870
			}
4871
		}
4872
	}
4873
4874
	/**
4875
	 * State is passed via cookies from one request to the next, but never to subsequent requests.
4876
	 * SET: state( $key, $value );
4877
	 * GET: $value = state( $key );
4878
	 *
4879
	 * @param string $key
4880
	 * @param string $value
4881
	 * @param bool $restate private
4882
	 */
4883
	public static function state( $key = null, $value = null, $restate = false ) {
4884
		static $state = array();
4885
		static $path, $domain;
4886
		if ( ! isset( $path ) ) {
4887
			require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
4888
			$admin_url = Jetpack::admin_url();
4889
			$bits      = parse_url( $admin_url );
4890
4891
			if ( is_array( $bits ) ) {
4892
				$path   = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
4893
				$domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
4894
			} else {
4895
				$path = $domain = null;
4896
			}
4897
		}
4898
4899
		// Extract state from cookies and delete cookies
4900
		if ( isset( $_COOKIE[ 'jetpackState' ] ) && is_array( $_COOKIE[ 'jetpackState' ] ) ) {
4901
			$yum = $_COOKIE[ 'jetpackState' ];
4902
			unset( $_COOKIE[ 'jetpackState' ] );
4903
			foreach ( $yum as $k => $v ) {
4904
				if ( strlen( $v ) )
4905
					$state[ $k ] = $v;
4906
				setcookie( "jetpackState[$k]", false, 0, $path, $domain );
4907
			}
4908
		}
4909
4910
		if ( $restate ) {
4911
			foreach ( $state as $k => $v ) {
4912
				setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
4913
			}
4914
			return;
4915
		}
4916
4917
		// Get a state variable
4918
		if ( isset( $key ) && ! isset( $value ) ) {
4919
			if ( array_key_exists( $key, $state ) )
4920
				return $state[ $key ];
4921
			return null;
4922
		}
4923
4924
		// Set a state variable
4925
		if ( isset ( $key ) && isset( $value ) ) {
4926
			if( is_array( $value ) && isset( $value[0] ) ) {
4927
				$value = $value[0];
4928
			}
4929
			$state[ $key ] = $value;
4930
			setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
4931
		}
4932
	}
4933
4934
	public static function restate() {
4935
		Jetpack::state( null, null, true );
4936
	}
4937
4938
	public static function check_privacy( $file ) {
4939
		static $is_site_publicly_accessible = null;
4940
4941
		if ( is_null( $is_site_publicly_accessible ) ) {
4942
			$is_site_publicly_accessible = false;
4943
4944
			Jetpack::load_xml_rpc_client();
4945
			$rpc = new Jetpack_IXR_Client();
4946
4947
			$success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
4948
			if ( $success ) {
4949
				$response = $rpc->getResponse();
4950
				if ( $response ) {
4951
					$is_site_publicly_accessible = true;
4952
				}
4953
			}
4954
4955
			Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
4956
		}
4957
4958
		if ( $is_site_publicly_accessible ) {
4959
			return;
4960
		}
4961
4962
		$module_slug = self::get_module_slug( $file );
4963
4964
		$privacy_checks = Jetpack::state( 'privacy_checks' );
4965
		if ( ! $privacy_checks ) {
4966
			$privacy_checks = $module_slug;
4967
		} else {
4968
			$privacy_checks .= ",$module_slug";
4969
		}
4970
4971
		Jetpack::state( 'privacy_checks', $privacy_checks );
4972
	}
4973
4974
	/**
4975
	 * Helper method for multicall XMLRPC.
4976
	 */
4977
	public static function xmlrpc_async_call() {
4978
		global $blog_id;
4979
		static $clients = array();
4980
4981
		$client_blog_id = is_multisite() ? $blog_id : 0;
4982
4983
		if ( ! isset( $clients[$client_blog_id] ) ) {
4984
			Jetpack::load_xml_rpc_client();
4985
			$clients[$client_blog_id] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => JETPACK_MASTER_USER, ) );
4986
			if ( function_exists( 'ignore_user_abort' ) ) {
4987
				ignore_user_abort( true );
4988
			}
4989
			add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
4990
		}
4991
4992
		$args = func_get_args();
4993
4994
		if ( ! empty( $args[0] ) ) {
4995
			call_user_func_array( array( $clients[$client_blog_id], 'addCall' ), $args );
4996
		} elseif ( is_multisite() ) {
4997
			foreach ( $clients as $client_blog_id => $client ) {
4998
				if ( ! $client_blog_id || empty( $client->calls ) ) {
4999
					continue;
5000
				}
5001
5002
				$switch_success = switch_to_blog( $client_blog_id, true );
5003
				if ( ! $switch_success ) {
5004
					continue;
5005
				}
5006
5007
				flush();
5008
				$client->query();
5009
5010
				restore_current_blog();
5011
			}
5012
		} else {
5013
			if ( isset( $clients[0] ) && ! empty( $clients[0]->calls ) ) {
5014
				flush();
5015
				$clients[0]->query();
5016
			}
5017
		}
5018
	}
5019
5020
	public static function staticize_subdomain( $url ) {
5021
5022
		// Extract hostname from URL
5023
		$host = parse_url( $url, PHP_URL_HOST );
5024
5025
		// Explode hostname on '.'
5026
		$exploded_host = explode( '.', $host );
5027
5028
		// Retrieve the name and TLD
5029
		if ( count( $exploded_host ) > 1 ) {
5030
			$name = $exploded_host[ count( $exploded_host ) - 2 ];
5031
			$tld = $exploded_host[ count( $exploded_host ) - 1 ];
5032
			// Rebuild domain excluding subdomains
5033
			$domain = $name . '.' . $tld;
5034
		} else {
5035
			$domain = $host;
5036
		}
5037
		// Array of Automattic domains
5038
		$domain_whitelist = array( 'wordpress.com', 'wp.com' );
5039
5040
		// Return $url if not an Automattic domain
5041
		if ( ! in_array( $domain, $domain_whitelist ) ) {
5042
			return $url;
5043
		}
5044
5045
		if ( is_ssl() ) {
5046
			return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
5047
		}
5048
5049
		srand( crc32( basename( $url ) ) );
5050
		$static_counter = rand( 0, 2 );
5051
		srand(); // this resets everything that relies on this, like array_rand() and shuffle()
5052
5053
		return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
5054
	}
5055
5056
/* JSON API Authorization */
5057
5058
	/**
5059
	 * Handles the login action for Authorizing the JSON API
5060
	 */
5061
	function login_form_json_api_authorization() {
5062
		$this->verify_json_api_authorization_request();
5063
5064
		add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
5065
5066
		add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
5067
		add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5068
		add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
5069
	}
5070
5071
	// Make sure the login form is POSTed to the signed URL so we can reverify the request
5072
	function post_login_form_to_signed_url( $url, $path, $scheme ) {
5073
		if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
5074
			return $url;
5075
		}
5076
5077
		$parsed_url = parse_url( $url );
5078
		$url = strtok( $url, '?' );
5079
		$url = "$url?{$_SERVER['QUERY_STRING']}";
5080
		if ( ! empty( $parsed_url['query'] ) )
5081
			$url .= "&{$parsed_url['query']}";
5082
5083
		return $url;
5084
	}
5085
5086
	// Make sure the POSTed request is handled by the same action
5087
	function preserve_action_in_login_form_for_json_api_authorization() {
5088
		echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
5089
		echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) . "' />\n";
5090
	}
5091
5092
	// If someone logs in to approve API access, store the Access Code in usermeta
5093
	function store_json_api_authorization_token( $user_login, $user ) {
5094
		add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5095
		add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
5096
		$token = wp_generate_password( 32, false );
5097
		update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5098
	}
5099
5100
	// Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
5101
	function allow_wpcom_public_api_domain( $domains ) {
5102
		$domains[] = 'public-api.wordpress.com';
5103
		return $domains;
5104
	}
5105
5106
	// Add the Access Code details to the public-api.wordpress.com redirect
5107
	function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5108
		return add_query_arg(
5109
			urlencode_deep(
5110
				array(
5111
					'jetpack-code'    => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5112
					'jetpack-user-id' => (int) $user->ID,
5113
					'jetpack-state'   => $this->json_api_authorization_request['state'],
5114
				)
5115
			),
5116
			$redirect_to
5117
		);
5118
	}
5119
5120
	// Verifies the request by checking the signature
5121
	function verify_json_api_authorization_request() {
5122
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
5123
5124
		$token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
5125
		if ( ! $token || empty( $token->secret ) ) {
5126
			wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.' , 'jetpack' ) );
5127
		}
5128
5129
		$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' );
5130
5131
		$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5132
5133
		if ( isset( $_POST['jetpack_json_api_original_query'] ) ) {
5134
			$signature = $jetpack_signature->sign_request( $_GET['token'], $_GET['timestamp'], $_GET['nonce'], '', 'GET', $_POST['jetpack_json_api_original_query'], null, true );
5135
		} else {
5136
			$signature = $jetpack_signature->sign_current_request( array( 'body' => null, 'method' => 'GET' ) );
5137
		}
5138
5139
		if ( ! $signature ) {
5140
			wp_die( $die_error );
5141
		} else if ( is_wp_error( $signature ) ) {
5142
			wp_die( $die_error );
5143
		} else if ( ! hash_equals( $signature, $_GET['signature'] ) ) {
5144
			if ( is_ssl() ) {
5145
				// 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
5146
				$signature = $jetpack_signature->sign_current_request( array( 'scheme' => 'http', 'body' => null, 'method' => 'GET' ) );
5147
				if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $_GET['signature'] ) ) {
5148
					wp_die( $die_error );
5149
				}
5150
			} else {
5151
				wp_die( $die_error );
5152
			}
5153
		}
5154
5155
		$timestamp = (int) $_GET['timestamp'];
5156
		$nonce     = stripslashes( (string) $_GET['nonce'] );
5157
5158
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
5159
			// De-nonce the nonce, at least for 5 minutes.
5160
			// 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)
5161
			$old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5162
			if ( $old_nonce_time < time() - 300 ) {
5163
				wp_die( __( 'The authorization process expired.  Please go back and try again.' , 'jetpack' ) );
5164
			}
5165
		}
5166
5167
		$data = json_decode( base64_decode( stripslashes( $_GET['data'] ) ) );
5168
		$data_filters = array(
5169
			'state'        => 'opaque',
5170
			'client_id'    => 'int',
5171
			'client_title' => 'string',
5172
			'client_image' => 'url',
5173
		);
5174
5175
		foreach ( $data_filters as $key => $sanitation ) {
5176
			if ( ! isset( $data->$key ) ) {
5177
				wp_die( $die_error );
5178
			}
5179
5180
			switch ( $sanitation ) {
5181
			case 'int' :
5182
				$this->json_api_authorization_request[$key] = (int) $data->$key;
5183
				break;
5184
			case 'opaque' :
5185
				$this->json_api_authorization_request[$key] = (string) $data->$key;
5186
				break;
5187
			case 'string' :
5188
				$this->json_api_authorization_request[$key] = wp_kses( (string) $data->$key, array() );
5189
				break;
5190
			case 'url' :
5191
				$this->json_api_authorization_request[$key] = esc_url_raw( (string) $data->$key );
5192
				break;
5193
			}
5194
		}
5195
5196
		if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5197
			wp_die( $die_error );
5198
		}
5199
	}
5200
5201
	function login_message_json_api_authorization( $message ) {
5202
		return '<p class="message">' . sprintf(
5203
			esc_html__( '%s wants to access your site&#8217;s data.  Log in to authorize that access.' , 'jetpack' ),
5204
			'<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5205
		) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5206
	}
5207
5208
	/**
5209
	 * Get $content_width, but with a <s>twist</s> filter.
5210
	 */
5211
	public static function get_content_width() {
5212
		$content_width = isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : false;
5213
		/**
5214
		 * Filter the Content Width value.
5215
		 *
5216
		 * @since 2.2.3
5217
		 *
5218
		 * @param string $content_width Content Width value.
5219
		 */
5220
		return apply_filters( 'jetpack_content_width', $content_width );
5221
	}
5222
5223
	/**
5224
	 * Centralize the function here until it gets added to core.
5225
	 *
5226
	 * @param int|string|object $id_or_email A user ID,  email address, or comment object
5227
	 * @param int $size Size of the avatar image
5228
	 * @param string $default URL to a default image to use if no avatar is available
5229
	 * @param bool $force_display Whether to force it to return an avatar even if show_avatars is disabled
5230
	 *
5231
	 * @return array First element is the URL, second is the class.
5232
	 */
5233
	public static function get_avatar_url( $id_or_email, $size = 96, $default = '', $force_display = false ) {
5234
		// Don't bother adding the __return_true filter if it's already there.
5235
		$has_filter = has_filter( 'pre_option_show_avatars', '__return_true' );
5236
5237
		if ( $force_display && ! $has_filter )
5238
			add_filter( 'pre_option_show_avatars', '__return_true' );
5239
5240
		$avatar = get_avatar( $id_or_email, $size, $default );
5241
5242
		if ( $force_display && ! $has_filter )
5243
			remove_filter( 'pre_option_show_avatars', '__return_true' );
5244
5245
		// If no data, fail out.
5246
		if ( is_wp_error( $avatar ) || ! $avatar )
5247
			return array( null, null );
5248
5249
		// Pull out the URL.  If it's not there, fail out.
5250
		if ( ! preg_match( '/src=["\']([^"\']+)["\']/', $avatar, $url_matches ) )
5251
			return array( null, null );
5252
		$url = wp_specialchars_decode( $url_matches[1], ENT_QUOTES );
5253
5254
		// Pull out the class, but it's not a big deal if it's missing.
5255
		$class = '';
5256
		if ( preg_match( '/class=["\']([^"\']+)["\']/', $avatar, $class_matches ) )
5257
			$class = wp_specialchars_decode( $class_matches[1], ENT_QUOTES );
5258
5259
		return array( $url, $class );
5260
	}
5261
5262
	/**
5263
	 * Pings the WordPress.com Mirror Site for the specified options.
5264
	 *
5265
	 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
5266
	 *
5267
	 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
5268
	 */
5269
	public function get_cloud_site_options( $option_names ) {
5270
		$option_names = array_filter( (array) $option_names, 'is_string' );
5271
5272
		Jetpack::load_xml_rpc_client();
5273
		$xml = new Jetpack_IXR_Client( array( 'user_id' => JETPACK_MASTER_USER, ) );
5274
		$xml->query( 'jetpack.fetchSiteOptions', $option_names );
5275
		if ( $xml->isError() ) {
5276
			return array(
5277
				'error_code' => $xml->getErrorCode(),
5278
				'error_msg'  => $xml->getErrorMessage(),
5279
			);
5280
		}
5281
		$cloud_site_options = $xml->getResponse();
5282
5283
		return $cloud_site_options;
5284
	}
5285
5286
	/**
5287
	 * Checks if the site is currently in an identity crisis.
5288
	 *
5289
	 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5290
	 */
5291
	public static function check_identity_crisis() {
5292
		if ( ! Jetpack::is_active() || Jetpack::is_development_mode() || ! self::validate_sync_error_idc_option() ) {
5293
			return false;
5294
		}
5295
5296
		return Jetpack_Options::get_option( 'sync_error_idc' );
5297
	}
5298
5299
	/**
5300
	 * Checks whether the home and siteurl specifically are whitelisted
5301
	 * Written so that we don't have re-check $key and $value params every time
5302
	 * we want to check if this site is whitelisted, for example in footer.php
5303
	 *
5304
	 * @since  3.8.0
5305
	 * @return bool True = already whitelisted False = not whitelisted
5306
	 */
5307
	public static function is_staging_site() {
5308
		$is_staging = false;
5309
5310
		$known_staging = array(
5311
			'urls' => array(
5312
				'#\.staging\.wpengine\.com$#i', // WP Engine
5313
				'#\.staging\.kinsta\.com$#i',   // Kinsta.com
5314
				),
5315
			'constants' => array(
5316
				'IS_WPE_SNAPSHOT',      // WP Engine
5317
				'KINSTA_DEV_ENV',       // Kinsta.com
5318
				'WPSTAGECOACH_STAGING', // WP Stagecoach
5319
				'JETPACK_STAGING_MODE', // Generic
5320
				)
5321
			);
5322
		/**
5323
		 * Filters the flags of known staging sites.
5324
		 *
5325
		 * @since 3.9.0
5326
		 *
5327
		 * @param array $known_staging {
5328
		 *     An array of arrays that each are used to check if the current site is staging.
5329
		 *     @type array $urls      URLs of staging sites in regex to check against site_url.
5330
		 *     @type array $constants PHP constants of known staging/developement environments.
5331
		 *  }
5332
		 */
5333
		$known_staging = apply_filters( 'jetpack_known_staging', $known_staging );
5334
5335
		if ( isset( $known_staging['urls'] ) ) {
5336
			foreach ( $known_staging['urls'] as $url ){
5337
				if ( preg_match( $url, site_url() ) ) {
5338
					$is_staging = true;
5339
					break;
5340
				}
5341
			}
5342
		}
5343
5344
		if ( isset( $known_staging['constants'] ) ) {
5345
			foreach ( $known_staging['constants'] as $constant ) {
5346
				if ( defined( $constant ) && constant( $constant ) ) {
5347
					$is_staging = true;
5348
				}
5349
			}
5350
		}
5351
5352
		// Last, let's check if sync is erroring due to an IDC. If so, set the site to staging mode.
5353
		if ( ! $is_staging && self::validate_sync_error_idc_option() ) {
5354
			$is_staging = true;
5355
		}
5356
5357
		/**
5358
		 * Filters is_staging_site check.
5359
		 *
5360
		 * @since 3.9.0
5361
		 *
5362
		 * @param bool $is_staging If the current site is a staging site.
5363
		 */
5364
		return apply_filters( 'jetpack_is_staging_site', $is_staging );
5365
	}
5366
5367
	/**
5368
	 * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
5369
	 *
5370
	 * @return bool
5371
	 */
5372
	public static function validate_sync_error_idc_option() {
5373
		$is_valid = false;
5374
5375
		$idc_allowed = get_transient( 'jetpack_idc_allowed' );
5376
		if ( false === $idc_allowed ) {
5377
			$response = wp_remote_get( 'https://jetpack.com/is-idc-allowed/' );
5378
			if ( 200 === (int) wp_remote_retrieve_response_code( $response ) ) {
5379
				$json = json_decode( wp_remote_retrieve_body( $response ) );
5380
				$idc_allowed = isset( $json, $json->result ) && $json->result ? '1' : '0';
5381
				$transient_duration = HOUR_IN_SECONDS;
5382
			} else {
5383
				// If the request failed for some reason, then assume IDC is allowed and set shorter transient.
5384
				$idc_allowed = '1';
5385
				$transient_duration = 5 * MINUTE_IN_SECONDS;
5386
			}
5387
5388
			set_transient( 'jetpack_idc_allowed', $idc_allowed, $transient_duration );
5389
		}
5390
5391
		// Is the site opted in and does the stored sync_error_idc option match what we now generate?
5392
		$sync_error = Jetpack_Options::get_option( 'sync_error_idc' );
5393
		$local_options = self::get_sync_error_idc_option();
5394
		if ( $idc_allowed && $sync_error && self::sync_idc_optin() ) {
5395
			if ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) {
5396
				$is_valid = true;
5397
			}
5398
		}
5399
5400
		/**
5401
		 * Filters whether the sync_error_idc option is valid.
5402
		 *
5403
		 * @since 4.4.0
5404
		 *
5405
		 * @param bool $is_valid If the sync_error_idc is valid or not.
5406
		 */
5407
		$is_valid = (bool) apply_filters( 'jetpack_sync_error_idc_validation', $is_valid );
5408
5409
		if ( ! $idc_allowed || ( ! $is_valid && $sync_error ) ) {
5410
			// Since the option exists, and did not validate, delete it
5411
			Jetpack_Options::delete_option( 'sync_error_idc' );
5412
		}
5413
5414
		return $is_valid;
5415
	}
5416
5417
	/**
5418
	 * Normalizes a url by doing three things:
5419
	 *  - Strips protocol
5420
	 *  - Strips www
5421
	 *  - Adds a trailing slash
5422
	 *
5423
	 * @since 4.4.0
5424
	 * @param string $url
5425
	 * @return WP_Error|string
5426
	 */
5427
	public static function normalize_url_protocol_agnostic( $url ) {
5428
		$parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
5429
		if ( ! $parsed_url ) {
5430
			return new WP_Error( 'cannot_parse_url', sprintf( esc_html__( 'Cannot parse URL %s', 'jetpack' ), $url ) );
5431
		}
5432
5433
		// Strip www and protocols
5434
		$url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
5435
		return $url;
5436
	}
5437
5438
	/**
5439
	 * Gets the value that is to be saved in the jetpack_sync_error_idc option.
5440
	 *
5441
	 * @since 4.4.0
5442
	 *
5443
	 * @param array $response
5444
	 * @return array Array of the local urls, wpcom urls, and error code
5445
	 */
5446
	public static function get_sync_error_idc_option( $response = array() ) {
5447
		$local_options = array(
5448
			'home' => get_home_url(),
5449
			'siteurl' => get_site_url(),
5450
		);
5451
5452
		$options = array_merge( $local_options, $response );
5453
5454
		$returned_values = array();
5455
		foreach( $options as $key => $option ) {
5456
			if ( 'error_code' === $key ) {
5457
				$returned_values[ $key ] = $option;
5458
				continue;
5459
			}
5460
5461
			if ( is_wp_error( $normalized_url = self::normalize_url_protocol_agnostic( $option ) ) ) {
5462
				continue;
5463
			}
5464
5465
			$returned_values[ $key ] = $normalized_url;
5466
		}
5467
5468
		return $returned_values;
5469
	}
5470
5471
	/**
5472
	 * Returns the value of the jetpack_sync_idc_optin filter, or constant.
5473
	 * If set to true, the site will be put into staging mode.
5474
	 *
5475
	 * @since 4.3.2
5476
	 * @return bool
5477
	 */
5478
	public static function sync_idc_optin() {
5479
		if ( Jetpack_Constants::is_defined( 'JETPACK_SYNC_IDC_OPTIN' ) ) {
5480
			$default = Jetpack_Constants::get_constant( 'JETPACK_SYNC_IDC_OPTIN' );
5481
		} else {
5482
			$default = ! Jetpack_Constants::is_defined( 'SUNRISE' ) && ! is_multisite();
5483
		}
5484
5485
		/**
5486
		 * Allows sites to optin to IDC mitigation which blocks the site from syncing to WordPress.com when the home
5487
		 * URL or site URL do not match what WordPress.com expects. The default value is either false, or the value of
5488
		 * JETPACK_SYNC_IDC_OPTIN constant if set.
5489
		 *
5490
		 * @since 4.3.2
5491
		 *
5492
		 * @param bool $default Whether the site is opted in to IDC mitigation.
5493
		 */
5494
		return (bool) apply_filters( 'jetpack_sync_idc_optin', $default );
5495
	}
5496
5497
	/**
5498
	 * Maybe Use a .min.css stylesheet, maybe not.
5499
	 *
5500
	 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
5501
	 */
5502
	public static function maybe_min_asset( $url, $path, $plugin ) {
5503
		// Short out on things trying to find actual paths.
5504
		if ( ! $path || empty( $plugin ) ) {
5505
			return $url;
5506
		}
5507
5508
		// Strip out the abspath.
5509
		$base = dirname( plugin_basename( $plugin ) );
5510
5511
		// Short out on non-Jetpack assets.
5512
		if ( 'jetpack/' !== substr( $base, 0, 8 ) ) {
5513
			return $url;
5514
		}
5515
5516
		// File name parsing.
5517
		$file              = "{$base}/{$path}";
5518
		$full_path         = JETPACK__PLUGIN_DIR . substr( $file, 8 );
5519
		$file_name         = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
5520
		$file_name_parts_r = array_reverse( explode( '.', $file_name ) );
5521
		$extension         = array_shift( $file_name_parts_r );
5522
5523
		if ( in_array( strtolower( $extension ), array( 'css', 'js' ) ) ) {
5524
			// Already pointing at the minified version.
5525
			if ( 'min' === $file_name_parts_r[0] ) {
5526
				return $url;
5527
			}
5528
5529
			$min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
5530
			if ( file_exists( $min_full_path ) ) {
5531
				$url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
5532
			}
5533
		}
5534
5535
		return $url;
5536
	}
5537
5538
	/**
5539
	 * Maybe inlines a stylesheet.
5540
	 *
5541
	 * If you'd like to inline a stylesheet instead of printing a link to it,
5542
	 * wp_style_add_data( 'handle', 'jetpack-inline', true );
5543
	 *
5544
	 * Attached to `style_loader_tag` filter.
5545
	 *
5546
	 * @param string $tag The tag that would link to the external asset.
5547
	 * @param string $handle The registered handle of the script in question.
5548
	 *
5549
	 * @return string
5550
	 */
5551
	public static function maybe_inline_style( $tag, $handle ) {
5552
		global $wp_styles;
5553
		$item = $wp_styles->registered[ $handle ];
5554
5555
		if ( ! isset( $item->extra['jetpack-inline'] ) || ! $item->extra['jetpack-inline'] ) {
5556
			return $tag;
5557
		}
5558
5559
		if ( preg_match( '# href=\'([^\']+)\' #i', $tag, $matches ) ) {
5560
			$href = $matches[1];
5561
			// Strip off query string
5562
			if ( $pos = strpos( $href, '?' ) ) {
5563
				$href = substr( $href, 0, $pos );
5564
			}
5565
			// Strip off fragment
5566
			if ( $pos = strpos( $href, '#' ) ) {
5567
				$href = substr( $href, 0, $pos );
5568
			}
5569
		} else {
5570
			return $tag;
5571
		}
5572
5573
		$plugins_dir = plugin_dir_url( JETPACK__PLUGIN_FILE );
5574
		if ( $plugins_dir !== substr( $href, 0, strlen( $plugins_dir ) ) ) {
5575
			return $tag;
5576
		}
5577
5578
		// If this stylesheet has a RTL version, and the RTL version replaces normal...
5579
		if ( isset( $item->extra['rtl'] ) && 'replace' === $item->extra['rtl'] && is_rtl() ) {
5580
			// And this isn't the pass that actually deals with the RTL version...
5581
			if ( false === strpos( $tag, " id='$handle-rtl-css' " ) ) {
5582
				// Short out, as the RTL version will deal with it in a moment.
5583
				return $tag;
5584
			}
5585
		}
5586
5587
		$file = JETPACK__PLUGIN_DIR . substr( $href, strlen( $plugins_dir ) );
5588
		$css  = Jetpack::absolutize_css_urls( file_get_contents( $file ), $href );
5589
		if ( $css ) {
5590
			$tag = "<!-- Inline {$item->handle} -->\r\n";
5591
			if ( empty( $item->extra['after'] ) ) {
5592
				wp_add_inline_style( $handle, $css );
5593
			} else {
5594
				array_unshift( $item->extra['after'], $css );
5595
				wp_style_add_data( $handle, 'after', $item->extra['after'] );
5596
			}
5597
		}
5598
5599
		return $tag;
5600
	}
5601
5602
	/**
5603
	 * Loads a view file from the views
5604
	 *
5605
	 * Data passed in with the $data parameter will be available in the
5606
	 * template file as $data['value']
5607
	 *
5608
	 * @param string $template - Template file to load
5609
	 * @param array $data - Any data to pass along to the template
5610
	 * @return boolean - If template file was found
5611
	 **/
5612
	public function load_view( $template, $data = array() ) {
5613
		$views_dir = JETPACK__PLUGIN_DIR . 'views/';
5614
5615
		if( file_exists( $views_dir . $template ) ) {
5616
			require_once( $views_dir . $template );
5617
			return true;
5618
		}
5619
5620
		error_log( "Jetpack: Unable to find view file $views_dir$template" );
5621
		return false;
5622
	}
5623
5624
	/**
5625
	 * Throws warnings for deprecated hooks to be removed from Jetpack
5626
	 */
5627
	public function deprecated_hooks() {
5628
		global $wp_filter;
5629
5630
		/*
5631
		 * Format:
5632
		 * deprecated_filter_name => replacement_name
5633
		 *
5634
		 * If there is no replacement, use null for replacement_name
5635
		 */
5636
		$deprecated_list = array(
5637
			'jetpack_bail_on_shortcode'                              => 'jetpack_shortcodes_to_include',
5638
			'wpl_sharing_2014_1'                                     => null,
5639
			'jetpack-tools-to-include'                               => 'jetpack_tools_to_include',
5640
			'jetpack_identity_crisis_options_to_check'               => null,
5641
			'update_option_jetpack_single_user_site'                 => null,
5642
			'audio_player_default_colors'                            => null,
5643
			'add_option_jetpack_featured_images_enabled'             => null,
5644
			'add_option_jetpack_update_details'                      => null,
5645
			'add_option_jetpack_updates'                             => null,
5646
			'add_option_jetpack_network_name'                        => null,
5647
			'add_option_jetpack_network_allow_new_registrations'     => null,
5648
			'add_option_jetpack_network_add_new_users'               => null,
5649
			'add_option_jetpack_network_site_upload_space'           => null,
5650
			'add_option_jetpack_network_upload_file_types'           => null,
5651
			'add_option_jetpack_network_enable_administration_menus' => null,
5652
			'add_option_jetpack_is_multi_site'                       => null,
5653
			'add_option_jetpack_is_main_network'                     => null,
5654
			'add_option_jetpack_main_network_site'                   => null,
5655
			'jetpack_sync_all_registered_options'                    => null,
5656
			'jetpack_has_identity_crisis'                            => 'jetpack_sync_error_idc_validation',
5657
			'jetpack_is_post_mailable'                               => null,
5658
		);
5659
5660
		// This is a silly loop depth. Better way?
5661
		foreach( $deprecated_list AS $hook => $hook_alt ) {
5662
			if ( has_action( $hook ) ) {
5663
				foreach( $wp_filter[ $hook ] AS $func => $values ) {
5664
					foreach( $values AS $hooked ) {
5665
						if ( is_callable( $hooked['function'] ) ) {
5666
							$function_name = 'an anonymous function';
5667
						} else {
5668
							$function_name = $hooked['function'];
5669
						}
5670
						_deprecated_function( $hook . ' used for ' . $function_name, null, $hook_alt );
5671
					}
5672
				}
5673
			}
5674
		}
5675
	}
5676
5677
	/**
5678
	 * Converts any url in a stylesheet, to the correct absolute url.
5679
	 *
5680
	 * Considerations:
5681
	 *  - Normal, relative URLs     `feh.png`
5682
	 *  - Data URLs                 `data:image/gif;base64,eh129ehiuehjdhsa==`
5683
	 *  - Schema-agnostic URLs      `//domain.com/feh.png`
5684
	 *  - Absolute URLs             `http://domain.com/feh.png`
5685
	 *  - Domain root relative URLs `/feh.png`
5686
	 *
5687
	 * @param $css string: The raw CSS -- should be read in directly from the file.
5688
	 * @param $css_file_url : The URL that the file can be accessed at, for calculating paths from.
5689
	 *
5690
	 * @return mixed|string
5691
	 */
5692
	public static function absolutize_css_urls( $css, $css_file_url ) {
5693
		$pattern = '#url\((?P<path>[^)]*)\)#i';
5694
		$css_dir = dirname( $css_file_url );
5695
		$p       = parse_url( $css_dir );
5696
		$domain  = sprintf(
5697
					'%1$s//%2$s%3$s%4$s',
5698
					isset( $p['scheme'] )           ? "{$p['scheme']}:" : '',
5699
					isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
5700
					$p['host'],
5701
					isset( $p['port'] )             ? ":{$p['port']}" : ''
5702
				);
5703
5704
		if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
5705
			$find = $replace = array();
5706
			foreach ( $matches as $match ) {
5707
				$url = trim( $match['path'], "'\" \t" );
5708
5709
				// If this is a data url, we don't want to mess with it.
5710
				if ( 'data:' === substr( $url, 0, 5 ) ) {
5711
					continue;
5712
				}
5713
5714
				// If this is an absolute or protocol-agnostic url,
5715
				// we don't want to mess with it.
5716
				if ( preg_match( '#^(https?:)?//#i', $url ) ) {
5717
					continue;
5718
				}
5719
5720
				switch ( substr( $url, 0, 1 ) ) {
5721
					case '/':
5722
						$absolute = $domain . $url;
5723
						break;
5724
					default:
5725
						$absolute = $css_dir . '/' . $url;
5726
				}
5727
5728
				$find[]    = $match[0];
5729
				$replace[] = sprintf( 'url("%s")', $absolute );
5730
			}
5731
			$css = str_replace( $find, $replace, $css );
5732
		}
5733
5734
		return $css;
5735
	}
5736
5737
	/**
5738
	 * This methods removes all of the registered css files on the front end
5739
	 * from Jetpack in favor of using a single file. In effect "imploding"
5740
	 * all the files into one file.
5741
	 *
5742
	 * Pros:
5743
	 * - Uses only ONE css asset connection instead of 15
5744
	 * - Saves a minimum of 56k
5745
	 * - Reduces server load
5746
	 * - Reduces time to first painted byte
5747
	 *
5748
	 * Cons:
5749
	 * - Loads css for ALL modules. However all selectors are prefixed so it
5750
	 *		should not cause any issues with themes.
5751
	 * - Plugins/themes dequeuing styles no longer do anything. See
5752
	 *		jetpack_implode_frontend_css filter for a workaround
5753
	 *
5754
	 * For some situations developers may wish to disable css imploding and
5755
	 * instead operate in legacy mode where each file loads seperately and
5756
	 * can be edited individually or dequeued. This can be accomplished with
5757
	 * the following line:
5758
	 *
5759
	 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
5760
	 *
5761
	 * @since 3.2
5762
	 **/
5763
	public function implode_frontend_css( $travis_test = false ) {
5764
		$do_implode = true;
5765
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
5766
			$do_implode = false;
5767
		}
5768
5769
		/**
5770
		 * Allow CSS to be concatenated into a single jetpack.css file.
5771
		 *
5772
		 * @since 3.2.0
5773
		 *
5774
		 * @param bool $do_implode Should CSS be concatenated? Default to true.
5775
		 */
5776
		$do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
5777
5778
		// Do not use the imploded file when default behaviour was altered through the filter
5779
		if ( ! $do_implode ) {
5780
			return;
5781
		}
5782
5783
		// We do not want to use the imploded file in dev mode, or if not connected
5784
		if ( Jetpack::is_development_mode() || ! self::is_active() ) {
5785
			if ( ! $travis_test ) {
5786
				return;
5787
			}
5788
		}
5789
5790
		// Do not use the imploded file if sharing css was dequeued via the sharing settings screen
5791
		if ( get_option( 'sharedaddy_disable_resources' ) ) {
5792
			return;
5793
		}
5794
5795
		/*
5796
		 * Now we assume Jetpack is connected and able to serve the single
5797
		 * file.
5798
		 *
5799
		 * In the future there will be a check here to serve the file locally
5800
		 * or potentially from the Jetpack CDN
5801
		 *
5802
		 * For now:
5803
		 * - Enqueue a single imploded css file
5804
		 * - Zero out the style_loader_tag for the bundled ones
5805
		 * - Be happy, drink scotch
5806
		 */
5807
5808
		add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
5809
5810
		$version = Jetpack::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
5811
5812
		wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
5813
		wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
5814
	}
5815
5816
	function concat_remove_style_loader_tag( $tag, $handle ) {
5817
		if ( in_array( $handle, $this->concatenated_style_handles ) ) {
5818
			$tag = '';
5819
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
5820
				$tag = "<!-- `" . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
5821
			}
5822
		}
5823
5824
		return $tag;
5825
	}
5826
5827
	/*
5828
	 * Check the heartbeat data
5829
	 *
5830
	 * Organizes the heartbeat data by severity.  For example, if the site
5831
	 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
5832
	 *
5833
	 * Data will be added to "caution" array, if it either:
5834
	 *  - Out of date Jetpack version
5835
	 *  - Out of date WP version
5836
	 *  - Out of date PHP version
5837
	 *
5838
	 * $return array $filtered_data
5839
	 */
5840
	public static function jetpack_check_heartbeat_data() {
5841
		$raw_data = Jetpack_Heartbeat::generate_stats_array();
5842
5843
		$good    = array();
5844
		$caution = array();
5845
		$bad     = array();
5846
5847
		foreach ( $raw_data as $stat => $value ) {
5848
5849
			// Check jetpack version
5850
			if ( 'version' == $stat ) {
5851
				if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
5852
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__VERSION;
5853
					continue;
5854
				}
5855
			}
5856
5857
			// Check WP version
5858
			if ( 'wp-version' == $stat ) {
5859
				if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
5860
					$caution[ $stat ] = $value . " - min supported is " . JETPACK__MINIMUM_WP_VERSION;
5861
					continue;
5862
				}
5863
			}
5864
5865
			// Check PHP version
5866
			if ( 'php-version' == $stat ) {
5867
				if ( version_compare( PHP_VERSION, '5.2.4', '<' ) ) {
5868
					$caution[ $stat ] = $value . " - min supported is 5.2.4";
5869
					continue;
5870
				}
5871
			}
5872
5873
			// Check ID crisis
5874
			if ( 'identitycrisis' == $stat ) {
5875
				if ( 'yes' == $value ) {
5876
					$bad[ $stat ] = $value;
5877
					continue;
5878
				}
5879
			}
5880
5881
			// The rest are good :)
5882
			$good[ $stat ] = $value;
5883
		}
5884
5885
		$filtered_data = array(
5886
			'good'    => $good,
5887
			'caution' => $caution,
5888
			'bad'     => $bad
5889
		);
5890
5891
		return $filtered_data;
5892
	}
5893
5894
5895
	/*
5896
	 * This method is used to organize all options that can be reset
5897
	 * without disconnecting Jetpack.
5898
	 *
5899
	 * It is used in class.jetpack-cli.php to reset options
5900
	 *
5901
	 * @return array of options to delete.
5902
	 */
5903
	public static function get_jetpack_options_for_reset() {
5904
		$jetpack_options            = Jetpack_Options::get_option_names();
5905
		$jetpack_options_non_compat = Jetpack_Options::get_option_names( 'non_compact' );
5906
		$jetpack_options_private    = Jetpack_Options::get_option_names( 'private' );
5907
5908
		$all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
5909
5910
		// A manual build of the wp options
5911
		$wp_options = array(
5912
			'sharing-options',
5913
			'disabled_likes',
5914
			'disabled_reblogs',
5915
			'jetpack_comments_likes_enabled',
5916
			'wp_mobile_excerpt',
5917
			'wp_mobile_featured_images',
5918
			'wp_mobile_app_promos',
5919
			'stats_options',
5920
			'stats_dashboard_widget',
5921
			'safecss_preview_rev',
5922
			'safecss_rev',
5923
			'safecss_revision_migrated',
5924
			'nova_menu_order',
5925
			'jetpack_portfolio',
5926
			'jetpack_portfolio_posts_per_page',
5927
			'jetpack_testimonial',
5928
			'jetpack_testimonial_posts_per_page',
5929
			'wp_mobile_custom_css',
5930
			'sharedaddy_disable_resources',
5931
			'sharing-options',
5932
			'sharing-services',
5933
			'site_icon_temp_data',
5934
			'featured-content',
5935
			'site_logo',
5936
			'jetpack_dismissed_notices',
5937
		);
5938
5939
		// Flag some Jetpack options as unsafe
5940
		$unsafe_options = array(
5941
			'id',                           // (int)    The Client ID/WP.com Blog ID of this site.
5942
			'master_user',                  // (int)    The local User ID of the user who connected this site to jetpack.wordpress.com.
5943
			'version',                      // (string) Used during upgrade procedure to auto-activate new modules. version:time
5944
			'jumpstart',                    // (string) A flag for whether or not to show the Jump Start.  Accepts: new_connection, jumpstart_activated, jetpack_action_taken, jumpstart_dismissed.
5945
5946
			// non_compact
5947
			'activated',
5948
5949
			// private
5950
			'register',
5951
			'blog_token',                  // (string) The Client Secret/Blog Token of this site.
5952
			'user_token',                  // (string) The User Token of this site. (deprecated)
5953
			'user_tokens'
5954
		);
5955
5956
		// Remove the unsafe Jetpack options
5957
		foreach ( $unsafe_options as $unsafe_option ) {
5958
			if ( false !== ( $key = array_search( $unsafe_option, $all_jp_options ) ) ) {
5959
				unset( $all_jp_options[ $key ] );
5960
			}
5961
		}
5962
5963
		$options = array(
5964
			'jp_options' => $all_jp_options,
5965
			'wp_options' => $wp_options
5966
		);
5967
5968
		return $options;
5969
	}
5970
5971
	/**
5972
	 * Check if an option of a Jetpack module has been updated.
5973
	 *
5974
	 * If any module option has been updated before Jump Start has been dismissed,
5975
	 * update the 'jumpstart' option so we can hide Jump Start.
5976
	 *
5977
	 * @param string $option_name
5978
	 *
5979
	 * @return bool
5980
	 */
5981
	public static function jumpstart_has_updated_module_option( $option_name = '' ) {
5982
		// Bail if Jump Start has already been dismissed
5983
		if ( 'new_connection' !== Jetpack_Options::get_option( 'jumpstart' ) ) {
5984
			return false;
5985
		}
5986
5987
		$jetpack = Jetpack::init();
5988
5989
		// Manual build of module options
5990
		$option_names = self::get_jetpack_options_for_reset();
5991
5992
		if ( in_array( $option_name, $option_names['wp_options'] ) ) {
5993
			Jetpack_Options::update_option( 'jumpstart', 'jetpack_action_taken' );
5994
5995
			//Jump start is being dismissed send data to MC Stats
5996
			$jetpack->stat( 'jumpstart', 'manual,'.$option_name );
5997
5998
			$jetpack->do_stats( 'server_side' );
5999
		}
6000
6001
	}
6002
6003
	/*
6004
	 * Strip http:// or https:// from a url, replaces forward slash with ::,
6005
	 * so we can bring them directly to their site in calypso.
6006
	 *
6007
	 * @param string | url
6008
	 * @return string | url without the guff
6009
	 */
6010
	public static function build_raw_urls( $url ) {
6011
		$strip_http = '/.*?:\/\//i';
6012
		$url = preg_replace( $strip_http, '', $url  );
6013
		$url = str_replace( '/', '::', $url );
6014
		return $url;
6015
	}
6016
6017
	/**
6018
	 * Stores and prints out domains to prefetch for page speed optimization.
6019
	 *
6020
	 * @param mixed $new_urls
6021
	 */
6022
	public static function dns_prefetch( $new_urls = null ) {
6023
		static $prefetch_urls = array();
6024
		if ( empty( $new_urls ) && ! empty( $prefetch_urls ) ) {
6025
			echo "\r\n";
6026
			foreach ( $prefetch_urls as $this_prefetch_url ) {
6027
				printf( "<link rel='dns-prefetch' href='%s'>\r\n", esc_attr( $this_prefetch_url ) );
6028
			}
6029
		} elseif ( ! empty( $new_urls ) ) {
6030
			if ( ! has_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) ) ) {
6031
				add_action( 'wp_head', array( __CLASS__, __FUNCTION__ ) );
6032
			}
6033
			foreach ( (array) $new_urls as $this_new_url ) {
6034
				$prefetch_urls[] = strtolower( untrailingslashit( preg_replace( '#^https?://#i', '//', $this_new_url ) ) );
6035
			}
6036
			$prefetch_urls = array_unique( $prefetch_urls );
6037
		}
6038
	}
6039
6040
	public function wp_dashboard_setup() {
6041
		if ( self::is_active() ) {
6042
			add_action( 'jetpack_dashboard_widget', array( __CLASS__, 'dashboard_widget_footer' ), 999 );
6043
			$widget_title = __( 'Site Stats', 'jetpack' );
6044
		} elseif ( ! self::is_development_mode() && current_user_can( 'jetpack_connect' ) ) {
6045
			add_action( 'jetpack_dashboard_widget', array( $this, 'dashboard_widget_connect_to_wpcom' ) );
6046
			$widget_title = __( 'Please Connect Jetpack', 'jetpack' );
6047
		}
6048
6049
		if ( has_action( 'jetpack_dashboard_widget' ) ) {
6050
			wp_add_dashboard_widget(
6051
				'jetpack_summary_widget',
6052
				$widget_title,
6053
				array( __CLASS__, 'dashboard_widget' )
6054
			);
6055
			wp_enqueue_style( 'jetpack-dashboard-widget', plugins_url( 'css/dashboard-widget.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
6056
6057
			// If we're inactive and not in development mode, sort our box to the top.
6058
			if ( ! self::is_active() && ! self::is_development_mode() ) {
6059
				global $wp_meta_boxes;
6060
6061
				$dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
6062
				$ours      = array( 'jetpack_summary_widget' => $dashboard['jetpack_summary_widget'] );
6063
6064
				$wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard );
6065
			}
6066
		}
6067
	}
6068
6069
	/**
6070
	 * @param mixed $result Value for the user's option
6071
	 * @return mixed
6072
	 */
6073
	function get_user_option_meta_box_order_dashboard( $sorted ) {
6074
		if ( ! is_array( $sorted ) ) {
6075
			return $sorted;
6076
		}
6077
6078
		foreach ( $sorted as $box_context => $ids ) {
6079
			if ( false === strpos( $ids, 'dashboard_stats' ) ) {
6080
				// If the old id isn't anywhere in the ids, don't bother exploding and fail out.
6081
				continue;
6082
			}
6083
6084
			$ids_array = explode( ',', $ids );
6085
			$key = array_search( 'dashboard_stats', $ids_array );
6086
6087
			if ( false !== $key ) {
6088
				// If we've found that exact value in the option (and not `google_dashboard_stats` for example)
6089
				$ids_array[ $key ] = 'jetpack_summary_widget';
6090
				$sorted[ $box_context ] = implode( ',', $ids_array );
6091
				// We've found it, stop searching, and just return.
6092
				break;
6093
			}
6094
		}
6095
6096
		return $sorted;
6097
	}
6098
6099
	public static function dashboard_widget() {
6100
		/**
6101
		 * Fires when the dashboard is loaded.
6102
		 *
6103
		 * @since 3.4.0
6104
		 */
6105
		do_action( 'jetpack_dashboard_widget' );
6106
	}
6107
6108
	public static function dashboard_widget_footer() {
6109
		?>
6110
		<footer>
6111
6112
		<div class="protect">
6113
			<?php if ( Jetpack::is_module_active( 'protect' ) ) : ?>
6114
				<h3><?php echo number_format_i18n( get_site_option( 'jetpack_protect_blocked_attempts', 0 ) ); ?></h3>
6115
				<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>
6116
			<?php elseif ( current_user_can( 'jetpack_activate_modules' ) && ! self::is_development_mode() ) : ?>
6117
				<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' ); ?>">
6118
					<?php esc_html_e( 'Activate Protect', 'jetpack' ); ?>
6119
				</a>
6120
			<?php else : ?>
6121
				<?php esc_html_e( 'Protect is inactive.', 'jetpack' ); ?>
6122
			<?php endif; ?>
6123
		</div>
6124
6125
		<div class="akismet">
6126
			<?php if ( is_plugin_active( 'akismet/akismet.php' ) ) : ?>
6127
				<h3><?php echo number_format_i18n( get_option( 'akismet_spam_count', 0 ) ); ?></h3>
6128
				<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>
6129
			<?php elseif ( current_user_can( 'activate_plugins' ) && ! is_wp_error( validate_plugin( 'akismet/akismet.php' ) ) ) : ?>
6130
				<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">
6131
					<?php esc_html_e( 'Activate Akismet', 'jetpack' ); ?>
6132
				</a>
6133
			<?php else : ?>
6134
				<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>
6135
			<?php endif; ?>
6136
		</div>
6137
6138
		</footer>
6139
		<?php
6140
	}
6141
6142
	public function dashboard_widget_connect_to_wpcom() {
6143
		if ( Jetpack::is_active() || Jetpack::is_development_mode() || ! current_user_can( 'jetpack_connect' ) ) {
6144
			return;
6145
		}
6146
		?>
6147
		<div class="wpcom-connect">
6148
			<div class="jp-emblem">
6149
			<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">
6150
				<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"/>
6151
			</svg>
6152
			</div>
6153
			<h3><?php esc_html_e( 'Please Connect Jetpack', 'jetpack' ); ?></h3>
6154
			<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>
6155
6156
			<div class="actions">
6157
				<a href="<?php echo $this->build_connect_url( false, false, 'widget-btn' ); ?>" class="button button-primary">
6158
					<?php esc_html_e( 'Connect Jetpack', 'jetpack' ); ?>
6159
				</a>
6160
			</div>
6161
		</div>
6162
		<?php
6163
	}
6164
6165
	/**
6166
	 * Return string containing the Jetpack logo.
6167
	 *
6168
	 * @since 3.9.0
6169
	 *
6170
	 * @return string
6171
	 */
6172
	public static function get_jp_emblem() {
6173
		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>';
6174
	}
6175
6176
	/*
6177
	 * Adds a "blank" column in the user admin table to display indication of user connection.
6178
	 */
6179
	function jetpack_icon_user_connected( $columns ) {
6180
		$columns['user_jetpack'] = '';
6181
		return $columns;
6182
	}
6183
6184
	/*
6185
	 * Show Jetpack icon if the user is linked.
6186
	 */
6187
	function jetpack_show_user_connected_icon( $val, $col, $user_id ) {
6188
		if ( 'user_jetpack' == $col && Jetpack::is_user_connected( $user_id ) ) {
6189
			$emblem_html = sprintf(
6190
				'<a title="%1$s" class="jp-emblem-user-admin">%2$s</a>',
6191
				esc_attr__( 'This user is linked and ready to fly with Jetpack.', 'jetpack' ),
6192
				Jetpack::get_jp_emblem()
6193
			);
6194
			return $emblem_html;
6195
		}
6196
6197
		return $val;
6198
	}
6199
6200
	/*
6201
	 * Style the Jetpack user column
6202
	 */
6203
	function jetpack_user_col_style() {
6204
		global $current_screen;
6205
		if ( ! empty( $current_screen->base ) && 'users' == $current_screen->base ) { ?>
6206
			<style>
6207
				.fixed .column-user_jetpack {
6208
					width: 21px;
6209
				}
6210
				.jp-emblem-user-admin svg {
6211
					width: 20px;
6212
					height: 20px;
6213
				}
6214
				.jp-emblem-user-admin path {
6215
					fill: #8cc258;
6216
				}
6217
			</style>
6218
		<?php }
6219
	}
6220
}
6221