summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony G. Basile <blueness@gentoo.org>2018-07-12 10:41:06 -0400
committerAnthony G. Basile <blueness@gentoo.org>2018-07-12 10:41:06 -0400
commitd38ad7e645bf525ba52f98eb3ac3b911569ba8ad (patch)
treec71f455934a7ad823422c9eb17e08d029d2c7612 /plugins/jetpack/modules
parentUpdate akismet 4.0.8 (diff)
downloadblogs-gentoo-d38ad7e645bf525ba52f98eb3ac3b911569ba8ad.tar.gz
blogs-gentoo-d38ad7e645bf525ba52f98eb3ac3b911569ba8ad.tar.bz2
blogs-gentoo-d38ad7e645bf525ba52f98eb3ac3b911569ba8ad.zip
Update jetpack 6.3.2
Signed-off-by: Anthony G. Basile <blueness@gentoo.org>
Diffstat (limited to 'plugins/jetpack/modules')
-rw-r--r--plugins/jetpack/modules/after-the-deadline/config-options.php2
-rw-r--r--plugins/jetpack/modules/carousel/jetpack-carousel.php4
-rw-r--r--plugins/jetpack/modules/comments/comments.php4
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css.php6
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js2
-rw-r--r--plugins/jetpack/modules/custom-post-types/nova.php5
-rw-r--r--plugins/jetpack/modules/custom-post-types/testimonial.php5
-rw-r--r--plugins/jetpack/modules/geo-location.php81
-rw-r--r--plugins/jetpack/modules/geo-location/class.jetpack-geo-location.php411
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.php2
-rw-r--r--plugins/jetpack/modules/lazy-images.php1
-rw-r--r--plugins/jetpack/modules/lazy-images/js/lazy-images.js3
-rw-r--r--plugins/jetpack/modules/lazy-images/lazy-images.php28
-rw-r--r--plugins/jetpack/modules/markdown/easy-markdown.php2
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php2
-rw-r--r--plugins/jetpack/modules/module-extras.php1
-rw-r--r--plugins/jetpack/modules/module-headings.php1
-rw-r--r--plugins/jetpack/modules/protect.php4
-rw-r--r--plugins/jetpack/modules/protect/blocked-login-page.php2
-rw-r--r--plugins/jetpack/modules/publicize/ui.php6
-rw-r--r--plugins/jetpack/modules/related-posts/jetpack-related-posts.php2
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharedaddy.php2
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing-sources.php2
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing.php2
-rw-r--r--plugins/jetpack/modules/simple-payments/paypal-express-checkout.js6
-rw-r--r--plugins/jetpack/modules/simple-payments/simple-payments.php51
-rw-r--r--plugins/jetpack/modules/sitemaps/sitemap-builder.php4
-rw-r--r--plugins/jetpack/modules/sitemaps/sitemap-stylist.php30
-rw-r--r--plugins/jetpack/modules/sso/class.jetpack-sso-notices.php2
-rw-r--r--plugins/jetpack/modules/stats.php2
-rw-r--r--plugins/jetpack/modules/theme-tools.php7
-rw-r--r--plugins/jetpack/modules/theme-tools/compat/twentyfifteen.php5
-rw-r--r--plugins/jetpack/modules/theme-tools/compat/twentyseventeen.php13
-rw-r--r--plugins/jetpack/modules/theme-tools/compat/twentysixteen.php5
-rw-r--r--plugins/jetpack/modules/theme-tools/content-options/customizer.php2
-rw-r--r--plugins/jetpack/modules/theme-tools/random-redirect.php2
-rw-r--r--plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php11
-rw-r--r--plugins/jetpack/modules/verification-tools/blog-verification-tools.php2
-rw-r--r--plugins/jetpack/modules/videopress/class.videopress-player.php6
-rw-r--r--plugins/jetpack/modules/widgets/customizer-utils.js15
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments.php487
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/customizer.css72
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/customizer.js373
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/form.php155
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/style.css8
-rw-r--r--plugins/jetpack/modules/widgets/simple-payments/widget.php25
-rw-r--r--plugins/jetpack/modules/widgets/wordpress-post-widget.php1058
-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
49 files changed, 2915 insertions, 1123 deletions
diff --git a/plugins/jetpack/modules/after-the-deadline/config-options.php b/plugins/jetpack/modules/after-the-deadline/config-options.php
index ef0ba8c9..0f81c2f0 100644
--- a/plugins/jetpack/modules/after-the-deadline/config-options.php
+++ b/plugins/jetpack/modules/after-the-deadline/config-options.php
@@ -80,7 +80,7 @@ function AtD_display_options_form() {
echo '<br />';
AtD_print_option( 'Redundant Expression', __('Redundant Phrases', 'jetpack'), $options_show_types );
?></p>
- <p><?php printf( __( '<a href="%s" target="_blank">Learn more</a> about these options.', 'jetpack' ), 'http://support.wordpress.com/proofreading/' );
+ <p><?php printf( __( '<a href="%s" rel="noopener noreferrer" target="_blank">Learn more</a> about these options.', 'jetpack' ), 'http://support.wordpress.com/proofreading/' );
?></p>
<p style="font-weight: bold"><?php _e( 'Language', 'jetpack' ); ?></p>
diff --git a/plugins/jetpack/modules/carousel/jetpack-carousel.php b/plugins/jetpack/modules/carousel/jetpack-carousel.php
index 1fb4394b..77dc78e6 100644
--- a/plugins/jetpack/modules/carousel/jetpack-carousel.php
+++ b/plugins/jetpack/modules/carousel/jetpack-carousel.php
@@ -707,7 +707,7 @@ class Jetpack_Carousel {
}
function carousel_display_exif_callback() {
- $this->settings_checkbox( 'carousel_display_exif', __( 'Show photo metadata (<a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) );
+ $this->settings_checkbox( 'carousel_display_exif', __( 'Show photo metadata (<a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format" rel="noopener noreferrer" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) );
}
function carousel_display_exif_sanitize( $value ) {
@@ -723,7 +723,7 @@ class Jetpack_Carousel {
}
function carousel_background_color_callback() {
- $this->settings_select( 'carousel_background_color', array( 'black' => __( 'Black', 'jetpack' ), 'white' => __( 'White', 'jetpack', 'jetpack' ) ) );
+ $this->settings_select( 'carousel_background_color', array( 'black' => __( 'Black', 'jetpack' ), 'white' => __( 'White', 'jetpack' ) ) );
}
function carousel_background_color_sanitize( $value ) {
diff --git a/plugins/jetpack/modules/comments/comments.php b/plugins/jetpack/modules/comments/comments.php
index a60bfd5e..d5fe9251 100644
--- a/plugins/jetpack/modules/comments/comments.php
+++ b/plugins/jetpack/modules/comments/comments.php
@@ -270,6 +270,10 @@ class Jetpack_Comments extends Highlander_Comments_Base {
if ( current_user_can( 'unfiltered_html' ) ) {
$params['_wp_unfiltered_html_comment'] = wp_create_nonce( 'unfiltered-html-comment_' . get_the_ID() );
}
+ } else {
+ $commenter = wp_get_current_commenter();
+ $params['show_cookie_consent'] = (int) has_action( 'set_comment_cookies', 'wp_set_comment_cookies' );
+ $params['has_cookie_consent'] = (int) ! empty( $commenter['comment_author_email'] );
}
$signature = Jetpack_Comments::sign_remote_comment_parameters( $params, Jetpack_Options::get_option( 'blog_token' ) );
diff --git a/plugins/jetpack/modules/custom-css/custom-css.php b/plugins/jetpack/modules/custom-css/custom-css.php
index 3e29a410..6229014b 100644
--- a/plugins/jetpack/modules/custom-css/custom-css.php
+++ b/plugins/jetpack/modules/custom-css/custom-css.php
@@ -1004,8 +1004,8 @@ class Jetpack_Custom_CSS {
*
* @param string $str Intro text appearing above the Custom CSS editor.
*/
- echo apply_filters( 'safecss_intro_text', __( 'New to CSS? Start with a <a href="http://www.htmldog.com/guides/cssbeginner/" target="_blank">beginner tutorial</a>. Questions?
- Ask in the <a href="https://wordpress.org/support/forum/themes-and-templates" target="_blank">Themes and Templates forum</a>.', 'jetpack' ) );
+ echo apply_filters( 'safecss_intro_text', __( 'New to CSS? Start with a <a href="http://www.htmldog.com/guides/cssbeginner/" rel="noopener noreferrer" target="_blank">beginner tutorial</a>. Questions?
+ Ask in the <a href="https://wordpress.org/support/forum/themes-and-templates" rel="noopener noreferrer" target="_blank">Themes and Templates forum</a>.', 'jetpack' ) );
?></p>
<p class="css-support"><?php echo __( 'Note: Custom CSS will be reset when changing themes.', 'jetpack' ); ?></p>
@@ -1053,7 +1053,7 @@ class Jetpack_Custom_CSS {
<?php
printf( /* translators: %1$s is replaced with an input field for numbers. */
- __( 'Limit width to %1$s pixels for full size images. (<a href="%2$s" target="_blank">More info</a>.)', 'jetpack' ),
+ __( 'Limit width to %1$s pixels for full size images. (<a href="%2$s" rel="noopener noreferrer" target="_blank">More info</a>.)', 'jetpack' ),
'<input type="text" id="custom_content_width_visible" value="' . esc_attr( $custom_content_width ) . '" size="4" />',
/**
* Filter the Custom CSS limited width's support doc URL.
diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js b/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js
index 4e98e155..7fef365f 100644
--- a/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js
+++ b/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js
@@ -41,6 +41,7 @@
$( '<a />', {
id : 'help-link',
target : '_blank',
+ rel: 'noopener noreferrer',
href : window._jp_css_settings.cssHelpUrl,
text : window._jp_css_settings.l10n.css_help_title
}).prependTo( '#css-help-links' );
@@ -50,6 +51,7 @@
$( '<a />', {
id : 'revisions-link',
target : '_blank',
+ rel: 'noopener noreferrer',
href : window._jp_css_settings.revisionsUrl,
text : window._jp_css_settings.l10n.revisions
}).prependTo( '#css-help-links' );
diff --git a/plugins/jetpack/modules/custom-post-types/nova.php b/plugins/jetpack/modules/custom-post-types/nova.php
index 37404974..7301a314 100644
--- a/plugins/jetpack/modules/custom-post-types/nova.php
+++ b/plugins/jetpack/modules/custom-post-types/nova.php
@@ -307,11 +307,10 @@ class Nova_Restaurant {
* Change ‘Enter Title Here’ text for the Menu Item.
*/
function change_default_title( $title ) {
- $screen = get_current_screen();
-
- if ( self::MENU_ITEM_POST_TYPE == $screen->post_type )
+ if ( self::MENU_ITEM_POST_TYPE == get_post_type() ) {
/* translators: this is about a food menu */
$title = esc_html__( "Enter the menu item's name here", 'jetpack' );
+ }
return $title;
}
diff --git a/plugins/jetpack/modules/custom-post-types/testimonial.php b/plugins/jetpack/modules/custom-post-types/testimonial.php
index 3199d5a6..7972078f 100644
--- a/plugins/jetpack/modules/custom-post-types/testimonial.php
+++ b/plugins/jetpack/modules/custom-post-types/testimonial.php
@@ -364,10 +364,9 @@ class Jetpack_Testimonial {
* Change ‘Enter Title Here’ text for the Testimonial.
*/
function change_default_title( $title ) {
- $screen = get_current_screen();
-
- if ( self::CUSTOM_POST_TYPE == $screen->post_type )
+ if ( self::CUSTOM_POST_TYPE == get_post_type() ) {
$title = esc_html__( "Enter the customer's name here", 'jetpack' );
+ }
return $title;
}
diff --git a/plugins/jetpack/modules/geo-location.php b/plugins/jetpack/modules/geo-location.php
new file mode 100644
index 00000000..4d3e255c
--- /dev/null
+++ b/plugins/jetpack/modules/geo-location.php
@@ -0,0 +1,81 @@
+<?php
+
+require_once dirname( __FILE__ ) . '/geo-location/class.jetpack-geo-location.php';
+
+/**
+ * Geo-location shortcode for display of location data associated with a post.
+ *
+ * Usage with current global $post:
+ * [geo-location]
+ *
+ * Usage with specific post ID:
+ * [geo-location post=5]
+ */
+add_shortcode( 'geo-location', 'jetpack_geo_shortcode' );
+
+function jetpack_geo_shortcode( $attributes ) {
+ $attributes = shortcode_atts( array( 'post' => null, 'id' => null ), $attributes );
+ return jetpack_geo_get_location( $attributes['post'] ? $attributes['post'] : $attributes['id'] );
+}
+
+/**
+ * Get the geo-location data associated with the supplied post ID, if it's available
+ * and marked as being available for public display. The returned array will contain
+ * "latitude", "longitude" and "label" keys.
+ *
+ * If you do not supply a value for $post_id, the global $post will be used, if
+ * available.
+ *
+ * @param integer|null $post_id
+ *
+ * @return array|null
+ */
+function jetpack_geo_get_data( $post_id = null) {
+ $geo = Jetpack_Geo_Location::init();
+
+ if ( ! $post_id ) {
+ $post_id = $geo->get_post_id();
+ }
+
+ $meta_values = $geo->get_meta_values( $post_id );
+
+ if ( ! $meta_values['is_public'] || ! $meta_values['is_populated'] ) {
+ return null;
+ }
+
+ return array(
+ 'latitude' => $meta_values['latitude'],
+ 'longitude' => $meta_values['longitude'],
+ 'label' => $meta_values['label']
+ );
+}
+
+/**
+ * Display the label HTML for the geo-location information associated with the supplied
+ * post ID.
+ *
+ * If you do not supply a value for $post_id, the global $post will be used, if
+ * available.
+ *
+ * @param integer|null $post_id
+ *
+ * @return void
+ */
+function jetpack_geo_display_location( $post_id = null ) {
+ echo jetpack_geo_get_location( $post_id );
+}
+
+/**
+ * Return the label HTML for the geo-location information associated with the supplied
+ * post ID.
+ *
+ * If you do not supply a value for $post_id, the global $post will be used, if
+ * available.
+ *
+ * @param integer|null $post_id
+ *
+ * @return string
+ */
+function jetpack_geo_get_location( $post_id = null ) {
+ return Jetpack_Geo_Location::init()->get_location_label( $post_id );
+}
diff --git a/plugins/jetpack/modules/geo-location/class.jetpack-geo-location.php b/plugins/jetpack/modules/geo-location/class.jetpack-geo-location.php
new file mode 100644
index 00000000..1e0094f2
--- /dev/null
+++ b/plugins/jetpack/modules/geo-location/class.jetpack-geo-location.php
@@ -0,0 +1,411 @@
+<?php
+
+/**
+ * Adds support for geo-location features.
+ *
+ * All Jetpack sites can support geo-location features. Users can tag posts with geo-location data
+ * using the UI provided by Calypso. That information will be included in RSS feeds, meta tags during
+ * wp_head, and in the Geo microformat following post content.
+ *
+ * If your theme declares support for "geo-location", you'll also get a small icon and location label
+ * visible to users at the bottom of single posts and pages.
+ *
+ * To declare support in your theme, call `add_theme_support( 'jetpack-geo-location' )`.
+ *
+ * Once you've added theme support, you can rely on the standard HTML output generated in the
+ * the_content_location_display() method of this class. Or, you can use the "geo_location_display"
+ * filter to generate custom HTML for your particular theme. Your filter function will receive an
+ * the default HTML as its first argument and an array containing the geo-location information as
+ * its second argument in the following format:
+ *
+ * array(
+ * 'is_public' => boolean,
+ * 'latitude' => float,
+ * 'longitude' => float,
+ * 'label' => string,
+ * 'is_populated' => boolean
+ * )
+ *
+ * Add your filter with:
+ *
+ * add_filter( 'jetpack_geo_location_display', 'your_filter_function_name', 10, 2);
+ */
+class Jetpack_Geo_Location {
+ private static $instance;
+
+ public static function init() {
+ if ( is_null( self::$instance ) ) {
+ self::$instance = new Jetpack_Geo_Location();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * This is mostly just used for testing purposes.
+ */
+ public static function reset_instance() {
+ self::$instance = null;
+ }
+
+ public function __construct() {
+ add_action( 'init', array( $this, 'wordpress_init' ) );
+ add_action( 'wp_head', array( $this, 'wp_head' ) );
+ add_filter( 'the_content', array( $this, 'the_content_microformat' ) );
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+
+ $this->register_rss_hooks();
+ }
+
+ /**
+ * Register support for the geo-location feature on pages and posts. Register the meta
+ * fields managed by this plugin so that they are properly sanitized during save.
+ */
+ public function wordpress_init() {
+ // Only render location label after post content, if the theme claims to support "geo-location".
+ if ( current_theme_supports( 'jetpack-geo-location' ) ) {
+ add_filter( 'the_content', array( $this, 'the_content_location_display' ), 15, 1 );
+ }
+
+ add_post_type_support( 'post', 'geo-location' );
+ add_post_type_support( 'page', 'geo-location' );
+
+ register_meta(
+ 'post',
+ 'geo_public',
+ array(
+ 'sanitize_callback' => array( $this, 'sanitize_public' ),
+ 'type' => 'boolean',
+ 'single' => true,
+ )
+ );
+
+ register_meta(
+ 'post',
+ 'geo_latitude',
+ array(
+ 'sanitize_callback' => array( $this, 'sanitize_coordinate' ),
+ 'type' => 'float',
+ 'single' => true,
+ )
+ );
+
+ register_meta(
+ 'post',
+ 'geo_longitude',
+ array(
+ 'sanitize_callback' => array( $this, 'sanitize_coordinate' ),
+ 'type' => 'float',
+ 'single' => true,
+ )
+ );
+
+ register_meta(
+ 'post',
+ 'geo_address',
+ array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ 'type' => 'string',
+ 'single' => true,
+ )
+ );
+ }
+
+ /**
+ * Filter "public" input to always be either 1 or 0.
+ *
+ * @param mixed $public
+ *
+ * @return int
+ */
+ public function sanitize_public( $public ) {
+ return absint( $public ) ? 1 : 0;
+ }
+
+ /**
+ * Filter geo coordinates and normalize them to floats with 7 digits of precision.
+ *
+ * @param mixed $coordinate
+ *
+ * @return float|null
+ */
+ public function sanitize_coordinate( $coordinate ) {
+ if ( ! $coordinate ) {
+ return null;
+ }
+
+ return round( (float) $coordinate, 7 );
+ }
+
+ /**
+ * Render geo.position and ICBM meta tags with public geo meta values when rendering
+ * a single post.
+ */
+ public function wp_head() {
+ if ( ! is_single() ) {
+ return;
+ }
+
+ $meta_values = $this->get_meta_values( $this->get_post_id() );
+
+ if ( ! $meta_values['is_public'] ) {
+ return;
+ }
+
+ echo "\n<!-- Jetpack Geo-location Tags -->\n";
+
+ if ( $meta_values['label'] ) {
+ printf(
+ '<meta name="geo.placename" content="%s" />',
+ esc_attr( $meta_values['label'] )
+ );
+ }
+
+ printf(
+ '<meta name="geo.position" content="%s;%s" />' . PHP_EOL,
+ esc_attr( $meta_values['latitude'] ),
+ esc_attr( $meta_values['longitude'] )
+ );
+
+ printf(
+ '<meta name="ICBM" content="%s, %s" />' . PHP_EOL,
+ esc_attr( $meta_values['latitude'] ),
+ esc_attr( $meta_values['longitude'] )
+ );
+
+ echo "\n<!-- End Jetpack Geo-location Tags -->\n";
+ }
+
+ /**
+ * Append public meta values in the Geo microformat (https://en.wikipedia.org/wiki/Geo_(microformat)
+ * to the supplied content.
+ *
+ * Note that we cannot render the microformat in the context of an excerpt because tags are stripped
+ * in that context, making our microformat data visible.
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+ public function the_content_microformat( $content ) {
+ if ( is_feed() || $this->is_currently_excerpt_filter() ) {
+ return $content;
+ }
+
+ $meta_values = $this->get_meta_values( $this->get_post_id() );
+
+ if ( ! $meta_values['is_public'] ) {
+ return $content;
+ }
+
+ $microformat = sprintf(
+ '<div id="geo-post-%d" class="geo geo-post" style="display: none">',
+ esc_attr( $this->get_post_id() )
+ );
+
+ $microformat .= sprintf(
+ '<span class="latitude">%s</span>',
+ esc_html( $meta_values['latitude'] )
+ );
+
+ $microformat .= sprintf(
+ '<span class="longitude">%s</span>',
+ esc_html( $meta_values['longitude'] )
+ );
+
+ $microformat .= '</div>';
+
+ return $content . $microformat;
+ }
+
+ /**
+ * Register a range of hooks for integrating geo data with various feeds.
+ */
+ public function register_rss_hooks() {
+ add_action( 'rss2_ns', array( $this, 'rss_namespace' ) );
+ add_action( 'atom_ns', array( $this, 'rss_namespace' ) );
+ add_action( 'rdf_ns', array( $this, 'rss_namespace' ) );
+ add_action( 'rss_item', array( $this, 'rss_item' ) );
+ add_action( 'rss2_item', array( $this, 'rss_item' ) );
+ add_action( 'atom_entry', array( $this, 'rss_item' ) );
+ add_action( 'rdf_item', array( $this, 'rss_item' ) );
+ }
+
+ /**
+ * Add the georss namespace during RSS generation.
+ */
+ public function rss_namespace() {
+ echo 'xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" ';
+ }
+
+ /**
+ * Output georss data for RSS items, assuming we have data for the currently rendered post and
+ * that data as marked as public.
+ */
+ public function rss_item() {
+ $meta_values = $this->get_meta_values( $this->get_post_id() );
+
+ if ( ! $meta_values['is_public'] ) {
+ return;
+ }
+
+ printf(
+ "\t<georss:point>%s %s</georss:point>\n",
+ ent2ncr( esc_html( $meta_values['latitude'] ) ),
+ ent2ncr( esc_html( $meta_values['longitude'] ) )
+ );
+
+ printf( "\t\t<geo:lat>%s</geo:lat>\n", ent2ncr( esc_html( $meta_values['latitude'] ) ) );
+ printf( "\t\t<geo:long>%s</geo:long>\n", ent2ncr( esc_html( $meta_values['longitude'] ) ) );
+ }
+
+ /**
+ * Enqueue CSS for rendering post flair with geo-location.
+ */
+ public function enqueue_scripts() {
+ wp_enqueue_style( 'dashicons' );
+ }
+
+ /**
+ * If we're rendering a single post and public geo-location data is available for it,
+ * include the human-friendly location label in the output.
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+ public function the_content_location_display( $content ) {
+ if ( ! is_single() ) {
+ return $content;
+ }
+
+ return $content . $this->get_location_label();
+ }
+
+ /**
+ * Get the HTML for displaying a label representing the location associated with the
+ * supplied post ID. If no post ID is given, we'll use the global $post variable, if
+ * it is available.
+ *
+ * @param integer|null $post_id
+ *
+ * @return string
+ */
+ public function get_location_label( $post_id = null ) {
+ $meta_values = $this->get_meta_values( $post_id ? $post_id : $this->get_post_id() );
+
+ if ( ! $meta_values['is_public'] ) {
+ return '';
+ }
+
+ // If the location has not been labeled, do not show the location.
+ if ( ! $meta_values['label'] ) {
+ return '';
+ }
+
+ $html = '<div class="post-geo-location-label geo-chip">';
+ $html .= '<span class="dashicons dashicons-location" style="vertical-align: text-top;"></span> ';
+ $html .= esc_html( $meta_values['label'] );
+ $html .= '</div>';
+
+ /**
+ * Allow modification or replacement of the default geo-location display HTML.
+ *
+ * @module geo-location
+ *
+ * @param array $html The default HTML for displaying a geo-location label.
+ * @param array $geo_data An array containing "latitude", "longitude" and "label".
+ */
+ $html = apply_filters( 'jetpack_geo_location_display', $html, $meta_values );
+
+ return $html;
+ }
+
+ /**
+ * Get the ID of the current global post object, if available. Otherwise, return null.
+ *
+ * This isolates the access of the global scope to this single method, making it easier to
+ * safeguard against unexpected missing $post objects in other hook functions.
+ *
+ * @return int|null
+ */
+ public function get_post_id() {
+ global $post;
+
+ if ( ! isset( $post ) || ! $post || ! is_object( $post ) || ! isset( $post->ID ) ) {
+ return null;
+ }
+
+ return $post->ID;
+ }
+
+ /**
+ * This method always returns an array with the following structure:
+ *
+ * array(is_public => bool, latitude => float, longitude => float, label => string, is_populated => bool)
+ *
+ * So, regardless of whether your post actually has values in postmeta for the geo-location fields,
+ * you can be sure that you can reference those array keys in calling code without having to juggle
+ * isset(), array_key_exists(), etc.
+ *
+ * Mocking this method during testing can also be useful for testing output and logic in various
+ * hook functions.
+ *
+ * @param integer $post_id
+ *
+ * @return array A predictably structured array representing the meta values for the supplied post ID.
+ */
+ public function get_meta_values( $post_id ) {
+ $meta_values = array(
+ 'is_public' => (bool) $this->sanitize_public( $this->get_meta_value( $post_id, 'public' ) ),
+ 'latitude' => $this->sanitize_coordinate( $this->get_meta_value( $post_id, 'latitude' ) ),
+ 'longitude' => $this->sanitize_coordinate( $this->get_meta_value( $post_id, 'longitude' ) ),
+ 'label' => trim( $this->get_meta_value( $post_id, 'address' ) ),
+ 'is_populated' => false,
+ );
+
+ if ( $meta_values['latitude'] && $meta_values['longitude'] && $meta_values['label'] ) {
+ $meta_values['is_populated'] = true;
+ }
+
+ return $meta_values;
+ }
+
+ /**
+ * This function wraps get_post_meta() to enable us to keep the "geo_" prefix isolated to a single
+ * location in the code and to assist in mocking during testing.
+ *
+ * @param integer $post_id
+ * @param string $meta_field_name
+ *
+ * @return mixed
+ */
+ public function get_meta_value( $post_id, $meta_field_name ) {
+ if ( ! $post_id ) {
+ return null;
+ }
+
+ return get_post_meta( $post_id, 'geo_' . $meta_field_name, true );
+ }
+
+ /**
+ * Check to see if the current filter is the get_the_excerpt filter.
+ *
+ * Just checking current_filter() here is not adequate because current_filter() only looks
+ * at the last element in the $wp_current_filter array. In the context of rendering an
+ * excerpt, however, both get_the_excerpt and the_content are present in that array.
+ *
+ * @return bool
+ */
+ public function is_currently_excerpt_filter() {
+ if ( ! isset( $GLOBALS['wp_current_filter'] ) ) {
+ return false;
+ }
+
+ $current_filters = (array) $GLOBALS['wp_current_filter'];
+
+ return in_array( 'get_the_excerpt', $current_filters, true );
+ }
+}
+
+Jetpack_Geo_Location::init();
diff --git a/plugins/jetpack/modules/infinite-scroll/infinity.php b/plugins/jetpack/modules/infinite-scroll/infinity.php
index 896db249..4cd5a26d 100644
--- a/plugins/jetpack/modules/infinite-scroll/infinity.php
+++ b/plugins/jetpack/modules/infinite-scroll/infinity.php
@@ -1541,7 +1541,7 @@ class The_Neverending_Home_Page {
*/
private function default_footer() {
$credits = sprintf(
- '<a href="https://wordpress.org/" target="_blank" rel="generator">%1$s</a> ',
+ '<a href="https://wordpress.org/" rel="noopener noreferrer" target="_blank" rel="generator">%1$s</a> ',
__( 'Proudly powered by WordPress', 'jetpack' )
);
$credits .= sprintf(
diff --git a/plugins/jetpack/modules/lazy-images.php b/plugins/jetpack/modules/lazy-images.php
index 56a005e5..5031710a 100644
--- a/plugins/jetpack/modules/lazy-images.php
+++ b/plugins/jetpack/modules/lazy-images.php
@@ -3,6 +3,7 @@
/**
* Module Name: Lazy Images
* Module Description: Lazy load images
+ * Jumpstart Description: Lazy-loading images improve your site's speed and create a smoother viewing experience. Images will load as visitors scroll down the screen, instead of all at once.
* Sort Order: 24
* Recommendation Order: 14
* First Introduced: 5.6.0
diff --git a/plugins/jetpack/modules/lazy-images/js/lazy-images.js b/plugins/jetpack/modules/lazy-images/js/lazy-images.js
index 2c7137b9..7da313ce 100644
--- a/plugins/jetpack/modules/lazy-images/js/lazy-images.js
+++ b/plugins/jetpack/modules/lazy-images/js/lazy-images.js
@@ -17,6 +17,9 @@ var jetpackLazyImagesModule = function( $ ) {
// Lazy load images that are brought in from Infinite Scroll
$( 'body' ).bind( 'post-load', lazy_load_init );
+
+ // Add event to provide optional compatibility for other code.
+ $( 'body' ).bind( 'jetpack-lazy-images-load', lazy_load_init );
} );
function lazy_load_init() {
diff --git a/plugins/jetpack/modules/lazy-images/lazy-images.php b/plugins/jetpack/modules/lazy-images/lazy-images.php
index 25ca181b..59c64a1e 100644
--- a/plugins/jetpack/modules/lazy-images/lazy-images.php
+++ b/plugins/jetpack/modules/lazy-images/lazy-images.php
@@ -47,6 +47,7 @@ class Jetpack_Lazy_Images {
add_action( 'admin_bar_menu', array( $this, 'remove_filters' ), 0 );
add_filter( 'wp_kses_allowed_html', array( $this, 'allow_lazy_attributes' ) );
+ add_action( 'wp_head', array( $this, 'add_nojs_fallback' ) );
}
public function setup_filters() {
@@ -176,6 +177,9 @@ class Jetpack_Lazy_Images {
return $matches[0];
}
+ // Ensure we add the jetpack-lazy-image class to this image.
+ $new_attributes['class'] = sprintf( '%s jetpack-lazy-image', empty( $new_attributes['class'] ) ? '' : $new_attributes['class'] );
+
$new_attributes_str = self::build_attributes_string( $new_attributes );
return sprintf( '<img %1$s><noscript>%2$s</noscript>', $new_attributes_str, $matches[0] );
@@ -250,6 +254,30 @@ class Jetpack_Lazy_Images {
return apply_filters( 'jetpack_lazy_images_new_attributes', $attributes );
}
+ /**
+ * Adds JavaScript to check if the current browser supports JavaScript as well as some styles to hide lazy
+ * images when the browser does not support JavaScript.
+ *
+ * @return void
+ */
+ public function add_nojs_fallback() {
+ ?>
+ <style type="text/css">
+ .jetpack-lazy-image {
+ display: none;
+ }
+ .jetpack-lazy-images-js .jetpack-lazy-image {
+ display: inline-block;
+ }
+ </style>
+ <script>
+ document.documentElement.classList.add(
+ 'jetpack-lazy-images-js'
+ );
+ </script>
+ <?php
+ }
+
private static function get_placeholder_image() {
/**
* Allows plugins and themes to modify the placeholder image.
diff --git a/plugins/jetpack/modules/markdown/easy-markdown.php b/plugins/jetpack/modules/markdown/easy-markdown.php
index 65e4685e..dd6bba01 100644
--- a/plugins/jetpack/modules/markdown/easy-markdown.php
+++ b/plugins/jetpack/modules/markdown/easy-markdown.php
@@ -451,7 +451,7 @@ class WPCom_Markdown {
?>
<script type="text/javascript">
jQuery( function() {
- tinymce.on( 'AddEditor', function( event ) {
+ ( 'undefined' !== typeof tinymce ) && tinymce.on( 'AddEditor', function( event ) {
event.editor.on( 'BeforeSetContent', function( event ) {
var editor = event.target;
Object.keys( editor.schema.elements ).forEach( function( key, index ) {
diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php
index c25fa83a..a55c1988 100644
--- a/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php
+++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php
@@ -56,7 +56,7 @@
do_action( 'minileven_credits' );
?>
- <a href="<?php echo esc_url( __( 'https://wordpress.org/', 'jetpack' ) ); ?>" target="_blank" title="<?php esc_attr_e( 'Semantic Personal Publishing Platform', 'jetpack' ); ?>" rel="generator"><?php printf( __( 'Proudly powered by %s', 'jetpack' ), 'WordPress' ); ?></a>
+ <a href="<?php echo esc_url( __( 'https://wordpress.org/', 'jetpack' ) ); ?>" rel="noopener noreferrer" target="_blank" title="<?php esc_attr_e( 'Semantic Personal Publishing Platform', 'jetpack' ); ?>" rel="generator"><?php printf( __( 'Proudly powered by %s', 'jetpack' ), 'WordPress' ); ?></a>
</div>
</footer><!-- #colophon -->
diff --git a/plugins/jetpack/modules/module-extras.php b/plugins/jetpack/modules/module-extras.php
index 368cf80b..01caab67 100644
--- a/plugins/jetpack/modules/module-extras.php
+++ b/plugins/jetpack/modules/module-extras.php
@@ -25,6 +25,7 @@ $tools = array(
'simple-payments/simple-payments.php',
'verification-tools/verification-tools-utils.php',
'woocommerce-analytics/wp-woocommerce-analytics.php',
+ 'geo-location.php'
);
// Not every tool needs to be included if Jetpack is inactive and not in development mode
diff --git a/plugins/jetpack/modules/module-headings.php b/plugins/jetpack/modules/module-headings.php
index 6fb48631..d5215490 100644
--- a/plugins/jetpack/modules/module-headings.php
+++ b/plugins/jetpack/modules/module-headings.php
@@ -83,6 +83,7 @@ function jetpack_get_module_i18n( $key ) {
'lazy-images' => array(
'name' => _x( 'Lazy Images', 'Module Name', 'jetpack' ),
'description' => _x( 'Lazy load images', 'Module Description', 'jetpack' ),
+ 'recommended description' => _x( 'Lazy-loading images improve your site\'s speed and create a smoother viewing experience. Images will load as visitors scroll down the screen, instead of all at once.', 'Jumpstart Description', 'jetpack' ),
),
'likes' => array(
diff --git a/plugins/jetpack/modules/protect.php b/plugins/jetpack/modules/protect.php
index 5a8b2e4f..48117d5e 100644
--- a/plugins/jetpack/modules/protect.php
+++ b/plugins/jetpack/modules/protect.php
@@ -50,7 +50,7 @@ class Jetpack_Protect_Module {
add_action( 'jetpack_activate_module_protect', array ( $this, 'on_activation' ) );
add_action( 'jetpack_deactivate_module_protect', array ( $this, 'on_deactivation' ) );
add_action( 'jetpack_modules_loaded', array ( $this, 'modules_loaded' ) );
- add_action( 'login_init', array ( $this, 'check_use_math' ) );
+ add_action( 'login_form', array ( $this, 'check_use_math' ), 0 );
add_filter( 'authenticate', array ( $this, 'check_preauth' ), 10, 3 );
add_action( 'wp_login', array ( $this, 'log_successful_login' ), 10, 2 );
add_action( 'wp_login_failed', array ( $this, 'log_failed_attempt' ) );
@@ -58,7 +58,7 @@ class Jetpack_Protect_Module {
add_action( 'admin_init', array ( $this, 'maybe_display_security_warning' ) );
// This is a backup in case $pagenow fails for some reason
- add_action( 'login_head', array ( $this, 'check_login_ability' ), 100, 3 );
+ add_action( 'login_form', array ( $this, 'check_login_ability' ), 1 );
// Runs a script every day to clean up expired transients so they don't
// clog up our users' databases
diff --git a/plugins/jetpack/modules/protect/blocked-login-page.php b/plugins/jetpack/modules/protect/blocked-login-page.php
index f26b5193..efba2b3e 100644
--- a/plugins/jetpack/modules/protect/blocked-login-page.php
+++ b/plugins/jetpack/modules/protect/blocked-login-page.php
@@ -600,7 +600,7 @@ class Jetpack_Protect_Blocked_Login_Page {
<a href='javascript:history.back()'><?php printf( __( '%s Back' ), $back_button_icon ); ?></a>
<?php } else {
$help_icon = '<svg class="gridicon gridicons-help" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm1 16h-2v-2h2v2zm0-4.14V15h-2v-2c0-.552.448-1 1-1 1.103 0 2-.897 2-2s-.897-2-2-2-2 .897-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.862-1.278 3.413-3 3.86z"/></g></svg>';?>
- <a href="<?php echo esc_url( self::HELP_URL ); ?>" target="_blank"><?php printf( __( '%s Get help unlocking your site' ), $help_icon );?></a>
+ <a href="<?php echo esc_url( self::HELP_URL ); ?>" rel="noopener noreferrer" target="_blank"><?php printf( __( '%s Get help unlocking your site' ), $help_icon );?></a>
<?php } ?>
</div>
</body>
diff --git a/plugins/jetpack/modules/publicize/ui.php b/plugins/jetpack/modules/publicize/ui.php
index 162c3ee0..5e2e84e7 100644
--- a/plugins/jetpack/modules/publicize/ui.php
+++ b/plugins/jetpack/modules/publicize/ui.php
@@ -149,7 +149,7 @@ class Publicize_UI {
}
?>
- <p>&rarr; <a href="<?php echo esc_url( $doc_link ); ?>" target="_blank"><?php esc_html_e( 'More information on using Publicize.', 'jetpack' ); ?></a></p>
+ <p>&rarr; <a href="<?php echo esc_url( $doc_link ); ?>" rel="noopener noreferrer" target="_blank"><?php esc_html_e( 'More information on using Publicize.', 'jetpack' ); ?></a></p>
<div id="publicize-services-block">
<?php
@@ -562,7 +562,7 @@ jQuery( function($) {
<strong><?php echo esc_html( $item ); ?></strong>
<?php endforeach; ?>
</span>
- <a href="#" id="publicize-form-edit"><?php esc_html_e( 'Edit', 'jetpack' ); ?></a>&nbsp;<a href="<?php echo esc_url( admin_url( 'options-general.php?page=sharing' ) ); ?>" target="_blank"><?php _e( 'Settings', 'jetpack' ); ?></a><br />
+ <a href="#" id="publicize-form-edit"><?php esc_html_e( 'Edit', 'jetpack' ); ?></a>&nbsp;<a href="<?php echo esc_url( admin_url( 'options-general.php?page=sharing' ) ); ?>" rel="noopener noreferrer" target="_blank"><?php _e( 'Settings', 'jetpack' ); ?></a><br />
<?php else : ?>
<?php $publicize_form = $this->get_metabox_form_disconnected( $available_services ); ?>
<strong><?php echo __( 'Not Connected', 'jetpack' ); ?></strong>
@@ -771,7 +771,7 @@ jQuery( function($) {
<ul class="not-connected">
<?php foreach ( $available_services as $service_name => $service ) : ?>
<li>
- <a class="pub-service" data-service="<?php echo esc_attr( $service_name ); ?>" title="<?php echo esc_attr( sprintf( __( 'Connect and share your posts on %s', 'jetpack' ), $this->publicize->get_service_label( $service_name ) ) ); ?>" target="_blank" href="<?php echo esc_url( $this->publicize->connect_url( $service_name ) ); ?>">
+ <a class="pub-service" data-service="<?php echo esc_attr( $service_name ); ?>" title="<?php echo esc_attr( sprintf( __( 'Connect and share your posts on %s', 'jetpack' ), $this->publicize->get_service_label( $service_name ) ) ); ?>" rel="noopener noreferrer" target="_blank" href="<?php echo esc_url( $this->publicize->connect_url( $service_name ) ); ?>">
<?php echo esc_html( $this->publicize->get_service_label( $service_name ) ); ?>
</a>
</li>
diff --git a/plugins/jetpack/modules/related-posts/jetpack-related-posts.php b/plugins/jetpack/modules/related-posts/jetpack-related-posts.php
index 751a66dd..37790d6b 100644
--- a/plugins/jetpack/modules/related-posts/jetpack-related-posts.php
+++ b/plugins/jetpack/modules/related-posts/jetpack-related-posts.php
@@ -384,7 +384,7 @@ EOT;
$ui_settings = sprintf(
$ui_settings_template,
checked( $options['show_headline'], true, false ),
- esc_html__( 'Show a "Related" header to more clearly separate the related section from posts', 'jetpack' ),
+ esc_html__( 'Highlight related content with a heading', 'jetpack' ),
checked( $options['show_thumbnails'], true, false ),
esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
checked( $options['show_date'], true, false ),
diff --git a/plugins/jetpack/modules/sharedaddy/sharedaddy.php b/plugins/jetpack/modules/sharedaddy/sharedaddy.php
index ec147d85..f8d0e92f 100644
--- a/plugins/jetpack/modules/sharedaddy/sharedaddy.php
+++ b/plugins/jetpack/modules/sharedaddy/sharedaddy.php
@@ -222,7 +222,7 @@ function sharing_plugin_settings( $links ) {
function sharing_add_plugin_settings($links, $file) {
if ( $file == basename( dirname( __FILE__ ) ).'/'.basename( __FILE__ ) ) {
$links[] = '<a href="options-general.php?page=sharing.php">' . __( 'Settings', 'jetpack' ) . '</a>';
- $links[] = '<a href="http://support.wordpress.com/sharing/" target="_blank">' . __( 'Support', 'jetpack' ) . '</a>';
+ $links[] = '<a href="http://support.wordpress.com/sharing/" rel="noopener noreferrer" target="_blank">' . __( 'Support', 'jetpack' ) . '</a>';
}
return $links;
diff --git a/plugins/jetpack/modules/sharedaddy/sharing-sources.php b/plugins/jetpack/modules/sharedaddy/sharing-sources.php
index 6df9b210..a086e0f0 100644
--- a/plugins/jetpack/modules/sharedaddy/sharing-sources.php
+++ b/plugins/jetpack/modules/sharedaddy/sharing-sources.php
@@ -205,7 +205,7 @@ abstract class Sharing_Source {
( $id ? esc_attr( $id ) : '' ),
implode( ' ', $klasses ),
$url,
- ( true == $this->open_link_in_new ) ? ' target="_blank"' : '',
+ ( true == $this->open_link_in_new ) ? ' rel="noopener noreferrer" target="_blank"' : '',
$title,
( 'icon' == $this->button_style ) ? '></span><span class="sharing-screen-reader-text"' : '',
$text
diff --git a/plugins/jetpack/modules/sharedaddy/sharing.php b/plugins/jetpack/modules/sharedaddy/sharing.php
index c6f57436..19b0e3be 100644
--- a/plugins/jetpack/modules/sharedaddy/sharing.php
+++ b/plugins/jetpack/modules/sharedaddy/sharing.php
@@ -183,7 +183,7 @@ class Sharing_Admin {
if ( false == function_exists( 'mb_stripos' ) ) {
echo '<div id="message" class="updated fade"><h3>' . __( 'Warning! Multibyte support missing!', 'jetpack' ) . '</h3>';
- echo '<p>' . sprintf( __( 'This plugin will work without it, but multibyte support is used <a href="%s" target="_blank">if available</a>. You may see minor problems with Tweets and other sharing services.', 'jetpack' ), 'http://www.php.net/manual/en/mbstring.installation.php' ) . '</p></div>';
+ echo '<p>' . sprintf( __( 'This plugin will work without it, but multibyte support is used <a href="%s" rel="noopener noreferrer" target="_blank">if available</a>. You may see minor problems with Tweets and other sharing services.', 'jetpack' ), 'http://www.php.net/manual/en/mbstring.installation.php' ) . '</p></div>';
}
if ( isset( $_GET['update'] ) && $_GET['update'] == 'saved' ) {
diff --git a/plugins/jetpack/modules/simple-payments/paypal-express-checkout.js b/plugins/jetpack/modules/simple-payments/paypal-express-checkout.js
index 5b070891..c166931a 100644
--- a/plugins/jetpack/modules/simple-payments/paypal-express-checkout.js
+++ b/plugins/jetpack/modules/simple-payments/paypal-express-checkout.js
@@ -76,7 +76,7 @@ var PaypalExpressCheckout = {
var cssClasses = PaypalExpressCheckout.messageCssClassName + ' show ';
cssClasses += isError ? 'error' : 'success';
- // show message 1s after Paypal popup is closed
+ // show message 1s after PayPal popup is closed
setTimeout( function() {
domEl.innerHTML = message;
domEl.setAttribute( 'class', cssClasses );
@@ -140,9 +140,9 @@ var PaypalExpressCheckout = {
style: {
label: 'pay',
- fundingicons: true,
shape: 'rect',
- color: 'silver'
+ color: 'silver',
+ fundingicons: true,
},
payment: function() {
diff --git a/plugins/jetpack/modules/simple-payments/simple-payments.php b/plugins/jetpack/modules/simple-payments/simple-payments.php
index a7e5371a..6ac3d8c7 100644
--- a/plugins/jetpack/modules/simple-payments/simple-payments.php
+++ b/plugins/jetpack/modules/simple-payments/simple-payments.php
@@ -27,7 +27,7 @@ class Jetpack_Simple_Payments {
return self::$instance;
}
- private function register_scripts() {
+ private function register_scripts_and_styles() {
/**
* Paypal heavily discourages putting that script in your own server:
* @see https://developer.paypal.com/docs/integration/direct/express-checkout/integration-jsv4/add-paypal-button/
@@ -35,18 +35,26 @@ class Jetpack_Simple_Payments {
wp_register_script( 'paypal-checkout-js', 'https://www.paypalobjects.com/api/checkout.js', array(), null, true );
wp_register_script( 'paypal-express-checkout', plugins_url( '/paypal-express-checkout.js', __FILE__ ),
array( 'jquery', 'paypal-checkout-js' ), self::$version );
+ wp_register_style( 'jetpack-simple-payments', plugins_url( '/simple-payments.css', __FILE__ ), array( 'dashicons' ) );
}
+
private function register_init_hook() {
add_action( 'init', array( $this, 'init_hook_action' ) );
}
+
private function register_shortcode() {
add_shortcode( self::$shortcode, array( $this, 'parse_shortcode' ) );
}
public function init_hook_action() {
+ if ( ! $this->is_enabled_jetpack_simple_payments() ) {
+ add_shortcode( self::$shortcode, array( $this, 'ignore_shortcode' ) );
+ return;
+ }
+
add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_rest_api_types' ) );
add_filter( 'jetpack_sync_post_meta_whitelist', array( $this, 'allow_sync_post_meta' ) );
- $this->register_scripts();
+ $this->register_scripts_and_styles();
$this->register_shortcode();
$this->setup_cpts();
@@ -69,6 +77,33 @@ class Jetpack_Simple_Payments {
return Jetpack_Options::get_option( 'id' );
}
+ /**
+ * Used to check whether Simple Payments are enabled for given site.
+ *
+ * @return bool True if Simple Payments are enabled, false otherwise.
+ */
+ function is_enabled_jetpack_simple_payments() {
+ /**
+ * Can be used by plugin authors to disable the conflicting output of Simple Payments.
+ *
+ * @since 6.3.0
+ *
+ * @param bool True if Simple Payments should be disabled, false otherwise.
+ */
+ if ( apply_filters( 'jetpack_disable_simple_payments', false ) ) {
+ return false;
+ }
+
+ // For WPCOM sites
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM && function_exists( 'has_blog_sticker' ) ) {
+ $site_id = $this->get_blog_id();
+ return has_blog_sticker( 'premium-plan', $site_id ) || has_blog_sticker( 'business-plan', $site_id );
+ }
+
+ // For all Jetpack sites
+ return Jetpack::is_active() && Jetpack::active_plan_supports( 'simple-payments');
+ }
+
function parse_shortcode( $attrs, $content = false ) {
if ( empty( $attrs['id'] ) ) {
return;
@@ -100,12 +135,14 @@ class Jetpack_Simple_Payments {
);
$data['id'] = $attrs['id'];
+
+ if( ! wp_style_is( 'jetpack-simple-payments', 'enqueue' ) ) {
+ wp_enqueue_style( 'jetpack-simple-payments' );
+ }
+
if ( ! wp_script_is( 'paypal-express-checkout', 'enqueued' ) ) {
wp_enqueue_script( 'paypal-express-checkout' );
}
- if ( ! wp_style_is( 'simple-payments', 'enqueued' ) ) {
- wp_enqueue_style( 'simple-payments', plugins_url( 'simple-payments.css', __FILE__ ), array( 'dashicons' ) );
- }
wp_add_inline_script( 'paypal-express-checkout', sprintf(
"try{PaypalExpressCheckout.renderButton( '%d', '%d', '%s', '%d' );}catch(e){}",
@@ -118,6 +155,8 @@ class Jetpack_Simple_Payments {
return $this->output_shortcode( $data );
}
+ function ignore_shortcode() { return; }
+
function output_shortcode( $data ) {
$items = '';
$css_prefix = self::$css_classname_prefix;
@@ -213,7 +252,7 @@ class Jetpack_Simple_Payments {
'read_private_posts' => 'read_private_posts',
);
$order_args = array(
- 'label' => esc_html__( 'Order', 'jetpack' ),
+ 'label' => esc_html_x( 'Order', 'noun: a quantity of goods or items purchased or sold', 'jetpack' ),
'description' => esc_html__( 'Simple Payments orders', 'jetpack' ),
'supports' => array( 'custom-fields', 'excerpt' ),
'hierarchical' => false,
diff --git a/plugins/jetpack/modules/sitemaps/sitemap-builder.php b/plugins/jetpack/modules/sitemaps/sitemap-builder.php
index 2af3b749..b83a7c49 100644
--- a/plugins/jetpack/modules/sitemaps/sitemap-builder.php
+++ b/plugins/jetpack/modules/sitemaps/sitemap-builder.php
@@ -475,13 +475,13 @@ class Jetpack_Sitemap_Builder {
if ( 0 < $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
if ( 1 === $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) {
$video['filename'] = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, 1 );
- $video['last_modified'] = $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'];
+ $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'] );
} else {
$video['filename'] = jp_sitemap_filename(
JP_VIDEO_SITEMAP_INDEX_TYPE,
$max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['number']
);
- $video['last_modified'] = $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'];
+ $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'] );
}
$buffer->append(
diff --git a/plugins/jetpack/modules/sitemaps/sitemap-stylist.php b/plugins/jetpack/modules/sitemaps/sitemap-stylist.php
index c5167d8b..2b4d2f0a 100644
--- a/plugins/jetpack/modules/sitemaps/sitemap-stylist.php
+++ b/plugins/jetpack/modules/sitemaps/sitemap-stylist.php
@@ -70,7 +70,7 @@ class Jetpack_Sitemap_Stylist {
$description = self::sanitize_with_links(
__(
- 'This is an XML Sitemap generated by <a href="%1$s" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" target="_blank">Google</a> or <a href="%3$s" target="_blank">Bing</a>.',
+ 'This is an XML Sitemap generated by <a href="%1$s" rel="noopener noreferrer" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" rel="noopener noreferrer" target="_blank">Google</a> or <a href="%3$s" rel="noopener noreferrer" target="_blank">Bing</a>.',
'jetpack'
),
array(
@@ -82,7 +82,7 @@ class Jetpack_Sitemap_Stylist {
$more_info = self::sanitize_with_links(
__(
- 'You can find more information on XML sitemaps at <a href="%1$s" target="_blank">sitemaps.org</a>',
+ 'You can find more information on XML sitemaps at <a href="%1$s" rel="noopener noreferrer" target="_blank">sitemaps.org</a>',
'jetpack'
),
array(
@@ -92,7 +92,7 @@ class Jetpack_Sitemap_Stylist {
$generated_by = self::sanitize_with_links(
__(
- 'Generated by <a href="%s" target="_blank">Jetpack for WordPress</a>',
+ 'Generated by <a href="%s" rel="noopener noreferrer" target="_blank">Jetpack for WordPress</a>',
'jetpack'
),
array(
@@ -182,7 +182,7 @@ XSL;
$description = self::sanitize_with_links(
__(
- 'This is an XML Sitemap Index generated by <a href="%1$s" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" target="_blank">Google</a> or <a href="%3$s" target="_blank">Bing</a>.',
+ 'This is an XML Sitemap Index generated by <a href="%1$s" rel="noopener noreferrer" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" rel="noopener noreferrer" target="_blank">Google</a> or <a href="%3$s" rel="noopener noreferrer" target="_blank">Bing</a>.',
'jetpack'
),
array(
@@ -194,7 +194,7 @@ XSL;
$more_info = self::sanitize_with_links(
__(
- 'You can find more information on XML sitemaps at <a href="%1$s" target="_blank">sitemaps.org</a>',
+ 'You can find more information on XML sitemaps at <a href="%1$s" rel="noopener noreferrer" target="_blank">sitemaps.org</a>',
'jetpack'
),
array(
@@ -204,7 +204,7 @@ XSL;
$generated_by = self::sanitize_with_links(
__(
- 'Generated by <a href="%s" target="_blank">Jetpack for WordPress</a>',
+ 'Generated by <a href="%s" rel="noopener noreferrer" target="_blank">Jetpack for WordPress</a>',
'jetpack'
),
array(
@@ -297,7 +297,7 @@ XSL;
$description = self::sanitize_with_links(
__(
- 'This is an XML Image Sitemap generated by <a href="%1$s" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" target="_blank">Google</a> or <a href="%3$s" target="_blank">Bing</a>.',
+ 'This is an XML Image Sitemap generated by <a href="%1$s" rel="noopener noreferrer" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" rel="noopener noreferrer" target="_blank">Google</a> or <a href="%3$s" rel="noopener noreferrer" target="_blank">Bing</a>.',
'jetpack'
),
array(
@@ -309,7 +309,7 @@ XSL;
$more_info = self::sanitize_with_links(
__(
- 'You can find more information on XML sitemaps at <a href="%1$s" target="_blank">sitemaps.org</a>',
+ 'You can find more information on XML sitemaps at <a href="%1$s" rel="noopener noreferrer" target="_blank">sitemaps.org</a>',
'jetpack'
),
array(
@@ -319,7 +319,7 @@ XSL;
$generated_by = self::sanitize_with_links(
__(
- 'Generated by <a href="%s" target="_blank">Jetpack for WordPress</a>',
+ 'Generated by <a href="%s" rel="noopener noreferrer" target="_blank">Jetpack for WordPress</a>',
'jetpack'
),
array(
@@ -437,7 +437,7 @@ XSL;
$description = self::sanitize_with_links(
__(
- 'This is an XML Video Sitemap generated by <a href="%1$s" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" target="_blank">Google</a> or <a href="%3$s" target="_blank">Bing</a>.',
+ 'This is an XML Video Sitemap generated by <a href="%1$s" rel="noopener noreferrer" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" rel="noopener noreferrer" target="_blank">Google</a> or <a href="%3$s" rel="noopener noreferrer" target="_blank">Bing</a>.',
'jetpack'
),
array(
@@ -449,7 +449,7 @@ XSL;
$more_info = self::sanitize_with_links(
__(
- 'You can find more information on XML sitemaps at <a href="%1$s" target="_blank">sitemaps.org</a>',
+ 'You can find more information on XML sitemaps at <a href="%1$s" rel="noopener noreferrer" target="_blank">sitemaps.org</a>',
'jetpack'
),
array(
@@ -459,7 +459,7 @@ XSL;
$generated_by = self::sanitize_with_links(
__(
- 'Generated by <a href="%s" target="_blank">Jetpack for WordPress</a>',
+ 'Generated by <a href="%s" rel="noopener noreferrer" target="_blank">Jetpack for WordPress</a>',
'jetpack'
),
array(
@@ -577,7 +577,7 @@ XSL;
$description = self::sanitize_with_links(
__(
- 'This is an XML News Sitemap generated by <a href="%1$s" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" target="_blank">Google</a> or <a href="%3$s" target="_blank">Bing</a>.',
+ 'This is an XML News Sitemap generated by <a href="%1$s" rel="noopener noreferrer" target="_blank">Jetpack</a>, meant to be consumed by search engines like <a href="%2$s" rel="noopener noreferrer" target="_blank">Google</a> or <a href="%3$s" rel="noopener noreferrer" target="_blank">Bing</a>.',
'jetpack'
),
array(
@@ -589,7 +589,7 @@ XSL;
$more_info = self::sanitize_with_links(
__(
- 'You can find more information on XML sitemaps at <a href="%1$s" target="_blank">sitemaps.org</a>',
+ 'You can find more information on XML sitemaps at <a href="%1$s" rel="noopener noreferrer" target="_blank">sitemaps.org</a>',
'jetpack'
),
array(
@@ -599,7 +599,7 @@ XSL;
$generated_by = self::sanitize_with_links(
__(
- 'Generated by <a href="%s" target="_blank">Jetpack for WordPress</a>',
+ 'Generated by <a href="%s" rel="noopener noreferrer" target="_blank">Jetpack for WordPress</a>',
'jetpack'
),
array(
diff --git a/plugins/jetpack/modules/sso/class.jetpack-sso-notices.php b/plugins/jetpack/modules/sso/class.jetpack-sso-notices.php
index f89de5ba..ca3c8baf 100644
--- a/plugins/jetpack/modules/sso/class.jetpack-sso-notices.php
+++ b/plugins/jetpack/modules/sso/class.jetpack-sso-notices.php
@@ -20,7 +20,7 @@ class Jetpack_SSO_Notices {
$error = sprintf(
wp_kses(
__(
- 'Two-Step Authentication is required to access this site. Please visit your <a href="%1$s" target="_blank">Security Settings</a> to configure <a href="%2$s" target="_blank">Two-step Authentication</a> for your account.',
+ 'Two-Step Authentication is required to access this site. Please visit your <a href="%1$s" rel="noopener noreferrer" target="_blank">Security Settings</a> to configure <a href="%2$s" rel="noopener noreferrer" target="_blank">Two-step Authentication</a> for your account.',
'jetpack'
),
array( 'a' => array( 'href' => array() ) )
diff --git a/plugins/jetpack/modules/stats.php b/plugins/jetpack/modules/stats.php
index fd20f517..220b1268 100644
--- a/plugins/jetpack/modules/stats.php
+++ b/plugins/jetpack/modules/stats.php
@@ -500,7 +500,7 @@ function stats_reports_css() {
}
#jp-stats-wrap {
- max-width: 720px;
+ max-width: 1040px;
margin: 0 auto;
overflow: hidden;
}
diff --git a/plugins/jetpack/modules/theme-tools.php b/plugins/jetpack/modules/theme-tools.php
index c9d82c45..b421021d 100644
--- a/plugins/jetpack/modules/theme-tools.php
+++ b/plugins/jetpack/modules/theme-tools.php
@@ -35,9 +35,10 @@ function jetpack_load_theme_compat() {
* @param array Associative array of theme compat files to load.
*/
$compat_files = apply_filters( 'jetpack_theme_compat_files', array(
- 'twentyfourteen' => JETPACK__PLUGIN_DIR . 'modules/theme-tools/compat/twentyfourteen.php',
- 'twentyfifteen' => JETPACK__PLUGIN_DIR . 'modules/theme-tools/compat/twentyfifteen.php',
- 'twentysixteen' => JETPACK__PLUGIN_DIR . 'modules/theme-tools/compat/twentysixteen.php',
+ 'twentyfourteen' => JETPACK__PLUGIN_DIR . 'modules/theme-tools/compat/twentyfourteen.php',
+ 'twentyfifteen' => JETPACK__PLUGIN_DIR . 'modules/theme-tools/compat/twentyfifteen.php',
+ 'twentysixteen' => JETPACK__PLUGIN_DIR . 'modules/theme-tools/compat/twentysixteen.php',
+ 'twentyseventeen' => JETPACK__PLUGIN_DIR . 'modules/theme-tools/compat/twentyseventeen.php',
) );
_jetpack_require_compat_file( get_stylesheet(), $compat_files );
diff --git a/plugins/jetpack/modules/theme-tools/compat/twentyfifteen.php b/plugins/jetpack/modules/theme-tools/compat/twentyfifteen.php
index b48a971f..adaa42b7 100644
--- a/plugins/jetpack/modules/theme-tools/compat/twentyfifteen.php
+++ b/plugins/jetpack/modules/theme-tools/compat/twentyfifteen.php
@@ -9,6 +9,11 @@ function twentyfifteen_jetpack_setup() {
* Add theme support for Responsive Videos.
*/
add_theme_support( 'jetpack-responsive-videos' );
+
+ /**
+ * Add theme support for geo-location.
+ */
+ add_theme_support( 'jetpack-geo-location' );
}
add_action( 'after_setup_theme', 'twentyfifteen_jetpack_setup' );
diff --git a/plugins/jetpack/modules/theme-tools/compat/twentyseventeen.php b/plugins/jetpack/modules/theme-tools/compat/twentyseventeen.php
new file mode 100644
index 00000000..4a60e504
--- /dev/null
+++ b/plugins/jetpack/modules/theme-tools/compat/twentyseventeen.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * Jetpack Compatibility File
+ * See: http://jetpack.com/
+ */
+
+function twentyseventeen_jetpack_setup() {
+ /**
+ * Add theme support for geo-location.
+ */
+ add_theme_support( 'jetpack-geo-location' );
+}
+add_action( 'after_setup_theme', 'twentyseventeen_jetpack_setup' );
diff --git a/plugins/jetpack/modules/theme-tools/compat/twentysixteen.php b/plugins/jetpack/modules/theme-tools/compat/twentysixteen.php
index 7e106037..20fd7f5d 100644
--- a/plugins/jetpack/modules/theme-tools/compat/twentysixteen.php
+++ b/plugins/jetpack/modules/theme-tools/compat/twentysixteen.php
@@ -9,6 +9,11 @@ function twentysixteen_jetpack_setup() {
* Add theme support for Responsive Videos.
*/
add_theme_support( 'jetpack-responsive-videos' );
+
+ /**
+ * Add theme support for geo-location.
+ */
+ add_theme_support( 'jetpack-geo-location' );
}
add_action( 'after_setup_theme', 'twentysixteen_jetpack_setup' );
diff --git a/plugins/jetpack/modules/theme-tools/content-options/customizer.php b/plugins/jetpack/modules/theme-tools/content-options/customizer.php
index 5123d98e..0fea7ae7 100644
--- a/plugins/jetpack/modules/theme-tools/content-options/customizer.php
+++ b/plugins/jetpack/modules/theme-tools/content-options/customizer.php
@@ -227,7 +227,7 @@ function jetpack_content_options_customize_register( $wp_customize ) {
$wp_customize->add_control( new Jetpack_Customize_Control_Title( $wp_customize, 'jetpack_content_featured_images_title', array(
'section' => 'jetpack_content_options',
- 'label' => esc_html__( 'Featured Images', 'jetpack' ) . sprintf( '<a href="https://en.support.wordpress.com/featured-images/" class="customize-help-toggle dashicons dashicons-editor-help" title="%1$s" target="_blank"><span class="screen-reader-text">%1$s</span></a>', esc_html__( 'Learn more about Featured Images', 'jetpack' ) ),
+ 'label' => esc_html__( 'Featured Images', 'jetpack' ) . sprintf( '<a href="https://en.support.wordpress.com/featured-images/" class="customize-help-toggle dashicons dashicons-editor-help" title="%1$s" rel="noopener noreferrer" target="_blank"><span class="screen-reader-text">%1$s</span></a>', esc_html__( 'Learn more about Featured Images', 'jetpack' ) ),
'type' => 'title',
'active_callback' => 'jetpack_post_thumbnail_supports',
) ) );
diff --git a/plugins/jetpack/modules/theme-tools/random-redirect.php b/plugins/jetpack/modules/theme-tools/random-redirect.php
index 2e58bee6..78c8f349 100644
--- a/plugins/jetpack/modules/theme-tools/random-redirect.php
+++ b/plugins/jetpack/modules/theme-tools/random-redirect.php
@@ -45,7 +45,7 @@ function jetpack_matt_random_redirect() {
// Persistent AppEngine abuse. ORDER BY RAND is expensive.
if ( strstr( $_SERVER['HTTP_USER_AGENT'], 'AppEngine-Google' ) )
- wp_die( 'Please <a href="http://en.support.wordpress.com/contact/" target="_blank">contact support</a>' );
+ wp_die( 'Please <a href="http://en.support.wordpress.com/contact/" rel="noopener noreferrer" target="_blank">contact support</a>' );
// Set the category ID if the parameter is set.
if ( isset( $_GET['random_cat_id'] ) )
diff --git a/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php b/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php
index 694e7bf6..2399cd39 100644
--- a/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php
+++ b/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php
@@ -19,11 +19,10 @@ abstract class Jetpack_Tiled_Gallery_Item {
}
$this->orig_file = wp_get_attachment_url( $this->image->ID );
- // If Photon is active, use it for original
- if ( in_array( 'photon', Jetpack::get_active_modules() ) ) {
- $this->orig_file = jetpack_photon_url( $this->orig_file );
- }
- $this->link = $needs_attachment_link ? get_attachment_link( $this->image->ID ) : $this->orig_file;
+ $this->link = $needs_attachment_link
+ ? get_attachment_link( $this->image->ID )
+ // The filter will photonize the URL if and only if Photon is active
+ : apply_filters( 'jetpack_photon_url', $this->orig_file );
$img_args = array(
'w' => $this->image->width,
@@ -33,6 +32,8 @@ abstract class Jetpack_Tiled_Gallery_Item {
if ( $this->image->height == $this->image->width ) {
$img_args['crop'] = true;
}
+ // The function will always photonoize the URL (even if Photon is
+ // not active). We need to photonize the URL to set the width/height.
$this->img_src = jetpack_photon_url( $this->orig_file, $img_args );
}
diff --git a/plugins/jetpack/modules/verification-tools/blog-verification-tools.php b/plugins/jetpack/modules/verification-tools/blog-verification-tools.php
index f8b4b4a5..b96bd371 100644
--- a/plugins/jetpack/modules/verification-tools/blog-verification-tools.php
+++ b/plugins/jetpack/modules/verification-tools/blog-verification-tools.php
@@ -151,7 +151,7 @@ function jetpack_verification_tool_box() {
$last = array_pop( $list );
if ( current_user_can( 'manage_options' ) ) {
- echo '<div class="jp-verification-tools card"><h3 class="title">' . __( 'Website Verification Services' , 'jetpack' ) . ' <a href="http://support.wordpress.com/webmaster-tools/" target="_blank">(?)</a></h3>';
+ echo '<div class="jp-verification-tools card"><h3 class="title">' . __( 'Website Verification Services' , 'jetpack' ) . ' <a href="http://support.wordpress.com/webmaster-tools/" rel="noopener noreferrer" target="_blank">(?)</a></h3>';
echo '<p>' . sprintf( esc_html( __( 'Enter your meta key "content" value to verify your blog with %s' , 'jetpack' ) ), implode( ', ', $list ) ) . ' ' . __( 'and' , 'jetpack' ) . ' ' . $last . '.</p>';
jetpack_verification_options_form();
echo '</div>';
diff --git a/plugins/jetpack/modules/videopress/class.videopress-player.php b/plugins/jetpack/modules/videopress/class.videopress-player.php
index 11abc45c..e301802b 100644
--- a/plugins/jetpack/modules/videopress/class.videopress-player.php
+++ b/plugins/jetpack/modules/videopress/class.videopress-player.php
@@ -292,7 +292,7 @@ class VideoPress_Player {
$html .= '<input type="submit" value="' . __( 'Submit', 'jetpack' ) . '" style="cursor:pointer;border-radius: 1em;border:1px solid #333;background-color:#333;background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0, #444), color-stop(1, #111) );background:-moz-linear-gradient(center top, #444 0%, #111 100%);font-size:13px;padding:4px 10px 5px;line-height:1em;vertical-align:top;color:white;text-decoration:none;margin:0" />';
$html .= '</fieldset>';
- $html .= '<p style="padding-top:20px;padding-bottom:60px;text-align:' . $text_align . ';"><a rel="nofollow" href="http://videopress.com/" target="_blank" style="color:rgb(128,128,128);text-decoration:underline;font-size:15px">' . __( 'More information', 'jetpack' ) . '</a></p>';
+ $html .= '<p style="padding-top:20px;padding-bottom:60px;text-align:' . $text_align . ';"><a rel="nofollow" href="http://videopress.com/" rel="noopener noreferrer" target="_blank" style="color:rgb(128,128,128);text-decoration:underline;font-size:15px">' . __( 'More information', 'jetpack' ) . '</a></p>';
$html .= '</div>';
return $html;
@@ -341,7 +341,7 @@ class VideoPress_Player {
$html .= esc_attr( $this->video->title );
$html .= '" src="' . $thumbnail . '" width="' . $this->video->calculated_width . '" height="' . $this->video->calculated_height . '" /></div>';
if ( isset( $this->options['freedom'] ) && $this->options['freedom'] === true )
- $html .= '<p class="robots-nocontent">' . sprintf( __( 'You do not have sufficient <a rel="nofollow" href="%s" target="_blank">freedom levels</a> to view this video. Support free software and upgrade.', 'jetpack' ), 'http://www.gnu.org/philosophy/free-sw.html' ) . '</p>';
+ $html .= '<p class="robots-nocontent">' . sprintf( __( 'You do not have sufficient <a rel="nofollow" href="%s" rel="noopener noreferrer" target="_blank">freedom levels</a> to view this video. Support free software and upgrade.', 'jetpack' ), 'http://www.gnu.org/philosophy/free-sw.html' ) . '</p>';
elseif ( isset( $this->video->title ) )
$html .= '<p>' . esc_html( $this->video->title ) . '</p>';
$html .= '</video>';
@@ -795,7 +795,7 @@ class VideoPress_Player {
foreach ( $this->get_flash_parameters() as $attribute => $value ) {
$flash_params .= '<param name="' . esc_attr( $attribute ) . '" value="' . esc_attr( $value ) . '" />';
}
- $flash_help = sprintf( __( 'This video requires <a rel="nofollow" href="%s" target="_blank">Adobe Flash</a> for playback.', 'jetpack' ), 'http://www.adobe.com/go/getflashplayer');
+ $flash_help = sprintf( __( 'This video requires <a rel="nofollow" href="%s" rel="noopener noreferrer" target="_blank">Adobe Flash</a> for playback.', 'jetpack' ), 'http://www.adobe.com/go/getflashplayer');
$flash_player_url = esc_url( $this->video->players->swf->url, array( 'http', 'https' ) );
$description = '';
if ( isset( $this->video->title ) ) {
diff --git a/plugins/jetpack/modules/widgets/customizer-utils.js b/plugins/jetpack/modules/widgets/customizer-utils.js
index e14c5f16..da73225c 100644
--- a/plugins/jetpack/modules/widgets/customizer-utils.js
+++ b/plugins/jetpack/modules/widgets/customizer-utils.js
@@ -1,4 +1,4 @@
-/* global wp, gapi, FB, twttr */
+/* global wp, gapi, FB, twttr, PaypalExpressCheckout */
/**
* Utilities to work with widgets in Customizer.
@@ -64,6 +64,16 @@ wp.isJetpackWidgetPlaced = function( placement, widgetName ) {
$( '.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
+ }
}
}
} );
@@ -74,6 +84,9 @@ wp.isJetpackWidgetPlaced = function( placement, widgetName ) {
// 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();
}
}
} );
diff --git a/plugins/jetpack/modules/widgets/simple-payments.php b/plugins/jetpack/modules/widgets/simple-payments.php
new file mode 100644
index 00000000..f31af1b0
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments.php
@@ -0,0 +1,487 @@
+<?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 Payment 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 Payment Button as a Widget.', 'jetpack' ),
+ 'customize_selective_refresh' => true,
+ )
+ );
+
+ if ( is_customize_preview() ) {
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_styles_and_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_and_scripts(){
+ wp_enqueue_style( 'jetpack-simple-payments-widget-customizer', plugins_url( 'simple-payments/customizer.css', __FILE__ ) );
+
+ 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 );
+ }
+
+ 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' );
+ }
+
+ if ( empty( $params['post_title'] ) ) {
+ $errors->add( 'post_title', __( 'People need to know what they\'re paying for! Please add a brief title.' ) );
+ }
+
+ if ( empty( $params['price'] ) || floatval( $params['price'] ) <= 0 ) {
+ $errors->add( 'price', __( 'Everything comes with a price tag these days. Please add a your product price.' ) );
+ }
+
+ 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.' ) );
+ }
+
+ 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 ) {
+ $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() {
+ $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/customizer.css b/plugins/jetpack/modules/widgets/simple-payments/customizer.css
new file mode 100644
index 00000000..48733d51
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/customizer.css
@@ -0,0 +1,72 @@
+.widget-content .jetpack-simple-payments,
+.widget-content .jetpack-simple-payments-form {
+ clear: both;
+}
+
+.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..8930ebba
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/customizer.js
@@ -0,0 +1,373 @@
+/* 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 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( parseFloat( productPrice ) ) || parseFloat( productPrice ) <= 0 ) {
+ 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..483c4661
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/form.php
@@ -0,0 +1,155 @@
+<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 $this->get_field_id('product_post_id'); ?>"><?php _e( 'Select a Simple Payment Button:', 'jetpack' ); ?></label>
+ <select
+ class="widefat jetpack-simple-payments-products"
+ id="<?php echo $this->get_field_id('product_post_id'); ?>"
+ name="<?php echo $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 echo __( 'Looks like you don\'t have any products. You can create one using the Add New button below.' ) ?>
+</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' ); ?>
+ </button>
+ </div>
+ <div class="alignright">
+ <button class="button jetpack-simple-payments-add-product"><?php esc_html_e( 'Add New' ); ?></button>
+ </div>
+ <br class="clear">
+</p>
+<hr />
+<div class="jetpack-simple-payments-form" style="display: none;">
+ <input
+ type="hidden"
+ id="<?php echo $this->get_field_id('form_action'); ?>"
+ name="<?php echo $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 $this->get_field_id('form_product_id'); ?>"
+ name="<?php echo $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?' ); ?></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 _e( 'For example: event tickets, charitable donations, training courses, coaching fees, etc.' ); ?></small>
+ </p>
+ <div class="jetpack-simple-payments-image-fieldset">
+ <label><?php esc_html_e( 'Product image' ); ?></label>
+ <div class="placeholder" <?php if ( ! empty( $instance['form_product_image_id'] ) ) echo 'style="display:none;"'; ?>><?php esc_html_e( 'Select an image' ); ?></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' ); ?></button>
+ </div>
+ </div>
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_description' ) ); ?>"><?php esc_html_e( 'Description' ); ?></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 esc_html_e( $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' ); ?></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 esc_html_e( $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.' ); ?></label>
+ </p>
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'form_product_email' ) ); ?>"><?php esc_html_e( 'Email' ); ?></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( esc_html__( 'This is where PayPal will send your money. To claim a payment, you\'ll need a %1$sPayPal account%2$s connected to a bank account.' ), '<a href="https://paypal.com" target="_blank">', '</a>' ) ?></small>
+ </p>
+ <p>
+ <div class="alignleft">
+ <button type="button" class="button-link button-link-delete jetpack-simple-payments-delete-product"><?php _e( 'Delete Product' ); ?></button>
+ </div>
+ <div class="alignright">
+ <button name="<?php echo $this->get_field_name('save'); ?>" class="button jetpack-simple-payments-save-product"><?php _e( 'Save' ); ?></button>
+ <span> | <button type="button" class="button-link jetpack-simple-payments-cancel-form"><?php _e( 'Cancel' ); ?></button></span>
+ </div>
+ <br class="clear">
+ </p>
+ <hr />
+</div>
+<?php } else { ?>
+<p class="jetpack-simple-payments-products-warning">
+ <?php
+ echo sprintf(
+ wp_kses(
+ __( '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>.' ),
+ 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..740cbd74
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/simple-payments/widget.php
@@ -0,0 +1,25 @@
+<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 esc_attr_e( $instance['form_product_title'] ); ?></p></div>
+ <div class='jetpack-simple-payments-description'><p><?php esc_html_e( $instance['form_product_description'] ); ?></p></div>
+ <div class='jetpack-simple-payments-price'><p><?php esc_attr_e( $instance['form_product_price'] ); ?> <?php esc_attr_e( $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/wordpress-post-widget.php b/plugins/jetpack/modules/widgets/wordpress-post-widget.php
index 95856d1e..bb465285 100644
--- a/plugins/jetpack/modules/widgets/wordpress-post-widget.php
+++ b/plugins/jetpack/modules/widgets/wordpress-post-widget.php
@@ -15,6 +15,9 @@ 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' );
@@ -111,1058 +114,3 @@ function jetpack_display_posts_widget_conditionally_activate_cron() {
*/
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' );
-
-/**
- * End of Cron tasks
- */
-/*
- * Display a list of recent posts from a WordPress.com or Jetpack-enabled blog.
- */
-
-class Jetpack_Display_Posts_Widget extends WP_Widget {
-
- /**
- * @var string Remote service API URL prefix.
- */
- public $service_url = 'https://public-api.wordpress.com/rest/v1.1/';
-
- /**
- * @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';
-
-
- 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' ) );
- }
- }
-
- /**
- * 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;
- }
-
- /**
- * 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;
-
- }
-
- /**
- * 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;
-
- }
-
- /**
- * Update a widget instance.
- *
- * @param string $site The site to fetch the latest data for.
- */
- 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 );
- }
- }
-
- /**
- * 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 );
- }
-
- /**
- * 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;
- }
-
- /**
- * Enqueue CSS and JavaScript.
- *
- * @since 4.0.0
- */
- public function enqueue_scripts() {
- wp_enqueue_style( 'jetpack_display_posts_widget', plugins_url( 'wordpress-post-widget/style.css', __FILE__ ) );
- }
-
- /**
- * 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 : '';
-
- /**
- * Forcefully activate the update cron when saving widget instance.
- *
- * So we can be sure that it will be running later.
- */
- $this->activate_cron();
-
-
- /**
- * 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;
- }
-
- /**
- * 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 );
- }
-
-
- /**
- * 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-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 );
+ }
+}