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

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