PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.3.4
Jetpack – WP Security, Backup, Speed, & Growth v6.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 6.3.4, at class.jetpack.php

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