summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/modules/widgets')
-rw-r--r--plugins/jetpack/modules/widgets/authors.php271
-rw-r--r--plugins/jetpack/modules/widgets/authors/style.css25
-rw-r--r--plugins/jetpack/modules/widgets/blog-stats.php173
-rw-r--r--plugins/jetpack/modules/widgets/contact-info.php394
-rw-r--r--plugins/jetpack/modules/widgets/contact-info/contact-info-admin.js11
-rw-r--r--plugins/jetpack/modules/widgets/contact-info/contact-info-map.css4
-rw-r--r--plugins/jetpack/modules/widgets/customizer-controls.css6
-rw-r--r--plugins/jetpack/modules/widgets/customizer-utils.js119
-rw-r--r--plugins/jetpack/modules/widgets/eu-cookie-law.php301
-rw-r--r--plugins/jetpack/modules/widgets/eu-cookie-law/eu-cookie-law-admin.js32
-rw-r--r--plugins/jetpack/modules/widgets/eu-cookie-law/eu-cookie-law.js80
-rw-r--r--plugins/jetpack/modules/widgets/eu-cookie-law/form.php277
-rw-r--r--plugins/jetpack/modules/widgets/eu-cookie-law/style.css105
-rw-r--r--plugins/jetpack/modules/widgets/eu-cookie-law/widget.php25
-rw-r--r--plugins/jetpack/modules/widgets/facebook-likebox.php309
-rw-r--r--plugins/jetpack/modules/widgets/facebook-likebox/style.css3
-rw-r--r--plugins/jetpack/modules/widgets/flickr.php218
-rw-r--r--plugins/jetpack/modules/widgets/flickr/form.php93
-rw-r--r--plugins/jetpack/modules/widgets/flickr/style.css16
-rw-r--r--plugins/jetpack/modules/widgets/flickr/widget.php13
-rw-r--r--plugins/jetpack/modules/widgets/gallery.php464
-rw-r--r--plugins/jetpack/modules/widgets/gallery/css/admin-rtl.css12
-rw-r--r--plugins/jetpack/modules/widgets/gallery/css/admin-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/widgets/gallery/css/admin.css11
-rw-r--r--plugins/jetpack/modules/widgets/gallery/css/admin.min.css2
-rw-r--r--plugins/jetpack/modules/widgets/gallery/css/rtl/admin-rtl.css13
-rw-r--r--plugins/jetpack/modules/widgets/gallery/js/admin.js236
-rw-r--r--plugins/jetpack/modules/widgets/gallery/js/gallery.js10
-rw-r--r--plugins/jetpack/modules/widgets/gallery/templates/form.php89
-rw-r--r--plugins/jetpack/modules/widgets/goodreads.php157
-rw-r--r--plugins/jetpack/modules/widgets/goodreads/css/goodreads.css48
-rw-r--r--plugins/jetpack/modules/widgets/goodreads/css/rtl/goodreads-rtl.css50
-rw-r--r--plugins/jetpack/modules/widgets/google-translate.php203
-rw-r--r--plugins/jetpack/modules/widgets/google-translate/google-translate.js32
-rw-r--r--plugins/jetpack/modules/widgets/gravatar-profile.css46
-rw-r--r--plugins/jetpack/modules/widgets/gravatar-profile.php435
-rw-r--r--plugins/jetpack/modules/widgets/image-widget.php274
-rw-r--r--plugins/jetpack/modules/widgets/image-widget/style.css13
-rw-r--r--plugins/jetpack/modules/widgets/internet-defense-league.php153
-rw-r--r--plugins/jetpack/modules/widgets/mailchimp.php103
-rw-r--r--plugins/jetpack/modules/widgets/migrate-to-core/gallery-widget.php198
-rw-r--r--plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php220
-rw-r--r--plugins/jetpack/modules/widgets/milestone.php5
-rw-r--r--plugins/jetpack/modules/widgets/milestone/admin.js31
-rw-r--r--plugins/jetpack/modules/widgets/milestone/milestone.js49
-rw-r--r--plugins/jetpack/modules/widgets/milestone/milestone.php683
-rw-r--r--plugins/jetpack/modules/widgets/milestone/style-admin.css50
-rw-r--r--plugins/jetpack/modules/widgets/my-community.php297
-rw-r--r--plugins/jetpack/modules/widgets/my-community/style.css35
-rw-r--r--plugins/jetpack/modules/widgets/rsslinks-widget.php242
-rw-r--r--plugins/jetpack/modules/widgets/search.php815
-rw-r--r--plugins/jetpack/modules/widgets/search/css/search-widget-admin-ui.css87
-rw-r--r--plugins/jetpack/modules/widgets/search/css/search-widget-frontend.css66
-rw-r--r--plugins/jetpack/modules/widgets/search/js/search-widget-admin.js360
-rw-r--r--plugins/jetpack/modules/widgets/search/js/search-widget.js19
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments.php544
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/admin-warning.php16
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/customizer.css80
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/customizer.js455
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/form.php205
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/style.css8
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/widget.php33
-rw-r--r--plugins/jetpack/modules/widgets/social-icons.php680
-rw-r--r--plugins/jetpack/modules/widgets/social-icons/social-icons-admin.css94
-rw-r--r--plugins/jetpack/modules/widgets/social-icons/social-icons-admin.js144
-rw-r--r--plugins/jetpack/modules/widgets/social-icons/social-icons.css75
-rw-r--r--plugins/jetpack/modules/widgets/social-media-icons.php346
-rw-r--r--plugins/jetpack/modules/widgets/social-media-icons/style.css49
-rw-r--r--plugins/jetpack/modules/widgets/top-posts.php674
-rw-r--r--plugins/jetpack/modules/widgets/top-posts/style.css114
-rw-r--r--plugins/jetpack/modules/widgets/twitter-timeline-admin.js35
-rw-r--r--plugins/jetpack/modules/widgets/twitter-timeline.php503
-rw-r--r--plugins/jetpack/modules/widgets/upcoming-events.php131
-rw-r--r--plugins/jetpack/modules/widgets/wordpress-post-widget.php116
-rw-r--r--plugins/jetpack/modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget-base.php843
-rw-r--r--plugins/jetpack/modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget.php274
-rw-r--r--plugins/jetpack/modules/widgets/wordpress-post-widget/style.css24
77 files changed, 13427 insertions, 0 deletions
diff --git a/plugins/jetpack/modules/widgets/authors.php b/plugins/jetpack/modules/widgets/authors.php
new file mode 100644
index 00000000..dfc78652
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/authors.php
@@ -0,0 +1,271 @@
+<?php
+/**
+ * Disable direct access/execution to/of the widget code.
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Widget to display blog authors with avatars and recent posts.
+ *
+ * Configurable parameters include:
+ * 1. Whether to display authors who haven't written any posts
+ * 2. The number of posts to be displayed per author (defaults to 0)
+ * 3. Avatar size
+ *
+ * @since 4.5.0
+ */
+class Jetpack_Widget_Authors extends WP_Widget {
+ public function __construct() {
+ parent::__construct(
+ 'authors',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Authors', 'jetpack' ) ),
+ array(
+ 'classname' => 'widget_authors',
+ 'description' => __( 'Display blogs authors with avatars and recent posts.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_active_widget( false, false, 'monster' ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
+ }
+
+ add_action( 'publish_post', array( __CLASS__, 'flush_cache' ) );
+ add_action( 'deleted_post', array( __CLASS__, 'flush_cache' ) );
+ add_action( 'switch_theme', array( __CLASS__, 'flush_cache' ) );
+ }
+
+ /**
+ * Enqueue stylesheet to adapt the widget to various themes.
+ *
+ * @since 4.5.0
+ */
+ function enqueue_style() {
+ wp_register_style( 'jetpack-authors-widget', plugins_url( 'authors/style.css', __FILE__ ), array(), '20161228' );
+ wp_enqueue_style( 'jetpack-authors-widget' );
+ }
+
+ public static function flush_cache() {
+ wp_cache_delete( 'widget_authors', 'widget' );
+ wp_cache_delete( 'widget_authors_ssl', 'widget' );
+ }
+
+ public function widget( $args, $instance ) {
+ $cache_bucket = is_ssl() ? 'widget_authors_ssl' : 'widget_authors';
+
+ if ( '%BEG_OF_TITLE%' != $args['before_title'] ) {
+ if ( $output = wp_cache_get( $cache_bucket, 'widget' ) ) {
+ echo $output;
+ return;
+ }
+
+ ob_start();
+ }
+
+ $instance = wp_parse_args(
+ $instance, array(
+ 'title' => __( 'Authors', 'jetpack' ),
+ 'all' => false,
+ 'number' => 5,
+ 'avatar_size' => 48,
+ )
+ );
+ $instance['number'] = min( 10, max( 0, (int) $instance['number'] ) );
+
+ // We need to query at least one post to determine whether an author has written any posts or not
+ $query_number = max( $instance['number'], 1 );
+
+ $default_excluded_authors = array();
+ /**
+ * Filter authors from the Widget Authors widget.
+ *
+ * @module widgets
+ *
+ * @since 4.5.0
+ *
+ * @param array $default_excluded_authors Array of user ID's that will be excluded
+ */
+ $excluded_authors = apply_filters( 'jetpack_widget_authors_exclude', $default_excluded_authors );
+
+ $authors = get_users(
+ array(
+ 'fields' => 'all',
+ 'who' => 'authors',
+ 'exclude' => (array) $excluded_authors,
+ )
+ );
+
+ echo $args['before_widget'];
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+ echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
+ echo '<ul>';
+
+ $default_post_type = 'post';
+ /**
+ * Filter types of posts that will be counted in the widget
+ *
+ * @module widgets
+ *
+ * @since 4.5.0
+ *
+ * @param string|array $default_post_type type(s) of posts to count for the widget.
+ */
+ $post_types = apply_filters( 'jetpack_widget_authors_post_types', $default_post_type );
+
+ foreach ( $authors as $author ) {
+ $r = new WP_Query(
+ array(
+ 'author' => $author->ID,
+ 'posts_per_page' => $query_number,
+ 'post_type' => $post_types,
+ 'post_status' => 'publish',
+ 'no_found_rows' => true,
+ 'has_password' => false,
+ )
+ );
+
+ if ( ! $r->have_posts() && ! $instance['all'] ) {
+ continue;
+ }
+
+ echo '<li>';
+
+ // Display avatar and author name
+ if ( $r->have_posts() ) {
+ echo '<a href="' . get_author_posts_url( $author->ID ) . '">';
+
+ if ( $instance['avatar_size'] > 1 ) {
+ echo ' ' . get_avatar( $author->ID, $instance['avatar_size'], '', true ) . ' ';
+ }
+
+ echo '<strong>' . esc_html( $author->display_name ) . '</strong>';
+ echo '</a>';
+ } elseif ( $instance['all'] ) {
+ if ( $instance['avatar_size'] > 1 ) {
+ echo get_avatar( $author->ID, $instance['avatar_size'], '', true ) . ' ';
+ }
+
+ echo '<strong>' . esc_html( $author->display_name ) . '</strong>';
+ }
+
+ if ( 0 == $instance['number'] ) {
+ echo '</li>';
+ continue;
+ }
+
+ // Display a short list of recent posts for this author
+
+ if ( $r->have_posts() ) {
+ echo '<ul>';
+
+ while ( $r->have_posts() ) {
+ $r->the_post();
+ echo '<li><a href="' . get_permalink() . '">';
+
+ if ( get_the_title() ) {
+ echo get_the_title();
+ } else {
+ echo get_the_ID();
+ }
+
+ echo '</a></li>';
+ }
+
+ echo '</ul>';
+ }
+
+ echo '</li>';
+ }
+
+ echo '</ul>';
+ echo $args['after_widget'];
+
+ wp_reset_postdata();
+
+ if ( '%BEG_OF_TITLE%' != $args['before_title'] ) {
+ wp_cache_add( $cache_bucket, ob_get_flush(), 'widget' );
+ }
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'authors' );
+ }
+
+ public function form( $instance ) {
+ $instance = wp_parse_args(
+ $instance, array(
+ 'title' => '',
+ 'all' => false,
+ 'avatar_size' => 48,
+ 'number' => 5,
+ )
+ );
+
+ ?>
+ <p>
+ <label>
+ <?php _e( 'Title:', 'jetpack' ); ?>
+ <input class="widefat" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
+ </label>
+ </p>
+ <p>
+ <label>
+ <input class="checkbox" type="checkbox" <?php checked( $instance['all'] ); ?> name="<?php echo $this->get_field_name( 'all' ); ?>" />
+ <?php _e( 'Display all authors (including those who have not written any posts)', 'jetpack' ); ?>
+ </label>
+ </p>
+ <p>
+ <label>
+ <?php _e( 'Number of posts to show for each author:', 'jetpack' ); ?>
+ <input style="width: 50px; text-align: center;" name="<?php echo $this->get_field_name( 'number' ); ?>" type="text" value="<?php echo esc_attr( $instance['number'] ); ?>" />
+ <?php _e( '(at most 10)', 'jetpack' ); ?>
+ </label>
+ </p>
+ <p>
+ <label>
+ <?php _e( 'Avatar Size (px):', 'jetpack' ); ?>
+ <select name="<?php echo $this->get_field_name( 'avatar_size' ); ?>">
+ <?php
+ foreach ( array(
+ '1' => __( 'No Avatars', 'jetpack' ),
+ '16' => '16x16',
+ '32' => '32x32',
+ '48' => '48x48',
+ '96' => '96x96',
+ '128' => '128x128',
+ ) as $value => $label ) {
+?>
+ <option value="<?php echo esc_attr( $value ); ?>" <?php selected( $value, $instance['avatar_size'] ); ?>><?php echo esc_html( $label ); ?></option>
+ <?php } ?>
+ </select>
+ </label>
+ </p>
+ <?php
+ }
+
+ /**
+ * Updates the widget on save and flushes cache.
+ *
+ * @param array $new_instance
+ * @param array $old_instance
+ * @return array
+ */
+ public function update( $new_instance, $old_instance ) {
+ $new_instance['title'] = strip_tags( $new_instance['title'] );
+ $new_instance['all'] = isset( $new_instance['all'] );
+ $new_instance['number'] = (int) $new_instance['number'];
+ $new_instance['avatar_size'] = (int) $new_instance['avatar_size'];
+
+ Jetpack_Widget_Authors::flush_cache();
+
+ return $new_instance;
+ }
+}
+
+add_action( 'widgets_init', 'jetpack_register_widget_authors' );
+function jetpack_register_widget_authors() {
+ register_widget( 'Jetpack_Widget_Authors' );
+};
diff --git a/plugins/jetpack/modules/widgets/authors/style.css b/plugins/jetpack/modules/widgets/authors/style.css
new file mode 100644
index 00000000..17ca1b69
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/authors/style.css
@@ -0,0 +1,25 @@
+/* Authors Widget */
+.widget_authors > ul, .widget.widget_authors li > ul {
+ margin-left: inherit;
+ padding-left: 0;
+}
+.widget_authors ul li li {
+ padding-left: 0;
+}
+
+.widget_authors > ul > li {
+ margin-bottom: 1em;
+ list-style: none;
+}
+
+.widget_authors > ul > li + li {
+ border-top: 0;
+}
+
+.widget.widget_authors img {
+ margin-right: 5px;
+ margin-bottom: 5px;
+ vertical-align: middle;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/blog-stats.php b/plugins/jetpack/modules/widgets/blog-stats.php
new file mode 100644
index 00000000..e89db686
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/blog-stats.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Blog Stats Widget.
+ *
+ * @since 4.5.0
+ *
+ * @package Jetpack
+ */
+
+/**
+ * Disable direct access/execution to/of the widget code.
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Blog Stats Widget.
+ *
+ * Displays all time stats for that site.
+ *
+ * @since 4.5.0
+ */
+class Jetpack_Blog_Stats_Widget extends WP_Widget {
+
+ /**
+ * Constructor
+ */
+ function __construct() {
+ $widget_ops = array(
+ 'classname' => 'blog-stats',
+ 'description' => esc_html__( 'Show a hit counter for your blog.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ );
+ parent::__construct(
+ 'blog-stats',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'Blog Stats', 'jetpack' ) ),
+ $widget_ops
+ );
+ $this->alt_option_name = 'widget_statscounter';
+ }
+
+ /**
+ * Return an associative array of default values
+ *
+ * These values are used in new widgets.
+ *
+ * @return array Array of default values for the Widget's options
+ */
+ public function defaults() {
+ return array(
+ 'title' => esc_html__( 'Blog Stats', 'jetpack' ),
+ /* Translators: Number of views, plural */
+ 'hits' => esc_html__( 'hits', 'jetpack' ),
+ );
+ }
+
+ /**
+ * Return All Time Stats for that blog.
+ *
+ * We query the WordPress.com Stats REST API endpoint.
+ *
+ * @uses stats_get_from_restapi(). That function caches data locally for 5 minutes.
+ *
+ * @return string|false $views All Time Stats for that blog.
+ */
+ public function get_stats() {
+ // Get data from the WordPress.com Stats REST API endpoint.
+ $stats = stats_get_from_restapi( array( 'fields' => 'stats' ) );
+
+ if ( isset( $stats->stats->views ) ) {
+ return $stats->stats->views;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Back end widget form.
+ *
+ * @see WP_Widget::form()
+ *
+ * @param array $instance Previously saved values from database.
+ *
+ * @return void
+ */
+ function form( $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults() );
+ ?>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
+ </p>
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'hits' ) ); ?>"><?php echo number_format_i18n( '12345' ); ?></label>
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'hits' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'hits' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['hits'] ); ?>" />
+ </p>
+ <p><?php esc_html_e( 'Hit counter is delayed by up to 60 seconds.', 'jetpack' ); ?></p>
+
+ <?php
+ }
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @see WP_Widget::update()
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ *
+ * @return array Updated safe values to be saved.
+ */
+ function update( $new_instance, $old_instance ) {
+ $instance = array();
+ $instance['title'] = wp_kses( $new_instance['title'], array() );
+ $instance['hits'] = wp_kses( $new_instance['hits'], array() );
+
+ return $instance;
+ }
+
+ /**
+ * Front-end display of widget.
+ *
+ * @see WP_Widget::widget()
+ *
+ * @param array $args Widget arguments.
+ * @param array $instance Saved values from database.
+ */
+ function widget( $args, $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults() );
+
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+
+ echo $args['before_widget'];
+
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
+ }
+
+ // Get the Site Stats.
+ $views = $this->get_stats();
+
+ if ( ! empty( $views ) ) {
+ printf(
+ '<ul><li>%1$s %2$s</li></ul>',
+ number_format_i18n( $views ),
+ isset( $instance['hits'] ) ? esc_html( $instance['hits'] ) : ''
+ );
+ } else {
+ esc_html_e( 'There was an issue retrieving stats. Please try again later.', 'jetpack' );
+ }
+
+ echo $args['after_widget'];
+
+ /** This action is already documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'blog_stats' );
+ }
+}
+
+/**
+ * If the Stats module is active in a recent version of Jetpack, register the widget.
+ *
+ * @since 4.5.0
+ */
+function jetpack_blog_stats_widget_init() {
+ if ( function_exists( 'stats_get_from_restapi' ) ) {
+ register_widget( 'Jetpack_Blog_Stats_Widget' );
+ }
+}
+add_action( 'widgets_init', 'jetpack_blog_stats_widget_init' );
diff --git a/plugins/jetpack/modules/widgets/contact-info.php b/plugins/jetpack/modules/widgets/contact-info.php
new file mode 100644
index 00000000..93b4695b
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/contact-info.php
@@ -0,0 +1,394 @@
+<?php
+
+if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) {
+
+ //register Contact_Info_Widget widget
+ function jetpack_contact_info_widget_init() {
+ register_widget( 'Jetpack_Contact_Info_Widget' );
+ }
+
+ add_action( 'widgets_init', 'jetpack_contact_info_widget_init' );
+
+ /**
+ * Makes a custom Widget for displaying Restaurant Location/Map, Hours, and Contact Info available.
+ *
+ * @package WordPress
+ */
+ class Jetpack_Contact_Info_Widget extends WP_Widget {
+
+ /**
+ * Constructor
+ */
+ function __construct() {
+ $widget_ops = array(
+ 'classname' => 'widget_contact_info',
+ 'description' => __( 'Display a map with your location, hours, and contact information.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ );
+ parent::__construct(
+ 'widget_contact_info',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Contact Info & Map', 'jetpack' ) ),
+ $widget_ops
+ );
+ $this->alt_option_name = 'widget_contact_info';
+
+ if ( is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+ }
+ }
+
+ /**
+ * Enqueue scripts and styles.
+ */
+ public function enqueue_scripts() {
+ wp_enqueue_style( 'contact-info-map-css', plugins_url( 'contact-info/contact-info-map.css', __FILE__ ), null, 20160623 );
+ }
+
+
+ /**
+ * Return an associative array of default values
+ *
+ * These values are used in new widgets.
+ *
+ * @return array Array of default values for the Widget's options
+ */
+ public function defaults() {
+ return array(
+ 'title' => __( 'Hours & Info', 'jetpack' ),
+ 'address' => __( "3999 Mission Boulevard,\nSan Diego CA 92109", 'jetpack' ),
+ 'phone' => _x( '1-202-555-1212', 'Example of a phone number', 'jetpack' ),
+ 'hours' => __( "Lunch: 11am - 2pm \nDinner: M-Th 5pm - 11pm, Fri-Sat:5pm - 1am", 'jetpack' ),
+ 'email' => null,
+ 'showmap' => 0,
+ 'apikey' => null,
+ 'lat' => null,
+ 'lon' => null,
+ );
+ }
+
+ /**
+ * Outputs the HTML for this widget.
+ *
+ * @param array $args An array of standard parameters for widgets in this theme
+ * @param array $instance An array of settings for this widget instance
+ *
+ * @return void Echoes it's output
+ **/
+ function widget( $args, $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults() );
+
+ echo $args['before_widget'];
+
+ if ( '' != $instance['title'] ) {
+ echo $args['before_title'] . $instance['title'] . $args['after_title'];
+ }
+
+ /**
+ * Fires at the beginning of the Contact Info widget, after the title.
+ *
+ * @module widgets
+ *
+ * @since 3.9.2
+ */
+ do_action( 'jetpack_contact_info_widget_start' );
+
+ echo '<div itemscope itemtype="http://schema.org/LocalBusiness">';
+
+ if ( '' != $instance['address'] ) {
+
+ $showmap = $instance['showmap'];
+
+ /** This action is documented in modules/widgets/contact-info.php */
+ if ( $showmap && $this->has_good_map( $instance ) ) {
+ /**
+ * Set a Google Maps API Key.
+ *
+ * @since 4.1.0
+ *
+ * @param string $api_key Google Maps API Key
+ */
+ $api_key = apply_filters( 'jetpack_google_maps_api_key', $instance['apikey'] );
+ echo $this->build_map( $instance['address'], $api_key );
+ }
+
+ $map_link = $this->build_map_link( $instance['address'] );
+
+ echo '<div class="confit-address" itemscope itemtype="http://schema.org/PostalAddress" itemprop="address"><a href="' . esc_url( $map_link ) . '" target="_blank">' . str_replace( "\n", '<br/>', esc_html( $instance['address'] ) ) . '</a></div>';
+ }
+
+ if ( '' != $instance['phone'] ) {
+ if ( wp_is_mobile() ) {
+ echo '<div class="confit-phone"><span itemprop="telephone"><a href="' . esc_url( 'tel:' . $instance['phone'] ) . '">' . esc_html( $instance['phone'] ) . '</a></span></div>';
+ } else {
+ echo '<div class="confit-phone"><span itemprop="telephone">' . esc_html( $instance['phone'] ) . '</span></div>';
+ }
+ }
+
+ if ( is_email( trim( $instance['email'] ) ) ) {
+ printf(
+ '<div class="confit-email"><a href="mailto:%1$s">%1$s</a></div>',
+ esc_html( $instance['email'] )
+ );
+ }
+
+ if ( '' != $instance['hours'] ) {
+ echo '<div class="confit-hours" itemprop="openingHours">' . str_replace( "\n", '<br/>', esc_html( $instance['hours'] ) ) . '</div>';
+ }
+
+ echo '</div>';
+
+ /**
+ * Fires at the end of Contact Info widget.
+ *
+ * @module widgets
+ *
+ * @since 3.9.2
+ */
+ do_action( 'jetpack_contact_info_widget_end' );
+
+ echo $args['after_widget'];
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'contact_info' );
+ }
+
+
+ /**
+ * Deals with the settings when they are saved by the admin. Here is
+ * where any validation should be dealt with.
+ *
+ * @param array $new_instance New configuration values
+ * @param array $old_instance Old configuration values
+ *
+ * @return array
+ */
+ function update( $new_instance, $old_instance ) {
+ $update_lat_lon = false;
+ if (
+ ! isset( $old_instance['address'] ) ||
+ $this->urlencode_address( $old_instance['address'] ) != $this->urlencode_address( $new_instance['address'] )
+ ) {
+ $update_lat_lon = true;
+ }
+
+ $instance = array();
+ $instance['title'] = wp_kses( $new_instance['title'], array() );
+ $instance['address'] = wp_kses( $new_instance['address'], array() );
+ $instance['phone'] = wp_kses( $new_instance['phone'], array() );
+ $instance['email'] = wp_kses( $new_instance['email'], array() );
+ $instance['hours'] = wp_kses( $new_instance['hours'], array() );
+ $instance['apikey'] = wp_kses( isset( $new_instance['apikey'] ) ? $new_instance['apikey'] : $old_instance['apikey'], array() );
+ $instance['lat'] = isset( $old_instance['lat'] ) ? floatval( $old_instance['lat'] ) : 0;
+ $instance['lon'] = isset( $old_instance['lon'] ) ? floatval( $old_instance['lon'] ) : 0;
+
+ if ( ! $instance['lat'] || ! $instance['lon'] ) {
+ $update_lat_lon = true;
+ }
+
+ if ( $instance['address'] && $update_lat_lon ) {
+
+ // Get the lat/lon of the user specified address.
+ $address = $this->urlencode_address( $instance['address'] );
+ $path = 'https://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=' . $address;
+ /** This action is documented in modules/widgets/contact-info.php */
+ $key = apply_filters( 'jetpack_google_maps_api_key', $instance['apikey'] );
+
+ if ( ! empty( $key ) ) {
+ $path = add_query_arg( 'key', $key, $path );
+ }
+ $json = wp_remote_retrieve_body( wp_remote_get( esc_url( $path, null, null ) ) );
+
+ if ( ! $json ) {
+ // The read failed :(
+ esc_html_e( 'There was a problem getting the data to display this address on a map. Please refresh your browser and try again.', 'jetpack' );
+ die();
+ }
+
+ $json_obj = json_decode( $json );
+
+ if ( 'ZERO_RESULTS' == $json_obj->status ) {
+ // The address supplied does not have a matching lat / lon.
+ // No map is available.
+ $instance['lat'] = '0';
+ $instance['lon'] = '0';
+ } else {
+
+ $loc = $json_obj->results[0]->geometry->location;
+
+ $lat = floatval( $loc->lat );
+ $lon = floatval( $loc->lng );
+
+ $instance['lat'] = "$lat";
+ $instance['lon'] = "$lon";
+ }
+ }
+
+ if ( ! isset( $new_instance['showmap'] ) ) {
+ $instance['showmap'] = 0;
+ } else {
+ $instance['showmap'] = intval( $new_instance['showmap'] );
+ }
+
+ return $instance;
+ }
+
+
+ /**
+ * Displays the form for this widget on the Widgets page of the WP Admin area.
+ *
+ * @param array $instance Instance configuration.
+ *
+ * @return void
+ */
+ function form( $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults() );
+ wp_enqueue_script(
+ 'contact-info-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/contact-info/contact-info-admin.min.js',
+ 'modules/widgets/contact-info/contact-info-admin.js'
+ ),
+ array( 'jquery' ),
+ 20160727
+ );
+
+ ?>
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'address' ) ); ?>"><?php esc_html_e( 'Address:', 'jetpack' ); ?></label>
+ <textarea class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'address' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'address' ) ); ?>"><?php echo esc_textarea( $instance['address'] ); ?></textarea>
+ <?php
+ if ( $this->has_good_map( $instance ) ) {
+ ?>
+ <input class="jp-contact-info-showmap" id="<?php echo esc_attr( $this->get_field_id( 'showmap' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'showmap' ) ); ?>" value="1" type="checkbox" <?php checked( $instance['showmap'], 1 ); ?> />
+ <label for="<?php echo esc_attr( $this->get_field_id( 'showmap' ) ); ?>"><?php esc_html_e( 'Show map', 'jetpack' ); ?></label>
+ <?php
+ } else {
+ ?>
+ <span class="error-message"><?php _e( 'Sorry. We can not plot this address. A map will not be displayed. Is the address formatted correctly?', 'jetpack' ); ?></span>
+ <input id="<?php echo esc_attr( $this->get_field_id( 'showmap' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'showmap' ) ); ?>" value="<?php echo( intval( $instance['showmap'] ) ); ?>" type="hidden" />
+ <?php
+ }
+ ?>
+ </p>
+
+ <p class="jp-contact-info-apikey" style="<?php echo $instance['showmap'] ? '' : 'display: none;'; ?>">
+ <label for="<?php echo esc_attr( $this->get_field_id( 'apikey' ) ); ?>">
+ <?php _e( 'Google Maps API Key', 'jetpack' ); ?>
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'apikey' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'apikey' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['apikey'] ); ?>" />
+ <br />
+ <small><?php printf( wp_kses( __( 'Google now requires an API key to use their maps on your site. <a href="%s">See our documentation</a> for instructions on acquiring a key.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ), 'https://jetpack.com/support/extra-sidebar-widgets/contact-info-widget/' ); ?></small>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'phone' ) ); ?>"><?php esc_html_e( 'Phone:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'phone' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'phone' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['phone'] ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'email' ) ); ?>"><?php esc_html_e( 'Email Address:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'email' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'email' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['email'] ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'hours' ) ); ?>"><?php esc_html_e( 'Hours:', 'jetpack' ); ?></label>
+ <textarea class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'hours' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'hours' ) ); ?>"><?php echo esc_textarea( $instance['hours'] ); ?></textarea>
+ </p>
+
+ <?php
+ }
+
+
+ /**
+ * Generate a Google Maps link for the supplied address.
+ *
+ * @param string $address Address to link to.
+ *
+ * @return string
+ */
+ function build_map_link( $address ) {
+ // Google map urls have lots of available params but zoom (z) and query (q) are enough.
+ return 'https://maps.google.com/maps?z=16&q=' . $this->urlencode_address( $address );
+ }
+
+
+ /**
+ * Builds map display HTML code from the supplied latitude and longitude.
+ *
+ * @param string $address Address.
+ * @param string $api_key API Key.
+ *
+ * @return string HTML of the map.
+ */
+ function build_map( $address, $api_key = null ) {
+ $this->enqueue_scripts();
+ $src = add_query_arg( 'q', rawurlencode( $address ), 'https://www.google.com/maps/embed/v1/place' );
+ if ( ! empty( $api_key ) ) {
+ $src = add_query_arg( 'key', $api_key, $src );
+ }
+
+ $height = 216;
+
+ $iframe_attributes = sprintf(
+ ' height="%d" frameborder="0" src="%s" class="contact-map"',
+ esc_attr( $height ),
+ esc_url( $src )
+ );
+
+ $iframe_html = sprintf( '<iframe width="600" %s></iframe>', $iframe_attributes );
+
+ if (
+ ! class_exists( 'Jetpack_AMP_Support' )
+ || ! Jetpack_AMP_Support::is_amp_request()
+ ) {
+ return $iframe_html;
+ }
+
+ $amp_iframe_html = sprintf( '<amp-iframe layout="fixed-height" width="auto" sandbox="allow-scripts allow-same-origin" %s>', $iframe_attributes );
+
+ // Add placeholder to avoid AMP error: <amp-iframe> elements must be positioned outside the first 75% of the viewport or 600px from the top (whichever is smaller).
+ $amp_iframe_html .= sprintf( '<span placeholder>%s</span>', esc_html__( 'Loading map&hellip;', 'jetpack' ) );
+
+ // Add original iframe as fallback in case JavaScript is disabled.
+ $amp_iframe_html .= sprintf( '<noscript>%s</noscript>', $iframe_html );
+
+ $amp_iframe_html .= '</amp-iframe>';
+ return $amp_iframe_html;
+ }
+
+ /**
+ * Encode an URL
+ *
+ * @param string $address The URL to encode
+ *
+ * @return string The encoded URL
+ */
+ function urlencode_address( $address ) {
+
+ $address = strtolower( $address );
+ $address = preg_replace( '/\s+/', ' ', trim( $address ) ); // Get rid of any unwanted whitespace
+ $address = str_ireplace( ' ', '+', $address ); // Use + not %20
+ return urlencode( $address );
+ }
+
+ /**
+ * Check if the instance has a valid Map location.
+ *
+ * @param array $instance Widget instance configuration.
+ *
+ * @return bool Whether or not there is a valid map.
+ */
+ function has_good_map( $instance ) {
+ // The lat and lon of an address that could not be plotted will have values of 0 and 0.
+ return ! ( '0' == $instance['lat'] && '0' == $instance['lon'] );
+ }
+
+ }
+
+}
diff --git a/plugins/jetpack/modules/widgets/contact-info/contact-info-admin.js b/plugins/jetpack/modules/widgets/contact-info/contact-info-admin.js
new file mode 100644
index 00000000..f51dccda
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/contact-info/contact-info-admin.js
@@ -0,0 +1,11 @@
+( function( $ ) {
+ $( document ).on( 'change', '.jp-contact-info-showmap', function() {
+ var $checkbox = $( this ),
+ isChecked = $checkbox.is( ':checked' );
+
+ $checkbox
+ .closest( '.widget' )
+ .find( '.jp-contact-info-apikey' )
+ .toggle( isChecked );
+ } );
+} )( window.jQuery );
diff --git a/plugins/jetpack/modules/widgets/contact-info/contact-info-map.css b/plugins/jetpack/modules/widgets/contact-info/contact-info-map.css
new file mode 100644
index 00000000..7aa9e698
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/contact-info/contact-info-map.css
@@ -0,0 +1,4 @@
+.contact-map {
+ max-width: 100%;
+ border: 0;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/customizer-controls.css b/plugins/jetpack/modules/widgets/customizer-controls.css
new file mode 100644
index 00000000..847bc2f3
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/customizer-controls.css
@@ -0,0 +1,6 @@
+/**
+ * Utilities to stylize widget in Customizer controls.
+ */
+
+/* My Community */
+#available-widgets [class*="community"] .widget-title:before { content: "\f307"; } \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/customizer-utils.js b/plugins/jetpack/modules/widgets/customizer-utils.js
new file mode 100644
index 00000000..754b6339
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/customizer-utils.js
@@ -0,0 +1,119 @@
+/* global wp, gapi, FB, twttr, PaypalExpressCheckout */
+
+/**
+ * Utilities to work with widgets in Customizer.
+ */
+
+/**
+ * Checks whether this Customizer supports partial widget refresh.
+ * @returns {boolean}
+ */
+wp.customizerHasPartialWidgetRefresh = function() {
+ return (
+ 'object' === typeof wp &&
+ 'function' === typeof wp.customize &&
+ 'object' === typeof wp.customize.selectiveRefresh &&
+ 'object' === typeof wp.customize.widgetsPreview &&
+ 'function' === typeof wp.customize.widgetsPreview.WidgetPartial
+ );
+};
+
+/**
+ * Verifies that the placed widget ID contains the widget name.
+ * @param {object} placement
+ * @param {string} widgetName
+ * @returns {*|boolean}
+ */
+wp.isJetpackWidgetPlaced = function( placement, widgetName ) {
+ return placement.partial.widgetId && 0 === placement.partial.widgetId.indexOf( widgetName );
+};
+
+/**
+ * Bind events for selective refresh in Customizer.
+ */
+( function( $ ) {
+ $( document ).ready( function() {
+ if ( wp && wp.customize && wp.customizerHasPartialWidgetRefresh() ) {
+ // Refresh widget contents when a partial is rendered.
+ wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
+ if ( placement.container ) {
+ // Refresh Google+
+ if (
+ wp.isJetpackWidgetPlaced( placement, 'googleplus-badge' ) &&
+ 'object' === typeof gapi &&
+ gapi.person &&
+ 'function' === typeof gapi.person.go
+ ) {
+ gapi.person.go( placement.container[ 0 ] );
+ }
+
+ // Refresh Facebook XFBML
+ else if (
+ wp.isJetpackWidgetPlaced( placement, 'facebook-likebox' ) &&
+ 'object' === typeof FB &&
+ 'object' === typeof FB.XFBML &&
+ 'function' === typeof FB.XFBML.parse
+ ) {
+ FB.XFBML.parse( placement.container[ 0 ], function() {
+ var $fbContainer = $( placement.container[ 0 ] ).find( '.fb_iframe_widget' ),
+ fbWidth = $fbContainer.data( 'width' ),
+ fbHeight = $fbContainer.data( 'height' );
+ $fbContainer.find( 'span' ).css( { width: fbWidth, height: fbHeight } );
+ setTimeout( function() {
+ $fbContainer
+ .find( 'iframe' )
+ .css( { width: fbWidth, height: fbHeight, position: 'relative' } );
+ }, 1 );
+ } );
+ }
+
+ // Refresh Twitter
+ else if (
+ wp.isJetpackWidgetPlaced( placement, 'twitter_timeline' ) &&
+ 'object' === typeof twttr &&
+ 'object' === typeof twttr.widgets &&
+ 'function' === typeof twttr.widgets.load
+ ) {
+ twttr.widgets.load( placement.container[ 0 ] );
+ } else if ( wp.isJetpackWidgetPlaced( placement, 'eu_cookie_law_widget' ) ) {
+ // Refresh EU Cookie Law
+ if ( $( '#eu-cookie-law' ).hasClass( 'top' ) ) {
+ $( '.widget_eu_cookie_law_widget' ).addClass( 'top' );
+ } else {
+ $( '.widget_eu_cookie_law_widget' ).removeClass( 'top' );
+ }
+ placement.container.fadeIn();
+ } else if ( wp.isJetpackWidgetPlaced( placement, 'jetpack_simple_payments_widget' ) ) {
+ // Refresh Simple Payments Widget
+ try {
+ var buttonId = $( '.jetpack-simple-payments-button', placement.container )
+ .attr( 'id' )
+ .replace( '_button', '' );
+ PaypalExpressCheckout.renderButton( null, null, buttonId, null );
+ } catch ( e ) {
+ // PaypalExpressCheckout may fail.
+ // For the same usage, see also:
+ // https://github.com/Automattic/jetpack/blob/6c1971e6bed7d3df793392a7a58ffe0afaeeb5fe/modules/simple-payments/simple-payments.php#L111
+ }
+ }
+ }
+ } );
+
+ // Refresh widgets when they're moved.
+ wp.customize.selectiveRefresh.bind( 'partial-content-moved', function( placement ) {
+ if ( placement.container ) {
+ // Refresh Twitter timeline iframe, since it has to be re-built.
+ if (
+ wp.isJetpackWidgetPlaced( placement, 'twitter_timeline' ) &&
+ placement.container.find( 'iframe.twitter-timeline:not([src]):first' ).length
+ ) {
+ placement.partial.refresh();
+ } else if ( wp.isJetpackWidgetPlaced( placement, 'jetpack_simple_payments_widget' ) ) {
+ // Refresh Simple Payments Widget
+ placement.partial.refresh();
+ }
+ }
+ } );
+ }
+ } );
+} )( jQuery );
diff --git a/plugins/jetpack/modules/widgets/eu-cookie-law.php b/plugins/jetpack/modules/widgets/eu-cookie-law.php
new file mode 100644
index 00000000..62acda4a
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/eu-cookie-law.php
@@ -0,0 +1,301 @@
+<?php
+
+/**
+ * Disable direct access/execution to/of the widget code.
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+if ( ! class_exists( 'Jetpack_EU_Cookie_Law_Widget' ) ) {
+ /**
+ * EU Cookie Law Widget
+ *
+ * Display the EU Cookie Law banner in the bottom part of the screen.
+ */
+ class Jetpack_EU_Cookie_Law_Widget extends WP_Widget {
+ /**
+ * EU Cookie Law cookie name.
+ *
+ * @var string
+ */
+ public static $cookie_name = 'eucookielaw';
+
+ /**
+ * Default hide options.
+ *
+ * @var array
+ */
+ private $hide_options = array(
+ 'button',
+ 'scroll',
+ 'time',
+ );
+
+ /**
+ * Default text options.
+ *
+ * @var array
+ */
+ private $text_options = array(
+ 'default',
+ 'custom',
+ );
+
+ /**
+ * Default color scheme options.
+ *
+ * @var array
+ */
+ private $color_scheme_options = array(
+ 'default',
+ 'negative',
+ );
+
+ /**
+ * Default policy URL options.
+ *
+ * @var array
+ */
+ private $policy_url_options = array(
+ 'default',
+ 'custom',
+ );
+
+ /**
+ * Widget position options.
+ *
+ * @var array
+ */
+ private $position_options = array(
+ 'bottom',
+ 'top',
+ );
+
+ /**
+ * Constructor.
+ */
+ function __construct() {
+ parent::__construct(
+ 'eu_cookie_law_widget',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'Cookies & Consents Banner', 'jetpack' ) ),
+ array(
+ 'description' => esc_html__( 'Display a banner for EU Cookie Law and GDPR compliance.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ ),
+ array()
+ );
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
+ }
+ }
+
+ /**
+ * Enqueue scripts and styles.
+ */
+ function enqueue_frontend_scripts() {
+ wp_enqueue_style( 'eu-cookie-law-style', plugins_url( 'eu-cookie-law/style.css', __FILE__ ), array(), '20170403' );
+ wp_enqueue_script(
+ 'eu-cookie-law-script',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/eu-cookie-law/eu-cookie-law.min.js',
+ 'modules/widgets/eu-cookie-law/eu-cookie-law.js'
+ ),
+ array( 'jquery' ),
+ '20180522',
+ true
+ );
+ }
+
+ /**
+ * Return an associative array of default values.
+ *
+ * These values are used in new widgets.
+ *
+ * @return array Default values for the widget options.
+ */
+ public function defaults() {
+ return array(
+ 'hide' => $this->hide_options[0],
+ 'hide-timeout' => 30,
+ 'consent-expiration' => 180,
+ 'text' => $this->text_options[0],
+ 'customtext' => '',
+ 'color-scheme' => $this->color_scheme_options[0],
+ 'policy-url' => get_option( 'wp_page_for_privacy_policy' ) ? $this->policy_url_options[1] : $this->policy_url_options[0],
+ 'default-policy-url' => 'https://automattic.com/cookies/',
+ 'custom-policy-url' => get_option( 'wp_page_for_privacy_policy' ) ? get_permalink( (int) get_option( 'wp_page_for_privacy_policy' ) ) : '',
+ 'position' => $this->position_options[0],
+ 'policy-link-text' => esc_html__( 'Cookie Policy', 'jetpack' ),
+ 'button' => esc_html__( 'Close and accept', 'jetpack' ),
+ 'default-text' => esc_html__( "Privacy & Cookies: This site uses cookies. By continuing to use this website, you agree to their use. \r\nTo find out more, including how to control cookies, see here:", 'jetpack' ),
+ );
+ }
+
+ /**
+ * Front-end display of the widget.
+ *
+ * @param array $args Widget arguments.
+ * @param array $instance Saved values from database.
+ */
+ public function widget( $args, $instance ) {
+ /**
+ * Filters the display of the EU Cookie Law widget.
+ *
+ * @since 6.1.1
+ *
+ * @param bool true Should the EU Cookie Law widget be disabled. Default to false.
+ */
+ if ( apply_filters( 'jetpack_disable_eu_cookie_law_widget', false ) ) {
+ return;
+ }
+
+ $instance = wp_parse_args( $instance, $this->defaults() );
+
+ $classes = array();
+ $classes['hide'] = 'hide-on-' . esc_attr( $instance['hide'] );
+ if ( 'negative' === $instance['color-scheme'] ) {
+ $classes['negative'] = 'negative';
+ }
+
+ if ( 'top' === $instance['position'] ) {
+ $classes['top'] = 'top';
+ }
+
+ if ( Jetpack::is_module_active( 'wordads' ) ) {
+ $classes['ads'] = 'ads-active';
+ $classes['hide'] = 'hide-on-button';
+ }
+
+ echo $args['before_widget'];
+ require( dirname( __FILE__ ) . '/eu-cookie-law/widget.php' );
+ echo $args['after_widget'];
+ /** This action is already documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'eu_cookie_law' );
+ }
+
+ /**
+ * Back-end widget form.
+ *
+ * @param array $instance Previously saved values from database.
+ */
+ public function form( $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults() );
+ if ( Jetpack::is_module_active( 'wordads' ) ) {
+ $instance['hide'] = 'button';
+ }
+
+ wp_enqueue_script(
+ 'eu-cookie-law-widget-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/eu-cookie-law/eu-cookie-law-admin.min.js',
+ 'modules/widgets/eu-cookie-law/eu-cookie-law-admin.js'
+ ),
+ array( 'jquery' ),
+ 20180417
+ );
+
+ require( dirname( __FILE__ ) . '/eu-cookie-law/form.php' );
+ }
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ * @return array Updated safe values to be saved.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $instance = array();
+ $defaults = $this->defaults();
+
+ $instance['hide'] = $this->filter_value( isset( $new_instance['hide'] ) ? $new_instance['hide'] : '', $this->hide_options );
+ $instance['text'] = $this->filter_value( isset( $new_instance['text'] ) ? $new_instance['text'] : '', $this->text_options );
+ $instance['color-scheme'] = $this->filter_value( isset( $new_instance['color-scheme'] ) ? $new_instance['color-scheme'] : '', $this->color_scheme_options );
+ $instance['policy-url'] = $this->filter_value( isset( $new_instance['policy-url'] ) ? $new_instance['policy-url'] : '', $this->policy_url_options );
+ $instance['position'] = $this->filter_value( isset( $new_instance['position'] ) ? $new_instance['position'] : '', $this->position_options );
+
+ if ( isset( $new_instance['hide-timeout'] ) ) {
+ // Time can be a value between 3 and 1000 seconds.
+ $instance['hide-timeout'] = min( 1000, max( 3, intval( $new_instance['hide-timeout'] ) ) );
+ }
+
+ if ( isset( $new_instance['consent-expiration'] ) ) {
+ // Time can be a value between 1 and 365 days.
+ $instance['consent-expiration'] = min( 365, max( 1, intval( $new_instance['consent-expiration'] ) ) );
+ }
+
+ if ( isset( $new_instance['customtext'] ) ) {
+ $instance['customtext'] = mb_substr( wp_kses( $new_instance['customtext'], array() ), 0, 4096 );
+ } else {
+ $instance['text'] = $this->text_options[0];
+ }
+
+ if ( isset( $new_instance['policy-url'] ) ) {
+ $instance['policy-url'] = 'custom' === $new_instance['policy-url']
+ ? 'custom'
+ : 'default';
+ } else {
+ $instance['policy-url'] = $this->policy_url_options[0];
+ }
+
+ if ( 'custom' === $instance['policy-url'] && isset( $new_instance['custom-policy-url'] ) ) {
+ $instance['custom-policy-url'] = esc_url( $new_instance['custom-policy-url'], array( 'http', 'https' ) );
+
+ if ( strlen( $instance['custom-policy-url'] ) < 10 ) {
+ unset( $instance['custom-policy-url'] );
+ global $wp_customize;
+ if ( ! isset( $wp_customize ) ) {
+ $instance['policy-url'] = $this->policy_url_options[0];
+ }
+ }
+ }
+
+ if ( isset( $new_instance['policy-link-text'] ) ) {
+ $instance['policy-link-text'] = trim( mb_substr( wp_kses( $new_instance['policy-link-text'], array() ), 0, 100 ) );
+ }
+
+ if ( empty( $instance['policy-link-text'] ) || $instance['policy-link-text'] == $defaults['policy-link-text'] ) {
+ unset( $instance['policy-link-text'] );
+ }
+
+ if ( isset( $new_instance['button'] ) ) {
+ $instance['button'] = trim( mb_substr( wp_kses( $new_instance['button'], array() ), 0, 100 ) );
+ }
+
+ if ( empty( $instance['button'] ) || $instance['button'] == $defaults['button'] ) {
+ unset( $instance['button'] );
+ }
+
+ // Show the banner again if a setting has been changed.
+ setcookie( self::$cookie_name, '', time() - 86400, '/' );
+
+ return $instance;
+ }
+
+ /**
+ * Check if the value is allowed and not empty.
+ *
+ * @param string $value Value to check.
+ * @param array $allowed Array of allowed values.
+ *
+ * @return string $value if pass the check or first value from allowed values.
+ */
+ function filter_value( $value, $allowed = array() ) {
+ $allowed = (array) $allowed;
+ if ( empty( $value ) || ( ! empty( $allowed ) && ! in_array( $value, $allowed ) ) ) {
+ $value = $allowed[0];
+ }
+ return $value;
+ }
+ }
+
+ // Register Jetpack_EU_Cookie_Law_Widget widget.
+ function jetpack_register_eu_cookie_law_widget() {
+ register_widget( 'Jetpack_EU_Cookie_Law_Widget' );
+ };
+
+ add_action( 'widgets_init', 'jetpack_register_eu_cookie_law_widget' );
+}
diff --git a/plugins/jetpack/modules/widgets/eu-cookie-law/eu-cookie-law-admin.js b/plugins/jetpack/modules/widgets/eu-cookie-law/eu-cookie-law-admin.js
new file mode 100644
index 00000000..6d3dd42e
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/eu-cookie-law/eu-cookie-law-admin.js
@@ -0,0 +1,32 @@
+/* eslint no-var: 0 */
+
+( function( $ ) {
+ var $document = $( document );
+
+ $document.on( 'ready', function() {
+ var maybeShowNotice = function( e, policyUrl ) {
+ var $policyUrl = $( policyUrl || this ).closest( '.eu-cookie-law-widget-policy-url' );
+
+ if ( $policyUrl.find( 'input[type="radio"][value="default"]' ).is( ':checked' ) ) {
+ $policyUrl.find( '.notice.default-policy' ).css( 'display', 'block' );
+ $policyUrl.find( '.notice.custom-policy' ).hide();
+ } else {
+ $policyUrl.find( '.notice.default-policy' ).hide();
+ $policyUrl.find( '.notice.custom-policy' ).css( 'display', 'block' );
+ }
+ };
+
+ $document.on(
+ 'click',
+ '.eu-cookie-law-widget-policy-url input[type="radio"]',
+ maybeShowNotice
+ );
+ $document.on( 'widget-updated widget-added', function( e, widget ) {
+ var widgetId = $( widget ).attr( 'id' );
+ if ( widgetId.indexOf( 'eu_cookie_law_widget' ) !== -1 ) {
+ maybeShowNotice( null, $( '#' + widgetId + ' .eu-cookie-law-widget-policy-url' ) );
+ }
+ } );
+ $( '.eu-cookie-law-widget-policy-url' ).each( maybeShowNotice );
+ } );
+} )( jQuery );
diff --git a/plugins/jetpack/modules/widgets/eu-cookie-law/eu-cookie-law.js b/plugins/jetpack/modules/widgets/eu-cookie-law/eu-cookie-law.js
new file mode 100644
index 00000000..7989ee69
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/eu-cookie-law/eu-cookie-law.js
@@ -0,0 +1,80 @@
+( function( $ ) {
+ var cookieValue = document.cookie.replace(
+ /(?:(?:^|.*;\s*)eucookielaw\s*\=\s*([^;]*).*$)|^.*$/,
+ '$1'
+ ),
+ overlay = $( '#eu-cookie-law' ),
+ initialScrollPosition,
+ scrollFunction;
+
+ if ( overlay.hasClass( 'top' ) ) {
+ $( '.widget_eu_cookie_law_widget' ).addClass( 'top' );
+ }
+
+ if ( overlay.hasClass( 'ads-active' ) ) {
+ var adsCookieValue = document.cookie.replace(
+ /(?:(?:^|.*;\s*)personalized-ads-consent\s*\=\s*([^;]*).*$)|^.*$/,
+ '$1'
+ );
+ if ( '' !== cookieValue && '' !== adsCookieValue ) {
+ overlay.remove();
+ }
+ } else if ( '' !== cookieValue ) {
+ overlay.remove();
+ }
+
+ $( '.widget_eu_cookie_law_widget' )
+ .appendTo( 'body' )
+ .fadeIn();
+
+ overlay.find( 'form' ).on( 'submit', accept );
+
+ if ( overlay.hasClass( 'hide-on-scroll' ) ) {
+ initialScrollPosition = $( window ).scrollTop();
+ scrollFunction = function() {
+ if ( Math.abs( $( window ).scrollTop() - initialScrollPosition ) > 50 ) {
+ accept();
+ }
+ };
+ $( window ).on( 'scroll', scrollFunction );
+ } else if ( overlay.hasClass( 'hide-on-time' ) ) {
+ setTimeout( accept, overlay.data( 'hide-timeout' ) * 1000 );
+ }
+
+ var accepted = false;
+ function accept( event ) {
+ if ( accepted ) {
+ return;
+ }
+ accepted = true;
+
+ if ( event && event.preventDefault ) {
+ event.preventDefault();
+ }
+
+ if ( overlay.hasClass( 'hide-on-scroll' ) ) {
+ $( window ).off( 'scroll', scrollFunction );
+ }
+
+ var expireTime = new Date();
+ expireTime.setTime(
+ expireTime.getTime() + overlay.data( 'consent-expiration' ) * 24 * 60 * 60 * 1000
+ );
+
+ document.cookie =
+ 'eucookielaw=' + expireTime.getTime() + ';path=/;expires=' + expireTime.toGMTString();
+ if ( overlay.hasClass( 'ads-active' ) && overlay.hasClass( 'hide-on-button' ) ) {
+ document.cookie =
+ 'personalized-ads-consent=' +
+ expireTime.getTime() +
+ ';path=/;expires=' +
+ expireTime.toGMTString();
+ }
+
+ overlay.fadeOut( 400, function() {
+ overlay.remove();
+ var widgetSection = document.querySelector( '.widget.widget_eu_cookie_law_widget' );
+ widgetSection.parentNode.removeChild( widgetSection );
+ } );
+ }
+} )( jQuery );
diff --git a/plugins/jetpack/modules/widgets/eu-cookie-law/form.php b/plugins/jetpack/modules/widgets/eu-cookie-law/form.php
new file mode 100644
index 00000000..7b00877b
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/eu-cookie-law/form.php
@@ -0,0 +1,277 @@
+<p>
+ <strong>
+ <?php esc_html_e( 'Banner text', 'jetpack' ); ?>
+ </strong>
+ <ul>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['text'], 'default' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>"
+ type="radio"
+ value="default"
+ />
+ <?php esc_html_e( 'Default', 'jetpack' ); ?>
+ </label>
+ </li>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['text'], 'custom' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>"
+ type="radio"
+ value="custom"
+ />
+ <?php esc_html_e( 'Custom:', 'jetpack' ); ?>
+ </label>
+ </li>
+ </ul>
+ <textarea
+ class="widefat"
+ name="<?php echo esc_attr( $this->get_field_name( 'customtext' ) ); ?>"
+ placeholder="<?php echo esc_attr( $instance['default-text'] ); ?>"
+ ><?php echo esc_html( $instance['customtext'] ); ?></textarea>
+</p>
+
+<hr />
+
+<p>
+ <strong>
+ <?php esc_html_e( 'Privacy Policy Link', 'jetpack' ); ?>
+ </strong>
+ <ul class="eu-cookie-law-widget-policy-url">
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['policy-url'], 'default' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'policy-url' ) ); ?>"
+ type="radio"
+ value="default"
+ />
+ <?php esc_html_e( 'Default', 'jetpack' ); ?>
+ </label>
+ </li>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['policy-url'], 'custom' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'policy-url' ) ); ?>"
+ type="radio"
+ value="custom"
+ />
+ <?php esc_html_e( 'Custom URL:', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ name="<?php echo esc_attr( $this->get_field_name( 'custom-policy-url' ) ); ?>"
+ placeholder="<?php echo esc_url( $instance['default-policy-url'] ); ?>"
+ style="margin-top: .5em;"
+ type="text"
+ value="<?php echo esc_url( $instance['custom-policy-url'] ); ?>"
+ />
+ <span class="notice notice-warning default-policy" style="display: none;">
+ <span style="display: block; margin: .5em 0;">
+ <strong><?php esc_html_e( 'Caution:', 'jetpack' ); ?></strong>
+ <?php esc_html_e( 'The default policy URL only covers cookies set by Jetpack. If you’re running other plugins, custom cookies, or third-party tracking technologies, you should create and link to your own cookie statement.', 'jetpack' ); ?>
+ </span>
+ </span>
+ <?php if ( Jetpack::is_module_active( 'wordads' ) ) : ?>
+ <span class="notice notice-warning custom-policy" style="display: none;">
+ <span style="display: block; margin: .5em 0;">
+ <strong><?php esc_html_e( 'Caution:', 'jetpack' ); ?></strong>
+ <?php echo sprintf(
+ __( 'For GDPR compliance, please make sure your policy contains <a href="%s" target="_blank">privacy information relating to Jetpack Ads</a>.', 'jetpack' ),
+ esc_url( 'https://jetpack.com/support/ads/#privacy' )
+ ); ?>
+ </span>
+ </span>
+ <?php endif; ?>
+ </li>
+ </ul>
+</p>
+
+<p>
+ <strong>
+ <?php esc_html_e( 'Link text', 'jetpack' ); ?>
+ </strong>
+ <label>
+ <input
+ class="widefat"
+ name="<?php echo $this->get_field_name( 'policy-link-text' ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['policy-link-text'] ); ?>"
+ />
+ </label>
+</p>
+
+<hr />
+
+<p>
+ <strong>
+ <?php esc_html_e( 'Button text', 'jetpack' ); ?>
+ </strong>
+ <label>
+ <input
+ class="widefat"
+ name="<?php echo $this->get_field_name( 'button' ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['button'] ); ?>"
+ />
+ </label>
+</p>
+
+<hr />
+
+<p>
+ <strong>
+ <?php _ex( 'Capture consent & hide the banner', 'action', 'jetpack' ); ?>
+ </strong>
+ <ul>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['hide'], 'button' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'hide' ) ); ?>"
+ type="radio"
+ value="button"
+ <?php echo Jetpack::is_module_active( 'wordads' ) ? 'disabled' : ''; ?>
+ />
+ <?php esc_html_e( 'after the user clicks the dismiss button', 'jetpack' ); ?>
+ </label>
+ </li>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['hide'], 'scroll' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'hide' ) ); ?>"
+ type="radio"
+ value="scroll"
+ <?php echo Jetpack::is_module_active( 'wordads' ) ? 'disabled' : ''; ?>
+ />
+ <?php esc_html_e( 'after the user scrolls the page', 'jetpack' ); ?>
+ </label>
+ </li>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['hide'], 'time' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'hide' ) ); ?>"
+ type="radio"
+ value="time"
+ <?php echo Jetpack::is_module_active( 'wordads' ) ? 'disabled' : ''; ?>
+ />
+ <?php esc_html_e( 'after this amount of time', 'jetpack' ); ?>
+ </label>
+ <input
+ max="1000"
+ min="3"
+ name="<?php echo esc_attr( $this->get_field_name( 'hide-timeout' ) ); ?>"
+ style="padding: 3px 5px; width: 3em;"
+ type="number"
+ value="<?php echo esc_attr( $instance['hide-timeout'] ); ?>"
+ />
+ <?php esc_html_e( 'seconds', 'jetpack' ); ?>
+ </li>
+ </ul>
+ <?php if ( Jetpack::is_module_active( 'wordads' ) ) : ?>
+ <span class="notice notice-warning" style="display: block;">
+ <span style="display: block; margin: .5em 0;">
+ <?php esc_html_e( 'Visitors must provide consent by clicking the dismiss button when Jetpack Ads is turned on.', 'jetpack' ); ?>
+ </span>
+ </span>
+ <?php endif; ?>
+</p>
+
+<hr />
+
+<p>
+ <strong>
+ <?php _ex( 'Consent expires after', 'action', 'jetpack' ); ?>
+ </strong>
+ <ul>
+ <li>
+ <input
+ max="365"
+ min="1"
+ name="<?php echo esc_attr( $this->get_field_name( 'consent-expiration' ) ); ?>"
+ style="padding: 3px 5px; width: 3.75em;"
+ type="number"
+ value="<?php echo esc_attr( $instance['consent-expiration'] ); ?>"
+ />
+ <?php esc_html_e( 'days', 'jetpack' ); ?>
+ </li>
+ </ul>
+</p>
+
+<hr />
+
+<p>
+ <strong>
+ <?php _e( 'Color scheme', 'jetpack' ); ?>
+ </strong>
+ <ul>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['color-scheme'], 'default' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'color-scheme' ) ); ?>"
+ type="radio"
+ value="default"
+ />
+ <?php esc_html_e( 'Light', 'jetpack' ); ?>
+ </label>
+ </li>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['color-scheme'], 'negative' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'color-scheme' ) ); ?>"
+ type="radio"
+ value="negative"
+ />
+ <?php esc_html_e( 'Dark', 'jetpack' ); ?>
+ </label>
+ </li>
+ </ul>
+</p>
+
+<hr />
+
+<p>
+ <strong>
+ <?php _e( 'Position', 'jetpack' ); ?>
+ </strong>
+ <ul>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['position'], 'bottom' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'position' ) ); ?>"
+ type="radio"
+ value="bottom"
+ />
+ <?php esc_html_e( 'Bottom', 'jetpack' ); ?>
+ </label>
+ </li>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['position'], 'top' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'position' ) ); ?>"
+ type="radio"
+ value="top"
+ />
+ <?php esc_html_e( 'Top', 'jetpack' ); ?>
+ </label>
+ </li>
+ </ul>
+</p>
+
+<hr />
+
+<p class="small">
+ <?php esc_html_e( 'It is your own responsibility to ensure that your site complies with the relevant laws.', 'jetpack' ); ?>
+ <a href="https://jetpack.com/support/extra-sidebar-widgets/eu-cookie-law-widget/">
+ <?php esc_html_e( 'Click here for more information', 'jetpack' ); ?>
+ </a>
+</p>
diff --git a/plugins/jetpack/modules/widgets/eu-cookie-law/style.css b/plugins/jetpack/modules/widgets/eu-cookie-law/style.css
new file mode 100644
index 00000000..07d5a9f6
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/eu-cookie-law/style.css
@@ -0,0 +1,105 @@
+.widget_eu_cookie_law_widget {
+ border: none;
+ bottom: 1em;
+ display: none;
+ left: 1em;
+ margin: 0;
+ padding: 0;
+ position: fixed;
+ right: 1em;
+ width: auto;
+ z-index: 50001;
+}
+
+.widget_eu_cookie_law_widget.widget.top {
+ bottom: auto;
+ top: 1em;
+}
+
+.admin-bar .widget_eu_cookie_law_widget.widget.top {
+ top: 3em;
+}
+
+#eu-cookie-law {
+ background-color: #fff;
+ border: 1px solid #dedede;
+ color: #2e4467;
+ font-size: 12px;
+ line-height: 1.5;
+ overflow: hidden;
+ padding: 6px 6px 6px 15px;
+ position: relative;
+}
+
+#eu-cookie-law a,
+#eu-cookie-law a:active,
+#eu-cookie-law a:visited {
+ color: inherit;
+ cursor: inherit;
+ text-decoration: underline;
+}
+
+#eu-cookie-law a:hover {
+ cursor: pointer;
+ text-decoration: none;
+}
+
+#eu-cookie-law.negative {
+ background-color: #000;
+ border: none;
+ color: #fff;
+}
+
+/**
+ * Using a highly-specific rule to make sure that certain form styles
+ * will be reset
+ */
+#eu-cookie-law form {
+ margin-bottom: 0;
+ position: static;
+}
+
+/**
+ * Using a highly-specific rule to make sure that all button styles
+ * will be reset
+ */
+#eu-cookie-law input,
+#eu-cookie-law input:hover,
+#eu-cookie-law input:focus {
+ background: #f3f3f3;
+ border: 1px solid #dedede;
+ border-radius: 4px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline;
+ float: right;
+ font-family: inherit;
+ font-size: 14px;
+ font-weight: inherit;
+ line-height: inherit;
+ margin: 0 0 0 5%;
+ padding: 8px 12px;
+ position: static;
+ text-transform: none;
+}
+
+#eu-cookie-law.negative input,
+#eu-cookie-law.negative input:hover,
+#eu-cookie-law.negative input:focus {
+ background: #282828;
+ border-color: #535353;
+ color: #fff;
+}
+
+@media ( max-width: 600px ) {
+ #eu-cookie-law {
+ padding-bottom: 55px;
+ }
+ #eu-cookie-law input.accept {
+ bottom: 8px;
+ position: absolute;
+ right: 8px;
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/eu-cookie-law/widget.php b/plugins/jetpack/modules/widgets/eu-cookie-law/widget.php
new file mode 100644
index 00000000..cd016a3e
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/eu-cookie-law/widget.php
@@ -0,0 +1,25 @@
+<div
+ class="<?php echo implode( ' ', $classes ); ?>"
+ data-hide-timeout="<?php echo intval( $instance['hide-timeout'] ); ?>"
+ data-consent-expiration="<?php echo intval( $instance['consent-expiration'] ); ?>"
+ id="eu-cookie-law"
+>
+ <form method="post">
+ <input type="submit" value="<?php echo esc_attr( $instance['button'] ); ?>" class="accept" />
+ </form>
+
+ <?php if ( 'default' == $instance['text'] || empty( $instance['customtext'] ) ) {
+ echo nl2br( $instance['default-text'] );
+ } else {
+ echo nl2br( esc_html( $instance['customtext'] ) );
+ } ?>
+
+ <a href="<?php
+ $policy_link_text = 'default' === $instance['policy-url'] || empty( $instance['custom-policy-url'] )
+ ? $instance['default-policy-url']
+ : $instance['custom-policy-url'];
+ echo esc_url( $policy_link_text );
+ ?>" >
+ <?php echo esc_html( $instance['policy-link-text'] ); ?>
+ </a>
+</div>
diff --git a/plugins/jetpack/modules/widgets/facebook-likebox.php b/plugins/jetpack/modules/widgets/facebook-likebox.php
new file mode 100644
index 00000000..5fbc23e0
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/facebook-likebox.php
@@ -0,0 +1,309 @@
+<?php
+
+/**
+ * Register the widget for use in Appearance -> Widgets
+ */
+add_action( 'widgets_init', 'jetpack_facebook_likebox_init' );
+
+function jetpack_facebook_likebox_init() {
+ register_widget( 'WPCOM_Widget_Facebook_LikeBox' );
+}
+
+/**
+ * Facebook Page Plugin (formerly known as the Like Box)
+ * Display a Facebook Page Plugin as a widget (replaces the old like box plugin)
+ * https://developers.facebook.com/docs/plugins/page-plugin
+ */
+class WPCOM_Widget_Facebook_LikeBox extends WP_Widget {
+
+ private $default_height = 580;
+ private $default_width = 340;
+ private $max_width = 500;
+ private $min_width = 180;
+ private $max_height = 9999;
+ private $min_height = 130;
+
+ function __construct() {
+ parent::__construct(
+ 'facebook-likebox',
+ /**
+ * Filter the name of a widget included in the Extra Sidebar Widgets module.
+ *
+ * @module widgets
+ *
+ * @since 2.1.2
+ *
+ * @param string $widget_name Widget title.
+ */
+ apply_filters( 'jetpack_widget_name', __( 'Facebook Page Plugin', 'jetpack' ) ),
+ array(
+ 'classname' => 'widget_facebook_likebox',
+ 'description' => __( 'Use the Facebook Page Plugin to connect visitors to your Facebook Page', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_active_widget( false, false, 'monster' ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+ }
+ }
+
+ /**
+ * Enqueue scripts.
+ */
+ public function enqueue_scripts() {
+ wp_enqueue_script( 'jetpack-facebook-embed' );
+ wp_enqueue_style( 'jetpack_facebook_likebox', plugins_url( 'facebook-likebox/style.css', __FILE__ ) );
+ wp_style_add_data( 'jetpack_facebook_likebox', 'jetpack-inline', true );
+ }
+
+ function widget( $args, $instance ) {
+ extract( $args );
+
+ $like_args = $this->normalize_facebook_args( $instance['like_args'] );
+
+ if ( empty( $like_args['href'] ) || ! $this->is_valid_facebook_url( $like_args['href'] ) ) {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ echo $before_widget;
+ echo '<p>' . sprintf( __( 'It looks like your Facebook URL is incorrectly configured. Please check it in your <a href="%s">widget settings</a>.', 'jetpack' ), admin_url( 'widgets.php' ) ) . '</p>';
+ echo $after_widget;
+ }
+ echo '<!-- Invalid Facebook Page URL -->';
+ return;
+ }
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+ $page_url = set_url_scheme( $like_args['href'], 'https' );
+
+ $like_args['show_faces'] = (bool) $like_args['show_faces'] ? 'true' : 'false';
+ $like_args['stream'] = (bool) $like_args['stream'] ? 'true' : 'false';
+ $like_args['cover'] = (bool) $like_args['cover'] ? 'false' : 'true';
+
+ echo $before_widget;
+
+ if ( ! empty( $title ) ) :
+ echo $before_title;
+
+ $likebox_widget_title = '<a href="' . esc_url( $page_url ) . '">' . esc_html( $title ) . '</a>';
+
+ /**
+ * Filter Facebook Likebox's widget title.
+ *
+ * @module widgets
+ *
+ * @since 3.3.0
+ *
+ * @param string $likebox_widget_title Likebox Widget title (including a link to the Page URL).
+ * @param string $title Widget title as set in the widget settings.
+ * @param string $page_url Facebook Page URL.
+ */
+ echo apply_filters( 'jetpack_facebook_likebox_title', $likebox_widget_title, $title, $page_url );
+
+ echo $after_title;
+ endif;
+
+ ?>
+ <div id="fb-root"></div>
+ <div class="fb-page" data-href="<?php echo esc_url( $page_url ); ?>" data-width="<?php echo intval( $like_args['width'] ); ?>" data-height="<?php echo intval( $like_args['height'] ); ?>" data-hide-cover="<?php echo esc_attr( $like_args['cover'] ); ?>" data-show-facepile="<?php echo esc_attr( $like_args['show_faces'] ); ?>" data-show-posts="<?php echo esc_attr( $like_args['stream'] ); ?>">
+ <div class="fb-xfbml-parse-ignore"><blockquote cite="<?php echo esc_url( $page_url ); ?>"><a href="<?php echo esc_url( $page_url ); ?>"><?php echo esc_html( $title ); ?></a></blockquote></div>
+ </div>
+ <?php
+ wp_enqueue_script( 'jetpack-facebook-embed' );
+ echo $after_widget;
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'facebook-likebox' );
+ }
+
+ function update( $new_instance, $old_instance ) {
+ $instance = array(
+ 'title' => '',
+ 'like_args' => $this->get_default_args(),
+ );
+
+ $instance['title'] = trim( strip_tags( stripslashes( $new_instance['title'] ) ) );
+
+ // Set up widget values
+ $instance['like_args'] = array(
+ 'href' => trim( strip_tags( stripslashes( $new_instance['href'] ) ) ),
+ 'width' => (int) $new_instance['width'],
+ 'height' => (int) $new_instance['height'],
+ 'show_faces' => isset( $new_instance['show_faces'] ),
+ 'stream' => isset( $new_instance['stream'] ),
+ 'cover' => isset( $new_instance['cover'] ),
+ );
+
+ $instance['like_args'] = $this->normalize_facebook_args( $instance['like_args'] );
+
+ return $instance;
+ }
+
+ function form( $instance ) {
+ $instance = wp_parse_args(
+ (array) $instance, array(
+ 'title' => '',
+ 'like_args' => $this->get_default_args(),
+ )
+ );
+ $like_args = $this->normalize_facebook_args( $instance['like_args'] );
+ ?>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
+ <?php _e( 'Title', 'jetpack' ); ?>
+ <input type="text" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ); ?>" class="widefat" />
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'href' ) ); ?>">
+ <?php _e( 'Facebook Page URL', 'jetpack' ); ?>
+ <input type="text" name="<?php echo esc_attr( $this->get_field_name( 'href' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'href' ) ); ?>" value="<?php echo esc_url( $like_args['href'] ); ?>" class="widefat" />
+ <br />
+ <small><?php _e( 'The widget only works with Facebook Pages.', 'jetpack' ); ?></small>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'width' ) ); ?>">
+ <?php _e( 'Width in pixels', 'jetpack' ); ?>
+ <input type="number" class="smalltext" min="<?php echo esc_attr( $this->min_width ); ?>" max="<?php echo esc_attr( $this->max_width ); ?>" maxlength="3" name="<?php echo esc_attr( $this->get_field_name( 'width' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'width' ) ); ?>" value="<?php echo esc_attr( $like_args['width'] ); ?>" style="text-align: center;" />
+ <small><?php echo sprintf( __( 'Minimum: %s', 'jetpack' ), $this->min_width ); ?> / <?php echo sprintf( __( 'Maximum: %s', 'jetpack' ), $this->max_width ); ?></small>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'height' ) ); ?>">
+ <?php _e( 'Height in pixels', 'jetpack' ); ?>
+ <input type="number" class="smalltext" min="<?php echo esc_attr( $this->min_height ); ?>" max="<?php echo esc_attr( $this->max_height ); ?>" maxlength="3" name="<?php echo esc_attr( $this->get_field_name( 'height' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'height' ) ); ?>" value="<?php echo esc_attr( $like_args['height'] ); ?>" style="text-align: center;" />
+ <small><?php echo sprintf( __( 'Minimum: %s', 'jetpack' ), $this->min_height ); ?> / <?php echo sprintf( __( 'Maximum: %s', 'jetpack' ), $this->max_height ); ?></small>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'show_faces' ) ); ?>">
+ <input type="checkbox" name="<?php echo esc_attr( $this->get_field_name( 'show_faces' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'show_faces' ) ); ?>" <?php checked( $like_args['show_faces'] ); ?> />
+ <?php _e( 'Show Faces', 'jetpack' ); ?>
+ <br />
+ <small><?php _e( 'Show profile photos in the plugin.', 'jetpack' ); ?></small>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'stream' ) ); ?>">
+ <input type="checkbox" name="<?php echo esc_attr( $this->get_field_name( 'stream' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'stream' ) ); ?>" <?php checked( $like_args['stream'] ); ?> />
+ <?php _e( 'Show Stream', 'jetpack' ); ?>
+ <br />
+ <small><?php _e( 'Show Page Posts.', 'jetpack' ); ?></small>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'cover' ) ); ?>">
+ <input type="checkbox" name="<?php echo esc_attr( $this->get_field_name( 'cover' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'cover' ) ); ?>" <?php checked( $like_args['cover'] ); ?> />
+ <?php _e( 'Show Cover Photo', 'jetpack' ); ?>
+ <br />
+ </label>
+ </p>
+
+ <?php
+ }
+
+ function get_default_args() {
+ $defaults = array(
+ 'href' => '',
+ 'width' => $this->default_width,
+ 'height' => $this->default_height,
+ 'show_faces' => 'true',
+ 'stream' => '',
+ 'cover' => 'true',
+ );
+
+ /**
+ * Filter Facebook Likebox default options.
+ *
+ * @module widgets
+ *
+ * @since 1.3.1
+ *
+ * @param array $defaults Array of default options.
+ */
+ return apply_filters( 'jetpack_facebook_likebox_defaults', $defaults );
+ }
+
+ function normalize_facebook_args( $args ) {
+ $args = wp_parse_args( (array) $args, $this->get_default_args() );
+
+ // Validate the Facebook Page URL
+ if ( $this->is_valid_facebook_url( $args['href'] ) ) {
+ $temp = explode( '?', $args['href'] );
+ $args['href'] = str_replace( array( 'http://facebook.com', 'https://facebook.com' ), array( 'http://www.facebook.com', 'https://www.facebook.com' ), $temp[0] );
+ } else {
+ $args['href'] = '';
+ }
+
+ $args['width'] = $this->normalize_int_value( (int) $args['width'], $this->default_width, $this->max_width, $this->min_width );
+ $args['height'] = $this->normalize_int_value( (int) $args['height'], $this->default_height, $this->max_height, $this->min_height );
+ $args['show_faces'] = (bool) $args['show_faces'];
+ $args['stream'] = (bool) $args['stream'];
+ $args['cover'] = (bool) $args['cover'];
+
+ // The height used to be dependent on other widget settings
+ // If the user changes those settings but doesn't customize the height,
+ // let's intelligently assign a new height.
+ if ( in_array( $args['height'], array( 580, 110, 432 ) ) ) {
+ if ( $args['show_faces'] && $args['stream'] ) {
+ $args['height'] = 580;
+ } elseif ( ! $args['show_faces'] && ! $args['stream'] ) {
+ $args['height'] = 130;
+ } else {
+ $args['height'] = 432;
+ }
+ }
+
+ return $args;
+ }
+
+ function is_valid_facebook_url( $url ) {
+ return ( false !== strpos( $url, 'facebook.com' ) ) ? true : false;
+ }
+
+ function normalize_int_value( $value, $default = 0, $max = 0, $min = 0 ) {
+ $value = (int) $value;
+
+ if ( $value > $max ) {
+ $value = $max;
+ } elseif ( $value < $min ) {
+ $value = $min;
+ }
+
+ return (int) $value;
+ }
+
+ function normalize_text_value( $value, $default = '', $allowed = array() ) {
+ $allowed = (array) $allowed;
+
+ if ( empty( $value ) || ( ! empty( $allowed ) && ! in_array( $value, $allowed ) ) ) {
+ $value = $default;
+ }
+
+ return $value;
+ }
+
+ /**
+ * @deprecated
+ */
+ function guess_locale_from_lang( $lang ) {
+ _deprecated_function( __METHOD__, '4.0.0', 'Jetpack::guess_locale_from_lang()' );
+ Jetpack::$instance->guess_locale_from_lang( $lang );
+ }
+
+ /**
+ * @deprecated
+ */
+ function get_locale() {
+ _deprecated_function( __METHOD__, '4.0.0', 'Jetpack::get_locale()' );
+ Jetpack::$instance->get_locale();
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/facebook-likebox/style.css b/plugins/jetpack/modules/widgets/facebook-likebox/style.css
new file mode 100644
index 00000000..d5d554ba
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/facebook-likebox/style.css
@@ -0,0 +1,3 @@
+.widget_facebook_likebox {
+ overflow: hidden;
+}
diff --git a/plugins/jetpack/modules/widgets/flickr.php b/plugins/jetpack/modules/widgets/flickr.php
new file mode 100644
index 00000000..a7867612
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/flickr.php
@@ -0,0 +1,218 @@
+<?php
+/**
+ * Disable direct access/execution to/of the widget code.
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+if ( ! class_exists( 'Jetpack_Flickr_Widget' ) ) {
+ /**
+ * Flickr Widget
+ *
+ * Display your recent Flickr photos.
+ */
+ class Jetpack_Flickr_Widget extends WP_Widget {
+ /**
+ * Constructor.
+ */
+ function __construct() {
+ parent::__construct(
+ 'flickr',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'Flickr', 'jetpack' ) ),
+ array(
+ 'description' => esc_html__( 'Display your recent Flickr photos.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ ),
+ array()
+ );
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
+ }
+ }
+
+ /**
+ * Enqueue style.
+ */
+ function enqueue_style() {
+ wp_enqueue_style( 'flickr-widget-style', plugins_url( 'flickr/style.css', __FILE__ ), array(), '20170405' );
+ }
+
+ /**
+ * Return an associative array of default values.
+ *
+ * These values are used in new widgets.
+ *
+ * @return array Default values for the widget options.
+ */
+ public function defaults() {
+ return array(
+ 'title' => esc_html__( 'Flickr Photos', 'jetpack' ),
+ 'items' => 4,
+ 'flickr_image_size' => 'thumbnail',
+ 'flickr_rss_url' => '',
+ );
+ }
+
+ /**
+ * Front-end display of the widget.
+ *
+ * @param array $args Widget arguments.
+ * @param array $instance Saved values from database.
+ */
+ public function widget( $args, $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults() );
+
+ $image_size_string = 'small' == $instance['flickr_image_size'] ? '_m.jpg' : '_t.jpg';
+
+ if ( ! empty( $instance['flickr_rss_url'] ) ) {
+
+ /*
+ * Parse the URL, and rebuild a URL that's sure to display images.
+ * Some Flickr Feeds do not display images by default.
+ */
+ $flickr_parameters = parse_url( htmlspecialchars_decode( $instance['flickr_rss_url'] ) );
+
+ // Is it a Flickr Feed.
+ if (
+ ! empty( $flickr_parameters['host'] )
+ && ! empty( $flickr_parameters['query'] )
+ && false !== strpos( $flickr_parameters['host'], 'flickr' )
+ ) {
+ parse_str( $flickr_parameters['query'], $vars );
+
+ // Do we have an ID in the feed? Let's continue.
+ if ( isset( $vars['id'] ) ) {
+
+ // Flickr Feeds can be used for groups or for individuals.
+ if (
+ ! empty( $flickr_parameters['path'] )
+ && false !== strpos( $flickr_parameters['path'], 'groups' )
+ ) {
+ $feed_url = 'https://api.flickr.com/services/feeds/groups_pool.gne';
+ } else {
+ $feed_url = 'https://api.flickr.com/services/feeds/photos_public.gne';
+ }
+
+ // Build our new RSS feed.
+ $rss_url = sprintf(
+ '%1$s?id=%2$s&format=rss_200_enc',
+ esc_url( $feed_url ),
+ esc_attr( $vars['id'] )
+ );
+ }
+ }
+ } // End if().
+
+ // Still no RSS feed URL? Get a default feed from Flickr to grab interesting photos.
+ if ( empty( $rss_url ) ) {
+ $rss_url = 'https://api.flickr.com/services/feeds/photos_interesting.gne?format=rss_200';
+ }
+
+ $rss = fetch_feed( $rss_url );
+
+ $photos = '';
+ if ( ! is_wp_error( $rss ) ) {
+ foreach ( $rss->get_items( 0, $instance['items'] ) as $photo ) {
+ switch ( $instance['flickr_image_size'] ) {
+ case 'thumbnail':
+ $src = $photo->get_enclosure()->get_thumbnail();
+ break;
+ case 'small':
+ $src = preg_match( '/src="(.*?)"/i', $photo->get_description(), $p );
+ $src = $p[1];
+ break;
+ case 'large':
+ $src = $photo->get_enclosure()->get_link();
+ break;
+ }
+
+ $photos .= '<a href="' . esc_url( $photo->get_permalink(), array( 'http', 'https' ) ) . '">';
+ $photos .= '<img src="' . esc_url( $src, array( 'http', 'https' ) ) . '" ';
+ $photos .= 'alt="' . esc_attr( $photo->get_title() ) . '" ';
+ $photos .= 'title="' . esc_attr( $photo->get_title() ) . '" ';
+ $photos .= ' /></a>';
+ }
+ if ( ! empty( $photos ) && class_exists( 'Jetpack_Photon' ) && Jetpack::is_module_active( 'photon' ) ) {
+ $photos = Jetpack_Photon::filter_the_content( $photos );
+ }
+
+ $flickr_home = $rss->get_link();
+ }
+
+ echo $args['before_widget'];
+ if ( empty( $photos ) ) {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ printf(
+ '<p>%1$s<br />%2$s</p>',
+ esc_html__( 'There are no photos to display. Make sure your Flickr feed URL is correct, and that your pictures are publicly accessible.', 'jetpack' ),
+ esc_html__( '(Only admins can see this message)', 'jetpack' )
+ );
+ }
+ } else {
+ echo $args['before_title'] . esc_html( $instance['title'] ) . $args['after_title'];
+ require( dirname( __FILE__ ) . '/flickr/widget.php' );
+ }
+ echo $args['after_widget'];
+ /** This action is already documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'flickr' );
+ }
+
+ /**
+ * Back-end widget form.
+ *
+ * @param array $instance Previously saved values from database.
+ */
+ public function form( $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults() );
+ require( dirname( __FILE__ ) . '/flickr/form.php' );
+ }
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ * @return array Updated safe values to be saved.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $instance = array();
+ $defaults = $this->defaults();
+
+ if ( isset( $new_instance['title'] ) ) {
+ $instance['title'] = wp_kses( $new_instance['title'], array() );
+ }
+
+ if ( isset( $new_instance['items'] ) ) {
+ $instance['items'] = intval( $new_instance['items'] );
+ }
+
+ if (
+ isset( $new_instance['flickr_image_size'] ) &&
+ in_array( $new_instance['flickr_image_size'], array( 'thumbnail', 'small', 'large' ) )
+ ) {
+ $instance['flickr_image_size'] = $new_instance['flickr_image_size'];
+ } else {
+ $instance['flickr_image_size'] = 'thumbnail';
+ }
+
+ if ( isset( $new_instance['flickr_rss_url'] ) ) {
+ $instance['flickr_rss_url'] = esc_url( $new_instance['flickr_rss_url'], array( 'http', 'https' ) );
+
+ if ( strlen( $instance['flickr_rss_url'] ) < 10 ) {
+ $instance['flickr_rss_url'] = '';
+ }
+ }
+
+ return $instance;
+ }
+ }
+
+ // Register Jetpack_Flickr_Widget widget.
+ function jetpack_register_flickr_widget() {
+ register_widget( 'Jetpack_Flickr_Widget' );
+ }
+ add_action( 'widgets_init', 'jetpack_register_flickr_widget' );
+}
diff --git a/plugins/jetpack/modules/widgets/flickr/form.php b/plugins/jetpack/modules/widgets/flickr/form.php
new file mode 100644
index 00000000..b08e7c4d
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/flickr/form.php
@@ -0,0 +1,93 @@
+<p>
+ <label>
+ <?php esc_html_e( 'Title:', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['title'] ); ?>"
+ />
+</p>
+
+<p>
+ <label>
+ <?php esc_html_e( 'Flickr RSS URL:', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ name="<?php echo esc_attr( $this->get_field_name( 'flickr_rss_url' ) ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['flickr_rss_url'] ); ?>"
+ />
+</p>
+<p>
+ <small>
+ <?php esc_html_e( 'To find your Flickr RSS URL, go to your photostream, add "?details=1" to the URL, and hit enter. Scroll down until you see the RSS icon or the "Latest" link. Right-click on either options and copy the URL. Paste into the box above.', 'jetpack' ); ?>
+ </small>
+</p>
+<p>
+ <small>
+ <?php printf(
+ __( 'Leave the Flickr RSS URL field blank to display <a target="_blank" href="%s">interesting</a> Flickr photos.', 'jetpack' ),
+ 'http://www.flickr.com/explore/interesting'
+ ); ?>
+ </small>
+</p>
+
+<p>
+ <label>
+ <?php esc_html_e( 'How many photos would you like to display?', 'jetpack' ); ?>
+ </label>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'items' ) ); ?>">
+ <?php for ( $i = 1; $i <= 10; ++$i ) { ?>
+ <option
+ <?php selected( $instance['items'], $i ); ?>
+ value="<?php echo $i; ?>"
+ >
+ <?php echo $i; ?>
+ </option>
+ <?php } ?>
+ </select>
+</p>
+
+<p>
+ <div>
+ <?php esc_html_e( 'What size photos would you like to display?', 'jetpack' ); ?>
+ </div>
+ <ul>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['flickr_image_size'], 'thumbnail' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'flickr_image_size' ) ); ?>"
+ type="radio"
+ value="thumbnail"
+ />
+ <?php esc_html_e( 'Thumbnail', 'jetpack' ); ?>
+ </label>
+ </li>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['flickr_image_size'], 'small' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'flickr_image_size' ) ); ?>"
+ type="radio"
+ value="small"
+ />
+ <?php esc_html_e( 'Medium', 'jetpack' ); ?>
+ </label>
+ </li>
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['flickr_image_size'], 'large' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'flickr_image_size' ) ); ?>"
+ type="radio"
+ value="large"
+ />
+ <?php esc_html_e( 'Large', 'jetpack' ); ?>
+ </label>
+ </li>
+ </ul>
+</p>
diff --git a/plugins/jetpack/modules/widgets/flickr/style.css b/plugins/jetpack/modules/widgets/flickr/style.css
new file mode 100644
index 00000000..689e240a
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/flickr/style.css
@@ -0,0 +1,16 @@
+.flickr-images {
+ text-align: center;
+}
+
+.flickr-size-thumbnail .flickr-images {
+ align-content: space-between;
+ align-items: center;
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: center;
+}
+
+.flickr-images img {
+ max-width: 100%;
+ margin: 5px;
+}
diff --git a/plugins/jetpack/modules/widgets/flickr/widget.php b/plugins/jetpack/modules/widgets/flickr/widget.php
new file mode 100644
index 00000000..0c45f3f0
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/flickr/widget.php
@@ -0,0 +1,13 @@
+<!-- Start of Flickr Widget -->
+<div class="flickr-wrapper flickr-size-<?php echo esc_attr( $instance['flickr_image_size'] ); ?>">
+ <div class="flickr-images">
+ <?php echo $photos; ?>
+ </div>
+
+ <?php if ( isset( $flickr_home ) ) { ?>
+ <a class="flickr-more" href="<?php echo esc_url( $flickr_home, array( 'http', 'https' ) ); ?>">
+ <?php esc_html_e( 'More Photos', 'jetpack' ); ?>
+ </a>
+ <?php } ?>
+</div>
+<!-- End of Flickr Widget -->
diff --git a/plugins/jetpack/modules/widgets/gallery.php b/plugins/jetpack/modules/widgets/gallery.php
new file mode 100644
index 00000000..8cb24d01
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery.php
@@ -0,0 +1,464 @@
+<?php
+
+/*
+Plugin Name: Gallery
+Description: Gallery widget
+Author: Automattic Inc.
+Version: 1.0
+Author URI: http://automattic.com
+*/
+
+class Jetpack_Gallery_Widget extends WP_Widget {
+ const THUMB_SIZE = 45;
+ const DEFAULT_WIDTH = 265;
+
+ protected $_instance_width;
+
+ public function __construct() {
+ $widget_ops = array(
+ 'classname' => 'widget-gallery',
+ 'description' => __( 'Display a photo gallery or slideshow', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ );
+
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
+
+ parent::__construct(
+ 'gallery',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Gallery', 'jetpack' ) ),
+ $widget_ops
+ );
+
+ if ( is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
+
+ if ( class_exists( 'Jetpack_Tiled_Gallery' ) ) {
+ add_action( 'wp_enqueue_scripts', array( 'Jetpack_Tiled_Gallery', 'default_scripts_and_styles' ) );
+ }
+
+ if ( class_exists( 'Jetpack_Slideshow_Shortcode' ) ) {
+ $slideshow = new Jetpack_Slideshow_Shortcode();
+ add_action( 'wp_enqueue_scripts', array( $slideshow, 'enqueue_scripts' ) );
+ }
+
+ if ( class_exists( 'Jetpack_Carousel' ) ) {
+ $carousel = new Jetpack_Carousel();
+ add_action( 'wp_enqueue_scripts', array( $carousel, 'enqueue_assets' ) );
+ }
+ }
+ }
+
+ /**
+ * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
+ * @param array $instance The settings for the particular instance of the widget.
+ */
+ public function widget( $args, $instance ) {
+ $instance = wp_parse_args( (array) $instance, $this->defaults() );
+
+ $this->enqueue_frontend_scripts();
+
+ extract( $args );
+
+ $instance['attachments'] = $this->get_attachments( $instance );
+
+ $classes = array();
+
+ $classes[] = 'widget-gallery-' . $instance['type'];
+
+ // Due to a bug in the carousel plugin, carousels will be triggered for all tiled galleries that exist on a page
+ // with other tiled galleries, regardless of whether or not the widget was set to Carousel mode. The onClick selector
+ // is simply too broad, since it was not written with widgets in mind. This special class prevents that behavior, via
+ // an override handler in gallery.js
+ if ( 'carousel' != $instance['link'] && 'slideshow' != $instance['type'] ) {
+ $classes[] = 'no-carousel';
+ } else {
+ $classes[] = 'carousel';
+ }
+
+ $classes = implode( ' ', $classes );
+
+ if ( 'carousel' == $instance['link'] ) {
+ require_once plugin_dir_path( realpath( dirname( __FILE__ ) . '/../carousel/jetpack-carousel.php' ) ) . 'jetpack-carousel.php';
+
+ if ( class_exists( 'Jetpack_Carousel' ) ) {
+ // Create new carousel so we can use the enqueue_assets() method. Not ideal, but there is a decent amount
+ // of logic in that method that shouldn't be duplicated.
+ $carousel = new Jetpack_Carousel();
+
+ // First parameter is $output, which comes from filters, and causes bypass of the asset enqueuing. Passing null is correct.
+ $carousel->enqueue_assets( null );
+ }
+ }
+
+ echo $before_widget . "\n";
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+
+ if ( $title ) {
+ echo $before_title . esc_html( $title ) . $after_title . "\n";
+ }
+
+ echo '<div class="' . esc_attr( $classes ) . '">' . "\n";
+
+ $method = $instance['type'] . '_widget';
+
+ /**
+ * Allow the width of a gallery to be altered by themes or other code.
+ *
+ * @module widgets
+ *
+ * @since 2.5.0
+ *
+ * @param int self::DEFAULT_WIDTH Default gallery width. Default is 265.
+ * @param string $args Display arguments including before_title, after_title, before_widget, and after_widget.
+ * @param array $instance The settings for the particular instance of the widget.
+ */
+ $this->_instance_width = apply_filters( 'gallery_widget_content_width', self::DEFAULT_WIDTH, $args, $instance );
+
+ // Register a filter to modify the tiled_gallery_content_width, so Jetpack_Tiled_Gallery
+ // can appropriately size the tiles.
+ add_filter( 'tiled_gallery_content_width', array( $this, 'tiled_gallery_content_width' ) );
+
+ if ( method_exists( $this, $method ) ) {
+ echo $this->$method( $args, $instance );
+ }
+
+ // Remove the stored $_instance_width, as it is no longer needed
+ $this->_instance_width = null;
+
+ // Remove the filter, so any Jetpack_Tiled_Gallery in a post is not affected
+ remove_filter( 'tiled_gallery_content_width', array( $this, 'tiled_gallery_content_width' ) );
+
+ echo "\n" . '</div>'; // .widget-gallery-$type
+
+ echo "\n" . $after_widget;
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'gallery' );
+ }
+
+ /**
+ * Fetch the images attached to the gallery Widget
+ *
+ * @param array $instance The Widget instance for which you'd like attachments
+ * @return array Array of attachment objects for the Widget in $instance
+ */
+ public function get_attachments( $instance ) {
+ $ids = explode( ',', $instance['ids'] );
+
+ if ( isset( $instance['random'] ) && 'on' == $instance['random'] ) {
+ shuffle( $ids );
+ }
+
+ $attachments_query = new WP_Query(
+ array(
+ 'post__in' => $ids,
+ 'post_status' => 'inherit',
+ 'post_type' => 'attachment',
+ 'post_mime_type' => 'image',
+ 'posts_per_page' => -1,
+ 'orderby' => 'post__in',
+ )
+ );
+
+ $attachments = $attachments_query->get_posts();
+
+ wp_reset_postdata();
+
+ return $attachments;
+ }
+
+ /**
+ * Generate HTML for a rectangular, tiled Widget
+ *
+ * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
+ * @param array $instance The Widget instance to generate HTML for
+ * @return string String of HTML representing a rectangular gallery
+ */
+ public function rectangular_widget( $args, $instance ) {
+ if ( ! class_exists( 'Jetpack_Tiled_Gallery' )
+ && ! class_exists( 'Jetpack_Tiled_Gallery_Layout_Rectangular' ) ) {
+ return;
+ }
+
+ Jetpack_Tiled_Gallery::default_scripts_and_styles();
+
+ $layout = new Jetpack_Tiled_Gallery_Layout_Rectangular( $instance['attachments'], $instance['link'], false, 3 );
+ return $layout->HTML();
+ }
+
+ /**
+ * Generate HTML for a square (grid style) Widget
+ *
+ * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
+ * @param array $instance The Widget instance to generate HTML for
+ * @return string String of HTML representing a square gallery
+ */
+ public function square_widget( $args, $instance ) {
+ if ( ! class_exists( 'Jetpack_Tiled_Gallery' )
+ && ! class_exists( 'Jetpack_Tiled_Gallery_Layout_Square' ) ) {
+ return;
+ }
+
+ Jetpack_Tiled_Gallery::default_scripts_and_styles();
+
+ $layout = new Jetpack_Tiled_Gallery_Layout_Square( $instance['attachments'], $instance['link'], false, 3 );
+ return $layout->HTML();
+ }
+
+ /**
+ * Generate HTML for a circular (grid style) Widget
+ *
+ * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
+ * @param array $instance The Widget instance to generate HTML for
+ * @return string String of HTML representing a circular gallery
+ */
+ public function circle_widget( $args, $instance ) {
+ if ( ! class_exists( 'Jetpack_Tiled_Gallery' )
+ && ! class_exists( 'Jetpack_Tiled_Gallery_Layout_Circle' ) ) {
+ return;
+ }
+
+ Jetpack_Tiled_Gallery::default_scripts_and_styles();
+
+ $layout = new Jetpack_Tiled_Gallery_Layout_Circle( $instance['attachments'], $instance['link'], false, 3 );
+ return $layout->HTML();
+ }
+
+ /**
+ * Generate HTML for a slideshow Widget
+ *
+ * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
+ * @param array $instance The Widget instance to generate HTML for
+ * @return string String of HTML representing a slideshow gallery
+ */
+ public function slideshow_widget( $args, $instance ) {
+ global $content_width;
+
+ require_once plugin_dir_path( realpath( dirname( __FILE__ ) . '/../shortcodes/slideshow.php' ) ) . 'slideshow.php';
+
+ if ( ! class_exists( 'Jetpack_Slideshow_Shortcode' ) ) {
+ return;
+ }
+
+ if ( count( $instance['attachments'] ) < 1 ) {
+ return;
+ }
+
+ $slideshow = new Jetpack_Slideshow_Shortcode();
+
+ $slideshow->enqueue_scripts();
+
+ $gallery_instance = 'widget-' . $args['widget_id'];
+
+ $gallery = array();
+
+ foreach ( $instance['attachments'] as $attachment ) {
+ $attachment_image_src = wp_get_attachment_image_src( $attachment->ID, 'full' );
+ $attachment_image_src = jetpack_photon_url( $attachment_image_src[0], array( 'w' => $this->_instance_width ) ); // [url, width, height]
+
+ $caption = wptexturize( strip_tags( $attachment->post_excerpt ) );
+
+ $gallery[] = (object) array(
+ 'src' => (string) esc_url_raw( $attachment_image_src ),
+ 'id' => (string) $attachment->ID,
+ 'caption' => (string) $caption,
+ );
+ }
+
+ $max_width = intval( get_option( 'large_size_w' ) );
+ $max_height = 175;
+
+ if ( intval( $content_width ) > 0 ) {
+ $max_width = min( intval( $content_width ), $max_width );
+ }
+
+ $color = Jetpack_Options::get_option( 'slideshow_background_color', 'black' );
+ $autostart = isset( $attr['autostart'] ) ? $attr['autostart'] : true;
+
+ $js_attr = array(
+ 'gallery' => $gallery,
+ 'selector' => $gallery_instance,
+ 'width' => $max_width,
+ 'height' => $max_height,
+ 'trans' => 'fade',
+ 'color' => $color,
+ 'autostart' => $autostart,
+ );
+
+ $html = $slideshow->slideshow_js( $js_attr );
+
+ return $html;
+ }
+
+ /**
+ * tiled_gallery_content_width filter
+ *
+ * Used to adjust the content width of Jetpack_Tiled_Gallery's in sidebars
+ *
+ * $this->_instance_width is filtered in widget() and this filter is added then removed in widget()
+ *
+ * @param int $width int The original width value
+ * @return int The filtered width
+ */
+ public function tiled_gallery_content_width( $width ) {
+ return $this->_instance_width;
+ }
+
+ public function form( $instance ) {
+ $defaults = $this->defaults();
+ $allowed_values = $this->allowed_values();
+
+ $instance = wp_parse_args( (array) $instance, $defaults );
+
+ include dirname( __FILE__ ) . '/gallery/templates/form.php';
+ }
+
+ public function update( $new_instance, $old_instance ) {
+ $instance = $this->sanitize( $new_instance );
+
+ return $instance;
+ }
+
+ /**
+ * Sanitize the $instance's values to the set of allowed values. If a value is not acceptable,
+ * it is set to its default.
+ *
+ * Helps keep things nice and secure by whitelisting only allowed values
+ *
+ * @param array $instance The Widget instance to sanitize values for
+ * @return array $instance The Widget instance with values sanitized
+ */
+ public function sanitize( $instance ) {
+ $allowed_values = $this->allowed_values();
+ $defaults = $this->defaults();
+
+ foreach ( $instance as $key => $value ) {
+ $value = trim( $value );
+
+ if ( isset( $allowed_values[ $key ] ) && $allowed_values[ $key ] && ! array_key_exists( $value, $allowed_values[ $key ] ) ) {
+ $instance[ $key ] = $defaults[ $key ];
+ } else {
+ $instance[ $key ] = sanitize_text_field( $value );
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Return a multi-dimensional array of allowed values (and their labels) for all widget form
+ * elements
+ *
+ * To allow all values on an input, omit it from the returned array
+ *
+ * @return array Array of allowed values for each option
+ */
+ public function allowed_values() {
+ $max_columns = 5;
+
+ // Create an associative array of allowed column values. This just automates the generation of
+ // column <option>s, from 1 to $max_columns
+ $allowed_columns = array_combine( range( 1, $max_columns ), range( 1, $max_columns ) );
+
+ return array(
+ 'type' => array(
+ 'rectangular' => __( 'Tiles', 'jetpack' ),
+ 'square' => __( 'Square Tiles', 'jetpack' ),
+ 'circle' => __( 'Circles', 'jetpack' ),
+ 'slideshow' => __( 'Slideshow', 'jetpack' ),
+ ),
+ 'columns' => $allowed_columns,
+ 'link' => array(
+ 'carousel' => __( 'Carousel', 'jetpack' ),
+ 'post' => __( 'Attachment Page', 'jetpack' ),
+ 'file' => __( 'Media File', 'jetpack' ),
+ ),
+ );
+ }
+
+ /**
+ * Return an associative array of default values
+ *
+ * These values are used in new widgets as well as when sanitizing input. If a given value is not allowed,
+ * as defined in allowed_values(), that input is set to the default value defined here.
+ *
+ * @return array Array of default values for the Widget's options
+ */
+ public function defaults() {
+ return array(
+ 'title' => '',
+ 'type' => 'rectangular',
+ 'ids' => '',
+ 'columns' => 3,
+ 'link' => 'carousel',
+ );
+ }
+
+ public function enqueue_frontend_scripts() {
+ wp_register_script(
+ 'gallery-widget',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/gallery/js/gallery.min.js',
+ 'modules/widgets/gallery/js/gallery.js'
+ )
+ );
+
+ wp_enqueue_script( 'gallery-widget' );
+ }
+
+ public function enqueue_admin_scripts() {
+ global $pagenow;
+
+ if ( 'widgets.php' == $pagenow || 'customize.php' == $pagenow ) {
+ wp_enqueue_media();
+
+ wp_enqueue_script(
+ 'gallery-widget-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/gallery/js/admin.min.js',
+ 'modules/widgets/gallery/js/admin.js'
+ ),
+ array(
+ 'media-models',
+ 'media-views',
+ ),
+ '20150501'
+ );
+
+ $js_settings = array(
+ 'thumbSize' => self::THUMB_SIZE,
+ );
+
+ wp_localize_script( 'gallery-widget-admin', '_wpGalleryWidgetAdminSettings', $js_settings );
+ wp_enqueue_style( 'gallery-widget-admin', plugins_url( '/gallery/css/admin.css', __FILE__ ) );
+ wp_style_add_data( 'gallery-widget-admin', 'rtl', 'replace' );
+ }
+ }
+}
+
+add_action( 'widgets_init', 'jetpack_gallery_widget_init' );
+
+function jetpack_gallery_widget_init() {
+ /**
+ * Allow the Gallery Widget to be enabled even when Core supports the Media Gallery Widget
+ *
+ * @module widgets
+ *
+ * @since 5.5.0
+ *
+ * @param bool false Whether to force-enable the gallery widget
+ */
+ if (
+ ! apply_filters( 'jetpack_force_enable_gallery_widget', false )
+ && class_exists( 'WP_Widget_Media_Gallery' )
+ && Jetpack_Options::get_option( 'gallery_widget_migration' )
+ ) {
+ return;
+ }
+ if ( ! method_exists( 'Jetpack', 'is_module_active' ) || Jetpack::is_module_active( 'tiled-gallery' ) ) {
+ register_widget( 'Jetpack_Gallery_Widget' );
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/gallery/css/admin-rtl.css b/plugins/jetpack/modules/widgets/gallery/css/admin-rtl.css
new file mode 100644
index 00000000..afd9550d
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery/css/admin-rtl.css
@@ -0,0 +1,12 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+.gallery-widget-thumbs-wrapper {
+ margin: -5px 0 0.3em 0;
+}
+
+.gallery-widget-thumbs img {
+ border: 1px solid #ccc;
+ padding: 2px;
+ background-color: #fff;
+ margin: 0 0 5px 5px;
+ float: right;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/gallery/css/admin-rtl.min.css b/plugins/jetpack/modules/widgets/gallery/css/admin-rtl.min.css
new file mode 100644
index 00000000..de937320
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery/css/admin-rtl.min.css
@@ -0,0 +1 @@
+.gallery-widget-thumbs-wrapper{margin:-5px 0 .3em 0}.gallery-widget-thumbs img{border:1px solid #ccc;padding:2px;background-color:#fff;margin:0 0 5px 5px;float:right} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/gallery/css/admin.css b/plugins/jetpack/modules/widgets/gallery/css/admin.css
new file mode 100644
index 00000000..c3d96d80
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery/css/admin.css
@@ -0,0 +1,11 @@
+.gallery-widget-thumbs-wrapper {
+ margin: -5px 0 0.3em 0;
+}
+
+.gallery-widget-thumbs img {
+ border: 1px solid #ccc;
+ padding: 2px;
+ background-color: #fff;
+ margin: 0 5px 5px 0;
+ float: left;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/gallery/css/admin.min.css b/plugins/jetpack/modules/widgets/gallery/css/admin.min.css
new file mode 100644
index 00000000..743791f9
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery/css/admin.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+.gallery-widget-thumbs-wrapper{margin:-5px 0 .3em 0}.gallery-widget-thumbs img{border:1px solid #ccc;padding:2px;background-color:#fff;margin:0 5px 5px 0;float:left} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/gallery/css/rtl/admin-rtl.css b/plugins/jetpack/modules/widgets/gallery/css/rtl/admin-rtl.css
new file mode 100644
index 00000000..e0e4b615
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery/css/rtl/admin-rtl.css
@@ -0,0 +1,13 @@
+/* This file was automatically generated on Mar 22 2013 21:33:14 */
+
+.gallery-widget-thumbs-wrapper {
+ margin: -5px 0 0.3em 0;
+}
+
+.gallery-widget-thumbs img {
+ border: 1px solid #ccc;
+ padding: 2px;
+ background-color: #fff;
+ margin: 0 0 5px 5px;
+ float: right;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/gallery/js/admin.js b/plugins/jetpack/modules/widgets/gallery/js/admin.js
new file mode 100644
index 00000000..71025a9b
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery/js/admin.js
@@ -0,0 +1,236 @@
+/* jshint onevar: false, multistr: true */
+/* global _wpMediaViewsL10n, _wpGalleryWidgetAdminSettings */
+
+( function( $ ) {
+ var $ids;
+ var $thumbs;
+
+ $( function() {
+ $( document.body ).on( 'click', '.gallery-widget-choose-images', function( event ) {
+ event.preventDefault();
+
+ var widget_form = $( this ).closest( 'form, .form' );
+
+ $ids = widget_form.find( '.gallery-widget-ids' );
+ $thumbs = widget_form.find( '.gallery-widget-thumbs' );
+
+ var idsString = $ids.val();
+
+ var attachments = getAttachments( idsString );
+
+ var selection = null;
+ var editing = false;
+
+ if ( attachments ) {
+ selection = getSelection( attachments );
+
+ editing = true;
+ }
+
+ var options = {
+ state: 'gallery-edit',
+ title: wp.media.view.l10n.addMedia,
+ multiple: true,
+ editing: editing,
+ selection: selection,
+ };
+
+ var workflow = getWorkflow( options );
+
+ workflow.open();
+ } );
+
+ // Setup an onchange handler to toggle various options when changing style. The different style options
+ // require different form inputs to be presented in the widget; this event will keep the UI in sync
+ // with the selected style
+ $( '.widget-inside' ).on( 'change', '.gallery-widget-style', setupStyleOptions );
+
+ // Setup the Link To options for all forms currently on the page. Does the same as the onChange handler, but
+ // is called once to display the correct form inputs for each widget on the page
+ setupStyleOptions();
+ } );
+
+ var media = wp.media,
+ l10n;
+
+ // Link any localized strings.
+ l10n = media.view.l10n = typeof _wpMediaViewsL10n === 'undefined' ? {} : _wpMediaViewsL10n;
+
+ /**
+ * wp.media.view.MediaFrame.GalleryWidget
+ *
+ * This behavior can be very nearly had by setting the workflow's state to 'gallery-edit', but
+ * we cannot use the custom WidgetGalleryEdit controller with it (must overide createStates(),
+ * which is necessary to disable the sidebar gallery settings in the media browser)
+ */
+ media.view.MediaFrame.GalleryWidget = media.view.MediaFrame.Post.extend( {
+ createStates: function() {
+ var options = this.options;
+
+ // `CollectionEdit` and `CollectionAdd` were only introduced in r27214-core,
+ // so they may not be available yet.
+ if ( 'CollectionEdit' in media.controller ) {
+ this.states.add( [
+ new media.controller.CollectionEdit( {
+ type: 'image',
+ collectionType: 'gallery',
+ title: l10n.editGalleryTitle,
+ SettingsView: media.view.Settings.Gallery,
+ library: options.selection,
+ editing: options.editing,
+ menu: 'gallery',
+ } ),
+ new media.controller.CollectionAdd( {
+ type: 'image',
+ collectionType: 'gallery',
+ title: l10n.addToGalleryTitle,
+ } ),
+ ] );
+ } else {
+ // If `CollectionEdit` is not available, then use the old approach.
+
+ if ( ! ( 'WidgetGalleryEdit' in media.controller ) ) {
+ // Remove the gallery settings sidebar when editing widgets.
+ media.controller.WidgetGalleryEdit = media.controller.GalleryEdit.extend( {
+ gallerySettings: function(/*browser*/) {
+ return;
+ },
+ } );
+ }
+
+ this.states.add( [
+ new media.controller.WidgetGalleryEdit( {
+ library: options.selection,
+ editing: options.editing,
+ menu: 'gallery',
+ } ),
+ new media.controller.GalleryAdd( {} ),
+ ] );
+ }
+ },
+ } );
+
+ function setupStyleOptions() {
+ $( '.widget-inside .gallery-widget-style' ).each( function(/*i*/) {
+ var style = $( this ).val();
+
+ var form = $( this ).parents( 'form' );
+
+ switch ( style ) {
+ case 'slideshow':
+ form.find( '.gallery-widget-link-wrapper' ).hide();
+ form.find( '.gallery-widget-columns-wrapper' ).hide();
+
+ break;
+
+ default:
+ form.find( '.gallery-widget-link-wrapper' ).show();
+ form.find( '.gallery-widget-columns-wrapper' ).show();
+ }
+ } );
+ }
+
+ /**
+ * Take a given Selection of attachments and a thumbs wrapper div (jQuery object)
+ * and fill it with thumbnails
+ */
+ function setupThumbs( selection, wrapper ) {
+ wrapper.empty();
+
+ var imageSize = _wpGalleryWidgetAdminSettings.thumbSize;
+
+ selection.each( function( model ) {
+ var sizedUrl = model.get( 'url' ) + '?w=' + imageSize + '&h=' + imageSize + '&crop=true';
+
+ var thumb = jQuery( '<img>', {
+ src: sizedUrl,
+ alt: model.get( 'title' ),
+ title: model.get( 'title' ),
+ width: imageSize,
+ height: imageSize,
+ class: 'thumb',
+ } );
+
+ wrapper.append( thumb );
+ } );
+ }
+
+ /**
+ * Take a csv string of ids (as stored in db) and fetch a full Attachments collection
+ */
+ function getAttachments( idsString ) {
+ if ( ! idsString ) {
+ return null;
+ }
+
+ // Found in /wp-includes/js/media-editor.js
+ var shortcode = wp.shortcode.next( 'gallery', '[gallery ids="' + idsString + '"]' );
+
+ // Ignore the rest of the match object, to give attachments() below what it expects
+ shortcode = shortcode.shortcode;
+
+ var attachments = wp.media.gallery.attachments( shortcode );
+
+ return attachments;
+ }
+
+ /**
+ * Take an Attachments collection and return a corresponding Selection model that can be
+ * passed to a MediaFrame to prepopulate the gallery picker
+ */
+ function getSelection( attachments ) {
+ var selection = new wp.media.model.Selection( attachments.models, {
+ props: attachments.props.toJSON(),
+ multiple: true,
+ } );
+
+ selection.gallery = attachments.gallery;
+
+ // Fetch the query's attachments, and then break ties from the
+ // query to allow for sorting.
+ selection.more().done( function() {
+ // Break ties with the query.
+ selection.props.set( { query: false } );
+ selection.unmirror();
+ selection.props.unset( 'orderby' );
+ } );
+
+ return selection;
+ }
+
+ /**
+ * Create a media 'workflow' (MediaFrame). This is the main entry point for the media picker
+ */
+ function getWorkflow( options ) {
+ var workflow = new wp.media.view.MediaFrame.GalleryWidget( options );
+
+ workflow.on(
+ 'update',
+ function( selection ) {
+ var state = workflow.state();
+
+ selection = selection || state.get( 'selection' );
+
+ if ( ! selection ) {
+ return;
+ }
+
+ // Map the Models down into a simple array of ids that can be easily imploded to a csv string
+ var ids = selection.map( function( model ) {
+ return model.get( 'id' );
+ } );
+
+ var id_string = ids.join( ',' );
+
+ $ids.val( id_string ).trigger( 'change' );
+
+ setupThumbs( selection, $thumbs );
+ },
+ this
+ );
+
+ workflow.setState( workflow.options.state );
+
+ return workflow;
+ }
+} )( jQuery );
diff --git a/plugins/jetpack/modules/widgets/gallery/js/gallery.js b/plugins/jetpack/modules/widgets/gallery/js/gallery.js
new file mode 100644
index 00000000..5b952bb4
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery/js/gallery.js
@@ -0,0 +1,10 @@
+( function( $ ) {
+ // Fixes a bug with carousels being triggered even when a widget's Link To option is not set to carousel.
+ // Happens when another gallery is loaded on the page, either in a post or separate widget
+ $( 'body' ).on( 'click', '.widget-gallery .no-carousel .tiled-gallery-item a', function( event ) {
+ // Have to trigger default, instead of carousel
+ event.stopPropagation();
+
+ return true;
+ } );
+} )( jQuery );
diff --git a/plugins/jetpack/modules/widgets/gallery/templates/form.php b/plugins/jetpack/modules/widgets/gallery/templates/form.php
new file mode 100644
index 00000000..f24cf1c2
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gallery/templates/form.php
@@ -0,0 +1,89 @@
+<p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>"
+ type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
+ </label>
+</p>
+
+<p>
+ <label>
+ <?php esc_html_e( 'Images:', 'jetpack' ); ?>
+ </label>
+</p>
+
+<div class="gallery-widget-thumbs-wrapper">
+ <div class="gallery-widget-thumbs">
+ <?php
+
+ // Add the thumbnails to the widget box
+ $attachments = $this->get_attachments( $instance );
+
+ foreach( $attachments as $attachment ){
+ $url = add_query_arg( array(
+ 'w' => self::THUMB_SIZE,
+ 'h' => self::THUMB_SIZE,
+ 'crop' => 'true'
+ ), wp_get_attachment_url( $attachment->ID ) );
+
+ ?>
+
+ <img src="<?php echo esc_url( $url ); ?>" title="<?php echo esc_attr( $attachment->post_title ); ?>" alt="<?php echo esc_attr( $attachment->post_title ); ?>"
+ width="<?php echo self::THUMB_SIZE; ?>" height="<?php echo self::THUMB_SIZE; ?>" class="thumb" />
+ <?php } ?>
+ </div>
+
+ <div style="clear: both;"></div>
+</div>
+
+<p>
+ <a class="button gallery-widget-choose-images"><span class="wp-media-buttons-icon"></span> <?php esc_html_e( 'Choose Images', 'jetpack' ); ?></a>
+</p>
+
+<p class="gallery-widget-link-wrapper">
+ <label for="<?php echo $this->get_field_id( 'link' ); ?>"><?php esc_html_e( 'Link To:', 'jetpack' ); ?></label>
+ <select name="<?php echo $this->get_field_name( 'link' ); ?>" id="<?php echo $this->get_field_id( 'link' ); ?>" class="widefat">
+ <?php foreach ( $allowed_values['link'] as $key => $label ) {
+ $selected = '';
+
+ if ( $instance['link'] == $key ) {
+ $selected = "selected='selected' ";
+ } ?>
+
+ <option value="<?php echo $key; ?>" <?php echo $selected; ?>><?php echo esc_html( $label, 'jetpack' ); ?></option>
+ <?php } ?>
+ </select>
+</p>
+
+<p>
+ <label for="<?php echo $this->get_field_id( 'random' ); ?>"><?php esc_html_e( 'Random Order:', 'jetpack' ); ?></label>
+ <?php $checked = '';
+
+ if ( isset( $instance['random'] ) && $instance['random'] )
+ $checked = 'checked="checked"';
+
+ ?>
+ <input name="<?php echo $this->get_field_name( 'random' ); ?>" id="<?php echo $this->get_field_id( 'random' ); ?>" type="checkbox" <?php echo $checked; ?>>
+</p>
+
+<p class="gallery-widget-style-wrapper">
+ <label for="<?php echo $this->get_field_id( 'type' ); ?>"><?php esc_html_e( 'Style:', 'jetpack' ); ?></label>
+ <select name="<?php echo $this->get_field_name( 'type' ); ?>" id="<?php echo $this->get_field_id( 'type' ); ?>" class="widefat gallery-widget-style">
+ <?php foreach ( $allowed_values['type'] as $key => $label ) {
+ $selected = '';
+
+ if ( $instance['type'] == $key ) {
+ $selected = "selected='selected' ";
+ } ?>
+
+ <option value="<?php echo $key; ?>" <?php echo $selected; ?>><?php echo esc_html( $label, 'jetpack' ); ?></option>
+ <?php } ?>
+ </select>
+</p>
+
+<?php
+
+
+?>
+
+<?php // Hidden input to hold the selected image ids as a csv list ?>
+<input type="hidden" class="gallery-widget-ids" name="<?php echo $this->get_field_name( 'ids' ); ?>" id="<?php echo $this->get_field_id( 'ids' ); ?>" value="<?php echo esc_attr( $instance['ids'] ); ?>" />
diff --git a/plugins/jetpack/modules/widgets/goodreads.php b/plugins/jetpack/modules/widgets/goodreads.php
new file mode 100644
index 00000000..4160c868
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/goodreads.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * Register the widget for use in Appearance -> Widgets
+ */
+add_action( 'widgets_init', 'jetpack_goodreads_widget_init' );
+
+function jetpack_goodreads_widget_init() {
+ register_widget( 'WPCOM_Widget_Goodreads' );
+}
+
+/**
+ * Goodreads widget class
+ * Display a user's Goodreads shelf.
+ * Customize user_id, title, and shelf
+ *
+ */
+class WPCOM_Widget_Goodreads extends WP_Widget {
+
+ private $goodreads_widget_id = 0;
+
+ function __construct() {
+ parent::__construct(
+ 'wpcom-goodreads',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Goodreads', 'jetpack' ) ),
+ array(
+ 'classname' => 'widget_goodreads',
+ 'description' => __( 'Display your books from Goodreads', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+ // For user input sanitization and display
+ $this->shelves = array(
+ 'read' => _x( 'Read', 'past participle: books I have read', 'jetpack' ),
+ 'currently-reading' => __( 'Currently Reading', 'jetpack' ),
+ 'to-read' => _x( 'To Read', 'my list of books to read', 'jetpack' ),
+ );
+
+ if ( is_active_widget( '', '', 'wpcom-goodreads' ) || is_customize_preview() ) {
+ add_action( 'wp_print_styles', array( $this, 'enqueue_style' ) );
+ }
+ }
+
+ function enqueue_style() {
+ wp_enqueue_style( 'goodreads-widget', plugins_url( 'goodreads/css/goodreads.css', __FILE__ ) );
+ wp_style_add_data( 'goodreads-widget', 'rtl', 'replace' );
+ }
+
+ function widget( $args, $instance ) {
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'goodreads' );
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', isset( $instance['title'] ) ? $instance['title'] : '' );
+
+ if ( empty( $instance['user_id'] ) || 'invalid' === $instance['user_id'] ) {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ echo $args['before_widget'];
+ echo '<p>' . sprintf(
+ __( 'You need to enter your numeric user ID for the <a href="%1$s">Goodreads Widget</a> to work correctly. <a href="%2$s" target="_blank">Full instructions</a>.', 'jetpack' ),
+ esc_url( admin_url( 'widgets.php' ) ),
+ 'https://support.wordpress.com/widgets/goodreads-widget/#goodreads-user-id'
+ ) . '</p>';
+ echo $args['after_widget'];
+ }
+ return;
+ }
+
+ if ( ! array_key_exists( $instance['shelf'], $this->shelves ) ) {
+ return;
+ }
+
+ $instance['user_id'] = absint( $instance['user_id'] );
+
+ // Set widget ID based on shelf.
+ $this->goodreads_widget_id = $instance['user_id'] . '_' . $instance['shelf'];
+
+ if ( empty( $title ) ) {
+ $title = esc_html__( 'Goodreads', 'jetpack' );
+ }
+
+ echo $args['before_widget'];
+ echo $args['before_title'] . $title . $args['after_title'];
+
+ $goodreads_url = 'https://www.goodreads.com/review/custom_widget/' . urlencode( $instance['user_id'] ) . '.' . urlencode( $instance['title'] ) . ':%20' . urlencode( $instance['shelf'] ) . '?cover_position=&cover_size=small&num_books=5&order=d&shelf=' . urlencode( $instance['shelf'] ) . '&sort=date_added&widget_bg_transparent=&widget_id=' . esc_attr( $this->goodreads_widget_id );
+
+ echo '<div class="gr_custom_widget" id="gr_custom_widget_' . esc_attr( $this->goodreads_widget_id ) . '"></div>' . "\n";
+ echo '<script src="' . esc_url( $goodreads_url ) . '"></script>' . "\n";
+
+ echo $args['after_widget'];
+ }
+
+ function goodreads_user_id_exists( $user_id ) {
+ $url = "https://www.goodreads.com/user/show/$user_id/";
+ $response = wp_remote_head(
+ $url, array(
+ 'httpversion' => '1.1',
+ 'timeout' => 3,
+ 'redirection' => 2,
+ )
+ );
+ if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function update( $new_instance, $old_instance ) {
+ $instance = $old_instance;
+
+ $instance['user_id'] = trim( wp_kses( stripslashes( $new_instance['user_id'] ), array() ) );
+ if ( ! empty( $instance['user_id'] ) && ( ! isset( $old_instance['user_id'] ) || $instance['user_id'] !== $old_instance['user_id'] ) ) {
+ if ( ! $this->goodreads_user_id_exists( $instance['user_id'] ) ) {
+ $instance['user_id'] = 'invalid';
+ }
+ }
+ $instance['title'] = wp_kses( stripslashes( $new_instance['title'] ), array() );
+ $shelf = wp_kses( stripslashes( $new_instance['shelf'] ), array() );
+ if ( array_key_exists( $shelf, $this->shelves ) ) {
+ $instance['shelf'] = $shelf;
+ }
+
+ return $instance;
+ }
+
+ function form( $instance ) {
+ //Defaults
+ $instance = wp_parse_args(
+ (array) $instance, array(
+ 'user_id' => '',
+ 'title' => 'Goodreads',
+ 'shelf' => 'read',
+ )
+ );
+
+ echo '<p><label for="' . esc_attr( $this->get_field_id( 'title' ) ) . '">' . esc_html__( 'Title:', 'jetpack' ) . '
+ <input class="widefat" id="' . esc_attr( $this->get_field_id( 'title' ) ) . '" name="' . esc_attr( $this->get_field_name( 'title' ) ) . '" type="text" value="' . esc_attr( $instance['title'] ) . '" />
+ </label></p>
+ <p><label for="' . esc_attr( $this->get_field_id( 'user_id' ) ) . '">';
+ printf( __( 'Goodreads numeric user ID <a href="%s" target="_blank">(instructions)</a>:', 'jetpack' ), 'https://en.support.wordpress.com/widgets/goodreads-widget/#goodreads-user-id' );
+ if ( 'invalid' === $instance['user_id'] ) {
+ printf( '<br /><small class="error">%s</small>&nbsp;', __( 'Invalid User ID, please verify and re-enter your Goodreads numeric user ID.', 'jetpack' ) );
+ $instance['user_id'] = '';
+ }
+ echo '<input class="widefat" id="' . esc_attr( $this->get_field_id( 'user_id' ) ) . '" name="' . esc_attr( $this->get_field_name( 'user_id' ) ) . '" type="text" value="' . esc_attr( $instance['user_id'] ) . '" />
+ </label></p>
+ <p><label for="' . esc_attr( $this->get_field_id( 'shelf' ) ) . '">' . esc_html__( 'Shelf:', 'jetpack' ) . '
+ <select class="widefat" id="' . esc_attr( $this->get_field_id( 'shelf' ) ) . '" name="' . esc_attr( $this->get_field_name( 'shelf' ) ) . '" >';
+ foreach ( $this->shelves as $_shelf_value => $_shelf_display ) {
+ echo "\t<option value='" . esc_attr( $_shelf_value ) . "'" . selected( $_shelf_value, $instance['shelf'] ) . '>' . $_shelf_display . "</option>\n";
+ }
+ echo '</select>
+ </label></p>
+ ';
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/goodreads/css/goodreads.css b/plugins/jetpack/modules/widgets/goodreads/css/goodreads.css
new file mode 100644
index 00000000..d329cb7e
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/goodreads/css/goodreads.css
@@ -0,0 +1,48 @@
+div[class^="gr_custom_container"] {
+ /* customize your Goodreads widget container here*/
+ border: 1px solid gray;
+ border-radius:10px;
+ padding: 10px 5px 10px 5px;
+ background-color: #FFF;
+ color: #000;
+}
+
+div[class^="gr_custom_container"] a {
+ color: #000;
+}
+
+h2[class^="gr_custom_header"] {
+ /* customize your Goodreads header here*/
+ display: none;
+}
+div[class^="gr_custom_each_container"] {
+ /* customize each individual book container here */
+ width: 100%;
+ clear: both;
+ margin-bottom: 10px;
+ overflow: auto;
+ padding-bottom: 4px;
+ border-bottom: 1px solid #aaa;
+}
+div[class^="gr_custom_book_container"] {
+ /* customize your book covers here */
+ float: right;
+ overflow: hidden;
+ height: 60px;
+ margin-left: 4px;
+ width: 39px;
+}
+div[class^="gr_custom_author"] {
+ /* customize your author names here */
+ font-size: 10px;
+}
+div[class^="gr_custom_tags"] {
+ /* customize your tags here */
+ font-size: 10px;
+ color: gray;
+}
+div[class^="gr_custom_review"] {
+}
+div[class^="gr_custom_rating"] {
+ display: none;
+}
diff --git a/plugins/jetpack/modules/widgets/goodreads/css/rtl/goodreads-rtl.css b/plugins/jetpack/modules/widgets/goodreads/css/rtl/goodreads-rtl.css
new file mode 100644
index 00000000..ff600c49
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/goodreads/css/rtl/goodreads-rtl.css
@@ -0,0 +1,50 @@
+/* This file was automatically generated on Nov 19 2013 15:54:57 */
+
+div[class^="gr_custom_container"] {
+ /* customize your Goodreads widget container here*/
+ border: 1px solid gray;
+ border-radius:10px;
+ padding: 10px 5px 10px 5px;
+ background-color: #FFF;
+ color: #000;
+}
+
+div[class^="gr_custom_container"] a {
+ color: #000;
+}
+
+h2[class^="gr_custom_header"] {
+ /* customize your Goodreads header here*/
+ display: none;
+}
+div[class^="gr_custom_each_container"] {
+ /* customize each individual book container here */
+ width: 100%;
+ clear: both;
+ margin-bottom: 10px;
+ overflow: auto;
+ padding-bottom: 4px;
+ border-bottom: 1px solid #aaa;
+}
+div[class^="gr_custom_book_container"] {
+ /* customize your book covers here */
+ float: left;
+ overflow: hidden;
+ height: 60px;
+ margin-right: 4px;
+ width: 39px;
+}
+div[class^="gr_custom_author"] {
+ /* customize your author names here */
+ font-size: 10px;
+}
+div[class^="gr_custom_tags"] {
+ /* customize your tags here */
+ font-size: 10px;
+ color: gray;
+}
+div[class^="gr_custom_review"] {
+}
+div[class^="gr_custom_rating"] {
+ display: none;
+}
diff --git a/plugins/jetpack/modules/widgets/google-translate.php b/plugins/jetpack/modules/widgets/google-translate.php
new file mode 100644
index 00000000..241fb8ed
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/google-translate.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Plugin Name: Google Translate Widget for WordPress.com
+ * Plugin URI: http://automattic.com
+ * Description: Add a widget for automatic translation
+ * Author: Artur Piszek
+ * Version: 0.1
+ * Author URI: http://automattic.com
+ * Text Domain: jetpack
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+class Jetpack_Google_Translate_Widget extends WP_Widget {
+ static $instance = null;
+
+ /**
+ * Default widget title.
+ *
+ * @var string $default_title
+ */
+ var $default_title;
+
+ /**
+ * Register widget with WordPress.
+ */
+ function __construct() {
+ parent::__construct(
+ 'google_translate_widget',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Google Translate', 'jetpack' ) ),
+ array(
+ 'description' => __( 'Provide your readers with the option to translate your site into their preferred language.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+
+ $this->default_title = esc_html__( 'Translate', 'jetpack' );
+ }
+
+ /**
+ * Enqueue frontend JS scripts.
+ */
+ public function enqueue_scripts() {
+ wp_register_script(
+ 'google-translate-init',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/google-translate/google-translate.min.js',
+ 'modules/widgets/google-translate/google-translate.js'
+ )
+ );
+ wp_register_script( 'google-translate', '//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit', array( 'google-translate-init' ) );
+ // Admin bar is also displayed on top of the site which causes google translate bar to hide beneath.
+ // Overwrite position of body.admin-bar
+ // This is a hack to show google translate bar a bit lower.
+ $lowerTranslateBar = '
+ .admin-bar {
+ position: inherit !important;
+ top: auto !important;
+ }
+ .admin-bar .goog-te-banner-frame {
+ top: 32px !important
+ }
+ @media screen and (max-width: 782px) {
+ .admin-bar .goog-te-banner-frame {
+ top: 46px !important;
+ }
+ }
+ @media screen and (max-width: 480px) {
+ .admin-bar .goog-te-banner-frame {
+ position: absolute;
+ }
+ }
+ ';
+ wp_add_inline_style( 'admin-bar', $lowerTranslateBar );
+ wp_add_inline_style( 'wpcom-admin-bar', $lowerTranslateBar );
+ }
+
+ /**
+ * Display the Widget.
+ *
+ * @see WP_Widget::widget()
+ *
+ * @param array $args Display arguments.
+ * @param array $instance The settings for the particular instance of the widget.
+ */
+ public function widget( $args, $instance ) {
+ // We never should show more than 1 instance of this.
+ if ( null === self::$instance ) {
+ $instance = wp_parse_args(
+ $instance, array(
+ 'title' => $this->default_title,
+ )
+ );
+
+ /**
+ * Filter the layout of the Google Translate Widget.
+ *
+ * 3 different integers are accepted.
+ * 0 for the vertical layout.
+ * 1 for the horizontal layout.
+ * 2 for the dropdown only.
+ *
+ * @see https://translate.google.com/manager/website/
+ *
+ * @module widgets
+ *
+ * @since 5.5.0
+ *
+ * @param string $layout layout of the Google Translate Widget.
+ */
+ $button_layout = apply_filters( 'jetpack_google_translate_widget_layout', 0 );
+
+ if (
+ ! is_int( $button_layout )
+ || 0 > $button_layout
+ || 2 < $button_layout
+ ) {
+ $button_layout = 0;
+ }
+
+ wp_localize_script(
+ 'google-translate-init',
+ '_wp_google_translate_widget',
+ array(
+ 'lang' => get_locale(),
+ 'layout' => intval( $button_layout ),
+ )
+ );
+ wp_enqueue_script( 'google-translate-init' );
+ wp_enqueue_script( 'google-translate' );
+
+ $title = $instance['title'];
+
+ if ( ! isset( $title ) ) {
+ $title = $this->default_title;
+ }
+
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
+ $title = apply_filters( 'widget_title', $title );
+
+ echo $args['before_widget'];
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
+ }
+ echo '<div id="google_translate_element"></div>';
+ echo $args['after_widget'];
+ self::$instance = $instance;
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'google-translate' );
+ }
+ }
+
+ /**
+ * Widget form in the dashboard.
+ *
+ * @see WP_Widget::form()
+ *
+ * @param array $instance Previously saved values from database.
+ */
+ public function form( $instance ) {
+ $title = isset( $instance['title'] ) ? $instance['title'] : false;
+ if ( false === $title ) {
+ $title = $this->default_title;
+ }
+ ?>
+<p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
+</p>
+ <?php
+ }
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @see WP_Widget::update()
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ *
+ * @return array $instance Updated safe values to be saved.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $instance = array();
+ $instance['title'] = wp_kses( $new_instance['title'], array() );
+ if ( $instance['title'] === $this->default_title ) {
+ $instance['title'] = false; // Store as false in case of language change
+ }
+ return $instance;
+ }
+
+}
+
+/**
+ * Register the widget for use in Appearance -> Widgets.
+ */
+function jetpack_google_translate_widget_init() {
+ register_widget( 'Jetpack_Google_Translate_Widget' );
+}
+add_action( 'widgets_init', 'jetpack_google_translate_widget_init' );
diff --git a/plugins/jetpack/modules/widgets/google-translate/google-translate.js b/plugins/jetpack/modules/widgets/google-translate/google-translate.js
new file mode 100644
index 00000000..31a1fa0f
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/google-translate/google-translate.js
@@ -0,0 +1,32 @@
+/*global google:true*/
+/*global _wp_google_translate_widget:true*/
+/*exported googleTranslateElementInit*/
+function googleTranslateElementInit() {
+ var lang = 'en';
+ var langParam;
+ var langRegex = /[?&#]lang=([a-zA-Z\-_]+)/;
+ if (
+ typeof _wp_google_translate_widget === 'object' &&
+ typeof _wp_google_translate_widget.lang === 'string'
+ ) {
+ lang = _wp_google_translate_widget.lang;
+ }
+ langParam = window.location.href.match( langRegex );
+ if ( langParam ) {
+ window.location.href =
+ window.location.href.replace( langRegex, '' ).replace( /#googtrans\([a-zA-Z\-_|]+\)/, '' ) +
+ '#googtrans(' +
+ lang +
+ '|' +
+ langParam[ 1 ] +
+ ')';
+ }
+ new google.translate.TranslateElement(
+ {
+ pageLanguage: lang,
+ layout: _wp_google_translate_widget.layout,
+ autoDisplay: false,
+ },
+ 'google_translate_element'
+ );
+}
diff --git a/plugins/jetpack/modules/widgets/gravatar-profile.css b/plugins/jetpack/modules/widgets/gravatar-profile.css
new file mode 100644
index 00000000..5316f6a3
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gravatar-profile.css
@@ -0,0 +1,46 @@
+.widget-grofile {
+}
+ .widget-grofile h4 {
+ margin: 1em 0 .5em;
+ }
+ .widget-grofile ul.grofile-urls {
+ margin-left: 0;
+ overflow: hidden;
+ }
+ .widget-grofile ul.grofile-accounts li {
+ list-style: none;
+ display: inline;
+ }
+
+ .widget-grofile ul.grofile-accounts li::before {
+ content: "" !important; /* Kubrick :( */
+ }
+ .widget-grofile .grofile-accounts-logo {
+ background-image: url('https://secure.gravatar.com/images/grav-share-sprite.png');
+ background-repeat: no-repeat;
+ /*background-position: -16px -16px;*/
+ width: 16px; /* So we don't show the topmost logo */
+ height: 16px; /* So we don't show the topmost logo */
+ float: left;
+ margin-right: 8px;
+ margin-bottom: 8px;
+ }
+ .rtl .widget-grofile .grofile-accounts-logo {
+ margin-left: 8px;
+ margin-right: 0;
+ }
+
+ .grofile-thumbnail {
+ width: 500px;
+ max-width: 100%;
+ }
+ @media
+only screen and (-webkit-min-device-pixel-ratio: 1.5),
+only screen and (-o-min-device-pixel-ratio: 3/2),
+only screen and (min--moz-device-pixel-ratio: 1.5),
+only screen and (min-device-pixel-ratio: 1.5) {
+ .widget-grofile .grofile-accounts-logo {
+ background-image: url('https://secure.gravatar.com/images/grav-share-sprite-2x.png');
+ background-size: 16px 784px;
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/gravatar-profile.php b/plugins/jetpack/modules/widgets/gravatar-profile.php
new file mode 100644
index 00000000..f5171665
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/gravatar-profile.php
@@ -0,0 +1,435 @@
+<?php
+
+/**
+ * Register the widget for use in Appearance -> Widgets
+ */
+add_action( 'widgets_init', 'jetpack_gravatar_profile_widget_init' );
+
+function jetpack_gravatar_profile_widget_init() {
+ register_widget( 'Jetpack_Gravatar_Profile_Widget' );
+}
+
+/**
+ * Display a widgetized version of your Gravatar Profile
+ * http://blog.gravatar.com/2010/03/26/gravatar-profiles/
+ */
+class Jetpack_Gravatar_Profile_Widget extends WP_Widget {
+
+ function __construct() {
+ parent::__construct(
+ 'grofile',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Gravatar Profile', 'jetpack' ) ),
+ array(
+ 'classname' => 'widget-grofile grofile',
+ 'description' => __( 'Display a mini version of your Gravatar Profile', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ if ( is_admin() ) {
+ add_action( 'admin_footer-widgets.php', array( $this, 'admin_script' ) );
+ }
+
+ if ( is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+ }
+ }
+
+ function widget( $args, $instance ) {
+ /**
+ * Fires when an item is displayed on the front end.
+ *
+ * Can be used to track stats about the number of displays for a specific item
+ *
+ * @module widgets, shortcodes
+ *
+ * @since 1.6.0
+ *
+ * @param string widget_view Item type (e.g. widget, or embed).
+ * @param string grofile Item description (e.g. grofile, goodreads).
+ */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'grofile' );
+
+ $instance = wp_parse_args(
+ $instance, array(
+ 'title' => '',
+ 'email' => '',
+ )
+ );
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+
+ if ( ! $instance['email'] ) {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ echo $args['before_widget'];
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . $title . $args['after_title'];
+ }
+ echo '<p>' . sprintf( __( 'You need to select what to show in this <a href="%s">Gravatar Profile widget</a>.', 'jetpack' ), admin_url( 'widgets.php' ) ) . '</p>';
+ echo $args['after_widget'];
+ }
+ return;
+ }
+
+ echo $args['before_widget'];
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . $title . $args['after_title'];
+ }
+
+ $profile = $this->get_profile( $instance['email'] );
+
+ if ( ! empty( $profile ) ) {
+ $profile = wp_parse_args(
+ $profile, array(
+ 'thumbnailUrl' => '',
+ 'profileUrl' => '',
+ 'displayName' => '',
+ 'aboutMe' => '',
+ 'urls' => array(),
+ 'accounts' => array(),
+ )
+ );
+ $gravatar_url = add_query_arg( 's', 320, $profile['thumbnailUrl'] ); // the default grav returned by grofiles is super small
+
+ // Enqueue front end assets.
+ $this->enqueue_scripts();
+
+ ?>
+ <img src="<?php echo esc_url( $gravatar_url ); ?>" class="grofile-thumbnail no-grav" alt="<?php echo esc_attr( $profile['displayName'] ); ?>" />
+ <div class="grofile-meta">
+ <h4><a href="<?php echo esc_url( $profile['profileUrl'] ); ?>"><?php echo esc_html( $profile['displayName'] ); ?></a></h4>
+ <p><?php echo wp_kses_post( $profile['aboutMe'] ); ?></p>
+ </div>
+
+ <?php
+
+ if ( $instance['show_personal_links'] ) {
+ $this->display_personal_links( (array) $profile['urls'] );
+ }
+
+ if ( $instance['show_account_links'] ) {
+ $this->display_accounts( (array) $profile['accounts'] );
+ }
+
+ ?>
+
+ <p><a href="<?php echo esc_url( $profile['profileUrl'] ); ?>" class="grofile-full-link">
+ <?php
+ echo esc_html(
+ /**
+ * Filter the Gravatar Profile widget's profile link title.
+ *
+ * @module widgets
+ *
+ * @since 2.8.0
+ *
+ * @param string $str Profile link title.
+ */
+ apply_filters(
+ 'jetpack_gravatar_full_profile_title',
+ __( 'View Full Profile &rarr;', 'jetpack' )
+ )
+ );
+ ?>
+ </a></p>
+
+ <?php
+ } else {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ echo '<p>' . esc_html__( 'Error loading profile', 'jetpack' ) . '</p>';
+ }
+ }
+
+ echo $args['after_widget'];
+ }
+
+ function display_personal_links( $personal_links = array() ) {
+ if ( empty( $personal_links ) ) {
+ return;
+ }
+ ?>
+
+ <h4>
+ <?php
+ echo esc_html(
+ apply_filters(
+ /**
+ * Filter the Gravatar Profile widget's "Personal Links" section title.
+ *
+ * @module widgets
+ *
+ * @since 2.8.0
+ *
+ * @param string $str "Personal Links" section title.
+ */
+ 'jetpack_gravatar_personal_links_title',
+ __( 'Personal Links', 'jetpack' )
+ )
+ );
+ ?>
+ </h4>
+ <ul class="grofile-urls grofile-links">
+
+ <?php foreach ( $personal_links as $personal_link ) : ?>
+ <li>
+ <a href="<?php echo esc_url( $personal_link['value'] ); ?>">
+ <?php
+ $link_title = ( ! empty( $personal_link['title'] ) ) ? $personal_link['title'] : $personal_link['value'];
+ echo esc_html( $link_title );
+ ?>
+ </a>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+
+ <?php
+ }
+
+ function display_accounts( $accounts = array() ) {
+ if ( empty( $accounts ) ) {
+ return;
+ }
+ ?>
+
+ <h4>
+ <?php
+ echo esc_html(
+ /**
+ * Filter the Gravatar Profile widget's "Verified Services" section title.
+ *
+ * @module widgets
+ *
+ * @since 2.8.0
+ *
+ * @param string $str "Verified Services" section title.
+ */
+ apply_filters(
+ 'jetpack_gravatar_verified_services_title',
+ __( 'Verified Services', 'jetpack' )
+ )
+ );
+ ?>
+ </h4>
+ <ul class="grofile-urls grofile-accounts">
+
+ <?php
+ foreach ( $accounts as $account ) :
+ if ( $account['verified'] != 'true' ) {
+ continue;
+ }
+
+ $sanitized_service_name = $this->get_sanitized_service_name( $account['shortname'] );
+ ?>
+
+ <li>
+ <a href="<?php echo esc_url( $account['url'] ); ?>" title="<?php echo sprintf( _x( '%1$s on %2$s', '1: User Name, 2: Service Name (Facebook, Twitter, ...)', 'jetpack' ), esc_html( $account['display'] ), esc_html( $sanitized_service_name ) ); ?>">
+ <span class="grofile-accounts-logo grofile-accounts-<?php echo esc_attr( $account['shortname'] ); ?> accounts_<?php echo esc_attr( $account['shortname'] ); ?>"></span>
+ </a>
+ </li>
+
+ <?php endforeach; ?>
+ </ul>
+
+ <?php
+ }
+
+ /**
+ * Enqueue CSS and JavaScript.
+ *
+ * @since 4.0.0
+ */
+ function enqueue_scripts() {
+ wp_enqueue_style(
+ 'gravatar-profile-widget',
+ plugins_url( 'gravatar-profile.css', __FILE__ ),
+ array(),
+ '20120711'
+ );
+
+ wp_enqueue_style(
+ 'gravatar-card-services',
+ 'https://secure.gravatar.com/css/services.css',
+ array(),
+ defined( 'GROFILES__CACHE_BUSTER' ) ? GROFILES__CACHE_BUSTER : gmdate( 'YW' )
+ );
+ }
+
+ function form( $instance ) {
+ $title = isset( $instance['title'] ) ? $instance['title'] : '';
+ $email = isset( $instance['email'] ) ? $instance['email'] : '';
+ $email_user = isset( $instance['email_user'] ) ? $instance['email_user'] : get_current_user_id();
+ $show_personal_links = isset( $instance['show_personal_links'] ) ? (bool) $instance['show_personal_links'] : '';
+ $show_account_links = isset( $instance['show_account_links'] ) ? (bool) $instance['show_account_links'] : '';
+ $profile_url = 'https://gravatar.com/profile/edit';
+
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $profile_url = admin_url( 'profile.php' );
+
+ if ( isset( $_REQUEST['calypso'] ) ) {
+ $profile_url = 'https://wordpress.com/me';
+ }
+ }
+ ?>
+ <p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>">
+ <?php esc_html_e( 'Title', 'jetpack' ); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'email_user' ); ?>">
+ <?php esc_html_e( 'Select a user or pick "custom" and enter a custom email address.', 'jetpack' ); ?>
+ <br />
+
+ <?php
+ wp_dropdown_users(
+ array(
+ 'show_option_none' => __( 'Custom', 'jetpack' ),
+ 'selected' => $email_user,
+ 'name' => $this->get_field_name( 'email_user' ),
+ 'id' => $this->get_field_id( 'email_user' ),
+ 'class' => 'gravatar-profile-user-select',
+ )
+ );
+ ?>
+ </label>
+ </p>
+
+ <p class="gprofile-email-container <?php echo empty( $email_user ) || $email_user == -1 ? '' : 'hidden'; ?>">
+ <label for="<?php echo $this->get_field_id( 'email' ); ?>"><?php esc_html_e( 'Custom Email Address', 'jetpack' ); ?>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'email' ); ?>" name="<?php echo $this->get_field_name( 'email' ); ?>" type="text" value="<?php echo esc_attr( $email ); ?>" />
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'show_personal_links' ); ?>">
+ <input type="checkbox" name="<?php echo $this->get_field_name( 'show_personal_links' ); ?>" id="<?php echo $this->get_field_id( 'show_personal_links' ); ?>" <?php checked( $show_personal_links ); ?> />
+ <?php esc_html_e( 'Show Personal Links', 'jetpack' ); ?>
+ <br />
+ <small><?php esc_html_e( 'Links to your websites, blogs, or any other sites that help describe who you are.', 'jetpack' ); ?></small>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'show_account_links' ); ?>">
+ <input type="checkbox" name="<?php echo $this->get_field_name( 'show_account_links' ); ?>" id="<?php echo $this->get_field_id( 'show_account_links' ); ?>" <?php checked( $show_account_links ); ?> />
+ <?php esc_html_e( 'Show Account Links', 'jetpack' ); ?>
+ <br />
+ <small><?php esc_html_e( 'Links to services that you use across the web.', 'jetpack' ); ?></small>
+ </label>
+ </p>
+
+ <p><a href="<?php echo esc_url( $profile_url ); ?>" target="_blank" title="<?php esc_attr_e( 'Opens in new window', 'jetpack' ); ?>"><?php esc_html_e( 'Edit Your Profile', 'jetpack' ); ?></a> | <a href="https://gravatar.com" target="_blank" title="<?php esc_attr_e( 'Opens in new window', 'jetpack' ); ?>"><?php esc_html_e( "What's a Gravatar?", 'jetpack' ); ?></a></p>
+
+ <?php
+ }
+
+ function admin_script() {
+ ?>
+ <script>
+ jQuery( function( $ ) {
+ $( '.wrap' ).on( 'change', '.gravatar-profile-user-select', function() {
+ var $input = $(this).closest('.widget-inside').find('.gprofile-email-container');
+ if ( '-1' === this.value.toLowerCase() ) {
+ $input.show();
+ } else {
+ $input.hide();
+ }
+ });
+ } );
+ </script>
+ <?php
+ }
+
+ function update( $new_instance, $old_instance ) {
+
+ $instance = array();
+
+ $instance['title'] = isset( $new_instance['title'] ) ? wp_kses( $new_instance['title'], array() ) : '';
+ $instance['email'] = isset( $new_instance['email'] ) ? wp_kses( $new_instance['email'], array() ) : '';
+ $instance['email_user'] = isset( $new_instance['email_user'] ) ? intval( $new_instance['email_user'] ) : -1;
+ $instance['show_personal_links'] = isset( $new_instance['show_personal_links'] ) ? (bool) $new_instance['show_personal_links'] : false;
+ $instance['show_account_links'] = isset( $new_instance['show_account_links'] ) ? (bool) $new_instance['show_account_links'] : false;
+
+ if ( $instance['email_user'] > 0 ) {
+ $user = get_userdata( $instance['email_user'] );
+ $instance['email'] = $user->user_email;
+ }
+
+ $hashed_email = md5( strtolower( trim( $instance['email'] ) ) );
+ $cache_key = 'grofile-' . $hashed_email;
+ delete_transient( $cache_key );
+
+ return $instance;
+ }
+
+ private function get_profile( $email ) {
+ $hashed_email = md5( strtolower( trim( $email ) ) );
+ $cache_key = 'grofile-' . $hashed_email;
+
+ if ( ! $profile = get_transient( $cache_key ) ) {
+ $profile_url = sprintf(
+ 'https://secure.gravatar.com/%s.json',
+ $hashed_email
+ );
+
+ $expire = 300;
+ $response = wp_remote_get(
+ esc_url_raw( $profile_url ),
+ array( 'User-Agent' => 'WordPress.com Gravatar Profile Widget' )
+ );
+ $response_code = wp_remote_retrieve_response_code( $response );
+ if ( 200 == $response_code ) {
+ $profile = wp_remote_retrieve_body( $response );
+ $profile = json_decode( $profile, true );
+
+ if ( is_array( $profile ) && ! empty( $profile['entry'] ) && is_array( $profile['entry'] ) ) {
+ $expire = 900; // cache for 15 minutes
+ $profile = $profile['entry'][0];
+ } else {
+ // Something strange happened. Cache for 5 minutes.
+ $profile = array();
+ }
+ } else {
+ $expire = 900; // cache for 15 minutes
+ $profile = array();
+ }
+
+ set_transient( $cache_key, $profile, $expire );
+ }
+ return $profile;
+ }
+
+ private function get_sanitized_service_name( $shortname ) {
+ // Some services have stylized or mixed cap names *cough* WP *cough*
+ switch ( $shortname ) {
+ case 'friendfeed':
+ return 'FriendFeed';
+ case 'linkedin':
+ return 'LinkedIn';
+ case 'yahoo':
+ return 'Yahoo!';
+ case 'youtube':
+ return 'YouTube';
+ // phpcs:ignore WordPress.WP.CapitalPDangit
+ case 'wordpress':
+ return 'WordPress';
+ case 'tripit':
+ return 'TripIt';
+ case 'myspace':
+ return 'MySpace';
+ case 'foursquare':
+ return 'foursquare';
+ case 'google':
+ return 'Google+';
+ default:
+ // Others don't
+ $shortname = ucwords( $shortname );
+ }
+ return $shortname;
+ }
+}
+
+// END
diff --git a/plugins/jetpack/modules/widgets/image-widget.php b/plugins/jetpack/modules/widgets/image-widget.php
new file mode 100644
index 00000000..37a353c1
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/image-widget.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Module Name: Image Widget
+ * Module Description: Easily add images to your theme's sidebar.
+ * Sort Order: 20
+ * First Introduced: 1.2
+ */
+
+/**
+* Register the widget for use in Appearance -> Widgets
+*/
+add_action( 'widgets_init', 'jetpack_image_widget_init', 11 );
+function jetpack_image_widget_init() {
+ if ( class_exists( 'WP_Widget_Media_Image' ) && Jetpack_Options::get_option( 'image_widget_migration' ) ) {
+ return;
+ }
+ register_widget( 'Jetpack_Image_Widget' );
+}
+
+class Jetpack_Image_Widget extends WP_Widget {
+ /**
+ * Register widget with WordPress.
+ */
+ public function __construct() {
+ parent::__construct(
+ 'image',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'Image', 'jetpack' ) ),
+ array(
+ 'classname' => 'widget_image',
+ 'description' => __( 'Display an image in your sidebar', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_active_widget( false, false, 'monster' ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
+ }
+ }
+
+ /**
+ * Loads file for front-end widget style.
+ *
+ * @uses wp_enqueue_style(), plugins_url()
+ */
+ public function enqueue_style() {
+ wp_enqueue_style( 'jetpack_image_widget', plugins_url( 'image-widget/style.css', __FILE__ ), array(), '20140808' );
+ }
+
+ /**
+ * Front-end display of widget.
+ *
+ * @see WP_Widget::widget()
+ *
+ * @param array $args Widget arguments.
+ * @param array $instance Saved values from database.
+ */
+ public function widget( $args, $instance ) {
+ echo $args['before_widget'];
+
+ $instance = wp_parse_args(
+ $instance, array(
+ 'title' => '',
+ 'img_url' => '',
+ )
+ );
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+
+ if ( $title ) {
+ echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
+ }
+
+ if ( '' != $instance['img_url'] ) {
+
+ $output = '<img src="' . esc_url( $instance['img_url'] ) . '" ';
+
+ if ( '' != $instance['alt_text'] ) {
+ $output .= 'alt="' . esc_attr( $instance['alt_text'] ) . '" ';
+ }
+ if ( '' != $instance['img_title'] ) {
+ $output .= 'title="' . esc_attr( $instance['img_title'] ) . '" ';
+ }
+ if ( '' == $instance['caption'] ) {
+ $output .= 'class="align' . esc_attr( $instance['align'] ) . '" ';
+ }
+ if ( '' != $instance['img_width'] ) {
+ $output .= 'width="' . esc_attr( $instance['img_width'] ) . '" ';
+ }
+ if ( '' != $instance['img_height'] ) {
+ $output .= 'height="' . esc_attr( $instance['img_height'] ) . '" ';
+ }
+ $output .= '/>';
+
+ if ( class_exists( 'Jetpack_Photon' ) && Jetpack::is_module_active( 'photon' ) ) {
+ $output = Jetpack_Photon::filter_the_content( $output );
+ }
+
+ if ( '' != $instance['link'] ) {
+ $target = ! empty( $instance['link_target_blank'] )
+ ? 'target="_blank"'
+ : '';
+ $output = '<a ' . $target . ' href="' . esc_url( $instance['link'] ) . '">' . $output . '</a>';
+ }
+ if ( '' != $instance['caption'] ) {
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $caption = apply_filters( 'widget_text', $instance['caption'] );
+ $img_width = ( ! empty( $instance['img_width'] ) ? 'style="width: ' . esc_attr( $instance['img_width'] ) . 'px"' : '' );
+ $output = '<figure ' . $img_width . ' class="wp-caption align' . esc_attr( $instance['align'] ) . '">
+ ' . $output . '
+ <figcaption class="wp-caption-text">' . $caption . '</figcaption>
+ </figure>'; // wp_kses_post caption on update
+ }
+ echo '<div class="jetpack-image-container">' . do_shortcode( $output ) . '</div>';
+ } else {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ echo '<p>' . sprintf( __( 'Image missing or invalid URL. Please check the Image widget URL in your <a href="%s">widget settings</a>.', 'jetpack' ), admin_url( 'widgets.php' ) ) . '</p>';
+ }
+ }
+
+ echo "\n" . $args['after_widget'];
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'image' );
+ }
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @see WP_Widget::update()
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ *
+ * @return array Updated safe values to be saved.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $allowed_caption_html = array(
+ 'a' => array(
+ 'href' => array(),
+ 'title' => array(),
+ ),
+ 'b' => array(),
+ 'em' => array(),
+ 'i' => array(),
+ 'p' => array(),
+ 'strong' => array(),
+ );
+
+ $instance = $old_instance;
+
+ $instance['title'] = strip_tags( $new_instance['title'] );
+ $instance['img_url'] = esc_url( trim( $new_instance['img_url'] ) );
+ $instance['alt_text'] = strip_tags( $new_instance['alt_text'] );
+ $instance['img_title'] = strip_tags( $new_instance['img_title'] );
+ $instance['caption'] = wp_kses( stripslashes( $new_instance['caption'] ), $allowed_caption_html );
+ $instance['align'] = $new_instance['align'];
+ $instance['link'] = esc_url( trim( $new_instance['link'] ) );
+ $instance['link_target_blank'] = isset( $new_instance['link_target_blank'] ) ? (bool) $new_instance['link_target_blank'] : false;
+
+ $new_img_width = absint( $new_instance['img_width'] );
+ $new_img_height = absint( $new_instance['img_height'] );
+
+ if ( ! empty( $instance['img_url'] ) && '' == $new_img_width && '' == $new_img_height ) {
+ // Download the url to a local temp file and then process it with getimagesize so we can optimize browser layout
+ $tmp_file = download_url( $instance['img_url'], 10 );
+ if ( ! is_wp_error( $tmp_file ) ) {
+ $size = getimagesize( $tmp_file );
+
+ $width = $size[0];
+ $instance['img_width'] = absint( $width );
+
+ $height = $size[1];
+ $instance['img_height'] = absint( $height );
+
+ unlink( $tmp_file );
+ } else {
+ $instance['img_width'] = $new_img_width;
+ $instance['img_height'] = $new_img_height;
+ }
+ } else {
+ $instance['img_width'] = $new_img_width;
+ $instance['img_height'] = $new_img_height;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Back end widget form.
+ *
+ * @see WP_Widget::form()
+ *
+ * @param array $instance Previously saved values from database.
+ */
+ public function form( $instance ) {
+ // Defaults
+ $instance = wp_parse_args(
+ (array) $instance, array(
+ 'title' => '',
+ 'img_url' => '',
+ 'alt_text' => '',
+ 'img_title' => '',
+ 'caption' => '',
+ 'align' => 'none',
+ 'img_width' => '',
+ 'img_height' => '',
+ 'link' => '',
+ 'link_target_blank' => false,
+ )
+ );
+
+ $title = esc_attr( $instance['title'] );
+ $img_url = esc_url( $instance['img_url'], null, 'display' );
+ $alt_text = esc_attr( $instance['alt_text'] );
+ $img_title = esc_attr( $instance['img_title'] );
+ $caption = esc_textarea( $instance['caption'] );
+ $align = esc_attr( $instance['align'] );
+ $img_width = esc_attr( $instance['img_width'] );
+ $img_height = esc_attr( $instance['img_height'] );
+ $link_target_blank = checked( $instance['link_target_blank'], true, false );
+
+ $link = esc_url( $instance['link'], null, 'display' );
+
+ echo '<p><label for="' . $this->get_field_id( 'title' ) . '">' . esc_html__( 'Widget title:', 'jetpack' ) . '
+ <input class="widefat" id="' . $this->get_field_id( 'title' ) . '" name="' . $this->get_field_name( 'title' ) . '" type="text" value="' . $title . '" />
+ </label></p>
+ <p><label for="' . $this->get_field_id( 'img_url' ) . '">' . esc_html__( 'Image URL:', 'jetpack' ) . '
+ <input class="widefat" id="' . $this->get_field_id( 'img_url' ) . '" name="' . $this->get_field_name( 'img_url' ) . '" type="text" value="' . $img_url . '" />
+ </label></p>
+ <p><label for="' . $this->get_field_id( 'alt_text' ) . '">' . esc_html__( 'Alternate text:', 'jetpack' ) . ' <a href="http://support.wordpress.com/widgets/image-widget/#image-widget-alt-text" target="_blank">( ? )</a>
+ <input class="widefat" id="' . $this->get_field_id( 'alt_text' ) . '" name="' . $this->get_field_name( 'alt_text' ) . '" type="text" value="' . $alt_text . '" />
+ </label></p>
+ <p><label for="' . $this->get_field_id( 'img_title' ) . '">' . esc_html__( 'Image title:', 'jetpack' ) . ' <a href="http://support.wordpress.com/widgets/image-widget/#image-widget-title" target="_blank">( ? )</a>
+ <input class="widefat" id="' . $this->get_field_id( 'img_title' ) . '" name="' . $this->get_field_name( 'img_title' ) . '" type="text" value="' . $img_title . '" />
+ </label></p>
+ <p><label for="' . $this->get_field_id( 'caption' ) . '">' . esc_html__( 'Caption:', 'jetpack' ) . ' <a href="http://support.wordpress.com/widgets/image-widget/#image-widget-caption" target="_blank">( ? )</a>
+ <textarea class="widefat" id="' . $this->get_field_id( 'caption' ) . '" name="' . $this->get_field_name( 'caption' ) . '" rows="2" cols="20">' . $caption . '</textarea>
+ </label></p>';
+
+ $alignments = array(
+ 'none' => __( 'None', 'jetpack' ),
+ 'left' => __( 'Left', 'jetpack' ),
+ 'center' => __( 'Center', 'jetpack' ),
+ 'right' => __( 'Right', 'jetpack' ),
+ );
+ echo '<p><label for="' . $this->get_field_id( 'align' ) . '">' . esc_html__( 'Image Alignment:', 'jetpack' ) . '
+ <select id="' . $this->get_field_id( 'align' ) . '" name="' . $this->get_field_name( 'align' ) . '">';
+ foreach ( $alignments as $alignment => $alignment_name ) {
+ echo '<option value="' . esc_attr( $alignment ) . '" ';
+ if ( $alignment == $align ) {
+ echo 'selected="selected" ';
+ }
+ echo '>' . esc_html( $alignment_name ) . "</option>\n";
+ }
+ echo '</select></label></p>';
+
+ echo '<p><label for="' . $this->get_field_id( 'img_width' ) . '">' . esc_html__( 'Width in pixels:', 'jetpack' ) . '
+ <input size="3" id="' . $this->get_field_id( 'img_width' ) . '" name="' . $this->get_field_name( 'img_width' ) . '" type="text" value="' . $img_width . '" />
+ </label>
+ <label for="' . $this->get_field_id( 'img_height' ) . '">' . esc_html__( 'Height in pixels:', 'jetpack' ) . '
+ <input size="3" id="' . $this->get_field_id( 'img_height' ) . '" name="' . $this->get_field_name( 'img_height' ) . '" type="text" value="' . $img_height . '" />
+ </label><br />
+ <small>' . esc_html__( 'If empty, we will attempt to determine the image size.', 'jetpack' ) . '</small></p>
+ <p><label for="' . $this->get_field_id( 'link' ) . '">' . esc_html__( 'Link URL (when the image is clicked):', 'jetpack' ) . '
+ <input class="widefat" id="' . $this->get_field_id( 'link' ) . '" name="' . $this->get_field_name( 'link' ) . '" type="text" value="' . $link . '" />
+ </label>
+ <label for="' . $this->get_field_id( 'link_target_blank' ) . '">
+ <input type="checkbox" name="' . $this->get_field_name( 'link_target_blank' ) . '" id="' . $this->get_field_id( 'link_target_blank' ) . '" value="1"' . $link_target_blank . '/>
+ ' . esc_html__( 'Open link in a new window/tab', 'jetpack' ) . '
+ </label></p>';
+ }
+} // Class Jetpack_Image_Widget
diff --git a/plugins/jetpack/modules/widgets/image-widget/style.css b/plugins/jetpack/modules/widgets/image-widget/style.css
new file mode 100644
index 00000000..ae650f2a
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/image-widget/style.css
@@ -0,0 +1,13 @@
+/*
+ * Image Widget styles for Jetpack
+ */
+
+/* Clear floats */
+.jetpack-image-container:after {
+ clear: both;
+}
+.jetpack-image-container:before,
+.jetpack-image-container:after {
+ display: table;
+ content: "";
+}
diff --git a/plugins/jetpack/modules/widgets/internet-defense-league.php b/plugins/jetpack/modules/widgets/internet-defense-league.php
new file mode 100644
index 00000000..9a69d1ab
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/internet-defense-league.php
@@ -0,0 +1,153 @@
+<?php
+
+class Jetpack_Internet_Defense_League_Widget extends WP_Widget {
+
+ public $defaults = array();
+
+ public $variant;
+ public $variants = array();
+
+ public $campaign;
+ public $campaigns = array();
+ public $no_current = true;
+
+ public $badge;
+ public $badges = array();
+
+ function __construct() {
+ parent::__construct(
+ 'internet_defense_league_widget',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'Internet Defense League', 'jetpack' ) ),
+ array(
+ 'description' => esc_html__( 'Show your support for the Internet Defense League.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ // When enabling campaigns other than 'none' or empty, change $no_current to false above.
+ $this->campaigns = array(
+ '' => esc_html__( 'All current and future campaigns', 'jetpack' ),
+ 'none' => esc_html__( 'None, just display the badge please', 'jetpack' ),
+ );
+
+ $this->variants = array(
+ 'banner' => esc_html__( 'Banner at the top of my site', 'jetpack' ),
+ 'modal' => esc_html__( 'Modal (Overlay Box)', 'jetpack' ),
+ );
+
+ $this->badges = array(
+ 'shield_badge' => esc_html__( 'Shield Badge', 'jetpack' ),
+ 'super_badge' => esc_html__( 'Super Badge', 'jetpack' ),
+ 'side_bar_badge' => esc_html__( 'Red Cat Badge', 'jetpack' ),
+ );
+
+ if ( $this->no_current === false ) {
+ $this->badges['none'] = esc_html__( 'Don\'t display a badge (just the campaign)', 'jetpack' );
+ }
+
+ $this->defaults = array(
+ 'campaign' => key( $this->campaigns ),
+ 'variant' => key( $this->variants ),
+ 'badge' => key( $this->badges ),
+ );
+ }
+
+ public function widget( $args, $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults );
+
+ if ( 'none' != $instance['badge'] ) {
+ if ( ! isset( $this->badges[ $instance['badge'] ] ) ) {
+ $instance['badge'] = $this->defaults['badge'];
+ }
+ $badge_url = esc_url( 'https://internetdefenseleague.org/images/badges/final/' . $instance['badge'] . '.png' );
+ $photon_badge_url = jetpack_photon_url( $badge_url );
+ $alt_text = esc_html__( 'Member of The Internet Defense League', 'jetpack' );
+ echo $args['before_widget'];
+ echo '<p><a href="https://internetdefenseleague.org/"><img src="' . $photon_badge_url . '" alt="' . $alt_text . '" style="max-width: 100%; height: auto;" /></a></p>';
+ echo $args['after_widget'];
+ }
+
+ if ( 'none' != $instance['campaign'] ) {
+ $this->campaign = $instance['campaign'];
+ $this->variant = $instance['variant'];
+ add_action( 'wp_footer', array( $this, 'footer_script' ) );
+ }
+
+ /** This action is already documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'internet_defense_league' );
+ }
+
+ public function footer_script() {
+ if ( ! isset( $this->campaigns[ $this->campaign ] ) ) {
+ $this->campaign = $this->defaults['campaign'];
+ }
+
+ if ( ! isset( $this->variants[ $this->variant ] ) ) {
+ $this->variant = $this->defaults['variant'];
+ }
+ ?>
+ <script type="text/javascript">
+ window._idl = {};
+ _idl.campaign = "<?php echo esc_js( $this->campaign ); ?>";
+ _idl.variant = "<?php echo esc_js( $this->variant ); ?>";
+ (function() {
+ var idl = document.createElement('script');
+ idl.type = 'text/javascript';
+ idl.async = true;
+ idl.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'members.internetdefenseleague.org/include/?url=' + (_idl.url || '') + '&campaign=' + (_idl.campaign || '') + '&variant=' + (_idl.variant || 'banner');
+ document.getElementsByTagName('body')[0].appendChild(idl);
+ })();
+ </script>
+ <?php
+ }
+
+ public function form( $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults );
+
+ // Hide first two form fields if no current campaigns.
+ if ( false === $this->no_current ) {
+ echo '<p><label>';
+ echo esc_html__( 'Which Internet Defense League campaign do you want to participate in?', 'jetpack' ) . '<br />';
+ $this->select( 'campaign', $this->campaigns, $instance['campaign'] );
+ echo '</label></p>';
+
+ echo '<p><label>';
+ echo esc_html__( 'How do you want to promote the campaign?', 'jetpack' ) . '<br />';
+ $this->select( 'variant', $this->variants, $instance['variant'] );
+ echo '</label></p>';
+ }
+
+ echo '<p><label>';
+ echo esc_html__( 'Which badge would you like to display?', 'jetpack' ) . '<br />';
+ $this->select( 'badge', $this->badges, $instance['badge'] );
+ echo '</label></p>';
+
+ /* translators: %s is a name of an internet campaign called the "Internet Defense League" */
+ echo '<p>' . sprintf( _x( 'Learn more about the %s', 'the Internet Defense League', 'jetpack' ), '<a href="https://www.internetdefenseleague.org/">Internet Defense League</a>' ) . '</p>';
+ }
+
+ public function select( $field_name, $options, $default = null ) {
+ echo '<select class="widefat" name="' . $this->get_field_name( $field_name ) . '">';
+ foreach ( $options as $option_slug => $option_name ) {
+ echo '<option value="' . esc_attr( $option_slug ) . '"' . selected( $option_slug, $default, false ) . '>' . esc_html( $option_name ) . '</option>';
+ }
+ echo '</select>';
+ }
+
+ public function update( $new_instance, $old_instance ) {
+ $instance = array();
+
+ $instance['campaign'] = ( isset( $new_instance['campaign'] ) && isset( $this->campaigns[ $new_instance['campaign'] ] ) ) ? $new_instance['campaign'] : $this->defaults['campaign'];
+ $instance['variant'] = ( isset( $new_instance['variant'] ) && isset( $this->variants[ $new_instance['variant'] ] ) ) ? $new_instance['variant'] : $this->defaults['variant'];
+ $instance['badge'] = ( isset( $new_instance['badge'] ) && isset( $this->badges[ $new_instance['badge'] ] ) ) ? $new_instance['badge'] : $this->defaults['badge'];
+
+ return $instance;
+ }
+}
+
+function jetpack_internet_defense_league_init() {
+ register_widget( 'Jetpack_Internet_Defense_League_Widget' );
+}
+
+add_action( 'widgets_init', 'jetpack_internet_defense_league_init' );
diff --git a/plugins/jetpack/modules/widgets/mailchimp.php b/plugins/jetpack/modules/widgets/mailchimp.php
new file mode 100644
index 00000000..a2aff1ba
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/mailchimp.php
@@ -0,0 +1,103 @@
+<?php
+
+if ( ! class_exists( 'Jetpack_MailChimp_Subscriber_Popup_Widget' ) ) {
+
+ if ( ! class_exists( 'MailChimp_Subscriber_Popup' ) ) {
+ include_once JETPACK__PLUGIN_DIR . 'modules/shortcodes/mailchimp.php';
+ }
+
+ //register MailChimp Subscriber Popup widget
+ function jetpack_mailchimp_subscriber_popup_widget_init() {
+ register_widget( 'Jetpack_MailChimp_Subscriber_Popup_Widget' );
+ }
+
+ add_action( 'widgets_init', 'jetpack_mailchimp_subscriber_popup_widget_init' );
+
+ /**
+ * Add a MailChimp subscription form.
+ */
+ class Jetpack_MailChimp_Subscriber_Popup_Widget extends WP_Widget {
+
+ /**
+ * Constructor
+ */
+ function __construct() {
+ parent::__construct(
+ 'widget_mailchimp_subscriber_popup',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'MailChimp Subscriber Popup', 'jetpack' ) ),
+ array(
+ 'classname' => 'widget_mailchimp_subscriber_popup',
+ 'description' => __( 'Allows displaying a popup subscription form to visitors.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+ }
+
+ /**
+ * Outputs the HTML for this widget.
+ *
+ * @param array $args An array of standard parameters for widgets in this theme
+ * @param array $instance An array of settings for this widget instance
+ *
+ * @return void Echoes it's output
+ **/
+ function widget( $args, $instance ) {
+ $instance = wp_parse_args( $instance, array( 'code' => '' ) );
+
+ // Regular expresion that will match maichimp shortcode.
+ $regex = '(\[mailchimp_subscriber_popup[^\]]+\])';
+
+ // Check if the shortcode exists.
+ preg_match( $regex, $instance['code'], $matches );
+
+ // Process the shortcode only, if exists.
+ if ( ! empty( $matches[0] ) ) {
+ echo do_shortcode( $matches[0] );
+ }
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'mailchimp_subscriber_popup' );
+ }
+
+
+ /**
+ * Deals with the settings when they are saved by the admin.
+ *
+ * @param array $new_instance New configuration values
+ * @param array $old_instance Old configuration values
+ *
+ * @return array
+ */
+ function update( $new_instance, $old_instance ) {
+ $instance = array();
+ $instance['code'] = MailChimp_Subscriber_Popup::reversal( $new_instance['code'] );
+
+ return $instance;
+ }
+
+
+ /**
+ * Displays the form for this widget on the Widgets page of the WP Admin area.
+ *
+ * @param array $instance Instance configuration.
+ *
+ * @return void
+ */
+ function form( $instance ) {
+ $instance = wp_parse_args( $instance, array( 'code' => '' ) );
+ ?>
+
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'code' ) ); ?>">
+ <?php printf( __( 'Code: <a href="%s" target="_blank">( ? )</a>', 'jetpack' ), 'https://en.support.wordpress.com/mailchimp/' ); ?>
+ </label>
+ <textarea class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'code' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'code' ) ); ?>" rows="3"><?php echo esc_textarea( $instance['code'] ); ?></textarea>
+ </p>
+
+ <?php
+ }
+
+ }
+
+}
diff --git a/plugins/jetpack/modules/widgets/migrate-to-core/gallery-widget.php b/plugins/jetpack/modules/widgets/migrate-to-core/gallery-widget.php
new file mode 100644
index 00000000..8cf2900f
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/migrate-to-core/gallery-widget.php
@@ -0,0 +1,198 @@
+<?php
+/**
+ * Migration from Jetpack's Gallery Widget to WordPress' Core Gallery Widget.
+ *
+ * @since 5.5
+ *
+ * @package Jetpack
+ */
+/**
+ * Migrates all active instances of Jetpack's Gallery widget to Core's Media Gallery widget.
+ */
+function jetpack_migrate_gallery_widget() {
+ // Only trigger the migration from wp-admin and outside unit tests
+ if ( ! is_admin() || defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
+ return;
+ }
+
+ // Only migrate if the new widget is available and we haven't yet migrated
+ if ( ! class_exists( 'WP_Widget_Media_Gallery' ) || Jetpack_Options::get_option( 'gallery_widget_migration' ) ) {
+ return;
+ }
+
+ $old_widgets = get_option( 'widget_gallery', array() );
+ $media_gallery = get_option( 'widget_media_gallery', array() );
+ $sidebars_widgets = wp_get_sidebars_widgets();
+
+ // Array to store legacy widget ids in to unregister on success.
+ $widgets_to_unregister = array();
+
+ $old_widgets = array_filter( $old_widgets, 'jetpack_migrate_gallery_widget_is_importable' );
+ foreach ( $old_widgets as $id => $widget ) {
+ $new_id = $id;
+ // Try to get an unique id for the new type of widget.
+ // It may be the case that the user has already created a core Gallery Widget
+ // before the migration begins. (Maybe Jetpack was deactivated during core's upgrade).
+ for( $i = 0; $i < 10 && in_array( $new_id, array_keys( $media_gallery ) ); $i++, $new_id++ );
+
+ $widget_copy = jetpack_migrate_gallery_widget_upgrade_widget( $widget );
+
+ if ( null === $widget_copy ) {
+ jetpack_migrate_gallery_widget_bump_stats( 'gallery-widget-skipped' );
+ continue;
+ }
+
+ $media_gallery[ $new_id ] = $widget_copy;
+
+ $sidebars_widgets = jetpack_migrate_gallery_widget_update_sidebars( $sidebars_widgets, $id, $new_id );
+
+ $widgets_to_unregister[ $id ] = $new_id;
+ }
+
+ if ( update_option( 'widget_media_gallery', $media_gallery ) ) {
+
+ // Now un-register old widgets and register new.
+ foreach ( $widgets_to_unregister as $id => $new_id ) {
+ wp_unregister_sidebar_widget( "gallery-${id}" );
+
+ // register new widget.
+ $media_gallery_widget = new WP_Widget_Media_Gallery();
+ $media_gallery_widget->_set( $new_id );
+ $media_gallery_widget->_register_one( $new_id );
+ }
+
+ wp_set_sidebars_widgets( $sidebars_widgets );
+
+ // Log if we migrated all, or some for this site.
+ foreach ( $widgets_to_unregister as $w ) {
+ jetpack_migrate_gallery_widget_bump_stats( 'gallery-widget-migrated' );
+ }
+
+ // We need to refresh on widgets page for changes to take effect.
+ // The jetpack_refresh_on_widget_page function is already defined in migrate-to-core/image-widget.php
+ add_action( 'current_screen', 'jetpack_refresh_on_widget_page' );
+ }
+ Jetpack_Options::update_option( 'gallery_widget_migration', true );
+}
+
+function jetpack_migrate_gallery_widget_is_importable( $widget ) {
+ // Can be caused by instantiating but not populating a widget in the Customizer.
+ if ( empty( $widget ) ) {
+ return false;
+ }
+ // The array as stored in the option constains two keys and one
+ // is a string `_multiwidget` which does not represent a widget, so we skip it
+ if ( ! is_array( $widget ) ) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Returns a transformed version of the Gallery Widget.
+ * Will return null if the widget is either empty, is not an array or has more keys than expected
+ *
+ * @param $widget One of the Jetpack Gallery widgets to be transformed into a new Core Media Gallery Widget
+ *
+ * @return array|null
+ */
+function jetpack_migrate_gallery_widget_upgrade_widget( $widget ) {
+ $whitelisted_keys = array(
+ 'ids' => '',
+ 'link' => '',
+ 'title' => '',
+ 'type' => '',
+ 'random' => '',
+ 'conditions' => '',
+ );
+
+ $default_data = array(
+ 'columns' => 3,
+ 'ids' => array(),
+ 'link_type' => '',
+ 'orderby_random' => false,
+ 'size' => 'thumbnail',
+ 'title' => '',
+ 'type' => '',
+ );
+
+ if ( ! jetpack_migrate_gallery_widget_is_importable( $widget ) ) {
+ return null;
+ }
+ // Ensure widget has no keys other than those expected.
+ // Not all widgets have conditions, so lets add it in.
+ $widget_copy = array_merge( array( 'conditions' => null ), $widget );
+ $non_whitelisted_keys = array_diff_key( $widget_copy, $whitelisted_keys );
+ if ( count( $non_whitelisted_keys ) > 0 ) {
+ jetpack_migrate_gallery_widget_bump_stats( 'extra-key' );
+
+ // Log the names of the keys not in our whitelist.
+ foreach ( $non_whitelisted_keys as $key => $value ) {
+ jetpack_migrate_gallery_widget_bump_stats( "extra-key-$key", "migration-extra-key" );
+ }
+ }
+
+ $widget_copy = array_merge( $default_data, $widget, array(
+ // ids in Jetpack's Gallery are a string of comma-separated values.
+ // Core's Media Gallery Widget stores ids in an array
+ 'ids' => explode( ',', $widget['ids'] ),
+ 'link_type' => $widget['link'],
+ 'orderby_random' => isset( $widget['random'] ) && $widget['random'] === 'on',
+ ) );
+
+ // Unsetting old widget fields
+ $widget_copy = array_diff_key( $widget_copy, array(
+ 'link' => false,
+ 'random' => false,
+ ) );
+
+ return $widget_copy;
+}
+
+/**
+ * Replaces the references to Jetpack Gallery Widget in the sidebars for references to the new version of the widget
+ *
+ * @param $sidebars_widgets The sidebar widgets array to update
+ * @param $id Old id of the widget (basically its index in the array )
+ * @param $new_id New id that will be using on the sidebar as a new widget
+ *
+ * @return mixed Updated sidebar widgets array
+ */
+function jetpack_migrate_gallery_widget_update_sidebars( $sidebars_widgets, $id, $new_id ) {
+ foreach ( $sidebars_widgets as $sidebar => $widgets ) {
+ if (
+ is_array( $widgets )
+ && false !== ( $key = array_search( "gallery-{$id}", $widgets, true ) )
+ ) {
+ $sidebars_widgets[ $sidebar ][ $key ] = "media_gallery-{$new_id}";
+ // Check if the inactive widgets sidebar exists
+ // Related: https://core.trac.wordpress.org/ticket/14893
+ if ( ! isset( $sidebars_widgets['wp_inactive_widgets'] ) || ! is_array( $sidebars_widgets['wp_inactive_widgets'] ) ) {
+ $sidebars_widgets['wp_inactive_widgets'] = array();
+ }
+ $sidebars_widgets['wp_inactive_widgets'][ $key ] = "gallery-{$id}";
+ }
+ }
+ return $sidebars_widgets;
+}
+
+/**
+ * Will bump stat in jetpack_gallery_widget_migration group.
+ *
+ * @param string $bin The bin to log into.
+ * @param string $group The group name. Defaults to "widget-migration".
+ */
+function jetpack_migrate_gallery_widget_bump_stats( $bin, $group = 'widget-migration' ) {
+ // If this is being run on .com bumps_stats_extra exists, but using the filter looks more elegant.
+ if ( function_exists( 'bump_stats_extras' ) ) {
+ $group = "jetpack-$group";
+ do_action( 'jetpack_bump_stats_extra', $group, $bin );
+ } else {
+ // $group is prepended with 'jetpack-'
+ $jetpack = Jetpack::init();
+ $jetpack->stat( $group, $bin ) ;
+ }
+
+}
+
+add_action( 'widgets_init', 'jetpack_migrate_gallery_widget' );
diff --git a/plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php b/plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php
new file mode 100644
index 00000000..70d2d12b
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ * Migration from Jetpack's Image Widget to WordPress' Core Image Widget.
+ *
+ * @since 4.9
+ *
+ * @package Jetpack
+ */
+
+/**
+ * Migrates all active instances of Jetpack's image widget to Core's media image widget.
+ */
+function jetpack_migrate_image_widget() {
+ // Only trigger the migration from wp-admin
+ if ( ! is_admin() ) {
+ return;
+ }
+
+ // Only migrate if the new widget is available and we haven't yet migrated
+ if ( ! class_exists( 'WP_Widget_Media_Image' ) || Jetpack_Options::get_option( 'image_widget_migration' ) ) {
+ return;
+ }
+
+ $default_data = array(
+ 'attachment_id' => 0,
+ 'url' => '',
+ 'title' => '',
+ 'size' => 'custom',
+ 'width' => 0,
+ 'height' => 0,
+ 'align' => 'none',
+ 'caption' => '',
+ 'alt' => '',
+ 'link_type' => '',
+ 'link_url' => '',
+ 'image_classes' => '',
+ 'link_classes' => '',
+ 'link_rel' => '',
+ 'image_title' => '',
+ 'link_target_blank' => false,
+ 'conditions' => null,
+ );
+
+ $old_widgets = get_option( 'widget_image', array() );
+ $media_image = get_option( 'widget_media_image', array() );
+ $sidebars_widgets = wp_get_sidebars_widgets();
+
+ // Persist old and current widgets in backup table.
+ jetpack_store_migration_data( 'widget_image', maybe_serialize( $old_widgets ) );
+ if ( jetpack_get_migration_data( 'widget_image' ) !== $old_widgets ) {
+ return false;
+ }
+
+ jetpack_store_migration_data( 'sidebars_widgets', maybe_serialize( $sidebars_widgets ) );
+ if ( jetpack_get_migration_data( 'sidebars_widgets' ) !== $sidebars_widgets ) {
+ return false;
+ }
+
+ // Array to store legacy widget ids in to unregister on success.
+ $widgets_to_unregister = array();
+
+ foreach ( $old_widgets as $id => $widget ) {
+ if ( is_string( $id ) ) {
+ continue;
+ }
+
+ // Can be caused by instanciating but not populating a widget in the Customizer.
+ if ( empty( $widget ) ) {
+ continue;
+ }
+
+ // Ensure widget has no keys other than those expected.
+ // Not all widgets have conditions, so lets add it in.
+ $widget_copy = array_merge( array( 'conditions' => null ), $widget );
+ $non_whitelisted_keys = array_diff_key( $widget_copy, array(
+ 'title' => '',
+ 'img_url' => '',
+ 'alt_text' => '',
+ 'img_title' => '',
+ 'caption' => '',
+ 'align' => '',
+ 'img_width' => '',
+ 'img_height' => '',
+ 'link' => '',
+ 'link_target_blank' => '',
+ 'conditions' => '',
+ ) );
+
+ if ( count( $non_whitelisted_keys ) > 0 ) {
+ // skipping the widget in question
+ continue;
+ }
+
+ $media_image[ $id ] = array_merge( $default_data, $widget, array(
+ 'alt' => $widget['alt_text'],
+ 'height' => $widget['img_height'],
+ 'image_classes' => ! empty( $widget['align'] ) ? 'align' . $widget['align'] : '',
+ 'image_title' => $widget['img_title'],
+ 'link_url' => $widget['link'],
+ 'url' => $widget['img_url'],
+ 'width' => $widget['img_width'],
+ ) );
+
+ // Unsetting old widget fields
+ $media_image[ $id ] = array_diff_key( $media_image[ $id ], array(
+ 'align' => false,
+ 'alt_text' => false,
+ 'img_height' => false,
+ 'img_title' => false,
+ 'img_url' => false,
+ 'img_width' => false,
+ 'link' => false,
+ ) );
+
+ // Check if the image is in the media library.
+ $image_basename = basename( $widget['img_url'] );
+
+ if ( empty( $image_basename ) ) {
+ continue;
+ }
+
+ $attachment_ids = get_posts( array(
+ 'fields' => 'ids',
+ 'meta_query' => array(
+ array(
+ 'value' => basename( $image_basename ),
+ 'compare' => 'LIKE',
+ 'key' => '_wp_attachment_metadata',
+ ),
+ ),
+ 'post_status' => 'inherit',
+ 'post_type' => 'attachment',
+ 'suppress_filters' => false,
+ ) );
+
+ foreach ( $attachment_ids as $attachment_id ) {
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
+
+ // Is it a full size image?
+ $image_path_pieces = explode( '/', $image_meta['file'] );
+ if ( $image_basename === array_pop( $image_path_pieces ) ) {
+ $media_image[ $id ]['attachment_id'] = $attachment_id;
+
+ // Set correct size if dimensions fit.
+ if (
+ $media_image[ $id ]['width'] == $image_meta['width'] ||
+ $media_image[ $id ]['height'] == $image_meta['height']
+ ) {
+ $media_image[ $id ]['size'] = 'full';
+ }
+ break;
+ }
+
+ // Is it a down-sized image?
+ foreach ( $image_meta['sizes'] as $size => $image ) {
+ if ( false !== array_search( $image_basename, $image ) ) {
+ $media_image[ $id ]['attachment_id'] = $attachment_id;
+
+ // Set correct size if dimensions fit.
+ if (
+ $media_image[ $id ]['width'] == $image['width'] ||
+ $media_image[ $id ]['height'] == $image['height']
+ ) {
+ $media_image[ $id ]['size'] = $size;
+ }
+ break 2;
+ }
+ }
+ }
+
+ if ( ! empty( $widget['link'] ) ) {
+ $media_image[ $id ]['link_type'] = $widget['link'] === $widget['img_url'] ? 'file' : 'custom';
+ }
+
+ foreach ( $sidebars_widgets as $sidebar => $widgets ) {
+ if (
+ is_array( $widgets )
+ && false !== ( $key = array_search( "image-{$id}", $widgets, true ) )
+ ) {
+ $sidebars_widgets[ $sidebar ][ $key ] = "media_image-{$id}";
+ }
+ }
+
+ $widgets_to_unregister[] = $id;
+ }
+
+ if ( update_option( 'widget_media_image', $media_image ) ) {
+ delete_option( 'widget_image' );
+
+ // Now un-register old widgets and register new.
+ foreach ( $widgets_to_unregister as $id ) {
+ wp_unregister_sidebar_widget( "image-${id}" );
+
+ // register new widget.
+ $media_image_widget = new WP_Widget_Media_Image();
+ $media_image_widget->_set( $id );
+ $media_image_widget->_register_one( $id );
+ }
+
+ wp_set_sidebars_widgets( $sidebars_widgets );
+
+ // We need to refresh on widgets page for changes to take effect.
+ add_action( 'current_screen', 'jetpack_refresh_on_widget_page' );
+ } else {
+ $widget_media_image = get_option( 'widget_media_image' );
+ if ( is_array( $widget_media_image ) ) {
+ delete_option( 'widget_image' );
+ }
+ }
+
+ Jetpack_Options::update_option( 'image_widget_migration', true );
+}
+add_action( 'widgets_init', 'jetpack_migrate_image_widget' );
+
+function jetpack_refresh_on_widget_page( $current ) {
+ if ( 'widgets' === $current->base ) {
+ wp_safe_redirect( admin_url( 'widgets.php' ) );
+ exit;
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/milestone.php b/plugins/jetpack/modules/widgets/milestone.php
new file mode 100644
index 00000000..0dd2140e
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/milestone.php
@@ -0,0 +1,5 @@
+<?php
+/**
+ * Register the milestone widget. This makes it easier to keep the /milestone/ dir content in sync with wpcom.
+ */
+include dirname( __FILE__ ) . '/milestone/milestone.php';
diff --git a/plugins/jetpack/modules/widgets/milestone/admin.js b/plugins/jetpack/modules/widgets/milestone/admin.js
new file mode 100644
index 00000000..1897f0b7
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/milestone/admin.js
@@ -0,0 +1,31 @@
+( function( $ ) {
+ // We could either be in wp-admin/widgets.php or the customizer.
+ var $container = $( '#customize-controls' );
+
+ if ( ! $container.length ) {
+ $container = $( '#wpbody' );
+ }
+
+ $container.on( 'change', '.milestone-type', function() {
+ var $messageWrapper = $( this )
+ .parent()
+ .find( '.milestone-message-wrapper' );
+
+ $( this )
+ .find( 'input[type="radio"]:checked' )
+ .val() === 'since'
+ ? $messageWrapper.hide()
+ : $messageWrapper.show();
+ } );
+
+ function triggerChange() {
+ $container.find( '.milestone-type' ).trigger( 'change' );
+ }
+
+ // Used when adding widget via customizer or saving settings.
+ $( document ).on( 'widget-added widget-updated', function() {
+ triggerChange();
+ } );
+
+ triggerChange();
+} )( jQuery );
diff --git a/plugins/jetpack/modules/widgets/milestone/milestone.js b/plugins/jetpack/modules/widgets/milestone/milestone.js
new file mode 100644
index 00000000..3780dec6
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/milestone/milestone.js
@@ -0,0 +1,49 @@
+/* global MilestoneConfig */
+
+var Milestone = ( function( $ ) {
+ var Milestone = function( args ) {
+ var $widget = $( '#' + args.id ),
+ id = args.id,
+ refresh = args.refresh * 1000;
+
+ this.timer = function() {
+ var instance = this;
+
+ $.ajax( {
+ url: MilestoneConfig.api_root + 'jetpack/v4/widgets/' + id,
+ success: function( result ) {
+ $widget.find( '.milestone-countdown' ).replaceWith( result.message );
+ refresh = result.refresh * 1000;
+
+ if ( ! refresh ) {
+ return;
+ }
+
+ setTimeout( function() {
+ instance.timer();
+ }, refresh );
+ },
+ } );
+ };
+
+ if ( refresh > 0 ) {
+ this.timer();
+ }
+ };
+ return function( args ) {
+ return new Milestone( args );
+ };
+} )( jQuery );
+
+( function() {
+ var i,
+ MilestoneInstances = {};
+
+ if ( typeof MilestoneConfig === 'undefined' ) {
+ return;
+ }
+
+ for ( i = 0; i < MilestoneConfig.instances.length; i++ ) {
+ MilestoneInstances[ i ] = new Milestone( MilestoneConfig.instances[ i ] );
+ }
+} )();
diff --git a/plugins/jetpack/modules/widgets/milestone/milestone.php b/plugins/jetpack/modules/widgets/milestone/milestone.php
new file mode 100644
index 00000000..8490a8ec
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/milestone/milestone.php
@@ -0,0 +1,683 @@
+<?php
+/*
+Plugin Name: Milestone
+Description: Countdown to a specific date.
+Version: 1.0
+Author: Automattic Inc.
+Author URI: http://automattic.com/
+License: GPLv2 or later
+*/
+
+function jetpack_register_widget_milestone() {
+ register_widget( 'Milestone_Widget' );
+}
+add_action( 'widgets_init', 'jetpack_register_widget_milestone' );
+
+class Milestone_Widget extends WP_Widget {
+ private static $dir = null;
+ private static $url = null;
+ private static $defaults = null;
+ private static $config_js = null;
+
+ /**
+ * Available time units sorted in descending order.
+ * @var Array
+ */
+ protected $available_units = array(
+ 'years',
+ 'months',
+ 'days',
+ 'hours',
+ 'minutes',
+ 'seconds'
+ );
+
+ function __construct() {
+ $widget = array(
+ 'classname' => 'milestone-widget',
+ 'description' => __( 'Display a countdown to a certain date.', 'jetpack' ),
+ );
+
+ parent::__construct(
+ 'Milestone_Widget',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Milestone', 'jetpack' ) ),
+ $widget
+ );
+
+ self::$dir = trailingslashit( dirname( __FILE__ ) );
+ self::$url = plugin_dir_url( __FILE__ );
+
+ add_action( 'wp_enqueue_scripts', array( __class__, 'enqueue_template' ) );
+ add_action( 'admin_enqueue_scripts', array( __class__, 'enqueue_admin' ) );
+ add_action( 'wp_footer', array( $this, 'localize_script' ) );
+
+ if ( is_active_widget( false, false, $this->id_base, true ) || is_active_widget( false, false, 'monster', true ) || is_customize_preview() ) {
+ add_action( 'wp_head', array( __class__, 'styles_template' ) );
+ }
+ }
+
+ public static function enqueue_admin( $hook_suffix ) {
+ if ( 'widgets.php' == $hook_suffix ) {
+ wp_enqueue_style( 'milestone-admin', self::$url . 'style-admin.css', array(), '20161215' );
+ wp_enqueue_script(
+ 'milestone-admin-js',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/milestone/admin.min.js',
+ 'modules/widgets/milestone/admin.js'
+ ),
+ array( 'jquery' ),
+ '20170915',
+ true
+ );
+ }
+ }
+
+ public static function enqueue_template() {
+ if ( Jetpack_AMP_Support::is_amp_request() ) {
+ return;
+ }
+
+ wp_enqueue_script(
+ 'milestone',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/milestone/milestone.min.js',
+ 'modules/widgets/milestone/milestone.js'
+ ),
+ array( 'jquery' ),
+ '20160520',
+ true
+ );
+ }
+
+ public static function styles_template() {
+ global $themecolors;
+ $colors = wp_parse_args( $themecolors, array(
+ 'bg' => 'ffffff',
+ 'border' => 'cccccc',
+ 'text' => '333333',
+ ) );
+?>
+<style>
+.milestone-widget {
+ margin-bottom: 1em;
+}
+.milestone-content {
+ line-height: 2;
+ margin-top: 5px;
+ max-width: 100%;
+ padding: 0;
+ text-align: center;
+}
+.milestone-header {
+ background-color: <?php echo self::sanitize_color_hex( $colors['text'] ); ?>;
+ color: <?php echo self::sanitize_color_hex( $colors['bg'] ); ?>;
+ line-height: 1.3;
+ margin: 0;
+ padding: .8em;
+}
+.milestone-header .event,
+.milestone-header .date {
+ display: block;
+}
+.milestone-header .event {
+ font-size: 120%;
+}
+.milestone-countdown .difference {
+ display: block;
+ font-size: 500%;
+ font-weight: bold;
+ line-height: 1.2;
+}
+.milestone-countdown,
+.milestone-message {
+ background-color: <?php echo self::sanitize_color_hex( $colors['bg'] ); ?>;
+ border: 1px solid <?php echo self::sanitize_color_hex( $colors['border'] ); ?>;
+ border-top: 0;
+ color: <?php echo self::sanitize_color_hex( $colors['text'] ); ?>;
+ padding-bottom: 1em;
+}
+.milestone-message {
+ padding-top: 1em
+}
+</style>
+<?php
+ }
+
+ /**
+ * Ensure that a string representing a color in hexadecimal
+ * notation is safe for use in css and database saves.
+ *
+ * @param string Color in hexadecimal notation. "#" may or may not be prepended to the string.
+ * @return string Color in hexadecimal notation on success - the string "transparent" otherwise.
+ */
+ public static function sanitize_color_hex( $hex, $prefix = '#' ) {
+ $hex = trim( $hex );
+
+ /* Strip recognized prefixes. */
+ if ( 0 === strpos( $hex, '#' ) ) {
+ $hex = substr( $hex, 1 );
+ } elseif ( 0 === strpos( $hex, '%23' ) ) {
+ $hex = substr( $hex, 3 );
+ }
+
+ if ( 0 !== preg_match( '/^[0-9a-fA-F]{6}$/', $hex ) ) {
+ return $prefix . $hex;
+ }
+
+ return 'transparent';
+ }
+
+ /**
+ * Localize Front-end Script.
+ *
+ * Print the javascript configuration array only if the
+ * current template has an instance of the widget that
+ * is still counting down. In all other cases, this
+ * function will dequeue milestone.js.
+ *
+ * Hooks into the "wp_footer" action.
+ */
+ function localize_script() {
+ if ( Jetpack_AMP_Support::is_amp_request() ) {
+ return;
+ }
+
+ if ( empty( self::$config_js['instances'] ) ) {
+ wp_dequeue_script( 'milestone' );
+ return;
+ }
+ self::$config_js['api_root'] = esc_url_raw( rest_url() );
+ wp_localize_script( 'milestone', 'MilestoneConfig', self::$config_js );
+ }
+
+ /**
+ * Widget
+ */
+ function widget( $args, $instance ) {
+ echo $args['before_widget'];
+
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . $title . $args['after_title'];
+ }
+
+ $data = $this->get_widget_data( $instance );
+
+ self::$config_js['instances'][] = array(
+ 'id' => $args['widget_id'],
+ 'message' => $data['message'],
+ 'refresh' => $data['refresh']
+ );
+
+ echo '<div class="milestone-content">';
+
+ echo '<div class="milestone-header">';
+ echo '<strong class="event">' . esc_html( $instance['event'] ) . '</strong>';
+ echo '<span class="date">' . esc_html( date_i18n( get_option( 'date_format' ), $data['milestone'] ) ) . '</span>';
+ echo '</div>';
+
+ echo $data['message'];
+
+ echo '</div><!--milestone-content-->';
+
+ echo $args['after_widget'];
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'milestone' );
+ }
+
+ function get_widget_data( $instance ) {
+ $data = array();
+
+ $instance = $this->sanitize_instance( $instance );
+
+ $milestone = mktime( $instance['hour'], $instance['min'], 0, $instance['month'], $instance['day'], $instance['year'] );
+ $now = (int) current_time( 'timestamp' );
+ $type = $instance['type'];
+
+ if ( 'since' === $type ) {
+ $diff = (int) floor( $now - $milestone );
+ } else {
+ $diff = (int) floor( $milestone - $now );
+ }
+
+ $data['diff'] = $diff;
+ $data['unit'] = $this->get_unit( $diff, $instance['unit'] );
+
+ // Setting the refresh counter to equal the number of seconds it takes to flip a unit
+ $refresh_intervals = array(
+ 0, // should be YEAR_IN_SECONDS, but doing setTimeout for a year doesn't seem to be logical
+ 0, // same goes for MONTH_IN_SECONDS,
+ DAY_IN_SECONDS,
+ HOUR_IN_SECONDS,
+ MINUTE_IN_SECONDS,
+ 1
+ );
+
+ $data['refresh'] = $refresh_intervals[ array_search( $data['unit'], $this->available_units ) ];
+ $data['milestone'] = $milestone;
+
+ if ( ( 1 > $diff ) && ( 'until' === $type ) ) {
+ $data['message'] = '<div class="milestone-message">' . $instance['message'] . '</div>';
+ $data['refresh'] = 0; // No need to refresh, the milestone has been reached
+ } else {
+ $interval_text = $this->get_interval_in_units( $diff, $data['unit'] );
+ $interval = intval( $interval_text );
+
+ if ( 'since' === $type ) {
+
+ switch ( $data['unit'] ) {
+ case 'years':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">year ago.</span>',
+ '<span class="difference">%s</span> <span class="label">years ago.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'months':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">month ago.</span>',
+ '<span class="difference">%s</span> <span class="label">months ago.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'days':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">day ago.</span>',
+ '<span class="difference">%s</span> <span class="label">days ago.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'hours':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">hour ago.</span>',
+ '<span class="difference">%s</span> <span class="label">hours ago.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'minutes':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">minute ago.</span>',
+ '<span class="difference">%s</span> <span class="label">minutes ago.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'seconds':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">second ago.</span>',
+ '<span class="difference">%s</span> <span class="label">seconds ago.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ }
+ } else {
+ switch ( $this->get_unit( $diff, $instance['unit'] ) ) {
+ case 'years':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">year to go.</span>',
+ '<span class="difference">%s</span> <span class="label">years to go.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'months':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">month to go.</span>',
+ '<span class="difference">%s</span> <span class="label">months to go.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'days':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">day to go.</span>',
+ '<span class="difference">%s</span> <span class="label">days to go.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'hours':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">hour to go.</span>',
+ '<span class="difference">%s</span> <span class="label">hours to go.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'minutes':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">minute to go.</span>',
+ '<span class="difference">%s</span> <span class="label">minutes to go.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ case 'seconds':
+ $data['message'] = sprintf(
+ _n(
+ '<span class="difference">%s</span> <span class="label">second to go.</span>',
+ '<span class="difference">%s</span> <span class="label">seconds to go.</span>',
+ $interval,
+ 'jetpack'
+ ),
+ $interval_text
+ );
+ break;
+ }
+ }
+ $data['message'] = '<div class="milestone-countdown">' . $data['message'] . '</div>';
+ }
+
+ return $data;
+ }
+
+ /**
+ * Return the largest possible time unit that the difference will be displayed in.
+ *
+ * @param Integer $seconds the interval in seconds
+ * @param String $maximum_unit the maximum unit that will be used. Optional.
+ * @return String $calculated_unit
+ */
+ protected function get_unit( $seconds, $maximum_unit = 'automatic' ) {
+ $unit = '';
+
+ if ( $seconds >= YEAR_IN_SECONDS * 2 ) {
+ // more than 2 years - show in years, one decimal point
+ $unit = 'years';
+
+ } else if ( $seconds >= YEAR_IN_SECONDS ) {
+ if ( 'years' === $maximum_unit ) {
+ $unit = 'years';
+ } else {
+ // automatic mode - showing months even if it's between one and two years
+ $unit = 'months';
+ }
+
+ } else if ( $seconds >= MONTH_IN_SECONDS * 3 ) {
+ // fewer than 2 years - show in months
+ $unit = 'months';
+
+ } else if ( $seconds >= MONTH_IN_SECONDS ) {
+ if ( 'months' === $maximum_unit ) {
+ $unit = 'months';
+ } else {
+ // automatic mode - showing days even if it's between one and three months
+ $unit = 'days';
+ }
+
+ } else if ( $seconds >= DAY_IN_SECONDS - 1 ) {
+ // fewer than a month - show in days
+ $unit = 'days';
+
+ } else if ( $seconds >= HOUR_IN_SECONDS - 1 ) {
+ // less than 1 day - show in hours
+ $unit = 'hours';
+
+ } else if ( $seconds >= MINUTE_IN_SECONDS - 1 ) {
+ // less than 1 hour - show in minutes
+ $unit = 'minutes';
+
+ } else {
+ // less than 1 minute - show in seconds
+ $unit = 'seconds';
+ }
+
+ $maximum_unit_index = array_search( $maximum_unit, $this->available_units );
+ $unit_index = array_search( $unit, $this->available_units );
+
+ if (
+ false === $maximum_unit_index // the maximum unit parameter is automatic
+ || $unit_index > $maximum_unit_index // there is not enough seconds for even one maximum time unit
+ ) {
+ return $unit;
+ }
+ return $maximum_unit;
+ }
+
+ /**
+ * Returns a time difference value in specified units.
+ *
+ * @param Integer $seconds
+ * @param String $units
+ * @return Integer|String $time_in_units
+ */
+ protected function get_interval_in_units( $seconds, $units ) {
+ switch ( $units ) {
+ case 'years':
+ $years = $seconds / YEAR_IN_SECONDS;
+ $decimals = abs( round( $years, 1 ) - round( $years ) ) > 0 ? 1 : 0;
+ return number_format_i18n( $years, $decimals );
+ case 'months':
+ return (int) ( $seconds / 60 / 60 / 24 / 30 );
+ case 'days':
+ return (int) ( $seconds / 60 / 60 / 24 + 1 );
+ case 'hours':
+ return (int) ( $seconds / 60 / 60 );
+ case 'minutes':
+ return (int) ( $seconds / 60 + 1 );
+ default:
+ return $seconds;
+ }
+ }
+
+ /**
+ * Update
+ */
+ function update( $new_instance, $old_instance ) {
+ return $this->sanitize_instance( $new_instance );
+ }
+
+ /*
+ * Make sure that a number is within a certain range.
+ * If the number is too small it will become the possible lowest value.
+ * If the number is too large it will become the possible highest value.
+ *
+ * @param int $n The number to check.
+ * @param int $floor The lowest possible value.
+ * @param int $ceil The highest possible value.
+ */
+ function sanitize_range( $n, $floor, $ceil ) {
+ $n = (int) $n;
+ if ( $n < $floor ) {
+ $n = $floor;
+ } elseif ( $n > $ceil ) {
+ $n = $ceil;
+ }
+ return $n;
+ }
+
+ /*
+ * Sanitize an instance of this widget.
+ *
+ * Date ranges match the documentation for mktime in the php manual.
+ * @see http://php.net/manual/en/function.mktime.php#refsect1-function.mktime-parameters
+ *
+ * @uses Milestone_Widget::sanitize_range().
+ */
+ function sanitize_instance( $dirty ) {
+ $now = (int) current_time( 'timestamp' );
+
+ $dirty = wp_parse_args( $dirty, array(
+ 'title' => '',
+ 'event' => __( 'The Big Day', 'jetpack' ),
+ 'unit' => 'automatic',
+ 'type' => 'until',
+ 'message' => __( 'The big day is here.', 'jetpack' ),
+ 'day' => date( 'd', $now ),
+ 'month' => date( 'm', $now ),
+ 'year' => date( 'Y', $now ),
+ 'hour' => 0,
+ 'min' => 0,
+ ) );
+
+ $allowed_tags = array(
+ 'a' => array( 'title' => array(), 'href' => array(), 'target' => array() ),
+ 'em' => array( 'title' => array() ),
+ 'strong' => array( 'title' => array() ),
+ );
+
+ $clean = array(
+ 'title' => trim( strip_tags( stripslashes( $dirty['title'] ) ) ),
+ 'event' => trim( strip_tags( stripslashes( $dirty['event'] ) ) ),
+ 'unit' => $dirty['unit'],
+ 'type' => $dirty['type'],
+ 'message' => wp_kses( $dirty['message'], $allowed_tags ),
+ 'year' => $this->sanitize_range( $dirty['year'], 1901, 2037 ),
+ 'month' => $this->sanitize_range( $dirty['month'], 1, 12 ),
+ 'hour' => $this->sanitize_range( $dirty['hour'], 0, 23 ),
+ 'min' => zeroise( $this->sanitize_range( $dirty['min'], 0, 59 ), 2 ),
+ );
+
+ $clean['day'] = $this->sanitize_range( $dirty['day'], 1, date( 't', mktime( 0, 0, 0, $clean['month'], 1, $clean['year'] ) ) );
+
+ return $clean;
+ }
+
+ /**
+ * Form
+ */
+ function form( $instance ) {
+ $instance = $this->sanitize_instance( $instance );
+
+ $units = array(
+ 'automatic' => _x( 'Automatic', 'Milestone widget: mode in which the date unit is determined automatically', 'jetpack' ),
+ 'years' => _x( 'Years', 'Milestone widget: mode in which the date unit is set to years', 'jetpack' ),
+ 'months' => _x( 'Months', 'Milestone widget: mode in which the date unit is set to months', 'jetpack' ),
+ 'days' => _x( 'Days', 'Milestone widget: mode in which the date unit is set to days', 'jetpack' ),
+ 'hours' => _x( 'Hours', 'Milestone widget: mode in which the date unit is set to hours', 'jetpack' ),
+ );
+ ?>
+
+ <div class="milestone-widget">
+ <p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'event' ); ?>"><?php _e( 'Description', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'event' ); ?>" name="<?php echo $this->get_field_name( 'event' ); ?>" type="text" value="<?php echo esc_attr( $instance['event'] ); ?>" />
+ </p>
+
+ <fieldset class="jp-ms-data-time">
+ <legend><?php esc_html_e( 'Date', 'jetpack' ); ?></legend>
+
+ <label for="<?php echo $this->get_field_id( 'month' ); ?>" class="assistive-text"><?php _e( 'Month', 'jetpack' ); ?></label>
+ <select id="<?php echo $this->get_field_id( 'month' ); ?>" class="month" name="<?php echo $this->get_field_name( 'month' ); ?>"><?php
+ global $wp_locale;
+ for ( $i = 1; $i < 13; $i++ ) {
+ $monthnum = zeroise( $i, 2 );
+ echo '<option value="' . esc_attr( $monthnum ) . '"' . selected( $i, $instance['month'], false ) . '>' . $monthnum . '-' . $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) ) . '</option>';
+ }
+ ?></select>
+
+ <label for="<?php echo $this->get_field_id( 'day' ); ?>" class="assistive-text"><?php _e( 'Day', 'jetpack' ); ?></label>
+ <input id="<?php echo $this->get_field_id( 'day' ); ?>" class="day" name="<?php echo $this->get_field_name( 'day' ); ?>" type="text" value="<?php echo esc_attr( $instance['day'] ); ?>">,
+
+ <label for="<?php echo $this->get_field_id( 'year' ); ?>" class="assistive-text"><?php _e( 'Year', 'jetpack' ); ?></label>
+ <input id="<?php echo $this->get_field_id( 'year' ); ?>" class="year" name="<?php echo $this->get_field_name( 'year' ); ?>" type="text" value="<?php echo esc_attr( $instance['year'] ); ?>">
+ </fieldset>
+
+ <fieldset class="jp-ms-data-time">
+ <legend><?php esc_html_e( 'Time', 'jetpack' ); ?></legend>
+
+ <label for="<?php echo $this->get_field_id( 'hour' ); ?>" class="assistive-text"><?php _e( 'Hour', 'jetpack' ); ?></label>
+ <input id="<?php echo $this->get_field_id( 'hour' ); ?>" class="hour" name="<?php echo $this->get_field_name( 'hour' ); ?>" type="text" value="<?php echo esc_attr( $instance['hour'] ); ?>">
+
+ <label for="<?php echo $this->get_field_id( 'min' ); ?>" class="assistive-text"><?php _e( 'Minutes', 'jetpack' ); ?></label>
+
+ <span class="time-separator">:</span>
+
+ <input id="<?php echo $this->get_field_id( 'min' ); ?>" class="minutes" name="<?php echo $this->get_field_name( 'min' ); ?>" type="text" value="<?php echo esc_attr( $instance['min'] ); ?>">
+ </fieldset>
+
+ <fieldset class="jp-ms-data-unit">
+ <legend><?php esc_html_e( 'Time Unit', 'jetpack' ); ?></legend>
+
+ <label for="<?php echo $this->get_field_id( 'unit' ); ?>" class="assistive-text">
+ <?php _e( 'Time Unit', 'jetpack' ); ?>
+ </label>
+ <select id="<?php echo $this->get_field_id( 'unit' ); ?>" class="unit" name="<?php echo $this->get_field_name( 'unit' ); ?>">
+ <?php
+ foreach ( $units as $key => $unit ) {
+ echo '<option value="' . esc_attr( $key ) . '"' . selected( $key, $instance['unit'], false ) . '>' . $unit . '</option>';
+ }
+ ?></select>
+ </fieldset>
+
+ <ul class="milestone-type">
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['type'], 'until' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'type' ) ); ?>"
+ type="radio"
+ value="until"
+ />
+ <?php esc_html_e( 'Until your milestone', 'jetpack' ); ?>
+ </label>
+ </li>
+
+ <li>
+ <label>
+ <input
+ <?php checked( $instance['type'], 'since' ); ?>
+ name="<?php echo esc_attr( $this->get_field_name( 'type' ) ); ?>"
+ type="radio"
+ value="since"
+ />
+ <?php esc_html_e( 'Since your milestone', 'jetpack' ); ?>
+ </label>
+ </li>
+ </ul>
+
+ <p class="milestone-message-wrapper">
+ <label for="<?php echo $this->get_field_id( 'message' ); ?>"><?php _e( 'Milestone Reached Message', 'jetpack' ); ?></label>
+ <textarea id="<?php echo $this->get_field_id( 'message' ); ?>" name="<?php echo $this->get_field_name( 'message' ); ?>" class="widefat" rows="3"><?php echo esc_textarea( $instance['message'] ); ?></textarea>
+ </p>
+ </div>
+
+ <?php
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/milestone/style-admin.css b/plugins/jetpack/modules/widgets/milestone/style-admin.css
new file mode 100644
index 00000000..15a97102
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/milestone/style-admin.css
@@ -0,0 +1,50 @@
+.milestone-widget fieldset {
+ margin-bottom: 1em;
+}
+
+.milestone-widget fieldset * {
+ vertical-align: middle;
+}
+
+.jp-ms-data-time input[type="text"] {
+ text-align: right;
+ width: 2.1em;
+}
+
+.jp-ms-data-time .month {
+ width: 5.4em;
+
+}
+
+.jp-ms-data-time .year[type="text"] {
+ text-align: right;
+ width: 3.2em;
+}
+
+.jp-ms-data-time input[type="text"] {
+ text-align: right;
+ width: 3.2em;
+}
+
+.jp-ms-data-time .year[type="text"] {
+ width: 4.5em;
+}
+
+.jp-ms-data-time .assistive-text,
+.jp-ms-data-unit .assistive-text {
+ position: absolute !important;
+ clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
+ clip: rect(1px, 1px, 1px, 1px);
+}
+
+@media screen and (max-width: 782px) {
+ .jp-ms-data-time input[type="text"],
+ .jp-ms-data-time .year[type="text"] {
+ width: 2.8em;
+
+ }
+
+ .jp-ms-data-time .year[type="text"] {
+ width: 4em;
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/my-community.php b/plugins/jetpack/modules/widgets/my-community.php
new file mode 100644
index 00000000..03958c38
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/my-community.php
@@ -0,0 +1,297 @@
+<?php
+/**
+ * Disable direct access/execution to/of the widget code.
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Jetpack_My_Community_Widget displays community members of this site.
+ *
+ * A community member is a WordPress.com user that liked or commented on an entry or subscribed to the site.
+ * Requires WordPress.com connection to work. Otherwise it won't be visible in Widgets screen in admin.
+ */
+class Jetpack_My_Community_Widget extends WP_Widget {
+ /**
+ * Transient expiration time.
+ *
+ * @var int $expiration
+ */
+ static $expiration = 600;
+
+ /**
+ * Default widget title.
+ *
+ * @var string $default_title
+ */
+ var $default_title;
+
+ /**
+ * Registers the widget with WordPress.
+ */
+ function __construct() {
+ parent::__construct(
+ 'jetpack_my_community', // Base ID
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'My Community', 'jetpack' ) ),
+ array(
+ 'description' => esc_html__( "Display members of your site's community.", 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_active_widget( false, false, 'monster' ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
+ }
+
+ $this->default_title = esc_html__( 'Community', 'jetpack' );
+ }
+
+ /**
+ * Enqueue stylesheet for grid layout.
+ */
+ function enqueue_style() {
+ wp_register_style( 'jetpack-my-community-widget', plugins_url( 'my-community/style.css', __FILE__ ), array(), '20160129' );
+ wp_enqueue_style( 'jetpack-my-community-widget' );
+ }
+
+ /**
+ * Back end widget form.
+ *
+ * @see WP_Widget::form()
+ *
+ * @param array $instance Previously saved values from database.
+ *
+ * @return string|void
+ */
+ function form( $instance ) {
+ $title = isset( $instance['title'] ) ? $instance['title'] : false;
+ if ( false === $title ) {
+ $title = $this->default_title;
+ }
+
+ $number = isset( $instance['number'] ) ? $instance['number'] : 10;
+ if ( ! in_array( $number, array( 10, 50 ) ) ) {
+ $number = 10;
+ }
+
+ $include_likers = isset( $instance['include_likers'] ) ? (bool) $instance['include_likers'] : true;
+ $include_followers = isset( $instance['include_followers'] ) ? (bool) $instance['include_followers'] : true;
+ $include_commenters = isset( $instance['include_commenters'] ) ? (bool) $instance['include_commenters'] : true;
+ ?>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
+ </p>
+
+ <p>
+ <label><?php esc_html_e( 'Show a maximum of', 'jetpack' ); ?></label>
+ </p>
+ <ul>
+ <li><label><input id="<?php echo $this->get_field_id( 'number' ); ?>-few" name="<?php echo $this->get_field_name( 'number' ); ?>" type="radio" value="10" <?php checked( '10', $number ); ?> /> <?php esc_html_e( '10 community members', 'jetpack' ); ?></label></li>
+ <li><label><input id="<?php echo $this->get_field_id( 'number' ); ?>-lots" name="<?php echo $this->get_field_name( 'number' ); ?>" type="radio" value="50" <?php checked( '50', $number ); ?> /> <?php esc_html_e( '50 community members', 'jetpack' ); ?></label></li>
+ </ul>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'include_likers' ); ?>">
+ <input type="checkbox" class="checkbox" id="<?php echo $this->get_field_id( 'include_likers' ); ?>" name="<?php echo $this->get_field_name( 'include_likers' ); ?>" value="1" <?php checked( $include_likers, 1 ); ?> />
+ <?php esc_html_e( 'Include activity from likers', 'jetpack' ); ?>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'include_followers' ); ?>">
+ <input type="checkbox" class="checkbox" id="<?php echo $this->get_field_id( 'include_followers' ); ?>" name="<?php echo $this->get_field_name( 'include_followers' ); ?>" value="1" <?php checked( $include_followers, 1 ); ?> />
+ <?php esc_html_e( 'Include activity from followers', 'jetpack' ); ?>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'include_commenters' ); ?>">
+ <input type="checkbox" class="checkbox" id="<?php echo $this->get_field_id( 'include_commenters' ); ?>" name="<?php echo $this->get_field_name( 'include_commenters' ); ?>" value="1" <?php checked( $include_commenters, 1 ); ?> />
+ <?php esc_html_e( 'Include activity from commenters', 'jetpack' ); ?>
+ </label>
+ </p>
+
+ <?php
+ }
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @see WP_Widget::update()
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ *
+ * @return array Updated safe values to be saved.
+ */
+ function update( $new_instance, $old_instance ) {
+ $instance = array();
+ $instance['title'] = wp_kses( $new_instance['title'], array() );
+ if ( $instance['title'] === $this->default_title ) {
+ $instance['title'] = false; // Store as false in case of language change
+ }
+
+ $instance['number'] = (int) $new_instance['number'];
+ if ( ! in_array( $instance['number'], array( 10, 50 ) ) ) {
+ $instance['number'] = 10;
+ }
+
+ $instance['include_likers'] = (bool) $new_instance['include_likers'];
+ $instance['include_followers'] = (bool) $new_instance['include_followers'];
+ $instance['include_commenters'] = (bool) $new_instance['include_commenters'];
+
+ delete_transient( "$this->id-v2-{$instance['number']}" . (int) $instance['include_likers'] . (int) $instance['include_followers'] . (int) $instance['include_commenters'] );
+
+ return $instance;
+ }
+
+ /**
+ * Front-end display of widget.
+ *
+ * @see WP_Widget::widget()
+ *
+ * @param array $args Widget arguments.
+ * @param array $instance Saved values from database.
+ */
+ function widget( $args, $instance ) {
+ $instance = wp_parse_args(
+ $instance, array(
+ 'title' => false,
+ 'number' => true,
+ 'include_likers' => true,
+ 'include_followers' => true,
+ 'include_commenters' => true,
+ )
+ );
+
+ $title = $instance['title'];
+
+ if ( false === $title ) {
+ $title = $this->default_title;
+ }
+
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
+ $title = apply_filters( 'widget_title', $title );
+
+ echo $args['before_widget'];
+
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . $title . $args['after_title'];
+ }
+
+ $transient_name = "$this->id-v2-{$instance['number']}" . (int) $instance['include_likers'] . (int) $instance['include_followers'] . (int) $instance['include_commenters'];
+
+ $my_community = get_transient( $transient_name );
+
+ if ( empty( $my_community ) ) {
+ $my_community = $this->get_community( $instance );
+
+ set_transient( $transient_name, $my_community, self::$expiration );
+ }
+
+ echo $my_community;
+
+ echo $args['after_widget'];
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'my_community' );
+ }
+
+ /**
+ * Initiate request and render the response.
+ *
+ * @since 4.0
+ *
+ * @param array $query
+ *
+ * @return string
+ */
+ function get_community( $query ) {
+ $members = $this->fetch_remote_community( $query );
+
+ if ( ! empty( $members ) ) {
+
+ $my_community = '<div class="widgets-multi-column-grid"><ul>';
+
+ foreach ( $members as $member ) {
+ $my_community .= sprintf(
+ '<li><a href="%s" title="%s"><img alt="%s" src="%s" class="avatar avatar-48" height="48" width="48"></a></li>',
+ esc_url( $member->profile_URL ),
+ esc_attr( $member->name ),
+ esc_attr( $member->name ),
+ esc_url( $member->avatar_URL )
+ );
+ }
+
+ $my_community .= '</ul></div>';
+
+ } else {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ $my_community = '<p>' . wp_kses(
+ sprintf(
+ __( 'There are no users to display in this <a href="%1$s">My Community widget</a>. <a href="%2$s">Want more traffic?</a>', 'jetpack' ),
+ admin_url( 'widgets.php' ),
+ 'https://jetpack.com/support/getting-more-views-and-traffic/'
+ ), array( 'a' => array( 'href' => true ) )
+ ) . '</p>';
+ } else {
+ $my_community = '<p>' . esc_html__( "I'm just starting out; leave me a comment or a like :)", 'jetpack' ) . '</p>';
+ }
+ }
+
+ return $my_community;
+ }
+
+ /**
+ * Request community members to WordPress.com endpoint.
+ *
+ * @since 4.0
+ *
+ * @param $query
+ *
+ * @return array
+ */
+ function fetch_remote_community( $query ) {
+ $jetpack_blog_id = Jetpack_Options::get_option( 'id' );
+ $url = add_query_arg(
+ array(
+ 'number' => $query['number'],
+ 'likers' => (int) $query['include_likers'],
+ 'followers' => (int) $query['include_followers'],
+ 'commenters' => (int) $query['include_commenters'],
+ ),
+ "https://public-api.wordpress.com/rest/v1.1/sites/$jetpack_blog_id/community"
+ );
+ $response = wp_remote_get( $url );
+ $response_body = wp_remote_retrieve_body( $response );
+
+ if ( empty( $response_body ) ) {
+ return array();
+ }
+
+ $response_body = json_decode( $response_body );
+
+ if ( isset( $response_body->users ) ) {
+ return $response_body->users;
+ }
+
+ return array();
+ }
+}
+
+/**
+ * If site is connected to WordPress.com, register the widget.
+ *
+ * @since 4.0
+ */
+function jetpack_my_community_init() {
+ if ( Jetpack::is_active() ) {
+ register_widget( 'Jetpack_My_Community_Widget' );
+ }
+}
+
+add_action( 'widgets_init', 'jetpack_my_community_init' );
diff --git a/plugins/jetpack/modules/widgets/my-community/style.css b/plugins/jetpack/modules/widgets/my-community/style.css
new file mode 100644
index 00000000..5616e890
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/my-community/style.css
@@ -0,0 +1,35 @@
+/* Multi-Column Grid Layout */
+.widgets-multi-column-grid ul {
+ overflow: hidden;
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
+}
+
+.widgets-multi-column-grid ul li {
+ background: none;
+ clear: none;
+ float: left;
+ margin: 0 -5px -3px 0;
+ padding: 0 8px 6px 0;
+ border: none;
+ list-style-type: none !important;
+}
+
+.widgets-multi-column-grid ul li a {
+ background: none;
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+
+.widgets-multi-column-grid .avatar {
+ vertical-align: middle;
+}
+
+/* Ensure My Community images fit the 48 pixel grid. */
+.widget_jetpack_my_community .avatar-48,
+.widget_jetpack_my_community .avatar-240 {
+ max-width: 48px;
+ max-height: 48px;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/rsslinks-widget.php b/plugins/jetpack/modules/widgets/rsslinks-widget.php
new file mode 100644
index 00000000..01d1ee2d
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/rsslinks-widget.php
@@ -0,0 +1,242 @@
+<?php
+/**
+ * Module Name: RSS Links Widget
+ * Module Description: Easily add RSS links to your theme's sidebar.
+ * Sort Order: 20
+ * First Introduced: 1.2
+ */
+
+class Jetpack_RSS_Links_Widget extends WP_Widget {
+
+ function __construct() {
+ $widget_ops = array(
+ 'classname' => 'widget_rss_links',
+ 'description' => __( "Links to your blog's RSS feeds", 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ );
+ parent::__construct(
+ 'rss_links',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'RSS Links', 'jetpack' ) ),
+ $widget_ops
+ );
+ }
+
+ function widget( $args, $instance ) {
+ $instance = wp_parse_args( (array) $instance, $this->defaults() );
+
+ extract( $args );
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+ echo $before_widget;
+
+ if ( $title ) {
+ echo $before_title . stripslashes( $title ) . $after_title;
+ }
+
+ if ( 'text' == $instance['format'] ) {
+ echo '<ul>';
+ }
+
+ if ( 'posts' == $instance['display'] ) {
+ $this->_rss_link( 'posts', $instance );
+ } elseif ( 'comments' == $instance['display'] ) {
+ $this->_rss_link( 'comments', $instance );
+ } elseif ( 'posts-comments' == $instance['display'] ) {
+ $this->_rss_link( 'posts', $instance );
+ $this->_rss_link( 'comments', $instance );
+ }
+
+ if ( 'text' == $instance['format'] ) {
+ echo '</ul>';
+ }
+
+ echo "\n" . $after_widget;
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'rss-links' );
+ }
+
+ /**
+ * Return an associative array of default values
+ * These values are used in new widgets as well as when sanitizing input.
+ *
+ * @return array Array of default values for the Widget's options
+ */
+ function defaults() {
+ return array(
+ 'title' => '',
+ 'display' => 'posts-comments',
+ 'format' => 'text',
+ );
+ }
+
+ function update( $new_instance, $old_instance ) {
+ $instance = $old_instance;
+
+ $instance['title'] = wp_filter_nohtml_kses( $new_instance['title'] );
+ $instance['display'] = $new_instance['display'];
+ $instance['format'] = $new_instance['format'];
+ $instance['imagesize'] = $new_instance['imagesize'];
+ $instance['imagecolor'] = $new_instance['imagecolor'];
+
+ return $instance;
+ }
+
+ function form( $instance ) {
+ $instance = wp_parse_args( (array) $instance, $this->defaults() );
+
+ $title = stripslashes( $instance['title'] );
+ $display = $instance['display'];
+ $format = $instance['format'];
+ $image_size = isset( $instance['imagesize'] ) ? $instance['imagesize'] : 0;
+ $image_color = isset( $instance['imagecolor'] ) ? $instance['imagecolor'] : 'red';
+
+ echo '<p><label for="' . $this->get_field_id( 'title' ) . '">' . esc_html__( 'Title:', 'jetpack' ) . '
+ <input class="widefat" id="' . $this->get_field_id( 'title' ) . '" name="' . $this->get_field_name( 'title' ) . '" type="text" value="' . esc_attr( $title ) . '" />
+ </label></p>';
+
+ $displays = array(
+ 'posts' => __( 'Posts', 'jetpack' ),
+ 'comments' => __( 'Comments', 'jetpack' ),
+ 'posts-comments' => __( 'Posts & Comments', 'jetpack' ),
+ );
+ echo '<p><label for="' . $this->get_field_id( 'display' ) . '">' . esc_html__( 'Feed(s) to Display:', 'jetpack' ) . '
+ <select class="widefat" id="' . $this->get_field_id( 'display' ) . '" name="' . $this->get_field_name( 'display' ) . '">';
+ foreach ( $displays as $display_option => $label ) {
+ echo '<option value="' . esc_attr( $display_option ) . '"';
+ if ( $display_option == $display ) {
+ echo ' selected="selected"';
+ }
+ echo '>' . esc_html( $label ) . '</option>' . "\n";
+ }
+ echo '</select></label></p>';
+
+ $formats = array(
+ 'text' => __( 'Text Link', 'jetpack' ),
+ 'image' => __( 'Image Link', 'jetpack' ),
+ 'text-image' => __( 'Text & Image Links', 'jetpack' ),
+ );
+ echo '<p><label for="' . $this->get_field_id( 'format' ) . '">' . _x( 'Format:', 'Noun', 'jetpack' ) . '
+ <select class="widefat" id="' . $this->get_field_id( 'format' ) . '" name="' . $this->get_field_name( 'format' ) . '" onchange="if ( this.value == \'text\' ) jQuery( \'#' . $this->get_field_id( 'image-settings' ) . '\' ).fadeOut(); else jQuery( \'#' . $this->get_field_id( 'image-settings' ) . '\' ).fadeIn();">';
+ foreach ( $formats as $format_option => $label ) {
+ echo '<option value="' . esc_attr( $format_option ) . '"';
+ if ( $format_option == $format ) {
+ echo ' selected="selected"';
+ }
+ echo '>' . esc_html( $label ) . '</option>' . "\n";
+ }
+ echo '</select></label></p>';
+
+ echo '<div id="' . $this->get_field_id( 'image-settings' ) . '"';
+ if ( 'text' == $format ) {
+ echo ' style="display: none;"';
+ }
+ echo '><h3>' . esc_html__( 'Image Settings:', 'jetpack' ) . '</h3>';
+
+ $sizes = array(
+ 'small' => __( 'Small', 'jetpack' ),
+ 'medium' => __( 'Medium', 'jetpack' ),
+ 'large' => __( 'Large', 'jetpack' ),
+ );
+ echo '<p><label for="' . $this->get_field_id( 'imagesize' ) . '">' . esc_html__( 'Image Size:', 'jetpack' ) . '
+ <select class="widefat" id="' . $this->get_field_id( 'imagesize' ) . '" name="' . $this->get_field_name( 'imagesize' ) . '">';
+ foreach ( $sizes as $size => $label ) {
+ echo '<option value="' . esc_attr( $size ) . '"';
+ if ( $size == $image_size ) {
+ echo ' selected="selected"';
+ }
+ echo '>' . esc_html( $label ) . '</option>' . "\n";
+ }
+ echo '</select></label></p>';
+
+ $colors = array(
+ 'red' => __( 'Red', 'jetpack' ),
+ 'orange' => __( 'Orange', 'jetpack' ),
+ 'green' => __( 'Green', 'jetpack' ),
+ 'blue' => __( 'Blue', 'jetpack' ),
+ 'purple' => __( 'Purple', 'jetpack' ),
+ 'pink' => __( 'Pink', 'jetpack' ),
+ 'silver' => __( 'Silver', 'jetpack' ),
+ );
+ echo '<p><label for="' . $this->get_field_id( 'imagecolor' ) . '">' . esc_html__( 'Image Color:', 'jetpack' ) . '
+ <select class="widefat" id="' . $this->get_field_id( 'imagecolor' ) . '" name="' . $this->get_field_name( 'imagecolor' ) . '">';
+ foreach ( $colors as $color => $label ) {
+ echo '<option value="' . esc_attr( $color ) . '"';
+ if ( $color == $image_color ) {
+ echo ' selected="selected"';
+ }
+ echo '>' . esc_html( $label ) . '</option>' . "\n";
+ }
+ echo '</select></label></p></div>';
+ }
+
+ function _rss_link( $type = 'posts', $args ) {
+ if ( 'posts' == $type ) {
+ $type_text = __( 'Posts', 'jetpack' );
+ $rss_type = 'rss2_url';
+ } elseif ( 'comments' == $type ) {
+ $type_text = __( 'Comments', 'jetpack' );
+ $rss_type = 'comments_rss2_url';
+ }
+
+ $subscribe_to = sprintf( __( 'Subscribe to %s', 'jetpack' ), $type_text );
+
+ $link_item = '';
+ $format = $args['format'];
+
+ /**
+ * Filters the target link attribute for the RSS link in the RSS widget.
+ *
+ * @module widgets
+ *
+ * @since 3.4.0
+ *
+ * @param bool false Control whether the link should open in a new tab. Default to false.
+ */
+ if ( apply_filters( 'jetpack_rsslinks_widget_target_blank', false ) ) {
+ $link_target = '_blank';
+ } else {
+ $link_target = '_self';
+ }
+
+ if ( 'image' == $format || 'text-image' == $format ) {
+ /**
+ * Filters the image used as RSS icon in the RSS widget.
+ *
+ * @module widgets
+ *
+ * @since 3.6.0
+ *
+ * @param string $var URL of RSS Widget icon.
+ */
+ $link_image = apply_filters( 'jetpack_rss_widget_icon', plugins_url( 'images/rss/' . $args['imagecolor'] . '-' . $args['imagesize'] . '.png', dirname( dirname( __FILE__ ) ) ) );
+ $link_item = '<a target="' . $link_target . '" href="' . get_bloginfo( $rss_type ) . '" title="' . esc_attr( $subscribe_to ) . '"><img src="' . esc_url( $link_image ) . '" alt="RSS Feed" /></a>';
+ }
+ if ( 'text-image' == $format ) {
+ $link_item .= '&nbsp;<a target="' . $link_target . '" href="' . get_bloginfo( $rss_type ) . '" title="' . esc_attr( $subscribe_to ) . '">' . esc_html__( 'RSS - ' . $type_text, 'jetpack' ) . '</a>';
+ }
+ if ( 'text' == $format ) {
+ $link_item = '<a target="' . $link_target . '" href="' . get_bloginfo( $rss_type ) . '" title="' . esc_attr( $subscribe_to ) . '">' . esc_html__( 'RSS - ' . $type_text, 'jetpack' ) . '</a>';
+ }
+
+ if ( 'text' == $format ) {
+ echo '<li>';
+ } else {
+ echo '<p>';
+ }
+ echo $link_item;
+ if ( 'text' == $format ) {
+ echo '</li>';
+ } else {
+ echo '</p>';
+ }
+
+ }
+} // Class Jetpack_RSS_Links_Widget
+
+function jetpack_rss_links_widget_init() {
+ register_widget( 'Jetpack_RSS_Links_Widget' );
+}
+add_action( 'widgets_init', 'jetpack_rss_links_widget_init' );
diff --git a/plugins/jetpack/modules/widgets/search.php b/plugins/jetpack/modules/widgets/search.php
new file mode 100644
index 00000000..54d866b5
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search.php
@@ -0,0 +1,815 @@
+<?php
+/**
+ * Jetpack Search: Jetpack_Search_Widget class
+ *
+ * @package Jetpack
+ * @subpackage Jetpack Search
+ * @since 5.0.0
+ */
+
+add_action( 'widgets_init', 'jetpack_search_widget_init' );
+
+function jetpack_search_widget_init() {
+ if ( ! Jetpack::is_active() || ! Jetpack_Plan::supports( 'search' ) ) {
+ return;
+ }
+
+ require_once JETPACK__PLUGIN_DIR . 'modules/search/class.jetpack-search-helpers.php';
+
+ register_widget( 'Jetpack_Search_Widget' );
+}
+
+/**
+ * Provides a widget to show available/selected filters on searches.
+ *
+ * @since 5.0.0
+ *
+ * @see WP_Widget
+ */
+class Jetpack_Search_Widget extends WP_Widget {
+
+ /**
+ * The Jetpack_Search instance.
+ *
+ * @since 5.7.0
+ * @var Jetpack_Search
+ */
+ protected $jetpack_search;
+
+ /**
+ * Number of aggregations (filters) to show by default.
+ *
+ * @since 5.8.0
+ * @var int
+ */
+ const DEFAULT_FILTER_COUNT = 5;
+
+ /**
+ * Default sort order for search results.
+ *
+ * @since 5.8.0
+ * @var string
+ */
+ const DEFAULT_SORT = 'relevance_desc';
+
+ /**
+ * Jetpack_Search_Widget constructor.
+ *
+ * @since 5.0.0
+ */
+ public function __construct( $name = null ) {
+ if ( empty( $name ) ) {
+ $name = esc_html__( 'Search', 'jetpack' );
+ }
+ parent::__construct(
+ Jetpack_Search_Helpers::FILTER_WIDGET_BASE,
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', $name ),
+ array(
+ 'classname' => 'jetpack-filters widget_search',
+ 'description' => __( 'Replaces the default search with an Elasticsearch-powered search interface and filters.', 'jetpack' ),
+ )
+ );
+
+ if (
+ Jetpack_Search_Helpers::is_active_widget( $this->id ) &&
+ ! $this->is_search_active()
+ ) {
+ $this->activate_search();
+ }
+
+ if ( is_admin() ) {
+ add_action( 'sidebar_admin_setup', array( $this, 'widget_admin_setup' ) );
+ } else {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
+ }
+
+ add_action( 'jetpack_search_render_filters_widget_title', array( 'Jetpack_Search_Template_Tags', 'render_widget_title' ), 10, 3 );
+ add_action( 'jetpack_search_render_filters', array( 'Jetpack_Search_Template_Tags', 'render_available_filters' ), 10, 2 );
+ }
+
+ /**
+ * Check whether search is currently active
+ *
+ * @since 6.3
+ */
+ public function is_search_active() {
+ return Jetpack::is_module_active( 'search' );
+ }
+
+ /**
+ * Activate search
+ *
+ * @since 6.3
+ */
+ public function activate_search() {
+ Jetpack::activate_module( 'search', false, false );
+ }
+
+
+ /**
+ * Enqueues the scripts and styles needed for the customizer.
+ *
+ * @since 5.7.0
+ */
+ public function widget_admin_setup() {
+ wp_enqueue_style( 'widget-jetpack-search-filters', plugins_url( 'search/css/search-widget-admin-ui.css', __FILE__ ) );
+
+ // Required for Tracks
+ wp_register_script(
+ 'jp-tracks',
+ '//stats.wp.com/w.js',
+ array(),
+ gmdate( 'YW' ),
+ true
+ );
+
+ wp_register_script(
+ 'jp-tracks-functions',
+ plugins_url( '_inc/lib/tracks/tracks-callables.js', JETPACK__PLUGIN_FILE ),
+ array(),
+ JETPACK__VERSION,
+ false
+ );
+
+ wp_register_script(
+ 'jetpack-search-widget-admin',
+ plugins_url( 'search/js/search-widget-admin.js', __FILE__ ),
+ array( 'jquery', 'jquery-ui-sortable', 'jp-tracks', 'jp-tracks-functions' ),
+ JETPACK__VERSION
+ );
+
+ wp_localize_script(
+ 'jetpack-search-widget-admin', 'jetpack_search_filter_admin', array(
+ 'defaultFilterCount' => self::DEFAULT_FILTER_COUNT,
+ 'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
+ 'tracksEventData' => array(
+ 'is_customizer' => (int) is_customize_preview(),
+ ),
+ 'i18n' => array(
+ 'month' => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', false ),
+ 'year' => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', false ),
+ 'monthUpdated' => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', true ),
+ 'yearUpdated' => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', true ),
+ ),
+ )
+ );
+
+ wp_enqueue_script( 'jetpack-search-widget-admin' );
+ }
+
+ /**
+ * Enqueue scripts and styles for the frontend.
+ *
+ * @since 5.8.0
+ */
+ public function enqueue_frontend_scripts() {
+ if ( ! is_active_widget( false, false, $this->id_base, true ) ) {
+ return;
+ }
+
+ wp_enqueue_script(
+ 'jetpack-search-widget',
+ plugins_url( 'search/js/search-widget.js', __FILE__ ),
+ array( 'jquery' ),
+ JETPACK__VERSION,
+ true
+ );
+
+ wp_enqueue_style( 'jetpack-search-widget', plugins_url( 'search/css/search-widget-frontend.css', __FILE__ ) );
+ }
+
+ /**
+ * Get the list of valid sort types/orders.
+ *
+ * @since 5.8.0
+ *
+ * @return array The sort orders.
+ */
+ private function get_sort_types() {
+ return array(
+ 'relevance|DESC' => is_admin() ? esc_html__( 'Relevance (recommended)', 'jetpack' ) : esc_html__( 'Relevance', 'jetpack' ),
+ 'date|DESC' => esc_html__( 'Newest first', 'jetpack' ),
+ 'date|ASC' => esc_html__( 'Oldest first', 'jetpack' ),
+ );
+ }
+
+ /**
+ * Callback for an array_filter() call in order to only get filters for the current widget.
+ *
+ * @see Jetpack_Search_Widget::widget()
+ *
+ * @since 5.7.0
+ *
+ * @param array $item Filter item.
+ *
+ * @return bool Whether the current filter item is for the current widget.
+ */
+ function is_for_current_widget( $item ) {
+ return isset( $item['widget_id'] ) && $this->id == $item['widget_id'];
+ }
+
+ /**
+ * This method returns a boolean for whether the widget should show site-wide filters for the site.
+ *
+ * This is meant to provide backwards-compatibility for VIP, and other professional plan users, that manually
+ * configured filters via `Jetpack_Search::set_filters()`.
+ *
+ * @since 5.7.0
+ *
+ * @return bool Whether the widget should display site-wide filters or not.
+ */
+ public function should_display_sitewide_filters() {
+ $filter_widgets = get_option( 'widget_jetpack-search-filters' );
+
+ // This shouldn't be empty, but just for sanity
+ if ( empty( $filter_widgets ) ) {
+ return false;
+ }
+
+ // If any widget has any filters, return false
+ foreach ( $filter_widgets as $number => $widget ) {
+ $widget_id = sprintf( '%s-%d', $this->id_base, $number );
+ if ( ! empty( $widget['filters'] ) && is_active_widget( false, $widget_id, $this->id_base ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function jetpack_search_populate_defaults( $instance ) {
+ $instance = wp_parse_args(
+ (array) $instance, array(
+ 'title' => '',
+ 'search_box_enabled' => true,
+ 'user_sort_enabled' => true,
+ 'sort' => self::DEFAULT_SORT,
+ 'filters' => array( array() ),
+ 'post_types' => array(),
+ )
+ );
+
+ return $instance;
+ }
+
+ /**
+ * Responsible for rendering the widget on the frontend.
+ *
+ * @since 5.0.0
+ *
+ * @param array $args Widgets args supplied by the theme.
+ * @param array $instance The current widget instance.
+ */
+ public function widget( $args, $instance ) {
+ $instance = $this->jetpack_search_populate_defaults( $instance );
+
+ $display_filters = false;
+
+ if ( Jetpack::is_development_mode() ) {
+ echo $args['before_widget'];
+ ?><div id="<?php echo esc_attr( $this->id ); ?>-wrapper">
+ <div class="jetpack-search-sort-wrapper">
+ <label>
+ <?php esc_html_e( 'Jetpack Search not supported in Development Mode', 'jetpack' ); ?>
+ </label>
+ </div>
+ </div><?php
+ echo $args['after_widget'];
+ return;
+ }
+
+ if ( is_search() ) {
+ if ( Jetpack_Search_Helpers::should_rerun_search_in_customizer_preview() ) {
+ Jetpack_Search::instance()->update_search_results_aggregations();
+ }
+
+ $filters = Jetpack_Search::instance()->get_filters();
+
+ if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
+ $filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
+ }
+
+ if ( ! empty( $filters ) ) {
+ $display_filters = true;
+ }
+ }
+
+ if ( ! $display_filters && empty( $instance['search_box_enabled'] ) && empty( $instance['user_sort_enabled'] ) ) {
+ return;
+ }
+
+ $title = isset( $instance['title'] ) ? $instance['title'] : '';
+
+ if ( empty( $title ) ) {
+ $title = '';
+ }
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
+
+ echo $args['before_widget'];
+ ?><div id="<?php echo esc_attr( $this->id ); ?>-wrapper">
+ <?php
+
+ if ( ! empty( $title ) ) {
+ /**
+ * Responsible for displaying the title of the Jetpack Search filters widget.
+ *
+ * @module search
+ *
+ * @since 5.7.0
+ *
+ * @param string $title The widget's title
+ * @param string $args['before_title'] The HTML tag to display before the title
+ * @param string $args['after_title'] The HTML tag to display after the title
+ */
+ do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
+ }
+
+ $default_sort = isset( $instance['sort'] ) ? $instance['sort'] : self::DEFAULT_SORT;
+ list( $orderby, $order ) = $this->sorting_to_wp_query_param( $default_sort );
+ $current_sort = "{$orderby}|{$order}";
+
+ // we need to dynamically inject the sort field into the search box when the search box is enabled, and display
+ // it separately when it's not.
+ if ( ! empty( $instance['search_box_enabled'] ) ) {
+ Jetpack_Search_Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
+ }
+
+ if ( ! empty( $instance['search_box_enabled'] ) && ! empty( $instance['user_sort_enabled'] ) ) :
+ ?>
+ <div class="jetpack-search-sort-wrapper">
+ <label>
+ <?php esc_html_e( 'Sort by', 'jetpack' ); ?>
+ <select class="jetpack-search-sort">
+ <?php foreach ( $this->get_sort_types() as $sort => $label ) { ?>
+ <option value="<?php echo esc_attr( $sort ); ?>" <?php selected( $current_sort, $sort ); ?>>
+ <?php echo esc_html( $label ); ?>
+ </option>
+ <?php } ?>
+ </select>
+ </label>
+ </div>
+ <?php
+ endif;
+
+ if ( $display_filters ) {
+ /**
+ * Responsible for rendering filters to narrow down search results.
+ *
+ * @module search
+ *
+ * @since 5.8.0
+ *
+ * @param array $filters The possible filters for the current query.
+ * @param array $post_types An array of post types to limit filtering to.
+ */
+ do_action(
+ 'jetpack_search_render_filters',
+ $filters,
+ isset( $instance['post_types'] ) ? $instance['post_types'] : null
+ );
+ }
+
+ $this->maybe_render_sort_javascript( $instance, $order, $orderby );
+
+ echo '</div>';
+ echo $args['after_widget'];
+ }
+
+ /**
+ * Renders JavaScript for the sorting controls on the frontend.
+ *
+ * This JS is a bit complicated, but here's what it's trying to do:
+ * - find the search form
+ * - find the orderby/order fields and set default values
+ * - detect changes to the sort field, if it exists, and use it to set the order field values
+ *
+ * @since 5.8.0
+ *
+ * @param array $instance The current widget instance.
+ * @param string $order The order to initialize the select with.
+ * @param string $orderby The orderby to initialize the select with.
+ */
+ private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
+ if ( ! empty( $instance['user_sort_enabled'] ) ) :
+ ?>
+ <script type="text/javascript">
+ jQuery( document ).ready( function( $ ) {
+ var orderByDefault = '<?php echo 'date' === $orderby ? 'date' : 'relevance'; ?>',
+ orderDefault = '<?php echo 'ASC' === $order ? 'ASC' : 'DESC'; ?>',
+ widgetId = decodeURIComponent( '<?php echo rawurlencode( $this->id ); ?>' ),
+ searchQuery = decodeURIComponent( '<?php echo rawurlencode( get_query_var( 's', '' ) ); ?>' ),
+ isSearch = <?php echo (int) is_search(); ?>;
+
+ var container = $( '#' + widgetId + '-wrapper' ),
+ form = container.find('.jetpack-search-form form'),
+ orderBy = form.find( 'input[name=orderby]'),
+ order = form.find( 'input[name=order]'),
+ searchInput = form.find( 'input[name="s"]' );
+
+ orderBy.val( orderByDefault );
+ order.val( orderDefault );
+
+ // Some themes don't set the search query, which results in the query being lost
+ // when doing a sort selection. So, if the query isn't set, let's set it now. This approach
+ // is chosen over running a regex over HTML for every search query performed.
+ if ( isSearch && ! searchInput.val() ) {
+ searchInput.val( searchQuery );
+ }
+
+ searchInput.addClass( 'show-placeholder' );
+
+ container.find( '.jetpack-search-sort' ).change( function( event ) {
+ var values = event.target.value.split( '|' );
+ orderBy.val( values[0] );
+ order.val( values[1] );
+
+ form.submit();
+ });
+ } );
+ </script>
+ <?php
+ endif;
+ }
+
+ /**
+ * Convert a sort string into the separate order by and order parts.
+ *
+ * @since 5.8.0
+ *
+ * @param string $sort A sort string.
+ *
+ * @return array Order by and order.
+ */
+ private function sorting_to_wp_query_param( $sort ) {
+ $parts = explode( '|', $sort );
+ $orderby = isset( $_GET['orderby'] )
+ ? $_GET['orderby']
+ : $parts[0];
+
+ $order = isset( $_GET['order'] )
+ ? strtoupper( $_GET['order'] )
+ : ( ( isset( $parts[1] ) && 'ASC' === strtoupper( $parts[1] ) ) ? 'ASC' : 'DESC' );
+
+ return array( $orderby, $order );
+ }
+
+ /**
+ * Updates a particular instance of the widget. Validates and sanitizes the options.
+ *
+ * @since 5.0.0
+ *
+ * @param array $new_instance New settings for this instance as input by the user via Jetpack_Search_Widget::form().
+ * @param array $old_instance Old settings for this instance.
+ *
+ * @return array Settings to save.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $instance = array();
+
+ $instance['title'] = sanitize_text_field( $new_instance['title'] );
+ $instance['search_box_enabled'] = empty( $new_instance['search_box_enabled'] ) ? '0' : '1';
+ $instance['user_sort_enabled'] = empty( $new_instance['user_sort_enabled'] ) ? '0' : '1';
+ $instance['sort'] = $new_instance['sort'];
+ $instance['post_types'] = empty( $new_instance['post_types'] ) || empty( $instance['search_box_enabled'] )
+ ? array()
+ : array_map( 'sanitize_key', $new_instance['post_types'] );
+
+ $filters = array();
+ if ( isset( $new_instance['filter_type'] ) ) {
+ foreach ( (array) $new_instance['filter_type'] as $index => $type ) {
+ $count = intval( $new_instance['num_filters'][ $index ] );
+ $count = min( 50, $count ); // Set max boundary at 50.
+ $count = max( 1, $count ); // Set min boundary at 1.
+
+ switch ( $type ) {
+ case 'taxonomy':
+ $filters[] = array(
+ 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
+ 'type' => 'taxonomy',
+ 'taxonomy' => sanitize_key( $new_instance['taxonomy_type'][ $index ] ),
+ 'count' => $count,
+ );
+ break;
+ case 'post_type':
+ $filters[] = array(
+ 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
+ 'type' => 'post_type',
+ 'count' => $count,
+ );
+ break;
+ case 'date_histogram':
+ $filters[] = array(
+ 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
+ 'type' => 'date_histogram',
+ 'count' => $count,
+ 'field' => sanitize_key( $new_instance['date_histogram_field'][ $index ] ),
+ 'interval' => sanitize_key( $new_instance['date_histogram_interval'][ $index ] ),
+ );
+ break;
+ }
+ }
+ }
+
+ if ( ! empty( $filters ) ) {
+ $instance['filters'] = $filters;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Outputs the settings update form.
+ *
+ * @since 5.0.0
+ *
+ * @param array $instance Current settings.
+ */
+ public function form( $instance ) {
+ $instance = $this->jetpack_search_populate_defaults( $instance );
+
+ $title = strip_tags( $instance['title'] );
+
+ $hide_filters = Jetpack_Search_Helpers::are_filters_by_widget_disabled();
+
+ $classes = sprintf(
+ 'jetpack-search-filters-widget %s %s %s',
+ $hide_filters ? 'hide-filters' : '',
+ $instance['search_box_enabled'] ? '' : 'hide-post-types',
+ $this->id
+ );
+ ?>
+ <div class="<?php echo esc_attr( $classes ); ?>">
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
+ <?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $title ); ?>"
+ />
+ </p>
+
+ <p>
+ <label>
+ <input
+ type="checkbox"
+ class="jetpack-search-filters-widget__search-box-enabled"
+ name="<?php echo esc_attr( $this->get_field_name( 'search_box_enabled' ) ); ?>"
+ <?php checked( $instance['search_box_enabled'] ); ?>
+ />
+ <?php esc_html_e( 'Show search box', 'jetpack' ); ?>
+ </label>
+ </p>
+ <p>
+ <label>
+ <input
+ type="checkbox"
+ class="jetpack-search-filters-widget__sort-controls-enabled"
+ name="<?php echo esc_attr( $this->get_field_name( 'user_sort_enabled' ) ); ?>"
+ <?php checked( $instance['user_sort_enabled'] ); ?>
+ <?php disabled( ! $instance['search_box_enabled'] ); ?>
+ />
+ <?php esc_html_e( 'Show sort selection dropdown', 'jetpack' ); ?>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__post-types-select">
+ <label><?php esc_html_e( 'Post types to search (minimum of 1):', 'jetpack' ); ?></label>
+ <?php foreach ( get_post_types( array( 'exclude_from_search' => false ), 'objects' ) as $post_type ) : ?>
+ <label>
+ <input
+ type="checkbox"
+ value="<?php echo esc_attr( $post_type->name ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'post_types' ) ); ?>[]"
+ <?php checked( empty( $instance['post_types'] ) || in_array( $post_type->name, $instance['post_types'] ) ); ?>
+ />&nbsp;
+ <?php echo esc_html( $post_type->label ); ?>
+ </label>
+ <?php endforeach; ?>
+ </p>
+
+ <p>
+ <label>
+ <?php esc_html_e( 'Default sort order:', 'jetpack' ); ?>
+ <select
+ name="<?php echo esc_attr( $this->get_field_name( 'sort' ) ); ?>"
+ class="widefat jetpack-search-filters-widget__sort-order">
+ <?php foreach ( $this->get_sort_types() as $sort_type => $label ) { ?>
+ <option value="<?php echo esc_attr( $sort_type ); ?>" <?php selected( $instance['sort'], $sort_type ); ?>>
+ <?php echo esc_html( $label ); ?>
+ </option>
+ <?php } ?>
+ </select>
+ </label>
+ </p>
+
+ <?php if ( ! $hide_filters ) : ?>
+ <script class="jetpack-search-filters-widget__filter-template" type="text/template">
+ <?php echo $this->render_widget_edit_filter( array(), true ); ?>
+ </script>
+ <div class="jetpack-search-filters-widget__filters">
+ <?php foreach ( (array) $instance['filters'] as $filter ) : ?>
+ <?php $this->render_widget_edit_filter( $filter ); ?>
+ <?php endforeach; ?>
+ </div>
+ <p class="jetpack-search-filters-widget__add-filter-wrapper">
+ <a class="button jetpack-search-filters-widget__add-filter" href="#">
+ <?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
+ </a>
+ </p>
+ <noscript>
+ <p class="jetpack-search-filters-help">
+ <?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
+ </p>
+ </noscript>
+ <?php if ( is_customize_preview() ) : ?>
+ <p class="jetpack-search-filters-help">
+ <a href="https://jetpack.com/support/search/#filters-not-showing-up" target="_blank">
+ <?php esc_html_e( "Why aren't my filters appearing?", 'jetpack' ); ?>
+ </a>
+ </p>
+ <?php endif; ?>
+ <?php endif; ?>
+ </div>
+ <?php
+ }
+
+ /**
+ * We need to render HTML in two formats: an Underscore template (client-side)
+ * and native PHP (server-side). This helper function allows for easy rendering
+ * of attributes in both formats.
+ *
+ * @since 5.8.0
+ *
+ * @param string $name Attribute name.
+ * @param string $value Attribute value.
+ * @param bool $is_template Whether this is for an Underscore template or not.
+ */
+ private function render_widget_attr( $name, $value, $is_template ) {
+ echo $is_template ? "<%= $name %>" : esc_attr( $value );
+ }
+
+ /**
+ * We need to render HTML in two formats: an Underscore template (client-size)
+ * and native PHP (server-side). This helper function allows for easy rendering
+ * of the "selected" attribute in both formats.
+ *
+ * @since 5.8.0
+ *
+ * @param string $name Attribute name.
+ * @param string $value Attribute value.
+ * @param string $compare Value to compare to the attribute value to decide if it should be selected.
+ * @param bool $is_template Whether this is for an Underscore template or not.
+ */
+ private function render_widget_option_selected( $name, $value, $compare, $is_template ) {
+ $compare_js = rawurlencode( $compare );
+ echo $is_template ? "<%= decodeURIComponent( '$compare_js' ) === $name ? 'selected=\"selected\"' : '' %>" : selected( $value, $compare );
+ }
+
+ /**
+ * Responsible for rendering a single filter in the customizer or the widget administration screen in wp-admin.
+ *
+ * We use this method for two purposes - rendering the fields server-side, and also rendering a script template for Underscore.
+ *
+ * @since 5.7.0
+ *
+ * @param array $filter The filter to render.
+ * @param bool $is_template Whether this is for an Underscore template or not.
+ */
+ public function render_widget_edit_filter( $filter, $is_template = false ) {
+ $args = wp_parse_args(
+ $filter, array(
+ 'name' => '',
+ 'type' => 'taxonomy',
+ 'taxonomy' => '',
+ 'post_type' => '',
+ 'field' => '',
+ 'interval' => '',
+ 'count' => self::DEFAULT_FILTER_COUNT,
+ )
+ );
+
+ $args['name_placeholder'] = Jetpack_Search_Helpers::generate_widget_filter_name( $args );
+
+ ?>
+ <div class="jetpack-search-filters-widget__filter is-<?php $this->render_widget_attr( 'type', $args['type'], $is_template ); ?>">
+ <p class="jetpack-search-filters-widget__type-select">
+ <label>
+ <?php esc_html_e( 'Filter Type:', 'jetpack' ); ?>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'filter_type' ) ); ?>[]" class="widefat filter-select">
+ <option value="taxonomy" <?php $this->render_widget_option_selected( 'type', $args['type'], 'taxonomy', $is_template ); ?>>
+ <?php esc_html_e( 'Taxonomy', 'jetpack' ); ?>
+ </option>
+ <option value="post_type" <?php $this->render_widget_option_selected( 'type', $args['type'], 'post_type', $is_template ); ?>>
+ <?php esc_html_e( 'Post Type', 'jetpack' ); ?>
+ </option>
+ <option value="date_histogram" <?php $this->render_widget_option_selected( 'type', $args['type'], 'date_histogram', $is_template ); ?>>
+ <?php esc_html_e( 'Date', 'jetpack' ); ?>
+ </option>
+ </select>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__taxonomy-select">
+ <label>
+ <?php
+ esc_html_e( 'Choose a taxonomy:', 'jetpack' );
+ $seen_taxonomy_labels = array();
+ ?>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'taxonomy_type' ) ); ?>[]" class="widefat taxonomy-select">
+ <?php foreach ( get_taxonomies( array( 'public' => true ), 'objects' ) as $taxonomy ) : ?>
+ <option value="<?php echo esc_attr( $taxonomy->name ); ?>" <?php $this->render_widget_option_selected( 'taxonomy', $args['taxonomy'], $taxonomy->name, $is_template ); ?>>
+ <?php
+ $label = in_array( $taxonomy->label, $seen_taxonomy_labels )
+ ? sprintf(
+ /* translators: %1$s is the taxonomy name, %2s is the name of its type to help distinguish between several taxonomies with the same name, e.g. category and tag. */
+ _x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack' ),
+ $taxonomy->label,
+ $taxonomy->name
+ )
+ : $taxonomy->label;
+ echo esc_html( $label );
+ $seen_taxonomy_labels[] = $taxonomy->label;
+ ?>
+ </option>
+ <?php endforeach; ?>
+ </select>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__date-histogram-select">
+ <label>
+ <?php esc_html_e( 'Choose a field:', 'jetpack' ); ?>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_field' ) ); ?>[]" class="widefat date-field-select">
+ <option value="post_date" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date', $is_template ); ?>>
+ <?php esc_html_e( 'Date', 'jetpack' ); ?>
+ </option>
+ <option value="post_date_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date_gmt', $is_template ); ?>>
+ <?php esc_html_e( 'Date GMT', 'jetpack' ); ?>
+ </option>
+ <option value="post_modified" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified', $is_template ); ?>>
+ <?php esc_html_e( 'Modified', 'jetpack' ); ?>
+ </option>
+ <option value="post_modified_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified_gmt', $is_template ); ?>>
+ <?php esc_html_e( 'Modified GMT', 'jetpack' ); ?>
+ </option>
+ </select>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__date-histogram-select">
+ <label>
+ <?php esc_html_e( 'Choose an interval:' ); ?>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_interval' ) ); ?>[]" class="widefat date-interval-select">
+ <option value="month" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'month', $is_template ); ?>>
+ <?php esc_html_e( 'Month', 'jetpack' ); ?>
+ </option>
+ <option value="year" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'year', $is_template ); ?>>
+ <?php esc_html_e( 'Year', 'jetpack' ); ?>
+ </option>
+ </select>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__title">
+ <label>
+ <?php esc_html_e( 'Title:', 'jetpack' ); ?>
+ <input
+ class="widefat"
+ type="text"
+ name="<?php echo esc_attr( $this->get_field_name( 'filter_name' ) ); ?>[]"
+ value="<?php $this->render_widget_attr( 'name', $args['name'], $is_template ); ?>"
+ placeholder="<?php $this->render_widget_attr( 'name_placeholder', $args['name_placeholder'], $is_template ); ?>"
+ />
+ </label>
+ </p>
+
+ <p>
+ <label>
+ <?php esc_html_e( 'Maximum number of filters (1-50):', 'jetpack' ); ?>
+ <input
+ class="widefat filter-count"
+ name="<?php echo esc_attr( $this->get_field_name( 'num_filters' ) ); ?>[]"
+ type="number"
+ value="<?php $this->render_widget_attr( 'count', $args['count'], $is_template ); ?>"
+ min="1"
+ max="50"
+ step="1"
+ required
+ />
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__controls">
+ <a href="#" class="delete"><?php esc_html_e( 'Remove', 'jetpack' ); ?></a>
+ </p>
+ </div>
+ <?php
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/search/css/search-widget-admin-ui.css b/plugins/jetpack/modules/widgets/search/css/search-widget-admin-ui.css
new file mode 100644
index 00000000..a3313f05
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search/css/search-widget-admin-ui.css
@@ -0,0 +1,87 @@
+.jetpack-search-filters-widget__filter {
+ background: #f9f9f9;
+ border: 1px solid #dfdfdf;
+ padding: 0 12px;
+ margin-bottom: 12px;
+ cursor: move;
+}
+
+.jetpack-search-filters-widget__controls {
+ text-align: right;
+}
+
+.jetpack-search-filters-widget .jetpack-search-filters-widget__sort-controls-enabled {
+ margin-left: 24px;
+}
+
+.jetpack-search-filters-widget__controls .delete {
+ color: #a00;
+}
+
+.jetpack-search-filters-widget.hide-filters .jetpack-search-filters-widget__filter {
+ display: none;
+}
+
+.button.jetpack-search-filters-widget__add-filter {
+ margin-bottom: 10px;
+}
+
+/* Assume that taxonomy select is the default selected. Other controls should be hidden here. */
+.jetpack-search-filters-widget__post-type-select {
+ display: none;
+}
+
+.jetpack-search-filters-widget__date-histogram-select {
+ display: none;
+}
+
+.jetpack-search-filters-widget__filter-placeholder {
+ border: 1px #555 dashed;
+ background-color: #eee;
+ height: 286px;
+ margin-bottom: 12px;
+}
+
+/* When post type is selected, remove the other controls */
+.jetpack-search-filters-widget__filter.is-post_type .jetpack-search-filters-widget__taxonomy-select {
+ display: none;
+}
+
+/* When date is selected, remove the other controls */
+.jetpack-search-filters-widget__filter.is-date_histogram .jetpack-search-filters-widget__date-histogram-select {
+ display: inline;
+}
+
+.jetpack-search-filters-widget__filter.is-date_histogram .jetpack-search-filters-widget__taxonomy-select {
+ display: none;
+}
+
+.jetpack-search-filters-widget.hide-post-types .jetpack-search-filters-widget__post-types-select {
+ display: none;
+}
+
+.jetpack-search-filters-help:before {
+ display: inline-block;
+ position: relative;
+ font-family: dashicons;
+ font-size: 20px;
+ top: 5px;
+ line-height: 1px;
+ content:"\f223";
+}
+.jetpack-search-filters-help {
+ padding: 5px 5px 15px 0;
+}
+
+.jetpack-search-filters-widget__post-types-select label {
+ display: block;
+ margin-bottom: 4px;
+}
+
+.jetpack-search-filters-widget__post-types-select input[type="checkbox"] {
+ margin-left: 24px;
+}
+
+body.no-js .jetpack-search-filters-widget__add-filter-wrapper {
+ display: none;
+}
diff --git a/plugins/jetpack/modules/widgets/search/css/search-widget-frontend.css b/plugins/jetpack/modules/widgets/search/css/search-widget-frontend.css
new file mode 100644
index 00000000..58c7cf3e
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search/css/search-widget-frontend.css
@@ -0,0 +1,66 @@
+.jetpack-search-filters-widget__sub-heading {
+ font-size: inherit;
+ font-weight: bold;
+ margin: 0 0 .5em;
+ padding: 0;
+}
+
+/* The first heading after the form */
+.jetpack-search-form + .jetpack-search-filters-widget__sub-heading {
+ margin-top: 1.5em;
+ margin-bottom: 0.5em !important;
+}
+
+.jetpack-search-filters-widget__clear {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+.jetpack-search-sort-wrapper {
+ margin-top: 1em;
+ margin-bottom: 1.5em;
+}
+
+.jetpack-search-sort-wrapper label {
+ display: inherit;
+}
+
+.widget_search .jetpack-search-filters-widget__filter-list input[type="checkbox"] {
+ width: auto;
+ height: auto;
+}
+
+ul.jetpack-search-filters-widget__filter-list li {
+ border: none;
+ padding: 0;
+ list-style: none;
+}
+
+ul.jetpack-search-filters-widget__filter-list li a {
+ text-decoration: none;
+}
+
+ul.jetpack-search-filters-widget__filter-list li a:hover {
+ box-shadow: none;
+}
+
+ul.jetpack-search-filters-widget__filter-list li label {
+ font-weight: inherit;
+ display: inherit;
+}
+
+.jetpack-search-filters-widget__filter-list {
+ list-style: none;
+}
+
+ul.jetpack-search-filters-widget__filter-list {
+ margin-bottom: 1.5em;
+}
+
+body.search .jetpack-search-form input[name="s"]::placeholder {
+ color: transparent;
+}
+
+body.search .jetpack-search-form input[name="s"].show-placeholder::placeholder {
+ color: inherit;
+}
diff --git a/plugins/jetpack/modules/widgets/search/js/search-widget-admin.js b/plugins/jetpack/modules/widgets/search/js/search-widget-admin.js
new file mode 100644
index 00000000..5840a408
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search/js/search-widget-admin.js
@@ -0,0 +1,360 @@
+/* globals jetpack_search_filter_admin, jQuery, analytics */
+
+( function( $, args ) {
+ var defaultFilterCount = ( 'undefined' !== typeof args && args.defaultFilterCount ) ?
+ args.defaultFilterCount :
+ 5; // Just in case we couldn't find the defaultFiltercount arg
+
+ $( document ).ready( function() {
+ setListeners();
+
+ window.JetpackSearch = window.JetpackSearch || {};
+ window.JetpackSearch.addFilter = addFilter;
+
+ // Initialize Tracks
+ if ( 'undefined' !== typeof analytics && args.tracksUserData ) {
+ analytics.initialize( args.tracksUserData.userid, args.tracksUserData.username );
+ }
+ } );
+
+ function generateFilterTitlePlaceholder( container ) {
+ var placeholder = null,
+ isModified = null,
+ isMonth = null,
+ type = container.find( '.filter-select' ).val();
+
+ if ( 'taxonomy' === type ) {
+ placeholder = container.find('.taxonomy-select option:selected').text().trim();
+ } else if ( 'date_histogram' === type && args && args.i18n ) {
+ isModified = ( -1 !== container.find( '.date-field-select' ).val().indexOf( 'modified' ) );
+ isMonth = ( 'month' === container.find( '.date-interval-select' ).val() );
+
+ if ( isMonth ) {
+ placeholder = isModified ?
+ args.i18n.monthUpdated :
+ args.i18n.month;
+ } else {
+ placeholder = isModified ?
+ args.i18n.yearUpdated :
+ args.i18n.year;
+ }
+ } else {
+ placeholder = container.find('.filter-select option:selected').text().trim();
+ }
+
+ $( container ).find('.jetpack-search-filters-widget__title input').prop( 'placeholder', placeholder );
+ }
+
+ var addFilter = function( filtersContainer, args ) {
+ var template = _.template(
+ filtersContainer
+ .closest( '.jetpack-search-filters-widget' )
+ .find( '.jetpack-search-filters-widget__filter-template' )
+ .html()
+ );
+ generateFilterTitlePlaceholder( filtersContainer.append( template( args ) ) );
+ };
+
+ var setListeners = function( widget ) {
+ widget = ( 'undefined' === typeof widget ) ?
+ $( '.jetpack-search-filters-widget' ):
+ widget;
+
+ var getContainer = function( el ) {
+ return $( el ).closest('.jetpack-search-filters-widget__filter');
+ };
+
+ widget.on( 'change', '.filter-select', function() {
+ var select = $( this ),
+ selectVal = select.val(),
+ eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.type = selectVal;
+
+ select
+ .closest( '.jetpack-search-filters-widget__filter' )
+ .attr( 'class', 'jetpack-search-filters-widget__filter' )
+ .addClass( 'is-' + selectVal );
+
+ generateFilterTitlePlaceholder( getContainer( this ) );
+
+ trackAndBumpMCStats( 'changed_filter_type', eventArgs );
+ } );
+
+ // enable showing sort controls only if showing search box is enabled
+ widget.on( 'change', '.jetpack-search-filters-widget__search-box-enabled', function() {
+ var checkbox = $( this ),
+ checkboxVal = checkbox.is(':checked'),
+ filterParent = checkbox.closest( '.jetpack-search-filters-widget' ),
+ sortControl = filterParent.find( '.jetpack-search-filters-widget__sort-controls-enabled' );
+
+ filterParent.toggleClass( 'hide-post-types' );
+
+ if ( checkboxVal ) {
+ sortControl.removeAttr( 'disabled' );
+ trackAndBumpMCStats( 'enabled_search_box', args.tracksEventData );
+ } else {
+ sortControl.prop( 'checked', false );
+ sortControl.prop( 'disabled', true );
+ trackAndBumpMCStats( 'disabled_search_box', args.tracksEventData );
+ }
+ } );
+
+ widget.on( 'change', '.jetpack-search-filters-widget__sort-controls-enabled', function() {
+ if ( $( this ).is( ':checked' ) ) {
+ trackAndBumpMCStats( 'enabled_sort_controls', args.tracksEventData );
+ } else {
+ trackAndBumpMCStats( 'disabled_sort_controls', args.tracksEventData );
+ }
+ } );
+
+ widget.on( 'click', '.jetpack-search-filters-widget__post-types-select input[type="checkbox"]', function( e ) {
+ var t = $( this );
+ var siblingsChecked = t.closest( '.jetpack-search-filters-widget' )
+ .find( '.jetpack-search-filters-widget__post-types-select input[type="checkbox"]:checked' );
+
+ if ( 0 === siblingsChecked.length ) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ trackAndBumpMCStats( 'attempted_no_post_types', args.tracksEventData );
+ }
+ } );
+
+ widget.on( 'change', '.jetpack-search-filters-widget__post-types-select input[type="checkbox"]', function() {
+ var t = $( this );
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer,
+ post_type: t.val()
+ };
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ if ( t.is( ':checked' ) ) {
+ trackAndBumpMCStats( 'added_post_type', eventArgs );
+ } else {
+ trackAndBumpMCStats( 'removed_post_type', eventArgs );
+ }
+ } );
+
+ widget.on( 'change', '.jetpack-search-filters-widget__sort-order', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.order = $( this ).val();
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_sort_order', eventArgs );
+ } );
+
+ widget.on( 'change', '.jetpack-search-filters-widget__taxonomy-select select', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.taxonomy = $( this ).val();
+
+ generateFilterTitlePlaceholder( getContainer( this ) );
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_taxonomy', eventArgs );
+ } );
+
+ widget.on( 'change', 'select.date-field-select', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.field = $( this ).val();
+
+ generateFilterTitlePlaceholder( getContainer( this ) );
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_date_field', eventArgs );
+ } );
+
+ widget.on( 'change', 'select.date-interval-select', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.interval = $( this ).val();
+
+ generateFilterTitlePlaceholder( getContainer( this ) );
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_date_interval', eventArgs );
+ } );
+
+ widget.on( 'change', 'input.filter-count', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.count = $( this ).val();
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_filter_count', eventArgs );
+ } );
+
+ // add filter button
+ widget.on( 'click', '.jetpack-search-filters-widget__add-filter', function( e ) {
+ e.preventDefault();
+
+ var filtersContainer = $( this )
+ .closest( '.jetpack-search-filters-widget' )
+ .find( '.jetpack-search-filters-widget__filters' );
+
+ addFilter( filtersContainer, {
+ type: 'taxonomy',
+ taxonomy: '',
+ post_type: '',
+ field: '',
+ interval: '',
+ count: defaultFilterCount,
+ name_placeholder: '',
+ name: ''
+ } );
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ // Trigger change event to let legacy widget admin know the widget state is "dirty"
+ filtersContainer
+ .find( '.jetpack-search-filters-widget__filter' )
+ .find( 'input, textarea, select' )
+ .change();
+
+ trackAndBumpMCStats( 'added_filter', args.tracksEventData );
+ } );
+
+ widget.on( 'click', '.jetpack-search-filters-widget__controls .delete', function( e ) {
+ e.preventDefault();
+ var filter = $( this ).closest( '.jetpack-search-filters-widget__filter' ),
+ eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.type = filter.find( '.filter-select' ).val();
+
+ switch ( eventArgs.type ) {
+ case 'taxonomy':
+ eventArgs.taxonomy = filter.find( '.jetpack-search-filters-widget__taxonomy-select select' ).val();
+ break;
+ case 'date_histogram':
+ eventArgs.dateField = filter.find( '.jetpack-search-filters-widget__date-histogram-select:first select' ).val();
+ eventArgs.dateInterval = filter.find( '.jetpack-search-filters-widget__date-histogram-select:nth-child( 2 ) select' ).val();
+ break;
+ }
+
+ eventArgs.filterCount = filter.find( '.filter-count' ).val();
+
+ trackAndBumpMCStats( 'deleted_filter', eventArgs );
+
+ filter.find( 'input, textarea, select' ).change();
+ filter.remove();
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+ } );
+
+ // make the filters sortable
+ $( '.jetpack-search-filters-widget__filters' ).sortable( {
+ placeholder: 'jetpack-search-filters-widget__filter-placeholder',
+ axis: 'y',
+ revert: true,
+ cancel: 'input,textarea,button,select,option,.jetpack-search-filters-widget__controls a',
+ change: function() {
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+ },
+ update: function( e, ui ) {
+ $( ui.item ).find( 'input, textarea, select' ).change();
+ }
+ } )
+ .disableSelection();
+ };
+
+ // When widgets are updated, remove and re-add listeners
+ $( document ).on( 'widget-updated widget-added', function( e, widget ) {
+ widget = $( widget );
+
+ var id = widget.attr( 'id' ),
+ isJetpackSearch = ( id && ( -1 !== id.indexOf( 'jetpack-search-filters' ) ) );
+
+ if ( ! isJetpackSearch ) {
+ return;
+ }
+
+ // Intentionally not tracking widget additions and updates here as these events
+ // seem noisy in the customizer. We'll track those via PHP.
+
+ widget.off( 'change', '.filter-select' );
+ widget.off( 'click', '.jetpack-search-filters-widget__controls .delete' );
+ widget.off( 'change', '.jetpack-search-filters-widget__use-filters' );
+ widget.off( 'change', '.jetpack-search-filters-widget__search-box-enabled' );
+ widget.off( 'change', '.jetpack-search-filters-widget__sort-controls-enabled' );
+ widget.off( 'change', '.jetpack-search-filters-widget__sort-controls-enabled' );
+ widget.off( 'change', '.jetpack-search-filters-widget__post-type-selector' );
+ widget.off( 'change', '.jetpack-search-filters-widget__sort-order' );
+ widget.off( 'change', '.jetpack-search-filters-widget__taxonomy-select' );
+ widget.off( 'change', '.jetpack-search-filters-widget__date-histogram-select:first select' );
+ widget.off( 'change', '.jetpack-search-filters-widget__date-histogram-select:eq(1) select' );
+ widget.off( 'click', '.jetpack-search-filters-widget__post-types-select input[type="checkbox"]' );
+ widget.off( 'click', '.jetpack-search-filters-widget__add-filter');
+
+ setListeners( widget );
+ } );
+
+ /**
+ * This function will fire both a Tracks and MC stat.
+ *
+ * Tracks: Will be prefixed by 'jetpack_widget_search_' and use underscores.
+ * MC: Will not be prefixed, and will use dashes.
+ *
+ * Logic borrowed from `idc-notice.js`.
+ *
+ * @param eventName string
+ * @param extraProps object
+ */
+ function trackAndBumpMCStats( eventName, extraProps ) {
+ if ( 'undefined' === typeof extraProps || 'object' !== typeof extraProps ) {
+ extraProps = {};
+ }
+
+ if ( eventName && eventName.length && 'undefined' !== typeof analytics && analytics.tracks && analytics.mc ) {
+ // Format for Tracks
+ eventName = eventName.replace( /-/g, '_' );
+ eventName = eventName.indexOf( 'jetpack_widget_search_' ) !== 0 ? 'jetpack_widget_search_' + eventName : eventName;
+ analytics.tracks.recordEvent( eventName, extraProps );
+
+ // Now format for MC stats
+ eventName = eventName.replace( 'jetpack_widget_search_', '' );
+ eventName = eventName.replace( /_/g, '-' );
+ analytics.mc.bumpStat( 'jetpack-search-widget', eventName );
+ }
+ }
+} )( jQuery, jetpack_search_filter_admin );
diff --git a/plugins/jetpack/modules/widgets/search/js/search-widget.js b/plugins/jetpack/modules/widgets/search/js/search-widget.js
new file mode 100644
index 00000000..31e55731
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search/js/search-widget.js
@@ -0,0 +1,19 @@
+jQuery( document ).ready( function() {
+ var filter_list = jQuery( '.jetpack-search-filters-widget__filter-list' );
+
+ filter_list.on( 'click', 'a', function() {
+ var checkbox = jQuery( this ).siblings( 'input[type="checkbox"]' );
+ checkbox.prop( 'checked', ! checkbox.prop( 'checked' ) );
+ } );
+
+ filter_list
+ .find( 'input[type="checkbox"]' )
+ .prop( 'disabled', false )
+ .css( 'cursor', 'inherit' )
+ .on( 'click', function() {
+ var anchor = jQuery( this ).siblings( 'a' );
+ if ( anchor.length ) {
+ window.location.href = anchor.prop( 'href' );
+ }
+ } );
+} );
diff --git a/plugins/jetpack/modules/widgets/simple-payments.php b/plugins/jetpack/modules/widgets/simple-payments.php
new file mode 100644
index 00000000..4eb60bdb
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments.php
@@ -0,0 +1,544 @@
+<?php
+/**
+ * Disable direct access/execution to/of the widget code.
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+if ( ! class_exists( 'Jetpack_Simple_Payments_Widget' ) ) {
+ /**
+ * Simple Payments Button
+ *
+ * Display a Simple Payments Button as a Widget.
+ */
+ class Jetpack_Simple_Payments_Widget extends WP_Widget {
+ // https://developer.paypal.com/docs/integration/direct/rest/currency-codes/
+ private static $supported_currency_list = array(
+ 'USD' => '$',
+ 'GBP' => '&#163;',
+ 'JPY' => '&#165;',
+ 'BRL' => 'R$',
+ 'EUR' => '&#8364;',
+ 'NZD' => 'NZ$',
+ 'AUD' => 'A$',
+ 'CAD' => 'C$',
+ 'INR' => '₹',
+ 'ILS' => '₪',
+ 'RUB' => '₽',
+ 'MXN' => 'MX$',
+ 'SEK' => 'Skr',
+ 'HUF' => 'Ft',
+ 'CHF' => 'CHF',
+ 'CZK' => 'Kč',
+ 'DKK' => 'Dkr',
+ 'HKD' => 'HK$',
+ 'NOK' => 'Kr',
+ 'PHP' => '₱',
+ 'PLN' => 'PLN',
+ 'SGD' => 'S$',
+ 'TWD' => 'NT$',
+ 'THB' => '฿',
+ );
+
+ /**
+ * Constructor.
+ */
+ function __construct() {
+ parent::__construct(
+ 'jetpack_simple_payments_widget',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Simple Payments', 'jetpack' ) ),
+ array(
+ 'classname' => 'jetpack-simple-payments',
+ 'description' => __( 'Add a Simple Payments Button as a Widget.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ global $pagenow;
+ if ( is_customize_preview() || 'widgets.php' === $pagenow ) {
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_styles' ) );
+ }
+
+ $jetpack_simple_payments = Jetpack_Simple_Payments::getInstance();
+ if ( is_customize_preview() && $jetpack_simple_payments->is_enabled_jetpack_simple_payments() ) {
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
+
+ add_filter( 'customize_refresh_nonces', array( $this, 'filter_nonces' ) );
+ add_action( 'wp_ajax_customize-jetpack-simple-payments-buttons-get', array( $this, 'ajax_get_payment_buttons' ) );
+ add_action( 'wp_ajax_customize-jetpack-simple-payments-button-save', array( $this, 'ajax_save_payment_button' ) );
+ add_action( 'wp_ajax_customize-jetpack-simple-payments-button-delete', array( $this, 'ajax_delete_payment_button' ) );
+ }
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
+ }
+ }
+
+ /**
+ * Return an associative array of default values.
+ *
+ * These values are used in new widgets.
+ *
+ * @return array Default values for the widget options.
+ */
+ private function defaults() {
+ $current_user = wp_get_current_user();
+ $default_product_id = $this->get_first_product_id();
+
+ return array(
+ 'title' => '',
+ 'product_post_id' => $default_product_id,
+ 'form_action' => '',
+ 'form_product_id' => 0,
+ 'form_product_title' => '',
+ 'form_product_description' => '',
+ 'form_product_image_id' => 0,
+ 'form_product_image_src' => '',
+ 'form_product_currency' => '',
+ 'form_product_price' => '',
+ 'form_product_multiple' => '',
+ 'form_product_email' => $current_user->user_email,
+ );
+ }
+
+ /**
+ * Adds a nonce for customizing menus.
+ *
+ * @param array $nonces Array of nonces.
+ * @return array $nonces Modified array of nonces.
+ */
+ function filter_nonces( $nonces ) {
+ $nonces['customize-jetpack-simple-payments'] = wp_create_nonce( 'customize-jetpack-simple-payments' );
+ return $nonces;
+ }
+
+ function enqueue_style() {
+ wp_enqueue_style( 'jetpack-simple-payments-widget-style', plugins_url( 'simple-payments/style.css', __FILE__ ), array(), '20180518' );
+ }
+
+ function admin_enqueue_styles() {
+ wp_enqueue_style( 'jetpack-simple-payments-widget-customizer', plugins_url( 'simple-payments/customizer.css', __FILE__ ) );
+ }
+
+ function admin_enqueue_scripts() {
+ wp_enqueue_media();
+ wp_enqueue_script( 'jetpack-simple-payments-widget-customizer', plugins_url( '/simple-payments/customizer.js', __FILE__ ), array( 'jquery' ), false, true );
+ wp_localize_script(
+ 'jetpack-simple-payments-widget-customizer', 'jpSimplePaymentsStrings', array(
+ 'deleteConfirmation' => __( 'Are you sure you want to delete this item? It will be disabled and removed from all locations where it currently appears.', 'jetpack' ),
+ )
+ );
+ }
+
+ public function ajax_get_payment_buttons() {
+ if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) {
+ wp_send_json_error( 'bad_nonce', 400 );
+ }
+
+ if ( ! current_user_can( 'customize' ) ) {
+ wp_send_json_error( 'customize_not_allowed', 403 );
+ }
+
+ $post_type_object = get_post_type_object( Jetpack_Simple_Payments::$post_type_product );
+ if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) {
+ wp_send_json_error( 'insufficient_post_permissions', 403 );
+ }
+
+ $product_posts = get_posts(
+ array(
+ 'numberposts' => 100,
+ 'orderby' => 'date',
+ 'post_type' => Jetpack_Simple_Payments::$post_type_product,
+ 'post_status' => 'publish',
+ )
+ );
+
+ $formatted_products = array_map( array( $this, 'format_product_post_for_ajax_reponse' ), $product_posts );
+
+ wp_send_json_success( $formatted_products );
+ }
+
+ public function format_product_post_for_ajax_reponse( $product_post ) {
+ return array(
+ 'ID' => $product_post->ID,
+ 'post_title' => $product_post->post_title,
+ );
+ }
+
+ public function ajax_save_payment_button() {
+ if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) {
+ wp_send_json_error( 'bad_nonce', 400 );
+ }
+
+ if ( ! current_user_can( 'customize' ) ) {
+ wp_send_json_error( 'customize_not_allowed', 403 );
+ }
+
+ $post_type_object = get_post_type_object( Jetpack_Simple_Payments::$post_type_product );
+ if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) {
+ wp_send_json_error( 'insufficient_post_permissions', 403 );
+ }
+
+ if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) {
+ wp_send_json_error( 'missing_params', 400 );
+ }
+
+ $params = wp_unslash( $_POST['params'] );
+ $errors = $this->validate_ajax_params( $params );
+ if ( ! empty( $errors->errors ) ) {
+ wp_send_json_error( $errors );
+ }
+
+ $product_post_id = isset( $params['product_post_id'] ) ? intval( $params['product_post_id'] ) : 0;
+
+ $product_post = array(
+ 'ID' => $product_post_id,
+ 'post_type' => Jetpack_Simple_Payments::$post_type_product,
+ 'post_status' => 'publish',
+ 'post_title' => $params['post_title'],
+ 'post_content' => $params['post_content'],
+ '_thumbnail_id' => ! empty( $params['image_id'] ) ? $params['image_id'] : -1,
+ 'meta_input' => array(
+ 'spay_currency' => $params['currency'],
+ 'spay_price' => $params['price'],
+ 'spay_multiple' => isset( $params['multiple'] ) ? intval( $params['multiple'] ) : 0,
+ 'spay_email' => is_email( $params['email'] ),
+ ),
+ );
+
+ if ( empty( $product_post_id ) ) {
+ $product_post_id = wp_insert_post( $product_post );
+ } else {
+ $product_post_id = wp_update_post( $product_post );
+ }
+
+ if ( ! $product_post_id || is_wp_error( $product_post_id ) ) {
+ wp_send_json_error( $product_post_id );
+ }
+
+ $tracks_properties = array(
+ 'id' => $product_post_id,
+ 'currency' => $params['currency'],
+ 'price' => $params['price'],
+ );
+ if ( 0 === $product_post['ID'] ) {
+ $this->record_event( 'created', 'create', $tracks_properties );
+ } else {
+ $this->record_event( 'updated', 'update', $tracks_properties );
+ }
+
+ wp_send_json_success(
+ array(
+ 'product_post_id' => $product_post_id,
+ 'product_post_title' => $params['post_title'],
+ )
+ );
+ }
+
+ public function ajax_delete_payment_button() {
+ if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) {
+ wp_send_json_error( 'bad_nonce', 400 );
+ }
+
+ if ( ! current_user_can( 'customize' ) ) {
+ wp_send_json_error( 'customize_not_allowed', 403 );
+ }
+
+ if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) {
+ wp_send_json_error( 'missing_params', 400 );
+ }
+
+ $params = wp_unslash( $_POST['params'] );
+ $illegal_params = array_diff( array_keys( $params ), array( 'product_post_id' ) );
+ if ( ! empty( $illegal_params ) ) {
+ wp_send_json_error( 'illegal_params', 400 );
+ }
+
+ $product_id = (int) $params['product_post_id'];
+ $product_post = get_post( $product_id );
+
+ $return = array( 'status' => $product_post->post_status );
+
+ wp_delete_post( $product_id, true );
+ $status = get_post_status( $product_id );
+ if ( false === $status ) {
+ $return['status'] = 'deleted';
+ }
+
+ $this->record_event( 'deleted', 'delete', array( 'id' => $product_id ) );
+
+ wp_send_json_success( $return );
+ }
+
+ /**
+ * Returns the number of decimal places on string representing a price.
+ *
+ * @param string $number Price to check.
+ * @return number number of decimal places.
+ */
+ private function get_decimal_places( $number ) {
+ $parts = explode( '.', $number );
+ if ( count( $parts ) > 2 ) {
+ return null;
+ }
+
+ return isset( $parts[1] ) ? strlen( $parts[1] ) : 0;
+ }
+
+ public function validate_ajax_params( $params ) {
+ $errors = new WP_Error();
+
+ $illegal_params = array_diff( array_keys( $params ), array( 'product_post_id', 'post_title', 'post_content', 'image_id', 'currency', 'price', 'multiple', 'email' ) );
+ if ( ! empty( $illegal_params ) ) {
+ $errors->add( 'illegal_params', __( 'Invalid parameters.', 'jetpack' ) );
+ }
+
+ if ( empty( $params['post_title'] ) ) {
+ $errors->add( 'post_title', __( "People need to know what they're paying for! Please add a brief title.", 'jetpack' ) );
+ }
+
+ if ( empty( $params['price'] ) || ! is_numeric( $params['price'] ) || floatval( $params['price'] ) <= 0 ) {
+ $errors->add( 'price', __( 'Everything comes with a price tag these days. Please add a your product price.', 'jetpack' ) );
+ }
+
+ // Japan's Yen is the only supported currency with a zero decimal precision.
+ $precision = strtoupper( $params['currency'] ) === 'JPY' ? 0 : 2;
+ $price_decimal_places = $this->get_decimal_places( $params['price'] );
+ if ( is_null( $price_decimal_places ) || $price_decimal_places > $precision ) {
+ $errors->add( 'price', __( 'Invalid price', 'jetpack' ) );
+ }
+
+ if ( empty( $params['email'] ) || ! is_email( $params['email'] ) ) {
+ $errors->add( 'email', __( 'We want to make sure payments reach you, so please add an email address.', 'jetpack' ) );
+ }
+
+ return $errors;
+ }
+
+ function get_first_product_id() {
+ $product_posts = get_posts(
+ array(
+ 'numberposts' => 1,
+ 'orderby' => 'date',
+ 'post_type' => Jetpack_Simple_Payments::$post_type_product,
+ 'post_status' => 'publish',
+ )
+ );
+
+ return ! empty( $product_posts ) ? $product_posts[0]->ID : null;
+ }
+
+ /**
+ * Front-end display of widget.
+ *
+ * @see WP_Widget::widget()
+ *
+ * @param array $args Widget arguments.
+ * @param array $instance Saved values from database.
+ */
+ function widget( $args, $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults() );
+
+ echo $args['before_widget'];
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $instance['title'] );
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . $title . $args['after_title'];
+ }
+
+ echo '<div class="jetpack-simple-payments-content">';
+
+ if ( ! empty( $instance['form_action'] ) && in_array( $instance['form_action'], array( 'add', 'edit' ) ) && is_customize_preview() ) {
+ require( dirname( __FILE__ ) . '/simple-payments/widget.php' );
+ } else {
+ $jsp = Jetpack_Simple_Payments::getInstance();
+ $simple_payments_button = $jsp->parse_shortcode(
+ array(
+ 'id' => $instance['product_post_id'],
+ )
+ );
+
+ if ( ! is_null( $simple_payments_button ) || is_customize_preview() ) {
+ echo $simple_payments_button;
+ }
+ }
+
+ echo '</div><!--simple-payments-->';
+
+ echo $args['after_widget'];
+
+ /** This action is already documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'simple_payments' );
+ }
+
+ /**
+ * Gets the latests field value from either the old instance or the new instance.
+ *
+ * @param array $mixed Array of values for the new form instance.
+ * @param array $mixed Array of values for the old form instance.
+ * @return mixed $mixed Field value.
+ */
+ private function get_latest_field_value( $new_instance, $old_instance, $field ) {
+ return ! empty( $new_instance[ $field ] )
+ ? sanitize_text_field( $new_instance[ $field ] )
+ : $old_instance[ $field ];
+ }
+
+ /**
+ * Gets the product fields from the product post. If no post found
+ * it returns the default values.
+ *
+ * @param int Product Post ID.
+ * @return array $fields Product Fields from the Product Post.
+ */
+ private function get_product_from_post( $product_post_id ) {
+ $product_post = get_post( $product_post_id );
+ $form_product_id = $product_post_id;
+ if ( ! empty( $product_post ) ) {
+ $form_product_image_id = get_post_thumbnail_id( $product_post_id );
+
+ return array(
+ 'form_product_id' => $form_product_id,
+ 'form_product_title' => get_the_title( $product_post ),
+ 'form_product_description' => $product_post->post_content,
+ 'form_product_image_id' => $form_product_image_id,
+ 'form_product_image_src' => wp_get_attachment_image_url( $form_product_image_id, 'thumbnail' ),
+ 'form_product_currency' => get_post_meta( $product_post_id, 'spay_currency', true ),
+ 'form_product_price' => get_post_meta( $product_post_id, 'spay_price', true ),
+ 'form_product_multiple' => get_post_meta( $product_post_id, 'spay_multiple', true ) || '0',
+ 'form_product_email' => get_post_meta( $product_post_id, 'spay_email', true ),
+ );
+ }
+
+ return $this->defaults();
+ }
+
+ /**
+ * Record a Track event and bump a MC stat.
+ *
+ * @param string $stat_name
+ * @param string $event_action
+ * @param array $event_properties
+ */
+ private function record_event( $stat_name, $event_action, $event_properties = array() ) {
+ $current_user = wp_get_current_user();
+
+ // `bumps_stats_extra` only exists on .com
+ if ( function_exists( 'bump_stats_extras' ) ) {
+ require_lib( 'tracks/client' );
+ tracks_record_event( $current_user, 'simple_payments_button_' . $event_action, $event_properties );
+ /** This action is documented in modules/widgets/social-media-icons.php */
+ do_action( 'jetpack_bump_stats_extra', 'jetpack-simple_payments', $stat_name );
+ return;
+ }
+
+ jetpack_tracks_record_event( $current_user, 'jetpack_wpa_simple_payments_button_' . $event_action, $event_properties );
+ $jetpack = Jetpack::init();
+ // $jetpack->stat automatically prepends the stat group with 'jetpack-'
+ $jetpack->stat( 'simple_payments', $stat_name );
+ $jetpack->do_stats( 'server_side' );
+ }
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @see WP_Widget::update()
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ *
+ * @return array Updated safe values to be saved.
+ */
+ function update( $new_instance, $old_instance ) {
+ $defaults = $this->defaults();
+ //do not overrite `product_post_id` for `$new_instance` with the defaults
+ $new_instance = wp_parse_args( $new_instance, array_diff_key( $defaults, array( 'product_post_id' => 0 ) ) );
+ $old_instance = wp_parse_args( $old_instance, $defaults );
+
+ $required_widget_props = array(
+ 'title' => $this->get_latest_field_value( $new_instance, $old_instance, 'title' ),
+ 'product_post_id' => $this->get_latest_field_value( $new_instance, $old_instance, 'product_post_id' ),
+ 'form_action' => $this->get_latest_field_value( $new_instance, $old_instance, 'form_action' ),
+ );
+
+ if ( strcmp( $new_instance['form_action'], $old_instance['form_action'] ) !== 0 ) {
+ if ( $new_instance['form_action'] == 'edit' ) {
+ return array_merge( $this->get_product_from_post( (int) $old_instance['product_post_id'] ), $required_widget_props );
+ }
+
+ if ( $new_instance['form_action'] == 'clear' ) {
+ return array_merge( $this->defaults(), $required_widget_props );
+ }
+ }
+
+ $form_product_image_id = (int) $new_instance['form_product_image_id'];
+
+ $form_product_email = ! empty( $new_instance['form_product_email'] )
+ ? sanitize_text_field( $new_instance['form_product_email'] )
+ : $defaults['form_product_email'];
+
+ return array_merge(
+ $required_widget_props, array(
+ 'form_product_id' => (int) $new_instance['form_product_id'],
+ 'form_product_title' => sanitize_text_field( $new_instance['form_product_title'] ),
+ 'form_product_description' => sanitize_text_field( $new_instance['form_product_description'] ),
+ 'form_product_image_id' => $form_product_image_id,
+ 'form_product_image_src' => wp_get_attachment_image_url( $form_product_image_id, 'thumbnail' ),
+ 'form_product_currency' => sanitize_text_field( $new_instance['form_product_currency'] ),
+ 'form_product_price' => sanitize_text_field( $new_instance['form_product_price'] ),
+ 'form_product_multiple' => sanitize_text_field( $new_instance['form_product_multiple'] ),
+ 'form_product_email' => $form_product_email,
+ )
+ );
+ }
+
+ /**
+ * Back-end widget form.
+ *
+ * @see WP_Widget::form()
+ *
+ * @param array $instance Previously saved values from database.
+ */
+ function form( $instance ) {
+ $jetpack_simple_payments = Jetpack_Simple_Payments::getInstance();
+ if ( ! method_exists( $jetpack_simple_payments, 'is_enabled_jetpack_simple_payments' ) ) {
+ return;
+ }
+ if ( ! $jetpack_simple_payments->is_enabled_jetpack_simple_payments() ) {
+ require dirname( __FILE__ ) . '/simple-payments/admin-warning.php';
+ return;
+ }
+
+ $instance = wp_parse_args( $instance, $this->defaults() );
+
+ $product_posts = get_posts(
+ array(
+ 'numberposts' => 100,
+ 'orderby' => 'date',
+ 'post_type' => Jetpack_Simple_Payments::$post_type_product,
+ 'post_status' => 'publish',
+ )
+ );
+
+ require dirname( __FILE__ ) . '/simple-payments/form.php';
+ }
+ }
+
+ // Register Jetpack_Simple_Payments_Widget widget.
+ function register_widget_jetpack_simple_payments() {
+ if ( ! class_exists( 'Jetpack_Simple_Payments' ) ) {
+ return;
+ }
+
+ $jetpack_simple_payments = Jetpack_Simple_Payments::getInstance();
+ if ( ! $jetpack_simple_payments->is_enabled_jetpack_simple_payments() ) {
+ return;
+ }
+
+ register_widget( 'Jetpack_Simple_Payments_Widget' );
+ }
+ add_action( 'widgets_init', 'register_widget_jetpack_simple_payments' );
+}
diff --git a/plugins/jetpack/modules/widgets/simple-payments/admin-warning.php b/plugins/jetpack/modules/widgets/simple-payments/admin-warning.php
new file mode 100644
index 00000000..f66e4413
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/admin-warning.php
@@ -0,0 +1,16 @@
+<div class='jetpack-simple-payments-disabled-error'>
+ <p>
+ <?php
+ $support_url = ( defined( 'IS_WPCOM' ) && IS_WPCOM )
+ ? 'https://support.wordpress.com/simple-payments/'
+ : 'https://jetpack.com/support/simple-payment-button/';
+ printf(
+ wp_kses(
+ __( 'Your plan doesn\'t include Simple Payments. <a href="%s" rel="noopener noreferrer" target="_blank">Learn more and upgrade</a>.', 'jetpack' ),
+ array( 'a' => array( 'href' => array(), 'rel' => array(), 'target' => array() ) )
+ ),
+ esc_url( $support_url )
+ );
+ ?>
+ </p>
+</div>
diff --git a/plugins/jetpack/modules/widgets/simple-payments/customizer.css b/plugins/jetpack/modules/widgets/simple-payments/customizer.css
new file mode 100644
index 00000000..12278a60
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/customizer.css
@@ -0,0 +1,80 @@
+.widget-content .jetpack-simple-payments,
+.widget-content .jetpack-simple-payments-form {
+ clear: both;
+}
+
+.widget-content .jetpack-simple-payments-disabled-error {
+ background: #fff;
+ border-left: 4px solid #dc3232;
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
+ margin: 5px 0 15px;
+ padding: 1px 12px;
+}
+
+.widget-content .jetpack-simple-payments-form .invalid {
+ border: 1px solid #dc3232;
+}
+
+.widget-content .jetpack-simple-payments-form .cost label {
+ display: block;
+}
+
+.widget-content .jetpack-simple-payments-image-fieldset {
+ position: relative;
+ width: 100%;
+}
+
+.widget-content .jetpack-simple-payments-image-fieldset .placeholder {
+ border: 1px dashed #b4b9be;
+ box-sizing: border-box;
+ cursor: pointer;
+ line-height: 20px;
+ padding: 9px 0;
+ position: relative;
+ text-align: center;
+ width: 100%;
+ margin: 4px 0 1em;
+}
+
+.widget-content .jetpack-simple-payments-image {
+ max-width: 100%;
+ margin-top: 4px;
+ position: relative;
+ text-align: center;
+}
+
+.widget-content .jetpack-simple-payments-image img {
+ max-width: 100%;
+ box-sizing: border-box;
+ border: 1px dashed #b4b9be;
+ padding: 4px;
+ height: auto;
+ cursor: pointer;
+}
+
+.widget-content .jetpack-simple-payments-image img:hover {
+ border-style: solid;
+}
+
+.widget-content .jetpack-simple-payments-form .field-currency {
+ display: inline-block;
+ vertical-align: top;
+ width: 40%;
+}
+
+.widget-content .jetpack-simple-payments-form .field-price {
+ display: inline-block;
+ line-height: 20px;
+ width: 58%;
+}
+
+.widget-content .jetpack-simple-payments-form .alignleft button,
+.widget-content .jetpack-simple-payments-form .alignright span {
+ display: inline-block;
+ margin-top: 5px;
+}
+
+.widget-content .button-link:disabled,
+.widget-content .button-link:hover[disabled] {
+ color: #a0a5aa;
+}
diff --git a/plugins/jetpack/modules/widgets/simple-payments/customizer.js b/plugins/jetpack/modules/widgets/simple-payments/customizer.js
new file mode 100644
index 00000000..c529db34
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/customizer.js
@@ -0,0 +1,455 @@
+/* global jQuery, jpSimplePaymentsStrings, confirm, _ */
+/* eslint no-var: 0, quote-props: 0 */
+
+( function( api, wp, $ ) {
+ var $document = $( document );
+
+ $document.ready( function() {
+ $document.on( 'widget-added', function( event, widgetContainer ) {
+ if ( widgetContainer.is( '[id*="jetpack_simple_payments_widget"]' ) ) {
+ initWidget( widgetContainer );
+ }
+ } );
+
+ $document.on( 'widget-synced widget-updated', function( event, widgetContainer ) {
+ //this fires for all widgets, this prevent errors for non SP widgets
+ if ( ! widgetContainer.is( '[id*="jetpack_simple_payments_widget"]' ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ syncProductLists();
+
+ var widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' );
+
+ enableFormActions( widgetForm );
+
+ updateProductImage( widgetForm );
+ } );
+ } );
+
+ function initWidget( widgetContainer ) {
+ var widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' );
+
+ //Add New Button
+ widgetForm
+ .find( '.jetpack-simple-payments-add-product' )
+ .on( 'click', showAddNewForm( widgetForm ) );
+ //Edit Button
+ widgetForm
+ .find( '.jetpack-simple-payments-edit-product' )
+ .on( 'click', showEditForm( widgetForm ) );
+ //Select an Image
+ widgetForm
+ .find(
+ '.jetpack-simple-payments-image-fieldset .placeholder, .jetpack-simple-payments-image > img'
+ )
+ .on( 'click', selectImage( widgetForm ) );
+ //Remove Image Button
+ widgetForm
+ .find( '.jetpack-simple-payments-remove-image' )
+ .on( 'click', removeImage( widgetForm ) );
+ //Save Product button
+ widgetForm
+ .find( '.jetpack-simple-payments-save-product' )
+ .on( 'click', saveChanges( widgetForm ) );
+ //Cancel Button
+ widgetForm
+ .find( '.jetpack-simple-payments-cancel-form' )
+ .on( 'click', clearForm( widgetForm ) );
+ //Delete Selected Product
+ widgetForm
+ .find( '.jetpack-simple-payments-delete-product' )
+ .on( 'click', deleteProduct( widgetForm ) );
+ //Input, Select and Checkbox change
+ widgetForm.find( 'select, input, textarea, checkbox' ).on(
+ 'change input propertychange',
+ _.debounce( function() {
+ disableFormActions( widgetForm );
+ }, 250 )
+ );
+ }
+
+ function syncProductLists() {
+ var request = wp.ajax.post( 'customize-jetpack-simple-payments-buttons-get', {
+ 'customize-jetpack-simple-payments-nonce':
+ api.settings.nonce[ 'customize-jetpack-simple-payments' ],
+ customize_changeset_uuid: api.settings.changeset.uuid,
+ } );
+
+ request.done( function( data ) {
+ var selectedProduct = 0;
+
+ $( document )
+ .find( 'select.jetpack-simple-payments-products' )
+ .each( function( index, select ) {
+ var $select = $( select );
+ selectedProduct = $select.val();
+
+ $select.find( 'option' ).remove();
+ $select.append(
+ $.map( data, function( product ) {
+ return $( '<option>', { value: product.ID, text: product.post_title } );
+ } )
+ );
+ $select.val( selectedProduct );
+ } );
+ } );
+ }
+
+ function showForm( widgetForm ) {
+ //reset validations
+ widgetForm.find( '.invalid' ).removeClass( 'invalid' );
+ //disable widget title and product selector
+ widgetForm
+ .find( '.jetpack-simple-payments-widget-title' )
+ .add( '.jetpack-simple-payments-products' )
+ //disable add and edit buttons
+ .add( '.jetpack-simple-payments-add-product' )
+ .add( '.jetpack-simple-payments-edit-product' )
+ //disable save, delete and cancel until the widget update event is fired
+ .add( '.jetpack-simple-payments-save-product' )
+ .add( '.jetpack-simple-payments-cancel-form' )
+ .add( '.jetpack-simple-payments-delete-product' )
+ .attr( 'disabled', 'disabled' );
+ //show form
+ widgetForm.find( '.jetpack-simple-payments-form' ).show();
+ }
+
+ function hideForm( widgetForm ) {
+ //enable widget title and product selector
+ widgetForm
+ .find( '.jetpack-simple-payments-widget-title' )
+ .add( '.jetpack-simple-payments-products' )
+ .removeAttr( 'disabled' );
+ //hide the form
+ widgetForm.find( '.jetpack-simple-payments-form' ).hide();
+ }
+
+ function changeFormAction( widgetForm, action ) {
+ widgetForm
+ .find( '.jetpack-simple-payments-form-action' )
+ .val( action )
+ .change();
+ }
+
+ function showAddNewForm( widgetForm ) {
+ return function( event ) {
+ event.preventDefault();
+
+ showForm( widgetForm );
+ changeFormAction( widgetForm, 'add' );
+ };
+ }
+
+ function showEditForm( widgetForm ) {
+ return function( event ) {
+ event.preventDefault();
+
+ showForm( widgetForm );
+ changeFormAction( widgetForm, 'edit' );
+ };
+ }
+
+ function clearForm( widgetForm ) {
+ return function( event ) {
+ event.preventDefault();
+
+ hideForm( widgetForm );
+ widgetForm
+ .find( '.jetpack-simple-payments-add-product, .jetpack-simple-payments-edit-product' )
+ .attr( 'disabled', 'disabled' );
+ changeFormAction( widgetForm, 'clear' );
+ };
+ }
+
+ function enableFormActions( widgetForm ) {
+ var isFormVisible = widgetForm.find( '.jetpack-simple-payments-form' ).is( ':visible' );
+ var isProductSelectVisible = widgetForm
+ .find( '.jetpack-simple-payments-products' )
+ .is( ':visible' ); //areProductsVisible ?
+ var isEdit = widgetForm.find( '.jetpack-simple-payments-form-action' ).val() === 'edit';
+
+ if ( isFormVisible ) {
+ widgetForm
+ .find( '.jetpack-simple-payments-save-product' )
+ .add( '.jetpack-simple-payments-cancel-form' )
+ .removeAttr( 'disabled' );
+ } else {
+ widgetForm.find( '.jetpack-simple-payments-add-product' ).removeAttr( 'disabled' );
+ }
+
+ if ( isFormVisible && isEdit ) {
+ widgetForm.find( '.jetpack-simple-payments-delete-product' ).removeAttr( 'disabled' );
+ }
+
+ if ( isProductSelectVisible && ! isFormVisible ) {
+ widgetForm.find( '.jetpack-simple-payments-edit-product' ).removeAttr( 'disabled' );
+ }
+ }
+
+ function disableFormActions( widgetForm ) {
+ widgetForm
+ .find( '.jetpack-simple-payments-add-product' )
+ .add( '.jetpack-simple-payments-edit-product' )
+ .add( '.jetpack-simple-payments-save-product' )
+ .add( '.jetpack-simple-payments-cancel-form' )
+ .add( '.jetpack-simple-payments-delete-product' )
+ .attr( 'disabled', 'disabled' );
+ }
+
+ function selectImage( widgetForm ) {
+ return function( event ) {
+ event.preventDefault();
+
+ var imageContainer = widgetForm.find( '.jetpack-simple-payments-image' );
+
+ var mediaFrame = new wp.media.view.MediaFrame.Select( {
+ title: 'Choose Product Image',
+ multiple: false,
+ library: { type: 'image' },
+ button: { text: 'Choose Image' },
+ } );
+
+ mediaFrame.on( 'select', function() {
+ var selection = mediaFrame
+ .state()
+ .get( 'selection' )
+ .first()
+ .toJSON();
+ //hide placeholder
+ widgetForm.find( '.jetpack-simple-payments-image-fieldset .placeholder' ).hide();
+
+ //load image from media library
+ imageContainer
+ .find( 'img' )
+ .attr( 'src', selection.url )
+ .show();
+
+ //show image and remove button
+ widgetForm.find( '.jetpack-simple-payments-image' ).show();
+
+ //set hidden field for the selective refresh
+ widgetForm
+ .find( '.jetpack-simple-payments-form-image-id' )
+ .val( selection.id )
+ .change();
+ } );
+
+ mediaFrame.open();
+ };
+ }
+
+ function removeImage( widgetForm ) {
+ return function( event ) {
+ event.preventDefault();
+
+ //show placeholder
+ widgetForm.find( '.jetpack-simple-payments-image-fieldset .placeholder' ).show();
+
+ //hide image and remove button
+ widgetForm.find( '.jetpack-simple-payments-image' ).hide();
+
+ //set hidden field for the selective refresh
+ widgetForm
+ .find( '.jetpack-simple-payments-form-image-id' )
+ .val( '' )
+ .change();
+ };
+ }
+
+ function updateProductImage( widgetForm ) {
+ var newImageId = parseInt(
+ widgetForm.find( '.jetpack-simple-payments-form-image-id' ).val(),
+ 10
+ );
+ var newImageSrc = widgetForm.find( '.jetpack-simple-payments-form-image-src' ).val();
+
+ var placeholder = widgetForm.find( '.jetpack-simple-payments-image-fieldset .placeholder' );
+ var image = widgetForm.find( '.jetpack-simple-payments-image > img' );
+ var imageControls = widgetForm.find( '.jetpack-simple-payments-image' );
+
+ if ( newImageId && newImageSrc ) {
+ image.attr( 'src', newImageSrc );
+ placeholder.hide();
+ imageControls.show();
+ } else {
+ placeholder.show();
+ image.removeAttr( 'src' );
+ imageControls.hide();
+ }
+ }
+
+ function decimalPlaces( number ) {
+ var parts = number.split( '.' );
+ if ( parts.length > 2 ) {
+ return null;
+ }
+
+ return parts[ 1 ] ? parts[ 1 ].length : 0;
+ }
+
+ function isFormValid( widgetForm ) {
+ widgetForm.find( '.invalid' ).removeClass( 'invalid' );
+
+ var errors = false;
+
+ var postTitle = widgetForm.find( '.jetpack-simple-payments-form-product-title' ).val();
+ if ( ! postTitle ) {
+ widgetForm.find( '.jetpack-simple-payments-form-product-title' ).addClass( 'invalid' );
+ errors = true;
+ }
+
+ var productPrice = widgetForm.find( '.jetpack-simple-payments-form-product-price' ).val();
+ if ( ! productPrice || isNaN( productPrice ) || parseFloat( productPrice ) <= 0 ) {
+ widgetForm.find( '.jetpack-simple-payments-form-product-price' ).addClass( 'invalid' );
+ errors = true;
+ }
+
+ // Japan's Yen is the only supported currency with a zero decimal precision.
+ var precision =
+ widgetForm.find( '.jetpack-simple-payments-form-product-currency' ).val() === 'JPY' ? 0 : 2;
+ var priceDecimalPlaces = decimalPlaces( productPrice );
+ if ( priceDecimalPlaces === null || priceDecimalPlaces > precision ) {
+ widgetForm.find( '.jetpack-simple-payments-form-product-price' ).addClass( 'invalid' );
+ errors = true;
+ }
+
+ var productEmail = widgetForm.find( '.jetpack-simple-payments-form-product-email' ).val();
+ var isProductEmailValid = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(
+ productEmail
+ );
+ if ( ! productEmail || ! isProductEmailValid ) {
+ widgetForm.find( '.jetpack-simple-payments-form-product-email' ).addClass( 'invalid' );
+ errors = true;
+ }
+
+ return ! errors;
+ }
+
+ function saveChanges( widgetForm ) {
+ return function( event ) {
+ event.preventDefault();
+ var productPostId = widgetForm.find( '.jetpack-simple-payments-form-product-id' ).val();
+
+ if ( ! isFormValid( widgetForm ) ) {
+ return;
+ }
+
+ disableFormActions( widgetForm );
+
+ widgetForm.find( '.spinner' ).show();
+
+ var request = wp.ajax.post( 'customize-jetpack-simple-payments-button-save', {
+ 'customize-jetpack-simple-payments-nonce':
+ api.settings.nonce[ 'customize-jetpack-simple-payments' ],
+ customize_changeset_uuid: api.settings.changeset.uuid,
+ params: {
+ product_post_id: productPostId,
+ post_title: widgetForm.find( '.jetpack-simple-payments-form-product-title' ).val(),
+ post_content: widgetForm
+ .find( '.jetpack-simple-payments-form-product-description' )
+ .val(),
+ image_id: widgetForm.find( '.jetpack-simple-payments-form-image-id' ).val(),
+ currency: widgetForm.find( '.jetpack-simple-payments-form-product-currency' ).val(),
+ price: widgetForm.find( '.jetpack-simple-payments-form-product-price' ).val(),
+ multiple: widgetForm
+ .find( '.jetpack-simple-payments-form-product-multiple' )
+ .is( ':checked' )
+ ? 1
+ : 0,
+ email: widgetForm.find( '.jetpack-simple-payments-form-product-email' ).val(),
+ },
+ } );
+
+ request.done( function( data ) {
+ var select = widgetForm.find( 'select.jetpack-simple-payments-products' );
+ var productOption = select.find( 'option[value="' + productPostId + '"]' );
+
+ if ( productOption.length > 0 ) {
+ productOption.text( data.product_post_title );
+ } else {
+ select.append(
+ $( '<option>', {
+ value: data.product_post_id,
+ text: data.product_post_title,
+ } )
+ );
+ select.val( data.product_post_id ).change();
+ }
+
+ widgetForm.find( '.jetpack-simple-payments-products-fieldset' ).show();
+ widgetForm.find( '.jetpack-simple-payments-products-warning' ).hide();
+
+ changeFormAction( widgetForm, 'clear' );
+ hideForm( widgetForm );
+ } );
+
+ request.fail( function( data ) {
+ var validCodes = {
+ post_title: 'product-title',
+ price: 'product-price',
+ email: 'product-email',
+ };
+
+ data.forEach( function( item ) {
+ if ( validCodes.hasOwnProperty( item.code ) ) {
+ widgetForm
+ .find( '.jetpack-simple-payments-form-' + validCodes[ item.code ] )
+ .addClass( 'invalid' );
+ }
+ } );
+
+ enableFormActions( widgetForm );
+ } );
+ };
+ }
+
+ function deleteProduct( widgetForm ) {
+ return function( event ) {
+ event.preventDefault();
+
+ if ( ! confirm( jpSimplePaymentsStrings.deleteConfirmation ) ) {
+ return;
+ }
+
+ var formProductId = parseInt(
+ widgetForm.find( '.jetpack-simple-payments-form-product-id' ).val(),
+ 10
+ );
+ if ( ! formProductId ) {
+ return;
+ }
+
+ disableFormActions( widgetForm );
+
+ widgetForm.find( '.spinner' ).show();
+
+ var request = wp.ajax.post( 'customize-jetpack-simple-payments-button-delete', {
+ 'customize-jetpack-simple-payments-nonce':
+ api.settings.nonce[ 'customize-jetpack-simple-payments' ],
+ customize_changeset_uuid: api.settings.changeset.uuid,
+ params: {
+ product_post_id: formProductId,
+ },
+ } );
+
+ request.done( function() {
+ var productList = widgetForm.find( 'select.jetpack-simple-payments-products' )[ 0 ];
+ productList.remove( productList.selectedIndex );
+ productList.dispatchEvent( new Event( 'change' ) );
+
+ if ( $( productList ).has( 'option' ).length === 0 ) {
+ //hide products select and label
+ widgetForm.find( '.jetpack-simple-payments-products-fieldset' ).hide();
+ //show empty products list warning
+ widgetForm.find( '.jetpack-simple-payments-products-warning' ).show();
+ }
+
+ changeFormAction( widgetForm, 'clear' );
+ hideForm( widgetForm );
+ } );
+ };
+ }
+} )( wp.customize, wp, jQuery );
diff --git a/plugins/jetpack/modules/widgets/simple-payments/form.php b/plugins/jetpack/modules/widgets/simple-payments/form.php
new file mode 100644
index 00000000..7732be5d
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/form.php
@@ -0,0 +1,205 @@
+<?php
+/**
+ * Display the Simple Payments Form.
+ *
+ * @package Jetpack
+ */
+
+?>
+<p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
+ <?php esc_html_e( 'Widget Title', 'jetpack' ); ?>
+ </label>
+ <input
+ type="text"
+ class="widefat jetpack-simple-payments-widget-title"
+ id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
+ value="<?php echo esc_attr( $instance['title'] ); ?>" />
+</p>
+<p class="jetpack-simple-payments-products-fieldset" <?php if ( empty( $product_posts ) ) { echo 'style="display:none;"'; } ?>>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'product_post_id' ) ); ?>">
+ <?php esc_html_e( 'Select a Simple Payments Button:', 'jetpack' ); ?>
+ </label>
+ <select
+ class="widefat jetpack-simple-payments-products"
+ id="<?php echo esc_attr( $this->get_field_id( 'product_post_id' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'product_post_id' ) ); ?>">
+ <?php foreach ( $product_posts as $product_post ) { ?>
+ <option value="<?php echo esc_attr( $product_post->ID ); ?>" <?php selected( (int) $instance['product_post_id'], $product_post->ID ); ?>>
+ <?php echo esc_attr( get_the_title( $product_post ) ); ?>
+ </option>
+ <?php } ?>
+ </select>
+</p>
+<?php if ( is_customize_preview() ) { ?>
+<p class="jetpack-simple-payments-products-warning" <?php if ( ! empty( $product_posts ) ) { echo 'style="display:none;"'; } ?>>
+ <?php esc_html_e( "Looks like you don't have any products. You can create one using the Add New button below.", 'jetpack' ); ?>
+</p>
+<p>
+ <div class="alignleft">
+ <button class="button jetpack-simple-payments-edit-product" <?php disabled( empty( $product_posts ), true ); ?>>
+ <?php esc_html_e( 'Edit Selected', 'jetpack' ); ?>
+ </button>
+ </div>
+ <div class="alignright">
+ <button class="button jetpack-simple-payments-add-product"><?php esc_html_e( 'Add New', 'jetpack' ); ?></button>
+ </div>
+ <br class="clear">
+</p>
+<hr />
+<div class="jetpack-simple-payments-form" style="display: none;">
+ <input
+ type="hidden"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_action' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_action' ) ); ?>"
+ value="<?php echo esc_attr( $instance['form_action'] ); ?>"
+ class="jetpack-simple-payments-form-action" />
+ <input
+ type="hidden"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_id' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_id' ) ); ?>"
+ value="<?php echo esc_attr( $instance['form_product_id'] ); ?>"
+ class="jetpack-simple-payments-form-product-id" />
+ <input
+ type="hidden"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_image_id' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_image_id' ) ); ?>"
+ value="<?php echo esc_attr( $instance['form_product_image_id'] ); ?>"
+ class="jetpack-simple-payments-form-image-id" />
+ <input
+ type="hidden"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_image_src' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_image_src' ) ); ?>"
+ value="<?php echo esc_attr( $instance['form_product_image_src'] ); ?>"
+ class="jetpack-simple-payments-form-image-src" />
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_title' ) ); ?>">
+ <?php esc_html_e( 'What is this payment for?', 'jetpack' ); ?>
+ </label>
+ <input
+ type="text"
+ class="widefat field-title jetpack-simple-payments-form-product-title"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_title' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_title' ) ); ?>"
+ value="<?php echo esc_attr( $instance['form_product_title'] ); ?>" />
+ <br />
+ <small>
+ <?php esc_html_e( 'For example: event tickets, charitable donations, training courses, coaching fees, etc.', 'jetpack' ); ?>
+ </small>
+ </p>
+ <div class="jetpack-simple-payments-image-fieldset">
+ <label><?php esc_html_e( 'Product image', 'jetpack' ); ?></label>
+ <div class="placeholder" <?php if ( ! empty( $instance['form_product_image_id'] ) ) echo 'style="display:none;"'; ?>>
+ <?php esc_html_e( 'Select an image', 'jetpack' ); ?>
+ </div>
+ <div class="jetpack-simple-payments-image" <?php if ( empty( $instance['form_product_image_id'] ) ) echo 'style="display:none;"'; ?>>
+ <img src="<?php echo esc_url( $instance['form_product_image_src'] ); ?>" />
+ <button class="button jetpack-simple-payments-remove-image"><?php esc_html_e( 'Remove image', 'jetpack' ); ?></button>
+ </div>
+ </div>
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_description' ) ); ?>">
+ <?php esc_html_e( 'Description', 'jetpack' ); ?>
+ </label>
+ <textarea
+ class="field-description widefat jetpack-simple-payments-form-product-description"
+ rows=5
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_description' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_description' ) ); ?>"><?php echo esc_textarea( $instance['form_product_description'] ); ?></textarea>
+ </p>
+ <p class="cost">
+ <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_price' ) ); ?>">
+ <?php esc_html_e( 'Price', 'jetpack' ); ?>
+ </label>
+ <select
+ class="field-currency widefat jetpack-simple-payments-form-product-currency"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_currency' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_currency' ) ); ?>">
+ <?php foreach ( Jetpack_Simple_Payments_Widget::$supported_currency_list as $code => $currency ) { ?>
+ <option value="<?php echo esc_attr( $code ); ?>"<?php selected( $instance['form_product_currency'], $code ); ?>>
+ <?php echo esc_html( "$code $currency" ); ?>
+ </option>
+ <?php } ?>
+ </select>
+ <input
+ type="text"
+ class="field-price widefat jetpack-simple-payments-form-product-price"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_price' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_price' ) ); ?>"
+ value="<?php echo esc_attr( $instance['form_product_price'] ); ?>"
+ placeholder="1.00" />
+ </p>
+ <p>
+ <input
+ class="field-multiple jetpack-simple-payments-form-product-multiple"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_multiple' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_multiple' ) ); ?>"
+ type="checkbox"
+ value="1"
+ <?php checked( $instance['form_product_multiple'], '1' ); ?> />
+ <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_multiple' ) ); ?>">
+ <?php esc_html_e( 'Allow people to buy more than one item at a time.', 'jetpack' ); ?>
+ </label>
+ </p>
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_email' ) ); ?>">
+ <?php esc_html_e( 'Email', 'jetpack' ); ?>
+ </label>
+ <input
+ class="field-email widefat jetpack-simple-payments-form-product-email"
+ id="<?php echo esc_attr( $this->get_field_id( 'form_product_email' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'form_product_email' ) ); ?>"
+ type="email"
+ value="<?php echo esc_attr( $instance['form_product_email'] ); ?>" />
+ <small>
+ <?php
+ printf(
+ wp_kses(
+ /* Translators: placeholders are a link to Paypal website and a target attribute. */
+ __( 'This is where PayPal will send your money. To claim a payment, you\'ll need a <a href="%1$s" %2$s>PayPal account</a> connected to a bank account.', 'jetpack' ),
+ array(
+ 'a' => array(
+ 'href' => array(),
+ 'target' => array(),
+ ),
+ )
+ ),
+ 'https://paypal.com',
+ 'target="_blank"'
+ );
+ ?>
+ </small>
+ </p>
+ <p>
+ <div class="alignleft">
+ <button type="button" class="button-link button-link-delete jetpack-simple-payments-delete-product">
+ <?php esc_html_e( 'Delete Product', 'jetpack' ); ?>
+ </button>
+ </div>
+ <div class="alignright">
+ <button name="<?php echo esc_attr( $this->get_field_name( 'save' ) ); ?>" class="button jetpack-simple-payments-save-product"><?php esc_html_e( 'Save', 'jetpack' ); ?></button>
+ <span> | <button type="button" class="button-link jetpack-simple-payments-cancel-form"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></button></span>
+ </div>
+ <br class="clear">
+ </p>
+ <hr />
+</div>
+<?php } else { ?>
+<p class="jetpack-simple-payments-products-warning">
+ <?php
+ printf(
+ wp_kses(
+ /* Translators: placeholder is a link to the customizer. */
+ __( 'This widget adds a payment button of your choice to your sidebar. To create or edit the payment buttons themselves, <a href="%s">use the Customizer</a>.', 'jetpack' ),
+ array(
+ 'a' => array(
+ 'href' => array(),
+ ),
+ )
+ ),
+ esc_url( add_query_arg( array( 'autofocus[panel]' => 'widgets' ), admin_url( 'customize.php' ) ) )
+ );
+ ?>
+</p>
+<?php } ?>
diff --git a/plugins/jetpack/modules/widgets/simple-payments/style.css b/plugins/jetpack/modules/widgets/simple-payments/style.css
new file mode 100644
index 00000000..3a701e01
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/style.css
@@ -0,0 +1,8 @@
+@media screen and (min-width: 400px) {
+ .widget.jetpack-simple-payments .jetpack-simple-payments-product {
+ flex-direction: column;
+ }
+ .widget.jetpack-simple-payments .jetpack-simple-payments-details {
+ padding-left: 0;
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/simple-payments/widget.php b/plugins/jetpack/modules/widgets/simple-payments/widget.php
new file mode 100644
index 00000000..001635ba
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/widget.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Display the Simple Payments Widget.
+ *
+ * @package Jetpack
+ */
+
+?>
+<div class='jetpack-simple-payments-wrapper'>
+ <div class='jetpack-simple-payments-product'>
+ <div class='jetpack-simple-payments-product-image' <?php if ( empty( $instance['form_product_image_id'] ) ) echo 'style="display:none;"'; ?>>
+ <div class='jetpack-simple-payments-image'>
+ <?php echo wp_get_attachment_image( $instance['form_product_image_id'], 'full' ); ?>
+ </div>
+ </div>
+ <div class='jetpack-simple-payments-details'>
+ <div class='jetpack-simple-payments-title'><p><?php echo esc_html( $instance['form_product_title'] ); ?></p></div>
+ <div class='jetpack-simple-payments-description'><p><?php echo esc_html( $instance['form_product_description'] ); ?></p></div>
+ <div class='jetpack-simple-payments-price'><p><?php echo esc_html( $instance['form_product_price'] ); ?> <?php echo esc_html( $instance['form_product_currency'] ); ?></p></div>
+ <div class='jetpack-simple-payments-purchase-box'>
+ <?php if ( $instance['form_product_multiple'] ) { ?>
+ <div class='jetpack-simple-payments-items'>
+ <input
+ type='number'
+ class='jetpack-simple-payments-items-number'
+ value='1'
+ min='1' />
+ </div>
+ <?php } ?>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/plugins/jetpack/modules/widgets/social-icons.php b/plugins/jetpack/modules/widgets/social-icons.php
new file mode 100644
index 00000000..7afbd27d
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/social-icons.php
@@ -0,0 +1,680 @@
+<?php
+class Jetpack_Widget_Social_Icons extends WP_Widget {
+ /**
+ * @var array Default widget options.
+ */
+ protected $defaults;
+
+ /**
+ * Widget constructor.
+ */
+ public function __construct() {
+ global $pagenow;
+
+ $widget_ops = array(
+ 'classname' => 'jetpack_widget_social_icons',
+ 'description' => __( 'Add social-media icons to your site.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ );
+
+ parent::__construct(
+ 'jetpack_widget_social_icons',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Social Icons', 'jetpack' ) ),
+ $widget_ops
+ );
+
+ $this->defaults = array(
+ 'title' => __( 'Follow Us', 'jetpack' ),
+ 'icon-size' => 'medium',
+ 'new-tab' => false,
+ 'icons' => array(
+ array(
+ 'url' => '',
+ ),
+ ),
+ );
+
+ // Enqueue admin scrips and styles, only in the customizer or the old widgets page.
+ if ( is_customize_preview() || 'widgets.php' === $pagenow ) {
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
+ add_action( 'admin_print_footer_scripts', array( $this, 'render_admin_js' ) );
+ }
+
+ // Enqueue scripts and styles for the display of the widget, on the frontend or in the customizer.
+ if ( is_active_widget( false, $this->id, $this->id_base, true ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_icon_scripts' ) );
+ add_action( 'wp_footer', array( $this, 'include_svg_icons' ), 9999 );
+ }
+ }
+
+ /**
+ * Script & styles for admin widget form.
+ */
+ public function enqueue_admin_scripts() {
+ wp_enqueue_script( 'jetpack-widget-social-icons-script', plugins_url( 'social-icons/social-icons-admin.js', __FILE__ ), array( 'jquery-ui-sortable' ), '20170506' );
+ wp_enqueue_style( 'jetpack-widget-social-icons-admin', plugins_url( 'social-icons/social-icons-admin.css', __FILE__ ), array(), '20170506' );
+ }
+
+ /**
+ * Styles for front-end widget.
+ */
+ public function enqueue_icon_scripts() {
+ wp_enqueue_style( 'jetpack-widget-social-icons-styles', plugins_url( 'social-icons/social-icons.css', __FILE__ ), array(), '20170506' );
+ }
+
+ /**
+ * JavaScript for admin widget form.
+ */
+ public function render_admin_js() {
+ ?>
+ <script type="text/html" id="tmpl-jetpack-widget-social-icons-template">
+ <?php self::render_icons_template(); ?>
+ </script>
+ <?php
+ }
+
+ /**
+ * Add SVG definitions to the footer.
+ */
+ public function include_svg_icons() {
+ // Define SVG sprite file in Jetpack
+ $svg_icons = dirname( dirname( __FILE__ ) ) . '/theme-tools/social-menu/social-menu.svg';
+
+ // Define SVG sprite file in WPCOM
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $svg_icons = dirname( dirname( __FILE__ ) ) . '/social-menu/social-menu.svg';
+ }
+
+ // If it exists, include it.
+ if ( is_file( $svg_icons ) ) {
+ require_once( $svg_icons );
+ }
+ }
+
+ /**
+ * Front-end display of widget.
+ *
+ * @see WP_Widget::widget()
+ *
+ * @param array $args Widget arguments.
+ * @param array $instance Saved values from database.
+ */
+ public function widget( $args, $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults );
+
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
+ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
+
+ echo $args['before_widget'];
+
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
+ }
+
+ if ( ! empty( $instance['icons'] ) ) :
+
+ // Get supported social icons.
+ $social_icons = $this->get_supported_icons();
+ $default_icon = $this->get_svg_icon( array( 'icon' => 'chain' ) );
+
+ // Set target attribute for the link
+ if ( true === $instance['new-tab'] ) {
+ $target = '_blank';
+ } else {
+ $target = '_self';
+ }
+ ?>
+
+ <ul class="jetpack-social-widget-list size-<?php echo esc_attr( $instance['icon-size'] ); ?>">
+
+ <?php foreach ( $instance['icons'] as $icon ) : ?>
+
+ <?php if ( ! empty( $icon['url'] ) ) : ?>
+ <li class="jetpack-social-widget-item">
+ <a href="<?php echo esc_url( $icon['url'], array( 'http', 'https', 'mailto', 'skype' ) ); ?>" target="<?php echo $target; ?>">
+ <?php
+ $found_icon = false;
+
+ foreach ( $social_icons as $social_icon ) {
+ if ( false !== stripos( $icon['url'], $social_icon['url'] ) ) {
+ echo '<span class="screen-reader-text">' . esc_attr( $social_icon['label'] ) . '</span>';
+ echo $this->get_svg_icon( array( 'icon' => esc_attr( $social_icon['icon'] ) ) );
+ $found_icon = true;
+ break;
+ }
+ }
+
+ if ( ! $found_icon ) {
+ echo $default_icon;
+ }
+ ?>
+ </a>
+ </li>
+ <?php endif; ?>
+
+ <?php endforeach; ?>
+
+ </ul>
+
+ <?php
+ endif;
+
+ echo $args['after_widget'];
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'social_icons' );
+ }
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @see WP_Widget::update()
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ *
+ * @return array Updated safe values to be saved.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $instance['title'] = sanitize_text_field( $new_instance['title'] );
+ $instance['icon-size'] = $this->defaults['icon-size'];
+
+ if ( in_array( $new_instance['icon-size'], array( 'small', 'medium', 'large' ) ) ) {
+ $instance['icon-size'] = $new_instance['icon-size'];
+ }
+
+ $instance['new-tab'] = isset( $new_instance['new-tab'] ) ? (bool) $new_instance['new-tab'] : false;
+ $icon_count = count( $new_instance['url-icons'] );
+ $instance['icons'] = array();
+
+ foreach ( $new_instance['url-icons'] as $url ) {
+ $url = filter_var( $url, FILTER_SANITIZE_URL );
+
+ if ( ! empty( $url ) ) {
+ $instance['icons'][] = array(
+ 'url' => $url,
+ );
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Back-end widget form.
+ *
+ * @see WP_Widget::form()
+ *
+ * @param array $instance Previously saved values from database.
+ *
+ * @return string|void
+ */
+ public function form( $instance ) {
+ $instance = wp_parse_args( $instance, $this->defaults );
+ $title = sanitize_text_field( $instance['title'] );
+ $sizes = array(
+ 'small' => __( 'Small', 'jetpack' ),
+ 'medium' => __( 'Medium', 'jetpack' ),
+ 'large' => __( 'Large', 'jetpack' ),
+ );
+ $new_tab = isset( $instance['new-tab'] ) ? (bool) $instance['new-tab'] : false;
+ ?>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'icon-size' ); ?>"><?php esc_html_e( 'Size:', 'jetpack' ); ?></label>
+ <select class="widefat" name="<?php echo $this->get_field_name( 'icon-size' ); ?>">
+ <?php foreach ( $sizes as $value => $label ) : ?>
+ <option value="<?php echo esc_attr( $value ); ?>" <?php selected( $value, $instance['icon-size'] ); ?>><?php echo esc_attr( $label ); ?></option>
+ <?php endforeach; ?>
+ </select>
+ </p>
+
+ <div class="jetpack-social-icons-widget-list"
+ data-url-icon-id="<?php echo $this->get_field_id( 'url-icons' ); ?>"
+ data-url-icon-name="<?php echo $this->get_field_name( 'url-icons' ); ?>"
+ >
+
+ <?php
+ foreach ( $instance['icons'] as $icon ) {
+ self::render_icons_template(
+ array(
+ 'url-icon-id' => $this->get_field_id( 'url-icons' ),
+ 'url-icon-name' => $this->get_field_name( 'url-icons' ),
+ 'url-value' => $icon['url'],
+ )
+ );
+ }
+ ?>
+
+ </div>
+
+ <p class="jetpack-social-icons-widget add-button">
+ <button type="button" class="button jetpack-social-icons-add-button">
+ <?php esc_html_e( 'Add an icon', 'jetpack' ); ?>
+ </button>
+ </p>
+
+ <?php
+ switch ( get_locale() ) {
+ case 'es':
+ $support = 'https://es.support.wordpress.com/social-media-icons-widget/#iconos-disponibles';
+ break;
+
+ case 'pt-br':
+ $support = 'https://br.support.wordpress.com/widgets/widget-de-icones-sociais/#ícones-disponíveis';
+ break;
+
+ default:
+ $support = 'https://en.support.wordpress.com/widgets/social-media-icons-widget/#available-icons';
+ }
+ ?>
+
+ <p>
+ <em><a href="<?php echo esc_url( $support ); ?>" target="_blank">
+ <?php esc_html_e( 'View available icons', 'jetpack' ); ?>
+ </a></em>
+ </p>
+
+ <p>
+ <input type="checkbox" class="checkbox" id="<?php echo $this->get_field_id( 'new-tab' ); ?>" name="<?php echo $this->get_field_name( 'new-tab' ); ?>" <?php checked( $new_tab ); ?> />
+ <label for="<?php echo $this->get_field_id( 'new-tab' ); ?>"><?php esc_html_e( 'Open link in a new tab', 'jetpack' ); ?></label>
+ </p>
+
+ <?php
+ }
+
+ /**
+ * Generates template to add icons.
+ *
+ * @param array $args Template arguments
+ */
+ static function render_icons_template( $args = array() ) {
+ $defaults = array(
+ 'url-icon-id' => '',
+ 'url-icon-name' => '',
+ 'url-value' => '',
+ );
+
+ $args = wp_parse_args( $args, $defaults );
+ ?>
+
+ <div class="jetpack-social-icons-widget-item">
+ <div class="jetpack-social-icons-widget-item-wrapper">
+ <div class="handle"></div>
+
+ <p class="jetpack-widget-social-icons-url">
+ <?php
+ printf(
+ '<input class="widefat id="%1$s" name="%2$s[]" type="text" placeholder="%3$s" value="%4$s"/>',
+ esc_attr( $args['url-icon-id'] ),
+ esc_attr( $args['url-icon-name'] ),
+ esc_attr__( 'Account URL', 'jetpack' ),
+ esc_url( $args['url-value'], array( 'http', 'https', 'mailto', 'skype' ) )
+ );
+ ?>
+ </p>
+
+ <p class="jetpack-widget-social-icons-remove-item">
+ <a class="jetpack-widget-social-icons-remove-item-button" href="javascript:;">
+ <?php esc_html_e( 'Remove', 'jetpack' ); ?>
+ </a>
+ </p>
+ </div>
+ </div>
+
+ <?php
+ }
+
+ /**
+ * Return SVG markup.
+ *
+ * @param array $args {
+ * Parameters needed to display an SVG.
+ *
+ * @type string $icon Required SVG icon filename.
+ * }
+ * @return string SVG markup.
+ */
+ public function get_svg_icon( $args = array() ) {
+ // Make sure $args are an array.
+ if ( empty( $args ) ) {
+ return esc_html__( 'Please define default parameters in the form of an array.', 'jetpack' );
+ }
+
+ // Set defaults.
+ $defaults = array(
+ 'icon' => '',
+ );
+
+ // Parse args.
+ $args = wp_parse_args( $args, $defaults );
+
+ // Define an icon.
+ if ( false === array_key_exists( 'icon', $args ) ) {
+ return esc_html__( 'Please define an SVG icon filename.', 'jetpack' );
+ }
+
+ // Set aria hidden.
+ $aria_hidden = ' aria-hidden="true"';
+
+ // Begin SVG markup.
+ $svg = '<svg class="icon icon-' . esc_attr( $args['icon'] ) . '"' . $aria_hidden . ' role="img">';
+
+ /*
+ * Display the icon.
+ *
+ * The whitespace around `<use>` is intentional - it is a work around to a keyboard navigation bug in Safari 10.
+ *
+ * See https://core.trac.wordpress.org/ticket/38387.
+ */
+ $svg .= ' <use href="#icon-' . esc_html( $args['icon'] ) . '" xlink:href="#icon-' . esc_html( $args['icon'] ) . '"></use> ';
+
+ $svg .= '</svg>';
+
+ return $svg;
+ }
+
+ /**
+ * Returns an array of supported social links (URL, icon, and label).
+ *
+ * @return array $social_links_icons
+ */
+ public function get_supported_icons() {
+ $social_links_icons = array(
+ array(
+ 'url' => '500px.com',
+ 'icon' => '500px',
+ 'label' => '500px',
+ ),
+ array(
+ 'url' => 'amazon.cn',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.in',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.fr',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.de',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.it',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.nl',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.es',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.co',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.ca',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'amazon.com',
+ 'icon' => 'amazon',
+ 'label' => 'Amazon',
+ ),
+ array(
+ 'url' => 'apple.com',
+ 'icon' => 'apple',
+ 'label' => 'Apple',
+ ),
+ array(
+ 'url' => 'itunes.com',
+ 'icon' => 'apple',
+ 'label' => 'iTunes',
+ ),
+ array(
+ 'url' => 'bandcamp.com',
+ 'icon' => 'bandcamp',
+ 'label' => 'Bandcamp',
+ ),
+ array(
+ 'url' => 'behance.net',
+ 'icon' => 'behance',
+ 'label' => 'Behance',
+ ),
+ array(
+ 'url' => 'codepen.io',
+ 'icon' => 'codepen',
+ 'label' => 'CodePen',
+ ),
+ array(
+ 'url' => 'deviantart.com',
+ 'icon' => 'deviantart',
+ 'label' => 'DeviantArt',
+ ),
+ array(
+ 'url' => 'digg.com',
+ 'icon' => 'digg',
+ 'label' => 'Digg',
+ ),
+ array(
+ 'url' => 'discord.gg',
+ 'icon' => 'discord',
+ 'label' => 'Discord',
+ ),
+ array(
+ 'url' => 'discordapp.com',
+ 'icon' => 'discord',
+ 'label' => 'Discord',
+ ),
+ array(
+ 'url' => 'dribbble.com',
+ 'icon' => 'dribbble',
+ 'label' => 'Dribbble',
+ ),
+ array(
+ 'url' => 'dropbox.com',
+ 'icon' => 'dropbox',
+ 'label' => 'Dropbox',
+ ),
+ array(
+ 'url' => 'etsy.com',
+ 'icon' => 'etsy',
+ 'label' => 'Etsy',
+ ),
+ array(
+ 'url' => 'facebook.com',
+ 'icon' => 'facebook',
+ 'label' => 'Facebook',
+ ),
+ array(
+ 'url' => '/feed/',
+ 'icon' => 'feed',
+ 'label' => __( 'RSS Feed', 'jetpack' ),
+ ),
+ array(
+ 'url' => 'flickr.com',
+ 'icon' => 'flickr',
+ 'label' => 'Flickr',
+ ),
+ array(
+ 'url' => 'foursquare.com',
+ 'icon' => 'foursquare',
+ 'label' => 'Foursquare',
+ ),
+ array(
+ 'url' => 'goodreads.com',
+ 'icon' => 'goodreads',
+ 'label' => 'Goodreads',
+ ),
+ array(
+ 'url' => 'google.com',
+ 'icon' => 'google',
+ 'label' => 'Google',
+ ),
+ array(
+ 'url' => 'github.com',
+ 'icon' => 'github',
+ 'label' => 'GitHub',
+ ),
+ array(
+ 'url' => 'instagram.com',
+ 'icon' => 'instagram',
+ 'label' => 'Instagram',
+ ),
+ array(
+ 'url' => 'linkedin.com',
+ 'icon' => 'linkedin',
+ 'label' => 'LinkedIn',
+ ),
+ array(
+ 'url' => 'mailto:',
+ 'icon' => 'mail',
+ 'label' => __( 'Email', 'jetpack' ),
+ ),
+ array(
+ 'url' => 'meetup.com',
+ 'icon' => 'meetup',
+ 'label' => 'Meetup',
+ ),
+ array(
+ 'url' => 'medium.com',
+ 'icon' => 'medium',
+ 'label' => 'Medium',
+ ),
+ array(
+ 'url' => 'pinterest.',
+ 'icon' => 'pinterest',
+ 'label' => 'Pinterest',
+ ),
+ array(
+ 'url' => 'getpocket.com',
+ 'icon' => 'pocket',
+ 'label' => 'Pocket',
+ ),
+ array(
+ 'url' => 'reddit.com',
+ 'icon' => 'reddit',
+ 'label' => 'Reddit',
+ ),
+ array(
+ 'url' => 'skype.com',
+ 'icon' => 'skype',
+ 'label' => 'Skype',
+ ),
+ array(
+ 'url' => 'skype:',
+ 'icon' => 'skype',
+ 'label' => 'Skype',
+ ),
+ array(
+ 'url' => 'slideshare.net',
+ 'icon' => 'slideshare',
+ 'label' => 'SlideShare',
+ ),
+ array(
+ 'url' => 'snapchat.com',
+ 'icon' => 'snapchat',
+ 'label' => 'Snapchat',
+ ),
+ array(
+ 'url' => 'soundcloud.com',
+ 'icon' => 'soundcloud',
+ 'label' => 'SoundCloud',
+ ),
+ array(
+ 'url' => 'spotify.com',
+ 'icon' => 'spotify',
+ 'label' => 'Spotify',
+ ),
+ array(
+ 'url' => 'stackoverflow.com',
+ 'icon' => 'stackoverflow',
+ 'label' => 'Stack Overflow',
+ ),
+ array(
+ 'url' => 'stumbleupon.com',
+ 'icon' => 'stumbleupon',
+ 'label' => 'StumbleUpon',
+ ),
+ array(
+ 'url' => 'tumblr.com',
+ 'icon' => 'tumblr',
+ 'label' => 'Tumblr',
+ ),
+ array(
+ 'url' => 'twitch.tv',
+ 'icon' => 'twitch',
+ 'label' => 'Twitch',
+ ),
+ array(
+ 'url' => 'twitter.com',
+ 'icon' => 'twitter',
+ 'label' => 'Twitter',
+ ),
+ array(
+ 'url' => 'vimeo.com',
+ 'icon' => 'vimeo',
+ 'label' => 'Vimeo',
+ ),
+ array(
+ 'url' => 'vk.com',
+ 'icon' => 'vk',
+ 'label' => 'VK',
+ ),
+ array(
+ 'url' => 'wordpress.com',
+ 'icon' => 'wordpress',
+ 'label' => 'WordPress.com',
+ ),
+ array(
+ 'url' => 'wordpress.org',
+ 'icon' => 'wordpress',
+ 'label' => 'WordPress',
+ ),
+ array(
+ 'url' => 'yelp.com',
+ 'icon' => 'yelp',
+ 'label' => 'Yelp',
+ ),
+ array(
+ 'url' => 'youtube.com',
+ 'icon' => 'youtube',
+ 'label' => 'YouTube',
+ ),
+ );
+
+ return $social_links_icons;
+ }
+} // Jetpack_Widget_Social_Icons
+
+/**
+ * Register and load the widget.
+ *
+ * @access public
+ * @return void
+ */
+function jetpack_widget_social_icons_load() {
+ register_widget( 'Jetpack_Widget_Social_Icons' );
+}
+add_action( 'widgets_init', 'jetpack_widget_social_icons_load' );
diff --git a/plugins/jetpack/modules/widgets/social-icons/social-icons-admin.css b/plugins/jetpack/modules/widgets/social-icons/social-icons-admin.css
new file mode 100644
index 00000000..575ac09f
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/social-icons/social-icons-admin.css
@@ -0,0 +1,94 @@
+.jetpack-social-icons-widget-item {
+ background: #fff;
+ border: 1px solid #e5e5e5;
+ cursor: move;
+ margin: 0;
+}
+
+html[class*='wordpress_com'] .jetpack-social-icons-widget-item,
+.in-calypso .jetpack-social-icons-widget-item {
+ border-color: #c8d7e1;
+}
+
+.jetpack-social-icons-widget-item:hover {
+ outline: 1px solid #999;
+ outline-offset: -1px;
+}
+
+html[class*='wordpress_com'] .jetpack-social-icons-widget-item:hover,
+.in-calypso .jetpack-social-icons-widget-item:hover {
+ outline-color: #a8bece;
+}
+
+.jetpack-social-icons-widget-item.ui-sortable-helper {
+ border-color: #999;
+}
+
+html[class*='wordpress_com'] .jetpack-social-icons-widget-item.ui-sortable-helper,
+.in-calypso .jetpack-social-icons-widget-item.ui-sortable-helper {
+ border-color: #a8bece;
+}
+
+.jetpack-social-icons-widget-item.ui-sortable-helper {
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.jetpack-social-icons-widget-item.ui-state-placeholder {
+ border-color: #a0a5aa;
+ border-style: dashed;
+ margin: 0 0 1px;
+}
+
+.jetpack-social-icons-widget-item + .jetpack-social-icons-widget-item:not(.ui-state-placeholder) {
+ margin-top: -1px;
+}
+
+.jetpack-social-icons-widget-item-wrapper {
+ padding: 1em;
+ position: relative;
+}
+
+.jetpack-social-icons-widget-item .handle {
+ display: block;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.jetpack-social-icons-widget-item p {
+ margin: 0;
+ position: relative;
+}
+
+.jetpack-social-icons-widget-item p + p {
+ margin-top: 1em;
+}
+
+.jetpack-widget-social-icons-remove-item {
+ display: inline-block;
+}
+
+.jetpack-widget-social-icons-remove-item-button {
+ color: #a00;
+ text-decoration: none;
+}
+
+.jetpack-widget-social-icons-remove-item-button:focus,
+.jetpack-widget-social-icons-remove-item-button:hover {
+ color: #f00;
+}
+
+.jetpack-social-icons-add-button:before {
+ content: "\f132";
+ display: inline-block;
+ position: relative;
+ left: -2px;
+ top: -1px;
+ font: 400 20px/1 dashicons;
+ vertical-align: middle;
+ transition: all 0.2s;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
diff --git a/plugins/jetpack/modules/widgets/social-icons/social-icons-admin.js b/plugins/jetpack/modules/widgets/social-icons/social-icons-admin.js
new file mode 100644
index 00000000..d44716a9
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/social-icons/social-icons-admin.js
@@ -0,0 +1,144 @@
+( function( $ ) {
+ var timeout = null;
+
+ // Make the list of items sortable.
+ function initWidget( widget ) {
+ widget.find( '.jetpack-social-icons-widget-list' ).sortable( {
+ items: '> .jetpack-social-icons-widget-item',
+ handle: '.handle',
+ cursor: 'move',
+ placeholder: 'jetpack-social-icons-widget-item ui-state-placeholder',
+ containment: widget,
+ forcePlaceholderSize: true,
+ update: function() {
+ livePreviewUpdate(
+ $( this )
+ .parents( '.form' )
+ .find( '.widget-control-save' )
+ );
+ },
+ } );
+ }
+
+ // Live preview update.
+ function livePreviewUpdate( button ) {
+ if ( ! $( document.body ).hasClass( 'wp-customizer' ) || ! button.length ) {
+ return;
+ }
+
+ button.trigger( 'click' ).hide();
+ }
+
+ $( document ).ready( function() {
+ // Add an item.
+ $( document ).on( 'click', '.jetpack-social-icons-widget.add-button button', function( event ) {
+ event.preventDefault();
+
+ var template, widgetContent, widgetList, widgetLastItem, urlId, urlName;
+
+ template = $( $.trim( $( '#tmpl-jetpack-widget-social-icons-template' ).html() ) );
+ widgetContent = $( this ).parents( '.widget-content' );
+ widgetList = widgetContent.find( '.jetpack-social-icons-widget-list' );
+ urlId = widgetList.data( 'url-icon-id' );
+ urlName = widgetList.data( 'url-icon-name' );
+
+ template
+ .find( '.jetpack-widget-social-icons-url input' )
+ .attr( 'id', urlId )
+ .attr( 'name', urlName + '[]' );
+
+ widgetList.append( template );
+
+ widgetLastItem = widgetContent.find( '.jetpack-social-icons-widget-item:last' );
+ widgetLastItem.find( 'input:first' ).trigger( 'focus' );
+ } );
+
+ // Remove an item.
+ $( document ).on( 'click', '.jetpack-widget-social-icons-remove-item-button', function(
+ event
+ ) {
+ event.preventDefault();
+
+ var button = $( this )
+ .parents( '.form' )
+ .find( '.widget-control-save' );
+
+ $( this )
+ .parents( '.jetpack-social-icons-widget-item' )
+ .remove();
+
+ livePreviewUpdate( button );
+ } );
+
+ // Event handler for widget open button.
+ $( document ).on(
+ 'click',
+ 'div.widget[id*="jetpack_widget_social_icons"] .widget-title, div.widget[id*="jetpack_widget_social_icons"] .widget-action',
+ function() {
+ if ( $( this ).parents( '#available-widgets' ).length ) {
+ return;
+ }
+
+ initWidget( $( this ).parents( '.widget[id*="jetpack_widget_social_icons"]' ) );
+ }
+ );
+
+ // Event handler for widget added.
+ $( document ).on( 'widget-added', function( event, widget ) {
+ if ( widget.is( '[id*="jetpack_widget_social_icons"]' ) ) {
+ event.preventDefault();
+ initWidget( widget );
+ }
+ } );
+
+ // Event handler for widget updated.
+ $( document ).on( 'widget-updated', function( event, widget ) {
+ if ( widget.is( '[id*="jetpack_widget_social_icons"]' ) ) {
+ event.preventDefault();
+ initWidget( widget );
+ }
+ } );
+
+ // Live preview update on input focus out.
+ $( document ).on( 'focusout', 'input[name*="jetpack_widget_social_icons"]', function() {
+ livePreviewUpdate(
+ $( this )
+ .parents( '.form' )
+ .find( '.widget-control-save' )
+ );
+ } );
+
+ // Live preview update on input enter key.
+ $( document ).on( 'keydown', 'input[name*="jetpack_widget_social_icons"]', function( event ) {
+ if ( event.keyCode === 13 ) {
+ livePreviewUpdate(
+ $( this )
+ .parents( '.form' )
+ .find( '.widget-control-save' )
+ );
+ }
+ } );
+
+ // Live preview update on input key up 1s.
+ $( document ).on( 'keyup', 'input[name*="jetpack_widget_social_icons"]', function() {
+ clearTimeout( timeout );
+
+ timeout = setTimeout( function() {
+ livePreviewUpdate(
+ $( this )
+ .parents( '.form' )
+ .find( '.widget-control-save' )
+ );
+ }, 1000 );
+ } );
+
+ // Live preview update on select change.
+ $( document ).on( 'change', 'select[name*="jetpack_widget_social_icons"]', function() {
+ livePreviewUpdate(
+ $( this )
+ .parents( '.form' )
+ .find( '.widget-control-save' )
+ );
+ } );
+ } );
+} )( jQuery );
diff --git a/plugins/jetpack/modules/widgets/social-icons/social-icons.css b/plugins/jetpack/modules/widgets/social-icons/social-icons.css
new file mode 100644
index 00000000..505f0663
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/social-icons/social-icons.css
@@ -0,0 +1,75 @@
+.jetpack_widget_social_icons ul,
+.jetpack_widget_social_icons li {
+ list-style: none;
+}
+
+.jetpack_widget_social_icons ul {
+ display: block;
+ margin: 0 0 1.5em;
+ padding: 0;
+}
+
+.jetpack_widget_social_icons ul li {
+ border: 0;
+ display: inline-block;
+ line-height: 1;
+ margin: 0;
+ padding: 0;
+}
+
+.jetpack_widget_social_icons ul li:before,
+.jetpack_widget_social_icons ul li:after {
+ display: none;
+}
+
+.jetpack_widget_social_icons a {
+ border: 0;
+ box-shadow: none;
+ display: block;
+ height: 24px;
+ text-decoration: none;
+ width: 24px;
+}
+
+.jetpack_widget_social_icons svg {
+ color: inherit;
+ fill: currentColor;
+ height: inherit;
+ vertical-align: middle;
+ width: inherit;
+}
+
+/* Sizes */
+
+.jetpack_widget_social_icons ul.size-small a {
+ height: 24px;
+ width: 24px;
+}
+
+.jetpack_widget_social_icons ul.size-medium a {
+ height: 32px;
+ width: 32px;
+}
+
+.jetpack_widget_social_icons ul.size-large a {
+ height: 48px;
+ width: 48px;
+}
+
+/*
+Text meant only for screen readers.
+Provides support for themes that do not bundle this CSS yet.
+@see https://make.wordpress.org/accessibility/2015/02/09/hiding-text-for-screen-readers-with-wordpress-core/
+***********************************/
+.screen-reader-text {
+ border: 0;
+ clip: rect(1px, 1px, 1px, 1px);
+ clip-path: inset(50%);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute ! important;
+ width: 1px;
+ word-wrap: normal ! important;
+}
diff --git a/plugins/jetpack/modules/widgets/social-media-icons.php b/plugins/jetpack/modules/widgets/social-media-icons.php
new file mode 100644
index 00000000..0e8028ef
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/social-media-icons.php
@@ -0,0 +1,346 @@
+<?php
+/*
+Plugin Name: Social Media Icons Widget
+Description: A simple widget that displays social media icons
+Author: Automattic Inc.
+
+This widget is now deprecated.
+Any new features should go into modules/widgets/social-icons.php instead.
+@see https://github.com/Automattic/jetpack/pull/8498
+
+*/
+
+
+/**
+ * WPCOM_social_media_icons_widget class.
+ *
+ * @extends WP_Widget
+ */
+class WPCOM_social_media_icons_widget extends WP_Widget {
+
+ /**
+ * Defaults
+ *
+ * @var mixed
+ * @access private
+ */
+ private $defaults;
+
+ /**
+ * Services
+ *
+ * @var mixed
+ * @access private
+ */
+ private $services;
+
+
+ /**
+ * __construct function.
+ *
+ * @access public
+ * @return void
+ */
+ public function __construct() {
+ parent::__construct(
+ 'wpcom_social_media_icons_widget',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'Social Media Icons (Deprecated)', 'jetpack' ) ),
+ array(
+ 'description' => __( 'A simple widget that displays social media icons.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+ $this->defaults = array(
+ 'title' => __( 'Social', 'jetpack' ),
+ 'facebook_username' => '',
+ 'twitter_username' => '',
+ 'instagram_username' => '',
+ 'pinterest_username' => '',
+ 'linkedin_username' => '',
+ 'github_username' => '',
+ 'youtube_username' => '',
+ 'vimeo_username' => '',
+ 'googleplus_username' => '',
+ 'flickr_username' => '',
+ 'wordpress_username' => '',
+ 'twitch_username' => '',
+ 'tumblr_username' => '',
+ );
+ $this->services = array(
+ 'facebook' => array( 'Facebook', 'https://www.facebook.com/%s/' ),
+ 'twitter' => array( 'Twitter', 'https://twitter.com/%s/' ),
+ 'instagram' => array( 'Instagram', 'https://www.instagram.com/%s/' ),
+ 'pinterest' => array( 'Pinterest', 'https://www.pinterest.com/%s/' ),
+ 'linkedin' => array( 'LinkedIn', 'https://www.linkedin.com/in/%s/' ),
+ 'github' => array( 'GitHub', 'https://github.com/%s/' ),
+ 'youtube' => array( 'YouTube', 'https://www.youtube.com/%s/' ),
+ 'vimeo' => array( 'Vimeo', 'https://vimeo.com/%s/' ),
+ 'googleplus' => array( 'Google+', 'https://plus.google.com/u/0/%s/' ),
+ 'flickr' => array( 'Flickr', 'https://www.flickr.com/photos/%s/' ),
+ 'wordpress' => array( 'WordPress.org', 'https://profiles.wordpress.org/%s/' ),
+ 'twitch' => array( 'Twitch', 'https://www.twitch.tv/%s/' ),
+ 'tumblr' => array( 'Tumblr', 'https://%s.tumblr.com' ),
+ );
+ if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
+ }
+ }
+
+ /**
+ * Enqueue Style.
+ *
+ * @access public
+ * @return void
+ */
+ public function enqueue_style() {
+ wp_register_style( 'jetpack_social_media_icons_widget', plugins_url( 'social-media-icons/style.css', __FILE__ ), array(), '20150602' );
+ wp_enqueue_style( 'jetpack_social_media_icons_widget' );
+ }
+
+ /**
+ * Check Genericons.
+ *
+ * @access private
+ * @return Bool.
+ */
+ private function check_genericons() {
+ global $wp_styles;
+ foreach ( $wp_styles->queue as $handle ) {
+ if ( false !== stristr( $handle, 'genericons' ) ) {
+ return $handle;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Widget Front End.
+ *
+ * @access public
+ * @param mixed $args Arguments.
+ * @param mixed $instance Instance.
+ * @return void
+ */
+ public function widget( $args, $instance ) {
+ $instance = wp_parse_args( (array) $instance, $this->defaults );
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $instance['title'] = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
+ if ( ! $this->check_genericons() ) {
+ wp_enqueue_style( 'genericons' );
+ }
+ $index = 10;
+ $html = array();
+ $alt_text = esc_attr__( 'View %1$s&#8217;s profile on %2$s', 'jetpack' );
+ foreach ( $this->services as $service => $data ) {
+ list( $service_name, $url ) = $data;
+ if ( ! isset( $instance[ $service . '_username' ] ) ) {
+ continue;
+ }
+ $username = $link_username = $instance[ $service . '_username' ];
+ if ( empty( $username ) ) {
+ continue;
+ }
+ $index += 10;
+ $predefined_url = false;
+
+ /** Check if full URL entered in configuration, use it instead of tinkering **/
+ if (
+ in_array(
+ parse_url( $username, PHP_URL_SCHEME ),
+ array( 'http', 'https' )
+ )
+ ) {
+ $predefined_url = $username;
+
+ // In case of a predefined link we only display the service name
+ // for screen readers
+ $alt_text = '%2$s';
+ }
+
+ if ( 'googleplus' === $service
+ && ! is_numeric( $username )
+ && substr( $username, 0, 1 ) !== '+'
+ ) {
+ $link_username = '+' . $username;
+ }
+ if ( 'youtube' === $service && 'UC' === substr( $username, 0, 2 ) ) {
+ $link_username = 'channel/' . $username;
+ } elseif ( 'youtube' === $service ) {
+ $link_username = 'user/' . $username;
+ }
+
+ if ( ! $predefined_url ) {
+ $predefined_url = sprintf( $url, $link_username );
+ }
+ /**
+ * Fires for each profile link in the social icons widget. Can be used
+ * to change the links for certain social networks if needed. All URLs
+ * will be passed through `esc_attr` on output.
+ *
+ * @module widgets
+ *
+ * @since 3.8.0
+ *
+ * @param string $url the currently processed URL
+ * @param string $service the lowercase service slug, e.g. 'facebook', 'youtube', etc.
+ */
+ $link = apply_filters(
+ 'jetpack_social_media_icons_widget_profile_link',
+ $predefined_url,
+ $service
+ );
+ $html[ $index ] = sprintf(
+ '<a href="%1$s" class="genericon genericon-%2$s" target="_blank"><span class="screen-reader-text">%3$s</span></a>',
+ esc_attr( $link ),
+ esc_attr( $service ),
+ sprintf( $alt_text, esc_html( $username ), $service_name )
+ );
+ }
+ /**
+ * Fires at the end of the list of Social Media accounts.
+ * Can be used to add a new Social Media Site to the Social Media Icons Widget.
+ * The filter function passed the array of HTML entries that will be sorted
+ * by key, each wrapped in a list item element and output as an unsorted list.
+ *
+ * @module widgets
+ *
+ * @since 3.8.0
+ *
+ * @param array $html Associative array of HTML snippets per each icon.
+ */
+ $html = apply_filters( 'jetpack_social_media_icons_widget_array', $html );
+ ksort( $html );
+ $html = '<ul><li>' . join( '</li><li>', $html ) . '</li></ul>';
+ if ( ! empty( $instance['title'] ) ) {
+ $html = $args['before_title'] . esc_html( $instance['title'] ) . $args['after_title'] . $html;
+ }
+ $html = $args['before_widget'] . $html . $args['after_widget'];
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'social_media_icons' );
+
+ /**
+ * Filters the Social Media Icons widget output.
+ *
+ * @module widgets
+ *
+ * @since 3.6.0
+ *
+ * @param string $html Social Media Icons widget html output.
+ */
+ echo apply_filters( 'jetpack_social_media_icons_widget_output', $html );
+ }
+
+ /**
+ * Widget Settings.
+ *
+ * @access public
+ * @param mixed $instance Instance.
+ * @return void
+ */
+ public function form( $instance ) {
+ $instance = wp_parse_args( (array) $instance, $this->defaults );
+ ?>
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_attr_e( 'Title:', 'jetpack' ); ?></label>
+ <input
+ class="widefat"
+ id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['title'] ); ?>"
+ />
+ </p>
+ <?php
+ foreach ( $this->services as $service => $data ) {
+ list( $service_name, $url ) = $data;
+ ?>
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( $service . '_username' ) ); ?>">
+ <?php
+ /* translators: %s is a social network name, e.g. Facebook. */
+ printf( __( '%s username:', 'jetpack' ), $service_name );
+ ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo esc_attr( $this->get_field_id( $service . '_username' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( $service . '_username' ) ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance[ $service . '_username' ] ); ?>"
+ />
+ </p>
+ <?php
+ }
+ }
+
+ /**
+ * Update Widget Settings.
+ *
+ * @access public
+ * @param mixed $new_instance New Instance.
+ * @param mixed $old_instance Old Instance.
+ * @return Instance.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $instance = (array) $old_instance;
+ foreach ( $new_instance as $field => $value ) {
+ $instance[ $field ] = sanitize_text_field( $new_instance[ $field ] );
+ }
+ // Stats.
+ $stats = $instance;
+ unset( $stats['title'] );
+ $stats = array_filter( $stats );
+ $stats = array_keys( $stats );
+ $stats = array_map( array( $this, 'remove_username' ), $stats );
+ foreach ( $stats as $val ) {
+ /**
+ * Fires for each Social Media account being saved in the Social Media Widget settings.
+ *
+ * @module widgets
+ *
+ * @since 3.6.0
+ *
+ * @param string social-media-links-widget-svcs Type of action to track.
+ * @param string $val Name of the Social Media account being saved.
+ */
+ do_action( 'jetpack_bump_stats_extras', 'social-media-links-widget-svcs', $val );
+ }
+ return $instance;
+ }
+
+ /**
+ * Remove username from value before to save stats.
+ *
+ * @access public
+ * @param mixed $val Value.
+ * @return Value.
+ */
+ public function remove_username( $val ) {
+ return str_replace( '_username', '', $val );
+ }
+} // End Class.
+
+/**
+ * Register and load the widget.
+ *
+ * @access public
+ * @return void
+ */
+function wpcom_social_media_icons_widget_load_widget() {
+ $transient = 'wpcom_social_media_icons_widget::is_active';
+ $has_widget = get_transient( $transient );
+
+ if ( false === $has_widget ) {
+ $is_active_widget = is_active_widget( false, false, 'wpcom_social_media_icons_widget', false );
+ $has_widget = (int) ! empty( $is_active_widget );
+ set_transient( $transient, $has_widget, 1 * HOUR_IN_SECONDS );
+ }
+
+ // [DEPRECATION]: Only register widget if active widget exists already
+ if ( $has_widget ) {
+ register_widget( 'wpcom_social_media_icons_widget' );
+ }
+}
+add_action( 'widgets_init', 'wpcom_social_media_icons_widget_load_widget' );
diff --git a/plugins/jetpack/modules/widgets/social-media-icons/style.css b/plugins/jetpack/modules/widgets/social-media-icons/style.css
new file mode 100644
index 00000000..665d1c7d
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/social-media-icons/style.css
@@ -0,0 +1,49 @@
+.widget_wpcom_social_media_icons_widget ul {
+ list-style-type: none;
+ margin-left: 0;
+}
+
+.widget_wpcom_social_media_icons_widget ul li {
+ border: 0 none;
+ display: inline;
+ margin-right: 0.5em;
+}
+
+.widget_wpcom_social_media_icons_widget li a {
+ border: 0 none;
+ text-decoration: none;
+}
+
+.widget_wpcom_social_media_icons_widget .genericon {
+ font-family: 'Genericons';
+}
+
+.widget_wpcom_social_media_icons_widget .screen-reader-text {
+ clip: rect(1px, 1px, 1px, 1px);
+ position: absolute !important;
+ height: 1px;
+ width: 1px;
+ overflow: hidden;
+}
+
+.widget_wpcom_social_media_icons_widget .screen-reader-text:hover,
+.widget_wpcom_social_media_icons_widget .screen-reader-text:active,
+.widget_wpcom_social_media_icons_widget .screen-reader-text:focus {
+ background-color: #f1f1f1;
+ border-radius: 3px;
+ box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6);
+ clip: auto !important;
+ color: #21759b;
+ display: block;
+ font-size: 14px;
+ font-size: 0.875rem;
+ font-weight: bold;
+ height: auto;
+ left: 5px;
+ line-height: normal;
+ padding: 15px 23px 14px;
+ text-decoration: none;
+ top: 5px;
+ width: auto;
+ z-index: 100000; /* Above WP toolbar. */
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/widgets/top-posts.php b/plugins/jetpack/modules/widgets/top-posts.php
new file mode 100644
index 00000000..cdcd59d1
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/top-posts.php
@@ -0,0 +1,674 @@
+<?php
+
+/*
+ * Currently, this widget depends on the Stats Module. To not load this file
+ * when the Stats Module is not active would potentially bypass Jetpack's
+ * fatal error detection on module activation, so we always load this file.
+ * Instead, we don't register the widget if the Stats Module isn't active.
+ */
+
+/**
+ * Register the widget for use in Appearance -> Widgets
+ */
+add_action( 'widgets_init', 'jetpack_top_posts_widget_init' );
+
+function jetpack_top_posts_widget_init() {
+ // Currently, this widget depends on the Stats Module
+ if (
+ ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM )
+ &&
+ ! function_exists( 'stats_get_from_restapi' )
+ ) {
+ return;
+ }
+
+ register_widget( 'Jetpack_Top_Posts_Widget' );
+}
+
+class Jetpack_Top_Posts_Widget extends WP_Widget {
+ public $alt_option_name = 'widget_stats_topposts';
+ public $default_title = '';
+
+ function __construct() {
+ parent::__construct(
+ 'top-posts',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Top Posts &amp; Pages', 'jetpack' ) ),
+ array(
+ 'description' => __( 'Shows your most viewed posts and pages.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ $this->default_title = __( 'Top Posts &amp; Pages', 'jetpack' );
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
+ }
+
+ /**
+ * Add explanation about how the statistics are calculated.
+ *
+ * @module widgets
+ *
+ * @since 3.9.3
+ */
+ add_action( 'jetpack_widget_top_posts_after_fields', array( $this, 'stats_explanation' ) );
+ }
+
+ function enqueue_style() {
+ wp_register_style( 'jetpack-top-posts-widget', plugins_url( 'top-posts/style.css', __FILE__ ), array(), '20141013' );
+ wp_enqueue_style( 'jetpack-top-posts-widget' );
+ }
+
+ function form( $instance ) {
+ $instance = wp_parse_args( (array) $instance, $this->defaults() );
+
+ if ( false === $instance['title'] ) {
+ $instance['title'] = $this->default_title;
+ }
+ $title = stripslashes( $instance['title'] );
+
+ $count = isset( $instance['count'] ) ? (int) $instance['count'] : 10;
+ if ( $count < 1 || 10 < $count ) {
+ $count = 10;
+ }
+
+ $allowed_post_types = array_values( get_post_types( array( 'public' => true ) ) );
+ $types = isset( $instance['types'] ) ? (array) $instance['types'] : array( 'post', 'page' );
+
+ // 'likes' are not available in Jetpack
+ $ordering = isset( $instance['ordering'] ) && 'likes' === $instance['ordering'] ? 'likes' : 'views';
+
+ if ( isset( $instance['display'] ) && in_array( $instance['display'], array( 'grid', 'list', 'text' ) ) ) {
+ $display = $instance['display'];
+ } else {
+ $display = 'text';
+ }
+
+ ?>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'count' ); ?>"><?php esc_html_e( 'Maximum number of posts to show (no more than 10):', 'jetpack' ); ?></label>
+ <input id="<?php echo $this->get_field_id( 'count' ); ?>" name="<?php echo $this->get_field_name( 'count' ); ?>" type="number" value="<?php echo (int) $count; ?>" min="1" max="10" />
+ </p>
+
+ <?php if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) : ?>
+ <p>
+ <label><?php esc_html_e( 'Order Top Posts &amp; Pages By:', 'jetpack' ); ?></label>
+ <ul>
+ <li><label><input id="<?php echo $this->get_field_id( 'ordering' ); ?>-likes" name="<?php echo $this->get_field_name( 'ordering' ); ?>" type="radio" value="likes" <?php checked( 'likes', $ordering ); ?> /> <?php esc_html_e( 'Likes', 'jetpack' ); ?></label></li>
+ <li><label><input id="<?php echo $this->get_field_id( 'ordering' ); ?>-views" name="<?php echo $this->get_field_name( 'ordering' ); ?>" type="radio" value="views" <?php checked( 'views', $ordering ); ?> /> <?php esc_html_e( 'Views', 'jetpack' ); ?></label></li>
+ </ul>
+ </p>
+ <?php endif; ?>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'types' ); ?>"><?php esc_html_e( 'Types of pages to display:', 'jetpack' ); ?></label>
+ <ul>
+ <?php
+ foreach ( $allowed_post_types as $type ) {
+ // Get the Post Type name to display next to the checkbox
+ $post_type_object = get_post_type_object( $type );
+ $label = $post_type_object->labels->name;
+
+ $checked = '';
+ if ( in_array( $type, $types ) ) {
+ $checked = 'checked="checked" ';
+ }
+ ?>
+
+ <li><label>
+ <input value="<?php echo esc_attr( $type ); ?>" name="<?php echo $this->get_field_name( 'types' ); ?>[]" id="<?php echo $this->get_field_id( 'types' ); ?>-<?php echo $type; ?>" type="checkbox" <?php echo $checked; ?>>
+ <?php echo esc_html( $label ); ?>
+ </label></li>
+
+ <?php } // End foreach ?>
+ </ul>
+ </p>
+
+ <p>
+ <label><?php esc_html_e( 'Display as:', 'jetpack' ); ?></label>
+ <ul>
+ <li><label><input id="<?php echo $this->get_field_id( 'display' ); ?>-text" name="<?php echo $this->get_field_name( 'display' ); ?>" type="radio" value="text" <?php checked( 'text', $display ); ?> /> <?php esc_html_e( 'Text List', 'jetpack' ); ?></label></li>
+ <li><label><input id="<?php echo $this->get_field_id( 'display' ); ?>-list" name="<?php echo $this->get_field_name( 'display' ); ?>" type="radio" value="list" <?php checked( 'list', $display ); ?> /> <?php esc_html_e( 'Image List', 'jetpack' ); ?></label></li>
+ <li><label><input id="<?php echo $this->get_field_id( 'display' ); ?>-grid" name="<?php echo $this->get_field_name( 'display' ); ?>" type="radio" value="grid" <?php checked( 'grid', $display ); ?> /> <?php esc_html_e( 'Image Grid', 'jetpack' ); ?></label></li>
+ </ul>
+ </p>
+ <?php
+
+ /**
+ * Fires after the fields are displayed in the Top Posts Widget settings in wp-admin.
+ *
+ * Allow adding extra content after the fields are displayed.
+ *
+ * @module widgets
+ *
+ * @since 3.9.3
+ *
+ * @param array $args {
+ * @param array $instance The widget instance.
+ * @param object $this The class object.
+ * }
+ */
+ do_action( 'jetpack_widget_top_posts_after_fields', array( $instance, $this ) );
+ }
+
+ /**
+ * Explains how the statics are calculated.
+ */
+ function stats_explanation() {
+ ?>
+
+ <p><?php esc_html_e( 'Top Posts &amp; Pages by views are calculated from 24-48 hours of stats. They take a while to change.', 'jetpack' ); ?></p>
+ <?php
+ }
+
+ function update( $new_instance, $old_instance ) {
+ $instance = array();
+ $instance['title'] = wp_kses( $new_instance['title'], array() );
+ if ( $instance['title'] === $this->default_title ) {
+ $instance['title'] = false; // Store as false in case of language change
+ }
+
+ $instance['count'] = (int) $new_instance['count'];
+ if ( $instance['count'] < 1 || 10 < $instance['count'] ) {
+ $instance['count'] = 10;
+ }
+
+ // 'likes' are not available in Jetpack
+ $instance['ordering'] = isset( $new_instance['ordering'] ) && 'likes' == $new_instance['ordering'] ? 'likes' : 'views';
+
+ $allowed_post_types = array_values( get_post_types( array( 'public' => true ) ) );
+ $instance['types'] = $new_instance['types'];
+ foreach ( $new_instance['types'] as $key => $type ) {
+ if ( ! in_array( $type, $allowed_post_types ) ) {
+ unset( $new_instance['types'][ $key ] );
+ }
+ }
+
+ if ( isset( $new_instance['display'] ) && in_array( $new_instance['display'], array( 'grid', 'list', 'text' ) ) ) {
+ $instance['display'] = $new_instance['display'];
+ } else {
+ $instance['display'] = 'text';
+ }
+
+ /**
+ * Filters Top Posts Widget settings before they're saved.
+ *
+ * @module widgets
+ *
+ * @since 3.9.3
+ *
+ * @param array $instance The santized widget instance. Only contains data processed by the current widget.
+ * @param array $new_instance The new widget instance before sanitization.
+ */
+ $instance = apply_filters( 'jetpack_top_posts_saving', $instance, $new_instance );
+
+ return $instance;
+ }
+
+ function widget( $args, $instance ) {
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'top_posts' );
+
+ $instance = wp_parse_args( (array) $instance, $this->defaults() );
+
+ $title = isset( $instance['title'] ) ? $instance['title'] : false;
+ if ( false === $title ) {
+ $title = $this->default_title;
+ }
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $title );
+
+ $count = isset( $instance['count'] ) ? (int) $instance['count'] : false;
+ if ( $count < 1 || 10 < $count ) {
+ $count = 10;
+ }
+ /**
+ * Control the number of displayed posts.
+ *
+ * @module widgets
+ *
+ * @since 3.3.0
+ *
+ * @param string $count Number of Posts displayed in the Top Posts widget. Default is 10.
+ */
+ $count = apply_filters( 'jetpack_top_posts_widget_count', $count );
+
+ $types = isset( $instance['types'] ) ? (array) $instance['types'] : array( 'post', 'page' );
+
+ // 'likes' are not available in Jetpack
+ $ordering = isset( $instance['ordering'] ) && 'likes' == $instance['ordering'] ? 'likes' : 'views';
+
+ if ( isset( $instance['display'] ) && in_array( $instance['display'], array( 'grid', 'list', 'text' ) ) ) {
+ $display = $instance['display'];
+ } else {
+ $display = 'text';
+ }
+
+ if ( 'text' != $display ) {
+ $get_image_options = array(
+ 'fallback_to_avatars' => true,
+ /** This filter is documented in modules/stats.php */
+ 'gravatar_default' => apply_filters( 'jetpack_static_url', set_url_scheme( 'https://en.wordpress.com/i/logo/white-gray-80.png' ) ),
+ 'avatar_size' => 40,
+ 'width' => null,
+ 'height' => null,
+ );
+ if ( 'grid' == $display ) {
+ $get_image_options['avatar_size'] = 200;
+ }
+ /**
+ * Top Posts Widget Image options.
+ *
+ * @module widgets
+ *
+ * @since 1.8.0
+ *
+ * @param array $get_image_options {
+ * Array of Image options.
+ * @type bool true Should we default to Gravatars when no image is found? Default is true.
+ * @type string $gravatar_default Default Image URL if no Gravatar is found.
+ * @type int $avatar_size Default Image size.
+ * @type mixed $width Image width, not set by default and $avatar_size is used instead.
+ * @type mixed $height Image height, not set by default and $avatar_size is used instead.
+ * }
+ */
+ $get_image_options = apply_filters( 'jetpack_top_posts_widget_image_options', $get_image_options );
+ }
+
+ if ( function_exists( 'wpl_get_blogs_most_liked_posts' ) && 'likes' == $ordering ) {
+ $posts = $this->get_by_likes( $count );
+ } else {
+ $posts = $this->get_by_views( $count, $args );
+ }
+
+ // Filter the returned posts. Remove all posts that do not match the chosen Post Types.
+ if ( isset( $types ) ) {
+ foreach ( $posts as $k => $post ) {
+ if ( ! in_array( $post['post_type'], $types ) ) {
+ unset( $posts[ $k ] );
+ }
+ }
+ }
+
+ if ( ! $posts ) {
+ $posts = $this->get_fallback_posts();
+ }
+
+ echo $args['before_widget'];
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . $title . $args['after_title'];
+ }
+
+ if ( ! $posts ) {
+ $link = 'https://jetpack.com/support/getting-more-views-and-traffic/';
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $link = 'http://en.support.wordpress.com/getting-more-site-traffic/';
+ }
+
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ echo '<p>' . sprintf(
+ __( 'There are no posts to display. <a href="%s" target="_blank">Want more traffic?</a>', 'jetpack' ),
+ esc_url( $link )
+ ) . '</p>';
+ }
+
+ echo $args['after_widget'];
+ return;
+ }
+
+ /**
+ * Filter the layout of the Top Posts Widget
+ *
+ * @module widgets
+ *
+ * @since 6.4.0
+ *
+ * @param string $layout layout of the Top Posts Widget (empty string)
+ * @param array $posts IDs of the posts to be displayed
+ * @param array $display Display option from widget form
+ */
+ $layout = apply_filters( 'jetpack_top_posts_widget_layout', '', $posts, $display );
+ if ( ! empty( $layout ) ) {
+ echo $layout;
+ echo $args['after_widget'];
+ return;
+ }
+
+ switch ( $display ) {
+ case 'list':
+ case 'grid':
+ // Keep the avatar_size as default dimensions for backward compatibility.
+ $width = (int) $get_image_options['avatar_size'];
+ $height = (int) $get_image_options['avatar_size'];
+
+ // Check if the user has changed the width.
+ if ( ! empty( $get_image_options['width'] ) ) {
+ $width = (int) $get_image_options['width'];
+ }
+
+ // Check if the user has changed the height.
+ if ( ! empty( $get_image_options['height'] ) ) {
+ $height = (int) $get_image_options['height'];
+ }
+
+ foreach ( $posts as &$post ) {
+ $image = Jetpack_PostImages::get_image(
+ $post['post_id'],
+ array(
+ 'fallback_to_avatars' => true,
+ 'width' => (int) $width,
+ 'height' => (int) $height,
+ 'avatar_size' => (int) $get_image_options['avatar_size'],
+ )
+ );
+ $post['image'] = $image['src'];
+ if ( 'blavatar' != $image['from'] && 'gravatar' != $image['from'] ) {
+ $post['image'] = jetpack_photon_url( $post['image'], array( 'resize' => "$width,$height" ) );
+ }
+ }
+
+ unset( $post );
+
+ if ( 'grid' == $display ) {
+ echo "<div class='widgets-grid-layout no-grav'>\n";
+ foreach ( $posts as $post ) :
+ ?>
+ <div class="widget-grid-view-image">
+ <?php
+ /**
+ * Fires before each Top Post result, inside <li>.
+ *
+ * @module widgets
+ *
+ * @since 3.2.0
+ *
+ * @param string $post['post_id'] Post ID.
+ */
+ do_action( 'jetpack_widget_top_posts_before_post', $post['post_id'] );
+
+ /**
+ * Filter the permalink of items in the Top Posts widget.
+ *
+ * @module widgets
+ *
+ * @since 4.4.0
+ *
+ * @param string $post['permalink'] Post permalink.
+ * @param array $post Post array.
+ */
+ $filtered_permalink = apply_filters( 'jetpack_top_posts_widget_permalink', $post['permalink'], $post );
+
+ ?>
+ <a href="<?php echo esc_url( $filtered_permalink ); ?>" title="<?php echo esc_attr( wp_kses( $post['title'], array() ) ); ?>" class="bump-view" data-bump-view="tp">
+ <img width="<?php echo absint( $width ); ?>" height="<?php echo absint( $height ); ?>" src="<?php echo esc_url( $post['image'] ); ?>" alt="<?php echo esc_attr( wp_kses( $post['title'], array() ) ); ?>" data-pin-nopin="true" />
+ </a>
+ <?php
+ /**
+ * Fires after each Top Post result, inside <li>.
+ *
+ * @module widgets
+ *
+ * @since 3.2.0
+ *
+ * @param string $post['post_id'] Post ID.
+ */
+ do_action( 'jetpack_widget_top_posts_after_post', $post['post_id'] );
+ ?>
+ </div>
+ <?php
+ endforeach;
+ echo "</div>\n";
+ } else {
+ echo "<ul class='widgets-list-layout no-grav'>\n";
+ foreach ( $posts as $post ) :
+ ?>
+ <li>
+ <?php
+ /** This action is documented in modules/widgets/top-posts.php */
+ do_action( 'jetpack_widget_top_posts_before_post', $post['post_id'] );
+
+ /** This filter is documented in modules/widgets/top-posts.php */
+ $filtered_permalink = apply_filters( 'jetpack_top_posts_widget_permalink', $post['permalink'], $post );
+ ?>
+ <a href="<?php echo esc_url( $filtered_permalink ); ?>" title="<?php echo esc_attr( wp_kses( $post['title'], array() ) ); ?>" class="bump-view" data-bump-view="tp">
+ <img width="<?php echo absint( $width ); ?>" height="<?php echo absint( $height ); ?>" src="<?php echo esc_url( $post['image'] ); ?>" class='widgets-list-layout-blavatar' alt="<?php echo esc_attr( wp_kses( $post['title'], array() ) ); ?>" data-pin-nopin="true" />
+ </a>
+ <div class="widgets-list-layout-links">
+ <a href="<?php echo esc_url( $filtered_permalink ); ?>" class="bump-view" data-bump-view="tp">
+ <?php echo esc_html( wp_kses( $post['title'], array() ) ); ?>
+ </a>
+ </div>
+ <?php
+ /** This action is documented in modules/widgets/top-posts.php */
+ do_action( 'jetpack_widget_top_posts_after_post', $post['post_id'] );
+ ?>
+ </li>
+ <?php
+ endforeach;
+ echo "</ul>\n";
+ }
+ break;
+ default:
+ echo '<ul>';
+ foreach ( $posts as $post ) :
+ ?>
+ <li>
+ <?php
+ /** This action is documented in modules/widgets/top-posts.php */
+ do_action( 'jetpack_widget_top_posts_before_post', $post['post_id'] );
+
+ /** This filter is documented in modules/widgets/top-posts.php */
+ $filtered_permalink = apply_filters( 'jetpack_top_posts_widget_permalink', $post['permalink'], $post );
+ ?>
+ <a href="<?php echo esc_url( $filtered_permalink ); ?>" class="bump-view" data-bump-view="tp">
+ <?php echo esc_html( wp_kses( $post['title'], array() ) ); ?>
+ </a>
+ <?php
+ /** This action is documented in modules/widgets/top-posts.php */
+ do_action( 'jetpack_widget_top_posts_after_post', $post['post_id'] );
+ ?>
+ </li>
+ <?php
+ endforeach;
+ echo '</ul>';
+ }
+
+ echo $args['after_widget'];
+ }
+
+ public static function defaults() {
+ return array(
+ 'title' => esc_html__( 'Top Posts &amp; Pages', 'jetpack' ),
+ 'count' => absint( 10 ),
+ 'types' => array( 'post', 'page' ),
+ 'ordering' => 'views',
+ 'display' => 'text',
+ );
+ }
+
+ /*
+ * Get most liked posts
+ *
+ * ONLY TO BE USED IN WPCOM
+ */
+ function get_by_likes( $count ) {
+ $post_likes = wpl_get_blogs_most_liked_posts();
+ if ( ! $post_likes ) {
+ return array();
+ }
+
+ return $this->get_posts( array_keys( $post_likes ), $count );
+ }
+
+ function get_by_views( $count, $args ) {
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ global $wpdb;
+
+ $post_views = wp_cache_get( "get_top_posts_$count", 'stats' );
+ if ( false === $post_views ) {
+ $post_views = array_shift( stats_get_daily_history( false, get_current_blog_id(), 'postviews', 'post_id', false, 2, '', $count * 2 + 10, true ) );
+ unset( $post_views[0] );
+ wp_cache_add( "get_top_posts_$count", $post_views, 'stats', 1200 );
+ }
+
+ return $this->get_posts( array_keys( $post_views ), $count );
+ }
+
+ /**
+ * Filter the number of days used to calculate Top Posts for the Top Posts widget.
+ * We do not recommend accessing more than 10 days of results at one.
+ * When more than 10 days of results are accessed at once, results should be cached via the WordPress transients API.
+ * Querying for -1 days will give results for an infinite number of days.
+ *
+ * @module widgets
+ *
+ * @since 3.9.3
+ *
+ * @param int 2 Number of days. Default is 2.
+ * @param array $args The widget arguments.
+ */
+ $days = (int) apply_filters( 'jetpack_top_posts_days', 2, $args );
+
+ /** Handling situations where the number of days makes no sense - allows for unlimited days where $days = -1 */
+ if ( 0 == $days || false == $days ) {
+ $days = 2;
+ }
+
+ $post_view_posts = stats_get_from_restapi( array(), 'top-posts?max=11&summarize=1&num=' . intval( $days ) );
+
+ if ( ! isset( $post_view_posts->summary ) || empty( $post_view_posts->summary->postviews ) ) {
+ return array();
+ }
+
+ $post_view_ids = array_filter( wp_list_pluck( $post_view_posts->summary->postviews, 'id' ) );
+
+ if ( ! $post_view_ids ) {
+ return array();
+ }
+
+ return $this->get_posts( $post_view_ids, $count );
+ }
+
+ function get_fallback_posts() {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ return array();
+ }
+
+ $post_query = new WP_Query;
+
+ $posts = $post_query->query(
+ array(
+ 'posts_per_page' => 1,
+ 'post_status' => 'publish',
+ 'post_type' => array( 'post', 'page' ),
+ 'no_found_rows' => true,
+ )
+ );
+
+ if ( ! $posts ) {
+ return array();
+ }
+
+ $post = array_pop( $posts );
+
+ return $this->get_posts( $post->ID, 1 );
+ }
+
+ function get_posts( $post_ids, $count ) {
+ $counter = 0;
+
+ $posts = array();
+ foreach ( (array) $post_ids as $post_id ) {
+ $post = get_post( $post_id );
+
+ if ( ! $post ) {
+ continue;
+ }
+
+ /**
+ * Attachment pages use the 'inherit' post status by default.
+ * To be able to remove attachment pages from private and password protect posts,
+ * we need to replace their post status by the parent post' status.
+ */
+ if ( 'inherit' == $post->post_status && 'attachment' == $post->post_type ) {
+ $post->post_status = get_post_status( $post_id );
+ }
+
+ // hide private and password protected posts
+ if ( 'publish' != $post->post_status || ! empty( $post->post_password ) ) {
+ continue;
+ }
+
+ // Both get HTML stripped etc on display
+ if ( empty( $post->post_title ) ) {
+ $title_source = $post->post_content;
+ $title = wp_html_excerpt( $title_source, 50 );
+ $title .= '&hellip;';
+ } else {
+ $title = $post->post_title;
+ }
+
+ $permalink = get_permalink( $post->ID );
+
+ $post_type = $post->post_type;
+
+ $posts[] = compact( 'title', 'permalink', 'post_id', 'post_type' );
+ $counter++;
+
+ if ( $counter == $count ) {
+ break; // only need to load and show x number of likes
+ }
+ }
+
+ /**
+ * Filter the Top Posts and Pages.
+ *
+ * @module widgets
+ *
+ * @since 3.0.0
+ *
+ * @param array $posts Array of the most popular posts.
+ * @param array $post_ids Array of Post IDs.
+ * @param string $count Number of Top Posts we want to display.
+ */
+ return apply_filters( 'jetpack_widget_get_top_posts', $posts, $post_ids, $count );
+ }
+}
+
+/**
+ * Create a shortcode to display the widget anywhere.
+ *
+ * @since 3.9.2
+ */
+function jetpack_do_top_posts_widget( $instance ) {
+ // Post Types can't be entered as an array in the shortcode parameters.
+ if ( isset( $instance['types'] ) && is_array( $instance['types'] ) ) {
+ $instance['types'] = implode( ',', $instance['types'] );
+ }
+
+ $instance = shortcode_atts(
+ Jetpack_Top_Posts_Widget::defaults(),
+ $instance,
+ 'jetpack_top_posts_widget'
+ );
+
+ // Add a class to allow styling
+ $args = array(
+ 'before_widget' => sprintf( '<div class="%s">', 'jetpack_top_posts_widget' ),
+ );
+
+ ob_start();
+ the_widget( 'Jetpack_Top_Posts_Widget', $instance, $args );
+ $output = ob_get_clean();
+
+ return $output;
+}
+add_shortcode( 'jetpack_top_posts_widget', 'jetpack_do_top_posts_widget' );
diff --git a/plugins/jetpack/modules/widgets/top-posts/style.css b/plugins/jetpack/modules/widgets/top-posts/style.css
new file mode 100644
index 00000000..ceeae12b
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/top-posts/style.css
@@ -0,0 +1,114 @@
+/*
+ * Top Posts Widget styles for Jetpack
+ */
+
+/* 2-Column Grid Layout */
+
+.widgets-grid-layout {
+ width: 100%;
+}
+
+.widgets-grid-layout:before,
+.widgets-grid-layout:after {
+ content: " ";
+ display: table;
+}
+
+.widgets-grid-layout:after {
+ clear: both;
+}
+
+.widget-grid-view-image {
+ float: left;
+ max-width: 50%;
+}
+
+.widget-grid-view-image a {
+ display: block;
+ margin: 0 2px 4px 0;
+}
+
+.widget-grid-view-image:nth-child(even) {
+ float: right;
+}
+
+.widget-grid-view-image:nth-child(even) a {
+ margin: 0 0 4px 2px;
+}
+
+.widgets-grid-layout .widget-grid-view-image img {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Multi-Column Grid Layout */
+
+.widgets-multi-column-grid ul {
+ overflow: hidden;
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
+}
+
+.widgets-multi-column-grid ul li {
+ background: none;
+ clear: none;
+ float: left;
+ margin: 0 -5px -3px 0;
+ padding: 0 8px 6px 0;
+ border: none;
+ list-style-type: none !important;
+}
+
+.widgets-multi-column-grid ul li a {
+ background: none;
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+
+.widgets-multi-column-grid .avatar {
+ vertical-align: middle;
+}
+
+/* List Layout */
+
+.widgets-list-layout {
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
+}
+
+.widgets-list-layout li:before,
+.widgets-list-layout li:after {
+ content:"";
+ display:table;
+}
+.widgets-list-layout li:after {
+ clear:both;
+}
+.widgets-list-layout li {
+ zoom:1;
+ margin-bottom: 1em;
+ list-style-type: none !important;
+}
+
+.widgets-list-layout .widgets-list-layout-blavatar {
+ float: left;
+ width: 21.276596%;
+ max-width: 40px;
+ height: auto;
+}
+
+.widgets-list-layout-links {
+ float: right;
+ width: 73.404255%;
+}
+
+.widgets-list-layout span {
+ opacity: 0.5;
+}
+
+.widgets-list-layout span:hover {
+ opacity: 0.8;
+}
diff --git a/plugins/jetpack/modules/widgets/twitter-timeline-admin.js b/plugins/jetpack/modules/widgets/twitter-timeline-admin.js
new file mode 100644
index 00000000..e6a65140
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/twitter-timeline-admin.js
@@ -0,0 +1,35 @@
+jQuery( function( $ ) {
+ function twitterWidgetTypeChanged( widgetTypeSelector ) {
+ var selectedType = $( widgetTypeSelector ).val();
+ $( widgetTypeSelector )
+ .closest( '.jetpack-twitter-timeline-widget-type-container' )
+ .next( '.jetpack-twitter-timeline-widget-id-container' )
+ .find( 'label' )
+ .css( 'display', function() {
+ var labelType = $( this ).data( 'widget-type' );
+ if ( selectedType === labelType ) {
+ return '';
+ } else {
+ return 'none';
+ }
+ } );
+ }
+
+ // We could either be in wp-admin/widgets.php or the Customizer.
+ var $container = $( '#customize-controls' );
+ if ( ! $container.length ) {
+ $container = $( '#wpbody' );
+ }
+
+ // Observe widget settings for 'change' events of the 'type' property for
+ // current and future Twitter timeline widgets.
+ $container.on( 'change', '.jetpack-twitter-timeline-widget-type', function() {
+ twitterWidgetTypeChanged( this );
+ } );
+
+ // Set the labels for currently existing widgets (including the "template"
+ // version that is copied when a new widget is added).
+ $container.find( '.jetpack-twitter-timeline-widget-type' ).each( function() {
+ twitterWidgetTypeChanged( this );
+ } );
+} );
diff --git a/plugins/jetpack/modules/widgets/twitter-timeline.php b/plugins/jetpack/modules/widgets/twitter-timeline.php
new file mode 100644
index 00000000..0f16e330
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/twitter-timeline.php
@@ -0,0 +1,503 @@
+<?php
+
+/*
+ * Based on Evolution Twitter Timeline
+ * (https://wordpress.org/extend/plugins/evolution-twitter-timeline/)
+ * For details on Twitter Timelines see:
+ * - https://twitter.com/settings/widgets
+ * - https://dev.twitter.com/docs/embedded-timelines
+ */
+
+/**
+ * Register the widget for use in Appearance -> Widgets
+ */
+add_action( 'widgets_init', 'jetpack_twitter_timeline_widget_init' );
+
+function jetpack_twitter_timeline_widget_init() {
+ register_widget( 'Jetpack_Twitter_Timeline_Widget' );
+}
+
+class Jetpack_Twitter_Timeline_Widget extends WP_Widget {
+ /**
+ * Register widget with WordPress.
+ */
+ public function __construct() {
+ parent::__construct(
+ 'twitter_timeline',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'Twitter Timeline', 'jetpack' ) ),
+ array(
+ 'classname' => 'widget_twitter_timeline',
+ 'description' => __( 'Display an official Twitter Embedded Timeline widget.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ if ( is_active_widget( false, false, $this->id_base ) || is_active_widget( false, false, 'monster' ) || is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+ }
+
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
+ }
+
+ /**
+ * Enqueue scripts.
+ */
+ public function enqueue_scripts() {
+ wp_enqueue_script( 'jetpack-twitter-timeline' );
+ }
+
+ /**
+ * Enqueue Twitter's widget library.
+ *
+ * @deprecated
+ */
+ public function library() {
+ _deprecated_function( __METHOD__, '4.0.0' );
+ wp_print_scripts( array( 'jetpack-twitter-timeline' ) );
+ }
+
+ /**
+ * Enqueue script to improve admin UI
+ */
+ public function admin_scripts( $hook ) {
+ // This is still 'widgets.php' when managing widgets via the Customizer.
+ if ( 'widgets.php' === $hook ) {
+ wp_enqueue_script(
+ 'twitter-timeline-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/twitter-timeline-admin.min.js',
+ 'modules/widgets/twitter-timeline-admin.js'
+ )
+ );
+ }
+ }
+
+ /**
+ * Front-end display of widget.
+ *
+ * @see WP_Widget::widget()
+ *
+ * @param array $args Widget arguments.
+ * @param array $instance Saved values from database.
+ */
+ public function widget( $args, $instance ) {
+ // Twitter deprecated `data-widget-id` on 2018-05-25,
+ // with cease support deadline on 2018-07-27.
+ if ( isset( $instance['type'] ) && 'widget-id' === $instance['type'] ) {
+ if ( current_user_can( 'edit_theme_options' ) ) {
+ echo $args['before_widget'];
+ echo $args['before_title'] . esc_html__( 'Twitter Timeline', 'jetpack' ) . $args['after_title'];
+ echo '<p>' . esc_html__( "The Twitter Timeline widget can't display tweets based on searches or hashtags. To display a simple list of tweets instead, change the Widget ID to a Twitter username. Otherwise, delete this widget.", 'jetpack' ) . '</p>';
+ echo '<p>' . esc_html__( '(Only administrators will see this message.)', 'jetpack' ) . '</p>';
+ echo $args['after_widget'];
+ }
+ return;
+ }
+
+ $instance['lang'] = substr( strtoupper( get_locale() ), 0, 2 );
+
+ echo $args['before_widget'];
+
+ $title = isset( $instance['title'] ) ? $instance['title'] : '';
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $title );
+ if ( ! empty( $title ) ) {
+ echo $args['before_title'] . $title . $args['after_title'];
+ }
+
+ if ( isset( $instance['type'] ) && 'widget-id' === $instance['type'] && current_user_can( 'edit_theme_options' ) ) {
+ echo '<p>' . esc_html__( 'As of July 27, 2018, the Twitter Timeline widget will no longer display tweets based on searches or hashtags. To display a simple list of tweets instead, change the Widget ID to a Twitter username.', 'jetpack' ) . '</p>';
+ echo '<p>' . esc_html__( '(Only administrators will see this message.)', 'jetpack' ) . '</p>';
+ }
+
+ // Start tag output
+ // This tag is transformed into the widget markup by Twitter's
+ // widgets.js code
+ echo '<a class="twitter-timeline"';
+
+ $data_attribs = array(
+ 'width',
+ 'height',
+ 'theme',
+ 'link-color',
+ 'border-color',
+ 'tweet-limit',
+ 'lang',
+ );
+ foreach ( $data_attribs as $att ) {
+ if ( ! empty( $instance[ $att ] ) && ! is_array( $instance[ $att ] ) ) {
+ echo ' data-' . esc_attr( $att ) . '="' . esc_attr( $instance[ $att ] ) . '"';
+ }
+ }
+
+ /** This filter is documented in modules/shortcodes/tweet.php */
+ $partner = apply_filters( 'jetpack_twitter_partner_id', 'jetpack' );
+ if ( ! empty( $partner ) ) {
+ echo ' data-partner="' . esc_attr( $partner ) . '"';
+ }
+
+ /**
+ * Allow the activation of Do Not Track for the Twitter Timeline Widget.
+ *
+ * @see https://developer.twitter.com/en/docs/twitter-for-websites/timelines/guides/parameter-reference.html
+ *
+ * @module widgets
+ *
+ * @since 6.9.0
+ *
+ * @param bool false Should the Twitter Timeline use the DNT attribute? Default to false.
+ */
+ $dnt = apply_filters( 'jetpack_twitter_timeline_default_dnt', false );
+ if ( true === $dnt ) {
+ echo ' data-dnt="true"';
+ }
+
+ if ( ! empty( $instance['chrome'] ) && is_array( $instance['chrome'] ) ) {
+ echo ' data-chrome="' . esc_attr( join( ' ', $instance['chrome'] ) ) . '"';
+ }
+
+ $type = ( isset( $instance['type'] ) ? $instance['type'] : '' );
+ $widget_id = ( isset( $instance['widget-id'] ) ? $instance['widget-id'] : '' );
+ switch ( $type ) {
+ case 'profile':
+ echo ' href="https://twitter.com/' . esc_attr( $widget_id ) . '"';
+ break;
+ case 'widget-id':
+ default:
+ echo ' data-widget-id="' . esc_attr( $widget_id ) . '"';
+ break;
+ }
+ echo ' href="https://twitter.com/' . esc_attr( $widget_id ) . '"';
+
+ // End tag output
+ echo '>';
+
+ $timeline_placeholder = __( 'My Tweets', 'jetpack' );
+
+ /**
+ * Filter the Timeline placeholder text.
+ *
+ * @module widgets
+ *
+ * @since 3.4.0
+ *
+ * @param string $timeline_placeholder Timeline placeholder text.
+ */
+ $timeline_placeholder = apply_filters( 'jetpack_twitter_timeline_placeholder', $timeline_placeholder );
+
+ echo esc_html( $timeline_placeholder ) . '</a>';
+
+ // End tag output
+
+ echo $args['after_widget'];
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'twitter_timeline' );
+ }
+
+
+ /**
+ * Sanitize widget form values as they are saved.
+ *
+ * @see WP_Widget::update()
+ *
+ * @param array $new_instance Values just sent to be saved.
+ * @param array $old_instance Previously saved values from database.
+ *
+ * @return array Updated safe values to be saved.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $instance = array();
+
+ $instance['title'] = sanitize_text_field( $new_instance['title'] );
+
+ $width = (int) $new_instance['width'];
+ if ( $width ) {
+ // From publish.twitter.com: 220 <= width <= 1200
+ $instance['width'] = min( max( $width, 220 ), 1200 );
+ } else {
+ $instance['width'] = '';
+ }
+
+ $height = (int) $new_instance['height'];
+ if ( $height ) {
+ // From publish.twitter.com: height >= 200
+ $instance['height'] = max( $height, 200 );
+ } else {
+ $instance['height'] = '';
+ }
+
+ $tweet_limit = (int) $new_instance['tweet-limit'];
+ if ( $tweet_limit ) {
+ $instance['tweet-limit'] = min( max( $tweet_limit, 1 ), 20 );
+ /**
+ * A timeline with a specified limit is expanded to the height of those Tweets.
+ * The specified height value no longer applies, so reject the height value
+ * when a valid limit is set: a widget attempting to save both limit 5 and
+ * height 400 would be saved with just limit 5.
+ */
+ $instance['height'] = '';
+ } else {
+ $instance['tweet-limit'] = null;
+ }
+
+ // If they entered something that might be a full URL, try to parse it out
+ if ( is_string( $new_instance['widget-id'] ) ) {
+ if ( preg_match(
+ '#https?://twitter\.com/settings/widgets/(\d+)#s',
+ $new_instance['widget-id'],
+ $matches
+ ) ) {
+ $new_instance['widget-id'] = $matches[1];
+ }
+ }
+
+ $instance['widget-id'] = sanitize_text_field( $new_instance['widget-id'] );
+
+ $hex_regex = '/#([a-f]|[A-F]|[0-9]){3}(([a-f]|[A-F]|[0-9]){3})?\b/';
+ foreach ( array( 'link-color', 'border-color' ) as $color ) {
+ $new_color = sanitize_text_field( $new_instance[ $color ] );
+ if ( preg_match( $hex_regex, $new_color ) ) {
+ $instance[ $color ] = $new_color;
+ }
+ }
+
+ $instance['type'] = 'profile';
+
+ $instance['theme'] = 'light';
+ if ( in_array( $new_instance['theme'], array( 'light', 'dark' ) ) ) {
+ $instance['theme'] = $new_instance['theme'];
+ }
+
+ $instance['chrome'] = array();
+ $chrome_settings = array(
+ 'noheader',
+ 'nofooter',
+ 'noborders',
+ 'transparent',
+ 'noscrollbar',
+ );
+ if ( isset( $new_instance['chrome'] ) ) {
+ foreach ( $new_instance['chrome'] as $chrome ) {
+ if ( in_array( $chrome, $chrome_settings ) ) {
+ $instance['chrome'][] = $chrome;
+ }
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Returns a link to the documentation for a feature of this widget on
+ * Jetpack or WordPress.com.
+ */
+ public function get_docs_link( $hash = '' ) {
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $base_url = 'https://support.wordpress.com/widgets/twitter-timeline-widget/';
+ } else {
+ $base_url = 'https://jetpack.com/support/extra-sidebar-widgets/twitter-timeline-widget/';
+ }
+ return '<a href="' . $base_url . $hash . '" target="_blank">( ? )</a>';
+ }
+
+ /**
+ * Back end widget form.
+ *
+ * @see WP_Widget::form()
+ *
+ * @param array $instance Previously saved values from database.
+ */
+ public function form( $instance ) {
+ $defaults = array(
+ 'title' => esc_html__( 'Follow me on Twitter', 'jetpack' ),
+ 'width' => '',
+ 'height' => '400',
+ 'type' => 'profile',
+ 'widget-id' => '',
+ 'link-color' => '#f96e5b',
+ 'border-color' => '#e8e8e8',
+ 'theme' => 'light',
+ 'chrome' => array(),
+ 'tweet-limit' => null,
+ );
+
+ $instance = wp_parse_args( (array) $instance, $defaults );
+
+ if ( 'widget-id' === $instance['type'] ) {
+ $instance['widget-id'] = '';
+ }
+
+ $instance['type'] = 'profile';
+ ?>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>">
+ <?php esc_html_e( 'Title:', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo $this->get_field_id( 'title' ); ?>"
+ name="<?php echo $this->get_field_name( 'title' ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['title'] ); ?>"
+ />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'width' ); ?>">
+ <?php esc_html_e( 'Maximum Width (px; 220 to 1200):', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo $this->get_field_id( 'width' ); ?>"
+ name="<?php echo $this->get_field_name( 'width' ); ?>"
+ type="number" min="220" max="1200"
+ value="<?php echo esc_attr( $instance['width'] ); ?>"
+ />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'height' ); ?>">
+ <?php esc_html_e( 'Height (px; at least 200):', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo $this->get_field_id( 'height' ); ?>"
+ name="<?php echo $this->get_field_name( 'height' ); ?>"
+ type="number" min="200"
+ value="<?php echo esc_attr( $instance['height'] ); ?>"
+ />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'tweet-limit' ); ?>">
+ <?php esc_html_e( '# of Tweets Shown (1 to 20):', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo $this->get_field_id( 'tweet-limit' ); ?>"
+ name="<?php echo $this->get_field_name( 'tweet-limit' ); ?>"
+ type="number" min="1" max="20"
+ value="<?php echo esc_attr( $instance['tweet-limit'] ); ?>"
+ />
+ </p>
+
+ <p class="jetpack-twitter-timeline-widget-id-container">
+ <label for="<?php echo $this->get_field_id( 'widget-id' ); ?>">
+ <?php esc_html_e( 'Twitter Username:', 'jetpack' ); ?>
+ <?php echo $this->get_docs_link( '#twitter-username' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo $this->get_field_id( 'widget-id' ); ?>"
+ name="<?php echo $this->get_field_name( 'widget-id' ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['widget-id'] ); ?>"
+ />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'chrome-noheader' ); ?>">
+ <?php esc_html_e( 'Layout Options:', 'jetpack' ); ?>
+ </label>
+ <br />
+ <input
+ type="checkbox"<?php checked( in_array( 'noheader', $instance['chrome'] ) ); ?>
+ id="<?php echo $this->get_field_id( 'chrome-noheader' ); ?>"
+ name="<?php echo $this->get_field_name( 'chrome' ); ?>[]"
+ value="noheader"
+ />
+ <label for="<?php echo $this->get_field_id( 'chrome-noheader' ); ?>">
+ <?php esc_html_e( 'No Header', 'jetpack' ); ?>
+ </label>
+ <br />
+ <input
+ type="checkbox"<?php checked( in_array( 'nofooter', $instance['chrome'] ) ); ?>
+ id="<?php echo $this->get_field_id( 'chrome-nofooter' ); ?>"
+ name="<?php echo $this->get_field_name( 'chrome' ); ?>[]"
+ value="nofooter"
+ />
+ <label for="<?php echo $this->get_field_id( 'chrome-nofooter' ); ?>">
+ <?php esc_html_e( 'No Footer', 'jetpack' ); ?>
+ </label>
+ <br />
+ <input
+ type="checkbox"<?php checked( in_array( 'noborders', $instance['chrome'] ) ); ?>
+ id="<?php echo $this->get_field_id( 'chrome-noborders' ); ?>"
+ name="<?php echo $this->get_field_name( 'chrome' ); ?>[]"
+ value="noborders"
+ />
+ <label for="<?php echo $this->get_field_id( 'chrome-noborders' ); ?>">
+ <?php esc_html_e( 'No Borders', 'jetpack' ); ?>
+ </label>
+ <br />
+ <input
+ type="checkbox"<?php checked( in_array( 'noscrollbar', $instance['chrome'] ) ); ?>
+ id="<?php echo $this->get_field_id( 'chrome-noscrollbar' ); ?>"
+ name="<?php echo $this->get_field_name( 'chrome' ); ?>[]"
+ value="noscrollbar"
+ />
+ <label for="<?php echo $this->get_field_id( 'chrome-noscrollbar' ); ?>">
+ <?php esc_html_e( 'No Scrollbar', 'jetpack' ); ?>
+ </label>
+ <br />
+ <input
+ type="checkbox"<?php checked( in_array( 'transparent', $instance['chrome'] ) ); ?>
+ id="<?php echo $this->get_field_id( 'chrome-transparent' ); ?>"
+ name="<?php echo $this->get_field_name( 'chrome' ); ?>[]"
+ value="transparent"
+ />
+ <label for="<?php echo $this->get_field_id( 'chrome-transparent' ); ?>">
+ <?php esc_html_e( 'Transparent Background', 'jetpack' ); ?>
+ </label>
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'link-color' ); ?>">
+ <?php _e( 'Link Color (hex):', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo $this->get_field_id( 'link-color' ); ?>"
+ name="<?php echo $this->get_field_name( 'link-color' ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['link-color'] ); ?>"
+ />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'border-color' ); ?>">
+ <?php _e( 'Border Color (hex):', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo $this->get_field_id( 'border-color' ); ?>"
+ name="<?php echo $this->get_field_name( 'border-color' ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $instance['border-color'] ); ?>"
+ />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'theme' ); ?>">
+ <?php _e( 'Timeline Theme:', 'jetpack' ); ?>
+ </label>
+ <select
+ name="<?php echo $this->get_field_name( 'theme' ); ?>"
+ id="<?php echo $this->get_field_id( 'theme' ); ?>"
+ class="widefat"
+ >
+ <option value="light"<?php selected( $instance['theme'], 'light' ); ?>>
+ <?php esc_html_e( 'Light', 'jetpack' ); ?>
+ </option>
+ <option value="dark"<?php selected( $instance['theme'], 'dark' ); ?>>
+ <?php esc_html_e( 'Dark', 'jetpack' ); ?>
+ </option>
+ </select>
+ </p>
+ <?php
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/upcoming-events.php b/plugins/jetpack/modules/widgets/upcoming-events.php
new file mode 100644
index 00000000..36a0257e
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/upcoming-events.php
@@ -0,0 +1,131 @@
+<?php
+
+class Jetpack_Upcoming_Events_Widget extends WP_Widget {
+ function __construct() {
+ parent::__construct(
+ 'upcoming_events_widget',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Upcoming Events', 'jetpack' ) ),
+ array(
+ 'description' => __( 'Display upcoming events from an iCalendar feed.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+ if ( is_active_widget( false, false, $this->id_base ) ) {
+ add_action( 'wp_head', array( $this, 'css' ) );
+ }
+ }
+
+ function css() {
+?>
+<style type="text/css">
+.upcoming-events li {
+ margin-bottom: 10px;
+}
+.upcoming-events li span {
+ display: block;
+}
+</style>
+<?php
+ }
+
+ function form( $instance ) {
+ $defaults = array(
+ 'title' => __( 'Upcoming Events', 'jetpack' ),
+ 'feed-url' => '',
+ 'count' => 3,
+ );
+ $instance = array_merge( $defaults, (array) $instance );
+?>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'feed-url' ); ?>"><?php _e( 'iCalendar Feed URL:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'feed-url' ); ?>" name="<?php echo $this->get_field_name( 'feed-url' ); ?>" type="text" value="<?php echo esc_attr( $instance['feed-url'] ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'count' ); ?>"><?php _e( 'Items to show:', 'jetpack' ); ?></label>
+ <select id="<?php echo $this->get_field_id( 'count' ); ?>" name="<?php echo $this->get_field_name( 'count' ); ?>">
+ <?php
+ $i = 1;
+ while ( $i <= 10 ) {
+ ?>
+ <option <?php selected( $instance['count'], $i ); ?>><?php echo $i; ?></option>
+ <?php $i++; } ?>
+ <option value="0" <?php selected( $instance['count'], 0 ); ?>><?php _e( 'All', 'jetpack' ); ?></option>
+ </select>
+ </p>
+<?php
+ }
+
+ function update( $new_instance, $old_instance ) {
+ $instance['title'] = strip_tags( $new_instance['title'] );
+ $instance['feed-url'] = strip_tags( $new_instance['feed-url'] );
+ $instance['count'] = min( absint( $new_instance['count'] ), 10 ); // 10 or less
+ return $instance;
+ }
+
+ function widget( $args, $instance ) {
+ jetpack_require_lib( 'icalendar-reader' );
+
+ $ical = new iCalendarReader();
+ $events = $ical->get_events( $instance['feed-url'], $instance['count'] );
+ $events = $this->apply_timezone_offset( $events );
+ $ical->timezone = null;
+
+ echo $args['before_widget'];
+ if ( ! empty( $instance['title'] ) ) {
+ echo $args['before_title'];
+ echo esc_html( $instance['title'] );
+ echo $args['after_title'];
+ }
+
+ if ( ! $events ) : // nothing to display?
+?>
+ <p><?php echo __( 'No upcoming events', 'jetpack' ); ?></p>
+<?php
+ else :
+?>
+ <ul class="upcoming-events">
+ <?php foreach ( $events as $event ) : ?>
+ <li>
+ <strong class="event-summary"><?php echo $ical->escape( stripslashes( $event['SUMMARY'] ) ); ?></strong>
+ <span class="event-when"><?php echo $ical->formatted_date( $event ); ?></span>
+ <?php if ( ! empty( $event['LOCATION'] ) ) : ?>
+ <span class="event-location"><?php echo $ical->escape( stripslashes( $event['LOCATION'] ) ); ?></span>
+ <?php endif; ?>
+ <?php if ( ! empty( $event['DESCRIPTION'] ) ) : ?>
+ <span class="event-description"><?php echo wp_trim_words( $ical->escape( stripcslashes( $event['DESCRIPTION'] ) ) ); ?></span>
+ <?php endif; ?>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+<?php
+ endif;
+
+ echo $args['after_widget'];
+
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'grofile' );
+ }
+
+ // Left this function here for backward compatibility
+ // just incase a site using jetpack is also using this function
+ function apply_timezone_offset( $events ) {
+ jetpack_require_lib( 'icalendar-reader' );
+
+ $ical = new iCalendarReader();
+ return $ical->apply_timezone_offset( $events );
+ }
+}
+
+function upcoming_events_register_widgets() {
+ register_widget( 'Jetpack_Upcoming_Events_Widget' );
+}
+
+add_action( 'widgets_init', 'upcoming_events_register_widgets' );
diff --git a/plugins/jetpack/modules/widgets/wordpress-post-widget.php b/plugins/jetpack/modules/widgets/wordpress-post-widget.php
new file mode 100644
index 00000000..f518ad61
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/wordpress-post-widget.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Plugin Name: Display Recent WordPress Posts Widget
+ * Description: Displays recent posts from a WordPress.com or Jetpack-enabled self-hosted WordPress site.
+ * Version: 1.0
+ * Author: Brad Angelcyk, Kathryn Presner, Justin Shreve, Carolyn Sonnek
+ * Author URI: http://automattic.com
+ * License: GPL2
+ */
+
+/**
+ * Disable direct access/execution to/of the widget code.
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+require dirname( __FILE__ ) . '/wordpress-post-widget/class.jetpack-display-posts-widget-base.php';
+require dirname( __FILE__ ) . '/wordpress-post-widget/class.jetpack-display-posts-widget.php';
+
+add_action( 'widgets_init', 'jetpack_display_posts_widget' );
+function jetpack_display_posts_widget() {
+ register_widget( 'Jetpack_Display_Posts_Widget' );
+}
+
+
+/**
+ * Cron tasks
+ */
+
+add_filter( 'cron_schedules', 'jetpack_display_posts_widget_cron_intervals' );
+
+/**
+ * Adds 10 minute running interval to the cron schedules.
+ *
+ * @param array $current_schedules Currently defined schedules list.
+ *
+ * @return array
+ */
+function jetpack_display_posts_widget_cron_intervals( $current_schedules ) {
+
+ /**
+ * Only add the 10 minute interval if it wasn't already set.
+ */
+ if ( ! isset( $current_schedules['minutes_10'] ) ) {
+ $current_schedules['minutes_10'] = array(
+ 'interval' => 10 * MINUTE_IN_SECONDS,
+ 'display' => 'Every 10 minutes',
+ );
+ }
+
+ return $current_schedules;
+}
+
+/**
+ * Execute the cron task
+ */
+add_action( 'jetpack_display_posts_widget_cron_update', 'jetpack_display_posts_update_cron_action' );
+function jetpack_display_posts_update_cron_action() {
+ $widget = new Jetpack_Display_Posts_Widget();
+ $widget->cron_task();
+}
+
+/**
+ * Handle activation procedures for the cron.
+ *
+ * `updating_jetpack_version` - Handle cron activation when Jetpack gets updated. It's here
+ * to cover the first cron activation after the update.
+ *
+ * `jetpack_activate_module_widgets` - Activate the cron when the Extra Sidebar widgets are activated.
+ *
+ * `activated_plugin` - Activate the cron when Jetpack gets activated.
+ *
+ */
+add_action( 'updating_jetpack_version', 'jetpack_display_posts_widget_conditionally_activate_cron' );
+add_action( 'jetpack_activate_module_widgets', 'Jetpack_Display_Posts_Widget::activate_cron' );
+add_action( 'activated_plugin', 'jetpack_conditionally_activate_cron_on_plugin_activation' );
+
+/**
+ * Executed when Jetpack gets activated. Tries to activate the cron if it is needed.
+ *
+ * @param string $plugin_file_name The plugin file that was activated.
+ */
+function jetpack_conditionally_activate_cron_on_plugin_activation( $plugin_file_name ) {
+ if ( plugin_basename( JETPACK__PLUGIN_FILE ) === $plugin_file_name ) {
+ jetpack_display_posts_widget_conditionally_activate_cron();
+ }
+}
+
+/**
+ * Activates the cron only when needed.
+ * @see Jetpack_Display_Posts_Widget::should_cron_be_running
+ */
+function jetpack_display_posts_widget_conditionally_activate_cron() {
+ $widget = new Jetpack_Display_Posts_Widget();
+ if ( $widget->should_cron_be_running() ) {
+ $widget->activate_cron();
+ }
+
+ unset( $widget );
+}
+
+/**
+ * End of cron activation handling.
+ */
+
+
+/**
+ * Handle deactivation procedures where they are needed.
+ *
+ * If Extra Sidebar Widgets module is deactivated, the cron is not needed.
+ *
+ * If Jetpack is deactivated, the cron is not needed.
+ */
+add_action( 'jetpack_deactivate_module_widgets', 'Jetpack_Display_Posts_Widget::deactivate_cron_static' );
+register_deactivation_hook( plugin_basename( JETPACK__PLUGIN_FILE ), 'Jetpack_Display_Posts_Widget::deactivate_cron_static' );
diff --git a/plugins/jetpack/modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget-base.php b/plugins/jetpack/modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget-base.php
new file mode 100644
index 00000000..8a59545a
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget-base.php
@@ -0,0 +1,843 @@
+<?php
+
+/*
+ * For back-compat, the final widget class must be named
+ * Jetpack_Display_Posts_Widget.
+ *
+ * For convenience, it's nice to have a widget class constructor with no
+ * arguments. Otherwise, we have to register the widget with an instance
+ * instead of a class name. This makes unregistering annoying.
+ *
+ * Both WordPress.com and Jetpack implement the final widget class by
+ * extending this __Base class and adding data fetching and storage.
+ *
+ * This would be a bit cleaner with dependency injection, but we already
+ * use mocking to test, so it's not a big win.
+ *
+ * That this widget is currently implemented as these two classes
+ * is an implementation detail and should not be depended on :)
+ */
+abstract class Jetpack_Display_Posts_Widget__Base extends WP_Widget {
+ /**
+ * @var string Remote service API URL prefix.
+ */
+ public $service_url = 'https://public-api.wordpress.com/rest/v1.1/';
+
+ public function __construct() {
+ parent::__construct(
+ // internal id
+ 'jetpack_display_posts_widget',
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', __( 'Display WordPress Posts', 'jetpack' ) ),
+ array(
+ 'description' => __( 'Displays a list of recent posts from another WordPress.com or Jetpack-enabled blog.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ if ( is_customize_preview() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+ }
+ }
+
+ /**
+ * Enqueue CSS and JavaScript.
+ *
+ * @since 4.0.0
+ */
+ public function enqueue_scripts() {
+ wp_enqueue_style( 'jetpack_display_posts_widget', plugins_url( 'style.css', __FILE__ ) );
+ }
+
+
+ // DATA STORE: Must implement
+
+ /**
+ * Gets blog data from the cache.
+ *
+ * @param string $site
+ *
+ * @return array|WP_Error
+ */
+ abstract public function get_blog_data( $site );
+
+ /**
+ * Update a widget instance.
+ *
+ * @param string $site The site to fetch the latest data for.
+ *
+ * @return array - the new data
+ */
+ abstract public function update_instance( $site );
+
+
+ // WIDGET API
+
+ /**
+ * Set up the widget display on the front end.
+ *
+ * @param array $args
+ * @param array $instance
+ */
+ public function widget( $args, $instance ) {
+ /** This action is documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'widget_view', 'display_posts' );
+
+ // Enqueue front end assets.
+ $this->enqueue_scripts();
+
+ $content = $args['before_widget'];
+
+ if ( empty( $instance['url'] ) ) {
+ if ( current_user_can( 'manage_options' ) ) {
+ $content .= '<p>';
+ /* Translators: the "Blog URL" field mentioned is the input field labeled as such in the widget form. */
+ $content .= esc_html__( 'The Blog URL is not properly setup in the widget.', 'jetpack' );
+ $content .= '</p>';
+ }
+ $content .= $args['after_widget'];
+
+ echo $content;
+ return;
+ }
+
+ $data = $this->get_blog_data( $instance['url'] );
+ // check for errors
+ if ( is_wp_error( $data ) || empty( $data['site_info']['data'] ) ) {
+ $content .= '<p>' . __( 'Cannot load blog information at this time.', 'jetpack' ) . '</p>';
+ $content .= $args['after_widget'];
+
+ echo $content;
+ return;
+ }
+
+ $site_info = $data['site_info']['data'];
+
+ if ( ! empty( $instance['title'] ) ) {
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $instance['title'] = apply_filters( 'widget_title', $instance['title'] );
+ $content .= $args['before_title'] . esc_html( $instance['title'] . ': ' . $site_info->name ) . $args['after_title'];
+ }
+ else {
+ $content .= $args['before_title'] . esc_html( $site_info->name ) . $args['after_title'];
+ }
+
+ $content .= '<div class="jetpack-display-remote-posts">';
+
+ if ( is_wp_error( $data['posts']['data'] ) || empty( $data['posts']['data'] ) ) {
+ $content .= '<p>' . __( 'Cannot load blog posts at this time.', 'jetpack' ) . '</p>';
+ $content .= '</div><!-- .jetpack-display-remote-posts -->';
+ $content .= $args['after_widget'];
+
+ echo $content;
+ return;
+ }
+
+ $posts_list = $data['posts']['data'];
+
+ /**
+ * Show only as much posts as we need. If we have less than configured amount,
+ * we must show only that much posts.
+ */
+ $number_of_posts = min( $instance['number_of_posts'], count( $posts_list ) );
+
+ for ( $i = 0; $i < $number_of_posts; $i ++ ) {
+ $single_post = $posts_list[ $i ];
+ $post_title = ( $single_post['title'] ) ? $single_post['title'] : '( No Title )';
+
+ $target = '';
+ if ( isset( $instance['open_in_new_window'] ) && $instance['open_in_new_window'] == true ) {
+ $target = ' target="_blank" rel="noopener"';
+ }
+ $content .= '<h4><a href="' . esc_url( $single_post['url'] ) . '"' . $target . '>' . esc_html( $post_title ) . '</a></h4>' . "\n";
+ if ( ( $instance['featured_image'] == true ) && ( ! empty ( $single_post['featured_image'] ) ) ) {
+ $featured_image = $single_post['featured_image'];
+ /**
+ * Allows setting up custom Photon parameters to manipulate the image output in the Display Posts widget.
+ *
+ * @see https://developer.wordpress.com/docs/photon/
+ *
+ * @module widgets
+ *
+ * @since 3.6.0
+ *
+ * @param array $args Array of Photon Parameters.
+ */
+ $image_params = apply_filters( 'jetpack_display_posts_widget_image_params', array() );
+ $content .= '<a title="' . esc_attr( $post_title ) . '" href="' . esc_url( $single_post['url'] ) . '"' . $target . '><img src="' . jetpack_photon_url( $featured_image, $image_params ) . '" alt="' . esc_attr( $post_title ) . '"/></a>';
+ }
+
+ if ( $instance['show_excerpts'] == true ) {
+ $content .= $single_post['excerpt'];
+ }
+ }
+
+ $content .= '</div><!-- .jetpack-display-remote-posts -->';
+ $content .= $args['after_widget'];
+
+ /**
+ * Filter the WordPress Posts widget content.
+ *
+ * @module widgets
+ *
+ * @since 4.7.0
+ *
+ * @param string $content Widget content.
+ */
+ echo apply_filters( 'jetpack_display_posts_widget_content', $content );
+ }
+
+ /**
+ * Display the widget administration form.
+ *
+ * @param array $instance Widget instance configuration.
+ *
+ * @return string|void
+ */
+ public function form( $instance ) {
+
+ /**
+ * Initialize widget configuration variables.
+ */
+ $title = ( isset( $instance['title'] ) ) ? $instance['title'] : __( 'Recent Posts', 'jetpack' );
+ $url = ( isset( $instance['url'] ) ) ? $instance['url'] : '';
+ $number_of_posts = ( isset( $instance['number_of_posts'] ) ) ? $instance['number_of_posts'] : 5;
+ $open_in_new_window = ( isset( $instance['open_in_new_window'] ) ) ? $instance['open_in_new_window'] : false;
+ $featured_image = ( isset( $instance['featured_image'] ) ) ? $instance['featured_image'] : false;
+ $show_excerpts = ( isset( $instance['show_excerpts'] ) ) ? $instance['show_excerpts'] : false;
+
+
+ /**
+ * Check if the widget instance has errors available.
+ *
+ * Only do so if a URL is set.
+ */
+ $update_errors = array();
+
+ if ( ! empty( $url ) ) {
+ $data = $this->get_blog_data( $url );
+ $update_errors = $this->extract_errors_from_blog_data( $data );
+ }
+
+ ?>
+ <p>
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
+ </p>
+
+ <p>
+ <label for="<?php echo $this->get_field_id( 'url' ); ?>"><?php _e( 'Blog URL:', 'jetpack' ); ?></label>
+ <input class="widefat" id="<?php echo $this->get_field_id( 'url' ); ?>" name="<?php echo $this->get_field_name( 'url' ); ?>" type="text" value="<?php echo esc_attr( $url ); ?>" />
+ <i>
+ <?php _e( "Enter a WordPress.com or Jetpack WordPress site URL.", 'jetpack' ); ?>
+ </i>
+ <?php
+ /**
+ * Show an error if the URL field was left empty.
+ *
+ * The error is shown only when the widget was already saved.
+ */
+ if ( empty( $url ) && ! preg_match( '/__i__|%i%/', $this->id ) ) {
+ ?>
+ <br />
+ <i class="error-message"><?php echo __( 'You must specify a valid blog URL!', 'jetpack' ); ?></i>
+ <?php
+ }
+ ?>
+ </p>
+ <p>
+ <label for="<?php echo $this->get_field_id( 'number_of_posts' ); ?>"><?php _e( 'Number of Posts to Display:', 'jetpack' ); ?></label>
+ <select name="<?php echo $this->get_field_name( 'number_of_posts' ); ?>">
+ <?php
+ for ( $i = 1; $i <= 10; $i ++ ) {
+ echo '<option value="' . $i . '" ' . selected( $number_of_posts, $i ) . '>' . $i . '</option>';
+ }
+ ?>
+ </select>
+ </p>
+ <p>
+ <label for="<?php echo $this->get_field_id( 'open_in_new_window' ); ?>"><?php _e( 'Open links in new window/tab:', 'jetpack' ); ?></label>
+ <input type="checkbox" name="<?php echo $this->get_field_name( 'open_in_new_window' ); ?>" <?php checked( $open_in_new_window, 1 ); ?> />
+ </p>
+ <p>
+ <label for="<?php echo $this->get_field_id( 'featured_image' ); ?>"><?php _e( 'Show Featured Image:', 'jetpack' ); ?></label>
+ <input type="checkbox" name="<?php echo $this->get_field_name( 'featured_image' ); ?>" <?php checked( $featured_image, 1 ); ?> />
+ </p>
+ <p>
+ <label for="<?php echo $this->get_field_id( 'show_excerpts' ); ?>"><?php _e( 'Show Excerpts:', 'jetpack' ); ?></label>
+ <input type="checkbox" name="<?php echo $this->get_field_name( 'show_excerpts' ); ?>" <?php checked( $show_excerpts, 1 ); ?> />
+ </p>
+
+ <?php
+
+ /**
+ * Show error messages.
+ */
+ if ( ! empty( $update_errors['message'] ) ) {
+
+ /**
+ * Prepare the error messages.
+ */
+
+ $where_message = '';
+ switch ( $update_errors['where'] ) {
+ case 'posts':
+ $where_message .= __( 'An error occurred while downloading blog posts list', 'jetpack' );
+ break;
+
+ /**
+ * If something else, beside `posts` and `site_info` broke,
+ * don't handle it and default to blog `information`,
+ * as it is generic enough.
+ */
+ case 'site_info':
+ default:
+ $where_message .= __( 'An error occurred while downloading blog information', 'jetpack' );
+ break;
+ }
+
+ ?>
+ <p class="error-message">
+ <?php echo esc_html( $where_message ); ?>:
+ <br />
+ <i>
+ <?php echo esc_html( $update_errors['message'] ); ?>
+ <?php
+ /**
+ * If there is any debug - show it here.
+ */
+ if ( ! empty( $update_errors['debug'] ) ) {
+ ?>
+ <br />
+ <br />
+ <?php esc_html_e( 'Detailed information', 'jetpack' ); ?>:
+ <br />
+ <?php echo esc_html( $update_errors['debug'] ); ?>
+ <?php
+ }
+ ?>
+ </i>
+ </p>
+
+ <?php
+ }
+ }
+
+ public function update( $new_instance, $old_instance ) {
+
+ $instance = array();
+ $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';
+ $instance['url'] = ( ! empty( $new_instance['url'] ) ) ? strip_tags( trim( $new_instance['url'] ) ) : '';
+ $instance['url'] = preg_replace( "!^https?://!is", "", $instance['url'] );
+ $instance['url'] = untrailingslashit( $instance['url'] );
+
+
+ /**
+ * Check if the URL should be with or without the www prefix before saving.
+ */
+ if ( ! empty( $instance['url'] ) ) {
+ $blog_data = $this->fetch_blog_data( $instance['url'], array(), true );
+
+ if ( is_wp_error( $blog_data['site_info']['error'] ) && 'www.' === substr( $instance['url'], 0, 4 ) ) {
+ $blog_data = $this->fetch_blog_data( substr( $instance['url'], 4 ), array(), true );
+
+ if ( ! is_wp_error( $blog_data['site_info']['error'] ) ) {
+ $instance['url'] = substr( $instance['url'], 4 );
+ }
+ }
+ }
+
+ $instance['number_of_posts'] = ( ! empty( $new_instance['number_of_posts'] ) ) ? intval( $new_instance['number_of_posts'] ) : '';
+ $instance['open_in_new_window'] = ( ! empty( $new_instance['open_in_new_window'] ) ) ? true : '';
+ $instance['featured_image'] = ( ! empty( $new_instance['featured_image'] ) ) ? true : '';
+ $instance['show_excerpts'] = ( ! empty( $new_instance['show_excerpts'] ) ) ? true : '';
+
+ /**
+ * If there is no cache entry for the specified URL, run a forced update.
+ *
+ * @see get_blog_data Returns WP_Error if the cache is empty, which is what is needed here.
+ */
+ $cached_data = $this->get_blog_data( $instance['url'] );
+
+ if ( is_wp_error( $cached_data ) ) {
+ $this->update_instance( $instance['url'] );
+ }
+
+ return $instance;
+ }
+
+
+ // DATA PROCESSING
+
+ /**
+ * Expiring transients have a name length maximum of 45 characters,
+ * so this function returns an abbreviated MD5 hash to use instead of
+ * the full URI.
+ *
+ * @param string $site Site to get the hash for.
+ *
+ * @return string
+ */
+ public function get_site_hash( $site ) {
+ return substr( md5( $site ), 0, 21 );
+ }
+
+ /**
+ * Fetch a remote service endpoint and parse it.
+ *
+ * Timeout is set to 15 seconds right now, because sometimes the WordPress API
+ * takes more than 5 seconds to fully respond.
+ *
+ * Caching is used here so we can avoid re-downloading the same endpoint
+ * in a single request.
+ *
+ * @param string $endpoint Parametrized endpoint to call.
+ *
+ * @param int $timeout How much time to wait for the API to respond before failing.
+ *
+ * @return array|WP_Error
+ */
+ public function fetch_service_endpoint( $endpoint, $timeout = 15 ) {
+
+ /**
+ * Holds endpoint request cache.
+ */
+ static $cache = array();
+
+ if ( ! isset( $cache[ $endpoint ] ) ) {
+ $raw_data = $this->wp_wp_remote_get( $this->service_url . ltrim( $endpoint, '/' ), array( 'timeout' => $timeout ) );
+ $cache[ $endpoint ] = $this->parse_service_response( $raw_data );
+ }
+
+ return $cache[ $endpoint ];
+ }
+
+ /**
+ * Parse data from service response.
+ * Do basic error handling for general service and data errors
+ *
+ * @param array $service_response Response from the service.
+ *
+ * @return array|WP_Error
+ */
+ public function parse_service_response( $service_response ) {
+ /**
+ * If there is an error, we add the error message to the parsed response
+ */
+ if ( is_wp_error( $service_response ) ) {
+ return new WP_Error(
+ 'general_error',
+ __( 'An error occurred fetching the remote data.', 'jetpack' ),
+ $service_response->get_error_messages()
+ );
+ }
+
+ /**
+ * Validate HTTP response code.
+ */
+ if ( 200 !== wp_remote_retrieve_response_code( $service_response ) ) {
+ return new WP_Error(
+ 'http_error',
+ __( 'An error occurred fetching the remote data.', 'jetpack' ),
+ wp_remote_retrieve_response_message( $service_response )
+ );
+ }
+
+
+ /**
+ * Extract service response body from the request.
+ */
+
+ $service_response_body = wp_remote_retrieve_body( $service_response );
+
+
+ /**
+ * No body has been set in the response. This should be pretty bad.
+ */
+ if ( ! $service_response_body ) {
+ return new WP_Error(
+ 'no_body',
+ __( 'Invalid remote response.', 'jetpack' ),
+ 'No body in response.'
+ );
+ }
+
+ /**
+ * Parse the JSON response from the API. Convert to associative array.
+ */
+ $parsed_data = json_decode( $service_response_body );
+
+ /**
+ * If there is a problem with parsing the posts return an empty array.
+ */
+ if ( is_null( $parsed_data ) ) {
+ return new WP_Error(
+ 'no_body',
+ __( 'Invalid remote response.', 'jetpack' ),
+ 'Invalid JSON from remote.'
+ );
+ }
+
+ /**
+ * Check for errors in the parsed body.
+ */
+ if ( isset( $parsed_data->error ) ) {
+ return new WP_Error(
+ 'remote_error',
+ __( 'It looks like the WordPress site URL is incorrectly configured. Please check it in your widget settings.', 'jetpack' ),
+ $parsed_data->error
+ );
+ }
+
+ /**
+ * No errors found, return parsed data.
+ */
+ return $parsed_data;
+ }
+
+ /**
+ * Fetch site information from the WordPress public API
+ *
+ * @param string $site URL of the site to fetch the information for.
+ *
+ * @return array|WP_Error
+ */
+ public function fetch_site_info( $site ) {
+
+ $response = $this->fetch_service_endpoint( sprintf( '/sites/%s', urlencode( $site ) ) );
+
+ return $response;
+ }
+
+ /**
+ * Parse external API response from the site info call and handle errors if they occur.
+ *
+ * @param array|WP_Error $service_response The raw response to be parsed.
+ *
+ * @return array|WP_Error
+ */
+ public function parse_site_info_response( $service_response ) {
+
+ /**
+ * If the service returned an error, we pass it on.
+ */
+ if ( is_wp_error( $service_response ) ) {
+ return $service_response;
+ }
+
+ /**
+ * Check if the service returned proper site information.
+ */
+ if ( ! isset( $service_response->ID ) ) {
+ return new WP_Error(
+ 'no_site_info',
+ __( 'Invalid site information returned from remote.', 'jetpack' ),
+ 'No site ID present in the response.'
+ );
+ }
+
+ return $service_response;
+ }
+
+ /**
+ * Fetch list of posts from the WordPress public API.
+ *
+ * @param int $site_id The site to fetch the posts for.
+ *
+ * @return array|WP_Error
+ */
+ public function fetch_posts_for_site( $site_id ) {
+
+ $response = $this->fetch_service_endpoint(
+ sprintf(
+ '/sites/%1$d/posts/%2$s',
+ $site_id,
+ /**
+ * Filters the parameters used to fetch for posts in the Display Posts Widget.
+ *
+ * @see https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/
+ *
+ * @module widgets
+ *
+ * @since 3.6.0
+ *
+ * @param string $args Extra parameters to filter posts returned from the WordPress.com REST API.
+ */
+ apply_filters( 'jetpack_display_posts_widget_posts_params', '' )
+ )
+ );
+
+ return $response;
+ }
+
+ /**
+ * Parse external API response from the posts list request and handle errors if any occur.
+ *
+ * @param object|WP_Error $service_response The raw response to be parsed.
+ *
+ * @return array|WP_Error
+ */
+ public function parse_posts_response( $service_response ) {
+
+ /**
+ * If the service returned an error, we pass it on.
+ */
+ if ( is_wp_error( $service_response ) ) {
+ return $service_response;
+ }
+
+ /**
+ * Check if the service returned proper posts array.
+ */
+ if ( ! isset( $service_response->posts ) || ! is_array( $service_response->posts ) ) {
+ return new WP_Error(
+ 'no_posts',
+ __( 'No posts data returned by remote.', 'jetpack' ),
+ 'No posts information set in the returned data.'
+ );
+ }
+
+ /**
+ * Format the posts to preserve storage space.
+ */
+
+ return $this->format_posts_for_storage( $service_response );
+ }
+
+ /**
+ * Format the posts for better storage. Drop all the data that is not used.
+ *
+ * @param object $parsed_data Array of posts returned by the APIs.
+ *
+ * @return array Formatted posts or an empty array if no posts were found.
+ */
+ public function format_posts_for_storage( $parsed_data ) {
+
+ $formatted_posts = array();
+
+ /**
+ * Only go through the posts list if we have valid posts array.
+ */
+ if ( isset( $parsed_data->posts ) && is_array( $parsed_data->posts ) ) {
+
+ /**
+ * Loop through all the posts and format them appropriately.
+ */
+ foreach ( $parsed_data->posts as $single_post ) {
+
+ $prepared_post = array(
+ 'title' => $single_post->title ? $single_post->title : '',
+ 'excerpt' => $single_post->excerpt ? $single_post->excerpt : '',
+ 'featured_image' => $single_post->featured_image ? $single_post->featured_image : '',
+ 'url' => $single_post->URL,
+ );
+
+ /**
+ * Append the formatted post to the results.
+ */
+ $formatted_posts[] = $prepared_post;
+ }
+ }
+
+ return $formatted_posts;
+ }
+
+ /**
+ * Fetch site information and posts list for a site.
+ *
+ * @param string $site Site to fetch the data for.
+ * @param array $original_data Optional original data to updated.
+ *
+ * @param bool $site_data_only Fetch only site information, skip posts list.
+ *
+ * @return array Updated or new data.
+ */
+ public function fetch_blog_data( $site, $original_data = array(), $site_data_only = false ) {
+
+ /**
+ * If no optional data is supplied, initialize a new structure
+ */
+ if ( ! empty( $original_data ) ) {
+ $widget_data = $original_data;
+ }
+ else {
+ $widget_data = array(
+ 'site_info' => array(
+ 'last_check' => null,
+ 'last_update' => null,
+ 'error' => null,
+ 'data' => array(),
+ ),
+ 'posts' => array(
+ 'last_check' => null,
+ 'last_update' => null,
+ 'error' => null,
+ 'data' => array(),
+ )
+ );
+ }
+
+ /**
+ * Update check time and fetch site information.
+ */
+ $widget_data['site_info']['last_check'] = time();
+
+ $site_info_raw_data = $this->fetch_site_info( $site );
+ $site_info_parsed_data = $this->parse_site_info_response( $site_info_raw_data );
+
+
+ /**
+ * If there is an error with the fetched site info, save the error and update the checked time.
+ */
+ if ( is_wp_error( $site_info_parsed_data ) ) {
+ $widget_data['site_info']['error'] = $site_info_parsed_data;
+
+ return $widget_data;
+ }
+ /**
+ * If data is fetched successfully, update the data and set the proper time.
+ *
+ * Data is only updated if we have valid results. This is done this way so we can show
+ * something if external service is down.
+ *
+ */
+ else {
+ $widget_data['site_info']['last_update'] = time();
+ $widget_data['site_info']['data'] = $site_info_parsed_data;
+ $widget_data['site_info']['error'] = null;
+ }
+
+
+ /**
+ * If only site data is needed, return it here, don't fetch posts data.
+ */
+ if ( true === $site_data_only ) {
+ return $widget_data;
+ }
+
+ /**
+ * Update check time and fetch posts list.
+ */
+ $widget_data['posts']['last_check'] = time();
+
+ $site_posts_raw_data = $this->fetch_posts_for_site( $site_info_parsed_data->ID );
+ $site_posts_parsed_data = $this->parse_posts_response( $site_posts_raw_data );
+
+
+ /**
+ * If there is an error with the fetched posts, save the error and update the checked time.
+ */
+ if ( is_wp_error( $site_posts_parsed_data ) ) {
+ $widget_data['posts']['error'] = $site_posts_parsed_data;
+
+ return $widget_data;
+ }
+ /**
+ * If data is fetched successfully, update the data and set the proper time.
+ *
+ * Data is only updated if we have valid results. This is done this way so we can show
+ * something if external service is down.
+ *
+ */
+ else {
+ $widget_data['posts']['last_update'] = time();
+ $widget_data['posts']['data'] = $site_posts_parsed_data;
+ $widget_data['posts']['error'] = null;
+ }
+
+ return $widget_data;
+ }
+
+ /**
+ * Scan and extract first error from blog data array.
+ *
+ * @param array|WP_Error $blog_data Blog data to scan for errors.
+ *
+ * @return string First error message found
+ */
+ public function extract_errors_from_blog_data( $blog_data ) {
+
+ $errors = array(
+ 'message' => '',
+ 'debug' => '',
+ 'where' => '',
+ );
+
+
+ /**
+ * When the cache result is an error. Usually when the cache is empty.
+ * This is not an error case for now.
+ */
+ if ( is_wp_error( $blog_data ) ) {
+ return $errors;
+ }
+
+ /**
+ * Loop through `site_info` and `posts` keys of $blog_data.
+ */
+ foreach ( array( 'site_info', 'posts' ) as $info_key ) {
+
+ /**
+ * Contains information on which stage the error ocurred.
+ */
+ $errors['where'] = $info_key;
+
+ /**
+ * If an error is set, we want to check it for usable messages.
+ */
+ if ( isset( $blog_data[ $info_key ]['error'] ) && ! empty( $blog_data[ $info_key ]['error'] ) ) {
+
+ /**
+ * Extract error message from the error, if possible.
+ */
+ if ( is_wp_error( $blog_data[ $info_key ]['error'] ) ) {
+ /**
+ * In the case of WP_Error we want to have the error message
+ * and the debug information available.
+ */
+ $error_messages = $blog_data[ $info_key ]['error']->get_error_messages();
+ $errors['message'] = reset( $error_messages );
+
+ $extra_data = $blog_data[ $info_key ]['error']->get_error_data();
+ if ( is_array( $extra_data ) ) {
+ $errors['debug'] = implode( '; ', $extra_data );
+ }
+ else {
+ $errors['debug'] = $extra_data;
+ }
+
+ break;
+ }
+ elseif ( is_array( $blog_data[ $info_key ]['error'] ) ) {
+ /**
+ * In this case we don't have debug information, because
+ * we have no way to know the format. The widget works with
+ * WP_Error objects only.
+ */
+ $errors['message'] = reset( $blog_data[ $info_key ]['error'] );
+ break;
+ }
+
+ /**
+ * We do nothing if no usable error is found.
+ */
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * This is just to make method mocks in the unit tests easier.
+ *
+ * @param string $url The URL to fetch
+ * @param array $args Optional. Request arguments.
+ *
+ * @return array|WP_Error
+ *
+ * @codeCoverageIgnore
+ */
+ public function wp_wp_remote_get( $url, $args = array() ) {
+ return wp_remote_get( $url, $args );
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget.php b/plugins/jetpack/modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget.php
new file mode 100644
index 00000000..265e2ebb
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget.php
@@ -0,0 +1,274 @@
+<?php
+
+/*
+ * Display a list of recent posts from a WordPress.com or Jetpack-enabled blog.
+ */
+
+class Jetpack_Display_Posts_Widget extends Jetpack_Display_Posts_Widget__Base {
+ /**
+ * @var string Widget options key prefix.
+ */
+ public $widget_options_key_prefix = 'display_posts_site_data_';
+
+ /**
+ * @var string The name of the cron that will update widget data.
+ */
+ public static $cron_name = 'jetpack_display_posts_widget_cron_update';
+
+
+ // DATA STORE
+
+ /**
+ * Gets blog data from the cache.
+ *
+ * @param string $site
+ *
+ * @return array|WP_Error
+ */
+ public function get_blog_data( $site ) {
+ // load from cache, if nothing return an error
+ $site_hash = $this->get_site_hash( $site );
+
+ $cached_data = $this->wp_get_option( $this->widget_options_key_prefix . $site_hash );
+
+ /**
+ * If the cache is empty, return an empty_cache error.
+ */
+ if ( false === $cached_data ) {
+ return new WP_Error(
+ 'empty_cache',
+ __( 'Information about this blog is currently being retrieved.', 'jetpack' )
+ );
+ }
+
+ return $cached_data;
+
+ }
+
+ /**
+ * Update a widget instance.
+ *
+ * @param string $site The site to fetch the latest data for.
+ *
+ * @return array - the new data
+ */
+ public function update_instance( $site ) {
+
+ /**
+ * Fetch current information for a site.
+ */
+ $site_hash = $this->get_site_hash( $site );
+
+ $option_key = $this->widget_options_key_prefix . $site_hash;
+
+ $instance_data = $this->wp_get_option( $option_key );
+
+ /**
+ * Fetch blog data and save it in $instance_data.
+ */
+ $new_data = $this->fetch_blog_data( $site, $instance_data );
+
+ /**
+ * If the option doesn't exist yet - create a new option
+ */
+ if ( false === $instance_data ) {
+ $this->wp_add_option( $option_key, $new_data );
+ }
+ else {
+ $this->wp_update_option( $option_key, $new_data );
+ }
+
+ return $new_data;
+ }
+
+
+ // WIDGET API
+
+ public function update( $new_instance, $old_instance ) {
+ $instance = parent::update( $new_instance, $old_instance );
+
+ /**
+ * Forcefully activate the update cron when saving widget instance.
+ *
+ * So we can be sure that it will be running later.
+ */
+ $this->activate_cron();
+
+ return $instance;
+ }
+
+
+ // CRON
+
+ /**
+ * Activates widget update cron task.
+ */
+ public static function activate_cron() {
+ if ( ! wp_next_scheduled( self::$cron_name ) ) {
+ wp_schedule_event( time(), 'minutes_10', self::$cron_name );
+ }
+ }
+
+ /**
+ * Deactivates widget update cron task.
+ *
+ * This is a wrapper over the static method as it provides some syntactic sugar.
+ */
+ public function deactivate_cron() {
+ self::deactivate_cron_static();
+ }
+
+ /**
+ * Deactivates widget update cron task.
+ */
+ public static function deactivate_cron_static() {
+ $next_scheduled_time = wp_next_scheduled( self::$cron_name );
+ wp_unschedule_event( $next_scheduled_time, self::$cron_name );
+ }
+
+ /**
+ * Checks if the update cron should be running and returns appropriate result.
+ *
+ * @return bool If the cron should be running or not.
+ */
+ public function should_cron_be_running() {
+ /**
+ * The cron doesn't need to run empty loops.
+ */
+ $widget_instances = $this->get_instances_sites();
+
+ if ( empty( $widget_instances ) || ! is_array( $widget_instances ) ) {
+ return false;
+ }
+
+ if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
+ /**
+ * If Jetpack is not active or in development mode, we don't want to update widget data.
+ */
+ if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
+ return false;
+ }
+
+ /**
+ * If Extra Sidebar Widgets module is not active, we don't need to update widget data.
+ */
+ if ( ! Jetpack::is_module_active( 'widgets' ) ) {
+ return false;
+ }
+ }
+
+ /**
+ * If none of the above checks failed, then we definitely want to update widget data.
+ */
+ return true;
+ }
+
+ /**
+ * Main cron code. Updates all instances of the widget.
+ *
+ * @return bool
+ */
+ public function cron_task() {
+
+ /**
+ * If the cron should not be running, disable it.
+ */
+ if ( false === $this->should_cron_be_running() ) {
+ return true;
+ }
+
+ $instances_to_update = $this->get_instances_sites();
+
+ /**
+ * If no instances are found to be updated - stop.
+ */
+ if ( empty( $instances_to_update ) || ! is_array( $instances_to_update ) ) {
+ return true;
+ }
+
+ foreach ( $instances_to_update as $site_url ) {
+ $this->update_instance( $site_url );
+ }
+
+ return true;
+ }
+
+ /**
+ * Get a list of unique sites from all instances of the widget.
+ *
+ * @return array|bool
+ */
+ public function get_instances_sites() {
+
+ $widget_settings = $this->wp_get_option( 'widget_jetpack_display_posts_widget' );
+
+ /**
+ * If the widget still hasn't been added anywhere, the config will not be present.
+ *
+ * In such case we don't want to continue execution.
+ */
+ if ( false === $widget_settings || ! is_array( $widget_settings ) ) {
+ return false;
+ }
+
+ $urls = array();
+
+ foreach ( $widget_settings as $widget_instance_data ) {
+ if ( isset( $widget_instance_data['url'] ) && ! empty( $widget_instance_data['url'] ) ) {
+ $urls[] = $widget_instance_data['url'];
+ }
+ }
+
+ /**
+ * Make sure only unique URLs are returned.
+ */
+ $urls = array_unique( $urls );
+
+ return $urls;
+
+ }
+
+
+ // MOCKABLES
+
+ /**
+ * This is just to make method mocks in the unit tests easier.
+ *
+ * @param string $param Option key to get
+ *
+ * @return mixed
+ *
+ * @codeCoverageIgnore
+ */
+ public function wp_get_option( $param ) {
+ return get_option( $param );
+ }
+
+ /**
+ * This is just to make method mocks in the unit tests easier.
+ *
+ * @param string $option_name Option name to be added
+ * @param mixed $option_value Option value
+ *
+ * @return mixed
+ *
+ * @codeCoverageIgnore
+ */
+ public function wp_add_option( $option_name, $option_value ) {
+ return add_option( $option_name, $option_value );
+ }
+
+ /**
+ * This is just to make method mocks in the unit tests easier.
+ *
+ * @param string $option_name Option name to be updated
+ * @param mixed $option_value Option value
+ *
+ * @return mixed
+ *
+ * @codeCoverageIgnore
+ */
+ public function wp_update_option( $option_name, $option_value ) {
+ return update_option( $option_name, $option_value );
+ }
+}
diff --git a/plugins/jetpack/modules/widgets/wordpress-post-widget/style.css b/plugins/jetpack/modules/widgets/wordpress-post-widget/style.css
new file mode 100644
index 00000000..651ec153
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/wordpress-post-widget/style.css
@@ -0,0 +1,24 @@
+.jetpack-display-remote-posts {
+ margin: 5px 0 20px 0;
+}
+
+.jetpack-display-remote-posts h4 {
+ font-size: 90%;
+ margin: 5px 0;
+ padding: 0;
+}
+
+.jetpack-display-remote-posts h4 a {
+ text-decoration: none;
+}
+
+.jetpack-display-remote-posts p {
+ margin: 0 !important;
+ padding: 0;
+ line-height: 1.4em !important;
+ font-size: 90%;
+}
+
+.jetpack-display-remote-posts img {
+ max-width: 100%;
+}