PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 5.3.4
Jetpack – WP Security, Backup, Speed, & Growth v5.3.4
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / class.jetpack.php

class.jetpack.php in Jetpack – WP Security, Backup, Speed, & Growth 5.3.4, at class.jetpack.php

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