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

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