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

4,419 lines 150.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 Options:
5 jetpack_options (array)
6 An array of options.
7 @see Jetpack_Options::get_option_names()
8
9 jetpack_register (string)
10 Temporary verification secrets.
11
12 jetpack_activated (int)
13 1: the plugin was activated normally
14 2: the plugin was activated on this site because of a network-wide activation
15 3: the plugin was auto-installed
16 4: the plugin was manually disconnected (but is still installed)
17
18 jetpack_active_modules (array)
19 Array of active module slugs.
20
21 jetpack_do_activate (bool)
22 Flag for "activating" the plugin on sites where the activation hook never fired (auto-installs)
23 */
24
25 class Jetpack {
26 var $xmlrpc_server = null;
27
28 private $xmlrpc_verification = null;
29
30 var $HTTP_RAW_POST_DATA = null; // copy of $GLOBALS['HTTP_RAW_POST_DATA']
31
32 var $plugins_to_deactivate = array(
33 'stats' => array( 'stats/stats.php', 'WordPress.com Stats' ),
34 'shortlinks' => array( 'stats/stats.php', 'WordPress.com Stats' ),
35 'sharedaddy' => array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
36 'twitter-widget' => array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
37 'after-the-deadline' => array( 'after-the-deadline/after-the-deadline.php', 'After The Deadline' ),
38 'contact-form' => array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
39 'custom-css' => array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
40 'random-redirect' => array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
41 'videopress' => array( 'video/video.php', 'VideoPress' ),
42 'widget-visibility' => array( 'jetpack-widget-visibility/widget-visibility.php', 'Jetpack Widget Visibility' ),
43 'widget-visibility' => array( 'widget-visibility-without-jetpack/widget-visibility-without-jetpack.php', 'Widget Visibility Without Jetpack' ),
44 'sharedaddy' => array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
45 'omnisearch' => array( 'jetpack-omnisearch/omnisearch.php', 'Jetpack Omnisearch' ),
46 'gravatar-hovercards' => array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
47 'latex' => array( 'wp-latex/wp-latex.php', 'WP LaTeX' ),
48 );
49
50 var $capability_translations = array(
51 'administrator' => 'manage_options',
52 'editor' => 'edit_others_posts',
53 'author' => 'publish_posts',
54 'contributor' => 'edit_posts',
55 'subscriber' => 'read',
56 );
57
58 /**
59 * Map of modules that have conflicts with plugins and should not be auto-activated
60 * if the plugins are active. Used by filter_default_modules
61 *
62 * Plugin Authors: If you'd like to prevent a single module from auto-activating,
63 * change `module-slug` and add this to your plugin:
64 *
65 * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
66 * function my_jetpack_get_default_modules( $modules ) {
67 * return array_diff( $modules, array( 'module-slug' ) );
68 * }
69 *
70 * @var array
71 */
72 private $conflicting_plugins = array(
73 'comments' => array(
74 'Intense Debate' => 'intensedebate/intensedebate.php',
75 'Disqus' => 'disqus-comment-system/disqus.php',
76 'Livefyre' => 'livefyre-comments/livefyre.php',
77 'Comments Evolved for WordPress' => 'gplus-comments/comments-evolved.php',
78 'Google+ Comments' => 'google-plus-comments/google-plus-comments.php',
79 ),
80 'contact-form' => array(
81 'Contact Form 7' => 'contact-form-7/wp-contact-form-7.php',
82 'Gravity Forms' => 'gravityforms/gravityforms.php',
83 'Contact Form Plugin' => 'contact-form-plugin/contact_form.php',
84 'Easy Contact Forms' => 'easy-contact-forms/easy-contact-forms.php',
85 'Fast Secure Contact Form' => 'si-contact-form/si-contact-form.php',
86 ),
87 'gplus-authorship' => array(
88 'WP SEO by Yoast' => 'wordpress-seo/wp-seo.php',
89 ),
90 'minileven' => array(
91 'WPtouch' => 'wptouch/wptouch.php',
92 ),
93 'latex' => array(
94 'LaTeX for WordPress' => 'latex/latex.php',
95 'Youngwhans Simple Latex' => 'youngwhans-simple-latex/yw-latex.php',
96 'Easy WP LaTeX' => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
97 'MathJax-LaTeX' => 'mathjax-latex/mathjax-latex.php',
98 'Enable Latex' => 'enable-latex/enable-latex.php',
99 'WP QuickLaTeX' => 'wp-quicklatex/wp-quicklatex.php',
100 ),
101 'random-redirect' => array(
102 'Random Redirect 2' => 'random-redirect-2/random-redirect.php',
103 ),
104 'related-posts' => array(
105 'YARPP' => 'yet-another-related-posts-plugin/yarpp.php',
106 'WordPress Related Posts' => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
107 'nrelate Related Content' => 'nrelate-related-content/nrelate-related.php',
108 'Contextual Related Posts' => 'contextual-related-posts/contextual-related-posts.php',
109 'Related Posts for WordPress' => 'microkids-related-posts/microkids-related-posts.php',
110 'outbrain' => 'outbrain/outbrain.php',
111 'Shareaholic' => 'shareaholic/shareaholic.php',
112 'Sexybookmarks' => 'sexybookmarks/shareaholic.php',
113 ),
114 'sharedaddy' => array(
115 'AddThis' => 'addthis/addthis_social_widget.php',
116 'Add To Any' => 'add-to-any/add-to-any.php',
117 'ShareThis' => 'share-this/sharethis.php',
118 'Shareaholic' => 'shareaholic/shareaholic.php',
119 ),
120 'verification-tools' => array(
121 'WordPress SEO by Yoast' => 'wordpress-seo/wp-seo.php',
122 'WordPress SEO Premium by Yoast' => 'wordpress-seo-premium/wp-seo-premium.php',
123 'All in One SEO Pack' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
124 ),
125 'widget-visibility' => array(
126 'Widget Logic' => 'widget-logic/widget_logic.php',
127 'Dynamic Widgets' => 'dynamic-widgets/dynamic-widgets.php',
128 ),
129 );
130
131 /**
132 * Plugins for which we turn off our Facebook OG Tags implementation.
133 */
134 private $open_graph_conflicting_plugins = array(
135 'facebook/facebook.php', // Official Facebook plugin
136 'wordpress-seo/wp-seo.php', // WordPress SEO by Yoast
137 'add-link-to-facebook/add-link-to-facebook.php', // Add Link to Facebook
138 'facebook-awd/AWD_facebook.php', // Facebook AWD All in one
139 'header-footer/plugin.php', // Header and Footer
140 'nextgen-facebook/nextgen-facebook.php', // NextGEN Facebook OG
141 'seo-facebook-comments/seofacebook.php', // SEO Facebook Comments
142 'seo-ultimate/seo-ultimate.php', // SEO Ultimate
143 'sexybookmarks/sexy-bookmarks.php', // Shareaholic
144 'shareaholic/sexy-bookmarks.php', // Shareaholic
145 'social-discussions/social-discussions.php', // Social Discussions
146 'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php',
147 // NextScripts SNAP
148 'wordbooker/wordbooker.php', // Wordbooker
149 'socialize/socialize.php', // Socialize
150 'simple-facebook-connect/sfc.php', // Simple Facebook Connect
151 'social-sharing-toolkit/social_sharing_toolkit.php', // Social Sharing Toolkit
152 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol
153 'opengraph/opengraph.php', // Open Graph
154 'sharepress/sharepress.php', // SharePress
155 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
156 // WP Facebook Like Send & Open Graph Meta
157 'network-publisher/networkpub.php', // Network Publisher
158 'wp-ogp/wp-ogp.php', // WP-OGP
159 'open-graph-protocol-framework/open-graph-protocol-framework.php',
160 // Open Graph Protocol Framework
161 'all-in-one-seo-pack/all_in_one_seo_pack.php', // All in One SEO Pack
162 'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
163 // Facebook Featured Image & OG Meta Tags
164 'add-meta-tags/add-meta-tags.php', // Add Meta Tags
165 'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
166 // Tweet, Like, Google +1 and Share
167 'easy-facebook-share-thumbnails/esft.php', // Easy Facebook Share Thumbnail
168 '2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
169 // 2 Click Social Media Buttons
170 'facebook-thumb-fixer/_facebook-thumb-fixer.php', // Facebook Thumb Fixer
171 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin
172 'wp-caregiver/wp-caregiver.php', // WP Caregiver
173 'facebook-revised-open-graph-meta-tag/index.php', // Facebook Revised Open Graph Meta Tag
174 'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
175 // Fedmich's Facebook Open Graph Meta
176 'facebook-meta-tags/facebook-metatags.php', // Facebook Meta Tags
177 'wordpress-seo-premium/wp-seo-premium.php', // WordPress SEO Premium by Yoast
178 );
179
180 /**
181 * Plugins for which we turn off our Twitter Cards Tags implementation.
182 */
183 private $twitter_cards_conflicting_plugins = array(
184 'twitter-cards/twitter-cards.php', // Twitter Cards
185 'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta
186 'ig-twitter-cards/ig-twitter-cards.php', // IG:Twitter Cards
187 'jm-twitter-cards/jm-twitter-cards.php', // JM Twitter Cards
188 'wp-twitter-cards/twitter_cards.php', // WP Twitter Cards
189 'eewee-twitter-card/index.php', // Eewee Twitter Card
190 'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',
191 // Pure Web Brilliant's Social Graph Twitter Cards Extension
192 );
193
194 /**
195 * Message to display in admin_notice
196 * @var string
197 */
198 var $message = '';
199
200 /**
201 * Error to display in admin_notice
202 * @var string
203 */
204 var $error = '';
205
206 /**
207 * Modules that need more privacy description.
208 * @var string
209 */
210 var $privacy_checks = '';
211
212 /**
213 * Stats to record once the page loads
214 *
215 * @var array
216 */
217 var $stats = array();
218
219 /**
220 * Jetpack_Sync object
221 */
222 var $sync;
223
224 /**
225 * Verified data for JSON authorization request
226 */
227 var $json_api_authorization_request = array();
228
229 /**
230 * Holds the singleton instance of this class
231 * @since 2.3.3
232 * @var Jetpack
233 */
234 static $instance = false;
235
236 /**
237 * Singleton
238 * @static
239 */
240 public static function init() {
241 if ( ! self::$instance ) {
242 if ( did_action( 'plugins_loaded' ) )
243 self::plugin_textdomain();
244 else
245 add_action( 'plugins_loaded', array( __CLASS__, 'plugin_textdomain' ) );
246
247 self::$instance = new Jetpack;
248
249 self::$instance->plugin_upgrade();
250 }
251
252 return self::$instance;
253 }
254
255 /**
256 * Must never be called statically
257 */
258 function plugin_upgrade() {
259 // Upgrade: 1.1 -> 1.2
260 if ( get_option( 'jetpack_id' ) ) {
261 // Move individual jetpack options to single array of options
262 $options = array();
263 foreach ( Jetpack_Options::get_option_names() as $option ) {
264 if ( false !== $value = get_option( "jetpack_$option" ) ) {
265 $options[$option] = $value;
266 }
267 }
268
269 if ( $options ) {
270 Jetpack_Options::update_options( $options );
271
272 foreach ( array_keys( $options ) as $option ) {
273 delete_option( "jetpack_$option" );
274 }
275 }
276
277 // Add missing version and old_version options
278 if ( ! $version = Jetpack_Options::get_option( 'version' ) ) {
279 $version = $old_version = '1.1:' . time();
280 Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
281 }
282 }
283
284 // Upgrade from a single user token to a user_id-indexed array and a master_user ID
285 if ( ! Jetpack_Options::get_option( 'user_tokens' ) ) {
286 if ( $user_token = Jetpack_Options::get_option( 'user_token' ) ) {
287 $token_parts = explode( '.', $user_token );
288 if ( isset( $token_parts[2] ) ) {
289 $master_user = $token_parts[2];
290 $user_tokens = array( $master_user => $user_token );
291 Jetpack_Options::update_options( compact( 'master_user', 'user_tokens' ) );
292 Jetpack_Options::delete_option( 'user_token' );
293 } else {
294 // @todo: is this even possible?
295 trigger_error( sprintf( 'Jetpack::plugin_upgrade found no user_id in user_token "%s"', $user_token ), E_USER_WARNING );
296 }
297 }
298 }
299 }
300
301 /**
302 * Constructor. Initializes WordPress hooks
303 */
304 private function Jetpack() {
305
306 /**
307 * Do things that should run even in the network admin
308 * here, before we potentially fail out.
309 */
310 add_filter( 'jetpack_require_lib_dir', array( $this, 'require_lib_dir' ) );
311
312 /*
313 * Load things that should only be in Network Admin.
314 *
315 * For now blow away everything else until a more full
316 * understanding of what is needed at the network level is
317 * available
318 */
319 if( is_multisite() ) {
320 $jpms = Jetpack_Network::init();
321
322 if( is_network_admin() )
323 return; // End here to prevent single site actions from firing
324 }
325
326
327
328 $this->sync = new Jetpack_Sync;
329
330 // Modules should do Jetpack_Sync::sync_options( __FILE__, $option, ... ); instead
331 // We access the "internal" method here only because the Jetpack object isn't instantiated yet
332 $this->sync->options(
333 JETPACK__PLUGIN_DIR . 'jetpack.php',
334 'home',
335 'siteurl',
336 'blogname',
337 'gmt_offset',
338 'timezone_string'
339 );
340
341 if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && isset( $_GET['for'] ) && 'jetpack' == $_GET['for'] ) {
342 @ini_set( 'display_errors', false ); // Display errors can cause the XML to be not well formed.
343
344 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
345 $this->xmlrpc_server = new Jetpack_XMLRPC_Server();
346
347 $this->require_jetpack_authentication();
348
349 if ( Jetpack::is_active() ) {
350 // Hack to preserve $HTTP_RAW_POST_DATA
351 add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
352
353 $signed = $this->verify_xml_rpc_signature();
354 if ( $signed && ! is_wp_error( $signed ) ) {
355 // The actual API methods.
356 add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'xmlrpc_methods' ) );
357 } else {
358 add_filter( 'xmlrpc_methods', '__return_empty_array' );
359 }
360 } else {
361 // The bootstrap API methods.
362 add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'bootstrap_xmlrpc_methods' ) );
363 }
364
365 // Now that no one can authenticate, and we're whitelisting all XML-RPC methods, force enable_xmlrpc on.
366 add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
367 } elseif ( is_admin() && isset( $_POST['action'] ) && 'jetpack_upload_file' == $_POST['action'] ) {
368 $this->require_jetpack_authentication();
369 $this->add_remote_request_handlers();
370 } else {
371 if ( Jetpack::is_active() ) {
372 add_action( 'login_form_jetpack_json_api_authorization', array( &$this, 'login_form_json_api_authorization' ) );
373 }
374 }
375
376 if ( Jetpack::is_active() ) {
377 Jetpack_Heartbeat::init();
378 }
379
380 add_action( 'jetpack_clean_nonces', array( 'Jetpack', 'clean_nonces' ) );
381 if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
382 wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
383 }
384
385 add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ) );
386
387 add_action( 'admin_menu', array( $this, 'admin_menu' ), 999 ); // run late so that other plugins hooking into this menu don't get left out
388 add_action( 'admin_init', array( $this, 'admin_init' ) );
389 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
390
391 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
392
393 add_action( 'wp_ajax_jetpack-check-news-subscription', array( $this, 'check_news_subscription' ) );
394 add_action( 'wp_ajax_jetpack-subscribe-to-news', array( $this, 'subscribe_to_news' ) );
395
396 add_action( 'wp_ajax_jetpack-sync-reindex-trigger', array( $this, 'sync_reindex_trigger' ) );
397 add_action( 'wp_ajax_jetpack-sync-reindex-status', array( $this, 'sync_reindex_status' ) );
398
399 add_action( 'wp_loaded', array( $this, 'register_assets' ) );
400 add_action( 'wp_enqueue_scripts', array( $this, 'devicepx' ) );
401 add_action( 'customize_controls_enqueue_scripts', array( $this, 'devicepx' ) );
402 add_action( 'admin_enqueue_scripts', array( $this, 'devicepx' ) );
403
404 // add_action( 'jetpack_admin_menu', array( $this, 'admin_menu_modules' ) );
405
406 add_action( 'jetpack_activate_module', array( $this, 'activate_module_actions' ) );
407
408 add_action( 'plugins_loaded', array( $this, 'extra_oembed_providers' ) );
409
410 /**
411 * These actions run checks to load additional files.
412 * They check for external files or plugins, so they need to run as late as possible.
413 */
414 add_action( 'wp_head', array( $this, 'check_open_graph' ), 1 );
415 add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ), 999 );
416 add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
417
418 add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 4 );
419
420 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
421 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
422 }
423
424 /**
425 * If there are any stats that need to be pushed, but haven't been, push them now.
426 */
427 function __destruct() {
428 if ( ! empty( $this->stats ) ) {
429 $this->do_stats( 'server_side' );
430 }
431 }
432
433 function jetpack_custom_caps( $caps, $cap, $user_id, $args ) {
434 switch( $cap ) {
435 case 'jetpack_connect' :
436 case 'jetpack_reconnect' :
437 if ( Jetpack::is_development_mode() ) {
438 $caps = array( 'do_not_allow' );
439 break;
440 }
441 /**
442 * Pass through. If it's not development mode, these should match disconnect.
443 * Let users disconnect if it's development mode, just in case things glitch.
444 */
445 case 'jetpack_disconnect' :
446 /**
447 * In multisite, can individual site admins manage their own connection?
448 *
449 * Ideally, this should be extracted out to a separate filter in the Jetpack_Network class.
450 */
451 if ( is_multisite() && ! is_super_admin() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
452 if ( ! Jetpack_Network::init()->get_option( 'sub-site-connection-override' ) ) {
453 /**
454 * We need to update the option name -- it's terribly unclear which
455 * direction the override goes.
456 *
457 * @todo: Update the option name to `sub-sites-can-manage-own-connections`
458 */
459 $caps = array( 'do_not_allow' );
460 break;
461 }
462 }
463
464 $caps = array( 'manage_options' );
465 break;
466 case 'jetpack_manage_modules' :
467 case 'jetpack_activate_modules' :
468 case 'jetpack_deactivate_modules' :
469 $caps = array( 'manage_options' );
470 break;
471 case 'jetpack_configure_modules' :
472 $caps = array( 'manage_options' );
473 break;
474 case 'jetpack_admin_page' :
475 if ( Jetpack::is_development_mode() ) {
476 $caps = array( 'manage_options' );
477 break;
478 }
479 /**
480 * Pass through. If it's not development mode, these should match the admin page.
481 * Let users disconnect if it's development mode, just in case things glitch.
482 */
483 case 'jetpack_connect_user' :
484 if ( Jetpack::is_development_mode() ) {
485 $caps = array( 'do_not_allow' );
486 break;
487 }
488 $caps = array( 'read' );
489 break;
490 }
491 return $caps;
492 }
493
494 function require_jetpack_authentication() {
495 // Don't let anyone authenticate
496 $_COOKIE = array();
497 remove_all_filters( 'authenticate' );
498
499 /**
500 * For the moment, remove Limit Login Attempts if its xmlrpc for Jetpack.
501 * If Limit Login Attempts is installed as a mu-plugin, it can occasionally
502 * generate false-positives.
503 */
504 remove_filter( 'wp_login_failed', 'limit_login_failed' );
505
506 if ( Jetpack::is_active() ) {
507 // Allow Jetpack authentication
508 add_filter( 'authenticate', array( $this, 'authenticate_jetpack' ), 10, 3 );
509 }
510 }
511
512 /**
513 * Load language files
514 */
515 public static function plugin_textdomain() {
516 load_plugin_textdomain( 'jetpack', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
517 }
518
519 /**
520 * Register assets for use in various modules and the Jetpack admin page.
521 *
522 * @uses wp_script_is, wp_register_script, plugins_url
523 * @action wp_loaded
524 * @return null
525 */
526 public function register_assets() {
527 if ( ! wp_script_is( 'spin', 'registered' ) )
528 wp_register_script( 'spin', plugins_url( '_inc/spin.js', __FILE__ ), false, '1.3' );
529
530 if ( ! wp_script_is( 'jquery.spin', 'registered' ) )
531 wp_register_script( 'jquery.spin', plugins_url( '_inc/jquery.spin.js', __FILE__ ) , array( 'jquery', 'spin' ), '1.3' );
532
533 if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) )
534 wp_register_script( 'jetpack-gallery-settings', plugins_url( '_inc/gallery-settings.js', __FILE__ ), array( 'media-views' ), '20121225' );
535
536 /**
537 * As jetpack_register_genericons is by default fired off a hook,
538 * the hook may have already fired by this point.
539 * So, let's just trigger it manually.
540 */
541 require_once( JETPACK__PLUGIN_DIR . '_inc/genericons.php' );
542 jetpack_register_genericons();
543
544 if ( ! wp_style_is( 'jetpack-icons', 'registered' ) )
545 wp_register_style( 'jetpack-icons', plugins_url( '_inc/jetpack-icons/jetpack-icons.css', __FILE__ ), false, JETPACK__VERSION );
546 }
547
548 /**
549 * Device Pixels support
550 * This improves the resolution of gravatars and wordpress.com uploads on hi-res and zoomed browsers.
551 */
552 function devicepx() {
553 if ( Jetpack::is_active() ) {
554 wp_enqueue_script( 'devicepx', set_url_scheme( 'http://s0.wp.com/wp-content/js/devicepx-jetpack.js' ), array(), gmdate( 'oW' ), true );
555 }
556 }
557
558 /*
559 * Returns the location of Jetpack's lib directory. This filter is applied
560 * in require_lib().
561 *
562 * @filter require_lib_dir
563 */
564 function require_lib_dir( $lib_dir ) {
565 return JETPACK__PLUGIN_DIR . '_inc/lib';
566 }
567
568 /**
569 * Is Jetpack active?
570 */
571 public static function is_active() {
572 return (bool) Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
573 }
574
575 /**
576 * Is Jetpack in development (offline) mode?
577 */
578 public static function is_development_mode() {
579 $development_mode = false;
580
581 if ( defined( 'JETPACK_DEV_DEBUG' ) ) {
582 $development_mode = JETPACK_DEV_DEBUG;
583 }
584
585 elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
586 $development_mode = true;
587 }
588
589 return apply_filters( 'jetpack_development_mode', $development_mode );
590 }
591
592 /**
593 * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
594 */
595 public static function is_user_connected( $user_id = false ) {
596 $user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
597 if ( ! $user_id ) {
598 return false;
599 }
600 return (bool) Jetpack_Data::get_access_token( $user_id );
601 }
602
603 /**
604 * Get the wpcom email of the current connected user.
605 */
606 public static function get_connected_user_email() {
607 Jetpack::load_xml_rpc_client();
608 $xml = new Jetpack_IXR_Client( array(
609 'user_id' => get_current_user_id()
610 ) );
611 $xml->query( 'wpcom.getUserEmail' );
612 if ( ! $xml->isError() ) {
613 return $xml->getResponse();
614 }
615 return false;
616 }
617
618 function current_user_is_connection_owner() {
619 $user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
620 return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && get_current_user_id() === $user_token->external_user_id;
621 }
622
623 /**
624 * Add any extra oEmbed providers that we know about and use on wpcom for feature parity.
625 */
626 function extra_oembed_providers() {
627 // Cloudup: https://dev.cloudup.com/#oembed
628 wp_oembed_add_provider( 'https://cloudup.com/*' , 'https://cloudup.com/oembed' );
629 }
630
631 /**
632 * Synchronize connected user role changes
633 */
634 function user_role_change( $user_id ) {
635 if ( Jetpack::is_active() && Jetpack::is_user_connected( $user_id ) ) {
636 $current_user_id = get_current_user_id();
637 wp_set_current_user( $user_id );
638 $role = $this->translate_current_user_to_role();
639 $signed_role = $this->sign_role( $role );
640 wp_set_current_user( $current_user_id );
641
642 $master_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
643 $master_user_id = absint( $master_token->external_user_id );
644
645 if ( ! $master_user_id )
646 return; // this shouldn't happen
647
648 Jetpack::xmlrpc_async_call( 'jetpack.updateRole', $user_id, $signed_role );
649 //@todo retry on failure
650
651 //try to choose a new master if we're demoting the current one
652 if ( $user_id == $master_user_id && 'administrator' != $role ) {
653 $query = new WP_User_Query(
654 array(
655 'fields' => array( 'id' ),
656 'role' => 'administrator',
657 'orderby' => 'id',
658 'exclude' => array( $master_user_id ),
659 )
660 );
661 $new_master = false;
662 foreach ( $query->results as $result ) {
663 $uid = absint( $result->id );
664 if ( $uid && Jetpack::is_user_connected( $uid ) ) {
665 $new_master = $uid;
666 break;
667 }
668 }
669
670 if ( $new_master ) {
671 Jetpack_Options::update_option( 'master_user', $new_master );
672 }
673 // else disconnect..?
674 }
675 }
676 }
677
678 /**
679 * Loads the currently active modules.
680 */
681 public static function load_modules() {
682
683 /*
684 if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
685 return;
686 }
687 */
688
689 $version = Jetpack_Options::get_option( 'version' );
690 if ( ! $version ) {
691 $version = $old_version = JETPACK__VERSION . ':' . time();
692 Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
693 }
694 list( $version ) = explode( ':', $version );
695
696 $modules = array_filter( Jetpack::get_active_modules(), array( 'Jetpack', 'is_module' ) );
697
698 $modules_data = array();
699
700 // Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
701 if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
702 $updated_modules = array();
703 foreach ( $modules as $module ) {
704 $modules_data[ $module ] = Jetpack::get_module( $module );
705 if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
706 continue;
707 }
708
709 if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
710 continue;
711 }
712
713 $updated_modules[] = $module;
714 }
715
716 $modules = array_diff( $modules, $updated_modules );
717 }
718
719 foreach ( $modules as $module ) {
720 // If not connected and we're in dev mode, disable modules requiring a connection
721 if ( ! Jetpack::is_active() ) {
722 if ( empty( $modules_data[ $module ] ) ) {
723 $modules_data[ $module ] = Jetpack::get_module( $module );
724 }
725 if ( $modules_data[ $module ]['requires_connection'] || ! Jetpack::is_development_mode() ) {
726 continue;
727 }
728 }
729
730 if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
731 continue;
732 }
733
734 require Jetpack::get_module_path( $module );
735 do_action( 'jetpack_module_loaded_' . $module );
736 }
737
738 do_action( 'jetpack_modules_loaded' );
739
740 // 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.
741 if ( Jetpack::is_active() || Jetpack::is_development_mode() )
742 require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' );
743 }
744
745 /**
746 * Check if Jetpack's REST API compat file should be included
747 * @action plugins_loaded
748 * @return null
749 */
750 public function check_rest_api_compat() {
751 $_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
752
753 if ( function_exists( 'bbpress' ) )
754 $_jetpack_rest_api_compat_includes[] = JETPACK__PLUGIN_DIR . 'class.jetpack-bbpress-json-api-compat.php';
755
756 foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include )
757 require_once $_jetpack_rest_api_compat_include;
758 }
759
760 /**
761 * Gets all plugins currently active in values, regardless of whether they're
762 * traditionally activated or network activated.
763 *
764 * @todo Store the result in core's object cache maybe?
765 */
766 public static function get_active_plugins() {
767 $active_plugins = (array) get_option( 'active_plugins', array() );
768
769 if ( is_multisite() ) {
770 // Due to legacy code, active_sitewide_plugins stores them in the keys,
771 // whereas active_plugins stores them in the values.
772 $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
773 if ( $network_plugins ) {
774 $active_plugins = array_merge( $active_plugins, $network_plugins );
775 }
776 }
777
778 sort( $active_plugins );
779
780 return $active_plugins;
781 }
782
783 /**
784 * Checks whether a specific plugin is active.
785 *
786 * We don't want to store these in a static variable, in case
787 * there are switch_to_blog() calls involved.
788 */
789 public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
790 return in_array( $plugin, self::get_active_plugins() );
791 }
792
793 /**
794 * Check if Jetpack's Open Graph tags should be used.
795 * If certain plugins are active, Jetpack's og tags are suppressed.
796 *
797 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
798 * @action plugins_loaded
799 * @return null
800 */
801 public function check_open_graph() {
802 if ( in_array( 'publicize', Jetpack::get_active_modules() ) || in_array( 'sharedaddy', Jetpack::get_active_modules() ) ) {
803 add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
804 }
805
806 $active_plugins = self::get_active_plugins();
807
808 if ( ! empty( $active_plugins ) ) {
809 foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
810 if ( in_array( $plugin, $active_plugins ) ) {
811 add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
812 break;
813 }
814 }
815 }
816
817 if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
818 require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
819 }
820 }
821
822 /**
823 * Check if Jetpack's Twitter tags should be used.
824 * If certain plugins are active, Jetpack's twitter tags are suppressed.
825 *
826 * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
827 * @action plugins_loaded
828 * @return null
829 */
830 public function check_twitter_tags() {
831
832 $active_plugins = self::get_active_plugins();
833
834 if ( ! empty( $active_plugins ) ) {
835 foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
836 if ( in_array( $plugin, $active_plugins ) ) {
837 add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
838 break;
839 }
840 }
841 }
842
843 if ( apply_filters( 'jetpack_disable_twitter_cards', true ) ) {
844 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
845 }
846 }
847
848 /* Jetpack Options API */
849
850 public static function get_option_names( $type = 'compact' ) {
851 return Jetpack_Options::get_option_names( $type );
852 }
853
854 /**
855 * Returns the requested option. Looks in jetpack_options or jetpack_$name as appropriate.
856 *
857 * @param string $name Option name
858 * @param mixed $default (optional)
859 */
860 public static function get_option( $name, $default = false ) {
861 return Jetpack_Options::get_option( $name, $default );
862 }
863
864 /**
865 * Stores two secrets and a timestamp so WordPress.com can make a request back and verify an action
866 * Does some extra verification so urls (such as those to public-api, register, etc) can't just be crafted
867 * $name must be a registered option name.
868 */
869 public static function create_nonce( $name ) {
870 $secret = wp_generate_password( 32, false ) . ':' . wp_generate_password( 32, false ) . ':' . ( time() + 600 );
871
872 Jetpack_Options::update_option( $name, $secret );
873 @list( $secret_1, $secret_2, $eol ) = explode( ':', Jetpack_Options::get_option( $name ) );
874 if ( empty( $secret_1 ) || empty( $secret_2 ) || $eol < time() )
875 return new Jetpack_Error( 'missing_secrets' );
876
877 return array(
878 'secret_1' => $secret_1,
879 'secret_2' => $secret_2,
880 'eol' => $eol,
881 );
882 }
883
884 /**
885 * Updates the single given option. Updates jetpack_options or jetpack_$name as appropriate.
886 *
887 * @param string $name Option name
888 * @param mixed $value Option value
889 */
890 public static function update_option( $name, $value ) {
891 return Jetpack_Options::update_option( $name, $value );
892 }
893
894 /**
895 * Updates the multiple given options. Updates jetpack_options and/or jetpack_$name as appropriate.
896 *
897 * @param array $array array( option name => option value, ... )
898 */
899 public static function update_options( $array ) {
900 return Jetpack_Options::update_options( $array );
901 }
902
903 /**
904 * Deletes the given option. May be passed multiple option names as an array.
905 * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
906 *
907 * @param string|array $names
908 */
909 public static function delete_option( $names ) {
910 return Jetpack_Options::delete_option( $names );
911 }
912
913 /**
914 * Enters a user token into the user_tokens option
915 *
916 * @param int $user_id
917 * @param string $token
918 * return bool
919 */
920 public static function update_user_token( $user_id, $token, $is_master_user ) {
921 // not designed for concurrent updates
922 $user_tokens = Jetpack_Options::get_option( 'user_tokens' );
923 if ( ! is_array( $user_tokens ) )
924 $user_tokens = array();
925 $user_tokens[$user_id] = $token;
926 if ( $is_master_user ) {
927 $master_user = $user_id;
928 $options = compact( 'user_tokens', 'master_user' );
929 } else {
930 $options = compact( 'user_tokens' );
931 }
932 return Jetpack_Options::update_options( $options );
933 }
934
935 /**
936 * Returns an array of all PHP files in the specified absolute path.
937 * Equivalent to glob( "$absolute_path/*.php" ).
938 *
939 * @param string $absolute_path The absolute path of the directory to search.
940 * @return array Array of absolute paths to the PHP files.
941 */
942 public static function glob_php( $absolute_path ) {
943 $absolute_path = untrailingslashit( $absolute_path );
944 $files = array();
945 if ( ! $dir = @opendir( $absolute_path ) ) {
946 return $files;
947 }
948
949 while ( false !== $file = readdir( $dir ) ) {
950 if ( '.' == substr( $file, 0, 1 ) || '.php' != substr( $file, -4 ) ) {
951 continue;
952 }
953
954 $file = "$absolute_path/$file";
955
956 if ( ! is_file( $file ) ) {
957 continue;
958 }
959
960 $files[] = $file;
961 }
962
963 closedir( $dir );
964
965 return $files;
966 }
967
968 public function activate_new_modules() {
969 if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
970 return;
971 }
972
973 $jetpack_old_version = Jetpack_Options::get_option( 'version' ); // [sic]
974 if ( ! $jetpack_old_version ) {
975 $jetpack_old_version = $version = $old_version = '1.1:' . time();
976 Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
977 }
978
979 list( $jetpack_version ) = explode( ':', $jetpack_old_version ); // [sic]
980
981 if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
982 return;
983 }
984
985 $active_modules = Jetpack::get_active_modules();
986 $reactivate_modules = array();
987 foreach ( $active_modules as $active_module ) {
988 $module = Jetpack::get_module( $active_module );
989 if ( ! isset( $module['changed'] ) ) {
990 continue;
991 }
992
993 if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
994 continue;
995 }
996
997 $reactivate_modules[] = $active_module;
998 Jetpack::deactivate_module( $active_module );
999 }
1000
1001 if ( version_compare( $jetpack_version, '1.9.2', '<' ) && version_compare( '1.9-something', JETPACK__VERSION, '<' ) ) {
1002 add_action( 'jetpack_activate_default_modules', array( $this->sync, 'sync_all_registered_options' ), 1000 );
1003 }
1004
1005 Jetpack_Options::update_options(
1006 array(
1007 'version' => JETPACK__VERSION . ':' . time(),
1008 'old_version' => $jetpack_old_version,
1009 )
1010 );
1011
1012 Jetpack::state( 'message', 'modules_activated' );
1013 Jetpack::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules );
1014 wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
1015 exit;
1016 }
1017
1018 /**
1019 * List available Jetpack modules. Simply lists .php files in /modules/.
1020 * Make sure to tuck away module "library" files in a sub-directory.
1021 */
1022 public static function get_available_modules( $min_version = false, $max_version = false ) {
1023 static $modules = null;
1024
1025 if ( ! isset( $modules ) ) {
1026 $files = Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules' );
1027
1028 $modules = array();
1029
1030 foreach ( $files as $file ) {
1031 if ( ! $headers = Jetpack::get_module( $file ) ) {
1032 continue;
1033 }
1034
1035 $modules[ Jetpack::get_module_slug( $file ) ] = $headers['introduced'];
1036 }
1037 }
1038
1039 $modules = apply_filters( 'jetpack_get_available_modules', $modules, $min_version, $max_version );
1040
1041 if ( ! $min_version && ! $max_version ) {
1042 return array_keys( $modules );
1043 }
1044
1045 $r = array();
1046 foreach ( $modules as $slug => $introduced ) {
1047 if ( $min_version && version_compare( $min_version, $introduced, '>=' ) ) {
1048 continue;
1049 }
1050
1051 if ( $max_version && version_compare( $max_version, $introduced, '<' ) ) {
1052 continue;
1053 }
1054
1055 $r[] = $slug;
1056 }
1057
1058 return $r;
1059 }
1060
1061 /**
1062 * Default modules loaded on activation.
1063 */
1064 public static function get_default_modules( $min_version = false, $max_version = false ) {
1065 $return = array();
1066
1067 foreach ( Jetpack::get_available_modules( $min_version, $max_version ) as $module ) {
1068 $module_data = Jetpack::get_module( $module );
1069
1070 switch ( strtolower( $module_data['auto_activate'] ) ) {
1071 case 'yes' :
1072 $return[] = $module;
1073 break;
1074 case 'public' :
1075 if ( Jetpack_Options::get_option( 'public' ) ) {
1076 $return[] = $module;
1077 }
1078 break;
1079 case 'no' :
1080 default :
1081 break;
1082 }
1083 }
1084 return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version );
1085 }
1086
1087 /**
1088 * Checks activated modules during auto-activation to determine
1089 * if any of those modules are being deprecated. If so, close
1090 * them out, and add any replacement modules.
1091 *
1092 * Runs at priority 99 by default.
1093 *
1094 * This is run late, so that it can still activate a module if
1095 * the new module is a replacement for another that the user
1096 * currently has active, even if something at the normal priority
1097 * would kibosh everything.
1098 *
1099 * @since 2.6
1100 * @uses jetpack_get_default_modules filter
1101 * @param array $modules
1102 * @return array
1103 */
1104 function handle_deprecated_modules( $modules ) {
1105 $deprecated_modules = array(
1106 'debug' => null, // Closed out and moved to ./class.jetpack-debugger.php
1107 'wpcc' => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
1108 );
1109
1110 // Don't activate SSO if they never completed activating WPCC.
1111 if ( Jetpack::is_module_active( 'wpcc' ) ) {
1112 $wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
1113 if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
1114 $deprecated_modules['wpcc'] = null;
1115 }
1116 }
1117
1118 foreach ( $deprecated_modules as $module => $replacement ) {
1119 if ( Jetpack::is_module_active( $module ) ) {
1120 self::deactivate_module( $module );
1121 if ( $replacement ) {
1122 $modules[] = $replacement;
1123 }
1124 }
1125 }
1126
1127 return array_unique( $modules );
1128 }
1129
1130 /**
1131 * Checks activated plugins during auto-activation to determine
1132 * if any of those plugins are in the list with a corresponding module
1133 * that is not compatible with the plugin. The module will not be allowed
1134 * to auto-activate.
1135 *
1136 * @since 2.6
1137 * @uses jetpack_get_default_modules filter
1138 * @param array $modules
1139 * @return array
1140 */
1141 function filter_default_modules( $modules ) {
1142
1143 $active_plugins = self::get_active_plugins();
1144
1145 if ( ! empty( $active_plugins ) ) {
1146
1147 // For each module we'd like to auto-activate...
1148 foreach ( $modules as $key => $module ) {
1149 // If there are potential conflicts for it...
1150 if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
1151 // For each potential conflict...
1152 foreach ( $this->conflicting_plugins[ $module ] as $title => $plugin ) {
1153 // If that conflicting plugin is active...
1154 if ( in_array( $plugin, $active_plugins ) ) {
1155 // Remove that item from being auto-activated.
1156 unset( $modules[ $key ] );
1157 }
1158 }
1159 }
1160 }
1161 }
1162
1163 return $modules;
1164 }
1165
1166 /**
1167 * Extract a module's slug from its full path.
1168 */
1169 public static function get_module_slug( $file ) {
1170 return str_replace( '.php', '', basename( $file ) );
1171 }
1172
1173 /**
1174 * Generate a module's path from its slug.
1175 */
1176 public static function get_module_path( $slug ) {
1177 return JETPACK__PLUGIN_DIR . "modules/$slug.php";
1178 }
1179
1180 /**
1181 * Load module data from module file. Headers differ from WordPress
1182 * plugin headers to avoid them being identified as standalone
1183 * plugins on the WordPress plugins page.
1184 */
1185 public static function get_module( $module ) {
1186 $headers = array(
1187 'name' => 'Module Name',
1188 'description' => 'Module Description',
1189 'sort' => 'Sort Order',
1190 'introduced' => 'First Introduced',
1191 'changed' => 'Major Changes In',
1192 'deactivate' => 'Deactivate',
1193 'free' => 'Free',
1194 'requires_connection' => 'Requires Connection',
1195 'auto_activate' => 'Auto Activate',
1196 'module_tags' => 'Module Tags',
1197 );
1198
1199 $file = Jetpack::get_module_path( Jetpack::get_module_slug( $module ) );
1200 if ( ! file_exists( $file ) )
1201 return false;
1202
1203 $mod = get_file_data( $file, $headers );
1204 if ( empty( $mod['name'] ) )
1205 return false;
1206
1207 $mod['name'] = translate( $mod['name'], 'jetpack' );
1208 $mod['description'] = translate( $mod['description'], 'jetpack' );
1209 $mod['sort'] = empty( $mod['sort'] ) ? 10 : (int) $mod['sort'];
1210 $mod['deactivate'] = empty( $mod['deactivate'] );
1211 $mod['free'] = empty( $mod['free'] );
1212 $mod['requires_connection'] = ( ! empty( $mod['requires_connection'] ) && 'No' == $mod['requires_connection'] ) ? false : true;
1213
1214 if ( empty( $mod['auto_activate'] ) || ! in_array( strtolower( $mod['auto_activate'] ), array( 'yes', 'no', 'public' ) ) ) {
1215 $mod['auto_activate'] = 'No';
1216 } else {
1217 $mod['auto_activate'] = (string) $mod['auto_activate'];
1218 }
1219
1220 if ( $mod['module_tags'] ) {
1221 $mod['module_tags'] = explode( ',', $mod['module_tags'] );
1222 $mod['module_tags'] = array_map( 'trim', $mod['module_tags'] );
1223 $mod['module_tags'] = array_map( array( __CLASS__, 'translate_module_tag' ), $mod['module_tags'] );
1224 } else {
1225 $mod['module_tags'] = array( self::translate_module_tag( 'Other' ) );
1226 }
1227
1228 return $mod;
1229 }
1230
1231 public static function translate_module_tag( $untranslated_tag ) {
1232 return _x( $untranslated_tag, 'Module Tag', 'jetpack' );
1233
1234 // Calls here are to populate translation files.
1235 _x( 'Photos and Videos', 'Module Tag', 'jetpack' );
1236 _x( 'Social', 'Module Tag', 'jetpack' );
1237 _x( 'WordPress.com Stats', 'Module Tag', 'jetpack' );
1238 _x( 'Writing', 'Module Tag', 'jetpack' );
1239 _x( 'Appearance', 'Module Tag', 'jetpack' );
1240 _x( 'Developers', 'Module Tag', 'jetpack' );
1241 _x( 'Mobile', 'Module Tag', 'jetpack' );
1242 _x( 'Other', 'Module Tag', 'jetpack' );
1243 }
1244
1245 /**
1246 * Get a list of activated modules as an array of module slugs.
1247 */
1248 public static function get_active_modules() {
1249 $active = Jetpack_Options::get_option( 'active_modules' );
1250 if ( ! is_array( $active ) )
1251 $active = array();
1252 if ( is_admin() ) {
1253 $active[] = 'vaultpress';
1254 } else {
1255 $active = array_diff( $active, array( 'vaultpress' ) );
1256 }
1257 return array_unique( $active );
1258 }
1259
1260 /**
1261 * Check whether or not a Jetpack module is active.
1262 *
1263 * @param string $module The slug of a Jetpack module.
1264 * @return bool
1265 *
1266 * @static
1267 */
1268 public static function is_module_active( $module ) {
1269 return in_array( $module, self::get_active_modules() );
1270 }
1271
1272 public static function is_module( $module ) {
1273 return ! empty( $module ) && ! validate_file( $module, Jetpack::get_available_modules() );
1274 }
1275
1276 /**
1277 * Catches PHP errors. Must be used in conjunction with output buffering.
1278 *
1279 * @param bool $catch True to start catching, False to stop.
1280 *
1281 * @static
1282 */
1283 public static function catch_errors( $catch ) {
1284 static $display_errors, $error_reporting;
1285
1286 if ( $catch ) {
1287 $display_errors = @ini_set( 'display_errors', 1 );
1288 $error_reporting = @error_reporting( E_ALL );
1289 add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
1290 } else {
1291 @ini_set( 'display_errors', $display_errors );
1292 @error_reporting( $error_reporting );
1293 remove_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 1 );
1294 }
1295 }
1296
1297 /**
1298 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
1299 */
1300 public static function catch_errors_on_shutdown() {
1301 Jetpack::state( 'php_errors', ob_get_clean() );
1302 }
1303
1304 public static function activate_default_modules( $min_version = false, $max_version = false, $other_modules = array() ) {
1305 $jetpack = Jetpack::init();
1306
1307 $modules = Jetpack::get_default_modules( $min_version, $max_version );
1308 $modules = array_merge( $other_modules, $modules );
1309
1310 // Look for standalone plugins and disable if active.
1311
1312 $to_deactivate = array();
1313 foreach ( $modules as $module ) {
1314 if ( isset( $jetpack->plugins_to_deactivate[$module] ) ) {
1315 $to_deactivate[$module] = $jetpack->plugins_to_deactivate[$module];
1316 }
1317 }
1318
1319 $deactivated = array();
1320 foreach ( $to_deactivate as $module => $deactivate_me ) {
1321 list( $probable_file, $probable_title ) = $deactivate_me;
1322 if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
1323 $deactivated[] = $module;
1324 }
1325 }
1326
1327 if ( $deactivated ) {
1328 Jetpack::state( 'deactivated_plugins', join( ',', $deactivated ) );
1329
1330 $url = add_query_arg(
1331 array(
1332 'action' => 'activate_default_modules',
1333 '_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
1334 ),
1335 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), Jetpack::admin_url( 'page=jetpack' ) )
1336 );
1337 wp_safe_redirect( $url );
1338 exit;
1339 }
1340
1341 do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
1342
1343 // Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
1344 Jetpack::restate();
1345 Jetpack::catch_errors( true );
1346 foreach ( $modules as $module ) {
1347 if ( did_action( "jetpack_module_loaded_$module" ) ) {
1348 $active[] = $module;
1349 Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
1350 continue;
1351 }
1352 $active = Jetpack::get_active_modules();
1353 if ( in_array( $module, $active ) ) {
1354 $module_info = Jetpack::get_module( $module );
1355 if ( ! $module_info['deactivate'] ) {
1356 $state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
1357 if ( $active_state = Jetpack::state( $state ) ) {
1358 $active_state = explode( ',', $active_state );
1359 } else {
1360 $active_state = array();
1361 }
1362 $active_state[] = $module;
1363 Jetpack::state( $state, implode( ',', $active_state ) );
1364 }
1365 continue;
1366 }
1367
1368 $file = Jetpack::get_module_path( $module );
1369 if ( ! file_exists( $file ) ) {
1370 continue;
1371 }
1372
1373 // we'll override this later if the plugin can be included without fatal error
1374 wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
1375 Jetpack::state( 'error', 'module_activation_failed' );
1376 Jetpack::state( 'module', $module );
1377 ob_start();
1378 require $file;
1379 do_action( 'jetpack_activate_module', $module );
1380 $active[] = $module;
1381 $state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
1382 if ( $active_state = Jetpack::state( $state ) ) {
1383 $active_state = explode( ',', $active_state );
1384 } else {
1385 $active_state = array();
1386 }
1387 $active_state[] = $module;
1388 Jetpack::state( $state, implode( ',', $active_state ) );
1389 Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
1390 ob_end_clean();
1391 }
1392 Jetpack::state( 'error', false );
1393 Jetpack::state( 'module', false );
1394 Jetpack::catch_errors( false );
1395 do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
1396 }
1397
1398 public static function activate_module( $module, $exit = true ) {
1399 do_action( 'jetpack_pre_activate_module', $module, $exit );
1400
1401 $jetpack = Jetpack::init();
1402
1403 if ( ! strlen( $module ) )
1404 return false;
1405
1406 if ( ! Jetpack::is_module( $module ) )
1407 return false;
1408
1409 // If it's already active, then don't do it again
1410 $active = Jetpack::get_active_modules();
1411 foreach ( $active as $act ) {
1412 if ( $act == $module )
1413 return true;
1414 }
1415
1416 $module_data = Jetpack::get_module( $module );
1417
1418 if ( ! Jetpack::is_active() ) {
1419 if ( ! Jetpack::is_development_mode() )
1420 return false;
1421
1422 // If we're not connected but in development mode, make sure the module doesn't require a connection
1423 if ( Jetpack::is_development_mode() && $module_data['requires_connection'] )
1424 return false;
1425 }
1426
1427 // Check and see if the old plugin is active
1428 if ( isset( $jetpack->plugins_to_deactivate[$module] ) ) {
1429 // Deactivate the old plugin
1430 if ( Jetpack_Client_Server::deactivate_plugin( $jetpack->plugins_to_deactivate[$module][0], $jetpack->plugins_to_deactivate[$module][1] ) ) {
1431 // If we deactivated the old plugin, remembere that with ::state() and redirect back to this page to activate the module
1432 // We can't activate the module on this page load since the newly deactivated old plugin is still loaded on this page load.
1433 Jetpack::state( 'deactivated_plugins', $module );
1434 wp_safe_redirect( add_query_arg( 'jetpack_restate', 1 ) );
1435 exit;
1436 }
1437 }
1438
1439 // Check the file for fatal errors, a la wp-admin/plugins.php::activate
1440 Jetpack::state( 'module', $module );
1441 Jetpack::state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error
1442 wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
1443
1444 Jetpack::catch_errors( true );
1445 ob_start();
1446 require Jetpack::get_module_path( $module );
1447 do_action( 'jetpack_activate_module', $module );
1448 $active[] = $module;
1449 Jetpack_Options::update_option( 'active_modules', array_unique( $active ) );
1450 Jetpack::state( 'error', false ); // the override
1451 Jetpack::state( 'message', 'module_activated' );
1452 Jetpack::state( 'module', $module );
1453 ob_end_clean();
1454 Jetpack::catch_errors( false );
1455
1456 if ( $exit ) {
1457 exit;
1458 }
1459 }
1460
1461 function activate_module_actions( $module ) {
1462 do_action( "jetpack_activate_module_$module" );
1463
1464 $this->sync->sync_all_module_options( $module );
1465 }
1466
1467 public static function deactivate_module( $module ) {
1468 do_action( 'jetpack_pre_deactivate_module', $module );
1469
1470 $active = Jetpack::get_active_modules();
1471 $new = array_filter( array_diff( $active, (array) $module ) );
1472
1473 do_action( "jetpack_deactivate_module_$module" );
1474 return Jetpack_Options::update_option( 'active_modules', array_unique( $new ) );
1475 }
1476
1477 public static function enable_module_configurable( $module ) {
1478 $module = Jetpack::get_module_slug( $module );
1479 add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
1480 }
1481
1482 public static function module_configuration_url( $module ) {
1483 $module = Jetpack::get_module_slug( $module );
1484 return Jetpack::admin_url( array( 'page' => 'jetpack', 'configure' => $module ) );
1485 }
1486
1487 public static function module_configuration_load( $module, $method ) {
1488 $module = Jetpack::get_module_slug( $module );
1489 add_action( 'jetpack_module_configuration_load_' . $module, $method );
1490 }
1491
1492 public static function module_configuration_head( $module, $method ) {
1493 $module = Jetpack::get_module_slug( $module );
1494 add_action( 'jetpack_module_configuration_head_' . $module, $method );
1495 }
1496
1497 public static function module_configuration_screen( $module, $method ) {
1498 $module = Jetpack::get_module_slug( $module );
1499 add_action( 'jetpack_module_configuration_screen_' . $module, $method );
1500 }
1501
1502 /* Installation */
1503
1504 public static function bail_on_activation( $message, $deactivate = true ) {
1505 ?>
1506 <!doctype html>
1507 <html>
1508 <head>
1509 <meta charset="<?php bloginfo( 'charset' ); ?>">
1510 <style>
1511 * {
1512 text-align: center;
1513 margin: 0;
1514 padding: 0;
1515 font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
1516 }
1517 p {
1518 margin-top: 1em;
1519 font-size: 18px;
1520 }
1521 </style>
1522 <body>
1523 <p><?php echo esc_html( $message ); ?></p>
1524 </body>
1525 </html>
1526 <?php
1527 if ( $deactivate ) {
1528 $plugins = get_option( 'active_plugins' );
1529 $jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
1530 $update = false;
1531 foreach ( $plugins as $i => $plugin ) {
1532 if ( $plugin === $jetpack ) {
1533 $plugins[$i] = false;
1534 $update = true;
1535 }
1536 }
1537
1538 if ( $update ) {
1539 update_option( 'active_plugins', array_filter( $plugins ) );
1540 }
1541 }
1542 exit;
1543 }
1544
1545 /**
1546 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
1547 * @static
1548 */
1549 public static function plugin_activation( $network_wide ) {
1550 Jetpack_Options::update_option( 'activated', 1 );
1551
1552 if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
1553 Jetpack::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
1554 }
1555
1556 if ( $network_wide )
1557 Jetpack::state( 'network_nag', true );
1558
1559 Jetpack::plugin_initialize();
1560 }
1561
1562 /**
1563 * Sets the internal version number and activation state.
1564 * @static
1565 */
1566 public static function plugin_initialize() {
1567 if ( ! Jetpack_Options::get_option( 'activated' ) ) {
1568 Jetpack_Options::update_option( 'activated', 2 );
1569 }
1570
1571 if ( ! Jetpack_Options::get_option( 'version' ) ) {
1572 $version = $old_version = JETPACK__VERSION . ':' . time();
1573 Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1574 }
1575
1576 Jetpack::load_modules();
1577
1578 Jetpack_Options::delete_option( 'do_activate' );
1579 }
1580
1581 /**
1582 * Removes all connection options
1583 * @static
1584 */
1585 public static function plugin_deactivation( ) {
1586 require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
1587 if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
1588 Jetpack_Network::init()->deactivate();
1589 } else {
1590 Jetpack::disconnect( false );
1591 //Jetpack_Heartbeat::init()->deactivate();
1592 }
1593 }
1594
1595 /**
1596 * Disconnects from the Jetpack servers.
1597 * Forgets all connection details and tells the Jetpack servers to do the same.
1598 * @static
1599 */
1600 public static function disconnect( $update_activated_state = true ) {
1601 wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
1602 Jetpack::clean_nonces( true );
1603
1604 Jetpack::load_xml_rpc_client();
1605 $xml = new Jetpack_IXR_Client();
1606 $xml->query( 'jetpack.deregister' );
1607
1608 Jetpack_Options::delete_option(
1609 array(
1610 'register',
1611 'blog_token',
1612 'user_token',
1613 'user_tokens',
1614 'master_user',
1615 'time_diff',
1616 'fallback_no_verify_ssl_certs',
1617 )
1618 );
1619
1620 if ( $update_activated_state ) {
1621 Jetpack_Options::update_option( 'activated', 4 );
1622 }
1623
1624 // Disable the Heartbeat cron
1625 Jetpack_Heartbeat::init()->deactivate();
1626 }
1627
1628 /**
1629 * Unlinks the current user from the linked WordPress.com user
1630 */
1631 public static function unlink_user( $user_id = null ) {
1632 if ( ! $tokens = Jetpack_Options::get_option( 'user_tokens' ) )
1633 return false;
1634
1635 $user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
1636
1637 if ( Jetpack_Options::get_option( 'master_user' ) == $user_id )
1638 return false;
1639
1640 if ( ! isset( $tokens[ $user_id ] ) )
1641 return false;
1642
1643 Jetpack::load_xml_rpc_client();
1644 $xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
1645 $xml->query( 'jetpack.unlink_user', $user_id );
1646
1647 unset( $tokens[ $user_id ] );
1648
1649 Jetpack_Options::update_option( 'user_tokens', $tokens );
1650
1651 return true;
1652 }
1653
1654 /**
1655 * Attempts Jetpack registration. If it fail, a state flag is set: @see ::admin_page_load()
1656 */
1657 public static function try_registration() {
1658 $result = Jetpack::register();
1659
1660 // If there was an error with registration and the site was not registered, record this so we can show a message.
1661 if ( ! $result || is_wp_error( $result ) ) {
1662 return $result;
1663 } else {
1664 return true;
1665 }
1666 }
1667
1668 /**
1669 * Tracking an internal event log. Try not to put too much chaff in here.
1670 *
1671 * [Everyone Loves a Log!](http://www.youtube.com/watch?v=8L6Dpq5kY_A)
1672 */
1673 public static function log( $code, $data = null ) {
1674 $log = Jetpack_Options::get_option( 'log', array() );
1675
1676 // Append our event to the log
1677 $log_entry = array(
1678 'time' => time(),
1679 'user_id' => get_current_user_id(),
1680 'blog_id' => Jetpack_Options::get_option( 'id' ),
1681 'code' => $code,
1682 );
1683 // Don't bother storing it unless we've got some.
1684 if ( ! is_null( $data ) ) {
1685 $log_entry['data'] = $data;
1686 }
1687 $log[] = $log_entry;
1688
1689 // Try add_option first, to make sure it's not autoloaded.
1690 // @todo: Add an add_option method to Jetpack_Options
1691 if ( ! add_option( 'jetpack_log', $log, null, 'no' ) ) {
1692 Jetpack_Options::update_option( 'log', $log );
1693 }
1694
1695 do_action( 'jetpack_log_entry', $log_entry );
1696 }
1697
1698 /**
1699 * Get the internal event log.
1700 */
1701 public static function get_log() {
1702 return Jetpack_Options::get_option( 'log', array() );
1703 }
1704
1705 /* Admin Pages */
1706
1707 function admin_init() {
1708 // If the plugin is not connected, display a connect message.
1709 if (
1710 // the plugin was auto-activated and needs its candy
1711 Jetpack_Options::get_option( 'do_activate' )
1712 ||
1713 // the plugin is active, but was never activated. Probably came from a site-wide network activation
1714 ! Jetpack_Options::get_option( 'activated' )
1715 ) {
1716 Jetpack::plugin_initialize();
1717 }
1718
1719 if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
1720 if ( 4 != Jetpack_Options::get_option( 'activated' ) ) {
1721 // Show connect notice on dashboard and plugins pages
1722 add_action( 'load-index.php', array( $this, 'prepare_connect_notice' ) );
1723 add_action( 'load-plugins.php', array( $this, 'prepare_connect_notice' ) );
1724 }
1725 } elseif ( false === Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) {
1726 // Upgrade: 1.1 -> 1.1.1
1727 // Check and see if host can verify the Jetpack servers' SSL certificate
1728 $args = array();
1729 Jetpack_Client::_wp_remote_request(
1730 Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'test' ) ),
1731 $args,
1732 true
1733 );
1734 }
1735 /* Toggle this off as it's not ready for prime time just yet.
1736 if( current_user_can( 'manage_options' ) && self::check_identity_crisis() ) {
1737 add_action( 'admin_notices', array( $this, 'alert_identity_crisis' ) );
1738 }
1739 /**/
1740
1741 if ( current_user_can( 'manage_options' ) && 'ALWAYS' == JETPACK_CLIENT__HTTPS && ! self::permit_ssl() ) {
1742 add_action( 'admin_notices', array( $this, 'alert_required_ssl_fail' ) );
1743 }
1744
1745 add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
1746 add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
1747 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
1748
1749 if ( Jetpack::is_active() || Jetpack::is_development_mode() ) {
1750 // Artificially throw errors in certain whitelisted cases during plugin activation
1751 add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
1752
1753 // Kick off synchronization of user role when it changes
1754 add_action( 'set_user_role', array( $this, 'user_role_change' ) );
1755 }
1756 }
1757
1758 function admin_body_class( $admin_body_class = '' ) {
1759 $classes = explode( ' ', trim( $admin_body_class ) );
1760
1761 $classes[] = self::is_active() ? 'jetpack-connected' : 'jetpack-disconnected';
1762
1763 // Handle pre-mp6 styling by adding a 'pre-mp6' body class.
1764 include( ABSPATH . WPINC . '/version.php' );
1765 if ( version_compare( $wp_version, '3.8-alpha', '<' ) ) {
1766 $classes[] = 'pre-mp6';
1767 }
1768
1769 return implode( ' ', array_unique( $classes ) );
1770 }
1771
1772 static function add_jetpack_pagestyles( $admin_body_class = '' ) {
1773 return $admin_body_class . ' jetpack-pagestyles';
1774 }
1775
1776 function prepare_connect_notice() {
1777 add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
1778
1779 add_action( 'admin_notices', array( $this, 'admin_connect_notice' ) );
1780
1781 if ( Jetpack::state( 'network_nag' ) )
1782 add_action( 'network_admin_notices', array( $this, 'network_connect_notice' ) );
1783 }
1784
1785 /**
1786 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
1787 * This function artificially throws errors for such cases (whitelisted).
1788 *
1789 * @param string $plugin The activated plugin.
1790 */
1791 function throw_error_on_activate_plugin( $plugin ) {
1792 $active_modules = Jetpack::get_active_modules();
1793
1794 // The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
1795 if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules ) ) {
1796 $throw = false;
1797
1798 // Try and make sure it really was the stats plugin
1799 if ( ! class_exists( 'ReflectionFunction' ) ) {
1800 if ( 'stats.php' == basename( $plugin ) ) {
1801 $throw = true;
1802 }
1803 } else {
1804 $reflection = new ReflectionFunction( 'stats_get_api_key' );
1805 if ( basename( $plugin ) == basename( $reflection->getFileName() ) ) {
1806 $throw = true;
1807 }
1808 }
1809
1810 if ( $throw ) {
1811 trigger_error( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR );
1812 }
1813 }
1814 }
1815
1816 function intercept_plugin_error_scrape_init() {
1817 add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
1818 }
1819
1820 function intercept_plugin_error_scrape( $action, $result ) {
1821 if ( ! $result ) {
1822 return;
1823 }
1824
1825 foreach ( $this->plugins_to_deactivate as $deactivate_me ) {
1826 if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
1827 Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
1828 }
1829 }
1830 }
1831
1832 function admin_menu() {
1833 list( $jetpack_version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
1834 if (
1835 $jetpack_version
1836 &&
1837 $jetpack_version != JETPACK__VERSION
1838 &&
1839 ( $new_modules = Jetpack::get_default_modules( $jetpack_version, JETPACK__VERSION ) )
1840 &&
1841 is_array( $new_modules )
1842 &&
1843 ( $new_modules_count = count( $new_modules ) )
1844 &&
1845 ( Jetpack::is_active() || Jetpack::is_development_mode() )
1846 ) {
1847 $new_modules_count_i18n = number_format_i18n( $new_modules_count );
1848 $span_title = esc_attr( sprintf( _n( 'One New Jetpack Module', '%s New Jetpack Modules', $new_modules_count, 'jetpack' ), $new_modules_count_i18n ) );
1849 $title = sprintf( 'Jetpack %s', "<span class='update-plugins count-{$new_modules_count}' title='$span_title'><span class='update-count'>$new_modules_count_i18n</span></span>" );
1850 } else {
1851 $title = __( 'Jetpack', 'jetpack' );
1852 }
1853
1854 $hook = add_menu_page( 'Jetpack', $title, 'read', 'jetpack', array( $this, 'admin_page' ), 'div' );
1855
1856 $debugger_hook = add_submenu_page( null, __( 'Jetpack Debugging Center', 'jetpack' ), '', 'manage_options', 'jetpack-debugger', array( $this, 'debugger_page' ) );
1857 add_action( "admin_head-$debugger_hook", array( 'Jetpack_Debugger', 'jetpack_debug_admin_head' ) );
1858
1859 add_action( "load-$hook", array( $this, 'admin_page_load' ) );
1860
1861 if ( version_compare( $GLOBALS['wp_version'], '3.3', '<' ) ) {
1862 if ( isset( $_GET['page'] ) && 'jetpack' == $_GET['page'] ) {
1863 add_contextual_help( $hook, $this->jetpack_help() );
1864 }
1865 } else {
1866 add_action( "load-$hook", array( $this, 'admin_help' ) );
1867 }
1868 add_action( "admin_head-$hook", array( $this, 'admin_head' ) );
1869 add_filter( 'custom_menu_order', array( $this, 'admin_menu_order' ) );
1870 add_filter( 'menu_order', array( $this, 'jetpack_menu_order' ) );
1871
1872 add_action( "admin_print_styles-$hook", array( $this, 'admin_styles' ) );
1873
1874 add_action( "admin_print_scripts-$hook", array( $this, 'admin_scripts' ) );
1875
1876 do_action( 'jetpack_admin_menu', $hook );
1877 }
1878 /*
1879 function admin_menu_modules() {
1880 $hook = add_submenu_page( 'jetpack', __( 'Jetpack Modules', 'jetpack' ), __( 'Modules', 'jetpack' ), 'edit_posts', 'jetpack_modules', array( $this, 'admin_page_modules' ) );
1881
1882 add_action( "load-$hook", array( $this, 'admin_page_load' ) );
1883 add_action( "load-$hook", array( $this, 'admin_help' ) );
1884 add_action( "admin_head-$hook", array( $this, 'admin_head' ) );
1885 add_action( "admin_print_styles-$hook", array( $this, 'admin_styles' ) );
1886 add_action( "admin_print_scripts-$hook", array( $this, 'admin_scripts' ) );
1887 }
1888 /**/
1889 function add_remote_request_handlers() {
1890 add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
1891 }
1892
1893 function remote_request_handlers() {
1894 switch ( current_filter() ) {
1895 case 'wp_ajax_nopriv_jetpack_upload_file' :
1896 $response = $this->upload_handler();
1897 break;
1898 default :
1899 $response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
1900 break;
1901 }
1902
1903 if ( ! $response ) {
1904 $response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
1905 }
1906
1907 if ( is_wp_error( $response ) ) {
1908 $status_code = $response->get_error_data();
1909 $error = $response->get_error_code();
1910 $error_description = $response->get_error_message();
1911
1912 if ( ! is_int( $status_code ) ) {
1913 $status_code = 400;
1914 }
1915
1916 status_header( $status_code );
1917 die( json_encode( (object) compact( 'error', 'error_description' ) ) );
1918 }
1919
1920 status_header( 200 );
1921 if ( true === $response ) {
1922 exit;
1923 }
1924
1925 die( json_encode( (object) $response ) );
1926 }
1927
1928 function upload_handler() {
1929 if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
1930 return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
1931 }
1932
1933 $user = wp_authenticate( '', '' );
1934 if ( ! $user || is_wp_error( $user ) ) {
1935 return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
1936 }
1937
1938 wp_set_current_user( $user->ID );
1939
1940 if ( ! current_user_can( 'upload_files' ) ) {
1941 return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
1942 }
1943
1944 if ( empty( $_FILES ) ) {
1945 return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
1946 }
1947
1948 foreach ( array_keys( $_FILES ) as $files_key ) {
1949 if ( ! isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
1950 return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
1951 }
1952 }
1953
1954 $media_keys = array_keys( $_FILES['media'] );
1955
1956 $token = Jetpack_Data::get_access_token( get_current_user_id() );
1957 if ( ! $token || is_wp_error( $token ) ) {
1958 return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
1959 }
1960
1961 $uploaded_files = array();
1962 $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
1963 unset( $GLOBALS['post'] );
1964 foreach ( $_FILES['media']['name'] as $index => $name ) {
1965 $file = array();
1966 foreach ( $media_keys as $media_key ) {
1967 $file[$media_key] = $_FILES['media'][$media_key][$index];
1968 }
1969
1970 list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
1971
1972 $hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
1973 if ( $hmac_provided !== $hmac_file ) {
1974 $uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
1975 continue;
1976 }
1977
1978 $_FILES['.jetpack.upload.'] = $file;
1979 $post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
1980 if ( ! current_user_can( 'edit_post', $post_id ) ) {
1981 $post_id = 0;
1982 }
1983 $attachment_id = media_handle_upload(
1984 '.jetpack.upload.',
1985 $post_id,
1986 array(),
1987 array(
1988 'action' => 'jetpack_upload_file',
1989 )
1990 );
1991
1992 if ( ! $attachment_id ) {
1993 $uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
1994 } elseif ( is_wp_error( $attachment_id ) ) {
1995 $uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
1996 } else {
1997 $attachment = get_post( $attachment_id );
1998 $uploaded_files[$index] = (object) array(
1999 'id' => (string) $attachment_id,
2000 'file' => $attachment->post_title,
2001 'url' => wp_get_attachment_url( $attachment_id ),
2002 'type' => $attachment->post_mime_type,
2003 'meta' => wp_get_attachment_metadata( $attachment_id ),
2004 );
2005 }
2006 }
2007 if ( ! is_null( $global_post ) ) {
2008 $GLOBALS['post'] = $global_post;
2009 }
2010
2011 return $uploaded_files;
2012 }
2013
2014 /**
2015 * Add help to the Jetpack page
2016 *
2017 * Deprecated. Remove when Jetpack requires WP 3.3+
2018 */
2019 function jetpack_help() {
2020 return
2021 '<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
2022 '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
2023 '<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>' .
2024 '<p><strong>' . __( 'Jetpack Module Options', 'jetpack' ) . '</strong></p>' .
2025 '<p>' . __( '<strong>To Activate/Deactivate a Module</strong> - Click on Learn More. An Activate or Deactivate button will now appear next to the Learn More button. Click the Activate/Deactivate button.', 'jetpack' ) . '</p>' .
2026 '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
2027 '<p><a href="http://jetpack.me/faq/" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
2028 '<p><a href="http://jetpack.me/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>';
2029 }
2030
2031 /**
2032 * Add help to the Jetpack page
2033 *
2034 * @since Jetpack (1.2.3)
2035 * @return false if not the Jetpack page
2036 */
2037 function admin_help() {
2038 $current_screen = get_current_screen();
2039
2040 // Overview
2041 $current_screen->add_help_tab(
2042 array(
2043 'id' => 'overview',
2044 'title' => __( 'Overview', 'jetpack' ),
2045 'content' =>
2046 '<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
2047 '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
2048 '<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>',
2049 )
2050 );
2051
2052 // Screen Content
2053 if ( current_user_can( 'manage_options' ) ) {
2054 $current_screen->add_help_tab(
2055 array(
2056 'id' => 'modules',
2057 'title' => __( 'Modules', 'jetpack' ),
2058 'content' =>
2059 '<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
2060 '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
2061 '<ol>' .
2062 '<li>' . __( 'Find the component you want to manage', 'jetpack' ) . '</li>' .
2063 '<li>' . __( 'Click on Learn More', 'jetpack' ) . '</li>' .
2064 '<li>' . __( 'An Activate or Deactivate button will appear', 'jetpack' ) . '</li>' .
2065 '<li>' . __( 'If additional settings are available, a link to them will appear', 'jetpack' ) . '</li>' .
2066 '</ol>',
2067 )
2068 );
2069 }
2070
2071 // Help Sidebar
2072 $current_screen->set_help_sidebar(
2073 '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
2074 '<p><a href="http://jetpack.me/faq/" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
2075 '<p><a href="http://jetpack.me/support/" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
2076 '<p><a href="' . Jetpack::admin_url( array( 'page' => 'jetpack-debugger' ) ) .'">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
2077 );
2078 }
2079
2080 function admin_menu_css() {
2081 // Make sure we're working off a clean version.
2082 include( ABSPATH . WPINC . '/version.php' );
2083 if ( version_compare( $wp_version, '3.8-alpha', '>=' ) ) {
2084 wp_enqueue_style( 'jetpack-icons' );
2085 $css = "
2086 #toplevel_page_jetpack .wp-menu-image:before {
2087 font-family: 'Jetpack' !important;
2088 content: '\\e600';
2089 }
2090 #toplevel_page_jetpack .wp-menu-image {
2091 background-repeat: no-repeat;
2092 }
2093 #menu-posts-feedback .wp-menu-image:before {
2094 font-family: dashicons !important;
2095 content: '\\f175';
2096 }
2097 #adminmenu #menu-posts-feedback div.wp-menu-image {
2098 background: none !important;
2099 background-repeat: no-repeat;
2100 }";
2101 } else {
2102 $css = "
2103 #toplevel_page_jetpack .wp-menu-image {
2104 background: url( " . plugins_url( '_inc/images/menuicon-sprite.png', __FILE__ ) . " ) 0 90% no-repeat;
2105 }
2106 /* Retina Jetpack Menu Icon */
2107 @media only screen and (-moz-min-device-pixel-ratio: 1.5),
2108 only screen and (-o-min-device-pixel-ratio: 3/2),
2109 only screen and (-webkit-min-device-pixel-ratio: 1.5),
2110 only screen and (min-device-pixel-ratio: 1.5) {
2111 #toplevel_page_jetpack .wp-menu-image {
2112 background: url( " . plugins_url( '_inc/images/menuicon-sprite-2x.png', __FILE__ ) . " ) 0 90% no-repeat;
2113 background-size:30px 64px;
2114 }
2115 }
2116 #toplevel_page_jetpack.current .wp-menu-image,
2117 #toplevel_page_jetpack.wp-has-current-submenu .wp-menu-image,
2118 #toplevel_page_jetpack:hover .wp-menu-image {
2119 background-position: top left;
2120 }";
2121 }
2122 wp_add_inline_style( 'wp-admin', $css );
2123 }
2124
2125 function admin_menu_order() {
2126 return true;
2127 }
2128
2129 function jetpack_menu_order( $menu_order ) {
2130 $jp_menu_order = array();
2131
2132 foreach ( $menu_order as $index => $item ) {
2133 if ( $item != 'jetpack' )
2134 $jp_menu_order[] = $item;
2135
2136 if ( $index == 0 )
2137 $jp_menu_order[] = 'jetpack';
2138 }
2139
2140 return $jp_menu_order;
2141 }
2142
2143 function admin_head() {
2144 if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) )
2145 do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
2146 }
2147
2148 function admin_styles() {
2149 global $wp_styles;
2150
2151 $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
2152
2153 wp_enqueue_style( 'jetpack', plugins_url( "_inc/jetpack{$min}.css", __FILE__ ), false, JETPACK__VERSION . '-20121016' );
2154 $wp_styles->add_data( 'jetpack', 'rtl', true );
2155 }
2156
2157 function admin_banner_styles() {
2158 global $wp_styles;
2159
2160 $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
2161
2162 wp_enqueue_style( 'jetpack', plugins_url( "_inc/jetpack-banners{$min}.css", __FILE__ ), false, JETPACK__VERSION . '-20121016' );
2163 $wp_styles->add_data( 'jetpack', 'rtl', true );
2164 }
2165
2166 function admin_scripts() {
2167 wp_enqueue_script( 'jetpack-icanhaz', plugins_url( '_inc/icanhaz.js', __FILE__ ), array( ), JETPACK__VERSION . '-20121111' );
2168 wp_enqueue_script( 'jetpack-js', plugins_url( '_inc/jp.js', __FILE__ ), array( 'jquery' ), JETPACK__VERSION . '-20121111' );
2169 wp_localize_script(
2170 'jetpack-js',
2171 'jetpackL10n',
2172 array(
2173 'ays_disconnect' => "This will deactivate all Jetpack modules.\nAre you sure you want to disconnect?",
2174 'ays_unlink' => "This will prevent user-specific modules such as Publicize, Notifications and Post By Email from working.\nAre you sure you want to unlink?",
2175 'ays_dismiss' => "This will deactivate Jetpack.\nAre you sure you want to deactivate Jetpack?",
2176 )
2177 );
2178 add_action( 'admin_footer', array( $this, 'do_stats' ) );
2179 }
2180
2181 function plugin_action_links( $actions ) {
2182 return array_merge(
2183 array( 'settings' => sprintf( '<a href="%s">%s</a>', Jetpack::admin_url(), __( 'Settings', 'jetpack' ) ) ),
2184 $actions
2185 );
2186 return $actions;
2187 }
2188
2189 function admin_connect_notice() {
2190 // Don't show the connect notice on the jetpack settings page. @todo: must be a better way?
2191 if ( false !== strpos( $_SERVER['QUERY_STRING'], 'page=jetpack' ) )
2192 return;
2193
2194 if ( ! current_user_can( 'jetpack_connect' ) )
2195 return;
2196
2197 $dismiss_and_deactivate_url = wp_nonce_url( Jetpack::admin_url( '?page=jetpack&jetpack-notice=dismiss' ), 'jetpack-deactivate' );
2198 ?>
2199 <div id="message" class="updated jetpack-message jp-connect" style="display:block !important;">
2200 <div id="jp-dismiss" class="jetpack-close-button-container">
2201 <a class="jetpack-close-button" href="<?php echo esc_url( $dismiss_and_deactivate_url ); ?>" title="<?php _e( 'Dismiss this notice and deactivate Jetpack.', 'jetpack' ); ?>"></a>
2202 </div>
2203 <div class="jetpack-wrap-container">
2204 <div class="jetpack-install-container">
2205 <?php if ( 1 == Jetpack_Options::get_option( 'activated' ) ) : ?>
2206 <p class="submit"><a href="<?php echo $this->build_connect_url() ?>" class="download-jetpack" id="wpcom-connect"><?php _e( 'Connect to WordPress.com', 'jetpack' ); ?></a></p>
2207 <?php else : ?>
2208 <p class="submit"><a href="<?php echo Jetpack::admin_url() ?>" class="button-connector" id="wpcom-connect"><?php _e( 'Learn More', 'jetpack' ); ?></a></p>
2209 <?php endif; ?>
2210 </div>
2211 <div class="jetpack-text-container">
2212 <?php if ( 1 == Jetpack_Options::get_option( 'activated' ) ) : ?>
2213 <p><?php _e( '<strong>Your Jetpack is almost ready!</strong>', 'jetpack' ); ?></p>
2214 <p><?php _e( 'Connect now to enable features like Stats, Likes, and Social Sharing.', 'jetpack' ); ?></p>
2215 <?php else : ?>
2216 <p><?php _e( '<strong>Jetpack is installed</strong>', 'jetpack' ) ?></p>
2217 <p><?php _e( 'It\'s ready to bring awesome, WordPress.com cloud-powered features to your site.', 'jetpack' ) ?></p>
2218 <?php endif; ?>
2219 </div>
2220 </div>
2221 </div>
2222
2223 <?php
2224 }
2225
2226 function network_connect_notice() {
2227 ?>
2228 <div id="message" class="updated jetpack-message">
2229 <div class="squeezer">
2230 <h4><?php _e( '<strong>Jetpack is activated!</strong> Each site on your network must be connected individually by an admin on that site.', 'jetpack' ) ?></h4>
2231 </div>
2232 </div>
2233 <?php
2234 }
2235
2236 public static function jetpack_comment_notice() {
2237 if ( in_array( 'comments', Jetpack::get_active_modules() ) ) {
2238 return '';
2239 }
2240
2241 $jetpack_old_version = explode( ':', Jetpack_Options::get_option( 'old_version' ) );
2242 $jetpack_new_version = explode( ':', Jetpack_Options::get_option( 'version' ) );
2243
2244 if ( $jetpack_old_version ) {
2245 if ( version_compare( $jetpack_old_version[0], '1.4', '>=' ) ) {
2246 return '';
2247 }
2248 }
2249
2250 if ( $jetpack_new_version ) {
2251 if ( version_compare( $jetpack_new_version[0], '1.4-something', '<' ) ) {
2252 return '';
2253 }
2254 }
2255
2256 return '<br /><br />' . sprintf(
2257 __( 'Jetpack now includes Jetpack Comments, which enables your visitors to use their WordPress.com, Twitter, or Facebook accounts when commenting on your site. To activate Jetpack Comments, <a href="%s">%s</a>.', 'jetpack' ),
2258 wp_nonce_url(
2259 Jetpack::admin_url(
2260 array(
2261 'page' => 'jetpack',
2262 'action' => 'activate',
2263 'module' => 'comments',
2264 )
2265 ),
2266 'jetpack_activate-comments'
2267 ),
2268 __( 'click here', 'jetpack' )
2269 );
2270 }
2271
2272 /*
2273 * Registration flow:
2274 * 1 - ::admin_page_load() action=register
2275 * 2 - ::try_registration()
2276 * 3 - ::register()
2277 * - Creates jetpack_register option containing two secrets and a timestamp
2278 * - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
2279 * siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
2280 * - That request to jetpack.wordpress.com does not immediately respond. It first makes a request BACK to this site's
2281 * xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
2282 * - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
2283 * - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
2284 * jetpack_id, jetpack_secret, jetpack_public
2285 * - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
2286 * 4 - redirect to https://jetpack.wordpress.com/jetpack.authorize/1/
2287 * 5 - user logs in with WP.com account
2288 * 6 - redirect to this site's wp-admin/index.php?page=jetpack&action=authorize with
2289 * code <-- OAuth2 style authorization code
2290 * 7 - ::admin_page_load() action=authorize
2291 * 8 - Jetpack_Client_Server::authorize()
2292 * 9 - Jetpack_Client_Server::get_token()
2293 * 10- GET https://jetpack.wordpress.com/jetpack.token/1/ with
2294 * client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
2295 * 11- which responds with
2296 * access_token, token_type, scope
2297 * 12- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
2298 * 13- Jetpack::activate_default_modules()
2299 * Deactivates deprecated plugins
2300 * Activates all default modules
2301 * Catches errors: redirects to wp-admin/index.php?page=jetpack state:error=something
2302 * 14- redirect to this site's wp-admin/index.php?page=jetpack with state:message=authorized
2303 * Done!
2304 */
2305
2306 /**
2307 * Handles the page load events for the Jetpack admin page
2308 */
2309 function admin_page_load() {
2310 $error = false;
2311
2312 // Make sure we have the right body class to hook stylings for subpages off of.
2313 add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ) );
2314
2315 if ( ! empty( $_GET['jetpack_restate'] ) ) {
2316 // Should only be used in intermediate redirects to preserve state across redirects
2317 Jetpack::restate();
2318 }
2319
2320 if ( isset( $_GET['connect_url_redirect'] ) ) {
2321 // User clicked in the iframe to link their accounts
2322 if ( ! Jetpack::is_user_connected() ) {
2323 $connect_url = $this->build_connect_url( true );
2324 if ( isset( $_GET['notes_iframe'] ) )
2325 $connect_url .= '&notes_iframe';
2326 wp_redirect( $connect_url );
2327 exit;
2328 } else {
2329 Jetpack::state( 'message', 'already_authorized' );
2330 wp_safe_redirect( Jetpack::admin_url() );
2331 exit;
2332 }
2333 }
2334
2335 if ( isset( $_GET['action'] ) ) {
2336 switch ( $_GET['action'] ) {
2337 case 'authorize' :
2338 if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
2339 Jetpack::state( 'message', 'already_authorized' );
2340 wp_safe_redirect( Jetpack::admin_url() );
2341 exit;
2342 }
2343 Jetpack::log( 'authorize' );
2344 $client_server = new Jetpack_Client_Server;
2345 $client_server->authorize();
2346 exit;
2347 case 'register' :
2348 check_admin_referer( 'jetpack-register' );
2349 Jetpack::log( 'register' );
2350 $registered = Jetpack::try_registration();
2351 if ( is_wp_error( $registered ) ) {
2352 $error = $registered->get_error_code();
2353 Jetpack::state( 'error_description', $registered->get_error_message() );
2354 break;
2355 }
2356
2357 wp_redirect( $this->build_connect_url( true ) );
2358 exit;
2359 case 'activate' :
2360 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
2361 $error = 'cheatin';
2362 break;
2363 }
2364
2365 $module = stripslashes( $_GET['module'] );
2366 check_admin_referer( "jetpack_activate-$module" );
2367 Jetpack::log( 'activate', $module );
2368 Jetpack::activate_module( $module );
2369 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
2370 wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2371 exit;
2372 case 'activate_default_modules' :
2373 check_admin_referer( 'activate_default_modules' );
2374 Jetpack::log( 'activate_default_modules' );
2375 Jetpack::restate();
2376 $min_version = isset( $_GET['min_version'] ) ? $_GET['min_version'] : false;
2377 $max_version = isset( $_GET['max_version'] ) ? $_GET['max_version'] : false;
2378 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? $_GET['other_modules'] : array();
2379 Jetpack::activate_default_modules( $min_version, $max_version, $other_modules );
2380 wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2381 exit;
2382 case 'disconnect' :
2383 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
2384 $error = 'cheatin';
2385 break;
2386 }
2387
2388 check_admin_referer( 'jetpack-disconnect' );
2389 Jetpack::log( 'disconnect' );
2390 Jetpack::disconnect();
2391 wp_safe_redirect( Jetpack::admin_url() );
2392 exit;
2393 case 'reconnect' :
2394 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
2395 $error = 'cheatin';
2396 break;
2397 }
2398
2399 check_admin_referer( 'jetpack-reconnect' );
2400 Jetpack::log( 'reconnect' );
2401 $this->disconnect();
2402 wp_redirect( $this->build_connect_url( true ) );
2403 exit;
2404 case 'deactivate' :
2405 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
2406 $error = 'cheatin';
2407 break;
2408 }
2409
2410 $modules = stripslashes( $_GET['module'] );
2411 check_admin_referer( "jetpack_deactivate-$modules" );
2412 foreach ( explode( ',', $modules ) as $module ) {
2413 Jetpack::log( 'deactivate', $module );
2414 Jetpack::deactivate_module( $module );
2415 Jetpack::state( 'message', 'module_deactivated' );
2416 }
2417 Jetpack::state( 'module', $modules );
2418 wp_safe_redirect( Jetpack::admin_url( 'page=jetpack' ) );
2419 exit;
2420 case 'unlink' :
2421 check_admin_referer( 'jetpack-unlink' );
2422 Jetpack::log( 'unlink' );
2423 $this->unlink_user();
2424 Jetpack::state( 'message', 'unlinked' );
2425 wp_safe_redirect( Jetpack::admin_url() );
2426 exit;
2427 default:
2428 do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
2429 }
2430 }
2431
2432 if ( ! $error = $error ? $error : Jetpack::state( 'error' ) ) {
2433 $this->activate_new_modules();
2434 }
2435
2436 switch ( $error ) {
2437 case 'cheatin' :
2438 $this->error = __( 'Cheatin&#8217; uh?', 'jetpack' );
2439 break;
2440 case 'access_denied' :
2441 $this->error = __( 'You need to authorize the Jetpack connection between your site and WordPress.com to enable the awesome features.', 'jetpack' );
2442 break;
2443 case 'wrong_state' :
2444 $this->error = __( 'Don&#8217;t cross the streams! You need to stay logged in to your WordPress blog while you authorize Jetpack.', 'jetpack' );
2445 break;
2446 case 'invalid_client' :
2447 // @todo re-register instead of deactivate/reactivate
2448 $this->error = __( 'Return to sender. Whoops! It looks like you got the wrong Jetpack in the mail; deactivate then reactivate the Jetpack plugin to get a new one.', 'jetpack' );
2449 break;
2450 case 'invalid_grant' :
2451 $this->error = __( 'Wrong size. Hm&#8230; it seems your Jetpack doesn&#8217;t quite fit. Have you lost weight? Click &#8220;Connect to WordPress.com&#8221; again to get your Jetpack adjusted.', 'jetpack' );
2452 break;
2453 case 'site_inaccessible' :
2454 case 'site_requires_authorization' :
2455 $this->error = sprintf( __( 'Your website needs to be publicly accessible to use Jetpack: %s', 'jetpack' ), "<code>$error</code>" );
2456 break;
2457 case 'module_activation_failed' :
2458 $module = Jetpack::state( 'module' );
2459 if ( ! empty( $module ) && $mod = Jetpack::get_module( $module ) ) {
2460 $this->error = sprintf( __( '%s could not be activated because it triggered a <strong>fatal error</strong>. Perhaps there is a conflict with another plugin you have installed?', 'jetpack' ), $mod['name'] );
2461 if ( isset( $this->plugins_to_deactivate[$module] ) ) {
2462 $this->error .= ' ' . sprintf( __( 'Do you still have the %s plugin installed?', 'jetpack' ), $this->plugins_to_deactivate[$module][1] );
2463 }
2464 } else {
2465 $this->error = __( 'Module could not be activated because it triggered a <strong>fatal error</strong>. Perhaps there is a conflict with another plugin you have installed?', 'jetpack' );
2466 }
2467 if ( $php_errors = Jetpack::state( 'php_errors' ) ) {
2468 $this->error .= "<br />\n";
2469 $this->error .= $php_errors;
2470 }
2471 break;
2472 case 'master_user_required' :
2473 $module = Jetpack::state( 'module' );
2474 $module_name = '';
2475 if ( ! empty( $module ) && $mod = Jetpack::get_module( $module ) ) {
2476 $module_name = $mod['name'];
2477 }
2478
2479 $master_user = Jetpack_Options::get_option( 'master_user' );
2480 $master_userdata = get_userdata( $master_user ) ;
2481 if ( $master_userdata ) {
2482 if ( ! in_array( $module, Jetpack::get_active_modules() ) ) {
2483 $this->error = sprintf( __( '%s was not activated.' , 'jetpack' ), $module_name );
2484 } else {
2485 $this->error = sprintf( __( '%s was not deactivated.' , 'jetpack' ), $module_name );
2486 }
2487 $this->error .= ' ' . sprintf( __( 'This module can only be altered by %s, the user who initiated the Jetpack connection on this site.' , 'jetpack' ), esc_html( $master_userdata->display_name ) );
2488
2489 } else {
2490 $this->error = sprintf( __( 'Only the user who initiated the Jetpack connection on this site can toggle %s, but that user no longer exists. This should not happen.', 'jetpack' ), $module_name );
2491 }
2492 break;
2493 case 'not_public' :
2494 $this->error = __( '<strong>Your Jetpack has a glitch.</strong> Connecting this site with WordPress.com is not possible. This usually means your site is not publicly accessible (localhost).', 'jetpack' );
2495 break;
2496 case 'wpcom_408' :
2497 case 'wpcom_5??' :
2498 case 'wpcom_bad_response' :
2499 case 'wpcom_outage' :
2500 $this->error = __( 'WordPress.com is currently having problems and is unable to fuel up your Jetpack. Please try again later.', 'jetpack' );
2501 break;
2502 case 'register_http_request_failed' :
2503 case 'token_http_request_failed' :
2504 $this->error = sprintf( __( 'Jetpack could not contact WordPress.com: %s. This usually means something is incorrectly configured on your web host.', 'jetpack' ), "<code>$error</code>" );
2505 break;
2506 default :
2507 if ( empty( $error ) ) {
2508 break;
2509 }
2510 $error = trim( substr( strip_tags( $error ), 0, 20 ) );
2511 // no break: fall through
2512 case 'no_role' :
2513 case 'no_cap' :
2514 case 'no_code' :
2515 case 'no_state' :
2516 case 'invalid_state' :
2517 case 'invalid_request' :
2518 case 'invalid_scope' :
2519 case 'unsupported_response_type' :
2520 case 'invalid_token' :
2521 case 'no_token' :
2522 case 'missing_secrets' :
2523 case 'home_missing' :
2524 case 'siteurl_missing' :
2525 case 'gmt_offset_missing' :
2526 case 'site_name_missing' :
2527 case 'secret_1_missing' :
2528 case 'secret_2_missing' :
2529 case 'site_lang_missing' :
2530 case 'home_malformed' :
2531 case 'siteurl_malformed' :
2532 case 'gmt_offset_malformed' :
2533 case 'timezone_string_malformed' :
2534 case 'site_name_malformed' :
2535 case 'secret_1_malformed' :
2536 case 'secret_2_malformed' :
2537 case 'site_lang_malformed' :
2538 case 'secrets_mismatch' :
2539 case 'verify_secret_1_missing' :
2540 case 'verify_secret_1_malformed' :
2541 case 'verify_secrets_missing' :
2542 case 'verify_secrets_mismatch' :
2543 $error = esc_html( $error );
2544 $this->error = sprintf( __( '<strong>Your Jetpack has a glitch.</strong> Something went wrong that&#8217;s never supposed to happen. Guess you&#8217;re just lucky: %s', 'jetpack' ), "<code>$error</code>" );
2545 if ( ! Jetpack::is_active() ) {
2546 $this->error .= '<br />';
2547 $this->error .= sprintf( __( 'Try connecting again.', 'jetpack' ) );
2548 }
2549 break;
2550 }
2551
2552 $message_code = Jetpack::state( 'message' );
2553
2554 $active_state = Jetpack::state( 'activated_modules' );
2555 if ( ! empty( $active_state ) ) {
2556 $available = Jetpack::get_available_modules();
2557 $active_state = explode( ',', $active_state );
2558 $active_state = array_intersect( $active_state, $available );
2559 if ( count( $active_state ) ) {
2560 foreach ( $active_state as $mod ) {
2561 $this->stat( 'module-activated', $mod );
2562 }
2563 } else {
2564 $active_state = false;
2565 }
2566 }
2567
2568 switch ( $message_code ) {
2569 case 'modules_activated' :
2570 $this->message = sprintf(
2571 __( 'Welcome to <strong>Jetpack %s</strong>!', 'jetpack' ),
2572 JETPACK__VERSION
2573 );
2574
2575 if ( $active_state ) {
2576 $titles = array();
2577 foreach ( $active_state as $mod ) {
2578 if ( $mod_headers = Jetpack::get_module( $mod ) ) {
2579 $titles[] = '<strong>' . preg_replace( '/\s+(?![^<>]++>)/', '&nbsp;', $mod_headers['name'] ) . '</strong>';
2580 }
2581 }
2582 if ( $titles ) {
2583 $this->message .= '<br /><br />' . wp_sprintf( __( 'The following new modules have been activated: %l.', 'jetpack' ), $titles );
2584 }
2585 }
2586
2587 if ( $reactive_state = Jetpack::state( 'reactivated_modules' ) ) {
2588 $titles = array();
2589 foreach ( explode( ',', $reactive_state ) as $mod ) {
2590 if ( $mod_headers = Jetpack::get_module( $mod ) ) {
2591 $titles[] = '<strong>' . preg_replace( '/\s+(?![^<>]++>)/', '&nbsp;', $mod_headers['name'] ) . '</strong>';
2592 }
2593 }
2594 if ( $titles ) {
2595 $this->message .= '<br /><br />' . wp_sprintf( __( 'The following modules have been updated: %l.', 'jetpack' ), $titles );
2596 }
2597 }
2598
2599 $this->message .= Jetpack::jetpack_comment_notice();
2600 break;
2601
2602 case 'module_activated' :
2603 if ( $module = Jetpack::get_module( Jetpack::state( 'module' ) ) ) {
2604 $this->message = sprintf( __( '<strong>%s Activated!</strong> You can deactivate at any time by clicking Learn More and then Deactivate on the module card.', 'jetpack' ), $module['name'] );
2605 $this->stat( 'module-activated', Jetpack::state( 'module' ) );
2606 }
2607 break;
2608
2609 case 'module_deactivated' :
2610 $modules = Jetpack::state( 'module' );
2611 if ( ! $modules ) {
2612 break;
2613 }
2614
2615 $module_names = array();
2616 foreach ( explode( ',', $modules ) as $module_slug ) {
2617 $module = Jetpack::get_module( $module_slug );
2618 if ( $module ) {
2619 $module_names[] = $module['name'];
2620 }
2621
2622 $this->stat( 'module-deactivated', $module_slug );
2623 }
2624
2625 if ( ! $module_names ) {
2626 break;
2627 }
2628
2629 $this->message = wp_sprintf(
2630 _nx(
2631 '<strong>%l Deactivated!</strong> You can activate it again at any time using the activate button on the module card.',
2632 '<strong>%l Deactivated!</strong> You can activate them again at any time using the activate buttons on their module cards.',
2633 count( $module_names ),
2634 '%l = list of Jetpack module/feature names',
2635 'jetpack'
2636 ),
2637 $module_names
2638 );
2639 break;
2640
2641 case 'module_configured' :
2642 $this->message = __( '<strong>Module settings were saved.</strong> ', 'jetpack' );
2643 break;
2644
2645 case 'already_authorized' :
2646 $this->message = __( '<strong>Your Jetpack is already connected.</strong> ', 'jetpack' );
2647 break;
2648
2649 case 'authorized' :
2650 $this->message = __( '<strong>You&#8217;re fueled up and ready to go.</strong> ', 'jetpack' );
2651 $this->message .= "<br />\n";
2652 $this->message .= sprintf( __( 'Jetpack is now active. Browse through each Jetpack feature below. Visit the <a href="%s">settings page</a> to activate/deactivate features.', 'jetpack' ), admin_url( 'admin.php?page=jetpack_modules' ) );
2653 $this->message .= Jetpack::jetpack_comment_notice();
2654 break;
2655
2656 case 'linked' :
2657 $this->message = __( '<strong>You&#8217;re fueled up and ready to go.</strong> ', 'jetpack' );
2658 $this->message .= Jetpack::jetpack_comment_notice();
2659 break;
2660
2661 case 'unlinked' :
2662 $user = wp_get_current_user();
2663 $this->message = sprintf( __( '<strong>You have unlinked your account (%s) from WordPress.com.</strong>', 'jetpack' ), $user->user_login );
2664 break;
2665 }
2666
2667 $deactivated_plugins = Jetpack::state( 'deactivated_plugins' );
2668
2669 if ( ! empty( $deactivated_plugins ) ) {
2670 $deactivated_plugins = explode( ',', $deactivated_plugins );
2671 $deactivated_titles = array();
2672 foreach ( $deactivated_plugins as $deactivated_plugin ) {
2673 if ( ! isset( $this->plugins_to_deactivate[$deactivated_plugin] ) ) {
2674 continue;
2675 }
2676
2677 $deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[$deactivated_plugin][1] ) . '</strong>';
2678 }
2679
2680 if ( $deactivated_titles ) {
2681 if ( $this->message ) {
2682 $this->message .= "<br /><br />\n";
2683 }
2684
2685 $this->message .= wp_sprintf(
2686 _n(
2687 'Jetpack contains the most recent version of the old %l plugin.',
2688 'Jetpack contains the most recent versions of the old %l plugins.',
2689 count( $deactivated_titles ),
2690 'jetpack'
2691 ),
2692 $deactivated_titles
2693 );
2694
2695 $this->message .= "<br />\n";
2696
2697 $this->message .= _n(
2698 'The old version has been deactivated and can be removed from your site.',
2699 'The old versions have been deactivated and can be removed from your site.',
2700 count( $deactivated_titles ),
2701 'jetpack'
2702 );
2703 }
2704 }
2705
2706 $this->privacy_checks = Jetpack::state( 'privacy_checks' );
2707
2708 if ( $this->message || $this->error || $this->privacy_checks ) {
2709 add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
2710 }
2711
2712 if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
2713 do_action( 'jetpack_module_configuration_load_' . $_GET['configure'] );
2714 }
2715
2716 add_filter( 'jetpack_short_module_description', 'wptexturize' );
2717 }
2718
2719 function admin_notices() {
2720
2721 if ( $this->error ) {
2722 ?>
2723 <div id="message" class="jetpack-message jetpack-err">
2724 <div class="squeezer">
2725 <h4><?php echo wp_kses( $this->error, array( 'code' => true, 'strong' => true, 'br' => true, 'b' => true ) ); ?></h4>
2726 <?php if ( $desc = Jetpack::state( 'error_description' ) ) : ?>
2727 <p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
2728 <?php endif; ?>
2729 </div>
2730 </div>
2731 <?php
2732 }
2733
2734 if ( $this->message ) {
2735 ?>
2736 <div id="message" class="jetpack-message">
2737 <div class="squeezer">
2738 <h4><?php echo wp_kses( $this->message, array( 'strong' => array(), 'a' => array( 'href' => true ), 'br' => true ) ); ?></h4>
2739 </div>
2740 </div>
2741 <?php
2742 }
2743
2744 if ( $this->privacy_checks ) :
2745 $module_names = $module_slugs = array();
2746
2747 $privacy_checks = explode( ',', $this->privacy_checks );
2748 foreach ( $privacy_checks as $module_slug ) {
2749 $module = Jetpack::get_module( $module_slug );
2750 if ( ! $module ) {
2751 continue;
2752 }
2753
2754 $module_slugs[] = $module_slug;
2755 $module_names[] = "<strong>{$module['name']}</strong>";
2756 }
2757
2758 $module_slugs = join( ',', $module_slugs );
2759 ?>
2760 <div id="message" class="jetpack-message jetpack-err">
2761 <div class="squeezer">
2762 <h4><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h4><br />
2763 <p><?php
2764 echo wp_kses(
2765 wptexturize(
2766 wp_sprintf(
2767 _nx(
2768 "Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
2769 "Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
2770 count( $privacy_checks ),
2771 '%l = list of Jetpack module/feature names',
2772 'jetpack'
2773 ),
2774 $module_names
2775 )
2776 ),
2777 array( 'strong' => true )
2778 );
2779
2780 echo "\n<br />\n";
2781
2782 echo wp_kses(
2783 sprintf(
2784 _nx(
2785 'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
2786 'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
2787 count( $privacy_checks ),
2788 '%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
2789 'jetpack'
2790 ),
2791 wp_nonce_url(
2792 Jetpack::admin_url(
2793 array(
2794 'page' => 'jetpack',
2795 'action' => 'deactivate',
2796 'module' => urlencode( $module_slugs ),
2797 )
2798 ),
2799 "jetpack_deactivate-$module_slugs"
2800 ),
2801 esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
2802 ),
2803 array( 'a' => array( 'href' => true, 'title' => true ) )
2804 );
2805 ?></p>
2806 </div>
2807 </div>
2808 <?php endif;
2809 }
2810
2811 /**
2812 * Record a stat for later output. This will only currently output in the admin_footer.
2813 */
2814 function stat( $group, $detail ) {
2815 if ( ! isset( $this->stats[ $group ] ) )
2816 $this->stats[ $group ] = array();
2817 $this->stats[ $group ][] = $detail;
2818 }
2819
2820 /**
2821 * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
2822 */
2823 function do_stats( $method = '' ) {
2824 if ( is_array( $this->stats ) && count( $this->stats ) ) {
2825 foreach ( $this->stats as $group => $stats ) {
2826 if ( is_array( $stats ) && count( $stats ) ) {
2827 $args = array( "x_jetpack-{$group}" => implode( ',', $stats ) );
2828 if ( 'server_side' === $method ) {
2829 self::do_server_side_stat( $args );
2830 } else {
2831 echo '<img src="' . esc_url( self::build_stats_url( $args ) ) . '" width="1" height="1" style="display:none;" />';
2832 }
2833 }
2834 unset( $this->stats[ $group ] );
2835 }
2836 }
2837 }
2838
2839 /**
2840 * Runs stats code for a one-off, server-side.
2841 *
2842 * @param $args array|string The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
2843 *
2844 * @return bool If it worked.
2845 */
2846 static function do_server_side_stat( $args ) {
2847 $response = wp_remote_get( self::build_stats_url( $args ) );
2848 if ( is_wp_error( $response ) )
2849 return false;
2850
2851 if ( 200 !== wp_remote_retrieve_response_code( $response ) )
2852 return false;
2853
2854 return true;
2855 }
2856
2857 /**
2858 * Builds the stats url.
2859 *
2860 * @param $args array|string The arguments to append to the URL.
2861 *
2862 * @return string The URL to be pinged.
2863 */
2864 static function build_stats_url( $args ) {
2865 $defaults = array(
2866 'v' => 'wpcom2',
2867 'rand' => md5( mt_rand( 0, 999 ) . time() ),
2868 );
2869 $args = wp_parse_args( $args, $defaults );
2870 $base_url = apply_filters(
2871 'jetpack_stats_base_url',
2872 set_url_scheme( 'http://stats.wordpress.com/g.gif' )
2873 );
2874 $url = add_query_arg( $args, $base_url );
2875 return $url;
2876 }
2877
2878 function translate_current_user_to_role() {
2879 foreach ( $this->capability_translations as $role => $cap ) {
2880 if ( current_user_can( $role ) || current_user_can( $cap ) ) {
2881 return $role;
2882 }
2883 }
2884
2885 return false;
2886 }
2887
2888 function translate_role_to_cap( $role ) {
2889 if ( ! isset( $this->capability_translations[$role] ) ) {
2890 return false;
2891 }
2892
2893 return $this->capability_translations[$role];
2894 }
2895
2896 function sign_role( $role ) {
2897 if ( ! $user_id = (int) get_current_user_id() ) {
2898 return false;
2899 }
2900
2901 $token = Jetpack_Data::get_access_token();
2902 if ( ! $token || is_wp_error( $token ) ) {
2903 return false;
2904 }
2905
2906 return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
2907 }
2908
2909 function build_connect_url( $raw = false, $redirect = false ) {
2910 if ( ! Jetpack_Options::get_option( 'blog_token' ) ) {
2911 $url = Jetpack::nonce_url_no_esc( Jetpack::admin_url( 'action=register' ), 'jetpack-register' );
2912 if( is_network_admin() ) {
2913 $url = add_query_arg( 'is_multisite', network_admin_url(
2914 'admin.php?page=jetpack-settings' ), $url );
2915 }
2916 } else {
2917 $role = $this->translate_current_user_to_role();
2918 $signed_role = $this->sign_role( $role );
2919
2920 $user = wp_get_current_user();
2921
2922 $redirect = $redirect ? esc_url_raw( $redirect ) : '';
2923
2924 if( isset( $_REQUEST['is_multisite'] ) ) {
2925 $redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
2926 }
2927
2928 $args = urlencode_deep(
2929 array(
2930 'response_type' => 'code',
2931 'client_id' => Jetpack_Options::get_option( 'id' ),
2932 'redirect_uri' => add_query_arg(
2933 array(
2934 'action' => 'authorize',
2935 '_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
2936 'redirect' => $redirect ? urlencode( $redirect ) : false,
2937 ),
2938 menu_page_url( 'jetpack', false )
2939 ),
2940 'state' => $user->ID,
2941 'scope' => $signed_role,
2942 'user_email' => $user->user_email,
2943 'user_login' => $user->user_login,
2944 'is_active' => Jetpack::is_active(),
2945 'jp_version' => JETPACK__VERSION,
2946 )
2947 );
2948
2949 $url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) );
2950 }
2951
2952 return $raw ? $url : esc_url( $url );
2953 }
2954
2955 function build_reconnect_url( $raw = false ) {
2956 $url = wp_nonce_url( Jetpack::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
2957 return $raw ? $url : esc_url( $url );
2958 }
2959
2960 public static function admin_url( $args = null ) {
2961 $args = wp_parse_args( $args, array( 'page' => 'jetpack' ) );
2962 $url = add_query_arg( $args, admin_url( 'admin.php' ) );
2963 return $url;
2964 }
2965
2966 public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
2967 $actionurl = str_replace( '&amp;', '&', $actionurl );
2968 return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
2969 }
2970
2971 function dismiss_jetpack_notice() {
2972 if ( isset( $_GET['jetpack-notice'] ) && 'dismiss' == $_GET['jetpack-notice'] && check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
2973 require_once ABSPATH . 'wp-admin/includes/plugin.php';
2974
2975 deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
2976
2977 wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
2978 exit;
2979 }
2980 }
2981
2982 /**
2983 * This is the old pre-3.0 admin page. It is replaced by the Jetpack_Admin class.
2984 * Tentatively left here for comparison purposes.
2985 */
2986 function admin_page() {
2987 global $current_user;
2988
2989 $is_connected = Jetpack::is_active();
2990 $user_token = Jetpack_Data::get_access_token( $current_user->ID );
2991 $is_user_connected = $user_token && ! is_wp_error( $user_token );
2992 $is_master_user = $current_user->ID == Jetpack_Options::get_option( 'master_user' );
2993
2994 $can_reconnect_jpms = true;
2995 if( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
2996 $jpms = Jetpack_Network::init();
2997 $can_reconnect_jpms = ( $jpms->get_option( 'sub-site-connection-override' ) )? 1: 0;
2998 }
2999
3000
3001
3002
3003 ?>
3004 <div class="wrap" id="jetpack-settings">
3005
3006 <div id="jp-header"<?php if ( $is_connected ) : ?> class="small"<?php endif; ?>>
3007 <div id="jp-clouds">
3008 <?php if ( $is_connected && $can_reconnect_jpms ) : ?>
3009 <div id="jp-disconnectors">
3010 <?php if ( current_user_can( 'jetpack_disconnect' ) ) : ?>
3011 <div id="jp-disconnect" class="jp-disconnect">
3012 <a href="<?php echo wp_nonce_url( Jetpack::admin_url( 'action=disconnect' ), 'jetpack-disconnect' ); ?>"><div class="deftext"><?php _e( 'Connected to WordPress.com', 'jetpack' ); ?></div><div class="hovertext"><?php _e( 'Disconnect from WordPress.com', 'jetpack' ) ?></div></a>
3013 </div>
3014 <?php endif; ?>
3015 <?php if ( $is_user_connected && ! $is_master_user ) : ?>
3016 <div id="jp-unlink" class="jp-disconnect">
3017 <a href="<?php echo wp_nonce_url( Jetpack::admin_url( 'action=unlink' ), 'jetpack-unlink' ); ?>"><div class="deftext"><?php _e( 'User linked to WordPress.com', 'jetpack' ); ?></div><div class="hovertext"><?php _e( 'Unlink user from WordPress.com', 'jetpack' ) ?></div></a>
3018 </div>
3019 <?php endif; ?>
3020 </div>
3021 <?php endif; ?>
3022 <h3><?php _e( 'Jetpack by WordPress.com', 'jetpack' ) ?></h3>
3023 <?php if ( ! $is_connected ) : ?>
3024 <div id="jp-notice">
3025 <p><?php _e( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ); ?></p>
3026 </div>
3027 <?php endif; ?>
3028 </div>
3029 </div>
3030
3031 <h2 style="display: none"></h2> <!-- For WP JS message relocation -->
3032
3033 <?php if ( isset( $_GET['jetpack-notice'] ) && 'dismiss' == $_GET['jetpack-notice'] ) : ?>
3034 <div id="message" class="error">
3035 <p><?php _e( 'Jetpack is network activated. Notices cannot be dismissed.', 'jetpack' ); ?></p>
3036 </div>
3037 <?php endif; ?>
3038
3039 <?php do_action( 'jetpack_notices' ) ?>
3040
3041 <?php
3042 // If the connection has not been made then show the marketing text.
3043 if( !$can_reconnect_jpms && !$is_connected ) {
3044 ?>
3045 <div id="message" class="updated jetpack-message jp-connect jp-multisite" style="display:block !important">
3046 <div class="jetpack-wrap-container">
3047 <div class="jetpack-text-container">
3048 <h4>
3049 <p><?php _e( 'To use Jetpack please contact your WordPress administrator to connect it for you.', 'jetpack' ) ?></p>
3050 </h4>
3051 </div>
3052 </div>
3053 </div> <?php
3054 }
3055 if ( ! Jetpack::is_development_mode() && $can_reconnect_jpms ) :
3056 ?>
3057 <?php if ( ! $is_connected ) :
3058 $dismiss_and_deactivate_url = wp_nonce_url( Jetpack::admin_url( '?page=jetpack&jetpack-notice=dismiss' ), 'jetpack-deactivate' );
3059 ?>
3060 <div id="message" class="updated jetpack-message jp-connect" style="display:block !important;">
3061 <div id="jp-dismiss" class="jetpack-close-button-container">
3062 <a class="jetpack-close-button" href="<?php echo esc_url( $dismiss_and_deactivate_url ); ?>"><?php _e( 'Dismiss this notice.', 'jetpack' ); ?></a>
3063 </div>
3064 <div class="jetpack-wrap-container">
3065 <div class="jetpack-text-container">
3066 <h4>
3067 <p><?php _e( 'To enable all of the Jetpack features you&#8217;ll need to connect your website to WordPress.com using the button to the right. Once you&#8217;ve made the connection you&#8217;ll activate all the delightful features below.', 'jetpack' ) ?></p>
3068 </h4>
3069 </div>
3070 <div class="jetpack-install-container">
3071 <p class="submit"><a href="<?php echo $this->build_connect_url() ?>" class="button-connector" id="wpcom-connect"><?php _e( 'Connect to WordPress.com', 'jetpack' ); ?></a></p>
3072 </div>
3073 </div>
3074 </div>
3075
3076 <?php else /* blog and user are connected */ : ?>
3077 <?php /* TODO: if not master user, show user disconnect button? */ ?>
3078 <?php endif; ?>
3079 <?php endif; // ! Jetpack::is_development_mode() ?>
3080
3081
3082
3083
3084
3085 <?php if ( Jetpack::is_active() && !Jetpack::is_development_mode() && ! $is_user_connected ) : ?>
3086
3087 <div id="message" class="updated jetpack-message jp-connect" style="display:block !important;">
3088 <div class="jetpack-wrap-container">
3089 <div class="jetpack-text-container">
3090 <h4>
3091 <p><?php _e( 'To enable all of the Jetpack features you&#8217;ll need to link your account here to your WordPress.com account using the button to the right.', 'jetpack' ) ?></p>
3092 </h4>
3093 </div>
3094 <div class="jetpack-install-container">
3095 <p class="submit"><a href="<?php echo $this->build_connect_url() ?>" class="button-connector" id="wpcom-connect"><?php _e( 'Link account with WordPress.com', 'jetpack' ); ?></a></p>
3096 </div>
3097 </div>
3098 </div>
3099
3100 <?php endif; ?>
3101
3102
3103
3104
3105 <?php
3106 if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
3107 $this->admin_screen_configure_module( $_GET['configure'] );
3108 } else {
3109 $this->admin_screen_list_modules();
3110 }
3111 ?>
3112
3113 <div id="survey" class="jp-survey">
3114 <div class="jp-survey-container">
3115 <div class="jp-survey-text">
3116 <h4><?php _e( 'Have feedback on Jetpack?', 'jetpack' ); ?></h4>
3117 <br />
3118 <?php _e( 'Answer a short survey to let us know how we&#8217;re doing and what to add in the future.', 'jetpack' ); ?>
3119 </div>
3120 <div class="jp-survey-button-container">
3121 <p class="submit"><?php printf( '<a id="jp-survey-button" class="button-primary" target="_blank" href="%1$s">%2$s</a>', 'http://jetpack.me/survey/?rel=' . JETPACK__VERSION, __( 'Take Survey', 'jetpack' ) ); ?></p>
3122 </div>
3123 </div>
3124 </div>
3125
3126 <?php if ( $is_connected && $this->current_user_is_connection_owner() ) : ?>
3127 <p id="news-sub"><?php _e( 'Checking email updates status&hellip;', 'jetpack' ); ?></p>
3128 <script type="text/javascript">
3129 jQuery(document).ready(function($){
3130 $.get( ajaxurl, { action: 'jetpack-check-news-subscription', rand: jQuery.now().toString() + Math.random().toString() }, function( data ) {
3131 if ( 'subscribed' == data ) {
3132 $( '#news-sub' ).html( '<?php printf(
3133 esc_js( _x( 'You are currently subscribed to email updates. %s', '%s = Unsubscribe link', 'jetpack' ) ),
3134 '<a href="#" class="jp-news-link button">' . esc_js( __( 'Unsubscribe', 'jetpack' ) ) . '</a>'
3135 ); ?>' );
3136 } else {
3137 $( '#news-sub' ).html( '<?php printf(
3138 esc_js( _x( 'Want to receive updates about Jetpack by email? %s', '%s = Subscribe link', 'jetpack' ) ),
3139 '<a href="#" class="jp-news-link button-primary">' . esc_js( __( 'Subscribe', 'jetpack' ) ) . '</a>'
3140 ); ?>' );
3141 }
3142 $( '.jp-news-link' ).click( function() {
3143 $( '#news-sub' ).append( ' <img src="<?php echo esc_js( esc_url( admin_url( 'images/loading.gif' ) ) ); ?>" align="absmiddle" id="jp-news-loading" />' );
3144 $.get( ajaxurl, { action: 'jetpack-subscribe-to-news', rand: jQuery.now().toString() + Math.random().toString() }, function( data ) {
3145 if ( 'subscribed' == data ) {
3146 $( '#news-sub' ).text( '<?php echo esc_js( __( 'You have been subscribed to receive email updates.', 'jetpack' ) ); ?>' );
3147 } else {
3148 $( '#news-sub' ).text( '<?php echo esc_js( __( 'You will no longer receive email updates about Jetpack.', 'jetpack' ) ); ?>' );
3149 }
3150 $( '#jp-news-loading' ).remove();
3151 } );
3152 return false;
3153 } );
3154 } );
3155 } );
3156 </script>
3157 <?php endif; ?>
3158
3159 <div id="jp-footer">
3160 <p class="automattic"><?php _e( 'An <span>Automattic</span> Airline', 'jetpack' ) ?></p>
3161 <p class="small">
3162 <a href="http://jetpack.me/" target="_blank">Jetpack <?php echo esc_html( JETPACK__VERSION ); ?></a> |
3163 <a href="http://automattic.com/privacy/" target="_blank"><?php _e( 'Privacy Policy', 'jetpack' ); ?></a> |
3164 <a href="http://wordpress.com/tos/" target="_blank"><?php _e( 'Terms of Service', 'jetpack' ); ?></a> |
3165 <?php if ( current_user_can( 'manage_options' ) ) : ?>
3166 <a href="<?php echo Jetpack::admin_url( array( 'page' => 'jetpack-debugger' ) ); ?>"><?php _e( 'Debug', 'jetpack' ); ?></a> |
3167 <?php endif; ?>
3168 <a href="http://jetpack.me/support/" target="_blank"><?php _e( 'Support', 'jetpack' ); ?></a>
3169 </p>
3170 </div>
3171
3172 <div id="jetpack-configuration" style="display:none;">
3173 <p><img width="16" src="<?php echo esc_url( plugins_url( '_inc/images/wpspin_light-2x.gif', __FILE__ ) ); ?>" alt="Loading ..." /></p>
3174 </div>
3175 </div>
3176 <?php
3177 }
3178
3179 function debugger_page() {
3180 nocache_headers();
3181 if ( ! current_user_can( 'manage_options' ) ) {
3182 die( '-1' );
3183 }
3184 Jetpack_Debugger::jetpack_debug_display_handler();
3185 exit;
3186 }
3187
3188 public static function admin_screen_configure_module( $module_id ) {
3189 if ( ! in_array( $module_id, Jetpack::get_active_modules() ) || ! current_user_can( 'manage_options' ) )
3190 return false; ?>
3191
3192 <div id="jp-settings-screen" style="position: relative">
3193 <h3>
3194 <?php
3195 $module = Jetpack::get_module( $module_id );
3196 echo '<a href="' . Jetpack::admin_url( 'page=jetpack_modules' ) . '">' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</a> &rarr; ';
3197 printf( __( 'Configure %s', 'jetpack' ), $module['name'] );
3198 ?>
3199 </h3>
3200
3201 <?php do_action( 'jetpack_module_configuration_screen_' . $module_id ); ?>
3202 </div><?php
3203 }
3204
3205 public static function sort_modules( $a, $b ) {
3206 if ( $a['sort'] == $b['sort'] )
3207 return 0;
3208
3209 return ( $a['sort'] < $b['sort'] ) ? -1 : 1;
3210 }
3211
3212 function admin_screen_list_modules() {
3213 require_once JETPACK__PLUGIN_DIR . 'modules/module-info.php';
3214 $jetpack_connected = true;
3215 if ( ! Jetpack::is_active() )
3216 $jetpack_connected = false;
3217
3218 ?>
3219 <div class="module-container">
3220 <?php
3221
3222 $avail_raw = Jetpack::get_available_modules();
3223 $available = array();
3224 $active = Jetpack::get_active_modules();
3225 $counter = 0;
3226
3227 foreach ( (array) $avail_raw as $module ) {
3228 if ( $plugin = Jetpack::get_module( $module ) ) {
3229 $plugin['module'] = $module;
3230 $available[] = $plugin;
3231 }
3232 }
3233 unset( $avail_raw );
3234 usort( $available, array( 'Jetpack', 'sort_modules' ) );
3235 $jetpack_version = Jetpack_Options::get_option( 'version' );
3236 if ( $jetpack_version ) {
3237 list( $jetpack_version, $jetpack_version_time ) = explode( ':', $jetpack_version );
3238 } else {
3239 $jetpack_version = 0;
3240 $jetpack_version_time = 0;
3241 }
3242
3243 $jetpack_old_version = Jetpack_Options::get_option( 'old_version' );
3244 if ( $jetpack_old_version ) {
3245 list( $jetpack_old_version ) = explode( ':', $jetpack_old_version );
3246 } else {
3247 $jetpack_old_version = 0;
3248 }
3249 $now = time();
3250
3251 foreach ( (array) $available as $module_data ) {
3252 $module = $module_data['module'];
3253 $activated = in_array( $module, $active );
3254 if ( $activated ) {
3255 $css = 'active';
3256 $toggle = __( 'Deactivate', 'jetpack' );
3257 $toggle_url = wp_nonce_url(
3258 Jetpack::admin_url(
3259 array(
3260 'page' => 'jetpack',
3261 'action' => 'deactivate',
3262 'module' => $module,
3263 )
3264 ),
3265 "jetpack_deactivate-$module"
3266 );
3267 } else {
3268 $css = 'inactive';
3269 $toggle = __( 'Activate', 'jetpack' );
3270 $toggle_url = wp_nonce_url(
3271 Jetpack::admin_url(
3272 array(
3273 'page' => 'jetpack',
3274 'action' => 'activate',
3275 'module' => $module,
3276 )
3277 ),
3278 "jetpack_activate-$module"
3279 );
3280 }
3281
3282 if ( $counter % 4 == 0 ) {
3283 $classes = $css . ' jetpack-newline';
3284 $counter = 0;
3285 } else {
3286 $classes = $css;
3287 }
3288
3289 $free_text = esc_html( $module_data['free'] ? __( 'Free', 'jetpack' ) : __( 'Purchase', 'jetpack' ) );
3290 $free_text = apply_filters( 'jetpack_module_free_text_' . $module, $free_text );
3291 $badge_text = $free_text;
3292
3293 if ( ( ! $jetpack_connected && ! Jetpack::is_development_mode() ) ) {
3294 $classes = 'x disabled';
3295 } elseif ( $jetpack_version_time + 604800 > $now ) { // 1 week
3296 if ( version_compare( $module_data['introduced'], $jetpack_old_version, '>' ) ) {
3297 $badge_text = esc_html__( 'New', 'jetpack' );
3298 $classes .= ' jetpack-new-module';
3299 } elseif ( isset( $module_data['changed'] ) && version_compare( $module_data['changed'], $jetpack_old_version, '>' ) ) {
3300 $badge_text = esc_html__( 'Updated', 'jetpack' );
3301 $classes .= ' jetpack-updated-module';
3302 } else {
3303 $badge_text = $free_text;
3304 }
3305 }
3306
3307 ?>
3308 <div class="jetpack-module jetpack-<?php echo $classes; ?>" id="<?php echo $module ?>">
3309 <h3><?php echo esc_html( $module_data['name'] ); ?></h3>
3310 <div class="jetpack-module-description">
3311 <div class="module-image">
3312 <p><span class="module-image-badge"><?php echo $badge_text; ?></span><span class="module-image-free" style="display: none"><?php echo $free_text; ?></span></p>
3313 </div>
3314
3315 <p><?php echo apply_filters( 'jetpack_short_module_description', $module_data['description'], $module ); ?></p>
3316 </div>
3317
3318 <div class="jetpack-module-actions">
3319 <?php if ( $jetpack_connected || ( Jetpack::is_development_mode() && ! $module_data['requires_connection'] ) ) : ?>
3320 <?php if ( ! $activated && current_user_can( 'manage_options' ) && apply_filters( 'jetpack_can_activate_' . $module, true ) ) : ?>
3321 <a href="<?php echo esc_url( $toggle_url ); ?>" class="<?php echo ( 'inactive' == $css ? ' button-primary' : ' button-secondary' ); ?>"><?php echo $toggle; ?></a>&nbsp;
3322 <?php endif; ?>
3323
3324 <?php do_action( 'jetpack_learn_more_button_' . $module ) ?>
3325
3326 <?php
3327 if ( current_user_can( 'manage_options' ) && apply_filters( 'jetpack_module_configurable_' . $module, false ) ) {
3328 echo '<a href="' . esc_url( Jetpack::module_configuration_url( $module ) ) . '" class="jetpack-configure-button button-secondary">' . __( 'Configure', 'jetpack' ) . '</a>';
3329 }
3330 ?><?php if ( $activated && $module_data['deactivate'] && current_user_can( 'manage_options' ) ) : ?><a style="display: none;" href="<?php echo esc_url( $toggle_url ); ?>" class="jetpack-deactivate-button button-secondary"><?php echo $toggle; ?></a>&nbsp;<?php endif; ?>
3331
3332 <?php else : ?>
3333 <?php do_action( 'jetpack_learn_more_button_' . $module ) ?>
3334 <?php endif; ?>
3335 </div>
3336 </div>
3337 <?php if ( 'inactive' == $css && $jetpack_connected && current_user_can( 'manage_options' ) && apply_filters( 'jetpack_can_activate_' . $module, true ) ) : ?>
3338 <script type="text/javascript">
3339 jQuery( '#<?php echo esc_js( $module ); ?>' ).bind( 'click', function(e){
3340 if ( ! jQuery(e.target).hasClass('more-info-link') )
3341 document.location.href = '<?php echo str_replace( '&amp;', '&', esc_js( esc_url( $toggle_url ) ) ); ?>';
3342 } );
3343 </script>
3344 <?php else : ?>
3345 <style>
3346 #<?php echo $module; ?> { cursor: default; }
3347 </style>
3348 <?php endif; ?>
3349
3350 <div id="jp-more-info-<?php echo esc_attr( $module ); ?>" style="display:none;">
3351 <?php
3352 if ( $jetpack_connected && has_action( 'jetpack_module_more_info_connected_' . $module ) )
3353 do_action( 'jetpack_module_more_info_connected_' . $module );
3354 else
3355 do_action( 'jetpack_module_more_info_' . $module );
3356 ?>
3357 </div>
3358
3359 <?php
3360 $counter++;
3361 }
3362
3363 // Add in some "Coming soon..." placeholders to fill up the current row and one more
3364 for ( $i = 0; $i < 4; $i++ ) { ?>
3365 <div class="jetpack-module placeholder"<?php if ( $i > 8 - $counter ) echo ' style="display: none;"'; ?>>
3366 <h3><?php _e( 'Coming soon&#8230;', 'jetpack' ) ?></h3>
3367 </div>
3368 <?php
3369 }
3370
3371 echo '</div><!-- .module-container -->';
3372 }
3373
3374 function check_news_subscription() {
3375 if ( ! $this->current_user_is_connection_owner() ) {
3376 exit;
3377 }
3378
3379 Jetpack::load_xml_rpc_client();
3380 $xml = new Jetpack_IXR_Client(
3381 array(
3382 'user_id' => JETPACK_MASTER_USER,
3383 )
3384 );
3385 $xml->query( 'jetpack.checkNewsSubscription' );
3386 if ( $xml->isError() ) {
3387 printf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() );
3388 } else {
3389 print_r( $xml->getResponse() );
3390 }
3391 exit;
3392 }
3393
3394 function subscribe_to_news() {
3395 if ( ! $this->current_user_is_connection_owner() ) {
3396 exit;
3397 }
3398
3399 Jetpack::load_xml_rpc_client();
3400 $xml = new Jetpack_IXR_Client(
3401 array(
3402 'user_id' => JETPACK_MASTER_USER,
3403 )
3404 );
3405 $xml->query( 'jetpack.subscribeToNews' );
3406 if ( $xml->isError() ) {
3407 printf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() );
3408 } else {
3409 print_r( $xml->getResponse() );
3410 }
3411 exit;
3412 }
3413
3414 function sync_reindex_trigger() {
3415 if ( $this->current_user_is_connection_owner() && current_user_can( 'manage_options' ) ) {
3416 echo json_encode( $this->sync->reindex_trigger() );
3417 } else {
3418 echo '{"status":"ERROR"}';
3419 }
3420 exit;
3421 }
3422
3423 function sync_reindex_status(){
3424 if ( $this->current_user_is_connection_owner() && current_user_can( 'manage_options' ) ) {
3425 echo json_encode( $this->sync->reindex_status() );
3426 } else {
3427 echo '{"status":"ERROR"}';
3428 }
3429 exit;
3430 }
3431
3432 /* Client API */
3433
3434 /**
3435 * Returns the requested Jetpack API URL
3436 *
3437 * @return string
3438 */
3439 public static function api_url( $relative_url ) {
3440 return trailingslashit( JETPACK__API_BASE . $relative_url ) . JETPACK__API_VERSION . '/';
3441 }
3442
3443 /**
3444 * Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
3445 */
3446 public static function fix_url_for_bad_hosts( $url ) {
3447 if ( 0 !== strpos( $url, 'https://' ) ) {
3448 return $url;
3449 }
3450
3451 switch ( JETPACK_CLIENT__HTTPS ) {
3452 case 'ALWAYS' :
3453 return $url;
3454 case 'NEVER' :
3455 return self::force_url_to_non_ssl( $url );
3456 // default : case 'AUTO' :
3457 }
3458
3459 // Yay! Your host is good!
3460 if ( self::permit_ssl() && wp_http_supports( array( 'ssl' => true ) ) ) {
3461 return $url;
3462 }
3463
3464 // Boo! Your host is bad and makes Jetpack cry!
3465 return self::force_url_to_non_ssl( $url );
3466 }
3467
3468 /**
3469 * Checks to see if the URL is using SSL to connect with Jetpack
3470 *
3471 * @since 2.3.3
3472 * @return boolean
3473 */
3474 public static function permit_ssl( $force_recheck = false ) {
3475 // Do some fancy tests to see if ssl is being supported
3476 if ( $force_recheck || false === ( $ssl = get_transient( 'jetpack_https_test' ) ) ) {
3477 if ( 'https' !== substr( JETPACK__API_BASE, 0, 5 ) ) {
3478 $ssl = 0;
3479 } else {
3480 switch ( JETPACK_CLIENT__HTTPS ) {
3481 case 'NEVER':
3482 $ssl = 0;
3483 break;
3484 case 'ALWAYS':
3485 case 'AUTO':
3486 default:
3487 $ssl = 1;
3488 break;
3489 }
3490
3491 // If it's not 'NEVER', test to see
3492 if ( $ssl ) {
3493 $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
3494 if ( is_wp_error( $response ) || ( 'OK' !== wp_remote_retrieve_body( $response ) ) ) {
3495 $ssl = 0;
3496 }
3497 }
3498 }
3499 set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
3500 }
3501
3502 return (bool) $ssl;
3503 }
3504
3505 /**
3506 * Take any URL, and if it starts with https:// change it to http://
3507 */
3508 public static function force_url_to_non_ssl( $url ) {
3509 return preg_replace( '#^https://#i', 'http://', $url );
3510 }
3511
3512 /*
3513 * Displays an admin_notice, alerting the user to their JETPACK_CLIENT__HTTPS constant being 'ALWAYS' but SSL isn't working.
3514 */
3515 public function alert_required_ssl_fail() {
3516 if ( ! current_user_can( 'manage_options' ) )
3517 return;
3518 ?>
3519
3520 <div id="message" class="error jetpack-message jp-identity-crisis">
3521 <div class="jetpack-wrap-container">
3522 <div class="jetpack-text-container">
3523 <h3><?php _e( 'Something is being cranky!', 'jetpack' ); ?></h3>
3524 <p><?php _e( 'Your site is configured to only permit SSL connections to Jetpack, but SSL connections don\'t seem to be functional!', 'jetpack' ); ?></p>
3525 </div>
3526 </div>
3527 </div>
3528
3529 <?php
3530 }
3531
3532 /**
3533 * Returns the Jetpack XML-RPC API
3534 *
3535 * @return string
3536 */
3537 public static function xmlrpc_api_url() {
3538 $base = preg_replace( '#(https?://[^?/]+)(/?.*)?$#', '\\1', JETPACK__API_BASE );
3539 return untrailingslashit( $base ) . '/xmlrpc.php';
3540 }
3541
3542 /**
3543 * Creates two secret tokens and the end of life timestamp for them.
3544 *
3545 * Note these tokens are unique per call, NOT static per site for connecting.
3546 *
3547 * @since 2.6
3548 * @return array
3549 */
3550 public function generate_secrets() {
3551 $secrets = array(
3552 wp_generate_password( 32, false ), // secret_1
3553 wp_generate_password( 32, false ), // secret_2
3554 ( time() + 600 ), // eol ( End of Life )
3555 );
3556
3557 return $secrets;
3558 }
3559
3560 /**
3561 * Builds the timeout limit for queries talking with the wpcom servers.
3562 *
3563 * Based on local php max_execution_time in php.ini
3564 *
3565 * @since 2.6
3566 * @return int
3567 **/
3568 public function get_remote_query_timeout_limit() {
3569 $timeout = (int) ini_get( 'max_execution_time' );
3570 if ( ! $timeout ) // Ensure exec time set in php.ini
3571 $timeout = 30;
3572 return intval( $timeout / 2 );
3573 }
3574
3575
3576 /**
3577 * Takes the response from the Jetpack register new site endpoint and
3578 * verifies it worked properly.
3579 *
3580 * @since 2.6
3581 * @return true or Jetpack_Error
3582 **/
3583 public function validate_remote_register_response( $response ) {
3584 if ( is_wp_error( $response ) ) {
3585 return new Jetpack_Error( 'register_http_request_failed', $response->get_error_message() );
3586 }
3587
3588 $code = wp_remote_retrieve_response_code( $response );
3589 $entity = wp_remote_retrieve_body( $response );
3590 if ( $entity )
3591 $json = json_decode( $entity );
3592 else
3593 $json = false;
3594
3595 $code_type = intval( $code / 100 );
3596 if ( 5 == $code_type ) {
3597 return new Jetpack_Error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
3598 } elseif ( 408 == $code ) {
3599 return new Jetpack_Error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
3600 } elseif ( ! empty( $json->error ) ) {
3601 $error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
3602 return new Jetpack_Error( (string) $json->error, $error_description, $code );
3603 } elseif ( 200 != $code ) {
3604 return new Jetpack_Error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
3605 }
3606
3607 // Jetpack ID error block
3608 if ( empty( $json->jetpack_id ) ) {
3609 return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ), $entity );
3610 } elseif ( ! is_scalar( $json->jetpack_id ) ) {
3611 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 );
3612 } elseif ( preg_match( '/[^0-9]/', $json->jetpack_id ) ) {
3613 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 );
3614 }
3615
3616 return true;
3617 }
3618 /**
3619 * @return bool|WP_Error
3620 */
3621 public static function register() {
3622 add_action( 'pre_update_jetpack_option_register', array( 'Jetpack_Options', 'delete_option' ) );
3623 $secrets = Jetpack::init()->generate_secrets();
3624
3625 Jetpack_Options::update_option( 'register', $secrets[0] . ':' . $secrets[1].
3626 ':' . $secrets[2] );
3627
3628 @list( $secret_1, $secret_2, $secret_eol ) = explode( ':', Jetpack_Options::get_option( 'register' ) );
3629 if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() )
3630 return new Jetpack_Error( 'missing_secrets' );
3631
3632 $timeout = Jetpack::init()->get_remote_query_timeout_limit();
3633
3634 $gmt_offset = get_option( 'gmt_offset' );
3635 if ( ! $gmt_offset ) {
3636 $gmt_offset = 0;
3637 }
3638
3639 $stats_options = get_option( 'stats_options' );
3640 $stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null;
3641
3642 $args = array(
3643 'method' => 'POST',
3644 'body' => array(
3645 'siteurl' => site_url(),
3646 'home' => home_url(),
3647 'gmt_offset' => $gmt_offset,
3648 'timezone_string' => (string) get_option( 'timezone_string' ),
3649 'site_name' => (string) get_option( 'blogname' ),
3650 'secret_1' => $secret_1,
3651 'secret_2' => $secret_2,
3652 'site_lang' => get_locale(),
3653 'timeout' => $timeout,
3654 'stats_id' => $stats_id,
3655 ),
3656 'headers' => array(
3657 'Accept' => 'application/json',
3658 ),
3659 'timeout' => $timeout,
3660 );
3661 $response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'register' ) ), $args, true );
3662
3663
3664 // Make sure the response is valid and does not contain any Jetpack errors
3665 $valid_response = Jetpack::init()->validate_remote_register_response( $response );
3666 if( is_wp_error( $valid_response ) || !$valid_response ) {
3667 return $valid_response;
3668 }
3669
3670
3671 // Grab the response values to work with
3672 $code = wp_remote_retrieve_response_code( $response );
3673 $entity = wp_remote_retrieve_body( $response );
3674
3675 if ( $entity )
3676 $json = json_decode( $entity );
3677 else
3678 $json = false;
3679
3680
3681
3682 if ( empty( $json->jetpack_secret ) || ! is_string( $json->jetpack_secret ) )
3683 return new Jetpack_Error( 'jetpack_secret', '', $code );
3684
3685 if ( isset( $json->jetpack_public ) ) {
3686 $jetpack_public = (int) $json->jetpack_public;
3687 } else {
3688 $jetpack_public = false;
3689 }
3690
3691 Jetpack_Options::update_options(
3692 array(
3693 'id' => (int) $json->jetpack_id,
3694 'blog_token' => (string) $json->jetpack_secret,
3695 'public' => $jetpack_public,
3696 )
3697 );
3698
3699 return true;
3700 }
3701
3702
3703 /* Client Server API */
3704
3705 /**
3706 * Loads the Jetpack XML-RPC client
3707 */
3708 public static function load_xml_rpc_client() {
3709 require_once ABSPATH . WPINC . '/class-IXR.php';
3710 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-ixr-client.php';
3711 }
3712
3713 function verify_xml_rpc_signature() {
3714 if ( $this->xmlrpc_verification ) {
3715 return $this->xmlrpc_verification;
3716 }
3717
3718 // It's not for us
3719 if ( ! isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
3720 return false;
3721 }
3722
3723 @list( $token_key, $version, $user_id ) = explode( ':', $_GET['token'] );
3724 if (
3725 empty( $token_key )
3726 ||
3727 empty( $version ) || strval( JETPACK__API_VERSION ) !== $version
3728 ) {
3729 return false;
3730 }
3731
3732 if ( '0' === $user_id ) {
3733 $token_type = 'blog';
3734 $user_id = 0;
3735 } else {
3736 $token_type = 'user';
3737 if ( empty( $user_id ) || ! ctype_digit( $user_id ) ) {
3738 return false;
3739 }
3740 $user_id = (int) $user_id;
3741
3742 $user = new WP_User( $user_id );
3743 if ( ! $user || ! $user->exists() ) {
3744 return false;
3745 }
3746 }
3747
3748 $token = Jetpack_Data::get_access_token( $user_id );
3749 if ( ! $token ) {
3750 return false;
3751 }
3752
3753 if ( 0 !== strpos( $token->secret, "$token_key." ) ) {
3754 return false;
3755 }
3756
3757 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
3758
3759 $jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
3760 if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
3761 $post_data = $_POST;
3762 $file_hashes = array();
3763 foreach ( $post_data as $post_data_key => $post_data_value ) {
3764 if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
3765 continue;
3766 }
3767 $post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
3768 $file_hashes[$post_data_key] = $post_data_value;
3769 }
3770
3771 foreach ( $file_hashes as $post_data_key => $post_data_value ) {
3772 unset( $post_data["_jetpack_file_hmac_{$post_data_key}"] );
3773 $post_data[$post_data_key] = $post_data_value;
3774 }
3775
3776 ksort( $post_data );
3777
3778 $body = http_build_query( stripslashes_deep( $post_data ) );
3779 } elseif ( is_null( $this->HTTP_RAW_POST_DATA ) ) {
3780 $body = file_get_contents( 'php://input' );
3781 } else {
3782 $body = null;
3783 }
3784 $signature = $jetpack_signature->sign_current_request(
3785 array( 'body' => is_null( $body ) ? $this->HTTP_RAW_POST_DATA : $body, )
3786 );
3787
3788 if ( ! $signature ) {
3789 return false;
3790 } else if ( is_wp_error( $signature ) ) {
3791 return $signature;
3792 } else if ( $signature !== $_GET['signature'] ) {
3793 return false;
3794 }
3795
3796 $timestamp = (int) $_GET['timestamp'];
3797 $nonce = stripslashes( (string) $_GET['nonce'] );
3798
3799 if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
3800 return false;
3801 }
3802
3803 $this->xmlrpc_verification = array(
3804 'type' => $token_type,
3805 'user_id' => $token->external_user_id,
3806 );
3807
3808 return $this->xmlrpc_verification;
3809 }
3810
3811 /**
3812 * Authenticates XML-RPC and other requests from the Jetpack Server
3813 */
3814 function authenticate_jetpack( $user, $username, $password ) {
3815 if ( is_a( $user, 'WP_User' ) ) {
3816 return $user;
3817 }
3818
3819 $token_details = $this->verify_xml_rpc_signature();
3820
3821 if ( ! $token_details || is_wp_error( $token_details ) ) {
3822 return $user;
3823 }
3824
3825 if ( 'user' !== $token_details['type'] ) {
3826 return $user;
3827 }
3828
3829 if ( ! $token_details['user_id'] ) {
3830 return $user;
3831 }
3832
3833 nocache_headers();
3834
3835 return new WP_User( $token_details['user_id'] );
3836 }
3837
3838 function add_nonce( $timestamp, $nonce ) {
3839 global $wpdb;
3840 static $nonces_used_this_request = array();
3841
3842 if ( isset( $nonces_used_this_request["$timestamp:$nonce"] ) ) {
3843 return $nonces_used_this_request["$timestamp:$nonce"];
3844 }
3845
3846 // This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce
3847 $timestamp = (int) $timestamp;
3848 $nonce = esc_sql( $nonce );
3849
3850 // Raw query so we can avoid races: add_option will also update
3851 $show_errors = $wpdb->show_errors( false );
3852
3853 $old_nonce = $wpdb->get_row(
3854 $wpdb->prepare( "SELECT * FROM `$wpdb->options` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
3855 );
3856
3857 if ( is_null( $old_nonce ) ) {
3858 $return = $wpdb->query(
3859 $wpdb->prepare(
3860 "INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
3861 "jetpack_nonce_{$timestamp}_{$nonce}",
3862 time(),
3863 'no'
3864 )
3865 );
3866 } else {
3867 $return = false;
3868 }
3869
3870 $wpdb->show_errors( $show_errors );
3871
3872 $nonces_used_this_request["$timestamp:$nonce"] = $return;
3873
3874 return $return;
3875 }
3876
3877 /**
3878 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths since it is passed by reference to various methods.
3879 * Capture it here so we can verify the signature later.
3880 */
3881 function xmlrpc_methods( $methods ) {
3882 $this->HTTP_RAW_POST_DATA = $GLOBALS['HTTP_RAW_POST_DATA'];
3883 return $methods;
3884 }
3885
3886 function xmlrpc_options( $options ) {
3887 $options['jetpack_version'] = array(
3888 'desc' => __( 'Jetpack Plugin Version', 'jetpack' ),
3889 'readonly' => true,
3890 'value' => JETPACK__VERSION,
3891 );
3892
3893 $options['jetpack_client_id'] = array(
3894 'desc' => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack' ),
3895 'readonly' => true,
3896 'value' => Jetpack_Options::get_option( 'id' ),
3897 );
3898 return $options;
3899 }
3900
3901 public static function clean_nonces( $all = false ) {
3902 global $wpdb;
3903
3904 $sql = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
3905 if ( method_exists ( $wpdb , 'esc_like' ) ) {
3906 $sql_args = array( $wpdb->esc_like( 'jetpack_nonce_' ) . '%' );
3907 } else {
3908 $sql_args = array( like_escape( 'jetpack_nonce_' ) . '%' );
3909 }
3910
3911 if ( true !== $all ) {
3912 $sql .= ' AND CAST( `option_value` AS UNSIGNED ) < %d';
3913 $sql_args[] = time() - 3600;
3914 }
3915
3916 $sql .= ' ORDER BY `option_id` LIMIT 100';
3917
3918 $sql = $wpdb->prepare( $sql, $sql_args );
3919
3920 for ( $i = 0; $i < 1000; $i++ ) {
3921 if ( ! $wpdb->query( $sql ) ) {
3922 break;
3923 }
3924 }
3925 }
3926
3927 /**
3928 * State is passed via cookies from one request to the next, but never to subsequent requests.
3929 * SET: state( $key, $value );
3930 * GET: $value = state( $key );
3931 *
3932 * @param string $key
3933 * @param string $value
3934 * @param bool $restate private
3935 */
3936 public static function state( $key = null, $value = null, $restate = false ) {
3937 static $state = array();
3938 static $path, $domain;
3939 if ( ! isset( $path ) ) {
3940 require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
3941 $admin_url = Jetpack::admin_url();
3942 $bits = parse_url( $admin_url );
3943
3944 if ( is_array( $bits ) ) {
3945 $path = ( isset( $bits['path'] ) ) ? dirname( $bits['path'] ) : null;
3946 $domain = ( isset( $bits['host'] ) ) ? $bits['host'] : null;
3947 } else {
3948 $path = $domain = null;
3949 }
3950 }
3951
3952 // Extract state from cookies and delete cookies
3953 if ( isset( $_COOKIE[ 'jetpackState' ] ) && is_array( $_COOKIE[ 'jetpackState' ] ) ) {
3954 $yum = $_COOKIE[ 'jetpackState' ];
3955 unset( $_COOKIE[ 'jetpackState' ] );
3956 foreach ( $yum as $k => $v ) {
3957 if ( strlen( $v ) )
3958 $state[ $k ] = $v;
3959 setcookie( "jetpackState[$k]", false, 0, $path, $domain );
3960 }
3961 }
3962
3963 if ( $restate ) {
3964 foreach ( $state as $k => $v ) {
3965 setcookie( "jetpackState[$k]", $v, 0, $path, $domain );
3966 }
3967 return;
3968 }
3969
3970 // Get a state variable
3971 if ( isset( $key ) && ! isset( $value ) ) {
3972 if ( array_key_exists( $key, $state ) )
3973 return $state[ $key ];
3974 return null;
3975 }
3976
3977 // Set a state variable
3978 if ( isset ( $key ) && isset( $value ) ) {
3979 if( is_array( $value ) && isset( $value[0] ) ) {
3980 $value = $value[0];
3981 }
3982 $state[ $key ] = $value;
3983 setcookie( "jetpackState[$key]", $value, 0, $path, $domain );
3984 }
3985 }
3986
3987 public static function restate() {
3988 Jetpack::state( null, null, true );
3989 }
3990
3991 public static function check_privacy( $file ) {
3992 static $is_site_publicly_accessible = null;
3993
3994 if ( is_null( $is_site_publicly_accessible ) ) {
3995 $is_site_publicly_accessible = false;
3996
3997 Jetpack::load_xml_rpc_client();
3998 $rpc = new Jetpack_IXR_Client();
3999
4000 $success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
4001 if ( $success ) {
4002 $response = $rpc->getResponse();
4003 if ( $response ) {
4004 $is_site_publicly_accessible = true;
4005 }
4006 }
4007
4008 Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
4009 }
4010
4011 if ( $is_site_publicly_accessible ) {
4012 return;
4013 }
4014
4015 $module_slug = self::get_module_slug( $file );
4016
4017 $privacy_checks = Jetpack::state( 'privacy_checks' );
4018 if ( ! $privacy_checks ) {
4019 $privacy_checks = $module_slug;
4020 } else {
4021 $privacy_checks .= ",$module_slug";
4022 }
4023
4024 Jetpack::state( 'privacy_checks', $privacy_checks );
4025 }
4026
4027 /**
4028 * Helper method for multicall XMLRPC.
4029 */
4030 public static function xmlrpc_async_call() {
4031 global $blog_id;
4032 static $clients = array();
4033
4034 $client_blog_id = is_multisite() ? $blog_id : 0;
4035
4036 if ( ! isset( $clients[$client_blog_id] ) ) {
4037 Jetpack::load_xml_rpc_client();
4038 $clients[$client_blog_id] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => JETPACK_MASTER_USER, ) );
4039 if ( function_exists( 'ignore_user_abort' ) ) {
4040 ignore_user_abort( true );
4041 }
4042 add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
4043 }
4044
4045 $args = func_get_args();
4046
4047 if ( ! empty( $args[0] ) ) {
4048 call_user_func_array( array( $clients[$client_blog_id], 'addCall' ), $args );
4049 } elseif ( is_multisite() ) {
4050 foreach ( $clients as $client_blog_id => $client ) {
4051 if ( ! $client_blog_id || empty( $client->calls ) ) {
4052 continue;
4053 }
4054
4055 $switch_success = switch_to_blog( $client_blog_id, true );
4056 if ( ! $switch_success ) {
4057 continue;
4058 }
4059
4060 flush();
4061 $client->query();
4062
4063 restore_current_blog();
4064 }
4065 } else {
4066 if ( isset( $clients[0] ) && ! empty( $clients[0]->calls ) ) {
4067 flush();
4068 $clients[0]->query();
4069 }
4070 }
4071 }
4072
4073 public static function staticize_subdomain( $url ) {
4074 $host = parse_url( $url, PHP_URL_HOST );
4075 if ( ! preg_match( '/.?(?:wordpress|wp)\.com$/', $host ) ) {
4076 return $url;
4077 }
4078
4079 if ( is_ssl() ) {
4080 return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
4081 }
4082
4083 srand( crc32( basename( $url ) ) );
4084 $static_counter = rand( 0, 2 );
4085 srand(); // this resets everything that relies on this, like array_rand() and shuffle()
4086
4087 return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
4088 }
4089
4090 /* JSON API Authorization */
4091
4092 /**
4093 * Handles the login action for Authorizing the JSON API
4094 */
4095 function login_form_json_api_authorization() {
4096 $this->verify_json_api_authorization_request();
4097
4098 add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
4099
4100 add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
4101 add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
4102 add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
4103 }
4104
4105 // Make sure the login form is POSTed to the signed URL so we can reverify the request
4106 function post_login_form_to_signed_url( $url, $path, $scheme ) {
4107 if ( 'wp-login.php' !== $path || 'login_post' !== $scheme ) {
4108 return $url;
4109 }
4110
4111 $parsed_url = parse_url( $url );
4112 $url = strtok( $url, '?' );
4113 $url = "$url?{$_SERVER['QUERY_STRING']}";
4114 if ( ! empty( $parsed_url['query'] ) )
4115 $url .= "&{$parsed_url['query']}";
4116
4117 return $url;
4118 }
4119
4120 // Make sure the POSTed request is handled by the same action
4121 function preserve_action_in_login_form_for_json_api_authorization() {
4122 echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
4123 echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . site_url( stripslashes( $_SERVER['REQUEST_URI'] ) ) . "' />\n";
4124 }
4125
4126 // If someone logs in to approve API access, store the Access Code in usermeta
4127 function store_json_api_authorization_token( $user_login, $user ) {
4128 add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
4129 add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
4130 $token = wp_generate_password( 32, false );
4131 update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
4132 }
4133
4134 // Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
4135 function allow_wpcom_public_api_domain( $domains ) {
4136 $domains[] = 'public-api.wordpress.com';
4137 return $domains;
4138 }
4139
4140 // Add the Access Code details to the public-api.wordpress.com redirect
4141 function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
4142 return add_query_arg(
4143 urlencode_deep(
4144 array(
4145 'jetpack-code' => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
4146 'jetpack-user-id' => (int) $user->ID,
4147 'jetpack-state' => $this->json_api_authorization_request['state'],
4148 )
4149 ),
4150 $redirect_to
4151 );
4152 }
4153
4154 // Verifies the request by checking the signature
4155 function verify_json_api_authorization_request() {
4156 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
4157
4158 $token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
4159 if ( ! $token || empty( $token->secret ) ) {
4160 wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.' , 'jetpack' ) );
4161 }
4162
4163 $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' );
4164
4165 $jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
4166
4167 if ( isset( $_POST['jetpack_json_api_original_query'] ) ) {
4168 $signature = $jetpack_signature->sign_request( $_GET['token'], $_GET['timestamp'], $_GET['nonce'], '', 'GET', $_POST['jetpack_json_api_original_query'], null, true );
4169 } else {
4170 $signature = $jetpack_signature->sign_current_request( array( 'body' => null, 'method' => 'GET' ) );
4171 }
4172
4173 if ( ! $signature ) {
4174 wp_die( $die_error );
4175 } else if ( is_wp_error( $signature ) ) {
4176 wp_die( $die_error );
4177 } else if ( $signature !== $_GET['signature'] ) {
4178 if ( is_ssl() ) {
4179 // 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
4180 $signature = $jetpack_signature->sign_current_request( array( 'scheme' => 'http', 'body' => null, 'method' => 'GET' ) );
4181 if ( ! $signature || is_wp_error( $signature ) || $signature !== $_GET['signature'] ) {
4182 wp_die( $die_error );
4183 }
4184 } else {
4185 wp_die( $die_error );
4186 }
4187 }
4188
4189 $timestamp = (int) $_GET['timestamp'];
4190 $nonce = stripslashes( (string) $_GET['nonce'] );
4191
4192 if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
4193 // De-nonce the nonce, at least for 5 minutes.
4194 // 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)
4195 $old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
4196 if ( $old_nonce_time < time() - 300 ) {
4197 wp_die( __( 'The authorization process expired. Please go back and try again.' , 'jetpack' ) );
4198 }
4199 }
4200
4201 $data = json_decode( base64_decode( stripslashes( $_GET['data'] ) ) );
4202 $data_filters = array(
4203 'state' => 'opaque',
4204 'client_id' => 'int',
4205 'client_title' => 'string',
4206 'client_image' => 'url',
4207 );
4208
4209 foreach ( $data_filters as $key => $sanitation ) {
4210 if ( ! isset( $data->$key ) ) {
4211 wp_die( $die_error );
4212 }
4213
4214 switch ( $sanitation ) {
4215 case 'int' :
4216 $this->json_api_authorization_request[$key] = (int) $data->$key;
4217 break;
4218 case 'opaque' :
4219 $this->json_api_authorization_request[$key] = (string) $data->$key;
4220 break;
4221 case 'string' :
4222 $this->json_api_authorization_request[$key] = wp_kses( (string) $data->$key, array() );
4223 break;
4224 case 'url' :
4225 $this->json_api_authorization_request[$key] = esc_url_raw( (string) $data->$key );
4226 break;
4227 }
4228 }
4229
4230 if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
4231 wp_die( $die_error );
4232 }
4233 }
4234
4235 function login_message_json_api_authorization( $message ) {
4236 return '<p class="message">' . sprintf(
4237 esc_html__( '%s wants to access your site&#8217;s data. Log in to authorize that access.' , 'jetpack' ),
4238 '<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
4239 ) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
4240 }
4241
4242 /**
4243 * Get $content_width, but with a <s>twist</s> filter.
4244 */
4245 public static function get_content_width() {
4246 $content_width = isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : false;
4247 return apply_filters( 'jetpack_content_width', $content_width );
4248 }
4249
4250 /**
4251 * Centralize the function here until it gets added to core.
4252 *
4253 * @param int|string|object $id_or_email A user ID, email address, or comment object
4254 * @param int $size Size of the avatar image
4255 * @param string $default URL to a default image to use if no avatar is available
4256 * @param bool $force_display Whether to force it to return an avatar even if show_avatars is disabled
4257 *
4258 * @return array First element is the URL, second is the class.
4259 */
4260 public static function get_avatar_url( $id_or_email, $size = 96, $default = '', $force_display = false ) {
4261 // Don't bother adding the __return_true filter if it's already there.
4262 $has_filter = has_filter( 'pre_option_show_avatars', '__return_true' );
4263
4264 if ( $force_display && ! $has_filter )
4265 add_filter( 'pre_option_show_avatars', '__return_true' );
4266
4267 $avatar = get_avatar( $id_or_email, $size, $default );
4268
4269 if ( $force_display && ! $has_filter )
4270 remove_filter( 'pre_option_show_avatars', '__return_true' );
4271
4272 // If no data, fail out.
4273 if ( is_wp_error( $avatar ) || ! $avatar )
4274 return array( null, null );
4275
4276 // Pull out the URL. If it's not there, fail out.
4277 if ( ! preg_match( '/src=["\']([^"\']+)["\']/', $avatar, $url_matches ) )
4278 return array( null, null );
4279 $url = wp_specialchars_decode( $url_matches[1], ENT_QUOTES );
4280
4281 // Pull out the class, but it's not a big deal if it's missing.
4282 $class = '';
4283 if ( preg_match( '/class=["\']([^"\']+)["\']/', $avatar, $class_matches ) )
4284 $class = wp_specialchars_decode( $class_matches[1], ENT_QUOTES );
4285
4286 return array( $url, $class );
4287 }
4288
4289 /**
4290 * Pings the WordPress.com Mirror Site for the specified options.
4291 *
4292 * @param string|array $option_names The option names to request from the WordPress.com Mirror Site
4293 *
4294 * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
4295 */
4296 public static function get_cloud_site_options( $option_names ) {
4297 $option_names = array_filter( (array) $option_names, 'is_string' );
4298
4299 Jetpack::load_xml_rpc_client();
4300 $xml = new Jetpack_IXR_Client( array( 'user_id' => JETPACK_MASTER_USER, ) );
4301 $xml->query( 'jetpack.fetchSiteOptions', $option_names );
4302 if ( $xml->isError() ) {
4303 return array_flip( $option_names );
4304 }
4305 $cloud_site_options = $xml->getResponse();
4306
4307 return $cloud_site_options;
4308 }
4309
4310 /**
4311 * Fetch the filtered array of options that we should compare to determine an identity crisis.
4312 *
4313 * @return array An array of options to check.
4314 */
4315 public static function identity_crisis_options_to_check() {
4316 $options = array(
4317 'siteurl',
4318 'home',
4319 );
4320 return apply_filters( 'jetpack_identity_crisis_options_to_check', $options );
4321 }
4322
4323 /**
4324 * Checks to make sure that local options have the same values as remote options. Will cache the results for up to 24 hours.
4325 *
4326 * @param bool $force_recheck Whether to ignore any cached transient and manually re-check.
4327 *
4328 * @return array An array of options that do not match. If everything is good, it will evaluate to false.
4329 */
4330 public static function check_identity_crisis( $force_recheck = false ) {
4331 if ( ! Jetpack::is_active() || Jetpack::is_development_mode() )
4332 return false;
4333
4334 if ( $force_recheck || false === ( $errors = get_transient( 'jetpack_has_identity_crisis' ) ) ) {
4335 $options_to_check = self::identity_crisis_options_to_check();
4336 $cloud_options = self::get_cloud_site_options( $options_to_check );
4337 $errors = array();
4338 foreach ( $cloud_options as $cloud_key => $cloud_value ) {
4339 // If it's not the same as the local value...
4340 if ( $cloud_value !== get_option( $cloud_key ) ) {
4341 // And it's not been added to the whitelist...
4342 if ( ! self::is_identity_crisis_value_whitelisted( $cloud_key, $cloud_value ) ) {
4343 // Then kick an error!
4344 $errors[ $cloud_key ] = $cloud_value;
4345 }
4346 }
4347 }
4348 }
4349 return apply_filters( 'jetpack_has_identity_crisis', $errors, $force_recheck );
4350 }
4351
4352 /**
4353 * Adds a value to the whitelist for the specified key.
4354 *
4355 * @param string $key The option name that we're whitelisting the value for.
4356 * @param string $value The value that we're intending to add to the whitelist.
4357 *
4358 * @return bool Whether the value was added to the whitelist, or false if it was already there.
4359 */
4360 public static function whitelist_identity_crisis_value( $key, $value ) {
4361 if ( self::is_identity_crisis_url_whitelisted( $key, $value ) ) {
4362 return false;
4363 }
4364
4365 $whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist', array() );
4366 if ( empty( $whitelist[ $key ] ) || ! is_array( $whitelist[ $key ] ) ) {
4367 $whitelist[ $key ] = array();
4368 }
4369 array_push( $whitelist[ $key ], $value );
4370
4371 Jetpack_Options::update_option( 'identity_crisis_whitelist', $whitelist );
4372 return true;
4373 }
4374
4375 /**
4376 * Checks whether a value is already whitelisted.
4377 *
4378 * @param string $key The option name that we're checking the value for.
4379 * @param string $value The value that we're curious to see if it's on the whitelist.
4380 *
4381 * @return bool Whether the value is whitelisted.
4382 */
4383 public static function is_identity_crisis_value_whitelisted( $key, $value ) {
4384 $whitelist = Jetpack_Options::get_option( 'identity_crisis_whitelist', array() );
4385 if ( ! empty( $whitelist[ $key ] ) && is_array( $whitelist[ $key ] ) && in_array( $value, $whitelist[ $key ] ) ) {
4386 return true;
4387 }
4388 return false;
4389 }
4390
4391 /**
4392 * Displays an admin_notice, alerting the user to an identity crisis.
4393 */
4394 public function alert_identity_crisis() {
4395 if ( ! current_user_can( 'manage_options' ) )
4396 return;
4397
4398 if ( ! $errors = self::check_identity_crisis() )
4399 return;
4400 ?>
4401
4402 <div id="message" class="updated jetpack-message jp-identity-crisis">
4403 <div class="jetpack-wrap-container">
4404 <div class="jetpack-text-container">
4405 <h3><?php _e( 'Something has gotten mixed up!', 'jetpack' ); ?></h3>
4406 <?php foreach ( $errors as $key => $value ) : ?>
4407 <p><?php printf( __( 'Your <code>%1$s</code> option is set up as <strong>%2$s</strong>, but your WordPress.com connection lists it as <strong>%3$s</strong>!', 'jetpack' ), $key, (string) get_option( $key ), $value ); ?></p>
4408 <?php endforeach; ?>
4409 <p><a href="<?php echo $this->build_reconnect_url() ?>"><?php _e( 'The data listed above is not for my current site. Please disconnect, and then form a new connection to WordPress.com for this site using my current settings.', 'jetpack' ); ?></a></p>
4410 <p><a href="#"><?php _e( 'Ignore the difference. This is just a staging site for the real site referenced above.', 'jetpack' ); ?></a></p>
4411 <p><a href="#"><?php _e( 'That used to be my URL for this site before I changed it. Update the WordPress.com Cloud\'s data to match my current settings.', 'jetpack' ); ?></a></p>
4412 </div>
4413 </div>
4414 </div>
4415
4416 <?php
4417 }
4418 }
4419