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

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