summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony G. Basile <blueness@gentoo.org>2018-03-10 19:17:40 -0500
committerAnthony G. Basile <blueness@gentoo.org>2018-03-10 19:17:40 -0500
commit1ede1db458d07b50cfede5937958cb20752df616 (patch)
treeb7484d24649fb07b8a591148ada617b14d8bbc6d /plugins/jetpack/modules
parentUpdate akismet 4.0.3 (diff)
downloadblogs-gentoo-1ede1db458d07b50cfede5937958cb20752df616.tar.gz
blogs-gentoo-1ede1db458d07b50cfede5937958cb20752df616.tar.bz2
blogs-gentoo-1ede1db458d07b50cfede5937958cb20752df616.zip
Update jetpack 5.9
Signed-off-by: Anthony G. Basile <blueness@gentoo.org>
Diffstat (limited to 'plugins/jetpack/modules')
-rw-r--r--plugins/jetpack/modules/after-the-deadline.php40
-rw-r--r--plugins/jetpack/modules/carousel/jetpack-carousel.php16
-rw-r--r--plugins/jetpack/modules/comment-likes.php10
-rw-r--r--plugins/jetpack/modules/comments/comments.php5
-rw-r--r--plugins/jetpack/modules/contact-form/admin.php9
-rw-r--r--plugins/jetpack/modules/contact-form/grunion-contact-form.php9
-rw-r--r--plugins/jetpack/modules/contact-form/grunion-editor-view.php16
-rw-r--r--plugins/jetpack/modules/contact-form/grunion-form-view.php11
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/data.inc.php22
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css-4.7.php21
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css.php22
-rw-r--r--plugins/jetpack/modules/custom-post-types/comics.php9
-rw-r--r--plugins/jetpack/modules/custom-post-types/nova.php34
-rw-r--r--plugins/jetpack/modules/custom-post-types/portfolios.php2
-rw-r--r--plugins/jetpack/modules/custom-post-types/testimonial.php1
-rw-r--r--plugins/jetpack/modules/google-analytics/classes/wp-google-analytics-legacy.php17
-rw-r--r--plugins/jetpack/modules/holiday-snow/snowstorm.js4
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.php11
-rw-r--r--plugins/jetpack/modules/lazy-images.php18
-rw-r--r--plugins/jetpack/modules/lazy-images/js/lazy-images.js746
-rw-r--r--plugins/jetpack/modules/lazy-images/lazy-images.php166
-rw-r--r--plugins/jetpack/modules/likes.php31
-rw-r--r--plugins/jetpack/modules/manage/confirm-admin.php4
-rw-r--r--plugins/jetpack/modules/masterbar/masterbar.php33
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php26
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/image.php2
-rw-r--r--plugins/jetpack/modules/module-extras.php1
-rw-r--r--plugins/jetpack/modules/module-headings.php5
-rw-r--r--plugins/jetpack/modules/module-info.php36
-rw-r--r--plugins/jetpack/modules/photon.php2
-rw-r--r--plugins/jetpack/modules/protect.php12
-rw-r--r--plugins/jetpack/modules/protect/blocked-login-page.php352
-rw-r--r--plugins/jetpack/modules/protect/jetpack-security.pngbin12131 -> 0 bytes
-rw-r--r--plugins/jetpack/modules/protect/math-fallback.php8
-rw-r--r--plugins/jetpack/modules/protect/protect.pngbin0 -> 14439 bytes
-rw-r--r--plugins/jetpack/modules/publicize/publicize.php2
-rw-r--r--plugins/jetpack/modules/publicize/ui.php5
-rw-r--r--plugins/jetpack/modules/related-posts/class.related-posts-customize.php19
-rw-r--r--plugins/jetpack/modules/related-posts/jetpack-related-posts.php10
-rw-r--r--plugins/jetpack/modules/search.php1
-rw-r--r--plugins/jetpack/modules/search/class.jetpack-search-helpers.php699
-rw-r--r--plugins/jetpack/modules/search/class.jetpack-search-template-tags.php225
-rw-r--r--plugins/jetpack/modules/search/class.jetpack-search-widget-filters.php126
-rw-r--r--plugins/jetpack/modules/search/class.jetpack-search.php982
-rw-r--r--plugins/jetpack/modules/seo-tools.php3
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharedaddy.php13
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing-service.php10
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing-sources.php2
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing.js16
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing.php16
-rw-r--r--plugins/jetpack/modules/shortcodes/brightcove.php2
-rw-r--r--plugins/jetpack/modules/shortcodes/getty.php131
-rw-r--r--plugins/jetpack/modules/shortcodes/mailchimp.php2
-rw-r--r--plugins/jetpack/modules/shortcodes/slideshow.php19
-rw-r--r--plugins/jetpack/modules/sso/class.jetpack-sso-helpers.php18
-rw-r--r--plugins/jetpack/modules/stats.php3
-rw-r--r--plugins/jetpack/modules/theme-tools/featured-content.php16
-rw-r--r--plugins/jetpack/modules/theme-tools/social-links.php2
-rw-r--r--plugins/jetpack/modules/tiled-gallery/tiled-gallery.php15
-rw-r--r--plugins/jetpack/modules/videopress/class.jetpack-videopress.php15
-rw-r--r--plugins/jetpack/modules/videopress/editor-media-view.php11
-rw-r--r--plugins/jetpack/modules/widget-visibility/widget-conditions.php11
-rw-r--r--plugins/jetpack/modules/widget-visibility/widget-conditions/widget-conditions.js10
-rw-r--r--plugins/jetpack/modules/widgets/contact-info.php10
-rw-r--r--plugins/jetpack/modules/widgets/eu-cookie-law.php11
-rw-r--r--plugins/jetpack/modules/widgets/gallery.php28
-rw-r--r--plugins/jetpack/modules/widgets/goodreads.php11
-rw-r--r--plugins/jetpack/modules/widgets/google-translate.php8
-rw-r--r--plugins/jetpack/modules/widgets/googleplus-badge.php9
-rw-r--r--plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php1
-rw-r--r--plugins/jetpack/modules/widgets/milestone/milestone.php22
-rw-r--r--plugins/jetpack/modules/widgets/search.php760
-rw-r--r--plugins/jetpack/modules/widgets/search/css/search-widget-admin-ui.css87
-rw-r--r--plugins/jetpack/modules/widgets/search/css/search-widget-frontend.css66
-rw-r--r--plugins/jetpack/modules/widgets/search/js/search-widget-admin.js360
-rw-r--r--plugins/jetpack/modules/widgets/search/js/search-widget.js15
-rw-r--r--plugins/jetpack/modules/widgets/top-posts.php2
-rw-r--r--plugins/jetpack/modules/widgets/twitter-timeline.php8
-rw-r--r--plugins/jetpack/modules/woocommerce-analytics/classes/wp-woocommerce-analytics-universal.php328
-rw-r--r--plugins/jetpack/modules/woocommerce-analytics/classes/wp-woocommerce-analytics-utils.php51
-rw-r--r--plugins/jetpack/modules/woocommerce-analytics/wp-woocommerce-analytics.php96
-rw-r--r--plugins/jetpack/modules/wordads/php/params.php2
-rw-r--r--plugins/jetpack/modules/wordads/php/widgets.php27
-rw-r--r--plugins/jetpack/modules/wordads/wordads.php127
84 files changed, 5334 insertions, 780 deletions
diff --git a/plugins/jetpack/modules/after-the-deadline.php b/plugins/jetpack/modules/after-the-deadline.php
index 47af703f..a30b38be 100644
--- a/plugins/jetpack/modules/after-the-deadline.php
+++ b/plugins/jetpack/modules/after-the-deadline.php
@@ -196,11 +196,43 @@ function AtD_settings() {
function AtD_load_javascripts() {
if ( AtD_should_load_on_page() ) {
- wp_enqueue_script( 'AtD_core', plugins_url( '/after-the-deadline/atd.core.js', __FILE__ ), array(), ATD_VERSION );
- wp_enqueue_script( 'AtD_quicktags', plugins_url( '/after-the-deadline/atd-nonvis-editor-plugin.js', __FILE__ ), array('quicktags'), ATD_VERSION );
- wp_enqueue_script( 'AtD_jquery', plugins_url( '/after-the-deadline/jquery.atd.js', __FILE__ ), array('jquery'), ATD_VERSION );
+ wp_enqueue_script(
+ 'AtD_core',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/after-the-deadline/atd.core.min.js',
+ 'modules/after-the-deadline/atd.core.js'
+ ),
+ array(),
+ ATD_VERSION
+ );
+ wp_enqueue_script(
+ 'AtD_quicktags',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/after-the-deadline/atd-nonvis-editor-plugin.min.js',
+ 'modules/after-the-deadline/atd-nonvis-editor-plugin.js'
+ ),
+ array('quicktags'),
+ ATD_VERSION
+ );
+ wp_enqueue_script(
+ 'AtD_jquery',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/after-the-deadline/jquery.atd.min.js',
+ 'modules/after-the-deadline/jquery.atd.js'
+ ),
+ array('jquery'),
+ ATD_VERSION
+ );
wp_enqueue_script( 'AtD_settings', admin_url() . 'admin-ajax.php?action=atd_settings', array('AtD_jquery'), ATD_VERSION );
- wp_enqueue_script( 'AtD_autoproofread', plugins_url( '/after-the-deadline/atd-autoproofread.js', __FILE__ ), array('AtD_jquery'), ATD_VERSION );
+ wp_enqueue_script(
+ 'AtD_autoproofread',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/after-the-deadline/atd-autoproofread.min.js',
+ 'modules/after-the-deadline/atd-autoproofread.js'
+ ),
+ array('AtD_jquery'),
+ ATD_VERSION
+ );
/* load localized strings for AtD */
wp_localize_script( 'AtD_core', 'AtD_l10n_r0ar', array (
diff --git a/plugins/jetpack/modules/carousel/jetpack-carousel.php b/plugins/jetpack/modules/carousel/jetpack-carousel.php
index e75b94cd..6b76fb0e 100644
--- a/plugins/jetpack/modules/carousel/jetpack-carousel.php
+++ b/plugins/jetpack/modules/carousel/jetpack-carousel.php
@@ -197,7 +197,16 @@ class Jetpack_Carousel {
function enqueue_assets() {
if ( $this->first_run ) {
- wp_enqueue_script( 'jetpack-carousel', plugins_url( 'jetpack-carousel.js', __FILE__ ), array( 'jquery.spin' ), $this->asset_version( '20170209' ), true );
+ wp_enqueue_script(
+ 'jetpack-carousel',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/carousel/jetpack-carousel.min.js',
+ 'modules/carousel/jetpack-carousel.js'
+ ),
+ array( 'jquery.spin' ),
+ $this->asset_version( '20170209' ),
+ true
+ );
// Note: using home_url() instead of admin_url() for ajaxurl to be sure to get same domain on wpcom when using mapped domains (also works on self-hosted)
// Also: not hardcoding path since there is no guarantee site is running on site root in self-hosted context.
@@ -365,7 +374,8 @@ class Jetpack_Carousel {
$attachments = get_posts( array(
'include' => array_keys( $selected_images ),
'post_type' => 'any',
- 'post_status' => 'any'
+ 'post_status' => 'any',
+ 'suppress_filters' => false,
) );
foreach ( $attachments as $attachment ) {
@@ -432,7 +442,7 @@ class Jetpack_Carousel {
unset( $img_meta['keywords'] );
}
- $img_meta = json_encode( array_map( 'strval', $img_meta ) );
+ $img_meta = json_encode( array_map( 'strval', array_filter( $img_meta, 'is_scalar' ) ) );
$attr['data-attachment-id'] = $attachment_id;
$attr['data-permalink'] = esc_attr( get_permalink( $attachment->ID ) );
diff --git a/plugins/jetpack/modules/comment-likes.php b/plugins/jetpack/modules/comment-likes.php
index c6e5ae8e..00c5a0bf 100644
--- a/plugins/jetpack/modules/comment-likes.php
+++ b/plugins/jetpack/modules/comment-likes.php
@@ -103,7 +103,15 @@ class Jetpack_Comment_Likes {
function enqueue_admin_styles_scripts() {
wp_enqueue_style( 'comment-like-count', plugins_url( 'comment-likes/admin-style.css', __FILE__ ), array(), JETPACK__VERSION );
- wp_enqueue_script( 'comment-like-count', plugins_url( 'comment-likes/comment-like-count.js', __FILE__ ), array( 'jquery' ), JETPACK__VERSION );
+ wp_enqueue_script(
+ 'comment-like-count',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/comment-likes/comment-like-count.min.js',
+ 'modules/comment-likes/comment-like-count.js'
+ ),
+ array( 'jquery' ),
+ JETPACK__VERSION
+ );
}
public function add_like_count_column( $columns ) {
diff --git a/plugins/jetpack/modules/comments/comments.php b/plugins/jetpack/modules/comments/comments.php
index 9160a164..2f317750 100644
--- a/plugins/jetpack/modules/comments/comments.php
+++ b/plugins/jetpack/modules/comments/comments.php
@@ -112,11 +112,10 @@ class Jetpack_Comments extends Highlander_Comments_Base {
// Selfishly remove everything from the existing comment form
remove_all_actions( 'comment_form_before' );
- remove_all_actions( 'comment_form_after' );
// Selfishly add only our actions back to the comment form
add_action( 'comment_form_before', array( $this, 'comment_form_before' ) );
- add_action( 'comment_form_after', array( $this, 'comment_form_after' ) );
+ add_action( 'comment_form_after', array( $this, 'comment_form_after' ), 1 ); // Set very early since we remove everything outputed before our action.
// Before a comment is posted
add_action( 'pre_comment_on_post', array( $this, 'pre_comment_on_post' ), 1 );
@@ -616,4 +615,4 @@ class Jetpack_Comments extends Highlander_Comments_Base {
}
}
-Jetpack_Comments::init(); \ No newline at end of file
+Jetpack_Comments::init();
diff --git a/plugins/jetpack/modules/contact-form/admin.php b/plugins/jetpack/modules/contact-form/admin.php
index 24329bcc..e09730a3 100644
--- a/plugins/jetpack/modules/contact-form/admin.php
+++ b/plugins/jetpack/modules/contact-form/admin.php
@@ -799,7 +799,14 @@ function grunion_enable_spam_recheck() {
}
// Add the scripts that handle the spam check event.
- wp_register_script( 'grunion-admin', plugin_dir_url( __FILE__ ) . 'js/grunion-admin.js', array( 'jquery' ) );
+ wp_register_script(
+ 'grunion-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/grunion-admin.min.js',
+ 'modules/contact-form/js/grunion-admin.js'
+ ),
+ array( 'jquery' )
+ );
wp_enqueue_script( 'grunion-admin' );
wp_enqueue_style( 'grunion.css' );
diff --git a/plugins/jetpack/modules/contact-form/grunion-contact-form.php b/plugins/jetpack/modules/contact-form/grunion-contact-form.php
index 3323f5e8..7bb7c47a 100644
--- a/plugins/jetpack/modules/contact-form/grunion-contact-form.php
+++ b/plugins/jetpack/modules/contact-form/grunion-contact-form.php
@@ -2683,7 +2683,14 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode {
$r .= "\t\t<input type='text' name='" . esc_attr( $field_id ) . "' id='" . esc_attr( $field_id ) . "' value='" . esc_attr( $field_value ) . "' " . $field_class . ( $field_required ? "required aria-required='true'" : '' ) . "/>\n";
$r .= "\t</div>\n";
- wp_enqueue_script( 'grunion-frontend', plugins_url( 'js/grunion-frontend.js', __FILE__ ), array( 'jquery', 'jquery-ui-datepicker' ) );
+ wp_enqueue_script(
+ 'grunion-frontend',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/grunion-frontend.min.js',
+ 'modules/contact-form/js/grunion-frontend.js'
+ ),
+ array( 'jquery', 'jquery-ui-datepicker' )
+ );
wp_enqueue_style( 'jp-jquery-ui-datepicker', plugins_url( 'css/jquery-ui-datepicker.css', __FILE__ ), array( 'dashicons' ), '1.0' );
// Using Core's built-in datepicker localization routine
diff --git a/plugins/jetpack/modules/contact-form/grunion-editor-view.php b/plugins/jetpack/modules/contact-form/grunion-editor-view.php
index ecdcf76e..e012894f 100644
--- a/plugins/jetpack/modules/contact-form/grunion-editor-view.php
+++ b/plugins/jetpack/modules/contact-form/grunion-editor-view.php
@@ -39,7 +39,10 @@ class Grunion_Editor_View {
}
public static function mce_external_plugins( $plugin_array ) {
- $plugin_array['grunion_form'] = plugins_url( 'js/tinymce-plugin-form-button.js', __FILE__ );
+ $plugin_array['grunion_form'] = Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/tinymce-plugin-form-button.min.js',
+ 'modules/contact-form/js/tinymce-plugin-form-button.js'
+ );
return $plugin_array;
}
@@ -64,7 +67,16 @@ class Grunion_Editor_View {
wp_enqueue_style( 'grunion-editor-ui', plugins_url( 'css/editor-ui.css', __FILE__ ) );
wp_style_add_data( 'grunion-editor-ui', 'rtl', 'replace' );
- wp_enqueue_script( 'grunion-editor-view', plugins_url( 'js/editor-view.js', __FILE__ ), array( 'wp-util', 'jquery', 'quicktags' ), false, true );
+ wp_enqueue_script(
+ 'grunion-editor-view',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/editor-view.min.js',
+ 'modules/contact-form/js/editor-view.js'
+ ),
+ array( 'wp-util', 'jquery', 'quicktags' ),
+ false,
+ true
+ );
wp_localize_script( 'grunion-editor-view', 'grunionEditorView', array(
'inline_editing_style' => plugins_url( 'css/editor-inline-editing-style.css', __FILE__ ),
'inline_editing_style_rtl' => plugins_url( 'css/editor-inline-editing-style-rtl.css', __FILE__ ),
diff --git a/plugins/jetpack/modules/contact-form/grunion-form-view.php b/plugins/jetpack/modules/contact-form/grunion-form-view.php
index e9c9a736..7417965e 100644
--- a/plugins/jetpack/modules/contact-form/grunion-form-view.php
+++ b/plugins/jetpack/modules/contact-form/grunion-form-view.php
@@ -14,7 +14,16 @@
*/
$max_new_fields = apply_filters( 'grunion_max_new_fields', 5 );
-wp_register_script( 'grunion', GRUNION_PLUGIN_URL . 'js/grunion.js', array( 'jquery-ui-sortable', 'jquery-ui-draggable' ), JETPACK__VERSION );
+wp_register_script(
+ 'grunion',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/grunion.min.js',
+ 'modules/contact-form/js/grunion.js'
+ ),
+ array( 'jquery-ui-sortable', 'jquery-ui-draggable' ),
+ JETPACK__VERSION
+);
+
wp_localize_script( 'grunion', 'GrunionFB_i18n', array(
'nameLabel' => esc_attr( _x( 'Name', 'Label for HTML form "Name" field in contact form builder', 'jetpack' ) ),
'emailLabel' => esc_attr( _x( 'Email', 'Label for HTML form "Email" field in contact form builder', 'jetpack' ) ),
diff --git a/plugins/jetpack/modules/custom-css/csstidy/data.inc.php b/plugins/jetpack/modules/custom-css/csstidy/data.inc.php
index f65869a6..9ed2e1c4 100644
--- a/plugins/jetpack/modules/custom-css/csstidy/data.inc.php
+++ b/plugins/jetpack/modules/custom-css/csstidy/data.inc.php
@@ -424,8 +424,26 @@ $GLOBALS['csstidy']['all_properties']['font-stretch'] = 'CSS2.0,CSS3.0';
$GLOBALS['csstidy']['all_properties']['font-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
$GLOBALS['csstidy']['all_properties']['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
$GLOBALS['csstidy']['all_properties']['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-area'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-auto-columns'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-auto-flow'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-auto-rows'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-column'] = 'CSS3.0';
$GLOBALS['csstidy']['all_properties']['grid-columns'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-column-end'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-column-gap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-column-start'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-gap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-row'] = 'CSS3.0';
$GLOBALS['csstidy']['all_properties']['grid-rows'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-row-end'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-row-gap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-row-start'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-template'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-template-areas'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-template-columns'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-template-rows'] = 'CSS3.0';
$GLOBALS['csstidy']['all_properties']['hanging-punctuation'] = 'CSS3.0';
$GLOBALS['csstidy']['all_properties']['height'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
$GLOBALS['csstidy']['all_properties']['hyphenate-after'] = 'CSS3.0';
@@ -440,6 +458,8 @@ $GLOBALS['csstidy']['all_properties']['image-rendering'] = 'CSS3.0';
$GLOBALS['csstidy']['all_properties']['image-resolution'] = 'CSS3.0';
$GLOBALS['csstidy']['all_properties']['inline-box-align'] = 'CSS3.0';
$GLOBALS['csstidy']['all_properties']['justify-content'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['justify-items'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['justify-self'] = 'CSS3.0';
$GLOBALS['csstidy']['all_properties']['left'] = 'CSS2.0,CSS2.1,CSS3.0';
$GLOBALS['csstidy']['all_properties']['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
$GLOBALS['csstidy']['all_properties']['line-break'] = 'CSS3.0';
@@ -670,4 +690,4 @@ $GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class=
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span>'."\n"; // after comment
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n";
-require dirname( __FILE__ ) . '/data-wp.inc.php'; \ No newline at end of file
+require dirname( __FILE__ ) . '/data-wp.inc.php';
diff --git a/plugins/jetpack/modules/custom-css/custom-css-4.7.php b/plugins/jetpack/modules/custom-css/custom-css-4.7.php
index 259bb79a..9e8fe4d8 100644
--- a/plugins/jetpack/modules/custom-css/custom-css-4.7.php
+++ b/plugins/jetpack/modules/custom-css/custom-css-4.7.php
@@ -67,15 +67,30 @@ class Jetpack_Custom_CSS_Enhancements {
wp_register_style( 'jetpack-customizer-css', plugins_url( 'custom-css/css/customizer-control.css', __FILE__ ), $deps, '20140728' );
wp_register_script( 'jetpack-codemirror', plugins_url( 'custom-css/js/codemirror.min.js', __FILE__ ), array(), '3.16', true );
$deps = array( 'customize-controls', 'underscore' );
- $src = plugins_url( 'custom-css/js/core-customizer-css.core-4.9.js', __FILE__ );
+ $src = Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/core-customizer-css.core-4.9.min.js',
+ 'modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js'
+ );
if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
// If Core < 4.9
$deps[] = 'jetpack-codemirror';
- $src = plugins_url( 'custom-css/js/core-customizer-css.js', __FILE__ );
+ $src = Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/core-customizer-css.min.js',
+ 'modules/custom-css/custom-css/js/core-customizer-css.js'
+ );
}
wp_register_script( 'jetpack-customizer-css', $src, $deps, JETPACK__VERSION, true );
- wp_register_script( 'jetpack-customizer-css-preview', plugins_url( 'custom-css/js/core-customizer-css-preview.js', __FILE__ ), array( 'customize-selective-refresh' ), JETPACK__VERSION, true );
+ wp_register_script(
+ 'jetpack-customizer-css-preview',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/core-customizer-css-preview.min.js',
+ 'modules/custom-css/custom-css/js/core-customizer-css-preview.js'
+ ),
+ array( 'customize-selective-refresh' ),
+ JETPACK__VERSION,
+ true
+ );
remove_action( 'wp_head', 'wp_custom_css_cb', 11 ); // 4.7.0 had it at 11, 4.7.1 moved it to 101.
remove_action( 'wp_head', 'wp_custom_css_cb', 101 );
diff --git a/plugins/jetpack/modules/custom-css/custom-css.php b/plugins/jetpack/modules/custom-css/custom-css.php
index f5e91f09..77570e9b 100644
--- a/plugins/jetpack/modules/custom-css/custom-css.php
+++ b/plugins/jetpack/modules/custom-css/custom-css.php
@@ -930,7 +930,16 @@ class Jetpack_Custom_CSS {
return;
wp_enqueue_script( 'postbox' );
- wp_enqueue_script( 'custom-css-editor', plugins_url( 'custom-css/js/css-editor.js', __FILE__ ), 'jquery', '20130325', true );
+ wp_enqueue_script(
+ 'custom-css-editor',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/css-editor.min.js',
+ 'modules/custom-css/custom-css/js/css-editor.js'
+ ),
+ 'jquery',
+ '20130325',
+ true
+ );
wp_enqueue_style( 'custom-css-editor', plugins_url( 'custom-css/css/css-editor.css', __FILE__ ) );
if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) {
@@ -938,7 +947,16 @@ class Jetpack_Custom_CSS {
wp_enqueue_style( 'jetpack-css-use-codemirror', plugins_url( 'custom-css/css/use-codemirror.css', __FILE__ ), array( 'jetpack-css-codemirror' ), '20120905' );
wp_register_script( 'jetpack-css-codemirror', plugins_url( 'custom-css/js/codemirror.min.js', __FILE__ ), array(), '3.16', true );
- wp_enqueue_script( 'jetpack-css-use-codemirror', plugins_url( 'custom-css/js/use-codemirror.js', __FILE__ ), array( 'jquery', 'underscore', 'jetpack-css-codemirror' ), '20131009', true );
+ wp_enqueue_script(
+ 'jetpack-css-use-codemirror',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/use-codemirror.min.js',
+ 'modules/custom-css/custom-css/js/use-codemirror.js'
+ ),
+ array( 'jquery', 'underscore', 'jetpack-css-codemirror' ),
+ '20131009',
+ true
+ );
}
}
diff --git a/plugins/jetpack/modules/custom-post-types/comics.php b/plugins/jetpack/modules/custom-post-types/comics.php
index a1d8bf78..6ff4fbad 100644
--- a/plugins/jetpack/modules/custom-post-types/comics.php
+++ b/plugins/jetpack/modules/custom-post-types/comics.php
@@ -176,7 +176,14 @@ class Jetpack_Comic {
wp_enqueue_style( 'jetpack-comics-style', plugins_url( 'comics/comics.css', __FILE__ ) );
wp_style_add_data( 'jetpack-comics-style', 'rtl', 'replace' );
- wp_enqueue_script( 'jetpack-comics', plugins_url( 'comics/comics.js', __FILE__ ), array( 'jquery', 'jquery.spin' ) );
+ wp_enqueue_script(
+ 'jetpack-comics',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-post-types/comics/comics.min.js',
+ 'modules/custom-post-types/comics/comics.js'
+ ),
+ array( 'jquery', 'jquery.spin' )
+ );
$options = array(
'nonce' => wp_create_nonce( 'jetpack_comic_upload_nonce' ),
diff --git a/plugins/jetpack/modules/custom-post-types/nova.php b/plugins/jetpack/modules/custom-post-types/nova.php
index 33d5bce9..8024825c 100644
--- a/plugins/jetpack/modules/custom-post-types/nova.php
+++ b/plugins/jetpack/modules/custom-post-types/nova.php
@@ -442,7 +442,16 @@ class Nova_Restaurant {
$this->setup_menu_item_columns();
- wp_register_script( 'nova-menu-checkboxes', plugins_url( 'js/menu-checkboxes.js', __FILE__ ), array( 'jquery' ), $this->version, true );
+ wp_register_script(
+ 'nova-menu-checkboxes',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-post-types/js/menu-checkboxes.min.js',
+ 'modules/custom-post-types/js/menu-checkboxes.js'
+ ),
+ array( 'jquery' ),
+ $this->version,
+ true
+ );
}
@@ -610,7 +619,17 @@ class Nova_Restaurant {
$this->maybe_reorder_menu_items();
- wp_enqueue_script( 'nova-drag-drop', plugins_url( 'js/nova-drag-drop.js', __FILE__ ), array( 'jquery-ui-sortable' ), $this->version, true );
+ wp_enqueue_script(
+ 'nova-drag-drop',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-post-types/js/nova-drag-drop.min.js',
+ 'modules/custom-post-types/js/nova-drag-drop.js'
+ ),
+ array( 'jquery-ui-sortable' ),
+ $this->version,
+ true
+ );
+
wp_localize_script( 'nova-drag-drop', '_novaDragDrop', array(
'nonce' => wp_create_nonce( 'drag-drop-reorder' ),
'nonceName' => 'drag-drop-reorder',
@@ -847,7 +866,16 @@ class Nova_Restaurant {
}
function enqueue_many_items_scripts() {
- wp_enqueue_script( 'nova-many-items', plugins_url( 'js/many-items.js', __FILE__ ), array( 'jquery' ), $this->version, true );
+ wp_enqueue_script(
+ 'nova-many-items',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-post-types/js/many-items.min.js',
+ 'modules/custom-post-types/js/many-items.js'
+ ),
+ array( 'jquery' ),
+ $this->version,
+ true
+ );
}
function process_form_request() {
diff --git a/plugins/jetpack/modules/custom-post-types/portfolios.php b/plugins/jetpack/modules/custom-post-types/portfolios.php
index 7d40510f..416aea81 100644
--- a/plugins/jetpack/modules/custom-post-types/portfolios.php
+++ b/plugins/jetpack/modules/custom-post-types/portfolios.php
@@ -252,6 +252,8 @@ class Jetpack_Portfolio {
'comments',
'publicize',
'wpcom-markdown',
+ 'revisions',
+ 'excerpt',
),
'rewrite' => array(
'slug' => 'portfolio',
diff --git a/plugins/jetpack/modules/custom-post-types/testimonial.php b/plugins/jetpack/modules/custom-post-types/testimonial.php
index c2006731..6a7426cc 100644
--- a/plugins/jetpack/modules/custom-post-types/testimonial.php
+++ b/plugins/jetpack/modules/custom-post-types/testimonial.php
@@ -319,6 +319,7 @@ class Jetpack_Testimonial {
'thumbnail',
'page-attributes',
'revisions',
+ 'excerpt',
),
'rewrite' => array(
'slug' => 'testimonial',
diff --git a/plugins/jetpack/modules/google-analytics/classes/wp-google-analytics-legacy.php b/plugins/jetpack/modules/google-analytics/classes/wp-google-analytics-legacy.php
index 58311ed4..1b85567e 100644
--- a/plugins/jetpack/modules/google-analytics/classes/wp-google-analytics-legacy.php
+++ b/plugins/jetpack/modules/google-analytics/classes/wp-google-analytics-legacy.php
@@ -102,22 +102,19 @@ class Jetpack_Google_Analytics_Legacy {
$custom_vars = apply_filters( 'jetpack_wga_classic_custom_vars', $custom_vars );
// Ref: https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingEcommerce#Example
- $async_code = "<!-- Jetpack Google Analytics -->
+ printf(
+ "<!-- Jetpack Google Analytics -->
<script type='text/javascript'>
var _gaq = _gaq || [];
- %custom_vars%
-
+ %s
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ ga.src = ('https:' === document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
- </script>";
-
- $custom_vars_string = implode( "\r\n", $custom_vars );
- $async_code = str_replace( '%custom_vars%', $custom_vars_string, $async_code );
-
- echo "$async_code\r\n";
+ </script>\r\n",
+ implode( "\r\n", $custom_vars )
+ );
}
/**
diff --git a/plugins/jetpack/modules/holiday-snow/snowstorm.js b/plugins/jetpack/modules/holiday-snow/snowstorm.js
index 96fd609d..2ff6320e 100644
--- a/plugins/jetpack/modules/holiday-snow/snowstorm.js
+++ b/plugins/jetpack/modules/holiday-snow/snowstorm.js
@@ -257,6 +257,8 @@ var snowStorm = (function(window, document) {
this.active = 1;
this.fontSize = (10+(this.type/5)*10);
this.o = document.createElement('div');
+ this.o.setAttribute('aria-hidden', 'true');
+ this.o.setAttribute('role', 'presentation');
this.o.innerHTML = storm.snowCharacter;
this.o.style.color = storm.snowColor;
this.o.style.position = (fixedForEverything?'fixed':'absolute');
@@ -536,4 +538,4 @@ var snowStorm = (function(window, document) {
return this;
-}(window, document)); \ No newline at end of file
+}(window, document));
diff --git a/plugins/jetpack/modules/infinite-scroll/infinity.php b/plugins/jetpack/modules/infinite-scroll/infinity.php
index 06223d15..4d3f05f6 100644
--- a/plugins/jetpack/modules/infinite-scroll/infinity.php
+++ b/plugins/jetpack/modules/infinite-scroll/infinity.php
@@ -406,7 +406,16 @@ class The_Neverending_Home_Page {
return;
// Add our scripts.
- wp_register_script( 'the-neverending-homepage', plugins_url( 'infinity.js', __FILE__ ), array( 'jquery' ), '4.0.0', true );
+ wp_register_script(
+ 'the-neverending-homepage',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/infinite-scroll/infinity.min.js',
+ 'modules/infinite-scroll/infinity.js'
+ ),
+ array( 'jquery' ),
+ '4.0.0',
+ true
+ );
// Add our default styles.
wp_register_style( 'the-neverending-homepage', plugins_url( 'infinity.css', __FILE__ ), array(), '20140422' );
diff --git a/plugins/jetpack/modules/lazy-images.php b/plugins/jetpack/modules/lazy-images.php
index d0dd0a38..a0c498cc 100644
--- a/plugins/jetpack/modules/lazy-images.php
+++ b/plugins/jetpack/modules/lazy-images.php
@@ -2,7 +2,7 @@
/**
* Module Name: Lazy Images
- * Module Description: Improve performance by loading images just before they scroll into view
+ * Module Description: Lazy load images
* Sort Order: 24
* Recommendation Order: 14
* First Introduced: 5.6.0
@@ -10,7 +10,7 @@
* Auto Activate: No
* Module Tags: Appearance, Recommended
* Feature: Appearance
- * Additional Search Queries: mobile, theme, performance
+ * Additional Search Queries: mobile, theme, performance, image
*/
/**
@@ -24,4 +24,16 @@
*/
require_once( JETPACK__PLUGIN_DIR . 'modules/lazy-images/lazy-images.php' );
-Jetpack_Lazy_Images::instance();
+
+/*
+ * Initialize lazy images on the wp action so that conditional
+ * tags are safe to use.
+ *
+ * As an example, this is important if a theme wants to disable lazy images except
+ * on single posts, pages, or attachments by short-circuiting lazy images when
+ * is_singular() returns false.
+ *
+ * See: https://github.com/Automattic/jetpack/issues/8888
+ */
+
+add_action( 'wp', array( 'Jetpack_Lazy_Images', 'instance' ) );
diff --git a/plugins/jetpack/modules/lazy-images/js/lazy-images.js b/plugins/jetpack/modules/lazy-images/js/lazy-images.js
index bc117e9b..08ff18ce 100644
--- a/plugins/jetpack/modules/lazy-images/js/lazy-images.js
+++ b/plugins/jetpack/modules/lazy-images/js/lazy-images.js
@@ -121,36 +121,736 @@ var jetpackLazyImagesModule = function( $ ) {
};
/**
- * The following is an Intersection observer polyfill which is licensed under MIT
- * and can be found at https://github.com/que-etc/intersection-observer-polyfill
+ * The following is an Intersection observer polyfill which is licensed under
+ * the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE and can be found at:
+ * https://github.com/w3c/IntersectionObserver/tree/master/polyfill
*/
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2016 Denis Rul
+/* jshint ignore:start */
+/**
+ * Copyright 2016 Google Inc. All Rights Reserved.
*
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
+ * Licensed under the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE.
*
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
*/
-/* jshint ignore:start */
-!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.IntersectionObserver=e():t.IntersectionObserver=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var o=r(1),i=n(o),s=void 0;s="function"==typeof window.IntersectionObserver?window.IntersectionObserver:i["default"],e["default"]=s,t.exports=e["default"]},function(t,e,r){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol?"symbol":typeof t},s=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),a=r(2),u=r(3),c=n(u),l=r(5),h=n(l),f=new c["default"],p=new a.WeakMap,d=function(){function t(e,r){if(o(this,t),!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=new h["default"](e,r,f,this);Object.defineProperties(this,{root:{value:n.root},thresholds:{value:n.thresholds},rootMargin:{value:n.rootMargin}}),p.set(this,n)}return s(t,null,[{key:"idleTimeout",get:function(){return f.idleTimeout},set:function(t){if("number"!=typeof t)throw new TypeError('type of "idleTimeout" value must be a number.');if(("undefined"==typeof t?"undefined":i(t))<0)throw new TypeError('"idleTimeout" value must be greater than 0.');f.idleTimeout=t}},{key:"trackHovers",get:function(){return f.isHoverEnabled()},set:function(t){if("boolean"!=typeof t)throw new TypeError('type of "trackHovers" value must be a boolean.');t?f.enableHover():f.disableHover()}}]),t}();["observe","unobserve","disconnect","takeRecords"].forEach(function(t){d.prototype[t]=function(){var e;return(e=p.get(this))[t].apply(e,arguments)}}),e["default"]=d,t.exports=e["default"]},function(t,e){"use strict";function r(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function n(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),s="function"==typeof window.WeakMap&&"function"==typeof window.Map,a=function(){function t(t,e){var r=-1;return t.some(function(t,n){var o=t[0]===e;return o&&(r=n),o}),r}return s?window.WeakMap:function(){function e(){o(this,e),this.__entries__=[]}return e.prototype.get=function(e){var r=t(this.__entries__,e);return this.__entries__[r][1]},e.prototype.set=function(e,r){var n=t(this.__entries__,e);~n?this.__entries__[n][1]=r:this.__entries__.push([e,r])},e.prototype["delete"]=function(e){var r=this.__entries__,n=t(r,e);~n&&r.splice(n,1)},e.prototype.has=function(e){return!!~t(this.__entries__,e)},e}()}(),u=function(){return s?window.Map:function(t){function e(){return o(this,e),r(this,t.apply(this,arguments))}return n(e,t),e.prototype.clear=function(){this.__entries__.splice(0,this.__entries__.length)},e.prototype.entries=function(){return this.__entries__.slice()},e.prototype.keys=function(){return this.__entries__.map(function(t){return t[0]})},e.prototype.values=function(){return this.__entries__.map(function(t){return t[1]})},e.prototype.forEach=function(t){for(var e=arguments.length<=1||void 0===arguments[1]?null:arguments[1],r=this.__entries__,n=Array.isArray(r),o=0,r=n?r:r[Symbol.iterator]();;){var i;if(n){if(o>=r.length)break;i=r[o++]}else{if(o=r.next(),o.done)break;i=o.value}var s=i;t.call(e,s[1],s[0])}},i(e,[{key:"size",get:function(){return this.__entries__.length}}]),e}(a)}();e.Map=u,e.WeakMap=a},function(t,e,r){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t){var e=arguments.length<=1||void 0===arguments[1]?0:arguments[1],r=!1;return function(){for(var n=this,o=arguments.length,i=Array(o),s=0;s<o;s++)i[s]=arguments[s];r!==!1&&clearTimeout(r),r=setTimeout(function(){r=!1,t.apply(n,i)},e)}}Object.defineProperty(e,"__esModule",{value:!0});var s=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),a=r(4),u=n(a),c="function"==typeof window.MutationObserver,l=function(){return window.requestAnimationFrame?window.requestAnimationFrame:function(t){return setTimeout(function(){return t((0,u["default"])())},1e3/60)}}(),h=function(){function t(){var e=arguments.length<=0||void 0===arguments[0]?50:arguments[0],r=!(arguments.length<=1||void 0===arguments[1])&&arguments[1];o(this,t),this._idleTimeout=e,this._trackHovers=r,this._cycleStartTime=-1,this._isUpdateScheduled=!1,this._repeatCycle=!1,this._hoverInitiated=!1,this._mutationsObserver=null,this._isListening=!1,this._observers=[],this.startUpdateCycle=this.startUpdateCycle.bind(this),this.scheduleUpdate=this.scheduleUpdate.bind(this),this._onMutation=this._onMutation.bind(this),this._repeatHandler=i(this.scheduleUpdate,200),this._onMouseOver=i(this.startUpdateCycle,200)}return t.prototype.connect=function(t){this.isConnected(t)||this._observers.push(t),this._isListening||this._initListeners()},t.prototype.disconnect=function(t){var e=this._observers,r=e.indexOf(t);~r&&e.splice(r,1),!e.length&&this._isListening&&this._removeListeners()},t.prototype.isConnected=function(t){return!!~this._observers.indexOf(t)},t.prototype._updateObservers=function(){for(var t=!1,e=this._observers,r=Array.isArray(e),n=0,e=r?e:e[Symbol.iterator]();;){var o;if(r){if(n>=e.length)break;o=e[n++]}else{if(n=e.next(),n.done)break;o=n.value}var i=o;i.updateObservations()&&(t=!0),i.hasEntries()&&i.notifySubscriber()}return t},t.prototype.startUpdateCycle=function(){this._cycleStartTime=(0,u["default"])(),this.scheduleUpdate()},t.prototype.scheduleUpdate=function(t){var e="number"==typeof t;if(e){var r=this._updateObservers();if(this._isUpdateScheduled=!1,!this._wasCycleStarted())return;r?this.startUpdateCycle():this._hasIdleTimeEnded()?this._onCycleEnded():this.scheduleUpdate()}else this._isUpdateScheduled||(l(this.scheduleUpdate),this._isUpdateScheduled=!0)},t.prototype._hasIdleTimeEnded=function(){return(0,u["default"])()-this._cycleStartTime>this._idleTimeout},t.prototype._wasCycleStarted=function(){return this._cycleStartTime!==-1},t.prototype._onCycleEnded=function(){this._cycleStartTime=-1,this._repeatCycle&&(this._cycleStartTime=0,this._repeatHandler())},t.prototype._initListeners=function(){this._isListening||(this._isListening=!0,window.addEventListener("resize",this.startUpdateCycle,!0),window.addEventListener("scroll",this.scheduleUpdate,!0),this._trackHovers&&this._addHoverListener(),c?(this._mutationsObserver=new MutationObserver(this._onMutation),this._mutationsObserver.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(this._repeatCycle=!0,window.addEventListener("click",this.startUpdateCycle,!0),this.startUpdateCycle()))},t.prototype._removeListeners=function(){this._isListening&&(window.removeEventListener("resize",this.startUpdateCycle,!0),window.removeEventListener("scroll",this.scheduleUpdate,!0),this._removeHoverListener(),c?this._mutationsObserver&&(this._mutationsObserver.disconnect(),this._mutationsObserver=null):(this._repeatCycle=!1,window.removeEventListener("click",this.startUpdateCycle,!0)),this._isListening=!1)},t.prototype.enableHover=function(){this._trackHovers=!0,this._isListening&&this._addHoverListener()},t.prototype.disableHover=function(){this._trackHovers=!1,this._removeHoverListener()},t.prototype.isHoverEnabled=function(){return this._trackHovers},t.prototype._addHoverListener=function(){this._hoverInitiated||(window.addEventListener("mouseover",this._onMouseOver,!0),this._hoverInitiated=!0)},t.prototype._removeHoverListener=function(){this._hoverInitiated&&(window.removeEventListener("mouseover",this._onMouseOver,!0),this._hoverInitiated=!1)},t.prototype._onMutation=function(t){var e=t.every(function(t){return"attributes"!==t.type});e?this.scheduleUpdate():this.startUpdateCycle()},s(t,[{key:"idleTimeout",get:function(){return this._idleTimeout},set:function(t){this._idleTimeout=t}}]),t}();e["default"]=h,t.exports=e["default"]},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=function(){return window.performance&&window.performance.now?function(){return window.performance.now()}:function(){return Date.now()}}(),t.exports=e["default"]},function(t,e,r){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(){var t=arguments.length<=0||void 0===arguments[0]?0:arguments[0],e=t;return Array.isArray(t)?t.length||(e=[0]):e=[t],e.map(function(t){if(t=Number(t),!window.isFinite(t))throw new TypeError("The provided double value is non-finite.");if(t<0||t>1)throw new RangeError("Threshold values must be between 0 and 1.");return t}).sort()}function s(){var t=arguments.length<=0||void 0===arguments[0]?"0px":arguments[0];if(t=(t+"").split(/\s+/),t.length>4)throw new Error("Extra text found at the end of rootMargin.");t[0]=t[0]||"0px",t[1]=t[1]||t[0],t[2]=t[2]||t[0],t[3]=t[3]||t[1];var e=t.join(" "),r=t.map(function(t){var e=/^(-?\d*\.?\d+)(px|%)$/.exec(t)||[],r=e[1],n=e[2],o="px"===n;if(r=parseFloat(r),!window.isFinite(r))throw new Error("rootMargin must be specified in pixels or percent.");return o||(r/=100),{value:r,pixels:o}});return{rawData:e,parsedData:r}}function a(t,e){e=e.map(function(e,r){var n=e.value;return e.pixels||(n*=r%2?t.width:t.height),n});var r={top:t.top-e[0],right:t.right+e[1],bottom:t.bottom+e[2],left:t.left-e[3]};return r.width=r.right-r.left,r.height=r.bottom-r.top,r}Object.defineProperty(e,"__esModule",{value:!0});var u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol?"symbol":typeof t},c=r(2),l=r(6),h=r(7),f=n(h),p=function(){function t(e){var r=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],n=arguments[2],a=arguments[3];if(o(this,t),"function"!=typeof e)throw new TypeError("The callback provided as parameter 1 is not a function.");if("object"!==("undefined"==typeof r?"undefined":u(r)))throw new TypeError("parameter 2 is not an object.");if("root"in r&&!(r.root instanceof Element))throw new TypeError("member root is not of type Element.");var l=i(r.threshold),h=s(r.rootMargin);this.root=r.root||null,this.rootMargin=h.rawData,this.thresholds=Object.freeze(l),this._root=r.root||document.documentElement,this._callback=e,this._rootMargin=h.parsedData,this._targets=new c.Map,this._quedEntries=[],this._publicObserver=a||this,this.controller=n}return t.prototype.observe=function(t){if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");if(!(t instanceof Element))throw new TypeError('parameter 1 is not of type "Element".');var e=this._targets;e.has(t)||(e.set(t,new f["default"](t,this)),this.controller.isConnected(this)||this.controller.connect(this),this.controller.startUpdateCycle())},t.prototype.unobserve=function(t){if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");if(!(t instanceof Element))throw new TypeError('parameter 1 is not of type "Element".');var e=this._targets;e.has(t)&&e["delete"](t),e.size||this.disconnect()},t.prototype.disconnect=function(){this._targets.clear(),this.controller.disconnect(this)},t.prototype.takeRecords=function(){return this._quedEntries.splice(0)},t.prototype.notifySubscriber=function(){var t=this.takeRecords(),e=this._publicObserver;t.length&&this._callback.call(e,t,e)},t.prototype.queueEntry=function(t){this._quedEntries.push(t)},t.prototype.hasEntries=function(){return!!this._quedEntries.length},t.prototype.updateObservations=function(){var t=this._root,e=this.getRootRect(),r=!1;return this._targets.forEach(function(n){var o=n.updateIntersection(t,e);(o.ratioChanged||o.targetRectChanged)&&(r=!0)}),r},t.prototype.getThresholdGreaterThan=function(t){for(var e=this.thresholds,r=e.length,n=0;n<r&&e[n]<=t;)++n;return n},t.prototype.getRootRect=function(){var t=(0,l.getRectangle)(this._root);return a(t,this._rootMargin)},t}();e["default"]=p,t.exports=e["default"]},function(t,e){"use strict";function r(t){for(var e={},r=Object.keys(t),n=Array.isArray(r),o=0,r=n?r:r[Symbol.iterator]();;){var i;if(n){if(o>=r.length)break;i=r[o++]}else{if(o=r.next(),o.done)break;i=o.value}var s=i;e[s]={value:t[s]}}return Object.defineProperties({},e)}function n(){var t=arguments.length<=0||void 0===arguments[0]?0:arguments[0],e=arguments.length<=1||void 0===arguments[1]?0:arguments[1],r=arguments.length<=2||void 0===arguments[2]?0:arguments[2],n=arguments.length<=3||void 0===arguments[3]?0:arguments[3];return{left:t,top:e,width:r,height:n,bottom:e+n,right:t+r}}function o(t){return t===document.documentElement?n(0,0,t.clientWidth,t.clientHeight):t.getBoundingClientRect()}function i(t){return t.width*t.height}function s(t){return 0===t.height&&0===t.width}function a(t,e){return t.top===e.top&&t.left===e.left&&t.right===e.right&&t.bottom===e.bottom}Object.defineProperty(e,"__esModule",{value:!0}),e.mapToClientRect=r,e.createRectangle=n,e.getRectangle=o,e.getArea=i,e.isEmpty=s,e.isEqual=a},function(t,e,r){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){var r=document.documentElement;return t!==r&&!r.contains(t)||!t.contains(e)}function s(t,e){var r=Math.max(e.left,t.left),n=Math.min(e.right,t.right),o=Math.max(e.top,t.top),i=Math.min(e.bottom,t.bottom),s=n-r,a=i-o;return(0,l.createRectangle)(r,o,s,a)}function a(t,e,r,n){for(var o=n,i=e.parentNode,a=!1;!a;){var u=null;i===t||1!==i.nodeType?(a=!0,u=r):"visible"!==window.getComputedStyle(i).overflow&&(u=(0,l.getRectangle)(i)),u&&(o=s(o,u)),i=i.parentNode}return o}Object.defineProperty(e,"__esModule",{value:!0});var u=r(4),c=n(u),l=r(6),h=r(8),f=n(h),p=(0,l.createRectangle)(),d=function(){function t(e,r){o(this,t),this.target=e,this.observer=r,this.prevTargetRect=p,this.prevThreshold=0,this.prevRatio=0}return t.prototype.updateIntersection=function(t,e){var r=(0,l.getRectangle)(this.target),n=this.getIntersectionData(t,e,r),o=+n.exists,i=n.ratio!==this.prevRatio,s=!(0,l.isEqual)(r,this.prevTargetRect),a=void 0;if(n.exists&&!(0,l.isEmpty)(r)&&(o=this.observer.getThresholdGreaterThan(n.ratio)),a=o!==this.prevThreshold,this.prevTargetRect=r,this.prevThreshold=o,this.prevRatio=n.ratio,n.exists||(n.ratio=0,n.rect=p),a){var u=new f["default"](this.target,r,n.rect,n.ratio,e,(0,c["default"])());this.observer.queueEntry(u)}return{ratioChanged:i,thresholdChanged:a,targetRectChanged:s}},t.prototype.getIntersectionData=function(t,e,r){var n=this.target;r||(r=(0,l.getRectangle)(this.target)),e||(e=(0,l.getRectangle)(t));var o=i(t,n),s=o?p:a(t,n,e,r),u=!o&&s.width>=0&&s.height>=0,c=(0,l.getArea)(s)/(0,l.getArea)(r)||0;return{rect:s,ratio:c,exists:u}},t}();e["default"]=d,t.exports=e["default"]},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var o=r(6),i=function s(t,e,r,i,a,u){n(this,s),Object.defineProperties(this,{boundingClientRect:{value:e},intersectionRatio:{value:i},intersectionRect:{value:(0,o.mapToClientRect)(r)},rootBounds:{value:(0,o.mapToClientRect)(a)},target:{value:t},time:{value:u}})};e["default"]=i,t.exports=e["default"]}])});
+(function(window, document) {
+ 'use strict';
+
+
+ // Exits early if all IntersectionObserver and IntersectionObserverEntry
+ // features are natively supported.
+ if ('IntersectionObserver' in window &&
+ 'IntersectionObserverEntry' in window &&
+ 'intersectionRatio' in window.IntersectionObserverEntry.prototype) {
+
+ // Minimal polyfill for Edge 15's lack of `isIntersecting`
+ // See: https://github.com/w3c/IntersectionObserver/issues/211
+ if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) {
+ Object.defineProperty(window.IntersectionObserverEntry.prototype,
+ 'isIntersecting', {
+ get: function () {
+ return this.intersectionRatio > 0;
+ }
+ });
+ }
+ return;
+ }
+
+
+ /**
+ * An IntersectionObserver registry. This registry exists to hold a strong
+ * reference to IntersectionObserver instances currently observering a target
+ * element. Without this registry, instances without another reference may be
+ * garbage collected.
+ */
+ var registry = [];
+
+
+ /**
+ * Creates the global IntersectionObserverEntry constructor.
+ * https://w3c.github.io/IntersectionObserver/#intersection-observer-entry
+ * @param {Object} entry A dictionary of instance properties.
+ * @constructor
+ */
+ function IntersectionObserverEntry(entry) {
+ this.time = entry.time;
+ this.target = entry.target;
+ this.rootBounds = entry.rootBounds;
+ this.boundingClientRect = entry.boundingClientRect;
+ this.intersectionRect = entry.intersectionRect || getEmptyRect();
+ this.isIntersecting = !!entry.intersectionRect;
+
+ // Calculates the intersection ratio.
+ var targetRect = this.boundingClientRect;
+ var targetArea = targetRect.width * targetRect.height;
+ var intersectionRect = this.intersectionRect;
+ var intersectionArea = intersectionRect.width * intersectionRect.height;
+
+ // Sets intersection ratio.
+ if (targetArea) {
+ this.intersectionRatio = intersectionArea / targetArea;
+ } else {
+ // If area is zero and is intersecting, sets to 1, otherwise to 0
+ this.intersectionRatio = this.isIntersecting ? 1 : 0;
+ }
+ }
+
+
+ /**
+ * Creates the global IntersectionObserver constructor.
+ * https://w3c.github.io/IntersectionObserver/#intersection-observer-interface
+ * @param {Function} callback The function to be invoked after intersection
+ * changes have queued. The function is not invoked if the queue has
+ * been emptied by calling the `takeRecords` method.
+ * @param {Object=} opt_options Optional configuration options.
+ * @constructor
+ */
+ function IntersectionObserver(callback, opt_options) {
+
+ var options = opt_options || {};
+
+ if (typeof callback != 'function') {
+ throw new Error('callback must be a function');
+ }
+
+ if (options.root && options.root.nodeType != 1) {
+ throw new Error('root must be an Element');
+ }
+
+ // Binds and throttles `this._checkForIntersections`.
+ this._checkForIntersections = throttle(
+ this._checkForIntersections.bind(this), this.THROTTLE_TIMEOUT);
+
+ // Private properties.
+ this._callback = callback;
+ this._observationTargets = [];
+ this._queuedEntries = [];
+ this._rootMarginValues = this._parseRootMargin(options.rootMargin);
+
+ // Public properties.
+ this.thresholds = this._initThresholds(options.threshold);
+ this.root = options.root || null;
+ this.rootMargin = this._rootMarginValues.map(function(margin) {
+ return margin.value + margin.unit;
+ }).join(' ');
+ }
+
+
+ /**
+ * The minimum interval within which the document will be checked for
+ * intersection changes.
+ */
+ IntersectionObserver.prototype.THROTTLE_TIMEOUT = 100;
+
+
+ /**
+ * The frequency in which the polyfill polls for intersection changes.
+ * this can be updated on a per instance basis and must be set prior to
+ * calling `observe` on the first target.
+ */
+ IntersectionObserver.prototype.POLL_INTERVAL = null;
+
+ /**
+ * Use a mutation observer on the root element
+ * to detect intersection changes.
+ */
+ IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true;
+
+
+ /**
+ * Starts observing a target element for intersection changes based on
+ * the thresholds values.
+ * @param {Element} target The DOM element to observe.
+ */
+ IntersectionObserver.prototype.observe = function(target) {
+ var isTargetAlreadyObserved = this._observationTargets.some(function(item) {
+ return item.element == target;
+ });
+
+ if (isTargetAlreadyObserved) {
+ return;
+ }
+
+ if (!(target && target.nodeType == 1)) {
+ throw new Error('target must be an Element');
+ }
+
+ this._registerInstance();
+ this._observationTargets.push({element: target, entry: null});
+ this._monitorIntersections();
+ this._checkForIntersections();
+ };
+
+
+ /**
+ * Stops observing a target element for intersection changes.
+ * @param {Element} target The DOM element to observe.
+ */
+ IntersectionObserver.prototype.unobserve = function(target) {
+ this._observationTargets =
+ this._observationTargets.filter(function(item) {
+
+ return item.element != target;
+ });
+ if (!this._observationTargets.length) {
+ this._unmonitorIntersections();
+ this._unregisterInstance();
+ }
+ };
+
+
+ /**
+ * Stops observing all target elements for intersection changes.
+ */
+ IntersectionObserver.prototype.disconnect = function() {
+ this._observationTargets = [];
+ this._unmonitorIntersections();
+ this._unregisterInstance();
+ };
+
+
+ /**
+ * Returns any queue entries that have not yet been reported to the
+ * callback and clears the queue. This can be used in conjunction with the
+ * callback to obtain the absolute most up-to-date intersection information.
+ * @return {Array} The currently queued entries.
+ */
+ IntersectionObserver.prototype.takeRecords = function() {
+ var records = this._queuedEntries.slice();
+ this._queuedEntries = [];
+ return records;
+ };
+
+
+ /**
+ * Accepts the threshold value from the user configuration object and
+ * returns a sorted array of unique threshold values. If a value is not
+ * between 0 and 1 and error is thrown.
+ * @private
+ * @param {Array|number=} opt_threshold An optional threshold value or
+ * a list of threshold values, defaulting to [0].
+ * @return {Array} A sorted list of unique and valid threshold values.
+ */
+ IntersectionObserver.prototype._initThresholds = function(opt_threshold) {
+ var threshold = opt_threshold || [0];
+ if (!Array.isArray(threshold)) threshold = [threshold];
+
+ return threshold.sort().filter(function(t, i, a) {
+ if (typeof t != 'number' || isNaN(t) || t < 0 || t > 1) {
+ throw new Error('threshold must be a number between 0 and 1 inclusively');
+ }
+ return t !== a[i - 1];
+ });
+ };
+
+
+ /**
+ * Accepts the rootMargin value from the user configuration object
+ * and returns an array of the four margin values as an object containing
+ * the value and unit properties. If any of the values are not properly
+ * formatted or use a unit other than px or %, and error is thrown.
+ * @private
+ * @param {string=} opt_rootMargin An optional rootMargin value,
+ * defaulting to '0px'.
+ * @return {Array<Object>} An array of margin objects with the keys
+ * value and unit.
+ */
+ IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) {
+ var marginString = opt_rootMargin || '0px';
+ var margins = marginString.split(/\s+/).map(function(margin) {
+ var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin);
+ if (!parts) {
+ throw new Error('rootMargin must be specified in pixels or percent');
+ }
+ return {value: parseFloat(parts[1]), unit: parts[2]};
+ });
+
+ // Handles shorthand.
+ margins[1] = margins[1] || margins[0];
+ margins[2] = margins[2] || margins[0];
+ margins[3] = margins[3] || margins[1];
+
+ return margins;
+ };
+
+
+ /**
+ * Starts polling for intersection changes if the polling is not already
+ * happening, and if the page's visibilty state is visible.
+ * @private
+ */
+ IntersectionObserver.prototype._monitorIntersections = function() {
+ if (!this._monitoringIntersections) {
+ this._monitoringIntersections = true;
+
+ // If a poll interval is set, use polling instead of listening to
+ // resize and scroll events or DOM mutations.
+ if (this.POLL_INTERVAL) {
+ this._monitoringInterval = setInterval(
+ this._checkForIntersections, this.POLL_INTERVAL);
+ }
+ else {
+ addEvent(window, 'resize', this._checkForIntersections, true);
+ addEvent(document, 'scroll', this._checkForIntersections, true);
+
+ if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in window) {
+ this._domObserver = new MutationObserver(this._checkForIntersections);
+ this._domObserver.observe(document, {
+ attributes: true,
+ childList: true,
+ characterData: true,
+ subtree: true
+ });
+ }
+ }
+ }
+ };
+
+
+ /**
+ * Stops polling for intersection changes.
+ * @private
+ */
+ IntersectionObserver.prototype._unmonitorIntersections = function() {
+ if (this._monitoringIntersections) {
+ this._monitoringIntersections = false;
+
+ clearInterval(this._monitoringInterval);
+ this._monitoringInterval = null;
+
+ removeEvent(window, 'resize', this._checkForIntersections, true);
+ removeEvent(document, 'scroll', this._checkForIntersections, true);
+
+ if (this._domObserver) {
+ this._domObserver.disconnect();
+ this._domObserver = null;
+ }
+ }
+ };
+
+
+ /**
+ * Scans each observation target for intersection changes and adds them
+ * to the internal entries queue. If new entries are found, it
+ * schedules the callback to be invoked.
+ * @private
+ */
+ IntersectionObserver.prototype._checkForIntersections = function() {
+ var rootIsInDom = this._rootIsInDom();
+ var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect();
+
+ this._observationTargets.forEach(function(item) {
+ var target = item.element;
+ var targetRect = getBoundingClientRect(target);
+ var rootContainsTarget = this._rootContainsTarget(target);
+ var oldEntry = item.entry;
+ var intersectionRect = rootIsInDom && rootContainsTarget &&
+ this._computeTargetAndRootIntersection(target, rootRect);
+
+ var newEntry = item.entry = new IntersectionObserverEntry({
+ time: now(),
+ target: target,
+ boundingClientRect: targetRect,
+ rootBounds: rootRect,
+ intersectionRect: intersectionRect
+ });
+
+ if (!oldEntry) {
+ this._queuedEntries.push(newEntry);
+ } else if (rootIsInDom && rootContainsTarget) {
+ // If the new entry intersection ratio has crossed any of the
+ // thresholds, add a new entry.
+ if (this._hasCrossedThreshold(oldEntry, newEntry)) {
+ this._queuedEntries.push(newEntry);
+ }
+ } else {
+ // If the root is not in the DOM or target is not contained within
+ // root but the previous entry for this target had an intersection,
+ // add a new record indicating removal.
+ if (oldEntry && oldEntry.isIntersecting) {
+ this._queuedEntries.push(newEntry);
+ }
+ }
+ }, this);
+
+ if (this._queuedEntries.length) {
+ this._callback(this.takeRecords(), this);
+ }
+ };
+
+
+ /**
+ * Accepts a target and root rect computes the intersection between then
+ * following the algorithm in the spec.
+ * TODO(philipwalton): at this time clip-path is not considered.
+ * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo
+ * @param {Element} target The target DOM element
+ * @param {Object} rootRect The bounding rect of the root after being
+ * expanded by the rootMargin value.
+ * @return {?Object} The final intersection rect object or undefined if no
+ * intersection is found.
+ * @private
+ */
+ IntersectionObserver.prototype._computeTargetAndRootIntersection =
+ function(target, rootRect) {
+
+ // If the element isn't displayed, an intersection can't happen.
+ if (window.getComputedStyle(target).display == 'none') return;
+
+ var targetRect = getBoundingClientRect(target);
+ var intersectionRect = targetRect;
+ var parent = getParentNode(target);
+ var atRoot = false;
+
+ while (!atRoot) {
+ var parentRect = null;
+ var parentComputedStyle = parent.nodeType == 1 ?
+ window.getComputedStyle(parent) : {};
+
+ // If the parent isn't displayed, an intersection can't happen.
+ if (parentComputedStyle.display == 'none') return;
+
+ if (parent == this.root || parent == document) {
+ atRoot = true;
+ parentRect = rootRect;
+ } else {
+ // If the element has a non-visible overflow, and it's not the <body>
+ // or <html> element, update the intersection rect.
+ // Note: <body> and <html> cannot be clipped to a rect that's not also
+ // the document rect, so no need to compute a new intersection.
+ if (parent != document.body &&
+ parent != document.documentElement &&
+ parentComputedStyle.overflow != 'visible') {
+ parentRect = getBoundingClientRect(parent);
+ }
+ }
+
+ // If either of the above conditionals set a new parentRect,
+ // calculate new intersection data.
+ if (parentRect) {
+ intersectionRect = computeRectIntersection(parentRect, intersectionRect);
+
+ if (!intersectionRect) break;
+ }
+ parent = getParentNode(parent);
+ }
+ return intersectionRect;
+ };
+
+
+ /**
+ * Returns the root rect after being expanded by the rootMargin value.
+ * @return {Object} The expanded root rect.
+ * @private
+ */
+ IntersectionObserver.prototype._getRootRect = function() {
+ var rootRect;
+ if (this.root) {
+ rootRect = getBoundingClientRect(this.root);
+ } else {
+ // Use <html>/<body> instead of window since scroll bars affect size.
+ var html = document.documentElement;
+ var body = document.body;
+ rootRect = {
+ top: 0,
+ left: 0,
+ right: html.clientWidth || body.clientWidth,
+ width: html.clientWidth || body.clientWidth,
+ bottom: html.clientHeight || body.clientHeight,
+ height: html.clientHeight || body.clientHeight
+ };
+ }
+ return this._expandRectByRootMargin(rootRect);
+ };
+
+
+ /**
+ * Accepts a rect and expands it by the rootMargin value.
+ * @param {Object} rect The rect object to expand.
+ * @return {Object} The expanded rect.
+ * @private
+ */
+ IntersectionObserver.prototype._expandRectByRootMargin = function(rect) {
+ var margins = this._rootMarginValues.map(function(margin, i) {
+ return margin.unit == 'px' ? margin.value :
+ margin.value * (i % 2 ? rect.width : rect.height) / 100;
+ });
+ var newRect = {
+ top: rect.top - margins[0],
+ right: rect.right + margins[1],
+ bottom: rect.bottom + margins[2],
+ left: rect.left - margins[3]
+ };
+ newRect.width = newRect.right - newRect.left;
+ newRect.height = newRect.bottom - newRect.top;
+
+ return newRect;
+ };
+
+
+ /**
+ * Accepts an old and new entry and returns true if at least one of the
+ * threshold values has been crossed.
+ * @param {?IntersectionObserverEntry} oldEntry The previous entry for a
+ * particular target element or null if no previous entry exists.
+ * @param {IntersectionObserverEntry} newEntry The current entry for a
+ * particular target element.
+ * @return {boolean} Returns true if a any threshold has been crossed.
+ * @private
+ */
+ IntersectionObserver.prototype._hasCrossedThreshold =
+ function(oldEntry, newEntry) {
+
+ // To make comparing easier, an entry that has a ratio of 0
+ // but does not actually intersect is given a value of -1
+ var oldRatio = oldEntry && oldEntry.isIntersecting ?
+ oldEntry.intersectionRatio || 0 : -1;
+ var newRatio = newEntry.isIntersecting ?
+ newEntry.intersectionRatio || 0 : -1;
+
+ // Ignore unchanged ratios
+ if (oldRatio === newRatio) return;
+
+ for (var i = 0; i < this.thresholds.length; i++) {
+ var threshold = this.thresholds[i];
+
+ // Return true if an entry matches a threshold or if the new ratio
+ // and the old ratio are on the opposite sides of a threshold.
+ if (threshold == oldRatio || threshold == newRatio ||
+ threshold < oldRatio !== threshold < newRatio) {
+ return true;
+ }
+ }
+ };
+
+
+ /**
+ * Returns whether or not the root element is an element and is in the DOM.
+ * @return {boolean} True if the root element is an element and is in the DOM.
+ * @private
+ */
+ IntersectionObserver.prototype._rootIsInDom = function() {
+ return !this.root || containsDeep(document, this.root);
+ };
+
+
+ /**
+ * Returns whether or not the target element is a child of root.
+ * @param {Element} target The target element to check.
+ * @return {boolean} True if the target element is a child of root.
+ * @private
+ */
+ IntersectionObserver.prototype._rootContainsTarget = function(target) {
+ return containsDeep(this.root || document, target);
+ };
+
+
+ /**
+ * Adds the instance to the global IntersectionObserver registry if it isn't
+ * already present.
+ * @private
+ */
+ IntersectionObserver.prototype._registerInstance = function() {
+ if (registry.indexOf(this) < 0) {
+ registry.push(this);
+ }
+ };
+
+
+ /**
+ * Removes the instance from the global IntersectionObserver registry.
+ * @private
+ */
+ IntersectionObserver.prototype._unregisterInstance = function() {
+ var index = registry.indexOf(this);
+ if (index != -1) registry.splice(index, 1);
+ };
+
+
+ /**
+ * Returns the result of the performance.now() method or null in browsers
+ * that don't support the API.
+ * @return {number} The elapsed time since the page was requested.
+ */
+ function now() {
+ return window.performance && performance.now && performance.now();
+ }
+
+
+ /**
+ * Throttles a function and delays its executiong, so it's only called at most
+ * once within a given time period.
+ * @param {Function} fn The function to throttle.
+ * @param {number} timeout The amount of time that must pass before the
+ * function can be called again.
+ * @return {Function} The throttled function.
+ */
+ function throttle(fn, timeout) {
+ var timer = null;
+ return function () {
+ if (!timer) {
+ timer = setTimeout(function() {
+ fn();
+ timer = null;
+ }, timeout);
+ }
+ };
+ }
+
+
+ /**
+ * Adds an event handler to a DOM node ensuring cross-browser compatibility.
+ * @param {Node} node The DOM node to add the event handler to.
+ * @param {string} event The event name.
+ * @param {Function} fn The event handler to add.
+ * @param {boolean} opt_useCapture Optionally adds the even to the capture
+ * phase. Note: this only works in modern browsers.
+ */
+ function addEvent(node, event, fn, opt_useCapture) {
+ if (typeof node.addEventListener == 'function') {
+ node.addEventListener(event, fn, opt_useCapture || false);
+ }
+ else if (typeof node.attachEvent == 'function') {
+ node.attachEvent('on' + event, fn);
+ }
+ }
+
+
+ /**
+ * Removes a previously added event handler from a DOM node.
+ * @param {Node} node The DOM node to remove the event handler from.
+ * @param {string} event The event name.
+ * @param {Function} fn The event handler to remove.
+ * @param {boolean} opt_useCapture If the event handler was added with this
+ * flag set to true, it should be set to true here in order to remove it.
+ */
+ function removeEvent(node, event, fn, opt_useCapture) {
+ if (typeof node.removeEventListener == 'function') {
+ node.removeEventListener(event, fn, opt_useCapture || false);
+ }
+ else if (typeof node.detatchEvent == 'function') {
+ node.detatchEvent('on' + event, fn);
+ }
+ }
+
+
+ /**
+ * Returns the intersection between two rect objects.
+ * @param {Object} rect1 The first rect.
+ * @param {Object} rect2 The second rect.
+ * @return {?Object} The intersection rect or undefined if no intersection
+ * is found.
+ */
+ function computeRectIntersection(rect1, rect2) {
+ var top = Math.max(rect1.top, rect2.top);
+ var bottom = Math.min(rect1.bottom, rect2.bottom);
+ var left = Math.max(rect1.left, rect2.left);
+ var right = Math.min(rect1.right, rect2.right);
+ var width = right - left;
+ var height = bottom - top;
+
+ return (width >= 0 && height >= 0) && {
+ top: top,
+ bottom: bottom,
+ left: left,
+ right: right,
+ width: width,
+ height: height
+ };
+ }
+
+
+ /**
+ * Shims the native getBoundingClientRect for compatibility with older IE.
+ * @param {Element} el The element whose bounding rect to get.
+ * @return {Object} The (possibly shimmed) rect of the element.
+ */
+ function getBoundingClientRect(el) {
+ var rect;
+
+ try {
+ rect = el.getBoundingClientRect();
+ } catch (err) {
+ // Ignore Windows 7 IE11 "Unspecified error"
+ // https://github.com/w3c/IntersectionObserver/pull/205
+ }
+
+ if (!rect) return getEmptyRect();
+
+ // Older IE
+ if (!(rect.width && rect.height)) {
+ rect = {
+ top: rect.top,
+ right: rect.right,
+ bottom: rect.bottom,
+ left: rect.left,
+ width: rect.right - rect.left,
+ height: rect.bottom - rect.top
+ };
+ }
+ return rect;
+ }
+
+
+ /**
+ * Returns an empty rect object. An empty rect is returned when an element
+ * is not in the DOM.
+ * @return {Object} The empty rect.
+ */
+ function getEmptyRect() {
+ return {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ width: 0,
+ height: 0
+ };
+ }
+
+ /**
+ * Checks to see if a parent element contains a child elemnt (including inside
+ * shadow DOM).
+ * @param {Node} parent The parent element.
+ * @param {Node} child The child element.
+ * @return {boolean} True if the parent node contains the child node.
+ */
+ function containsDeep(parent, child) {
+ var node = child;
+ while (node) {
+ if (node == parent) return true;
+
+ node = getParentNode(node);
+ }
+ return false;
+ }
+
+
+ /**
+ * Gets the parent node of an element or its host element if the parent node
+ * is a shadow root.
+ * @param {Node} node The node whose parent to get.
+ * @return {Node|null} The parent node or null if no parent exists.
+ */
+ function getParentNode(node) {
+ var parent = node.parentNode;
+
+ if (parent && parent.nodeType == 11 && parent.host) {
+ // If the parent is a shadow root, return the host element.
+ return parent.host;
+ }
+ return parent;
+ }
+
+
+ // Exposes the constructors globally.
+ window.IntersectionObserver = IntersectionObserver;
+ window.IntersectionObserverEntry = IntersectionObserverEntry;
+
+ }(window, document));
/* jshint ignore:end */
// Let's kick things off now
diff --git a/plugins/jetpack/modules/lazy-images/lazy-images.php b/plugins/jetpack/modules/lazy-images/lazy-images.php
index 38610bab..fc800159 100644
--- a/plugins/jetpack/modules/lazy-images/lazy-images.php
+++ b/plugins/jetpack/modules/lazy-images/lazy-images.php
@@ -45,18 +45,49 @@ class Jetpack_Lazy_Images {
// Do not lazy load avatar in admin bar
add_action( 'admin_bar_menu', array( $this, 'remove_filters' ), 0 );
+
+ add_filter( 'wp_kses_allowed_html', array( $this, 'allow_lazy_attributes' ) );
}
public function setup_filters() {
- add_filter( 'the_content', array( $this, 'add_image_placeholders' ), 99 ); // run this later, so other content filters have run, including image_add_wh on WP.com
- add_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), 11 );
- add_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), 11 );
+ add_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); // run this later, so other content filters have run, including image_add_wh on WP.com
+ add_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
+ add_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
+ add_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
+ add_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX);
+ add_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX );
}
public function remove_filters() {
- remove_filter( 'the_content', array( $this, 'add_image_placeholders' ), 99 );
- remove_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), 11 );
- remove_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), 11 );
+ remove_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
+ remove_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
+ remove_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
+ remove_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
+ remove_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX);
+ remove_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX );
+ }
+
+ /**
+ * Ensure that our lazy image attributes are not filtered out of image tags.
+ *
+ * @param array $allowed_tags The allowed tags and their attributes.
+ * @return array
+ */
+ public function allow_lazy_attributes( $allowed_tags ) {
+ if ( ! isset( $allowed_tags['img'] ) ) {
+ return $allowed_tags;
+ }
+
+ // But, if images are allowed, ensure that our attributes are allowed!
+ $img_attributes = array_merge( $allowed_tags['img'], array(
+ 'data-lazy-src' => 1,
+ 'data-lazy-srcset' => 1,
+ 'data-lazy-sizes' => 1,
+ ) );
+
+ $allowed_tags['img'] = $img_attributes;
+
+ return $allowed_tags;
}
public function add_image_placeholders( $content ) {
@@ -81,6 +112,54 @@ class Jetpack_Lazy_Images {
return $content;
}
+ /**
+ * Returns true when a given string of classes contains a class signifying lazy images
+ * should not process the image.
+ *
+ * @since 5.9.0
+ *
+ * @param string $classes A string of space-separated classes.
+ * @return bool
+ */
+ public static function should_skip_image_with_blacklisted_class( $classes ) {
+ $blacklisted_classes = array(
+ 'skip-lazy',
+ 'gazette-featured-content-thumbnail',
+ );
+
+ /**
+ * Allow plugins and themes to tell lazy images to skip an image with a given class.
+ *
+ * @module lazy-images
+ *
+ * @since 5.9.0
+ *
+ * @param array An array of strings where each string is a class.
+ */
+ $blacklisted_classes = apply_filters( 'jetpack_lazy_images_blacklisted_classes', $blacklisted_classes );
+
+ if ( ! is_array( $blacklisted_classes ) || empty( $blacklisted_classes ) ) {
+ return false;
+ }
+
+ foreach ( $blacklisted_classes as $class ) {
+ if ( false !== strpos( $classes, $class ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Processes images in content by acting as the preg_replace_callback
+ *
+ * @since 5.6.0
+ *
+ * @param array $matches
+ *
+ * @return string The image with updated lazy attributes
+ */
static function process_image( $matches ) {
$old_attributes_str = $matches[2];
$old_attributes_kses_hair = wp_kses_hair( $old_attributes_str, wp_allowed_protocols() );
@@ -90,22 +169,67 @@ class Jetpack_Lazy_Images {
}
$old_attributes = self::flatten_kses_hair_data( $old_attributes_kses_hair );
- $new_attributes = $old_attributes;
+ $new_attributes = self::process_image_attributes( $old_attributes );
+
+ // If we didn't add lazy attributes, just return the original image source.
+ if ( empty( $new_attributes['data-lazy-src'] ) ) {
+ return $matches[0];
+ }
+
+ $new_attributes_str = self::build_attributes_string( $new_attributes );
+
+ return sprintf( '<img %1$s><noscript>%2$s</noscript>', $new_attributes_str, $matches[0] );
+ }
+
+ /**
+ * Given an array of image attributes, updates the `src`, `srcset`, and `sizes` attributes so
+ * that they load lazily.
+ *
+ * @since 5.7.0
+ *
+ * @param array $attributes
+ *
+ * @return array The updated image attributes array with lazy load attributes
+ */
+ static function process_image_attributes( $attributes ) {
+ if ( empty( $attributes['src'] ) ) {
+ return $attributes;
+ }
+
+ if ( ! empty( $attributes['class'] ) && self::should_skip_image_with_blacklisted_class( $attributes['class'] ) ) {
+ return $attributes;
+ }
+
+ /**
+ * Allow plugins and themes to conditionally skip processing an image via its attributes.
+ *
+ * @module-lazy-images
+ *
+ * @since 5.9.0
+ *
+ * @param bool Default to not skip processing the current image.
+ * @param array An array of attributes via wp_kses_hair() for the current image.
+ */
+ if ( apply_filters( 'jetpack_lazy_images_skip_image_with_atttributes', false, $attributes ) ) {
+ return $attributes;
+ }
+
+ $old_attributes = $attributes;
// Set placeholder and lazy-src
- $new_attributes['src'] = self::get_placeholder_image();
- $new_attributes['data-lazy-src'] = $old_attributes['src'];
+ $attributes['src'] = self::get_placeholder_image();
+ $attributes['data-lazy-src'] = $old_attributes['src'];
// Handle `srcset`
- if ( ! empty( $new_attributes['srcset'] ) ) {
- $new_attributes['data-lazy-srcset'] = $old_attributes['srcset'];
- unset( $new_attributes['srcset'] );
+ if ( ! empty( $attributes['srcset'] ) ) {
+ $attributes['data-lazy-srcset'] = $old_attributes['srcset'];
+ unset( $attributes['srcset'] );
}
// Handle `sizes`
- if ( ! empty( $new_attributes['sizes'] ) ) {
- $new_attributes['data-lazy-sizes'] = $old_attributes['sizes'];
- unset( $new_attributes['sizes'] );
+ if ( ! empty( $attributes['sizes'] ) ) {
+ $attributes['data-lazy-sizes'] = $old_attributes['sizes'];
+ unset( $attributes['sizes'] );
}
/**
@@ -113,7 +237,7 @@ class Jetpack_Lazy_Images {
*
* One potential use of this filter is for themes that set `height:auto` on the `img` tag.
* With this filter, the theme could get the width and height attributes from the
- * $new_attributes array and then add a style tag that sets those values as well, which could
+ * $attributes array and then add a style tag that sets those values as well, which could
* minimize reflow as images load.
*
* @module lazy-images
@@ -123,10 +247,7 @@ class Jetpack_Lazy_Images {
* @param array An array containing the attributes for the image, where the key is the attribute name
* and the value is the attribute value.
*/
- $new_attributes = apply_filters( 'jetpack_lazy_images_new_attributes', $new_attributes );
- $new_attributes_str = self::build_attributes_string( $new_attributes );
-
- return sprintf( '<img %1$s><noscript>%2$s</noscript>', $new_attributes_str, $matches[0] );
+ return apply_filters( 'jetpack_lazy_images_new_attributes', $attributes );
}
private static function get_placeholder_image() {
@@ -171,7 +292,10 @@ class Jetpack_Lazy_Images {
public function enqueue_assets() {
wp_enqueue_script(
'jetpack-lazy-images',
- plugins_url( 'modules/lazy-images/js/lazy-images.js', JETPACK__PLUGIN_FILE ),
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/lazy-images/js/lazy-images.min.js',
+ 'modules/lazy-images/js/lazy-images.js'
+ ),
array( 'jquery' ),
JETPACK__VERSION,
true
diff --git a/plugins/jetpack/modules/likes.php b/plugins/jetpack/modules/likes.php
index 84204cc3..deaaa676 100644
--- a/plugins/jetpack/modules/likes.php
+++ b/plugins/jetpack/modules/likes.php
@@ -296,7 +296,16 @@ class Jetpack_Likes {
JETPACK__VERSION,
false
);
- wp_register_script( 'jetpack_likes_queuehandler', plugins_url( 'likes/queuehandler.js' , __FILE__ ), array( 'jquery', 'postmessage', 'jetpack_resize' ), JETPACK__VERSION, true );
+ wp_register_script(
+ 'jetpack_likes_queuehandler',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/likes/queuehandler.min.js',
+ 'modules/likes/queuehandler.js'
+ ),
+ array( 'jquery', 'postmessage', 'jetpack_resize' ),
+ JETPACK__VERSION,
+ true
+ );
}
/**
@@ -348,8 +357,24 @@ class Jetpack_Likes {
function enqueue_admin_scripts() {
if ( empty( $_GET['post_type'] ) || 'post' == $_GET['post_type'] || 'page' == $_GET['post_type'] ) {
if ( $this->in_jetpack ) {
- wp_enqueue_script( 'likes-post-count', plugins_url( 'modules/likes/post-count.js', dirname( __FILE__ ) ), array( 'jquery' ), JETPACK__VERSION );
- wp_enqueue_script( 'likes-post-count-jetpack', plugins_url( 'modules/likes/post-count-jetpack.js', dirname( __FILE__ ) ), array( 'likes-post-count' ), JETPACK__VERSION );
+ wp_enqueue_script(
+ 'likes-post-count',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/likes/post-count.min.js',
+ 'modules/likes/post-count.js'
+ ),
+ array( 'jquery' ),
+ JETPACK__VERSION
+ );
+ wp_enqueue_script(
+ 'likes-post-count-jetpack',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/likes/post-count-jetpack.min.js',
+ 'modules/likes/post-count-jetpack.js'
+ ),
+ array( 'likes-post-count' ),
+ JETPACK__VERSION
+ );
} else {
wp_enqueue_script( 'jquery.wpcom-proxy-request', "/wp-content/js/jquery/jquery.wpcom-proxy-request.js", array('jquery'), NULL, true );
wp_enqueue_script( 'likes-post-count', plugins_url( 'likes/post-count.js', dirname( __FILE__ ) ), array( 'jquery' ), JETPACK__VERSION );
diff --git a/plugins/jetpack/modules/manage/confirm-admin.php b/plugins/jetpack/modules/manage/confirm-admin.php
index c8f105f9..d0507053 100644
--- a/plugins/jetpack/modules/manage/confirm-admin.php
+++ b/plugins/jetpack/modules/manage/confirm-admin.php
@@ -18,7 +18,7 @@ $description = __( 'Well that was easy. You can now manage all of your sites in
switch( $section ) {
case 'plugins':
- $link = 'https://wordpress.com/plugins/' . $normalized_site_url;
+ $link = 'https://wordpress.com/plugins/manage/' . $normalized_site_url;
$link_title = __( 'Manage Your Plugins', 'jetpack' );
break;
@@ -28,7 +28,7 @@ switch( $section ) {
break;
case 'themes':
- $link = 'https://wordpress.com/design/' . $normalized_site_url;
+ $link = 'https://wordpress.com/themes/' . $normalized_site_url;
$link_title = __( 'Manage Your Themes', 'jetpack' );
break;
diff --git a/plugins/jetpack/modules/masterbar/masterbar.php b/plugins/jetpack/modules/masterbar/masterbar.php
index c634c9f8..d38b9e67 100644
--- a/plugins/jetpack/modules/masterbar/masterbar.php
+++ b/plugins/jetpack/modules/masterbar/masterbar.php
@@ -40,10 +40,13 @@ class A8C_WPCOM_Masterbar {
'//2.gravatar.com',
) );
- // Atomic only - override user setting that hides masterbar from site's front.
- // https://github.com/Automattic/jetpack/issues/7667
+ // Atomic only
if ( jetpack_is_atomic_site() ) {
+ // override user setting that hides masterbar from site's front.
+ // https://github.com/Automattic/jetpack/issues/7667
add_filter( 'show_admin_bar', '__return_true' );
+ // Always sign out from .com from the masterbar
+ add_filter( 'jetpack_masterbar_should_logout_from_wpcom', '__return_true' );
}
$this->user_data = Jetpack::get_connected_user_data( $this->user_id );
@@ -84,7 +87,19 @@ class A8C_WPCOM_Masterbar {
}
public function maybe_logout_user_from_wpcom() {
- if ( isset( $_GET['context'] ) && 'masterbar' === $_GET['context'] ) {
+ /**
+ * Whether we should sign out from wpcom too when signing out from the masterbar.
+ *
+ * @since 5.9.0
+ *
+ * @param bool $masterbar_should_logout_from_wpcom False by default.
+ */
+ $masterbar_should_logout_from_wpcom = apply_filters( 'jetpack_masterbar_should_logout_from_wpcom', false );
+ if (
+ isset( $_GET['context'] ) &&
+ 'masterbar' === $_GET['context'] &&
+ $masterbar_should_logout_from_wpcom
+ ) {
do_action( 'wp_masterbar_logout' );
}
}
@@ -138,7 +153,15 @@ class A8C_WPCOM_Masterbar {
array(),
JETPACK__VERSION
);
- wp_enqueue_script( 'a8c_wpcom_masterbar_tracks_events', plugins_url( 'tracks-events.js', __FILE__ ), array( 'jquery' ), JETPACK__VERSION );
+ wp_enqueue_script(
+ 'a8c_wpcom_masterbar_tracks_events',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/masterbar/tracks-events.min.js',
+ 'modules/masterbar/tracks-events.js'
+ ),
+ array( 'jquery' ),
+ JETPACK__VERSION
+ );
wp_enqueue_script( 'a8c_wpcom_masterbar_overrides', $this->wpcom_static_url( '/wp-content/mu-plugins/admin-bar/masterbar-overrides/masterbar.js' ), array( 'jquery' ), JETPACK__VERSION );
}
@@ -852,7 +875,7 @@ class A8C_WPCOM_Masterbar {
$theme_title = $this->create_menu_item_pair(
array(
- 'url' => 'https://wordpress.com/design/' . esc_attr( $this->primary_site_slug ),
+ 'url' => 'https://wordpress.com/themes/' . esc_attr( $this->primary_site_slug ),
'id' => 'wp-admin-bar-themes',
'label' => esc_html__( 'Themes', 'jetpack' ),
),
diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php
index 5e0119e7..fadd678c 100644
--- a/plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php
+++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php
@@ -79,7 +79,16 @@ function minileven_scripts() {
wp_enqueue_style( 'style', get_stylesheet_uri() );
- wp_enqueue_script( 'small-menu', get_template_directory_uri() . '/js/small-menu.js', array( 'jquery' ), '20120206', true );
+ wp_enqueue_script(
+ 'small-menu',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/minileven/theme/pub/minileven/js/small-menu.min.js',
+ 'modules/minileven/theme/pub/minileven/js/small-menu.js'
+ ),
+ array( 'jquery' ),
+ '20120206',
+ true
+ );
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
wp_enqueue_script( 'comment-reply' );
@@ -223,13 +232,14 @@ function minileven_get_gallery_images() {
if ( ! $images ) {
$images = get_posts( array(
- 'fields' => 'ids',
- 'numberposts' => 999,
- 'order' => 'ASC',
- 'orderby' => 'menu_order',
- 'post_mime_type' => 'image',
- 'post_parent' => get_the_ID(),
- 'post_type' => 'attachment',
+ 'fields' => 'ids',
+ 'numberposts' => 999,
+ 'order' => 'ASC',
+ 'orderby' => 'menu_order',
+ 'post_mime_type' => 'image',
+ 'post_parent' => get_the_ID(),
+ 'post_type' => 'attachment',
+ 'suppress_filters' => false,
) );
}
diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/image.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/image.php
index b63cd70f..02032c2c 100644
--- a/plugins/jetpack/modules/minileven/theme/pub/minileven/image.php
+++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/image.php
@@ -26,7 +26,7 @@ get_header(); ?>
* Grab the IDs of all the image attachments in a gallery so we can get the URL of the next adjacent image in a gallery,
* or the first image (if we're looking at the last image in a gallery), or, in a gallery of one, just the link to that image file
*/
- $attachments = array_values( get_children( array( 'post_parent' => $post->post_parent, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID' ) ) );
+ $attachments = array_values( get_children( array( 'post_parent' => $post->post_parent, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID', 'suppress_filters' => false ) ) );
foreach ( $attachments as $k => $attachment ) {
if ( $attachment->ID == $post->ID )
break;
diff --git a/plugins/jetpack/modules/module-extras.php b/plugins/jetpack/modules/module-extras.php
index cbc3dc02..b6bacd6f 100644
--- a/plugins/jetpack/modules/module-extras.php
+++ b/plugins/jetpack/modules/module-extras.php
@@ -25,6 +25,7 @@ $tools = array(
'seo-tools/jetpack-seo-posts.php',
'simple-payments/simple-payments.php',
'verification-tools/verification-tools-utils.php',
+ 'woocommerce-analytics/wp-woocommerce-analytics.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 5612ccbb..477094ba 100644
--- a/plugins/jetpack/modules/module-headings.php
+++ b/plugins/jetpack/modules/module-headings.php
@@ -82,7 +82,7 @@ function jetpack_get_module_i18n( $key ) {
'lazy-images' => array(
'name' => _x( 'Lazy Images', 'Module Name', 'jetpack' ),
- 'description' => _x( 'Improve performance by loading images just before they scroll into view', 'Module Description', 'jetpack' ),
+ 'description' => _x( 'Lazy load images', 'Module Description', 'jetpack' ),
),
'likes' => array(
@@ -125,7 +125,7 @@ function jetpack_get_module_i18n( $key ) {
'photon' => array(
'name' => _x( 'Photon', 'Module Name', 'jetpack' ),
- 'description' => _x( 'Speed up images and photos', 'Module Description', 'jetpack' ),
+ 'description' => _x( 'Serve images from our servers', 'Module Description', 'jetpack' ),
'recommended description' => _x( 'Mirrors and serves your images from our free and fast image CDN, improving your site’s performance with no additional load on your servers.', 'Jumpstart Description', 'jetpack' ),
),
@@ -163,7 +163,6 @@ function jetpack_get_module_i18n( $key ) {
'seo-tools' => array(
'name' => _x( 'SEO Tools', 'Module Name', 'jetpack' ),
'description' => _x( 'Better results on search engines and social media.', 'Module Description', 'jetpack' ),
- 'recommended description' => _x( 'Better results on search engines and social media.', 'Jumpstart Description', 'jetpack' ),
),
'sharedaddy' => array(
diff --git a/plugins/jetpack/modules/module-info.php b/plugins/jetpack/modules/module-info.php
index 620b5e16..ece1e050 100644
--- a/plugins/jetpack/modules/module-info.php
+++ b/plugins/jetpack/modules/module-info.php
@@ -404,13 +404,30 @@ add_action( 'jetpack_learn_more_button_photon', 'jetpack_photon_more_link' );
function jetpack_photon_more_info() {
esc_html_e(
- "Your images are automatically optimized for different display resolutions to serve the best
- possible image quality. We also cache and serve them from our fast, global network (CDN)."
+ 'Jetpack will optimize your images and serve them from the server location nearest
+ to your visitors. Using our global content delivery network will boost the loading speed of your site.'
, 'jetpack' );
}
add_action( 'jetpack_module_more_info_photon', 'jetpack_photon_more_info' );
/**
+ * Lazy Images
+ */
+function jetpack_lazy_images_more_link() {
+ echo 'https://jetpack.com/support/lazy-images/';
+}
+add_action( 'jetpack_learn_more_button_lazy-images', 'jetpack_lazy_images_more_link' );
+
+function jetpack_lazy_images_more_info() {
+ esc_html_e(
+ 'Improve your site\'s speed by only loading images visible on the screen.
+ New images will load just before they scroll into view. This prevents viewers
+ from having to download all the images on a page all at once, even ones they can\'t see.'
+ , 'jetpack' );
+}
+add_action( 'jetpack_module_more_info_lazy-images', 'jetpack_lazy_images_more_info' );
+
+/**
* Tiled Galleries
*/
function jetpack_tiled_gallery_more_link() {
@@ -650,3 +667,18 @@ function jetpack_google_analytics_more_info() {
, 'jetpack' );
}
add_action( 'jetpack_module_more_info_google-analytics', 'jetpack_google_analytics_more_info' );
+
+/**
+ * WooCommerce Analytics
+ */
+function jetpack_woocommerce_analytics_more_link() {
+ echo 'https://jetpack.com/support/';
+}
+add_action( 'jetpack_learn_more_button_woocommerce-analytics', 'jetpack_woocommerce_analytics_more_link' );
+
+function jetpack_woocommerce_analytics_more_info() {
+ esc_html_e(
+ 'Enhanced analytics for WooCommerce and Jetpack users.'
+ , 'jetpack' );
+}
+add_action( 'jetpack_module_more_info_woocommerce-analytics', 'jetpack_woocommerce_analytics_more_info' );
diff --git a/plugins/jetpack/modules/photon.php b/plugins/jetpack/modules/photon.php
index c2eb97a0..628f57c5 100644
--- a/plugins/jetpack/modules/photon.php
+++ b/plugins/jetpack/modules/photon.php
@@ -1,7 +1,7 @@
<?php
/**
* Module Name: Photon
- * Module Description: Speed up images and photos
+ * Module Description: Serve images from our servers
* Jumpstart Description: Mirrors and serves your images from our free and fast image CDN, improving your site’s performance with no additional load on your servers.
* Sort Order: 25
* Recommendation Order: 1
diff --git a/plugins/jetpack/modules/protect.php b/plugins/jetpack/modules/protect.php
index a47a1af9..6865179e 100644
--- a/plugins/jetpack/modules/protect.php
+++ b/plugins/jetpack/modules/protect.php
@@ -268,7 +268,8 @@ class Jetpack_Protect_Module {
*
* @return void
*/
- function log_failed_attempt() {
+ function log_failed_attempt( $login_user = null ) {
+
/**
* Fires before every failed login attempt.
*
@@ -276,9 +277,12 @@ class Jetpack_Protect_Module {
*
* @since 3.4.0
*
- * @param string jetpack_protect_get_ip IP stored by Protect.
+ * @param array Information about failed login attempt
+ * [
+ * 'login' => (string) Username or email used in failed login attempt
+ * ]
*/
- do_action( 'jpp_log_failed_attempt', jetpack_protect_get_ip() );
+ do_action( 'jpp_log_failed_attempt', array( 'login' => $login_user ) );
if ( isset( $_COOKIE['jpp_math_pass'] ) ) {
@@ -431,7 +435,7 @@ class Jetpack_Protect_Module {
/**
* JETPACK_ALWAYS_PROTECT_LOGIN will always disable the login page, and use a page provided by Jetpack.
*/
- if ( defined( 'JETPACK_ALWAYS_PROTECT_LOGIN' ) && JETPACK_ALWAYS_PROTECT_LOGIN ) {
+ if ( Jetpack_Constants::is_true( 'JETPACK_ALWAYS_PROTECT_LOGIN' ) ) {
$this->kill_login();
}
diff --git a/plugins/jetpack/modules/protect/blocked-login-page.php b/plugins/jetpack/modules/protect/blocked-login-page.php
index ec1e8835..f26b5193 100644
--- a/plugins/jetpack/modules/protect/blocked-login-page.php
+++ b/plugins/jetpack/modules/protect/blocked-login-page.php
@@ -17,7 +17,6 @@ class Jetpack_Protect_Blocked_Login_Page {
public $can_send_recovery_emails;
public $ip_address;
public $valid_blocked_user_id;
- public $page_title;
public $email_address;
const HELP_URL = 'https://jetpack.com/support/security-features/#unblock';
const HTTP_STATUS_CODE_TOO_MANY_REQUESTS = 429;
@@ -183,8 +182,6 @@ class Jetpack_Protect_Blocked_Login_Page {
}
public function render_and_die() {
- $this->page_title = __( 'Login Blocked by Jetpack', 'jetpack' );
-
if ( ! $this->can_send_recovery_emails ) {
$this->render_blocked_login_message();
@@ -192,7 +189,8 @@ class Jetpack_Protect_Blocked_Login_Page {
}
if ( isset( $_GET['validate_jetpack_protect_recovery'] ) && $_GET['user_id'] ) {
- $this->protect_die( __( 'Could not validate recovery token.', 'jetpack' ) );
+ $error = new WP_Error( 'invalid_token', __( "Oops, we couldn't validate the recovery token.", 'jetpack' ) );
+ $this->protect_die( $error );
return;
}
@@ -220,9 +218,12 @@ class Jetpack_Protect_Blocked_Login_Page {
function process_recovery_email() {
$sent = $this->send_recovery_email();
-
+ $show_recovery_form = true;
if ( is_wp_error( $sent ) ) {
- $this->protect_die( $sent, true );
+ if ( 'email_already_sent' === $sent->get_error_code() ) {
+ $show_recovery_form = false;
+ }
+ $this->protect_die( $sent,null,true, $show_recovery_form );
} else {
$this->render_recovery_success();
}
@@ -236,7 +237,7 @@ class Jetpack_Protect_Blocked_Login_Page {
$user = get_user_by( 'email', trim( $email ) );
if ( ! $user ) {
- return new WP_Error( 'invalid_user', __( 'Oops, could not find a user with that email address.', 'jetpack' ) );
+ return new WP_Error( 'invalid_user', __( "Oops, we couldn't find a user with that email. Please try again!", 'jetpack' ) );
}
$this->email_address = $email;
$path = sprintf( '/sites/%d/protect/recovery/request', Jetpack::get_option( 'id' ) );
@@ -258,72 +259,62 @@ class Jetpack_Protect_Blocked_Login_Page {
$result = json_decode( wp_remote_retrieve_body( $response ) );
if ( self::HTTP_STATUS_CODE_TOO_MANY_REQUESTS === $code ) {
- return new WP_Error( 'email_already_sent', __( 'An email was already sent to this address.', 'jetpack' ) );
+ return new WP_Error( 'email_already_sent', sprintf( __( 'Recovery instructions were sent to %s. Check your inbox!', 'jetpack' ), $this->email_address ) );
} else if ( is_wp_error( $result ) || empty( $result ) || isset( $result->error ) ) {
- return new WP_Error( 'email_send_error', __( 'There was an error sending your email.', 'jetpack' ) );
+ return new WP_Error( 'email_send_error', __( 'Oops, we were unable to send a recovery email. Try again.', 'jetpack' ) );
}
return true;
}
- function protect_die( $content, $back_link = false ) {
- $image = sprintf(
- '<img src="%s" width="180" style="display: block; margin: 0 auto;" />',
- plugins_url( 'modules/protect/jetpack-security.png', JETPACK__PLUGIN_FILE )
- );
-
+ function protect_die( $content, $title = null, $back_link = false, $recovery_form = false ) {
+ if ( empty( $title ) ) {
+ $title = __( 'Jetpack has locked your site\'s login page.', 'jetpack' );
+ }
if ( is_wp_error( $content ) ) {
- $content = $content->get_error_message();
+ $svg = '<svg class="gridicon gridicons-notice" 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 15h-2v-2h2v2zm0-4h-2l-.5-6h3l-.5 6z"/></g></svg>';
+ $content = '<span class="error"> '. $svg . $content->get_error_message() . '</span>';
}
- // hack to get around default wp_die_handler. https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/functions.php#L2698
- $content = $image . '</p> ' . $content . '<p>';
+ $content = '<p>'. $content .'</p>';
+ // If for some reason the login pop up box show up in the wp-admin.
if ( isset( $_GET['interim-login'] ) ) {
- $content = "<style>html{ background-color: #fff; } #error-page { margin:0 auto; padding: 1em; box-shadow: none; } </style>" . $content;
+ $content = "<style>html{ background-color: #fff; } #error-message { margin:0 auto; padding: 1em; box-shadow: none; } </style>" . $content;
}
+ $this->display_page( $title, $content, $back_link, $recovery_form );
- wp_die( $content, $this->page_title, array( 'back_link' => $back_link, 'response' => 200 ) );
}
function render_recovery_form() {
- $content = $this->get_html_blocked_login_message() . $this->get_html_recovery_form();
- $this->protect_die( $content );
+ $content = $this->get_html_blocked_login_message();
+ $this->protect_die( $content, null, null, true );
}
function render_recovery_success() {
- $this->protect_die( sprintf( __( 'An email with recovery instructions was sent to %s.', 'jetpack' ), $this->email_address ) );
+ $this->protect_die( sprintf( __( 'Recovery instructions were sent to %s. Check your inbox!', 'jetpack' ), $this->email_address ) );
}
function get_html_blocked_login_message() {
$icon = '<svg class="gridicon gridicons-spam" style="fill:#d94f4f" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M17 2H7L2 7v10l5 5h10l5-5V7l-5-5zm-4 15h-2v-2h2v2zm0-4h-2l-.5-6h3l-.5 6z"/></g></svg>';
$ip = str_replace( 'http://', '', esc_url( 'http://' . $this->ip_address ) );
- ob_start(); ?>
- <h3><?php printf( __( 'Jetpack Protect has locked your site\'s login page.', 'jetpack' ) ); ?></h3>
- <?php printf(
- __( '<p><span style="float:left; display:block; margin-right:10px;">%1$s</span>Your IP (%2$s) has been flagged for potential security violations. <a href="%3$s">Learn More</a></p>', 'jetpack' ),
+ return sprintf(
+ __( '<p>Your IP address <code>%2$s</code> has been flagged for potential security violations. You can unlock your login by sending yourself a special link via email. <a href="%3$s">Learn More</a></p>', 'jetpack' ),
$icon,
$ip,
esc_url( self::HELP_URL )
);
-
- $contents = ob_get_contents();
- ob_end_clean();
-
- return $contents;
}
function get_html_recovery_form() {
ob_start(); ?>
- <div style="margin-top:100px;">
- <p><?php _e( 'Email yourself a special link to regain access the login form.', 'jetpack' ); ?></p>
+ <div>
<form method="post" action="?jetpack-protect-recovery=true">
<?php echo wp_nonce_field( 'bypass-protect' ); ?>
- <p><label for="email" style="font-size:12px;">Email Address<br/></label>
- <input type="email" name="email" style="font-size:24px; padding:3px; margin: 2px 6px 16px 0; width:100%; border: 1px solid #ddd;
- box-shadow: inset 0 1px 2px rgba(0,0,0,.07);"/>
- <input type="submit" class="button button-primary button-large"
- value="<?php echo esc_attr( __( 'Send', 'jetpack' ) ); ?>"/>
+ <p><label for="email"><?php esc_html_e( 'Your email', 'jetpack' ); ?><br/></label>
+ <input type="email" name="email" class="text-input"/>
+ <input type="submit" class="button"
+ value="<?php esc_attr_e( 'Send email', 'jetpack' ); ?>"/>
</p>
</form>
</div>
@@ -334,4 +325,287 @@ class Jetpack_Protect_Blocked_Login_Page {
return $contents;
}
+
+ function display_page( $title, $message, $back_button = false, $recovery_form = false ) {
+
+ if ( ! headers_sent() ) {
+ nocache_headers();
+ header( 'Content-Type: text/html; charset=utf-8' );
+ }
+
+ $text_direction = 'ltr';
+ if ( is_rtl() ) {
+ $text_direction = 'rtl';
+ }
+ ?>
+ <!DOCTYPE html>
+ <html xmlns="http://www.w3.org/1999/xhtml" <?php if ( function_exists( 'language_attributes' ) && function_exists( 'is_rtl' ) ) {
+ language_attributes();
+ } else {
+ echo "dir='$text_direction'";
+ } ?>>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width">
+ <?php
+ if ( function_exists( 'wp_no_robots' ) ) {
+ wp_no_robots();
+ }
+ ?>
+ <title><?php echo $title ?></title>
+ <style type="text/css">
+ html {
+ background: #f3f6f8;
+ }
+
+ body {
+ color: #2e4453;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+ margin: 2em auto;
+ padding: 1em 2em;
+ max-width: 460px;
+ text-align: left;
+ }
+ body.is-rtl {
+ text-align: right;
+ }
+ h1 {
+ clear: both;
+ color: #3d596d;
+ font-size: 24px;
+ margin:0 0 24px 0;
+ padding: 0;
+ font-weight: 400;
+ }
+
+ #error-message {
+ box-sizing: border-box;
+ background: white;
+ box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3;
+ padding: 24px;
+ }
+
+ #error-message img {
+ margin: 0 auto;
+ display: block;
+ }
+
+ #error-page {
+ margin-top: 50px;
+ }
+
+ #error-page p {
+ font-size: 14px;
+ line-height: 1.5;
+ margin: 24px 0 0;
+ }
+
+ #error-page code {
+ font-family: Consolas, Monaco, monospace;
+ }
+
+ ul li {
+ margin-bottom: 10px;
+ font-size: 14px;
+ }
+
+ a {
+ color: #00aadc;
+ }
+
+ label {
+ font-weight: bold;
+ font-size:16px;
+ }
+
+ a:hover,
+ a:active {
+ color: #0085be;
+ }
+
+ a:focus {
+ color: #124964;
+ -webkit-box-shadow: 0 0 0 1px #5b9dd9,
+ 0 0 2px 1px rgba(30, 140, 190, .8);
+ box-shadow: 0 0 0 1px #5b9dd9,
+ 0 0 2px 1px rgba(30, 140, 190, .8);
+ outline: none;
+ }
+
+ .button {
+ background: #00aadc;
+ color: white;
+ border-color: #008ab3;
+ border-style: solid;
+ border-width: 1px 1px 2px;
+ cursor: pointer;
+ display: inline-block;
+ margin: 0;
+ margin-right: 0px;
+ outline: 0;
+ overflow: hidden;
+ font-weight: 500;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ box-sizing: border-box;
+ font-size: 14px;
+ line-height: 21px;
+ border-radius: 4px;
+ padding: 7px 14px 9px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ font-size: 14px;
+ width: 100%;
+ }
+
+ .button:hover,
+ .button:focus {
+ border-color: #005082;
+ outline: none;
+ }
+
+ .button:focus {
+ border-color: #005082;
+ -webkit-box-shadow: 0 0 3px rgba(0, 115, 170, .8);
+ box-shadow: 0 0 3px rgba(0, 115, 170, .8);
+ outline: none;
+ }
+ .button::-moz-focus-inner {
+ border: 0;
+ }
+
+ .button:active {
+ border-width: 2px 1px 1px;
+ }
+ .gridicon {
+ fill: currentColor;
+ vertical-align: middle;
+ }
+ #error-footer {
+ padding: 16px;
+ }
+ #error-footer a {
+ text-decoration: none;
+ line-height:20px;
+ font-size: 14px;
+ color: #4f748e;
+ }
+ #error-footer a:hover {
+ color: #2e4453;
+ }
+ #error-footer .gridicon{
+ width: 16px;
+ }
+ #error-footer .gridicons-help {
+ width: 24px;
+ margin-right:8px;
+ }
+
+ .is-rtl #error-footer .gridicons-help {
+ margin-left:8px;
+ }
+
+ .error {
+ background: #d94f4f;
+ color:#FFF;
+ display: block;
+ border-radius: 3px;
+ line-height: 1.5;
+ padding: 16px;
+ padding-left: 42px;
+ }
+ .is-rtl .error {
+ padding-right: 42px;
+ }
+ .error .gridicon {
+ float: left;
+ margin-left: -32px;
+ }
+
+ .is-rtl .error .gridicon {
+ float: right;
+ margin-right: -32px;
+ }
+
+ .text-input {
+ margin: 0;
+ padding: 7px 14px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: white;
+ transition: all .15s ease-in-out;
+ box-sizing: border-box;
+ margin: 8px 0 16px;
+ }
+ #image {
+ display: block;
+ width: 200px;
+ margin: 0 auto;
+ }
+ <?php
+ $rtl_class = '';
+ if ( 'rtl' == $text_direction ) {
+ $rtl_class = 'class="is-rtl"';
+ echo 'body { font-family: Tahoma, Arial; }';
+ }
+ ?>
+ </style>
+ </head>
+ <body id="error-page" <?php echo $rtl_class; ?>>
+ <h1 id="error-title"><?php echo esc_html( $title ); ?></h1>
+ <div id="error-message">
+ <svg id="image" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 250 134">
+ <path fill="#E9EFF4" d="M205.2,129.8c3.7-0.7,7.4-0.9,11.1-1.1l5.5-0.1l5.5,0c3.7,0,7.4,0.1,11.1,0.2c3.7,0.1,7.4,0.3,11.1,0.8 c0.3,0,0.5,0.3,0.5,0.6c0,0.2-0.2,0.4-0.5,0.5c-3.7,0.5-7.4,0.6-11.1,0.8c-3.7,0.1-7.4,0.2-11.1,0.2l-5.5,0l-5.5-0.1 c-3.7-0.1-7.4-0.4-11.1-1.1c-0.1,0-0.2-0.2-0.2-0.3C205,129.9,205.1,129.8,205.2,129.8"/>
+ <path fill="#E9EFF4" d="M0.2,130.9c3-0.7,5.9-0.9,8.9-1.1l4.4-0.1l4.4,0c3,0,5.9,0.1,8.9,0.2c3,0.1,5.9,0.3,8.9,0.8 c0.3,0,0.5,0.3,0.4,0.6c0,0.2-0.2,0.4-0.4,0.4c-3,0.5-5.9,0.6-8.9,0.8c-3,0.1-5.9,0.2-8.9,0.2l-4.4,0l-4.4-0.1 c-3-0.1-5.9-0.4-8.9-1.1c-0.1,0-0.2-0.2-0.2-0.3C0,131,0.1,130.9,0.2,130.9"/>
+ <path fill="#C8D7E2" d="M101.6,130.1H70.1V52.5c0-8.5,6.9-15.3,15.3-15.3h16.1V130.1z"/>
+ <path fill="#0DA9DD" d="M191.5,130.1h-73.8v-5.4c0-8.9,7.2-16.1,16.1-16.1h57.7V130.1z"/>
+ <path fill="#C7E9F5" d="M55.2,25.6l-0.1,9.8L55,57l-0.1,21.6c0,0.2,0.2,0.4,0.4,0.4c0.2,0,0.4-0.2,0.4-0.4L56.6,57l0.8-21.6 c0.1-3.3,0.2-6.5,0.3-9.8H55.2z"/>
+ <path fill="#C7E9F5" d="M203.1,25.6l0.1,18.1c0.2,28.8,0.4,57.6,1.2,86.3c0,0.4,0.4,0.8,0.8,0.8c0.4,0,0.8-0.3,0.8-0.8 c0.8-28.8,1-57.6,1.2-86.3l0.1-18.1H203.1z"/>
+ <path fill="#7FD3F2" d="M55.3,25.6v-8.2v-6.8c0-5.9,4-10.7,9-10.7h134c5,0,9,4.8,9,10.7v14.9H55.3z"/>
+ <path fill="#005083" d="M210.7,25.6c-13.3,1.1-26.7,1-40,1l-40,0.2l-40-0.2c-13.3-0.1-26.7,0-40-1V25c13.3-1.1,26.7-1,40-1l40-0.2 l40,0.2c13.3,0.1,26.7,0,40,1V25.6z"/>
+ <polygon fill="#C7E9F5" points="168.7,95.6 117.7,95.6 117.7,44.6 "/>
+ <path fill="#C8D7E2" d="M191.5,56.5c0,11-8.9,19.9-19.9,19.9c-11,0-19.9-8.9-19.9-19.9c0-11,8.9-19.9,19.9-19.9 C182.6,36.6,191.5,45.5,191.5,56.5"/>
+ <path fill="#FFFFFF" d="M213.2,95.5c-3.3-5.1-3.2-16.7-3.2-28.4h-32.3c0,0-5.2,25.5,4.6,33c7.5-0.1,29.9-0.6,29.9-0.6"/>
+ <path fill="#C8D7E2" d="M213.5,95.3l-0.1-0.1l-0.3-0.5c-0.2-0.4-0.3-0.7-0.5-1.1c-0.3-0.8-0.5-1.6-0.7-2.4c-0.1-0.5-0.2-1.1-0.3-1.6 c-0.4,0-0.8,0-1.2,0c0.5,2.1,1.1,4.3,2.4,6.1l0.2,0.2c0.2,0,0.4-0.1,0.5-0.3C213.6,95.5,213.6,95.4,213.5,95.3L213.5,95.3z"/>
+ <path fill="#C8D7E2" d="M212.5,98.6c-0.1,0-0.2,0-0.3,0l-0.1,0H212l-0.3,0l-0.6,0l-1.3,0l-2.5,0l-5,0l-19.5,0.2 c-1.9-1.7-3.1-4.1-3.8-6.5c-0.8-2.6-1.1-5.4-1.2-8.2c-0.2-5.2,0.3-10.4,1.1-15.6l5.7-0.1c0-0.9,0-1.8,0-2.6l-4.4,0l-2.5,0 c-0.4,0-0.8,0.2-1,0.5c-0.1,0.2-0.2,0.3-0.3,0.5l-0.1,0.3l-0.2,1.2c-0.3,1.7-0.5,3.3-0.7,5c-0.3,3.3-0.5,6.7-0.4,10.1 c0.1,3.4,0.5,6.7,1.5,10c0.5,1.6,1.2,3.2,2.2,4.7c0.5,0.7,1,1.4,1.7,2c0.3,0.3,0.6,0.6,1,0.9l0.1,0.1c0.1,0,0.2,0.1,0.3,0.2 c0.2,0.1,0.5,0.1,0.6,0.1l0.6,0l20-0.6l5-0.2l2.5-0.1l1.2,0l0.3,0l0.2,0c0,0,0.3,0,0.4-0.1c0.3-0.2,0.5-0.5,0.5-0.9 C213.1,99.1,212.9,98.7,212.5,98.6z"/>
+ <path fill="#FFFFFF" d="M223.1,84.8c-3.3-5.1-4.8-16.7-4.8-28.4h-32.3c0,0-3.5,25.5,6.3,33c7.5-0.1,29.9-0.6,29.9-0.6"/>
+ <path fill="#C8D7E2" d="M222.9,84.9c-1.3-2.1-2.2-4.4-2.8-6.7c-0.6-2.4-1.1-4.8-1.5-7.2c-0.7-4.8-1-9.1-1-13.9l0,0l-31,0.1l0,0 c-0.4,2.8-0.5,5.1-0.5,7.9c-0.1,2.9,0,5.7,0.3,8.6c0.3,2.8,0.8,5.7,1.7,8.3c0.9,2.6,2.3,5.2,4.5,6.9l-0.4-0.1l14.9-0.2 c5-0.1,10-0.1,14.9-0.1c0.1,0,0.3,0.1,0.3,0.3c0,0.1-0.1,0.3-0.2,0.3c-5,0.2-10,0.4-14.9,0.5l-14.9,0.4c-0.1,0-0.3,0-0.4-0.1l0,0 c-2.5-1.9-3.9-4.7-5-7.4c-1-2.8-1.5-5.7-1.9-8.6c-0.3-2.9-0.4-5.8-0.4-8.8c0.1-2.9,0.2-5.8,0.6-8.8c0-0.4,0.4-0.6,0.7-0.6h0 l32.3,0.1h0c0.3,0,0.6,0.3,0.6,0.6v0c0,4.8,0.2,9.6,0.7,14.4c0.3,2.4,0.6,4.8,1.2,7.1c0.5,2.3,1.2,4.7,2.4,6.8c0,0.1,0,0.1,0,0.2 C223.1,85,223,85,222.9,84.9"/>
+ <path fill="#C8D7E2" d="M192.1,67.1c1.6-0.9,3.4-1.2,5.1-1.3c1.7-0.2,3.5-0.2,5.2-0.2c3.5,0.1,6.9,0.2,10.3,1c0.1,0,0.2,0.2,0.2,0.3 c0,0.1-0.1,0.2-0.2,0.2c-3.4,0.2-6.9,0-10.3,0c-1.7,0-3.4,0-5.1,0c-1.7,0-3.4,0.1-5.1,0.3l0,0c-0.1,0-0.1,0-0.1-0.1 C192,67.2,192.1,67.1,192.1,67.1"/>
+ <path fill="#C8D7E2" d="M194.1,74c1.4,0,2.7,0,4.1,0c1.4,0,2.7,0,4.1,0c2.7,0,5.4-0.1,8.2-0.2c0.1,0,0.3,0.1,0.3,0.3 c0,0.1-0.1,0.2-0.2,0.3c-1.3,0.5-2.7,0.7-4.1,0.9c-1.4,0.2-2.8,0.2-4.2,0.3c-1.4,0-2.8,0-4.2-0.2c-1.4-0.2-2.8-0.4-4.1-1.1 c-0.1,0-0.1-0.1,0-0.2C193.9,74.1,194,74,194.1,74L194.1,74z"/>
+ <path fill="#86A6BD" d="M40.2,88.6c-0.5,0-0.8-0.4-0.9-0.9l-0.1-8.2c0-0.7,0-1.4,0-2.1c0.1-0.7,0.2-1.5,0.4-2.2c0.4-1.4,1-2.8,1.9-4 c1.7-2.5,4.3-4.3,7.1-5.1c0.7-0.2,1.5-0.3,2.2-0.5c0.7-0.1,1.5-0.1,2.2-0.1c1.3,0,2.9,0,4.4,0.4c2.9,0.7,5.6,2.5,7.4,4.9 c0.9,1.2,1.6,2.6,2.1,4c0.5,1.4,0.6,3,0.6,4.4l0,16.4c0,0.7-0.6,1.3-1.3,1.3l-6.7,0c-0.7,0-1.3-0.6-1.3-1.3v0l0-10.8l0-5.4 c0-1.4-0.7-2.8-1.8-3.5c-0.6-0.4-1.3-0.6-2-0.7c-0.7,0-1.9,0-2.5,0c-1.4,0.1-2.7,1-3.3,2.3c-0.3,0.7-0.4,1.3-0.4,2.1l0,2.7 l-0.1,5.4l0,0c0,0.5-0.4,0.9-1,0.9"/>
+ <path fill="#FFFFFF" d="M41.1,86.9l0.1-7.3c-0.1-2.6,0.7-5,2.1-7.1c1.4-2,3.6-3.5,5.9-4.1c0.6-0.2,1.2-0.3,1.8-0.3 c0.6,0,1.2-0.1,1.9,0c1.4,0,2.5,0,3.7,0.4c2.4,0.6,4.5,2,5.9,4c0.7,1,1.3,2.1,1.6,3.2c0.4,1.2,0.5,2.3,0.5,3.7l0,15.1l0,0l-4.2,0 l0-9.5l0-5.4c0-2.2-1.2-4.4-3-5.5c-0.9-0.6-2-0.9-3.1-1c-1.1,0-1.7,0-2.9,0c-2.2,0.2-4.2,1.7-5.1,3.6c-0.5,0.9-0.7,2.1-0.6,3.1 l0,2.7l0.1,4.4l0,0L41.1,86.9L41.1,86.9"/>
+ <path fill="#86A6BD" d="M36.3,133c-1.9,0-3.8-1.1-4.8-2.8c-0.5-0.8-0.7-1.8-0.7-2.8l0-2.4l0-9.6l-0.1-9.6l0-4.8c0-0.7,0-1.8,0.3-2.8 c0.3-1,0.9-1.8,1.7-2.5c0.8-0.6,1.7-1.1,2.7-1.3c1.1-0.2,1.8-0.1,2.6-0.1l4.8,0l9.6-0.1l19.2,0c2.1,0,4.1,1.2,5.1,3 c0.5,0.9,0.8,2,0.8,3l0,2.4l0,9.6l-0.1,9.6l0,4.8c0,0.7,0,1.8-0.4,2.8c-0.3,0.9-1,1.8-1.7,2.4c-0.8,0.6-1.7,1.1-2.7,1.2 c-1.1,0.1-1.8,0-2.6,0.1l-4.8,0l-9.6-0.1L36.3,133z"/>
+ <path fill="#FFFFFF" d="M74.8,112.3l-0.1-9.6l0-2.4c0-0.6-0.1-1.1-0.4-1.6c-0.6-1-1.7-1.6-2.8-1.6l-19.2,0L42.7,97l-4.8,0 c-0.8,0-1.7,0-2.2,0c-0.6,0.1-1.1,0.3-1.6,0.7c-0.5,0.4-0.8,0.9-1,1.4c-0.2,0.6-0.2,1.1-0.2,2l0,4.8l-0.1,9.6l0,9.6l0,2.4 c0,0.6,0.2,1.3,0.5,1.8c0.6,1.1,1.9,1.8,3.1,1.8l19.2-0.1l9.6-0.1l4.8,0c0.8,0,1.7,0,2.2-0.1c0.6-0.1,1.2-0.4,1.6-0.8 c0.5-0.4,0.8-0.9,1-1.5c0.2-0.6,0.2-1.1,0.2-2l0-4.8L74.8,112.3z"/>
+ <path fill="#86A6BD" d="M48.1,121.4l2.9-6.2c0.3-0.6,0.2-1.3-0.3-1.8c-1-1-1.5-2.5-1.2-4c0.3-1.7,1.7-3.1,3.4-3.4 c2.9-0.6,5.4,1.6,5.4,4.4c0,1.2-0.5,2.3-1.3,3.1c-0.5,0.5-0.6,1.2-0.3,1.8l2.9,6.2c0.1,0.2-0.1,0.5-0.3,0.5H48.4 C48.1,121.9,48,121.6,48.1,121.4"/>
+ </svg>
+
+ <?php echo $message; ?>
+ <?php if ( $recovery_form ) {
+ echo $this->get_html_recovery_form();
+ } ?>
+ </div>
+ <div id="error-footer">
+ <?php if ( $back_button && ! $recovery_form ) {
+ if ( 'rtl' == $text_direction ) {
+ $back_button_icon = '<svg class="gridicon gridicons-arrow-right" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z"/></g></svg>';
+ } else {
+ $back_button_icon = '<svg class="gridicon gridicons-arrow-left" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></g></svg>';
+ }
+ ?>
+ <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>
+ <?php } ?>
+ </div>
+ </body>
+ </html>
+ <?php
+ die();
+ }
}
diff --git a/plugins/jetpack/modules/protect/jetpack-security.png b/plugins/jetpack/modules/protect/jetpack-security.png
deleted file mode 100644
index 189ae6b3..00000000
--- a/plugins/jetpack/modules/protect/jetpack-security.png
+++ /dev/null
Binary files differ
diff --git a/plugins/jetpack/modules/protect/math-fallback.php b/plugins/jetpack/modules/protect/math-fallback.php
index 69d283e7..94b5d942 100644
--- a/plugins/jetpack/modules/protect/math-fallback.php
+++ b/plugins/jetpack/modules/protect/math-fallback.php
@@ -37,7 +37,9 @@ if ( ! class_exists( 'Jetpack_Protect_Math_Authenticate' ) ) {
$correct_ans = isset( $_POST[ 'jetpack_protect_answer' ] ) ? $_POST[ 'jetpack_protect_answer' ] : '' ;
if( isset( $_COOKIE[ 'jpp_math_pass' ] ) ) {
- $transient = Jetpack_Protect_Module::get_transient( 'jpp_math_pass_' . $_COOKIE[ 'jpp_math_pass' ] );
+ $jetpack_protect = Jetpack_Protect_Module::instance();
+ $transient = $jetpack_protect->get_transient( 'jpp_math_pass_' . $_COOKIE[ 'jpp_math_pass' ] );
+
if( !$transient || $transient < 1 ) {
Jetpack_Protect_Math_Authenticate::generate_math_page();
}
@@ -100,7 +102,9 @@ if ( ! class_exists( 'Jetpack_Protect_Math_Authenticate' ) ) {
Jetpack_Protect_Math_Authenticate::generate_math_page(true);
} else {
$temp_pass = substr( sha1( rand( 1, 100000000 ) . get_site_option( 'jetpack_protect_key' ) ), 5, 25 );
- Jetpack_Protect_Module::set_transient( 'jpp_math_pass_' . $temp_pass, 3, DAY_IN_SECONDS );
+
+ $jetpack_protect = Jetpack_Protect_Module::instance();
+ $jetpack_protect->set_transient( 'jpp_math_pass_' . $temp_pass, 3, DAY_IN_SECONDS );
setcookie('jpp_math_pass', $temp_pass, time() + DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false);
return true;
}
diff --git a/plugins/jetpack/modules/protect/protect.png b/plugins/jetpack/modules/protect/protect.png
new file mode 100644
index 00000000..67bcdbad
--- /dev/null
+++ b/plugins/jetpack/modules/protect/protect.png
Binary files differ
diff --git a/plugins/jetpack/modules/publicize/publicize.php b/plugins/jetpack/modules/publicize/publicize.php
index 75c9333e..8f051f75 100644
--- a/plugins/jetpack/modules/publicize/publicize.php
+++ b/plugins/jetpack/modules/publicize/publicize.php
@@ -260,7 +260,7 @@ abstract class Publicize_Base {
/**
* Fires when a post is saved, checks conditions and saves state in postmeta so that it
- * can be picked up later by @see ::publicize_post()
+ * can be picked up later by @see ::publicize_post() on WordPress.com codebase.
*/
function save_meta( $post_id, $post ) {
$cron_user = null;
diff --git a/plugins/jetpack/modules/publicize/ui.php b/plugins/jetpack/modules/publicize/ui.php
index 7d5e3f36..9d347e35 100644
--- a/plugins/jetpack/modules/publicize/ui.php
+++ b/plugins/jetpack/modules/publicize/ui.php
@@ -78,7 +78,10 @@ class Publicize_UI {
function load_assets() {
wp_enqueue_script(
'publicize',
- plugins_url( 'assets/publicize.js', __FILE__ ),
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/publicize/assets/publicize.min.js',
+ 'modules/publicize/assets/publicize.js'
+ ),
array( 'jquery', 'thickbox' ),
'20121019'
);
diff --git a/plugins/jetpack/modules/related-posts/class.related-posts-customize.php b/plugins/jetpack/modules/related-posts/class.related-posts-customize.php
index 4b937396..ae1d1d78 100644
--- a/plugins/jetpack/modules/related-posts/class.related-posts-customize.php
+++ b/plugins/jetpack/modules/related-posts/class.related-posts-customize.php
@@ -156,6 +156,15 @@ class Jetpack_Related_Posts_Customize {
restore_previous_locale();
}
+ /**
+ * The filter allows you to change the options used to display Related Posts in the Customizer.
+ *
+ * @module related-posts
+ *
+ * @since 4.4.0
+ *
+ * @param array $options Array of options used to display Related Posts in the Customizer.
+ */
return apply_filters(
'jetpack_related_posts_customize_options', array(
'enabled' => array(
@@ -235,7 +244,15 @@ class Jetpack_Related_Posts_Customize {
* @since 4.4.0
*/
function customize_controls_enqueue_scripts() {
- wp_enqueue_script( 'jetpack_related-posts-customizer', plugins_url( 'related-posts-customizer.js', __FILE__ ), array( 'customize-controls' ), JETPACK__VERSION);
+ wp_enqueue_script(
+ 'jetpack_related-posts-customizer',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/related-posts/related-posts-customizer.min.js',
+ 'modules/related-posts/related-posts-customizer.js'
+ ),
+ array( 'customize-controls' ),
+ JETPACK__VERSION
+ );
}
} // class end
diff --git a/plugins/jetpack/modules/related-posts/jetpack-related-posts.php b/plugins/jetpack/modules/related-posts/jetpack-related-posts.php
index c9255afd..131d6a93 100644
--- a/plugins/jetpack/modules/related-posts/jetpack-related-posts.php
+++ b/plugins/jetpack/modules/related-posts/jetpack-related-posts.php
@@ -1449,7 +1449,15 @@ EOT;
protected function _enqueue_assets( $script, $style ) {
$dependencies = is_customize_preview() ? array( 'customize-base' ) : array( 'jquery' );
if ( $script ) {
- wp_enqueue_script( 'jetpack_related-posts', plugins_url( 'related-posts.js', __FILE__ ), $dependencies, self::VERSION );
+ wp_enqueue_script(
+ 'jetpack_related-posts',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/related-posts/related-posts.min.js',
+ 'modules/related-posts/related-posts.js'
+ ),
+ $dependencies,
+ self::VERSION
+ );
$related_posts_js_options = array(
/**
* Filter each Related Post Heading structure.
diff --git a/plugins/jetpack/modules/search.php b/plugins/jetpack/modules/search.php
index 82a71c4c..585fe0b6 100644
--- a/plugins/jetpack/modules/search.php
+++ b/plugins/jetpack/modules/search.php
@@ -4,6 +4,7 @@
* Module Name: Search
* Module Description: Enhanced search, powered by Elasticsearch
* First Introduced: 5.0
+ * Sort Order: 34
* Free: false
* Requires Connection: Yes
* Auto Activate: No
diff --git a/plugins/jetpack/modules/search/class.jetpack-search-helpers.php b/plugins/jetpack/modules/search/class.jetpack-search-helpers.php
new file mode 100644
index 00000000..e235ba9a
--- /dev/null
+++ b/plugins/jetpack/modules/search/class.jetpack-search-helpers.php
@@ -0,0 +1,699 @@
+<?php
+/**
+ * Jetpack Search: Jetpack_Search_Helpers class
+ *
+ * @package Jetpack
+ * @subpackage Jetpack Search
+ * @since 5.8.0
+ */
+
+/**
+ * Various helper functions for reuse throughout the Jetpack Search code.
+ *
+ * @since 5.8.0
+ */
+class Jetpack_Search_Helpers {
+
+ /**
+ * The search widget's base ID.
+ *
+ * @since 5.8.0
+ * @var string
+ */
+ const FILTER_WIDGET_BASE = 'jetpack-search-filters';
+
+ /**
+ * Create a URL for the current search that doesn't include the "paged" parameter.
+ *
+ * @since 5.8.0
+ *
+ * @return string The search URL.
+ */
+ static function get_search_url() {
+ $query_args = $_GET;
+
+ // Handle the case where a permastruct is being used, such as /search/{$query}
+ if ( ! isset( $query_args['s'] ) ) {
+ $query_args['s'] = get_search_query();
+ }
+
+ if ( isset( $query_args['paged'] ) ) {
+ unset( $query_args['paged'] );
+ }
+
+ $query = http_build_query( $query_args );
+
+ return home_url( "?{$query}" );
+ }
+
+ /**
+ * Wraps add_query_arg() with the URL defaulting to the current search URL.
+ *
+ * @see add_query_arg()
+ *
+ * @since 5.8.0
+ *
+ * @param string|array $key Either a query variable key, or an associative array of query variables.
+ * @param string $value Optional. A query variable value.
+ * @param bool|string $url Optional. A URL to act upon. Defaults to the current search URL.
+ *
+ * @return string New URL query string (unescaped).
+ */
+ static function add_query_arg( $key, $value = false, $url = false ) {
+ $url = empty( $url ) ? self::get_search_url() : $url;
+ if ( is_array( $key ) ) {
+ return add_query_arg( $key, $url );
+ }
+
+ return add_query_arg( $key, $value, $url );
+ }
+
+ /**
+ * Wraps remove_query_arg() with the URL defaulting to the current search URL.
+ *
+ * @see remove_query_arg()
+ *
+ * @since 5.8.0
+ *
+ * @param string|array $key Query key or keys to remove.
+ * @param bool|string $query Optional. A URL to act upon. Defaults to the current search URL.
+ *
+ * @return string New URL query string (unescaped).
+ */
+ static function remove_query_arg( $key, $url = false ) {
+ $url = empty( $url ) ? self::get_search_url() : $url;
+
+ return remove_query_arg( $key, $url );
+ }
+
+ /**
+ * Returns the name of the search widget's option.
+ *
+ * @since 5.8.0
+ *
+ * @return string The search widget option name.
+ */
+ static function get_widget_option_name() {
+ return sprintf( 'widget_%s', self::FILTER_WIDGET_BASE );
+ }
+
+ /**
+ * Returns the search widget instances from the widget's option.
+ *
+ * @since 5.8.0
+ *
+ * @return array The widget options.
+ */
+ static function get_widgets_from_option() {
+ $widget_options = get_option( self::get_widget_option_name(), array() );
+
+ // We don't need this
+ if ( ! empty( $widget_options ) && isset( $widget_options['_multiwidget'] ) ) {
+ unset( $widget_options['_multiwidget'] );
+ }
+
+ return $widget_options;
+ }
+
+ /**
+ * Returns the widget ID (widget base plus the numeric ID).
+ *
+ * @param int $number The widget's numeric ID.
+ *
+ * @return string The widget's numeric ID prefixed with the search widget base.
+ */
+ static function build_widget_id( $number ) {
+ return sprintf( '%s-%d', self::FILTER_WIDGET_BASE, $number );
+ }
+
+ /**
+ * Wrapper for is_active_widget() with the other parameters automatically supplied.
+ *
+ * @see is_active_widget()
+ *
+ * @since 5.8.0
+ *
+ * @param int $widget_id Widget ID.
+ *
+ * @return bool Whether the widget is active or not.
+ */
+ static function is_active_widget( $widget_id ) {
+ return (bool) is_active_widget( false, $widget_id, self::FILTER_WIDGET_BASE, true );
+ }
+
+ /**
+ * Returns an array of the filters from all active search widgets.
+ *
+ * @since 5.8.0
+ *
+ * @return array Active filters.
+ */
+ static function get_filters_from_widgets() {
+ $filters = array();
+
+ $widget_options = self::get_widgets_from_option();
+ if ( empty( $widget_options ) ) {
+ return $filters;
+ }
+
+ foreach ( (array) $widget_options as $number => $settings ) {
+ $widget_id = self::build_widget_id( $number );
+ if ( ! self::is_active_widget( $widget_id ) || empty( $settings['filters'] ) ) {
+ continue;
+ }
+
+ foreach ( (array) $settings['filters'] as $widget_filter ) {
+ $widget_filter['widget_id'] = $widget_id;
+
+ if ( empty( $widget_filter['name'] ) ) {
+ $widget_filter['name'] = self::generate_widget_filter_name( $widget_filter );
+ }
+
+ $key = sprintf( '%s_%d', $widget_filter['type'], count( $filters ) );
+
+ $filters[ $key ] = $widget_filter;
+ }
+ }
+
+ return $filters;
+ }
+
+ /**
+ * Get the localized default label for a date filter.
+ *
+ * @since 5.8.0
+ *
+ * @param string $type Date type, either year or month.
+ * @param bool $is_updated Whether the filter was updated or not (adds "Updated" to the end).
+ *
+ * @return string The filter label.
+ */
+ static function get_date_filter_type_name( $type, $is_updated = false ) {
+ switch ( $type ) {
+ case 'year':
+ $string = ( $is_updated )
+ ? esc_html_x( 'Year Updated', 'label for filtering posts', 'jetpack' )
+ : esc_html_x( 'Year', 'label for filtering posts', 'jetpack' );
+ break;
+ case 'month':
+ default:
+ $string = ( $is_updated )
+ ? esc_html_x( 'Month Updated', 'label for filtering posts', 'jetpack' )
+ : esc_html_x( 'Month', 'label for filtering posts', 'jetpack' );
+ break;
+ }
+
+ return $string;
+ }
+
+ /**
+ * Creates a default name for a filter. Used when the filter label is blank.
+ *
+ * @since 5.8.0
+ *
+ * @param array $widget_filter The filter to generate the title for.
+ *
+ * @return string The suggested filter name.
+ */
+ static function generate_widget_filter_name( $widget_filter ) {
+ $name = '';
+
+ switch ( $widget_filter['type'] ) {
+ case 'post_type':
+ $name = _x( 'Post Types', 'label for filtering posts', 'jetpack' );
+ break;
+
+ case 'date_histogram':
+ $modified_fields = array(
+ 'post_modified',
+ 'post_modified_gmt',
+ );
+ switch ( $widget_filter['interval'] ) {
+ case 'year':
+ $name = self::get_date_filter_type_name(
+ 'year',
+ in_array( $widget_filter['field'], $modified_fields )
+ );
+ break;
+ case 'month':
+ default:
+ $name = self::get_date_filter_type_name(
+ 'month',
+ in_array( $widget_filter['field'], $modified_fields )
+ );
+ break;
+ }
+ break;
+
+ case 'taxonomy':
+ $tax = get_taxonomy( $widget_filter['taxonomy'] );
+ if ( ! $tax ) {
+ break;
+ }
+
+ if ( isset( $tax->label ) ) {
+ $name = $tax->label;
+ } elseif ( isset( $tax->labels ) && isset( $tax->labels->name ) ) {
+ $name = $tax->labels->name;
+ }
+ break;
+ }
+
+ return $name;
+ }
+
+ /**
+ * Whether we should rerun a search in the customizer preview or not.
+ *
+ * @since 5.8.0
+ *
+ * @return bool
+ */
+ static function should_rerun_search_in_customizer_preview() {
+ // Only update when in a customizer preview and data is being posted.
+ // Check for $_POST removes an extra update when the customizer loads.
+ //
+ // Note: We use $GLOBALS['wp_customize'] here instead of is_customize_preview() to support unit tests.
+ if ( ! isset( $GLOBALS['wp_customize'] ) || ! $GLOBALS['wp_customize']->is_preview() || empty( $_POST ) ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Since PHP's built-in array_diff() works by comparing the values that are in array 1 to the other arrays,
+ * if there are less values in array 1, it's possible to get an empty diff where one might be expected.
+ *
+ * @since 5.8.0
+ *
+ * @param array $array_1
+ * @param array $array_2
+ *
+ * @return array
+ */
+ static function array_diff( $array_1, $array_2 ) {
+ // If the array counts are the same, then the order doesn't matter. If the count of
+ // $array_1 is higher than $array_2, that's also fine. If the count of $array_2 is higher,
+ // we need to swap the array order though.
+ if ( count( $array_1 ) !== count( $array_2 ) && count( $array_2 ) > count( $array_1 ) ) {
+ $temp = $array_1;
+ $array_1 = $array_2;
+ $array_2 = $temp;
+ }
+
+ // Disregard keys
+ return array_values( array_diff( $array_1, $array_2 ) );
+ }
+
+ /**
+ * Given the widget instance, will return true when selected post types differ from searchable post types.
+ *
+ * @since 5.8.0
+ *
+ * @param array $post_types An array of post types.
+ *
+ * @return bool
+ */
+ static function post_types_differ_searchable( $post_types ) {
+ if ( empty( $post_types ) ) {
+ return false;
+ }
+
+ $searchable_post_types = get_post_types( array( 'exclude_from_search' => false ) );
+ $diff_of_searchable = self::array_diff( $searchable_post_types, (array) $post_types );
+
+ return ! empty( $diff_of_searchable );
+ }
+
+ /**
+ * Given the array of post types, will return true when these differ from the current search query.
+ *
+ * @since 5.8.0
+ *
+ * @param array $post_types An array of post types.
+ *
+ * @return bool
+ */
+ static function post_types_differ_query( $post_types ) {
+ if ( empty( $post_types ) ) {
+ return false;
+ }
+
+ if ( empty( $_GET['post_type'] ) ) {
+ $post_types_from_query = array();
+ } elseif ( is_array( $_GET['post_type'] ) ) {
+ $post_types_from_query = $_GET['post_type'];
+ } else {
+ $post_types_from_query = (array) explode( ',', $_GET['post_type'] );
+ }
+
+ $post_types_from_query = array_map( 'trim', $post_types_from_query );
+
+ $diff_query = self::array_diff( (array) $post_types, $post_types_from_query );
+
+ return ! empty( $diff_query );
+ }
+
+ /**
+ * Determine what Tracks value should be used when updating a widget.
+ *
+ * @since 5.8.0
+ *
+ * @param mixed $old_value The old option value.
+ * @param mixed $new_value The new option value.
+ *
+ * @return array|false False if the widget wasn't updated, otherwise an array of the Tracks action and widget properties.
+ */
+ static function get_widget_tracks_value( $old_value, $new_value ) {
+ $old_value = (array) $old_value;
+ if ( isset( $old_value['_multiwidget'] ) ) {
+ unset( $old_value['_multiwidget'] );
+ }
+
+ $new_value = (array) $new_value;
+ if ( isset( $new_value['_multiwidget'] ) ) {
+ unset( $new_value['_multiwidget'] );
+ }
+
+ $old_keys = array_keys( $old_value );
+ $new_keys = array_keys( $new_value );
+
+ if ( count( $new_keys ) > count( $old_keys ) ) { // This is the case for a widget being added
+ $diff = self::array_diff( $new_keys, $old_keys );
+ $action = 'widget_added';
+ $widget = empty( $diff ) || ! isset( $new_value[ $diff[0] ] )
+ ? false
+ : $new_value[ $diff[0] ];
+ } elseif ( count( $old_keys ) > count( $new_keys ) ) { // This is the case for a widget being deleted
+ $diff = self::array_diff( $old_keys, $new_keys );
+ $action = 'widget_deleted';
+ $widget = empty( $diff ) || ! isset( $old_value[ $diff[0] ] )
+ ? false
+ : $old_value[ $diff[0] ];
+ } else {
+ $action = 'widget_updated';
+ $widget = false;
+
+ // This is a bit crazy. Since there can be multiple widgets stored in a single option,
+ // we need to diff the old and new values to figure out which widget was updated.
+ foreach ( $new_value as $key => $new_instance ) {
+ if ( ! isset( $old_value[ $key ] ) ) {
+ continue;
+ }
+ $old_instance = $old_value[ $key ];
+
+ // First, let's test the keys of each instance
+ $diff = self::array_diff( array_keys( $new_instance ), array_keys( $old_instance ) );
+ if ( ! empty( $diff ) ) {
+ $widget = $new_instance;
+ break;
+ }
+
+ // Next, lets's loop over each value and compare it
+ foreach ( $new_instance as $k => $v ) {
+ if ( is_scalar( $v ) && (string) $v !== (string) $old_instance[ $k ] ) {
+ $widget = $new_instance;
+ break;
+ }
+
+ if ( 'filters' == $k ) {
+ if ( count( $new_instance['filters'] ) != count( $old_instance['filters'] ) ) {
+ $widget = $new_instance;
+ break;
+ }
+
+ foreach ( $v as $filter_key => $new_filter_value ) {
+ $diff = self::array_diff( $new_filter_value, $old_instance['filters'][ $filter_key ] );
+ if ( ! empty( $diff ) ) {
+ $widget = $new_instance;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( empty( $action ) || empty( $widget ) ) {
+ return false;
+ }
+
+ return array(
+ 'action' => $action,
+ 'widget' => self::get_widget_properties_for_tracks( $widget ),
+ );
+ }
+
+ /**
+ * Creates the widget properties for sending to Tracks.
+ *
+ * @since 5.8.0
+ *
+ * @param array $widget The widget instance.
+ *
+ * @return array The widget properties.
+ */
+ static function get_widget_properties_for_tracks( $widget ) {
+ $sanitized = array();
+
+ foreach ( (array) $widget as $key => $value ) {
+ if ( '_multiwidget' == $key ) {
+ continue;
+ }
+
+ if ( is_scalar( $value ) ) {
+ $key = str_replace( '-', '_', sanitize_key( $key ) );
+ $key = "widget_{$key}";
+ $sanitized[ $key ] = $value;
+ }
+ }
+
+ $filters_properties = ! empty( $widget['filters'] )
+ ? self::get_filter_properties_for_tracks( $widget['filters'] )
+ : array();
+
+ return array_merge( $sanitized, $filters_properties );
+ }
+
+ /**
+ * Creates the filter properties for sending to Tracks.
+ *
+ * @since 5.8.0
+ *
+ * @param array $filters An array of filters.
+ *
+ * @return array The filter properties.
+ */
+ static function get_filter_properties_for_tracks( $filters ) {
+ if ( empty( $filters ) ) {
+ return $filters;
+ }
+
+ $filters_properties = array(
+ 'widget_filter_count' => count( $filters ),
+ );
+
+ foreach ( $filters as $filter ) {
+ if ( empty( $filter['type'] ) ) {
+ continue;
+ }
+
+ $key = sprintf( 'widget_filter_type_%s', $filter['type'] );
+ if ( isset( $filters_properties[ $key ] ) ) {
+ $filters_properties[ $key ] ++;
+ } else {
+ $filters_properties[ $key ] = 1;
+ }
+ }
+
+ return $filters_properties;
+ }
+
+ /**
+ * Gets the active post types given a set of filters.
+ *
+ * @since 5.8.0
+ *
+ * @param array $filters The active filters for the current query.
+ *
+ * @return array The active post types.
+ */
+ public static function get_active_post_types( $filters ) {
+ $active_post_types = array();
+
+ foreach ( $filters as $item ) {
+ if ( ( 'post_type' == $item['type'] ) && isset( $item['query_vars']['post_type'] ) ) {
+ $active_post_types[] = $item['query_vars']['post_type'];
+ }
+ }
+
+ return $active_post_types;
+ }
+
+ /**
+ * Sets active to false on all post type buckets.
+ *
+ * @since 5.8.0
+ *
+ * @param array $filters The available filters for the current query.
+ *
+ * @return array The filters for the current query with modified active field.
+ */
+ public static function remove_active_from_post_type_buckets( $filters ) {
+ $modified = $filters;
+ foreach ( $filters as $key => $filter ) {
+ if ( 'post_type' === $filter['type'] && ! empty( $filter['buckets'] ) ) {
+ foreach ( $filter['buckets'] as $k => $bucket ) {
+ $bucket['active'] = false;
+ $modified[ $key ]['buckets'][ $k ] = $bucket;
+ }
+ }
+ }
+
+ return $modified;
+ }
+
+ /**
+ * Given a url and an array of post types, will ensure that the post types are properly applied to the URL as args.
+ *
+ * @since 5.8.0
+ *
+ * @param string $url The URL to add post types to.
+ * @param array $post_types An array of post types that should be added to the URL.
+ *
+ * @return string The URL with added post types.
+ */
+ public static function add_post_types_to_url( $url, $post_types ) {
+ $url = Jetpack_Search_Helpers::remove_query_arg( 'post_type', $url );
+ if ( empty( $post_types ) ) {
+ return $url;
+ }
+
+ $url = Jetpack_Search_Helpers::add_query_arg(
+ 'post_type',
+ implode( ',', $post_types ),
+ $url
+ );
+
+ return $url;
+ }
+
+ /**
+ * Since we provide support for the widget restricting post types by adding the selected post types as
+ * active filters, if removing a post type filter would result in there no longer be post_type args in the URL,
+ * we need to be sure to add them back.
+ *
+ * @since 5.8.0
+ *
+ * @param array $filters An array of possible filters for the current query.
+ * @param array $post_types The post types to ensure are on the link.
+ *
+ * @return array The updated array of filters with post typed added to the remove URLs.
+ */
+ public static function ensure_post_types_on_remove_url( $filters, $post_types ) {
+ $modified = $filters;
+
+ foreach ( (array) $filters as $filter_key => $filter ) {
+ if ( 'post_type' !== $filter['type'] || empty( $filter['buckets'] ) ) {
+ $modified[ $filter_key ] = $filter;
+ continue;
+ }
+
+ foreach ( (array) $filter['buckets'] as $bucket_key => $bucket ) {
+ if ( empty( $bucket['remove_url'] ) ) {
+ continue;
+ }
+
+ $parsed = wp_parse_url( $bucket['remove_url'] );
+ if ( ! $parsed ) {
+ continue;
+ }
+
+ $query = array();
+ if ( ! empty( $parsed['query'] ) ) {
+ wp_parse_str( $parsed['query'], $query );
+ }
+
+ if ( empty( $query['post_type'] ) ) {
+ $modified[ $filter_key ]['buckets'][ $bucket_key ]['remove_url'] = self::add_post_types_to_url(
+ $bucket['remove_url'],
+ $post_types
+ );
+ }
+ }
+ }
+
+ return $modified;
+ }
+
+ /**
+ * Wraps a WordPress filter called "jetpack_search_disable_widget_filters" that allows
+ * developers to disable filters supplied by the search widget. Useful if filters are
+ * being defined at the code level.
+ *
+ * @since 5.8.0
+ *
+ * @return bool
+ */
+ public static function are_filters_by_widget_disabled() {
+ /**
+ * Allows developers to disable filters being set by widget, in favor of manually
+ * setting filters via `Jetpack_Search::set_filters()`.
+ *
+ * @module search
+ *
+ * @since 5.7.0
+ *
+ * @param bool false
+ */
+ return apply_filters( 'jetpack_search_disable_widget_filters', false );
+ }
+
+ /**
+ * Returns a boolean for whether the current site has a VIP index.
+ *
+ * @since 5.8.0
+ *
+ * @return bool
+ */
+ public static function site_has_vip_index() {
+ $has_vip_index = (
+ Jetpack_Constants::is_defined( 'JETPACK_SEARCH_VIP_INDEX' ) &&
+ Jetpack_Constants::get_constant( 'JETPACK_SEARCH_VIP_INDEX' )
+ );
+
+ /**
+ * Allows developers to filter whether the current site has a VIP index.
+ *
+ * @module search
+ *
+ * @since 5.8.0
+ *
+ * @param bool $has_vip_index Whether the current site has a VIP index.
+ */
+ return apply_filters( 'jetpack_search_has_vip_index', $has_vip_index );
+ }
+
+ /**
+ * Returns the maximum posts per page for a search query.
+ *
+ * @since 5.8.0
+ *
+ * @return int
+ */
+ public static function get_max_posts_per_page() {
+ return self::site_has_vip_index() ? 1000 : 100;
+ }
+
+ /**
+ * Returns the maximum offset for a search query.
+ *
+ * @since 5.8.0
+ *
+ * @return int
+ */
+ public static function get_max_offset() {
+ return self::site_has_vip_index() ? 9000 : 1000;
+ }
+}
diff --git a/plugins/jetpack/modules/search/class.jetpack-search-template-tags.php b/plugins/jetpack/modules/search/class.jetpack-search-template-tags.php
new file mode 100644
index 00000000..303e6caa
--- /dev/null
+++ b/plugins/jetpack/modules/search/class.jetpack-search-template-tags.php
@@ -0,0 +1,225 @@
+<?php
+/**
+ * Jetpack Search: Jetpack_Search_Template_Tags class
+ *
+ * @package Jetpack
+ * @subpackage Jetpack Search
+ * @since 5.8.0
+ */
+
+/**
+ * Class that has various methods for outputting functionality into a theme that doesn't support widgets.
+ * Additionally the widget itself makes use of these class.
+ *
+ * @since 5.8.0
+ */
+class Jetpack_Search_Template_Tags {
+
+ /**
+ * Renders all available filters that can be used to filter down search results on the frontend.
+ *
+ * @since 5.8.0
+ *
+ * @param array $filters The available filters for the current query.
+ * @param array $post_types An array of post types to make filterable
+ */
+ public static function render_available_filters( $filters = null, $post_types = null ) {
+ if ( is_null( $filters ) ) {
+ $filters = Jetpack_Search::instance()->get_filters();
+ }
+
+ if ( is_null( $post_types ) ) {
+ $post_types = get_post_types( array( 'exclude_from_search' => false ) );
+ }
+
+ /**
+ * If the post types specified by the widget differ from the default set of searchable post types,
+ * then we need to track their state.
+ */
+ $active_post_types = array();
+ if ( Jetpack_Search_Helpers::post_types_differ_searchable( $post_types ) ) {
+ // get the active filter buckets from the query
+ $active_buckets = Jetpack_Search::instance()->get_active_filter_buckets();
+ $post_types_differ_query = Jetpack_Search_Helpers::post_types_differ_query( $post_types );
+
+ // remove any post_type filters from display if the current query
+ // already specifies to match all post types
+ if ( ! $post_types_differ_query ) {
+ $active_buckets = array_filter( $active_buckets, array( __CLASS__, 'is_not_post_type_filter' ) );
+ }
+
+ $active_post_types = Jetpack_Search_Helpers::get_active_post_types( $active_buckets );
+ if ( empty( $active_post_types ) ) {
+ $active_post_types = $post_types;
+ }
+
+ if ( $post_types_differ_query ) {
+ $filters = Jetpack_Search_Helpers::ensure_post_types_on_remove_url( $filters, $post_types );
+ } else {
+ $filters = Jetpack_Search_Helpers::remove_active_from_post_type_buckets( $filters );
+ }
+ } else {
+ $post_types = array();
+ }
+
+ foreach ( (array) $filters as $filter ) {
+ if ( 'post_type' == $filter['type'] ) {
+ self::render_filter( $filter, $post_types );
+ } else {
+ self::render_filter( $filter, $active_post_types );
+ }
+ }
+ }
+
+ /**
+ * Renders a single filter that can be applied to the current search.
+ *
+ * @since 5.8.0
+ *
+ * @param array $filter The filter to render.
+ * @param array $default_post_types The default post types for this filter.
+ */
+ public static function render_filter( $filter, $default_post_types ) {
+ if ( empty( $filter ) || empty( $filter['buckets'] ) ) {
+ return;
+ }
+
+ $query_vars = null;
+ foreach ( $filter['buckets'] as $item ) {
+ if ( $item['active'] ) {
+ $query_vars = array_keys( $item['query_vars'] );
+ break;
+ }
+ }
+ $clear_url = null;
+ if ( ! empty( $query_vars ) ) {
+ $clear_url = Jetpack_Search_Helpers::remove_query_arg( $query_vars );
+ if ( ! empty( $default_post_types ) ) {
+ $clear_url = Jetpack_Search_Helpers::add_post_types_to_url( $clear_url, $default_post_types );
+ }
+ }
+
+ ?>
+ <h4 class="jetpack-search-filters-widget__sub-heading">
+ <?php echo esc_html( $filter['name'] ); ?>
+ </h4>
+ <?php if ( $clear_url ) : ?>
+ <div class="jetpack-search-filters-widget__clear">
+ <a href="<?php echo esc_url( $clear_url ); ?>">
+ <?php esc_html_e( '< Clear Filters', 'jetpack' ); ?>
+ </a>
+ </div>
+ <?php endif; ?>
+ <ul class="jetpack-search-filters-widget__filter-list">
+ <?php
+ foreach ( $filter['buckets'] as $item ) :
+ $url = ( empty( $item['active'] ) ) ? $item['url'] : $item['remove_url'];
+ ?>
+ <li>
+ <label>
+ <input type="checkbox"<?php checked( ! empty( $item['active'] ) ); ?> disabled="disabled" />&nbsp;
+ <a href="<?php echo esc_url( $url ); ?>">
+ <?php
+ echo esc_html( $item['name'] );
+ echo '&nbsp;';
+ echo esc_html( sprintf(
+ '(%s)',
+ number_format_i18n( absint( $item['count'] ) )
+ ) );
+ ?>
+ </a>
+ </label>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+ <?php
+ }
+
+ /**
+ * Outputs the search widget's title.
+ *
+ * @since 5.8.0
+ *
+ * @param string $title The widget's title
+ * @param string $before_title The HTML tag to display before the title
+ * @param string $after_title The HTML tag to display after the title
+ */
+ public static function render_widget_title( $title, $before_title, $after_title ) {
+ echo $before_title . esc_html( $title ) . $after_title;
+ }
+
+ /**
+ * Responsible for rendering the search box within our widget on the frontend.
+ *
+ * @since 5.8.0
+ *
+ * @param array $post_types Array of post types to limit search results to.
+ * @param string $orderby How to order the search results.
+ * @param string $order In what direction to order the search results.
+ */
+ public static function render_widget_search_form( $post_types, $orderby, $order ) {
+ $form = get_search_form( false );
+
+ $fields_to_inject = array(
+ 'orderby' => $orderby,
+ 'order' => $order
+ );
+
+ // If the widget has specified post types to search within and IF the post types differ
+ // from the default post types that would have been searched, set the selected post
+ // types via hidden inputs.
+ if ( Jetpack_Search_Helpers::post_types_differ_searchable( $post_types ) ) {
+ $fields_to_inject['post_type'] = implode( ',', $post_types );
+ }
+
+ $form = self::inject_hidden_form_fields( $form, $fields_to_inject );
+
+ echo '<div class="jetpack-search-form">';
+ echo $form;
+ echo '</div>';
+ }
+
+ /**
+ * Modifies an HTML form to add some additional hidden fields.
+ *
+ * @since 5.8.0
+ *
+ * @param string $form The form HTML to modify.
+ * @param array $fields Array of hidden fields to add. Key is field name and value is the field value.
+ *
+ * @return string The modified form HTML.
+ */
+ private static function inject_hidden_form_fields( $form, $fields ) {
+ $form_injection = '';
+
+ foreach ( $fields as $field_name => $field_value ) {
+ $form_injection .= sprintf(
+ '<input type="hidden" name="%s" value="%s" />',
+ esc_attr( $field_name ),
+ esc_attr( $field_value )
+ );
+ }
+
+ // This shouldn't need to be escaped since we've escaped above as we built $form_injection
+ $form = str_replace(
+ '</form>',
+ $form_injection . '</form>',
+ $form
+ );
+
+ return $form;
+ }
+
+ /**
+ * Internal method for filtering out non-post_type filters.
+ *
+ * @since 5.8.0
+ *
+ * @param array $filter
+ *
+ * @return bool
+ */
+ private static function is_not_post_type_filter( $filter ) {
+ return 'post_type' !== $filter['type'];
+ }
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/search/class.jetpack-search-widget-filters.php b/plugins/jetpack/modules/search/class.jetpack-search-widget-filters.php
deleted file mode 100644
index 7de39e10..00000000
--- a/plugins/jetpack/modules/search/class.jetpack-search-widget-filters.php
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-
-
-/**
- * Provides a widget to show available/selected filters on searches
- */
-class Jetpack_Search_Widget_Filters extends WP_Widget {
-
- function __construct() {
- if ( ! class_exists( 'Jetpack_Search' ) ) {
- return;
- }
-
- parent::__construct(
- 'jetpack-search-filters',
- /** This filter is documented in modules/widgets/facebook-likebox.php */
- apply_filters( 'jetpack_widget_name', esc_html__( 'Search Filters', 'jetpack' ) ),
- array(
- 'classname' => 'jetpack-filters',
- 'description' => __( 'Displays search result filters when viewing search results.', 'jetpack' ),
- )
- );
- }
-
- function widget( $args, $instance ) {
- if ( ! class_exists( 'Jetpack_Search' ) || ! is_search() ) {
- return;
- }
-
- $search = Jetpack_Search::instance();
-
- $filters = $search->get_filters();
-
- $active_buckets = $search->get_active_filter_buckets();
-
- if ( empty( $filters ) && empty( $active_buckets ) ) {
- return;
- }
-
- $buckets_found = false;
-
- foreach ( $filters as $filter ) {
- if ( isset( $filter['buckets'] ) && count( $filter['buckets'] ) > 1 ) {
- $buckets_found = true;
-
- break;
- }
- }
-
- if ( ! $buckets_found && empty( $active_buckets ) ) {
- return;
- }
-
- $title = $instance['title'];
-
- if ( empty( $title ) ) {
- $title = __( 'Filter By', 'jetpack' );
- }
-
- /** This filter is documented in core/src/wp-includes/default-widgets.php */
- $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
-
- echo $args['before_widget'];
-
- echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
-
- if ( ! empty( $active_buckets ) ) {
- echo '<h3>' . esc_html__( 'Current Filters', 'jetpack' ) . '</h3>';
-
- echo '<ul>';
-
- foreach ( $active_buckets as $item ) {
- echo '<li><a href="' . esc_url( $item['remove_url'] ) . '">' . sprintf( _x( '(X) %1$s: %2$s', 'aggregation widget: active filter type and name', 'jetpack' ), esc_html( $item['type_label'] ), esc_html( $item['name'] ) ) . '</a></li>';
- }
-
- if ( count( $active_buckets ) > 1 ) {
- echo '<li><a href="' . esc_url( add_query_arg( 's', get_query_var( 's' ), home_url() ) ) . '">' . esc_html__( 'Remove All Filters', 'jetpack' ) . '</a></li>';
- }
-
- echo '</ul>';
- }
-
- foreach ( $filters as $label => $filter ) {
- if ( count( $filter['buckets'] ) < 2 ) {
- continue;
- }
-
- echo '<h3>' . esc_html( $label ) . '</h3>';
-
- echo '<ul>';
-
- foreach ( $filter['buckets'] as $item ) {
- if ( $item['active'] ) {
- continue;
- }
-
- echo '<li><a href="' . esc_url( $item['url'] ) . '">' . esc_html( $item['name'] ) . '</a> (' . number_format_i18n( absint( $item['count'] ) ) . ')</li>';
- }
-
- echo '</ul>';
- }
-
- echo $args['after_widget'];
- }
-
- function update( $new_instance, $old_instance ) {
- $instance = array();
-
- $instance['title'] = sanitize_text_field( $new_instance['title'] );
-
- return $instance;
- }
-
- function form( $instance ) {
- $instance = wp_parse_args( (array) $instance, array(
- 'title' => '',
- ) );
-
- $title = strip_tags( $instance['title'] );
-
- ?>
- <p><label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
- <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" /></p>
- <?php
- }
-}
diff --git a/plugins/jetpack/modules/search/class.jetpack-search.php b/plugins/jetpack/modules/search/class.jetpack-search.php
index 9733d2a2..19b55e3a 100644
--- a/plugins/jetpack/modules/search/class.jetpack-search.php
+++ b/plugins/jetpack/modules/search/class.jetpack-search.php
@@ -1,54 +1,136 @@
<?php
-
+/**
+ * Jetpack Search: Main Jetpack_Search class
+ *
+ * @package Jetpack
+ * @subpackage Jetpack Search
+ * @since 5.0.0
+ */
+
+/**
+ * The main class for the Jetpack Search module.
+ *
+ * @since 5.0.0
+ */
class Jetpack_Search {
+ /**
+ * The number of found posts.
+ *
+ * @since 5.0.0
+ *
+ * @var int
+ */
protected $found_posts = 0;
/**
- * The maximum offset ('from' param), since deep pages get exponentially slower.
+ * The search result, as returned by the WordPress.com REST API.
*
- * @see https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html
+ * @since 5.0.0
+ *
+ * @var array
*/
- protected $max_offset = 200;
-
protected $search_result;
- protected $original_blog_id;
+ /**
+ * This site's blog ID on WordPress.com.
+ *
+ * @since 5.0.0
+ *
+ * @var int
+ */
protected $jetpack_blog_id;
+ /**
+ * The Elasticsearch aggregations (filters).
+ *
+ * @since 5.0.0
+ *
+ * @var array
+ */
protected $aggregations = array();
+
+ /**
+ * The maximum number of aggregations allowed.
+ *
+ * @since 5.0.0
+ *
+ * @var int
+ */
protected $max_aggregations_count = 100;
- // used to output query meta into page
- protected $last_query_info;
- protected $last_query_failure_info;
+ /**
+ * Statistics about the last Elasticsearch query.
+ *
+ * @since 5.6.0
+ *
+ * @var array
+ */
+ protected $last_query_info = array();
+ /**
+ * Statistics about the last Elasticsearch query failure.
+ *
+ * @since 5.6.0
+ *
+ * @var array
+ */
+ protected $last_query_failure_info = array();
+
+ /**
+ * The singleton instance of this class.
+ *
+ * @since 5.0.0
+ *
+ * @var Jetpack_Search
+ */
protected static $instance;
- //Languages with custom analyzers, other languages are supported,
- // but are analyzed with the default analyzer.
+ /**
+ * Languages with custom analyzers. Other languages are supported, but are analyzed with the default analyzer.
+ *
+ * @since 5.0.0
+ *
+ * @var array
+ */
public static $analyzed_langs = array( 'ar', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'eu', 'fa', 'fi', 'fr', 'he', 'hi', 'hu', 'hy', 'id', 'it', 'ja', 'ko', 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' );
+ /**
+ * Jetpack_Search constructor.
+ *
+ * @since 5.0.0
+ *
+ * Doesn't do anything. This class needs to be initialized via the instance() method instead.
+ */
protected function __construct() {
- /* Don't do anything, needs to be initialized via instance() method */
}
+ /**
+ * Prevent __clone()'ing of this class.
+ *
+ * @since 5.0.0
+ */
public function __clone() {
wp_die( "Please don't __clone Jetpack_Search" );
}
+ /**
+ * Prevent __wakeup()'ing of this class.
+ *
+ * @since 5.0.0
+ */
public function __wakeup() {
wp_die( "Please don't __wakeup Jetpack_Search" );
}
/**
- * Get singleton instance of Jetpack_Search
+ * Get singleton instance of Jetpack_Search.
*
- * Instantiates and sets up a new instance if needed, or returns the singleton
+ * Instantiates and sets up a new instance if needed, or returns the singleton.
*
- * @module search
+ * @since 5.0.0
*
- * @return Jetpack_Search The Jetpack_Search singleton
+ * @return Jetpack_Search The Jetpack_Search singleton.
*/
public static function instance() {
if ( ! isset( self::$instance ) ) {
@@ -61,11 +143,11 @@ class Jetpack_Search {
}
/**
- * Perform various setup tasks for the class
+ * Perform various setup tasks for the class.
*
- * Checks various pre-requisites and adds hooks
+ * Checks various pre-requisites and adds hooks.
*
- * @module search
+ * @since 5.0.0
*/
public function setup() {
if ( ! Jetpack::is_active() || ! Jetpack::active_plan_supports( 'search' ) ) {
@@ -78,61 +160,170 @@ class Jetpack_Search {
return;
}
+ require_once( dirname( __FILE__ ) . '/class.jetpack-search-helpers.php' );
+ require_once( dirname( __FILE__ ) . '/class.jetpack-search-template-tags.php' );
+
$this->init_hooks();
}
/**
- * Setup the various hooks needed for the plugin to take over Search duties
+ * Setup the various hooks needed for the plugin to take over search duties.
*
- * @module search
+ * @since 5.0.0
*/
public function init_hooks() {
- add_action( 'widgets_init', array( $this, 'action__widgets_init' ) );
-
if ( ! is_admin() ) {
add_filter( 'posts_pre_query', array( $this, 'filter__posts_pre_query' ), 10, 2 );
- add_filter( 'jetpack_search_es_wp_query_args', array( $this, 'filter__add_date_filter_to_query' ), 10, 2 );
+ add_filter( 'jetpack_search_es_wp_query_args', array( $this, 'filter__add_date_filter_to_query' ), 10, 2 );
- add_action( 'did_jetpack_search_query', array( $this, 'store_query_success' ) );
+ add_action( 'did_jetpack_search_query', array( $this, 'store_last_query_info' ) );
add_action( 'failed_jetpack_search_query', array( $this, 'store_query_failure' ) );
+
+ add_action( 'init', array( $this, 'set_filters_from_widgets' ) );
+
+ add_action( 'pre_get_posts', array( $this, 'maybe_add_post_type_as_var' ) );
+ } else {
+ add_action( 'update_option', array( $this, 'track_widget_updates' ), 10, 3 );
}
+
+ add_action( 'jetpack_deactivate_module_search', array( $this, 'move_search_widgets_to_inactive' ) );
}
/**
- * Print query info as a HTML comment in the footer
+ * When an Elasticsearch query fails, this stores it and enqueues some debug information in the footer.
+ *
+ * @since 5.6.0
+ *
+ * @param array $meta Information about the failure.
*/
-
public function store_query_failure( $meta ) {
$this->last_query_failure_info = $meta;
add_action( 'wp_footer', array( $this, 'print_query_failure' ) );
}
+ /**
+ * Outputs information about the last Elasticsearch failure.
+ *
+ * @since 5.6.0
+ */
public function print_query_failure() {
if ( $this->last_query_failure_info ) {
- echo '<!-- Jetpack Search failed with code ' . $this->last_query_failure_info['response_code'] . ': ' . $this->last_query_failure_info['json']['error'] . ' - ' . $this->last_query_failure_info['json']['message'] . ' -->';
+ printf(
+ '<!-- Jetpack Search failed with code %s: %s - %s -->',
+ esc_html( $this->last_query_failure_info['response_code'] ),
+ esc_html( $this->last_query_failure_info['json']['error'] ),
+ esc_html( $this->last_query_failure_info['json']['message'] )
+ );
}
}
- public function store_query_success( $meta ) {
+ /**
+ * Stores information about the last Elasticsearch query and enqueues some debug information in the footer.
+ *
+ * @since 5.6.0
+ *
+ * @param array $meta Information about the query.
+ */
+ public function store_last_query_info( $meta ) {
$this->last_query_info = $meta;
add_action( 'wp_footer', array( $this, 'print_query_success' ) );
}
+ /**
+ * Outputs information about the last Elasticsearch search.
+ *
+ * @since 5.6.0
+ */
public function print_query_success() {
if ( $this->last_query_info ) {
- echo '<!-- Jetpack Search took ' . intval( $this->last_query_info['elapsed_time'] ) . ' ms, ES time ' . $this->last_query_info['es_time'] . ' ms -->';
+ printf(
+ '<!-- Jetpack Search took %s ms, ES time %s ms -->',
+ intval( $this->last_query_info['elapsed_time'] ),
+ esc_html( $this->last_query_info['es_time'] )
+ );
+ }
+ }
+
+ /**
+ * Returns the last query information, or false if no information was stored.
+ *
+ * @since 5.8.0
+ *
+ * @return bool|array
+ */
+ public function get_last_query_info() {
+ return empty( $this->last_query_info ) ? false : $this->last_query_info;
+ }
+
+ /**
+ * Returns the last query failure information, or false if no failure information was stored.
+ *
+ * @since 5.8.0
+ *
+ * @return bool|array
+ */
+ public function get_last_query_failure_info() {
+ return empty( $this->last_query_failure_info ) ? false : $this->last_query_failure_info;
+ }
+
+ /**
+ * Wraps a WordPress filter called "jetpack_search_disable_widget_filters" that allows
+ * developers to disable filters supplied by the search widget. Useful if filters are
+ * being defined at the code level.
+ *
+ * @since 5.7.0
+ * @deprecated 5.8.0 Use Jetpack_Search_Helpers::are_filters_by_widget_disabled() directly.
+ *
+ * @return bool
+ */
+ public function are_filters_by_widget_disabled() {
+ return Jetpack_Search_Helpers::are_filters_by_widget_disabled();
+ }
+
+ /**
+ * Retrieves a list of known Jetpack search filters widget IDs, gets the filters for each widget,
+ * and applies those filters to this Jetpack_Search object.
+ *
+ * @since 5.7.0
+ */
+ public function set_filters_from_widgets() {
+ if ( Jetpack_Search_Helpers::are_filters_by_widget_disabled() ) {
+ return;
+ }
+
+ $filters = Jetpack_Search_Helpers::get_filters_from_widgets();
+
+ if ( ! empty( $filters ) ) {
+ $this->set_filters( $filters );
+ }
+ }
+
+ /**
+ * Restricts search results to certain post types via a GET argument.
+ *
+ * @since 5.8.0
+ *
+ * @param WP_Query $query A WP_Query instance.
+ */
+ public function maybe_add_post_type_as_var( WP_Query $query ) {
+ if ( $query->is_main_query() && $query->is_search && ! empty( $_GET['post_type'] ) ) {
+ $post_types = ( is_string( $_GET['post_type'] ) && false !== strpos( $_GET['post_type'], ',' ) )
+ ? $post_type = explode( ',', $_GET['post_type'] )
+ : (array) $_GET['post_type'];
+ $post_types = array_map( 'sanitize_key', $post_types );
+ $query->set( 'post_type', $post_types );
}
}
/*
- * Run a search on the WP.com public API.
+ * Run a search on the WordPress.com public API.
*
- * @module search
+ * @since 5.0.0
*
- * @param array $es_args Args conforming to the WP.com /sites/<blog_id>/search endpoint
+ * @param array $es_args Args conforming to the WP.com /sites/<blog_id>/search endpoint.
*
- * @return object|WP_Error The response from the public api, or a WP_Error
+ * @return object|WP_Error The response from the public API, or a WP_Error.
*/
public function search( array $es_args ) {
$endpoint = sprintf( '/sites/%s/search', $this->jetpack_blog_id );
@@ -141,22 +332,22 @@ class Jetpack_Search {
$do_authenticated_request = false;
if ( class_exists( 'Jetpack_Client' ) &&
- isset( $es_args['authenticated_request'] ) &&
- true === $es_args['authenticated_request'] ) {
+ isset( $es_args['authenticated_request'] ) &&
+ true === $es_args['authenticated_request'] ) {
$do_authenticated_request = true;
}
unset( $es_args['authenticated_request'] );
$request_args = array(
- 'headers' => array(
+ 'headers' => array(
'Content-Type' => 'application/json',
),
'timeout' => 10,
'user-agent' => 'jetpack_search',
);
- $request_body = json_encode( $es_args );
+ $request_body = wp_json_encode( $es_args );
$start_time = microtime( true );
@@ -179,35 +370,23 @@ class Jetpack_Search {
}
$response_code = wp_remote_retrieve_response_code( $request );
- $response = json_decode( wp_remote_retrieve_body( $request ), true );
+ $response = json_decode( wp_remote_retrieve_body( $request ), true );
- if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
- /**
- * Fires after a search query request has failed
- *
- * @module search
- *
- * @since 5.6.0
- *
- * @param array Array containing the response code and response from the failed search query
- */
- do_action( 'failed_jetpack_search_query', array( 'response_code' => $response_code, 'json' => $response ) );
- return new WP_Error( 'invalid_search_api_response', 'Invalid response from API - ' . $response_code );
- }
-
- $took = is_array( $response ) && $response['took'] ? $response['took'] : null;
+ $took = is_array( $response ) && ! empty( $response['took'] )
+ ? $response['took']
+ : null;
$query = array(
'args' => $es_args,
'response' => $response,
'response_code' => $response_code,
- 'elapsed_time' => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms
+ 'elapsed_time' => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms.
'es_time' => $took,
'url' => $service_url,
);
/**
- * Fires after a search request has been performed
+ * Fires after a search request has been performed.
*
* Includes the following info in the $query parameter:
*
@@ -220,36 +399,56 @@ class Jetpack_Search {
*
* @module search
*
- * @since 5.0.0
+ * @since 5.0.0
+ * @since 5.8.0 This action now fires on all queries instead of just successful queries.
*
* @param array $query Array of information about the query performed
*/
do_action( 'did_jetpack_search_query', $query );
+ if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
+ /**
+ * Fires after a search query request has failed
+ *
+ * @module search
+ *
+ * @since 5.6.0
+ *
+ * @param array Array containing the response code and response from the failed search query
+ */
+ do_action( 'failed_jetpack_search_query', array(
+ 'response_code' => $response_code,
+ 'json' => $response,
+ ) );
+
+ return new WP_Error( 'invalid_search_api_response', 'Invalid response from API - ' . $response_code );
+ }
+
return $response;
}
/**
- * Bypass the normal Search query and offload it to Jetpack servers
+ * Bypass the normal Search query and offload it to Jetpack servers.
*
- * This is the main hook of the plugin and is responsible for returning the posts that match the search query
+ * This is the main hook of the plugin and is responsible for returning the posts that match the search query.
*
- * @module search
+ * @since 5.0.0
*
- * @param array $posts Current array of posts (still pre-query)
- * @param WP_Query $query The WP_Query being filtered
+ * @param array $posts Current array of posts (still pre-query).
+ * @param WP_Query $query The WP_Query being filtered.
*
- * @return array Array of matching posts
+ * @return array Array of matching posts.
*/
public function filter__posts_pre_query( $posts, $query ) {
/**
- * Determine whether a given WP_Query should be handled by ElasticSearch
+ * Determine whether a given WP_Query should be handled by ElasticSearch.
*
* @module search
*
- * @since 5.6.0
- * @param bool $should_handle Should be handled by Jetpack Search
- * @param WP_Query $query The wp_query object
+ * @since 5.6.0
+ *
+ * @param bool $should_handle Should be handled by Jetpack Search.
+ * @param WP_Query $query The WP_Query object.
*/
if ( ! apply_filters( 'jetpack_search_should_handle_query', ( $query->is_main_query() && $query->is_search() ), $query ) ) {
return $posts;
@@ -274,15 +473,17 @@ class Jetpack_Search {
// Query all posts now
$args = array(
- 'post__in' => $post_ids,
- 'perm' => 'readable',
- 'post_type' => 'any',
+ 'post__in' => $post_ids,
+ 'orderby' => 'post__in',
+ 'perm' => 'readable',
+ 'post_type' => 'any',
+ 'ignore_sticky_posts' => true,
+ 'suppress_filters' => true,
);
$posts_query = new WP_Query( $args );
- // WP Core doesn't call the set_found_posts and its filters when filtering posts_pre_query like we do, so need to
- // do these manually
+ // WP Core doesn't call the set_found_posts and its filters when filtering posts_pre_query like we do, so need to do these manually.
$query->found_posts = $this->found_posts;
$query->max_num_pages = ceil( $this->found_posts / $query->get( 'posts_per_page' ) );
@@ -290,22 +491,26 @@ class Jetpack_Search {
}
/**
- * Build up the search, then run it against the Jetpack servers
+ * Build up the search, then run it against the Jetpack servers.
+ *
+ * @since 5.0.0
*
- * @param WP_Query $query The original WP_Query to use for the parameters of our search
+ * @param WP_Query $query The original WP_Query to use for the parameters of our search.
*/
public function do_search( WP_Query $query ) {
$page = ( $query->get( 'paged' ) ) ? absint( $query->get( 'paged' ) ) : 1;
- $posts_per_page = $query->get( 'posts_per_page' );
+ // Get maximum allowed offset and posts per page values for the API.
+ $max_offset = Jetpack_Search_Helpers::get_max_offset();
+ $max_posts_per_page = Jetpack_Search_Helpers::get_max_posts_per_page();
- // ES API does not allow more than 15 results at a time
- if ( $posts_per_page > 15 ) {
- $posts_per_page = 15;
+ $posts_per_page = $query->get( 'posts_per_page' );
+ if ( $posts_per_page > $max_posts_per_page ) {
+ $posts_per_page = $max_posts_per_page;
}
- // Start building the WP-style search query args
- // They'll be translated to ES format args later
+ // Start building the WP-style search query args.
+ // They'll be translated to ES format args later.
$es_wp_query_args = array(
'query' => $query->get( 's' ),
'posts_per_page' => $posts_per_page,
@@ -324,10 +529,8 @@ class Jetpack_Search {
}
$es_wp_query_args['post_type'] = $this->get_es_wp_query_post_type_for_query( $query );
-
$es_wp_query_args['terms'] = $this->get_es_wp_query_terms_for_query( $query );
-
/**
* Modify the search query parameters, such as controlling the post_type.
*
@@ -335,30 +538,30 @@ class Jetpack_Search {
*
* @module search
*
- * @since 5.0.0
+ * @since 5.0.0
*
- * @param array $es_wp_query_args The current query args, in WP_Query format
- * @param WP_Query $query The original query object
+ * @param array $es_wp_query_args The current query args, in WP_Query format.
+ * @param WP_Query $query The original WP_Query object.
*/
$es_wp_query_args = apply_filters( 'jetpack_search_es_wp_query_args', $es_wp_query_args, $query );
// If page * posts_per_page is greater than our max offset, send a 404. This is necessary because the offset is
- // capped at $this->max_offset, so a high page would always return the last page of results otherwise
- if ( ( $es_wp_query_args['paged'] * $es_wp_query_args['posts_per_page'] ) > $this->max_offset ) {
+ // capped at Jetpack_Search_Helpers::get_max_offset(), so a high page would always return the last page of results otherwise.
+ if ( ( $es_wp_query_args['paged'] * $es_wp_query_args['posts_per_page'] ) > $max_offset ) {
$query->set_404();
return;
}
// If there were no post types returned, then 404 to avoid querying against non-public post types, which could
- // happen if we don't add the post type restriction to the ES query
+ // happen if we don't add the post type restriction to the ES query.
if ( empty( $es_wp_query_args['post_type'] ) ) {
$query->set_404();
return;
}
- // Convert the WP-style args into ES args
+ // Convert the WP-style args into ES args.
$es_query_args = $this->convert_wp_es_to_es_args( $es_wp_query_args );
//Only trust ES to give us IDs, not the content since it is a mirror
@@ -373,10 +576,10 @@ class Jetpack_Search {
*
* @module search
*
- * @since 5.0.0
+ * @since 5.0.0
*
- * @param array $es_query_args The raw ES query args
- * @param WP_Query $query The original query object
+ * @param array $es_query_args The raw Elasticsearch query args.
+ * @param WP_Query $query The original WP_Query object.
*/
$es_query_args = apply_filters( 'jetpack_search_es_query_args', $es_query_args, $query );
@@ -389,27 +592,48 @@ class Jetpack_Search {
return;
}
- // If we have aggregations, fix the ordering to match the input order (ES doesn't
- // guarantee the return order)
+ // If we have aggregations, fix the ordering to match the input order (ES doesn't guarantee the return order).
if ( isset( $this->search_result['results']['aggregations'] ) && ! empty( $this->search_result['results']['aggregations'] ) ) {
$this->search_result['results']['aggregations'] = $this->fix_aggregation_ordering( $this->search_result['results']['aggregations'], $this->aggregations );
}
- // Total number of results for paging purposes. Capped at $this->>max_offset + $posts_per_page, as deep paging
- // gets quite expensive
- $this->found_posts = min( $this->search_result['results']['total'], $this->max_offset + $posts_per_page );
+ // Total number of results for paging purposes. Capped at $max_offset + $posts_per_page, as deep paging gets quite expensive.
+ $this->found_posts = min( $this->search_result['results']['total'], $max_offset + $posts_per_page );
+ }
+
+ /**
+ * If the query has already been run before filters have been updated, then we need to re-run the query
+ * to get the latest aggregations.
+ *
+ * This is especially useful for supporting widget management in the customizer.
+ *
+ * @since 5.8.0
+ *
+ * @return bool Whether the query was successful or not.
+ */
+ public function update_search_results_aggregations() {
+ if ( empty( $this->last_query_info ) || empty( $this->last_query_info['args'] ) ) {
+ return false;
+ }
- return;
+ $es_args = $this->last_query_info['args'];
+ $builder = new Jetpack_WPES_Query_Builder();
+ $this->add_aggregations_to_es_query_builder( $this->aggregations, $builder );
+ $es_args['aggregations'] = $builder->build_aggregation();
+
+ $this->search_result = $this->search( $es_args );
+
+ return ! is_wp_error( $this->search_result );
}
/**
- * Given a WP_Query, convert its WP_Tax_Query (if present) into the WP-style ES term arguments for the search
+ * Given a WP_Query, convert its WP_Tax_Query (if present) into the WP-style Elasticsearch term arguments for the search.
*
- * @module search
+ * @since 5.0.0
*
- * @param WP_Query $query The original WP_Query object for which to parse the taxonomy query
+ * @param WP_Query $query The original WP_Query object for which to parse the taxonomy query.
*
- * @return array The new WP-style ES arguments (that will be converted into 'real' ES arguments)
+ * @return array The new WP-style Elasticsearch arguments (that will be converted into 'real' Elasticsearch arguments).
*/
public function get_es_wp_query_terms_for_query( WP_Query $query ) {
$args = array();
@@ -446,20 +670,20 @@ class Jetpack_Search {
}
/**
- * Parse out the post type from a WP_Query
+ * Parse out the post type from a WP_Query.
*
- * Only allows post types that are not marked as 'exclude_from_search'
+ * Only allows post types that are not marked as 'exclude_from_search'.
*
- * @module search
+ * @since 5.0.0
*
- * @param WP_Query $query Original WP_Query object
+ * @param WP_Query $query Original WP_Query object.
*
- * @return array Array of searchable post types corresponding to the original query
+ * @return array Array of searchable post types corresponding to the original query.
*/
public function get_es_wp_query_post_type_for_query( WP_Query $query ) {
$post_types = $query->get( 'post_type' );
- // If we're searching 'any', we want to only pass searchable post types to ES
+ // If we're searching 'any', we want to only pass searchable post types to Elasticsearch.
if ( 'any' === $post_types ) {
$post_types = array_values( get_post_types( array(
'exclude_from_search' => false,
@@ -474,7 +698,7 @@ class Jetpack_Search {
$sanitized_post_types = array();
- // Make sure the post types are queryable
+ // Make sure the post types are queryable.
foreach ( $post_types as $post_type ) {
if ( ! $post_type ) {
continue;
@@ -491,25 +715,15 @@ class Jetpack_Search {
return $sanitized_post_types;
}
- /**
- * Initialze widgets for the Search module
- *
- * @module search
- */
- public function action__widgets_init() {
- require_once( dirname( __FILE__ ) . '/class.jetpack-search-widget-filters.php' );
-
- register_widget( 'Jetpack_Search_Widget_Filters' );
- }
/**
- * Get the Elasticsearch result
+ * Get the Elasticsearch result.
*
- * @module search
+ * @since 5.0.0
*
- * @param bool $raw If true, does not check for WP_Error or return the 'results' array - the JSON decoded HTTP response
+ * @param bool $raw If true, does not check for WP_Error or return the 'results' array - the JSON decoded HTTP response.
*
- * @return array|bool The search results, or false if there was a failure
+ * @return array|bool The search results, or false if there was a failure.
*/
public function get_search_result( $raw = false ) {
if ( $raw ) {
@@ -520,12 +734,14 @@ class Jetpack_Search {
}
/**
- * Add the date portion of a WP_Query onto the query args
+ * Add the date portion of a WP_Query onto the query args.
*
- * @param array $es_wp_query_args
- * @param WP_Query $query The original WP_Query
+ * @since 5.0.0
*
- * @return array The es wp query args, with date filters added (as needed)
+ * @param array $es_wp_query_args The Elasticsearch query arguments in WordPress form.
+ * @param WP_Query $query The original WP_Query.
+ *
+ * @return array The es wp query args, with date filters added (as needed).
*/
public function filter__add_date_filter_to_query( array $es_wp_query_args, WP_Query $query ) {
if ( $query->get( 'year' ) ) {
@@ -561,41 +777,31 @@ class Jetpack_Search {
}
/**
- * Converts WP_Query style args to ES args
+ * Converts WP_Query style args to Elasticsearch args.
*
- * @module search
+ * @since 5.0.0
*
- * @param array $args Array of WP_Query style arguments
+ * @param array $args Array of WP_Query style arguments.
*
- * @return array Array of ES style query arguments
+ * @return array Array of ES style query arguments.
*/
- function convert_wp_es_to_es_args( array $args ) {
- jetpack_require_lib( 'jetpack-wpes-query-builder' );
-
- $builder = new Jetpack_WPES_Query_Builder();
+ public function convert_wp_es_to_es_args( array $args ) {
+ jetpack_require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-query-parser' );
$defaults = array(
'blog_id' => get_current_blog_id(),
-
'query' => null, // Search phrase
- 'query_fields' => array( 'title', 'content', 'author', 'tag', 'category' ),
-
- 'post_type' => null, // string or an array
+ 'query_fields' => array(), //list of fields to search
+ 'post_type' => null, // string or an array
'terms' => array(), // ex: array( 'taxonomy-1' => array( 'slug' ), 'taxonomy-2' => array( 'slug-a', 'slug-b' ) )
-
'author' => null, // id or an array of ids
'author_name' => array(), // string or an array
-
'date_range' => null, // array( 'field' => 'date', 'gt' => 'YYYY-MM-dd', 'lte' => 'YYYY-MM-dd' ); date formats: 'YYYY-MM-dd' or 'YYYY-MM-dd HH:MM:SS'
-
'orderby' => null, // Defaults to 'relevance' if query is set, otherwise 'date'. Pass an array for multiple orders.
'order' => 'DESC',
-
'posts_per_page' => 10,
-
'offset' => null,
'paged' => null,
-
/**
* Aggregations. Examples:
* array(
@@ -603,11 +809,137 @@ class Jetpack_Search {
* 'Post Type' => array( 'type' => 'post_type', 'count' => 10 ) ),
* );
*/
- 'aggregations' => null,
+ 'aggregations' => null,
);
$args = wp_parse_args( $args, $defaults );
+ $parser = new Jetpack_WPES_Search_Query_Parser( $args['query'], array( get_locale() ) );
+
+ if ( empty( $args['query_fields'] ) ) {
+ if ( defined( 'JETPACK_SEARCH_VIP_INDEX' ) && JETPACK_SEARCH_VIP_INDEX ) {
+ // VIP indices do not have per language fields
+ $match_fields = array(
+ 'title^0.1',
+ 'content^0.1',
+ 'excerpt^0.1',
+ 'tag.name^0.1',
+ 'category.name^0.1',
+ 'author_login^0.1',
+ 'author^0.1',
+ );
+ $boost_fields = array(
+ 'title^2',
+ 'tag.name',
+ 'category.name',
+ 'author_login',
+ 'author',
+ );
+ $boost_phrase_fields = array(
+ 'title',
+ 'content',
+ 'excerpt',
+ 'tag.name',
+ 'category.name',
+ 'author',
+ );
+ } else {
+ $match_fields = $parser->merge_ml_fields(
+ array(
+ 'title' => 0.1,
+ 'content' => 0.1,
+ 'excerpt' => 0.1,
+ 'tag.name' => 0.1,
+ 'category.name' => 0.1,
+ ),
+ array(
+ 'author_login^0.1',
+ 'author^0.1',
+ )
+ );
+
+ $boost_fields = $parser->merge_ml_fields(
+ array(
+ 'title' => 2,
+ 'tag.name' => 1,
+ 'category.name' => 1,
+ ),
+ array(
+ 'author_login',
+ 'author',
+ )
+ );
+
+ $boost_phrase_fields = $parser->merge_ml_fields(
+ array(
+ 'title' => 1,
+ 'content' => 1,
+ 'excerpt' => 1,
+ 'tag.name' => 1,
+ 'category.name' => 1,
+ ),
+ array(
+ 'author',
+ )
+ );
+ }
+ } else {
+ // If code is overriding the fields, then use that. Important for backwards compatibility.
+ $match_fields = $args['query_fields'];
+ $boost_phrase_fields = $match_fields;
+ $boost_fields = null;
+ }
+
+ $parser->phrase_filter( array(
+ 'must_query_fields' => $match_fields,
+ 'boost_query_fields' => null,
+ ) );
+ $parser->remaining_query( array(
+ 'must_query_fields' => $match_fields,
+ 'boost_query_fields' => $boost_fields,
+ ) );
+
+ // Boost on phrase matches
+ $parser->remaining_query( array(
+ 'boost_query_fields' => $boost_phrase_fields,
+ 'boost_query_type' => 'phrase',
+ ) );
+
+ /**
+ * Modify the recency decay parameters for the search query.
+ *
+ * The recency decay lowers the search scores based on the age of a post relative to an origin date. Basic adjustments:
+ * - origin: A date. Posts with this date will have the highest score and no decay applied. Default is today.
+ * - offset: Number of days/months/years (eg 30d). All posts within this time range of the origin (before and after) will have no decay applied. Default is no offset.
+ * - scale: The number of days/months/years from the origin+offset at which the decay will equal the decay param. Default 360d
+ * - decay: The amount of decay applied at offset+scale. Default 0.9.
+ *
+ * The curve applied is a Gaussian. More details available at {@see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay}
+ *
+ * @module search
+ *
+ * @since 5.8.0
+ *
+ * @param array $decay_params The decay parameters.
+ * @param array $args The WP query parameters.
+ */
+ $decay_params = apply_filters(
+ 'jetpack_search_recency_score_decay',
+ array(
+ 'origin' => date( 'Y-m-d' ),
+ 'scale' => '360d',
+ 'decay' => 0.9,
+ ),
+ $args
+ );
+
+ if ( ! empty( $decay_params ) ) {
+ // Newer content gets weighted slightly higher
+ $parser->add_decay( 'gauss', array(
+ 'date_gmt' => $decay_params
+ ) );
+ }
+
$es_query_args = array(
'blog_id' => absint( $args['blog_id'] ),
'size' => absint( $args['posts_per_page'] ),
@@ -620,9 +952,7 @@ class Jetpack_Search {
$es_query_args['from'] = max( 0, ( absint( $args['paged'] ) - 1 ) * $es_query_args['size'] );
}
- // Limit the offset to $this->max_offset posts, as deep pages get exponentially slower
- // See https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html
- $es_query_args['from'] = min( $es_query_args['from'], $this->max_offset );
+ $es_query_args['from'] = min( $es_query_args['from'], Jetpack_Search_Helpers::get_max_offset() );
if ( ! is_array( $args['author_name'] ) ) {
$args['author_name'] = array( $args['author_name'] );
@@ -648,26 +978,25 @@ class Jetpack_Search {
// Filters rock because they are cached from one query to the next
// but they are cached as individual filters, rather than all combined together.
// May get performance boost by also caching the top level boolean filter too.
- $filters = array();
if ( $args['post_type'] ) {
if ( ! is_array( $args['post_type'] ) ) {
$args['post_type'] = array( $args['post_type'] );
}
- $filters[] = array(
+ $parser->add_filter( array(
'terms' => array(
'post_type' => $args['post_type'],
),
- );
+ ) );
}
if ( $args['author_name'] ) {
- $filters[] = array(
+ $parser->add_filter( array(
'terms' => array(
'author_login' => $args['author_name'],
),
- );
+ ) );
}
if ( ! empty( $args['date_range'] ) && isset( $args['date_range']['field'] ) ) {
@@ -675,11 +1004,11 @@ class Jetpack_Search {
unset( $args['date_range']['field'] );
- $filters[] = array(
+ $parser->add_filter( array(
'range' => array(
$field => $args['date_range'],
),
- );
+ ) );
}
if ( is_array( $args['terms'] ) ) {
@@ -705,35 +1034,20 @@ class Jetpack_Search {
}
foreach ( $terms as $term ) {
- $filters[] = array(
+ $parser->add_filter( array(
'term' => array(
$tax_fld => $term,
),
- );
+ ) );
}
}
}
}
- if ( $args['query'] ) {
- $query = array(
- 'multi_match' => array(
- 'query' => $args['query'],
- 'fields' => $args['query_fields'],
- 'operator' => 'and',
- 'type' => 'cross_fields',
- ),
- );
-
- $builder->add_query( $query );
-
- Jetpack_Search::score_query_by_recency( $builder );
-
- if ( ! $args['orderby'] ) {
+ if ( ! $args['orderby'] ) {
+ if ( $args['query'] ) {
$args['orderby'] = array( 'relevance' );
- }
- } else {
- if ( ! $args['orderby'] ) {
+ } else {
$args['orderby'] = array( 'date' );
}
}
@@ -798,34 +1112,24 @@ class Jetpack_Search {
unset( $es_query_args['sort'] );
}
- if ( ! empty( $filters ) && is_array( $filters ) ) {
- foreach ( $filters as $filter ) {
- $builder->add_filter( $filter );
- }
-
- $es_query_args['filter'] = $builder->build_filter();
- }
-
- $es_query_args['query'] = $builder->build_query();
-
- // Aggregations
if ( ! empty( $args['aggregations'] ) ) {
- $this->add_aggregations_to_es_query_builder( $args['aggregations'], $builder );
-
- $es_query_args['aggregations'] = $builder->build_aggregation();
+ $this->add_aggregations_to_es_query_builder( $args['aggregations'], $parser );
}
+ $es_query_args['filter'] = $parser->build_filter();
+ $es_query_args['query'] = $parser->build_query();
+ $es_query_args['aggregations'] = $parser->build_aggregation();
+
return $es_query_args;
}
/**
- * Given an array of aggregations, parse and add them onto the Jetpack_WPES_Query_Builder object for use in ES
- *
- * @module search
+ * Given an array of aggregations, parse and add them onto the Jetpack_WPES_Query_Builder object for use in Elasticsearch.
*
- * @param array $aggregations Array of Aggregations (filters) to add to the Jetpack_WPES_Query_Builder
+ * @since 5.0.0
*
- * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
+ * @param array $aggregations Array of aggregations (filters) to add to the Jetpack_WPES_Query_Builder.
+ * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the Elasticsearch query.
*/
public function add_aggregations_to_es_query_builder( array $aggregations, Jetpack_WPES_Query_Builder $builder ) {
foreach ( $aggregations as $label => $aggregation ) {
@@ -849,13 +1153,13 @@ class Jetpack_Search {
}
/**
- * Given an individual taxonomy aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
+ * Given an individual taxonomy aggregation, add it to the Jetpack_WPES_Query_Builder object for use in Elasticsearch.
*
- * @module search
+ * @since 5.0.0
*
- * @param array $aggregation The aggregation to add to the query builder
- * @param string $label The 'label' (unique id) for this aggregation
- * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
+ * @param array $aggregation The aggregation to add to the query builder.
+ * @param string $label The 'label' (unique id) for this aggregation.
+ * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the Elasticsearch query.
*/
public function add_taxonomy_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
$field = null;
@@ -877,37 +1181,37 @@ class Jetpack_Search {
$builder->add_aggs( $label, array(
'terms' => array(
'field' => $field . '.slug',
- 'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
+ 'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
),
- ));
+ ) );
}
/**
- * Given an individual post_type aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
+ * Given an individual post_type aggregation, add it to the Jetpack_WPES_Query_Builder object for use in Elasticsearch.
*
- * @module search
+ * @since 5.0.0
*
- * @param array $aggregation The aggregation to add to the query builder
- * @param string $label The 'label' (unique id) for this aggregation
- * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
+ * @param array $aggregation The aggregation to add to the query builder.
+ * @param string $label The 'label' (unique id) for this aggregation.
+ * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the Elasticsearch query.
*/
public function add_post_type_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
$builder->add_aggs( $label, array(
'terms' => array(
'field' => 'post_type',
- 'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
+ 'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
),
- ));
+ ) );
}
/**
- * Given an individual date_histogram aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
+ * Given an individual date_histogram aggregation, add it to the Jetpack_WPES_Query_Builder object for use in Elasticsearch.
*
- * @module search
+ * @since 5.0.0
*
- * @param array $aggregation The aggregation to add to the query builder
- * @param string $label The 'label' (unique id) for this aggregation
- * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
+ * @param array $aggregation The aggregation to add to the query builder.
+ * @param string $label The 'label' (unique id) for this aggregation.
+ * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the Elasticsearch query.
*/
public function add_date_histogram_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
$args = array(
@@ -923,7 +1227,7 @@ class Jetpack_Search {
$builder->add_aggs( $label, array(
'date_histogram' => $args,
- ));
+ ) );
}
/**
@@ -931,12 +1235,12 @@ class Jetpack_Search {
*
* Attempts to optimize the filters somewhat.
*
- * @module search
+ * @since 5.0.0
*
- * @param array $curr_filter The existing filters to build upon
- * @param array $filters The new filters to add
+ * @param array $curr_filter The existing filters to build upon.
+ * @param array $filters The new filters to add.
*
- * @return array The resulting merged filters
+ * @return array The resulting merged filters.
*/
public static function and_es_filters( array $curr_filter, array $filters ) {
if ( ! is_array( $curr_filter ) || isset( $curr_filter['match_all'] ) ) {
@@ -955,61 +1259,38 @@ class Jetpack_Search {
}
/**
- * Add a recency score to a given Jetpack_WPES_Query_Builder object, for emphasizing newer posts in results
- *
- * Internally uses a gauss decay function
- *
- * @module search
- *
- * @param Jetpack_WPES_Query_Builder $builder The Jetpack_WPES_Query_Builder to add the recency score to
- *
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay
- */
- public static function score_query_by_recency( Jetpack_WPES_Query_Builder &$builder ) {
- //Newer content gets weighted slightly higher
- $date_scale = '360d';
- $date_decay = 0.9;
- $date_origin = date( 'Y-m-d' );
-
- $builder->add_decay( 'gauss', array(
- 'date_gmt' => array(
- 'origin' => $date_origin,
- 'scale' => $date_scale,
- 'decay' => $date_decay,
- ),
- ));
- }
-
- /**
- * Set the available filters for the search
+ * Set the available filters for the search.
*
- * These get rendered via the Jetpack_Search_Widget_Filters() widget
+ * These get rendered via the Jetpack_Search_Widget() widget.
*
* Behind the scenes, these are implemented using Elasticsearch Aggregations.
*
* If you do not require counts of how many documents match each filter, please consider using regular WP Query
* arguments instead, such as via the jetpack_search_es_wp_query_args filter
*
- * @module search
+ * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
*
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
+ * @since 5.0.0
*
* @param array $aggregations Array of filters (aggregations) to apply to the search
*/
public function set_filters( array $aggregations ) {
+ foreach ( (array) $aggregations as $key => $agg ) {
+ if ( empty( $agg['name'] ) ) {
+ $aggregations[ $key ]['name'] = $key;
+ }
+ }
$this->aggregations = $aggregations;
}
/**
- * Set the search's facets (deprecated)
+ * Set the search's facets (deprecated).
*
- * @module search
+ * @deprecated 5.0 Please use Jetpack_Search::set_filters() instead.
*
- * @deprecated 5.0 Please use Jetpack_Search::set_filters() instead
+ * @see Jetpack_Search::set_filters()
*
- * @see Jetpack_Search::set_filters()
- *
- * @param array $facets Array of facets to apply to the search
+ * @param array $facets Array of facets to apply to the search.
*/
public function set_facets( array $facets ) {
_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::set_filters()' );
@@ -1018,13 +1299,13 @@ class Jetpack_Search {
}
/**
- * Get the raw Aggregation results from the ES response
+ * Get the raw Aggregation results from the Elasticsearch response.
*
- * @module search
+ * @since 5.0.0
*
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
+ * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
*
- * @return array Array of Aggregations performed on the search
+ * @return array Array of Aggregations performed on the search.
*/
public function get_search_aggregations_results() {
$aggregations = array();
@@ -1039,15 +1320,13 @@ class Jetpack_Search {
}
/**
- * Get the raw Facet results from the ES response
- *
- * @module search
+ * Get the raw Facet results from the Elasticsearch response.
*
- * @deprecated 5.0 Please use Jetpack_Search::get_search_aggregations_results() instead
+ * @deprecated 5.0 Please use Jetpack_Search::get_search_aggregations_results() instead.
*
- * @see Jetpack_Search::get_search_aggregations_results()
+ * @see Jetpack_Search::get_search_aggregations_results()
*
- * @return array Array of Facets performed on the search
+ * @return array Array of Facets performed on the search.
*/
public function get_search_facets() {
_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_search_aggregations_results()' );
@@ -1056,19 +1335,19 @@ class Jetpack_Search {
}
/**
- * Get the results of the Filters performed, including the number of matching documents
+ * Get the results of the Filters performed, including the number of matching documents.
*
* Returns an array of Filters (keyed by $label, as passed to Jetpack_Search::set_filters()), containing the Filter and all resulting
* matching buckets, the url for applying/removing each bucket, etc.
*
* NOTE - if this is called before the search is performed, an empty array will be returned. Use the $aggregations class
- * member if you need to access the raw filters set in Jetpack_Search::set_filters()
+ * member if you need to access the raw filters set in Jetpack_Search::set_filters().
*
- * @module search
+ * @since 5.0.0
*
- * @param WP_Query $query The optional original WP_Query to use for determining which filters are active. Defaults to the main query
+ * @param WP_Query $query The optional original WP_Query to use for determining which filters are active. Defaults to the main query.
*
- * @return array Array of Filters applied and info about them
+ * @return array Array of filters applied and info about them.
*/
public function get_filters( WP_Query $query = null ) {
if ( ! $query instanceof WP_Query ) {
@@ -1105,10 +1384,10 @@ class Jetpack_Search {
// Figure out which terms are active in the query, for this taxonomy
if ( 'taxonomy' === $this->aggregations[ $label ]['type'] ) {
- $tax_query_var = $this->get_taxonomy_query_var( $this->aggregations[ $label ]['taxonomy'] );
+ $tax_query_var = $this->get_taxonomy_query_var( $this->aggregations[ $label ]['taxonomy'] );
if ( ! empty( $query->tax_query ) && ! empty( $query->tax_query->queries ) && is_array( $query->tax_query->queries ) ) {
- foreach( $query->tax_query->queries as $tax_query ) {
+ foreach ( $query->tax_query->queries as $tax_query ) {
if ( is_array( $tax_query ) && $this->aggregations[ $label ]['taxonomy'] === $tax_query['taxonomy'] &&
'slug' === $tax_query['field'] &&
is_array( $tax_query['terms'] ) ) {
@@ -1118,14 +1397,18 @@ class Jetpack_Search {
}
}
- // Now take the resulting found aggregation items and generate the additional info about them, such as
- // activation/deactivation url, name, count, etc
+ // Now take the resulting found aggregation items and generate the additional info about them, such as activation/deactivation url, name, count, etc.
$buckets = array();
if ( ! empty( $aggregation['buckets'] ) ) {
$buckets = (array) $aggregation['buckets'];
}
+ if ( 'date_histogram' == $type ) {
+ //re-order newest to oldest
+ $buckets = array_reverse( $buckets );
+ }
+
// Some aggregation types like date_histogram don't support the max results parameter
if ( is_int( $this->aggregations[ $label ]['count'] ) && count( $buckets ) > $this->aggregations[ $label ]['count'] ) {
$buckets = array_slice( $buckets, 0, $this->aggregations[ $label ]['count'] );
@@ -1162,9 +1445,12 @@ class Jetpack_Search {
$slug_count = count( $existing_term_slugs );
if ( $slug_count > 1 ) {
- $remove_url = add_query_arg( $tax_query_var, urlencode( implode( '+', array_diff( $existing_term_slugs, array( $item['key'] ) ) ) ) );
+ $remove_url = Jetpack_Search_Helpers::add_query_arg(
+ $tax_query_var,
+ rawurlencode( implode( '+', array_diff( $existing_term_slugs, array( $item['key'] ) ) ) )
+ );
} else {
- $remove_url = remove_query_arg( $tax_query_var );
+ $remove_url = Jetpack_Search_Helpers::remove_query_arg( $tax_query_var );
}
}
@@ -1197,9 +1483,12 @@ class Jetpack_Search {
// For the right 'remove filter' url, we need to remove the post type from the array, or remove the param entirely if it's the only one
if ( $post_type_count > 1 ) {
- $remove_url = add_query_arg( 'post_type', urlencode_deep( array_diff( $post_types, array( $item['key'] ) ) ) );
+ $remove_url = Jetpack_Search_Helpers::add_query_arg(
+ 'post_type',
+ rawurlencode( implode( ',', array_diff( $post_types, array( $item['key'] ) ) ) )
+ );
} else {
- $remove_url = remove_query_arg( 'post_type' );
+ $remove_url = Jetpack_Search_Helpers::remove_query_arg( 'post_type' );
}
}
@@ -1228,7 +1517,7 @@ class Jetpack_Search {
if ( ! empty( $current_year ) && (int) $current_year === $year ) {
$active = true;
- $remove_url = remove_query_arg( array( 'year', 'monthnum', 'day' ) );
+ $remove_url = Jetpack_Search_Helpers::remove_query_arg( array( 'year', 'monthnum', 'day' ) );
}
break;
@@ -1250,7 +1539,7 @@ class Jetpack_Search {
! empty( $current_month ) && (int) $current_month === $month ) {
$active = true;
- $remove_url = remove_query_arg( array( 'monthnum', 'day' ) );
+ $remove_url = Jetpack_Search_Helpers::remove_query_arg( array( 'year', 'monthnum' ) );
}
break;
@@ -1274,7 +1563,7 @@ class Jetpack_Search {
! empty( $current_day ) && (int) $current_day === $day ) {
$active = true;
- $remove_url = remove_query_arg( array( 'day' ) );
+ $remove_url = Jetpack_Search_Helpers::remove_query_arg( array( 'day' ) );
}
break;
@@ -1293,14 +1582,15 @@ class Jetpack_Search {
$url_params = urlencode_deep( $query_vars );
$aggregation_data[ $label ]['buckets'][] = array(
- 'url' => add_query_arg( $url_params ),
+ 'url' => Jetpack_Search_Helpers::add_query_arg( $url_params ),
'query_vars' => $query_vars,
'name' => $name,
'count' => $item['doc_count'],
'active' => $active,
'remove_url' => $remove_url,
'type' => $type,
- 'type_label' => $label,
+ 'type_label' => $aggregation_data[ $label ]['name'],
+ 'widget_id' => ! empty( $aggregation_data[ $label ]['widget_id'] ) ? $aggregation_data[ $label ]['widget_id'] : 0
);
} // End foreach().
} // End foreach().
@@ -1309,15 +1599,13 @@ class Jetpack_Search {
}
/**
- * Get the results of the Facets performed
+ * Get the results of the facets performed.
*
- * @module search
+ * @deprecated 5.0 Please use Jetpack_Search::get_filters() instead.
*
- * @deprecated 5.0 Please use Jetpack_Search::get_filters() instead
+ * @see Jetpack_Search::get_filters()
*
- * @see Jetpack_Search::get_filters()
- *
- * @return array $facets Array of Facets applied and info about them
+ * @return array $facets Array of facets applied and info about them.
*/
public function get_search_facet_data() {
_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_filters()' );
@@ -1326,11 +1614,11 @@ class Jetpack_Search {
}
/**
- * Get the Filters that are currently applied to this search
+ * Get the filters that are currently applied to this search.
*
- * @module search
+ * @since 5.0.0
*
- * @return array Array if Filters that were applied
+ * @return array Array of filters that were applied.
*/
public function get_active_filter_buckets() {
$active_buckets = array();
@@ -1341,9 +1629,9 @@ class Jetpack_Search {
return $active_buckets;
}
- foreach( $filters as $filter ) {
+ foreach ( $filters as $filter ) {
if ( isset( $filter['buckets'] ) && is_array( $filter['buckets'] ) ) {
- foreach( $filter['buckets'] as $item ) {
+ foreach ( $filter['buckets'] as $item ) {
if ( isset( $item['active'] ) && $item['active'] ) {
$active_buckets[] = $item;
}
@@ -1355,11 +1643,13 @@ class Jetpack_Search {
}
/**
- * Get the Filters that are currently applied to this search
+ * Get the filters that are currently applied to this search.
+ *
+ * @deprecated 5.0 Please use Jetpack_Search::get_active_filter_buckets() instead.
*
- * @module search
+ * @see Jetpack_Search::get_active_filter_buckets()
*
- * @return array Array if Filters that were applied
+ * @return array Array of filters that were applied.
*/
public function get_current_filters() {
_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_active_filter_buckets()' );
@@ -1368,15 +1658,15 @@ class Jetpack_Search {
}
/**
- * Calculate the right query var to use for a given taxonomy
+ * Calculate the right query var to use for a given taxonomy.
*
- * Allows custom code to modify the GET var that is used to represent a given taxonomy, via the jetpack_search_taxonomy_query_var filter
+ * Allows custom code to modify the GET var that is used to represent a given taxonomy, via the jetpack_search_taxonomy_query_var filter.
*
- * @module search
+ * @since 5.0.0
*
- * @param string $taxonomy_name The name of the taxonomy for which to get the query var
+ * @param string $taxonomy_name The name of the taxonomy for which to get the query var.
*
- * @return bool|string The query var to use for this taxonomy, or false if none found
+ * @return bool|string The query var to use for this taxonomy, or false if none found.
*/
public function get_taxonomy_query_var( $taxonomy_name ) {
$taxonomy = get_taxonomy( $taxonomy_name );
@@ -1390,9 +1680,9 @@ class Jetpack_Search {
*
* @module search
*
- * @since 5.0.0
+ * @since 5.0.0
*
- * @param string $query_var The current query_var for the taxonomy
+ * @param string $query_var The current query_var for the taxonomy
* @param string $taxonomy_name The taxonomy name
*/
return apply_filters( 'jetpack_search_taxonomy_query_var', $taxonomy->query_var, $taxonomy_name );
@@ -1400,17 +1690,17 @@ class Jetpack_Search {
/**
* Takes an array of aggregation results, and ensures the array key ordering matches the key order in $desired
- * which is the input order
+ * which is the input order.
*
- * Necessary because ES does not always return Aggs in the same order that you pass them in, and it should be possible
- * to control the display order easily
+ * Necessary because ES does not always return aggregations in the same order that you pass them in,
+ * and it should be possible to control the display order easily.
*
- * @module search
+ * @since 5.0.0
*
- * @param array $aggregations Agg results to be reordered
- * @param array $desired Array with keys representing the desired ordering
+ * @param array $aggregations Aggregation results to be reordered.
+ * @param array $desired Array with keys representing the desired ordering.
*
- * @return array A new array with reordered keys, matching those in $desired
+ * @return array A new array with reordered keys, matching those in $desired.
*/
public function fix_aggregation_ordering( array $aggregations, array $desired ) {
if ( empty( $aggregations ) || empty( $desired ) ) {
@@ -1419,7 +1709,7 @@ class Jetpack_Search {
$reordered = array();
- foreach( array_keys( $desired ) as $agg_name ) {
+ foreach ( array_keys( $desired ) as $agg_name ) {
if ( isset( $aggregations[ $agg_name ] ) ) {
$reordered[ $agg_name ] = $aggregations[ $agg_name ];
}
@@ -1427,4 +1717,72 @@ class Jetpack_Search {
return $reordered;
}
+
+ /**
+ * Sends events to Tracks when a search filters widget is updated.
+ *
+ * @since 5.8.0
+ *
+ * @param string $option The option name. Only "widget_jetpack-search-filters" is cared about.
+ * @param array $old_value The old option value.
+ * @param array $new_value The new option value.
+ */
+ public function track_widget_updates( $option, $old_value, $new_value ) {
+ if ( 'widget_jetpack-search-filters' !== $option ) {
+ return;
+ }
+
+ $event = Jetpack_Search_Helpers::get_widget_tracks_value( $old_value, $new_value );
+ if ( ! $event ) {
+ return;
+ }
+
+ jetpack_tracks_record_event(
+ wp_get_current_user(),
+ sprintf( 'jetpack_search_widget_%s', $event['action'] ),
+ $event['widget']
+ );
+ }
+
+ /**
+ * Moves any active search widgets to the inactive category.
+ *
+ * @since 5.9.0
+ *
+ * @param string $module Unused. The Jetpack module being disabled.
+ */
+ public function move_search_widgets_to_inactive( $module ) {
+ if ( ! is_active_widget( false, false, Jetpack_Search_Helpers::FILTER_WIDGET_BASE, true ) ) {
+ return;
+ }
+
+ $sidebars_widgets = wp_get_sidebars_widgets();
+
+ if ( ! is_array( $sidebars_widgets ) ) {
+ return;
+ }
+
+ $changed = false;
+
+ foreach ( $sidebars_widgets as $sidebar => $widgets ) {
+ if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) {
+ continue;
+ }
+
+ if ( is_array( $widgets ) ) {
+ foreach ( $widgets as $key => $widget ) {
+ if ( _get_widget_id_base( $widget ) == Jetpack_Search_Helpers::FILTER_WIDGET_BASE ) {
+ $changed = true;
+
+ array_unshift( $sidebars_widgets['wp_inactive_widgets'], $widget );
+ unset( $sidebars_widgets[ $sidebar ][ $key ] );
+ }
+ }
+ }
+ }
+
+ if ( $changed ) {
+ wp_set_sidebars_widgets( $sidebars_widgets );
+ }
+ }
}
diff --git a/plugins/jetpack/modules/seo-tools.php b/plugins/jetpack/modules/seo-tools.php
index 18b3ea84..db573a4f 100644
--- a/plugins/jetpack/modules/seo-tools.php
+++ b/plugins/jetpack/modules/seo-tools.php
@@ -2,14 +2,13 @@
/**
* Module Name: SEO Tools
* Module Description: Better results on search engines and social media.
- * Jumpstart Description: Better results on search engines and social media.
* Sort Order: 35
* Recommendation Order: 15
* First Introduced: 4.4
* Requires Connection: Yes
* Auto Activate: No
* Module Tags: Social, Appearance
- * Feature: Traffic, Jumpstart
+ * Feature: Traffic
* Additional Search Queries: search engine optimization, social preview, meta description, custom title format
*/
diff --git a/plugins/jetpack/modules/sharedaddy/sharedaddy.php b/plugins/jetpack/modules/sharedaddy/sharedaddy.php
index 2bc170e7..c9de29b1 100644
--- a/plugins/jetpack/modules/sharedaddy/sharedaddy.php
+++ b/plugins/jetpack/modules/sharedaddy/sharedaddy.php
@@ -51,7 +51,18 @@ function sharing_email_send_post( $data ) {
// Make sure to pass the title through the normal sharing filters.
$title = $data['sharing_source']->get_share_title( $data['post']->ID );
- wp_mail( $data['target'], '[' . __( 'Shared Post', 'jetpack' ) . '] ' . $title, $content, $headers );
+ /**
+ * Filter the Sharing Email Send Post Subject.
+ *
+ * @module sharedaddy
+ *
+ * @since 5.8.0
+ *
+ * @param string $var Sharing Email Send Post Subject. Default is "Shared Post".
+ */
+ $subject = apply_filters( 'wp_sharing_email_send_post_subject', '[' . __( 'Shared Post', 'jetpack' ) . '] ' . $title );
+
+ wp_mail( $data['target'], $subject, $content, $headers );
}
diff --git a/plugins/jetpack/modules/sharedaddy/sharing-service.php b/plugins/jetpack/modules/sharedaddy/sharing-service.php
index 21e42836..2a056991 100644
--- a/plugins/jetpack/modules/sharedaddy/sharing-service.php
+++ b/plugins/jetpack/modules/sharedaddy/sharing-service.php
@@ -791,7 +791,15 @@ function sharing_display( $text = '', $echo = false ) {
} else {
$ver = '20141212';
}
- wp_register_script( 'sharing-js', plugin_dir_url( __FILE__ ).'sharing.js', array( 'jquery' ), $ver );
+ wp_register_script(
+ 'sharing-js',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/sharedaddy/sharing.min.js',
+ 'modules/sharedaddy/sharing.js'
+ ),
+ array( 'jquery' ),
+ $ver
+ );
// Enqueue scripts for the footer
add_action( 'wp_footer', 'sharing_add_footer' );
diff --git a/plugins/jetpack/modules/sharedaddy/sharing-sources.php b/plugins/jetpack/modules/sharedaddy/sharing-sources.php
index c4427a1a..679449e1 100644
--- a/plugins/jetpack/modules/sharedaddy/sharing-sources.php
+++ b/plugins/jetpack/modules/sharedaddy/sharing-sources.php
@@ -1699,7 +1699,7 @@ class Jetpack_Share_WhatsApp extends Sharing_Source {
}
public function get_display( $post ) {
- return $this->get_link( 'whatsapp://send?text=' . rawurlencode( $this->get_share_title( $post->ID ) ) . ' ' . rawurlencode( $this->get_share_url( $post->ID ) ), _x( 'WhatsApp', 'share to', 'jetpack' ), __( 'Click to share on WhatsApp', 'jetpack' ) );
+ return $this->get_link( 'https://api.whatsapp.com/send?text=' . rawurlencode( $this->get_share_title( $post->ID ) ) . ' ' . rawurlencode( $this->get_share_url( $post->ID ) ), _x( 'WhatsApp', 'share to', 'jetpack' ), __( 'Click to share on WhatsApp', 'jetpack' ) );
}
}
diff --git a/plugins/jetpack/modules/sharedaddy/sharing.js b/plugins/jetpack/modules/sharedaddy/sharing.js
index 6c31c6c7..fd7d47b9 100644
--- a/plugins/jetpack/modules/sharedaddy/sharing.js
+++ b/plugins/jetpack/modules/sharedaddy/sharing.js
@@ -19,12 +19,7 @@ if ( sharing_js_options && sharing_js_options.counts ) {
}
requests = {
- // LinkedIn actually gets the share count for both the http and https version automatically -- so we don't need to do extra magic
- linkedin: [
- 'https://www.linkedin.com/countserv/count/share?format=jsonp&callback=updateLinkedInCount&url=' +
- encodeURIComponent( url )
- ],
- // Pinterest, like LinkedIn, handles share counts for both http and https
+ // Pinterest handles share counts for both http and https
pinterest: [
window.location.protocol +
'//api.pinterest.com/v1/urls/count.json?callback=WPCOMSharing.update_pinterest_count&url=' +
@@ -85,11 +80,6 @@ if ( sharing_js_options && sharing_js_options.counts ) {
WPCOMSharing.inject_share_count( 'sharing-facebook-' + WPCOM_sharing_counts[ permalink ], data[ url ].share.share_count );
}
},
- update_linkedin_count : function( data ) {
- if ( 'undefined' !== typeof data.count && ( data.count * 1 ) > 0 ) {
- WPCOMSharing.inject_share_count( 'sharing-linkedin-' + WPCOM_sharing_counts[ data.url ], data.count );
- }
- },
update_pinterest_count : function( data ) {
if ( 'undefined' !== typeof data.count && ( data.count * 1 ) > 0 ) {
WPCOMSharing.inject_share_count( 'sharing-pinterest-' + WPCOM_sharing_counts[ data.url ], data.count );
@@ -115,10 +105,6 @@ if ( sharing_js_options && sharing_js_options.counts ) {
};
}
-var updateLinkedInCount = function( data ) {
- WPCOMSharing.update_linkedin_count( data );
-};
-
(function($){
var $body, $sharing_email;
diff --git a/plugins/jetpack/modules/sharedaddy/sharing.php b/plugins/jetpack/modules/sharedaddy/sharing.php
index b147b81a..f6a6bebd 100644
--- a/plugins/jetpack/modules/sharedaddy/sharing.php
+++ b/plugins/jetpack/modules/sharedaddy/sharing.php
@@ -23,7 +23,15 @@ class Sharing_Admin {
}
public function sharing_head() {
- wp_enqueue_script( 'sharing-js', WP_SHARING_PLUGIN_URL . 'admin-sharing.js', array( 'jquery-ui-draggable', 'jquery-ui-droppable', 'jquery-ui-sortable', 'jquery-form' ), 2 );
+ wp_enqueue_script(
+ 'sharing-js',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/sharedaddy/admin-sharing.min.js',
+ 'modules/sharedaddy/admin-sharing.js'
+ ),
+ array( 'jquery-ui-draggable', 'jquery-ui-droppable', 'jquery-ui-sortable', 'jquery-form' ),
+ 2
+ );
$postfix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
if ( is_rtl() ) {
wp_enqueue_style( 'sharing-admin', WP_SHARING_PLUGIN_URL . 'admin-sharing-rtl' . $postfix . '.css', false, JETPACK__VERSION );
@@ -376,10 +384,10 @@ class Sharing_Admin {
$label = $post_type_object->labels->name;
}
?>
- <?php
- if ( $br ) {
+ <?php
+ if ( $br ) {
echo '<br />';
- }
+ }
?>
<label><input type="checkbox"<?php checked( in_array( $show, $global['show'] ) ); ?> name="show[]" value="<?php echo esc_attr( $show ); ?>" /> <?php echo esc_html( $label ); ?></label>
<?php
diff --git a/plugins/jetpack/modules/shortcodes/brightcove.php b/plugins/jetpack/modules/shortcodes/brightcove.php
index 8dbc9fd5..c774c915 100644
--- a/plugins/jetpack/modules/shortcodes/brightcove.php
+++ b/plugins/jetpack/modules/shortcodes/brightcove.php
@@ -56,7 +56,7 @@ class Jetpack_Brightcove_Shortcode {
* @return array
*/
static public function normalize_attributes( $atts ) {
- if ( 1 == count( $atts ) ) { // this is the case we need to take care of.
+ if ( is_array( $atts ) && 1 == count( $atts ) ) { // this is the case we need to take care of.
$parsed_atts = array();
$params = shortcode_new_to_old_params( $atts );
$params = apply_filters( 'brightcove_dimensions', $params );
diff --git a/plugins/jetpack/modules/shortcodes/getty.php b/plugins/jetpack/modules/shortcodes/getty.php
index 3fa4956a..cdca6859 100644
--- a/plugins/jetpack/modules/shortcodes/getty.php
+++ b/plugins/jetpack/modules/shortcodes/getty.php
@@ -9,26 +9,20 @@
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
add_action( 'init', 'jetpack_getty_enable_embeds' );
} else {
- jetpack_getty_enable_embeds( 'jetpack' );
+ jetpack_getty_enable_embeds();
}
/**
* Register Getty as oembed provider. Add filter to reverse iframes to shortcode. Register [getty] shortcode.
*
* @since 4.5.0
- *
- * @param string $site Can be 'wpcom' or 'jetpack' and determines if we're in wpcom or in a Jetpack site.
+ * @since 5.8.0 removed string parameter.
*/
-function jetpack_getty_enable_embeds( $site = 'wpcom' ) {
-
- // Set the caller argument to pass to Getty's oembed provider.
- $caller = 'jetpack' === $site
- ? parse_url( get_home_url(), PHP_URL_HOST )
- : 'wordpress.com';
+function jetpack_getty_enable_embeds() {
// Support their oEmbed Endpoint
- wp_oembed_add_provider( '#https?://www\.gettyimages\.com/detail/.*#i', "https://embed.gettyimages.com/oembed/?caller=$caller", true );
- wp_oembed_add_provider( '#https?://(www\.)?gty\.im/.*#i', "https://embed.gettyimages.com/oembed/?caller=$caller", true );
+ wp_oembed_add_provider( '#https?://www\.gettyimages\.com/detail/.*#i', "https://embed.gettyimages.com/oembed/", true );
+ wp_oembed_add_provider( '#https?://(www\.)?gty\.im/.*#i', "https://embed.gettyimages.com/oembed/", true );
// Allow iframes to be filtered to short code (so direct copy+paste can be done)
add_filter( 'pre_kses', 'wpcom_shortcodereverse_getty' );
@@ -38,6 +32,47 @@ function jetpack_getty_enable_embeds( $site = 'wpcom' ) {
}
/**
+ * Filters the oEmbed provider URL for Getty URLs to include site URL host as
+ * caller if available, falling back to "wordpress.com". Must be applied at
+ * time of embed in case that `init` is too early (WP.com REST API).
+ *
+ * @module shortcodes
+ *
+ * @since 5.8.0
+ *
+ * @see WP_oEmbed::fetch
+ *
+ * @return string oEmbed provider URL
+ */
+add_filter( 'oembed_fetch_url', 'getty_add_oembed_endpoint_caller' );
+
+function getty_add_oembed_endpoint_caller( $provider ) {
+ // By time filter is called, original provider URL has had url, maxwidth,
+ // maxheight query parameters added.
+ if ( 0 !== strpos( $provider, 'https://embed.gettyimages.com/oembed/' ) ) {
+ return $provider;
+ }
+
+ // Set the caller argument to pass to Getty's oembed provider.
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+
+ // Only include caller for non-private sites
+ if ( ! function_exists( 'is_private_blog' ) || ! is_private_blog() ) {
+ $host = parse_url( get_bloginfo( 'url' ), PHP_URL_HOST );
+ }
+
+ // Fall back to WordPress.com
+ if ( empty( $host ) ) {
+ $host = 'wordpress.com';
+ }
+ } else {
+ $host = parse_url( get_home_url(), PHP_URL_HOST );
+ }
+
+ return add_query_arg( 'caller', $host, $provider );
+}
+
+/**
* Compose shortcode based on Getty iframes.
*
* @since 4.5.0
@@ -47,39 +82,68 @@ function jetpack_getty_enable_embeds( $site = 'wpcom' ) {
* @return mixed
*/
function wpcom_shortcodereverse_getty( $content ) {
- if ( ! is_string( $content ) || false === stripos( $content, 'embed.gettyimages.com/embed' ) ) {
+ if ( ! is_string( $content ) || false === stripos( $content, '.gettyimages.com/' ) ) {
return $content;
}
- $regexp = '!<iframe\s+src=[\'"](https?:)?//embed\.gettyimages\.com/embed(/|/?\?assets=)(\d+(,\d+)*)[^\'"]*?[\'"]((?:\s+\w+=[\'"][^\'"]*[\'"])*)((?:[\s\w]*))></iframe>!i';
+ $regexp = '!<iframe\s+src=[\'"](https?:)?//embed\.gettyimages\.com/embed(/|/?\?assets=)([a-z0-9_-]+(,[a-z0-9_-]+)*)[^\'"]*?[\'"]((?:\s+\w+=[\'"][^\'"]*[\'"])*)((?:[\s\w]*))></iframe>!i';
$regexp_ent = str_replace( '&amp;#0*58;', '&amp;#0*58;|&#0*58;', htmlspecialchars( $regexp, ENT_NOQUOTES ) );
- foreach ( array( 'regexp', 'regexp_ent' ) as $reg ) {
+
+ // Markup pattern for 2017 embed syntax with significant differences from
+ // the prior pattern:
+ $regexp_2017 = '!<a.+?class=\'gie-(single|slideshow)\'.+?gie\.widgets\.load\({([^}]+)}\).+?embed-cdn\.gettyimages\.com/widgets\.js.+?</script>!';
+ $regexp_2017_ent = str_replace( '&amp;#0*58;', '&amp;#0*58;|&#0*58;', htmlspecialchars( $regexp_2017, ENT_NOQUOTES ) );
+
+ foreach ( array( 'regexp_2017', 'regexp_2017_ent', 'regexp', 'regexp_ent' ) as $reg ) {
if ( ! preg_match_all( $$reg, $content, $matches, PREG_SET_ORDER ) ) {
continue;
}
foreach ( $matches as $match ) {
- $ids = esc_html( $match[3] );
-
- $params = $match[5];
-
- if ( 'regexp_ent' == $reg ) {
- $params = html_entity_decode( $params );
+ if ( 'regexp_2017' === $reg || 'regexp_2017_ent' === $reg ) {
+ // Extract individual keys from the matched JavaScript object
+ $params = $match[2];
+ if ( ! preg_match_all( '!(?P<key>\w+)\s*:\s*([\'"](?P<value>[^\'"]*?)(px)?[\'"])!', $params, $key_matches, PREG_SET_ORDER ) ) {
+ continue;
+ }
+
+ foreach ( $key_matches as $key_match ) {
+ switch ( $key_match['key'] ) {
+ case 'items': $ids = $key_match['value']; break;
+ case 'w': $width = (int) $key_match['value']; break;
+ case 'h': $height = (int) $key_match['value']; break;
+ case 'tld': $tld = $key_match['value']; break;
+ }
+ }
+ } else {
+ $params = $match[5];
+ if ( 'regexp_ent' === $reg ) {
+ $params = html_entity_decode( $params );
+ }
+ $params = wp_kses_hair( $params, array( 'http' ) );
+
+ $ids = esc_html( $match[3] );
+ $width = isset( $params['width'] ) ? (int) $params['width']['value'] : 0;
+ $height = isset( $params['height'] ) ? (int) $params['height']['value'] : 0;
}
- $params = wp_kses_hair( $params, array( 'http' ) );
-
- $width = isset( $params['width'] ) ? (int) $params['width']['value'] : 0;
- $height = isset( $params['height'] ) ? (int) $params['height']['value'] : 0;
+ if ( empty( $ids ) ) {
+ continue;
+ }
$shortcode = '[getty src="' . esc_attr( $ids ) . '"';
- if ( $width ) {
+ if ( ! empty( $width ) ) {
$shortcode .= ' width="' . esc_attr( $width ) . '"';
}
- if ( $height ) {
+ if ( ! empty( $height ) ) {
$shortcode .= ' height="' . esc_attr( $height ) . '"';
}
+ // While it does not appear to have any practical impact, Getty has
+ // requested that we include TLD in the embed request
+ if ( ! empty( $tld ) ) {
+ $shortcode .= ' tld="' . esc_attr( $tld ). '"';
+ }
$shortcode .= ']';
$content = str_replace( $match[0], $shortcode, $content );
@@ -128,11 +192,16 @@ function jetpack_getty_shortcode( $atts, $content = '' ) {
return '<!-- Missing Getty Source ID -->';
}
- $src = preg_replace( '/^(\d+(,\d+)*).*$/', '$1', $src );
+ $src = preg_replace( '/^([\da-z-]+(,[\da-z-]+)*).*$/', '$1', $src );
- $args = array();
- $args['width'] = isset( $atts['width'] ) ? (int) $atts['width'] : '462';
- $args['height'] = isset( $atts['height'] ) ? (int) $atts['height'] : '370';
+ $params = array(
+ 'width' => isset( $atts['width'] ) ? (int) $atts['width'] : null,
+ 'height' => isset( $atts['height'] ) ? (int) $atts['height'] : null
+ );
+
+ if ( ! empty( $atts['tld'] ) ) {
+ $params['tld'] = $atts['tld'];
+ }
- return wp_oembed_get( 'https://gty.im/' . $src, $args );
+ return wp_oembed_get( 'https://gty.im/' . $src, array_filter( $params ) );
}
diff --git a/plugins/jetpack/modules/shortcodes/mailchimp.php b/plugins/jetpack/modules/shortcodes/mailchimp.php
index 8bba9978..2cbbefac 100644
--- a/plugins/jetpack/modules/shortcodes/mailchimp.php
+++ b/plugins/jetpack/modules/shortcodes/mailchimp.php
@@ -199,6 +199,6 @@ class MailChimp_Subscriber_Popup {
$displayed_once = true;
- return "\n\n" . '<script type="text/javascript" data-dojo-config="' . esc_attr( implode( ', ', $config_vars ) ) . '">jQuery.getScript( "//downloads.mailchimp.com/js/signup-forms/popup/embed.js", function( data, textStatus, jqxhr ) { require(["mojo/signup-forms/Loader"], function(L) { L.start(' . wp_json_encode( $js_vars ) . ') }); } );</script>' . "\n\n";
+ return "\n\n" . '<script type="text/javascript" data-dojo-config="' . esc_attr( implode( ', ', $config_vars ) ) . '">jQuery.getScript( "//downloads.mailchimp.com/js/signup-forms/popup/embed.js", function( data, textStatus, jqxhr ) { require(["mojo/signup-forms/Loader"], function(L) { L.start(' . wp_json_encode( $js_vars ) . ') }); window.define.amd = undefined; } );</script>' . "\n\n";
}
}
diff --git a/plugins/jetpack/modules/shortcodes/slideshow.php b/plugins/jetpack/modules/shortcodes/slideshow.php
index e84ab0d1..e9046b6c 100644
--- a/plugins/jetpack/modules/shortcodes/slideshow.php
+++ b/plugins/jetpack/modules/shortcodes/slideshow.php
@@ -140,15 +140,16 @@ class Jetpack_Slideshow_Shortcode {
$attachments = get_posts(
array(
- 'post_status' => 'inherit',
- 'post_type' => 'attachment',
- 'post_mime_type' => 'image',
- 'posts_per_page' => - 1,
- 'post_parent' => $post_parent,
- 'order' => $attr['order'],
- 'orderby' => $attr['orderby'],
- 'include' => $attr['include'],
- 'exclude' => $attr['exclude'],
+ 'post_status' => 'inherit',
+ 'post_type' => 'attachment',
+ 'post_mime_type' => 'image',
+ 'posts_per_page' => - 1,
+ 'post_parent' => $post_parent,
+ 'order' => $attr['order'],
+ 'orderby' => $attr['orderby'],
+ 'include' => $attr['include'],
+ 'exclude' => $attr['exclude'],
+ 'suppress_filters' => false,
)
);
diff --git a/plugins/jetpack/modules/sso/class.jetpack-sso-helpers.php b/plugins/jetpack/modules/sso/class.jetpack-sso-helpers.php
index fed7601b..1bf2617c 100644
--- a/plugins/jetpack/modules/sso/class.jetpack-sso-helpers.php
+++ b/plugins/jetpack/modules/sso/class.jetpack-sso-helpers.php
@@ -58,7 +58,7 @@ class Jetpack_SSO_Helpers {
$new_user_override = defined( 'WPCC_NEW_USER_OVERRIDE' ) ? WPCC_NEW_USER_OVERRIDE : false;
/**
- * Allow users to register on your site with a WordPress.com account, even though you disallow normal registrations.
+ * Allow users to register on your site with a WordPress.com account, even though you disallow normal registrations.
* If you return a string that corresponds to a user role, the user will be given that role.
*
* @module sso
@@ -203,7 +203,6 @@ class Jetpack_SSO_Helpers {
static function generate_user( $user_data ) {
$username = $user_data->login;
-
/**
* Determines how many times the SSO module can attempt to randomly generate a user.
*
@@ -224,10 +223,10 @@ class Jetpack_SSO_Helpers {
return false;
}
- $password = wp_generate_password( 20 );
- $user_id = wp_create_user( $username, $password, $user_data->email );
- $user = get_userdata( $user_id );
-
+ $user = (object) array();
+ $user->user_pass = wp_generate_password( 20 );
+ $user->user_login = wp_slash( $username );
+ $user->user_email = wp_slash( $user_data->email );
$user->display_name = $user_data->display_name;
$user->first_name = $user_data->first_name;
$user->last_name = $user_data->last_name;
@@ -238,11 +237,10 @@ class Jetpack_SSO_Helpers {
$user->role = $user_data->role;
}
- wp_update_user( $user );
+ $created_user_id = wp_insert_user( $user );
- update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
-
- return $user;
+ update_user_meta( $created_user_id, 'wpcom_user_id', $user_data->ID );
+ return get_userdata( $created_user_id );
}
static function extend_auth_cookie_expiration_for_sso() {
diff --git a/plugins/jetpack/modules/stats.php b/plugins/jetpack/modules/stats.php
index a0a1f7d4..841c603e 100644
--- a/plugins/jetpack/modules/stats.php
+++ b/plugins/jetpack/modules/stats.php
@@ -164,7 +164,7 @@ function stats_template_redirect() {
$data_stats_array = stats_array( $data );
$stats_footer = <<<END
-<script type='text/javascript' src='{$script}' async defer></script>
+<script type='text/javascript' src='{$script}' async='async' defer='defer'></script>
<script type='text/javascript'>
_stq = window._stq || [];
_stq.push([ 'view', {{$data_stats_array}} ]);
@@ -702,6 +702,7 @@ function stats_convert_post_titles( $html ) {
'post_type' => 'any',
'post_status' => 'any',
'numberposts' => -1,
+ 'suppress_filters' => false,
)
);
foreach ( $posts as $post ) {
diff --git a/plugins/jetpack/modules/theme-tools/featured-content.php b/plugins/jetpack/modules/theme-tools/featured-content.php
index 7fdfa70c..07ad833b 100644
--- a/plugins/jetpack/modules/theme-tools/featured-content.php
+++ b/plugins/jetpack/modules/theme-tools/featured-content.php
@@ -172,9 +172,10 @@ class Featured_Content {
}
$featured_posts = get_posts( array(
- 'include' => $post_ids,
- 'posts_per_page' => count( $post_ids ),
- 'post_type' => self::$post_types,
+ 'include' => $post_ids,
+ 'posts_per_page' => count( $post_ids ),
+ 'post_type' => self::$post_types,
+ 'suppress_filters' => false,
) );
return $featured_posts;
@@ -230,6 +231,7 @@ class Featured_Content {
$featured = get_posts( array(
'numberposts' => $quantity,
'post_type' => self::$post_types,
+ 'suppress_filters' => false,
'tax_query' => array(
array(
'field' => 'term_id',
@@ -459,7 +461,7 @@ class Featured_Content {
*/
public static function customize_register( $wp_customize ) {
$wp_customize->add_section( 'featured_content', array(
- 'title' => __( 'Featured Content', 'jetpack' ),
+ 'title' => esc_html__( 'Featured Content', 'jetpack' ),
'description' => sprintf( __( 'Easily feature all posts with the <a href="%1$s">"featured" tag</a> or a tag of your choice. Your theme supports up to %2$s posts in its featured content area.', 'jetpack' ), admin_url( '/edit.php?tag=featured' ), absint( self::$max_posts ) ),
'priority' => 130,
'theme_supports' => 'featured-content',
@@ -487,20 +489,20 @@ class Featured_Content {
// Add Featured Content controls.
$wp_customize->add_control( 'featured-content[tag-name]', array(
- 'label' => __( 'Tag name', 'jetpack' ),
+ 'label' => esc_html__( 'Tag name', 'jetpack' ),
'section' => 'featured_content',
'theme_supports' => 'featured-content',
'priority' => 20,
) );
$wp_customize->add_control( 'featured-content[hide-tag]', array(
- 'label' => __( 'Do not display tag in post details and tag clouds.', 'jetpack' ),
+ 'label' => esc_html__( 'Do not display tag in post details and tag clouds.', 'jetpack' ),
'section' => 'featured_content',
'theme_supports' => 'featured-content',
'type' => 'checkbox',
'priority' => 30,
) );
$wp_customize->add_control( 'featured-content[show-all]', array(
- 'label' => __( 'Also display tagged posts outside the Featured Content area.', 'jetpack' ),
+ 'label' => esc_html__( 'Also display tagged posts outside the Featured Content area.', 'jetpack' ),
'section' => 'featured_content',
'theme_supports' => 'featured-content',
'type' => 'checkbox',
diff --git a/plugins/jetpack/modules/theme-tools/social-links.php b/plugins/jetpack/modules/theme-tools/social-links.php
index 5f477cd8..8b38814a 100644
--- a/plugins/jetpack/modules/theme-tools/social-links.php
+++ b/plugins/jetpack/modules/theme-tools/social-links.php
@@ -118,7 +118,7 @@ class Social_Links {
*/
public function customize_register( $wp_customize ) {
$wp_customize->add_section( 'jetpack_social_links', array(
- 'title' => __( 'Connect', 'jetpack' ),
+ 'title' => esc_html__( 'Connect', 'jetpack' ),
'priority' => 35,
) );
diff --git a/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php b/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php
index 9955b0f1..ac4d294b 100644
--- a/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php
+++ b/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php
@@ -70,7 +70,7 @@ class Jetpack_Tiled_Gallery {
if ( !empty( $include ) ) {
$include = preg_replace( '/[^0-9,]+/', '', $include );
- $_attachments = get_posts( array('include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby) );
+ $_attachments = get_posts( array('include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby, 'suppress_filters' => false) );
$attachments = array();
foreach ( $_attachments as $key => $val ) {
@@ -83,15 +83,22 @@ class Jetpack_Tiled_Gallery {
$attachments = array();
} elseif ( !empty( $exclude ) ) {
$exclude = preg_replace( '/[^0-9,]+/', '', $exclude );
- $attachments = get_children( array('post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby) );
+ $attachments = get_children( array('post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby, 'suppress_filters' => false) );
} else {
- $attachments = get_children( array('post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ $attachments = get_children( array('post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby, 'suppress_filters' => false ) );
}
return $attachments;
}
public static function default_scripts_and_styles() {
- wp_enqueue_script( 'tiled-gallery', plugins_url( 'tiled-gallery/tiled-gallery.js', __FILE__ ), array( 'jquery' ) );
+ wp_enqueue_script(
+ 'tiled-gallery',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/tiled-gallery/tiled-gallery/tiled-gallery.min.js',
+ 'modules/tiled-gallery/tiled-gallery/tiled-gallery.js'
+ ),
+ array( 'jquery' )
+ );
wp_enqueue_style( 'tiled-gallery', plugins_url( 'tiled-gallery/tiled-gallery.css', __FILE__ ), array(), '2012-09-21' );
wp_style_add_data( 'tiled-gallery', 'rtl', 'replace' );
}
diff --git a/plugins/jetpack/modules/videopress/class.jetpack-videopress.php b/plugins/jetpack/modules/videopress/class.jetpack-videopress.php
index 8cbd3bd7..f69fa34d 100644
--- a/plugins/jetpack/modules/videopress/class.jetpack-videopress.php
+++ b/plugins/jetpack/modules/videopress/class.jetpack-videopress.php
@@ -130,7 +130,10 @@ class Jetpack_VideoPress {
if ( $this->should_override_media_uploader() ) {
wp_enqueue_script(
'videopress-plupload',
- plugins_url( 'js/videopress-plupload.js', __FILE__ ),
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/videopress/js/videopress-plupload.min.js',
+ 'modules/videopress/js/videopress-plupload.js'
+ ),
array(
'jquery',
'wp-plupload'
@@ -140,7 +143,10 @@ class Jetpack_VideoPress {
wp_enqueue_script(
'videopress-uploader',
- plugins_url( 'js/videopress-uploader.js', __FILE__ ),
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/videopress/js/videopress-uploader.min.js',
+ 'modules/videopress/js/videopress-uploader.js'
+ ),
array(
'videopress-plupload'
),
@@ -149,7 +155,10 @@ class Jetpack_VideoPress {
wp_enqueue_script(
'media-video-widget-extensions',
- plugins_url( 'js/media-video-widget-extensions.js', __FILE__ ),
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/videopress/js/media-video-widget-extensions.min.js',
+ 'modules/videopress/js/media-video-widget-extensions.js'
+ ),
array(),
$this->version,
true
diff --git a/plugins/jetpack/modules/videopress/editor-media-view.php b/plugins/jetpack/modules/videopress/editor-media-view.php
index 427c3552..559eed3a 100644
--- a/plugins/jetpack/modules/videopress/editor-media-view.php
+++ b/plugins/jetpack/modules/videopress/editor-media-view.php
@@ -13,7 +13,16 @@ function videopress_handle_editor_view_js() {
add_action( 'admin_print_footer_scripts', 'videopress_editor_view_js_templates' );
wp_enqueue_style( 'videopress-editor-ui', plugins_url( 'css/editor.css', __FILE__ ) );
- wp_enqueue_script( 'videopress-editor-view', plugins_url( 'js/editor-view.js', __FILE__ ), array( 'wp-util', 'jquery' ), false, true );
+ wp_enqueue_script(
+ 'videopress-editor-view',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/videopress/js/editor-view.min.js',
+ 'modules/videopress/js/editor-view.js'
+ ),
+ array( 'wp-util', 'jquery' ),
+ false,
+ true
+ );
wp_localize_script( 'videopress-editor-view', 'vpEditorView', array(
'home_url_host' => parse_url( home_url(), PHP_URL_HOST ),
'min_content_width' => VIDEOPRESS_MIN_WIDTH,
diff --git a/plugins/jetpack/modules/widget-visibility/widget-conditions.php b/plugins/jetpack/modules/widget-visibility/widget-conditions.php
index e85b25ae..0360909a 100644
--- a/plugins/jetpack/modules/widget-visibility/widget-conditions.php
+++ b/plugins/jetpack/modules/widget-visibility/widget-conditions.php
@@ -23,7 +23,16 @@ class Jetpack_Widget_Conditions {
public static function widget_admin_setup() {
wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.css', __FILE__ ) );
wp_style_add_data( 'widget-conditions', 'rtl', 'replace' );
- wp_enqueue_script( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.js', __FILE__ ), array( 'jquery', 'jquery-ui-core' ), 20140721, true );
+ wp_enqueue_script(
+ 'widget-conditions',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widget-visibility/widget-conditions/widget-conditions.min.js',
+ 'modules/widget-visibility/widget-conditions/widget-conditions.js'
+ ),
+ array( 'jquery', 'jquery-ui-core' ),
+ 20171227,
+ true
+ );
// Set up a single copy of all of the data that Widget Visibility needs.
// This allows all widget conditions to reuse the same data, keeping page size down
diff --git a/plugins/jetpack/modules/widget-visibility/widget-conditions/widget-conditions.js b/plugins/jetpack/modules/widget-visibility/widget-conditions/widget-conditions.js
index 609c6c7f..4a9eac59 100644
--- a/plugins/jetpack/modules/widget-visibility/widget-conditions/widget-conditions.js
+++ b/plugins/jetpack/modules/widget-visibility/widget-conditions/widget-conditions.js
@@ -224,12 +224,12 @@ jQuery( function( $ ) {
subkey = majorData[i][1][j][0];
subval = majorData[i][1][j][1];
- optgroup.append( $( '<option/>' ).val( subkey ).text( subval.replace( /&nbsp;/g, '\xA0' ) ) );
+ optgroup.append( $( '<option/>' ).val( subkey ).text( decodeEntities( subval.replace( /&nbsp;/g, '\xA0' ) ) ) );
}
select.append( optgroup );
} else {
- select.append( $( '<option/>' ).val( key ).text( val.replace( /&nbsp;/g, '\xA0' ) ) );
+ select.append( $( '<option/>' ).val( key ).text( decodeEntities( val.replace( /&nbsp;/g, '\xA0' ) ) ) );
}
}
@@ -256,4 +256,10 @@ jQuery( function( $ ) {
index++;
} );
}
+
+ function decodeEntities( encodedString ) {
+ var textarea = document.createElement( 'textarea' );
+ textarea.innerHTML = encodedString;
+ return textarea.value;
+ }
} );
diff --git a/plugins/jetpack/modules/widgets/contact-info.php b/plugins/jetpack/modules/widgets/contact-info.php
index d994f20b..3705e975 100644
--- a/plugins/jetpack/modules/widgets/contact-info.php
+++ b/plugins/jetpack/modules/widgets/contact-info.php
@@ -246,7 +246,15 @@ if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) {
*/
function form( $instance ) {
$instance = wp_parse_args( $instance, $this->defaults() );
- wp_enqueue_script( 'contact-info-admin', plugins_url( 'contact-info/contact-info-admin.js', __FILE__ ), array( 'jquery' ), 20160727 );
+ wp_enqueue_script(
+ 'contact-info-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/contact-info/contact-info-admin.min.js',
+ 'modules/widgets/contact-info/contact-info-admin.js'
+ ),
+ array( 'jquery' ),
+ 20160727
+ );
?>
<p>
diff --git a/plugins/jetpack/modules/widgets/eu-cookie-law.php b/plugins/jetpack/modules/widgets/eu-cookie-law.php
index 14bb9460..a5e37dba 100644
--- a/plugins/jetpack/modules/widgets/eu-cookie-law.php
+++ b/plugins/jetpack/modules/widgets/eu-cookie-law.php
@@ -94,7 +94,16 @@ if ( ! class_exists( 'Jetpack_EU_Cookie_Law_Widget' ) ) {
*/
function enqueue_frontend_scripts() {
wp_enqueue_style( 'eu-cookie-law-style', plugins_url( 'eu-cookie-law/style.css', __FILE__ ), array(), '20170403' );
- wp_enqueue_script( 'eu-cookie-law-script', plugins_url( 'eu-cookie-law/eu-cookie-law.js', __FILE__ ), array( 'jquery' ), '20170404', true );
+ wp_enqueue_script(
+ 'eu-cookie-law-script',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/eu-cookie-law/eu-cookie-law.min.js',
+ 'modules/widgets/eu-cookie-law/eu-cookie-law.js'
+ ),
+ array( 'jquery' ),
+ '20170404',
+ true
+ );
}
/**
diff --git a/plugins/jetpack/modules/widgets/gallery.php b/plugins/jetpack/modules/widgets/gallery.php
index ca68dd03..83b73845 100644
--- a/plugins/jetpack/modules/widgets/gallery.php
+++ b/plugins/jetpack/modules/widgets/gallery.php
@@ -34,8 +34,7 @@ class Jetpack_Gallery_Widget extends WP_Widget {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
if ( class_exists( 'Jetpack_Tiled_Gallery' ) ) {
- $tiled_gallery = new Jetpack_Tiled_Gallery();
- add_action( 'wp_enqueue_scripts', array( $tiled_gallery, 'default_scripts_and_styles' ) );
+ add_action( 'wp_enqueue_scripts', array( 'Jetpack_Tiled_Gallery', 'default_scripts_and_styles' ) );
}
if ( class_exists( 'Jetpack_Slideshow_Shortcode' ) ) {
@@ -179,8 +178,7 @@ class Jetpack_Gallery_Widget extends WP_Widget {
return;
}
- $widget_tiled_gallery = new Jetpack_Tiled_Gallery();
- $widget_tiled_gallery->default_scripts_and_styles();
+ Jetpack_Tiled_Gallery::default_scripts_and_styles();
$layout = new Jetpack_Tiled_Gallery_Layout_Rectangular( $instance['attachments'], $instance['link'], false, 3 );
return $layout->HTML();
@@ -199,8 +197,7 @@ class Jetpack_Gallery_Widget extends WP_Widget {
return;
}
- $widget_tiled_gallery = new Jetpack_Tiled_Gallery();
- $widget_tiled_gallery->default_scripts_and_styles();
+ Jetpack_Tiled_Gallery::default_scripts_and_styles();
$layout = new Jetpack_Tiled_Gallery_Layout_Square( $instance['attachments'], $instance['link'], false, 3 );
return $layout->HTML();
@@ -219,8 +216,7 @@ class Jetpack_Gallery_Widget extends WP_Widget {
return;
}
- $widget_tiled_gallery = new Jetpack_Tiled_Gallery();
- $widget_tiled_gallery->default_scripts_and_styles();
+ Jetpack_Tiled_Gallery::default_scripts_and_styles();
$layout = new Jetpack_Tiled_Gallery_Layout_Circle( $instance['attachments'], $instance['link'], false, 3 );
return $layout->HTML();
@@ -394,7 +390,13 @@ class Jetpack_Gallery_Widget extends WP_Widget {
}
public function enqueue_frontend_scripts() {
- wp_register_script( 'gallery-widget', plugins_url( '/gallery/js/gallery.js', __FILE__ ) );
+ wp_register_script(
+ 'gallery-widget',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/gallery/js/gallery.min.js',
+ 'modules/widgets/gallery/js/gallery.js'
+ )
+ );
wp_enqueue_script( 'gallery-widget' );
}
@@ -405,7 +407,13 @@ class Jetpack_Gallery_Widget extends WP_Widget {
if ( 'widgets.php' == $pagenow || 'customize.php' == $pagenow ) {
wp_enqueue_media();
- wp_enqueue_script( 'gallery-widget-admin', plugins_url( '/gallery/js/admin.js', __FILE__ ), array(
+ wp_enqueue_script(
+ 'gallery-widget-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/gallery/js/admin.min.js',
+ 'modules/widgets/gallery/js/admin.js'
+ ),
+ array(
'media-models',
'media-views'
),
diff --git a/plugins/jetpack/modules/widgets/goodreads.php b/plugins/jetpack/modules/widgets/goodreads.php
index 2b823c23..e8b04136 100644
--- a/plugins/jetpack/modules/widgets/goodreads.php
+++ b/plugins/jetpack/modules/widgets/goodreads.php
@@ -59,7 +59,7 @@ class WPCOM_Widget_Goodreads extends WP_Widget {
echo '<p>' . sprintf(
__( 'You need to enter your numeric user ID for the <a href="%1$s">Goodreads Widget</a> to work correctly. <a href="%2$s" target="_blank">Full instructions</a>.', 'jetpack' ),
esc_url( admin_url( 'widgets.php' ) ),
- 'http://support.wordpress.com/widgets/goodreads-widget/#goodreads-user-id'
+ 'https://support.wordpress.com/widgets/goodreads-widget/#goodreads-user-id'
) . '</p>';
echo $args['after_widget'];
}
@@ -88,12 +88,13 @@ class WPCOM_Widget_Goodreads extends WP_Widget {
}
function goodreads_user_id_exists( $user_id ) {
- $url = "http://www.goodreads.com/user/show/$user_id/";
- $response = wp_remote_head( $url, array( 'httpversion'=>'1.1', 'timeout'=>3, 'redirection'=> 2 ) );
- if ( wp_remote_retrieve_response_code( $response ) === 200 )
+ $url = "https://www.goodreads.com/user/show/$user_id/";
+ $response = wp_remote_head( $url, array( 'httpversion' => '1.1', 'timeout' => 3, 'redirection' => 2 ) );
+ if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
return true;
- else
+ } else {
return false;
+ }
}
function update( $new_instance, $old_instance ) {
diff --git a/plugins/jetpack/modules/widgets/google-translate.php b/plugins/jetpack/modules/widgets/google-translate.php
index a5787112..6bb8c7e6 100644
--- a/plugins/jetpack/modules/widgets/google-translate.php
+++ b/plugins/jetpack/modules/widgets/google-translate.php
@@ -44,7 +44,13 @@ class Jetpack_Google_Translate_Widget extends WP_Widget {
* Enqueue frontend JS scripts.
*/
public function enqueue_scripts() {
- wp_register_script( 'google-translate-init', plugins_url( 'google-translate/google-translate.js', __FILE__ ) );
+ wp_register_script(
+ 'google-translate-init',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/google-translate/google-translate.min.js',
+ 'modules/widgets/google-translate/google-translate.js'
+ )
+ );
wp_register_script( 'google-translate', '//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit', array( 'google-translate-init' ) );
// Admin bar is also displayed on top of the site which causes google translate bar to hide beneath.
// This is a hack to show google translate bar a bit lower.
diff --git a/plugins/jetpack/modules/widgets/googleplus-badge.php b/plugins/jetpack/modules/widgets/googleplus-badge.php
index 1a0ff1af..7f1f7693 100644
--- a/plugins/jetpack/modules/widgets/googleplus-badge.php
+++ b/plugins/jetpack/modules/widgets/googleplus-badge.php
@@ -71,7 +71,14 @@ class WPCOM_Widget_GooglePlus_Badge extends WP_Widget {
global $pagenow;
if ( 'widgets.php' == $pagenow || 'customize.php' == $pagenow ) {
- wp_enqueue_script( 'googleplus-widget-admin', plugins_url( '/google-plus/js/admin.js', __FILE__ ), array( 'jquery' ) );
+ wp_enqueue_script(
+ 'googleplus-widget-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/google-plus/js/admin.min.js',
+ 'modules/widgets/google-plus/js/admin.js'
+ ),
+ array( 'jquery' )
+ );
}
}
diff --git a/plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php b/plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php
index e2a12ca8..70d2d12b 100644
--- a/plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php
+++ b/plugins/jetpack/modules/widgets/migrate-to-core/image-widget.php
@@ -130,6 +130,7 @@ function jetpack_migrate_image_widget() {
),
'post_status' => 'inherit',
'post_type' => 'attachment',
+ 'suppress_filters' => false,
) );
foreach ( $attachment_ids as $attachment_id ) {
diff --git a/plugins/jetpack/modules/widgets/milestone/milestone.php b/plugins/jetpack/modules/widgets/milestone/milestone.php
index 2364d7fa..2dd38361 100644
--- a/plugins/jetpack/modules/widgets/milestone/milestone.php
+++ b/plugins/jetpack/modules/widgets/milestone/milestone.php
@@ -60,12 +60,30 @@ class Milestone_Widget extends WP_Widget {
public static function enqueue_admin( $hook_suffix ) {
if ( 'widgets.php' == $hook_suffix ) {
wp_enqueue_style( 'milestone-admin', self::$url . 'style-admin.css', array(), '20161215' );
- wp_enqueue_script( 'milestone-admin-js', self::$url . 'admin.js', array( 'jquery' ), '20170915', true );
+ wp_enqueue_script(
+ 'milestone-admin-js',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/milestone/admin.min.js',
+ 'modules/widgets/milestone/admin.js'
+ ),
+ array( 'jquery' ),
+ '20170915',
+ true
+ );
}
}
public static function enqueue_template() {
- wp_enqueue_script( 'milestone', self::$url . 'milestone.js', array( 'jquery' ), '20160520', true );
+ wp_enqueue_script(
+ 'milestone',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/milestone/milestone.min.js',
+ 'modules/widgets/milestone/milestone.js'
+ ),
+ array( 'jquery' ),
+ '20160520',
+ true
+ );
}
public static function styles_template() {
diff --git a/plugins/jetpack/modules/widgets/search.php b/plugins/jetpack/modules/widgets/search.php
new file mode 100644
index 00000000..38752ffd
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search.php
@@ -0,0 +1,760 @@
+<?php
+/**
+ * Jetpack Search: Jetpack_Search_Widget class
+ *
+ * @package Jetpack
+ * @subpackage Jetpack Search
+ * @since 5.0.0
+ */
+
+require_once JETPACK__PLUGIN_DIR . 'modules/search/class.jetpack-search-helpers.php';
+
+add_action( 'widgets_init', 'jetpack_search_widget_init' );
+
+function jetpack_search_widget_init() {
+ if ( ! Jetpack::is_active() || ! Jetpack::active_plan_supports( 'search' ) ) {
+ return;
+ }
+
+ register_widget( 'Jetpack_Search_Widget' );
+}
+
+/**
+ * Provides a widget to show available/selected filters on searches.
+ *
+ * @since 5.0.0
+ *
+ * @see WP_Widget
+ */
+class Jetpack_Search_Widget extends WP_Widget {
+
+ /**
+ * The Jetpack_Search instance.
+ *
+ * @since 5.7.0
+ * @var Jetpack_Search
+ */
+ protected $jetpack_search;
+
+ /**
+ * Number of aggregations (filters) to show by default.
+ *
+ * @since 5.8.0
+ * @var int
+ */
+ const DEFAULT_FILTER_COUNT = 5;
+
+ /**
+ * Default sort order for search results.
+ *
+ * @since 5.8.0
+ * @var string
+ */
+ const DEFAULT_SORT = 'relevance_desc';
+
+ /**
+ * Jetpack_Search_Widget constructor.
+ *
+ * @since 5.0.0
+ */
+ public function __construct() {
+ parent::__construct(
+ Jetpack_Search_Helpers::FILTER_WIDGET_BASE,
+ /** This filter is documented in modules/widgets/facebook-likebox.php */
+ apply_filters( 'jetpack_widget_name', esc_html__( 'Search', 'jetpack' ) ),
+ array(
+ 'classname' => 'jetpack-filters widget_search',
+ 'description' => __( 'Replaces the default search with an Elasticsearch-powered search interface and filters.', 'jetpack' ),
+ )
+ );
+
+ if (
+ Jetpack_Search_Helpers::is_active_widget( $this->id ) &&
+ ! Jetpack::is_module_active( 'search' )
+ ) {
+ Jetpack::activate_module( 'search', false, false );
+ }
+
+ if ( is_admin() ) {
+ add_action( 'sidebar_admin_setup', array( $this, 'widget_admin_setup' ) );
+ } else {
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
+ }
+
+ add_action( 'jetpack_search_render_filters_widget_title', array( 'Jetpack_Search_Template_Tags', 'render_widget_title' ), 10, 3 );
+ add_action( 'jetpack_search_render_filters', array( 'Jetpack_Search_Template_Tags', 'render_available_filters' ), 10, 2 );
+ }
+
+ /**
+ * Enqueues the scripts and styles needed for the customizer.
+ *
+ * @since 5.7.0
+ */
+ public function widget_admin_setup() {
+ wp_enqueue_style( 'widget-jetpack-search-filters', plugins_url( 'search/css/search-widget-admin-ui.css', __FILE__ ) );
+
+ // Required for Tracks
+ wp_register_script(
+ 'jp-tracks',
+ '//stats.wp.com/w.js',
+ array(),
+ gmdate( 'YW' ),
+ true
+ );
+
+ wp_register_script(
+ 'jp-tracks-functions',
+ plugins_url( '_inc/lib/tracks/tracks-callables.js', JETPACK__PLUGIN_FILE ),
+ array(),
+ JETPACK__VERSION,
+ false
+ );
+
+ wp_register_script(
+ 'jetpack-search-widget-admin',
+ plugins_url( 'search/js/search-widget-admin.js', __FILE__ ),
+ array( 'jquery', 'jquery-ui-sortable', 'jp-tracks', 'jp-tracks-functions' ),
+ JETPACK__VERSION
+ );
+
+ wp_localize_script( 'jetpack-search-widget-admin', 'jetpack_search_filter_admin', array(
+ 'defaultFilterCount' => self::DEFAULT_FILTER_COUNT,
+ 'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
+ 'tracksEventData' => array(
+ 'is_customizer' => ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) ? 1 : 0,
+ ),
+ 'i18n' => array(
+ 'month' => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', false ),
+ 'year' => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', false ),
+ 'monthUpdated' => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', true ),
+ 'yearUpdated' => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', true ),
+ ),
+ ) );
+
+ wp_enqueue_script( 'jetpack-search-widget-admin' );
+ }
+
+ /**
+ * Enqueue scripts and styles for the frontend.
+ *
+ * @since 5.8.0
+ */
+ public function enqueue_frontend_scripts() {
+ if ( ! is_active_widget( false, false, $this->id_base, true ) ) {
+ return;
+ }
+
+ wp_enqueue_script(
+ 'jetpack-search-widget',
+ plugins_url( 'search/js/search-widget.js', __FILE__ ),
+ array( 'jquery' ),
+ JETPACK__VERSION,
+ true
+ );
+
+ wp_enqueue_style( 'jetpack-search-widget', plugins_url( 'search/css/search-widget-frontend.css', __FILE__ ) );
+ }
+
+ /**
+ * Get the list of valid sort types/orders.
+ *
+ * @since 5.8.0
+ *
+ * @return array The sort orders.
+ */
+ private function get_sort_types() {
+ return array(
+ 'relevance|DESC' => is_admin() ? esc_html__( 'Relevance (recommended)', 'jetpack' ) : esc_html__( 'Relevance', 'jetpack' ),
+ 'date|DESC' => esc_html__( 'Newest first', 'jetpack' ),
+ 'date|ASC' => esc_html__( 'Oldest first', 'jetpack' )
+ );
+ }
+
+ /**
+ * Callback for an array_filter() call in order to only get filters for the current widget.
+ *
+ * @see Jetpack_Search_Widget::widget()
+ *
+ * @since 5.7.0
+ *
+ * @param array $item Filter item.
+ *
+ * @return bool Whether the current filter item is for the current widget.
+ */
+ function is_for_current_widget( $item ) {
+ return isset( $item['widget_id'] ) && $this->id == $item['widget_id'];
+ }
+
+ /**
+ * This method returns a boolean for whether the widget should show site-wide filters for the site.
+ *
+ * This is meant to provide backwards-compatibility for VIP, and other professional plan users, that manually
+ * configured filters via `Jetpack_Search::set_filters()`.
+ *
+ * @since 5.7.0
+ *
+ * @return bool Whether the widget should display site-wide filters or not.
+ */
+ public function should_display_sitewide_filters() {
+ $filter_widgets = get_option( 'widget_jetpack-search-filters' );
+
+ // This shouldn't be empty, but just for sanity
+ if ( empty( $filter_widgets ) ) {
+ return false;
+ }
+
+ // If any widget has any filters, return false
+ foreach ( $filter_widgets as $number => $widget ) {
+ $widget_id = sprintf( '%s-%d', $this->id_base, $number );
+ if ( ! empty( $widget['filters'] ) && is_active_widget( false, $widget_id, $this->id_base ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Responsible for rendering the widget on the frontend.
+ *
+ * @since 5.0.0
+ *
+ * @param array $args Widgets args supplied by the theme.
+ * @param array $instance The current widget instance.
+ */
+ public function widget( $args, $instance ) {
+ $display_filters = false;
+
+ if ( is_search() ) {
+ if ( Jetpack_Search_Helpers::should_rerun_search_in_customizer_preview() ) {
+ Jetpack_Search::instance()->update_search_results_aggregations();
+ }
+
+ $filters = Jetpack_Search::instance()->get_filters();
+
+ if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
+ $filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
+ }
+
+ if ( ! empty( $filters ) ) {
+ $display_filters = true;
+ }
+ }
+
+ if ( ! $display_filters && empty( $instance['search_box_enabled'] ) && empty( $instance['user_sort_enabled'] ) ) {
+ return;
+ }
+
+ $title = isset( $instance['title'] ) ? $instance['title'] : '';
+
+ if ( empty( $title ) ) {
+ $title = '';
+ }
+
+ /** This filter is documented in core/src/wp-includes/default-widgets.php */
+ $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
+
+ echo $args['before_widget'];
+ ?><div id="<?php echo esc_attr( $this->id ); ?>-wrapper"><?php
+
+ if ( ! empty( $title ) ) {
+ /**
+ * Responsible for displaying the title of the Jetpack Search filters widget.
+ *
+ * @module search
+ *
+ * @since 5.7.0
+ *
+ * @param string $title The widget's title
+ * @param string $args['before_title'] The HTML tag to display before the title
+ * @param string $args['after_title'] The HTML tag to display after the title
+ */
+ do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
+ }
+
+ $default_sort = isset( $instance['sort'] ) ? $instance['sort'] : self::DEFAULT_SORT;
+ list( $orderby, $order ) = $this->sorting_to_wp_query_param( $default_sort );
+ $current_sort = "{$orderby}|{$order}";
+
+ // we need to dynamically inject the sort field into the search box when the search box is enabled, and display
+ // it separately when it's not.
+ if ( ! empty( $instance['search_box_enabled'] ) ) {
+ Jetpack_Search_Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
+ }
+
+ if ( ! empty( $instance['search_box_enabled'] ) && ! empty( $instance['user_sort_enabled'] ) ): ?>
+ <div class="jetpack-search-sort-wrapper">
+ <label>
+ <?php esc_html_e( 'Sort by', 'jetpack' ); ?>
+ <select class="jetpack-search-sort">
+ <?php foreach ( $this->get_sort_types() as $sort => $label ) { ?>
+ <option value="<?php echo esc_attr( $sort ); ?>" <?php selected( $current_sort, $sort ); ?>>
+ <?php echo esc_html( $label ); ?>
+ </option>
+ <?php } ?>
+ </select>
+ </label>
+ </div>
+ <?php endif;
+
+ if ( $display_filters ) {
+ /**
+ * Responsible for rendering filters to narrow down search results.
+ *
+ * @module search
+ *
+ * @since 5.8.0
+ *
+ * @param array $filters The possible filters for the current query.
+ * @param array $post_types An array of post types to limit filtering to.
+ */
+ do_action(
+ 'jetpack_search_render_filters',
+ $filters,
+ isset( $instance['post_types'] ) ? $instance['post_types'] : null
+ );
+ }
+
+ $this->maybe_render_sort_javascript( $instance, $order, $orderby );
+
+ echo "</div>";
+ echo $args['after_widget'];
+ }
+
+ /**
+ * Renders JavaScript for the sorting controls on the frontend.
+ *
+ * This JS is a bit complicated, but here's what it's trying to do:
+ * - find the search form
+ * - find the orderby/order fields and set default values
+ * - detect changes to the sort field, if it exists, and use it to set the order field values
+ *
+ * @since 5.8.0
+ *
+ * @param array $instance The current widget instance.
+ * @param string $order The order to initialize the select with.
+ * @param string $orderby The orderby to initialize the select with.
+ */
+ private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
+ if ( ! empty( $instance['user_sort_enabled'] ) ) :
+ ?>
+ <script type="text/javascript">
+ jQuery( document ).ready( function( $ ) {
+ var orderByDefault = <?php echo wp_json_encode( $orderby ); ?>,
+ orderDefault = <?php echo wp_json_encode( $order ); ?>,
+ widgetId = <?php echo wp_json_encode( $this->id ); ?>,
+ searchQuery = <?php echo wp_json_encode( get_query_var( 's', '' ) ); ?>,
+ isSearch = <?php echo wp_json_encode( is_search() ); ?>;
+
+ var container = $( '#' + widgetId + '-wrapper' ),
+ form = container.find('.jetpack-search-form form'),
+ orderBy = form.find( 'input[name=orderby]'),
+ order = form.find( 'input[name=order]'),
+ searchInput = form.find( 'input[name="s"]' );
+
+ orderBy.val( orderByDefault );
+ order.val( orderDefault );
+
+ // Some themes don't set the search query, which results in the query being lost
+ // when doing a sort selection. So, if the query isn't set, let's set it now. This approach
+ // is chosen over running a regex over HTML for every search query performed.
+ if ( isSearch && ! searchInput.val() ) {
+ searchInput.val( searchQuery );
+ }
+
+ searchInput.addClass( 'show-placeholder' );
+
+ container.find( '.jetpack-search-sort' ).change( function( event ) {
+ var values = event.target.value.split( '|' );
+ orderBy.val( values[0] );
+ order.val( values[1] );
+
+ form.submit();
+ });
+ } );
+ </script>
+ <?php
+ endif;
+ }
+
+ /**
+ * Convert a sort string into the separate order by and order parts.
+ *
+ * @since 5.8.0
+ *
+ * @param string $sort A sort string.
+ *
+ * @return array Order by and order.
+ */
+ private function sorting_to_wp_query_param( $sort ) {
+ $parts = explode( '|', $sort );
+ $orderby = isset( $_GET['orderby'] )
+ ? $_GET['orderby']
+ : $parts[0];
+
+ $order = isset( $_GET['order'] )
+ ? strtoupper( $_GET['order'] )
+ : ( ( isset( $parts[1] ) && 'ASC' === strtoupper( $parts[1] ) ) ? 'ASC' : 'DESC' );
+
+ return array( $orderby, $order );
+ }
+
+ /**
+ * Updates a particular instance of the widget. Validates and sanitizes the options.
+ *
+ * @since 5.0.0
+ *
+ * @param array $new_instance New settings for this instance as input by the user via Jetpack_Search_Widget::form().
+ * @param array $old_instance Old settings for this instance.
+ *
+ * @return array Settings to save.
+ */
+ public function update( $new_instance, $old_instance ) {
+ $instance = array();
+
+ $instance['title'] = sanitize_text_field( $new_instance['title'] );
+ $instance['search_box_enabled'] = empty( $new_instance['search_box_enabled'] ) ? '0' : '1';
+ $instance['user_sort_enabled'] = empty( $new_instance['user_sort_enabled'] ) ? '0' : '1';
+ $instance['sort'] = $new_instance['sort'];
+ $instance['post_types'] = empty( $new_instance['post_types'] ) || empty( $instance['search_box_enabled'] )
+ ? array()
+ : array_map( 'sanitize_key', $new_instance['post_types'] );
+
+ $filters = array();
+ if ( isset( $new_instance['filter_type'] ) ) {
+ foreach ( (array) $new_instance['filter_type'] as $index => $type ) {
+ $count = intval( $new_instance['num_filters'][ $index ] );
+ $count = min( 50, $count ); // Set max boundary at 20.
+ $count = max( 1, $count ); // Set min boundary at 1.
+
+ switch ( $type ) {
+ case 'taxonomy':
+ $filters[] = array(
+ 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
+ 'type' => 'taxonomy',
+ 'taxonomy' => sanitize_key( $new_instance['taxonomy_type'][ $index ] ),
+ 'count' => $count,
+ );
+ break;
+ case 'post_type':
+ $filters[] = array(
+ 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
+ 'type' => 'post_type',
+ 'count' => $count,
+ );
+ break;
+ case 'date_histogram':
+ $filters[] = array(
+ 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
+ 'type' => 'date_histogram',
+ 'count' => $count,
+ 'field' => sanitize_key( $new_instance['date_histogram_field'][ $index ] ),
+ 'interval' => sanitize_key( $new_instance['date_histogram_interval'][ $index ] ),
+ );
+ break;
+ }
+ }
+ }
+
+ if ( ! empty( $filters ) ) {
+ $instance['filters'] = $filters;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Outputs the settings update form.
+ *
+ * @since 5.0.0
+ *
+ * @param array $instance Current settings.
+ */
+ public function form( $instance ) {
+ $instance = wp_parse_args( (array) $instance, array(
+ 'title' => '',
+ 'filters' => array( array() )
+ ) );
+
+ $title = strip_tags( $instance['title'] );
+
+ $hide_filters = Jetpack_Search_Helpers::are_filters_by_widget_disabled();
+ $search_box_enabled = ! isset( $instance['search_box_enabled'] ) || ! empty( $instance['search_box_enabled'] );
+ $user_sort_enabled = ! empty( $instance['user_sort_enabled'] );
+ $sort = isset( $instance['sort'] ) ? $instance['sort'] : self::DEFAULT_SORT;
+ $classes = sprintf(
+ 'jetpack-search-filters-widget %s %s %s',
+ $hide_filters ? 'hide-filters' : '',
+ $search_box_enabled ? '' : 'hide-post-types',
+ $this->id
+ );
+ ?>
+ <div class="<?php echo esc_attr( $classes ); ?>">
+ <p>
+ <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
+ <?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
+ </label>
+ <input
+ class="widefat"
+ id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
+ type="text"
+ value="<?php echo esc_attr( $title ); ?>"
+ />
+ </p>
+
+ <p>
+ <label>
+ <input
+ type="checkbox"
+ class="jetpack-search-filters-widget__search-box-enabled"
+ name="<?php echo esc_attr( $this->get_field_name( 'search_box_enabled' ) ); ?>"
+ <?php checked( $search_box_enabled ); ?>
+ />
+ <?php esc_html_e( 'Show search box', 'jetpack' ); ?>
+ </label>
+ </p>
+ <p>
+ <label>
+ <input
+ type="checkbox"
+ class="jetpack-search-filters-widget__sort-controls-enabled"
+ name="<?php echo esc_attr( $this->get_field_name( 'user_sort_enabled' ) ); ?>"
+ <?php checked( $user_sort_enabled ); ?>
+ <?php disabled( ! $search_box_enabled ); ?>
+ />
+ <?php esc_html_e( 'Show sort selection dropdown', 'jetpack' ); ?>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__post-types-select">
+ <label><?php esc_html_e( 'Post types to search (minimum of 1):', 'jetpack' ); ?></label>
+ <?php foreach ( get_post_types( array( 'exclude_from_search' => false ), 'objects' ) as $post_type ) : ?>
+ <label>
+ <input
+ type="checkbox"
+ value="<?php echo esc_attr( $post_type->name ); ?>"
+ name="<?php echo esc_attr( $this->get_field_name( 'post_types' ) ); ?>[]"
+ <?php checked( empty( $instance['post_types'] ) || in_array( $post_type->name, $instance['post_types'] ) ); ?>
+ />&nbsp;
+ <?php echo esc_html( $post_type->label ); ?>
+ </label>
+ <?php endforeach; ?>
+ </p>
+
+ <p>
+ <label>
+ <?php esc_html_e( 'Default sort order:', 'jetpack' ); ?>
+ <select
+ name="<?php echo esc_attr( $this->get_field_name( 'sort' ) ); ?>"
+ class="widefat jetpack-search-filters-widget__sort-order">
+ <?php foreach ( $this->get_sort_types() as $sort_type => $label ) { ?>
+ <option value="<?php echo esc_attr( $sort_type ); ?>" <?php selected( $sort, $sort_type ); ?>>
+ <?php echo esc_html( $label ); ?>
+ </option>
+ <?php } ?>
+ </select>
+ </label>
+ </p>
+
+ <?php if ( ! $hide_filters ): ?>
+ <script class="jetpack-search-filters-widget__filter-template" type="text/template">
+ <?php echo $this->render_widget_edit_filter( array(), true ); ?>
+ </script>
+ <div class="jetpack-search-filters-widget__filters">
+ <?php foreach ( (array) $instance['filters'] as $filter ) : ?>
+ <?php $this->render_widget_edit_filter( $filter ); ?>
+ <?php endforeach; ?>
+ </div>
+ <p class="jetpack-search-filters-widget__add-filter-wrapper">
+ <a class="button jetpack-search-filters-widget__add-filter" href="#">
+ <?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
+ </a>
+ </p>
+ <noscript>
+ <p class="jetpack-search-filters-help">
+ <?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
+ </p>
+ </noscript>
+ <?php if ( is_customize_preview() ) : ?>
+ <p class="jetpack-search-filters-help">
+ <a href="https://jetpack.com/support/search/#filters-not-showing-up" target="_blank">
+ <?php esc_html_e( "Why aren't my filters appearing?", 'jetpack' ); ?>
+ </a>
+ </p>
+ <?php endif; ?>
+ <?php endif; ?>
+ </div>
+ <?php
+ }
+
+ /**
+ * We need to render HTML in two formats: an Underscore template (client-size)
+ * and native PHP (server-side). This helper function allows for easy rendering
+ * of attributes in both formats.
+ *
+ * @since 5.8.0
+ *
+ * @param string $name Attribute name.
+ * @param string $value Attribute value.
+ * @param bool $is_template Whether this is for an Underscore template or not.
+ */
+ private function render_widget_attr( $name, $value, $is_template ) {
+ echo $is_template ? "<%= $name %>" : esc_attr( $value );
+ }
+
+ /**
+ * We need to render HTML in two formats: an Underscore template (client-size)
+ * and native PHP (server-side). This helper function allows for easy rendering
+ * of the "selected" attribute in both formats.
+ *
+ * @since 5.8.0
+ *
+ * @param string $name Attribute name.
+ * @param string $value Attribute value.
+ * @param string $compare Value to compare to the attribute value to decide if it should be selected.
+ * @param bool $is_template Whether this is for an Underscore template or not.
+ */
+ private function render_widget_option_selected( $name, $value, $compare, $is_template ) {
+ $compare_json = wp_json_encode( $compare );
+ echo $is_template ? "<%= $compare_json === $name ? 'selected=\"selected\"' : '' %>" : selected( $value, $compare );
+ }
+
+ /**
+ * Responsible for rendering a single filter in the customizer or the widget administration screen in wp-admin.
+ *
+ * We use this method for two purposes - rendering the fields server-side, and also rendering a script template for Underscore.
+ *
+ * @since 5.7.0
+ *
+ * @param array $filter The filter to render.
+ * @param bool $is_template Whether this is for an Underscore template or not.
+ */
+ public function render_widget_edit_filter( $filter, $is_template = false ) {
+ $args = wp_parse_args( $filter, array(
+ 'name' => '',
+ 'type' => 'taxonomy',
+ 'taxonomy' => '',
+ 'post_type' => '',
+ 'field' => '',
+ 'interval' => '',
+ 'count' => self::DEFAULT_FILTER_COUNT,
+ ) );
+
+ $args['name_placeholder'] = Jetpack_Search_Helpers::generate_widget_filter_name( $args );
+
+ ?>
+ <div class="jetpack-search-filters-widget__filter is-<?php $this->render_widget_attr( 'type', $args['type'], $is_template ); ?>">
+ <p class="jetpack-search-filters-widget__type-select">
+ <label>
+ <?php esc_html_e( 'Filter Type:', 'jetpack' ); ?>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'filter_type' ) ); ?>[]" class="widefat filter-select">
+ <option value="taxonomy" <?php $this->render_widget_option_selected( 'type', $args['type'], 'taxonomy', $is_template ); ?>>
+ <?php esc_html_e( 'Taxonomy', 'jetpack' ); ?>
+ </option>
+ <option value="post_type" <?php $this->render_widget_option_selected( 'type', $args['type'], 'post_type', $is_template ); ?>>
+ <?php esc_html_e( 'Post Type', 'jetpack' ); ?>
+ </option>
+ <option value="date_histogram" <?php $this->render_widget_option_selected( 'type', $args['type'], 'date_histogram', $is_template ); ?>>
+ <?php esc_html_e( 'Date', 'jetpack' ); ?>
+ </option>
+ </select>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__taxonomy-select">
+ <label>
+ <?php
+ esc_html_e( 'Choose a taxonomy:', 'jetpack' );
+ $seen_taxonomy_labels = array();
+ ?>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'taxonomy_type' ) ); ?>[]" class="widefat taxonomy-select">
+ <?php foreach ( get_taxonomies( array( 'public' => true ), 'objects' ) as $taxonomy ) : ?>
+ <option value="<?php echo esc_attr( $taxonomy->name ); ?>" <?php $this->render_widget_option_selected( 'taxonomy', $args['taxonomy'], $taxonomy->name, $is_template ); ?>>
+ <?php
+ $label = in_array( $taxonomy->label, $seen_taxonomy_labels )
+ ? sprintf(
+ /* translators: %1$s is the taxonomy name, %2s is the name of its type to help distinguish between several taxonomies with the same name, e.g. category and tag. */
+ _x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack' ),
+ $taxonomy->label,
+ $taxonomy->name
+ )
+ : $taxonomy->label;
+ echo esc_html( $label );
+ $seen_taxonomy_labels[] = $taxonomy->label;
+ ?>
+ </option>
+ <?php endforeach; ?>
+ </select>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__date-histogram-select">
+ <label>
+ <?php esc_html_e( 'Choose a field:', 'jetpack' ); ?>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_field' ) ); ?>[]" class="widefat date-field-select">
+ <option value="post_date" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date', $is_template ); ?>>
+ <?php esc_html_e( 'Date', 'jetpack' ); ?>
+ </option>
+ <option value="post_date_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date_gmt', $is_template ); ?>>
+ <?php esc_html_e( 'Date GMT', 'jetpack' ); ?>
+ </option>
+ <option value="post_modified" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified', $is_template ); ?>>
+ <?php esc_html_e( 'Modified', 'jetpack' ); ?>
+ </option>
+ <option value="post_modified_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified_gmt', $is_template ); ?>>
+ <?php esc_html_e( 'Modified GMT', 'jetpack' ); ?>
+ </option>
+ </select>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__date-histogram-select">
+ <label>
+ <?php esc_html_e( 'Choose an interval:' ); ?>
+ <select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_interval' ) ); ?>[]" class="widefat date-interval-select">
+ <option value="month" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'month', $is_template ); ?>>
+ <?php esc_html_e( 'Month', 'jetpack' ); ?>
+ </option>
+ <option value="year" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'year', $is_template ); ?>>
+ <?php esc_html_e( 'Year', 'jetpack' ); ?>
+ </option>
+ </select>
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__title">
+ <label>
+ <?php esc_html_e( 'Title:', 'jetpack' ); ?>
+ <input
+ class="widefat"
+ type="text"
+ name="<?php echo esc_attr( $this->get_field_name( 'filter_name' ) ); ?>[]"
+ value="<?php $this->render_widget_attr( 'name', $args['name'], $is_template ); ?>"
+ placeholder="<?php $this->render_widget_attr( 'name_placeholder', $args['name_placeholder'], $is_template ); ?>"
+ />
+ </label>
+ </p>
+
+ <p>
+ <label>
+ <?php esc_html_e( 'Maximum number of filters (1-50):', 'jetpack' ); ?>
+ <input
+ class="widefat filter-count"
+ name="<?php echo esc_attr( $this->get_field_name( 'num_filters' ) ); ?>[]"
+ type="number"
+ value="<?php $this->render_widget_attr( 'count', $args['count'], $is_template ); ?>"
+ min="1"
+ max="50"
+ step="1"
+ required
+ />
+ </label>
+ </p>
+
+ <p class="jetpack-search-filters-widget__controls">
+ <a href="#" class="delete"><?php esc_html_e( 'Remove', 'jetpack' ); ?></a>
+ </p>
+ </div>
+ <?php }
+}
diff --git a/plugins/jetpack/modules/widgets/search/css/search-widget-admin-ui.css b/plugins/jetpack/modules/widgets/search/css/search-widget-admin-ui.css
new file mode 100644
index 00000000..a3313f05
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search/css/search-widget-admin-ui.css
@@ -0,0 +1,87 @@
+.jetpack-search-filters-widget__filter {
+ background: #f9f9f9;
+ border: 1px solid #dfdfdf;
+ padding: 0 12px;
+ margin-bottom: 12px;
+ cursor: move;
+}
+
+.jetpack-search-filters-widget__controls {
+ text-align: right;
+}
+
+.jetpack-search-filters-widget .jetpack-search-filters-widget__sort-controls-enabled {
+ margin-left: 24px;
+}
+
+.jetpack-search-filters-widget__controls .delete {
+ color: #a00;
+}
+
+.jetpack-search-filters-widget.hide-filters .jetpack-search-filters-widget__filter {
+ display: none;
+}
+
+.button.jetpack-search-filters-widget__add-filter {
+ margin-bottom: 10px;
+}
+
+/* Assume that taxonomy select is the default selected. Other controls should be hidden here. */
+.jetpack-search-filters-widget__post-type-select {
+ display: none;
+}
+
+.jetpack-search-filters-widget__date-histogram-select {
+ display: none;
+}
+
+.jetpack-search-filters-widget__filter-placeholder {
+ border: 1px #555 dashed;
+ background-color: #eee;
+ height: 286px;
+ margin-bottom: 12px;
+}
+
+/* When post type is selected, remove the other controls */
+.jetpack-search-filters-widget__filter.is-post_type .jetpack-search-filters-widget__taxonomy-select {
+ display: none;
+}
+
+/* When date is selected, remove the other controls */
+.jetpack-search-filters-widget__filter.is-date_histogram .jetpack-search-filters-widget__date-histogram-select {
+ display: inline;
+}
+
+.jetpack-search-filters-widget__filter.is-date_histogram .jetpack-search-filters-widget__taxonomy-select {
+ display: none;
+}
+
+.jetpack-search-filters-widget.hide-post-types .jetpack-search-filters-widget__post-types-select {
+ display: none;
+}
+
+.jetpack-search-filters-help:before {
+ display: inline-block;
+ position: relative;
+ font-family: dashicons;
+ font-size: 20px;
+ top: 5px;
+ line-height: 1px;
+ content:"\f223";
+}
+.jetpack-search-filters-help {
+ padding: 5px 5px 15px 0;
+}
+
+.jetpack-search-filters-widget__post-types-select label {
+ display: block;
+ margin-bottom: 4px;
+}
+
+.jetpack-search-filters-widget__post-types-select input[type="checkbox"] {
+ margin-left: 24px;
+}
+
+body.no-js .jetpack-search-filters-widget__add-filter-wrapper {
+ display: none;
+}
diff --git a/plugins/jetpack/modules/widgets/search/css/search-widget-frontend.css b/plugins/jetpack/modules/widgets/search/css/search-widget-frontend.css
new file mode 100644
index 00000000..58c7cf3e
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search/css/search-widget-frontend.css
@@ -0,0 +1,66 @@
+.jetpack-search-filters-widget__sub-heading {
+ font-size: inherit;
+ font-weight: bold;
+ margin: 0 0 .5em;
+ padding: 0;
+}
+
+/* The first heading after the form */
+.jetpack-search-form + .jetpack-search-filters-widget__sub-heading {
+ margin-top: 1.5em;
+ margin-bottom: 0.5em !important;
+}
+
+.jetpack-search-filters-widget__clear {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+.jetpack-search-sort-wrapper {
+ margin-top: 1em;
+ margin-bottom: 1.5em;
+}
+
+.jetpack-search-sort-wrapper label {
+ display: inherit;
+}
+
+.widget_search .jetpack-search-filters-widget__filter-list input[type="checkbox"] {
+ width: auto;
+ height: auto;
+}
+
+ul.jetpack-search-filters-widget__filter-list li {
+ border: none;
+ padding: 0;
+ list-style: none;
+}
+
+ul.jetpack-search-filters-widget__filter-list li a {
+ text-decoration: none;
+}
+
+ul.jetpack-search-filters-widget__filter-list li a:hover {
+ box-shadow: none;
+}
+
+ul.jetpack-search-filters-widget__filter-list li label {
+ font-weight: inherit;
+ display: inherit;
+}
+
+.jetpack-search-filters-widget__filter-list {
+ list-style: none;
+}
+
+ul.jetpack-search-filters-widget__filter-list {
+ margin-bottom: 1.5em;
+}
+
+body.search .jetpack-search-form input[name="s"]::placeholder {
+ color: transparent;
+}
+
+body.search .jetpack-search-form input[name="s"].show-placeholder::placeholder {
+ color: inherit;
+}
diff --git a/plugins/jetpack/modules/widgets/search/js/search-widget-admin.js b/plugins/jetpack/modules/widgets/search/js/search-widget-admin.js
new file mode 100644
index 00000000..5840a408
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search/js/search-widget-admin.js
@@ -0,0 +1,360 @@
+/* globals jetpack_search_filter_admin, jQuery, analytics */
+
+( function( $, args ) {
+ var defaultFilterCount = ( 'undefined' !== typeof args && args.defaultFilterCount ) ?
+ args.defaultFilterCount :
+ 5; // Just in case we couldn't find the defaultFiltercount arg
+
+ $( document ).ready( function() {
+ setListeners();
+
+ window.JetpackSearch = window.JetpackSearch || {};
+ window.JetpackSearch.addFilter = addFilter;
+
+ // Initialize Tracks
+ if ( 'undefined' !== typeof analytics && args.tracksUserData ) {
+ analytics.initialize( args.tracksUserData.userid, args.tracksUserData.username );
+ }
+ } );
+
+ function generateFilterTitlePlaceholder( container ) {
+ var placeholder = null,
+ isModified = null,
+ isMonth = null,
+ type = container.find( '.filter-select' ).val();
+
+ if ( 'taxonomy' === type ) {
+ placeholder = container.find('.taxonomy-select option:selected').text().trim();
+ } else if ( 'date_histogram' === type && args && args.i18n ) {
+ isModified = ( -1 !== container.find( '.date-field-select' ).val().indexOf( 'modified' ) );
+ isMonth = ( 'month' === container.find( '.date-interval-select' ).val() );
+
+ if ( isMonth ) {
+ placeholder = isModified ?
+ args.i18n.monthUpdated :
+ args.i18n.month;
+ } else {
+ placeholder = isModified ?
+ args.i18n.yearUpdated :
+ args.i18n.year;
+ }
+ } else {
+ placeholder = container.find('.filter-select option:selected').text().trim();
+ }
+
+ $( container ).find('.jetpack-search-filters-widget__title input').prop( 'placeholder', placeholder );
+ }
+
+ var addFilter = function( filtersContainer, args ) {
+ var template = _.template(
+ filtersContainer
+ .closest( '.jetpack-search-filters-widget' )
+ .find( '.jetpack-search-filters-widget__filter-template' )
+ .html()
+ );
+ generateFilterTitlePlaceholder( filtersContainer.append( template( args ) ) );
+ };
+
+ var setListeners = function( widget ) {
+ widget = ( 'undefined' === typeof widget ) ?
+ $( '.jetpack-search-filters-widget' ):
+ widget;
+
+ var getContainer = function( el ) {
+ return $( el ).closest('.jetpack-search-filters-widget__filter');
+ };
+
+ widget.on( 'change', '.filter-select', function() {
+ var select = $( this ),
+ selectVal = select.val(),
+ eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.type = selectVal;
+
+ select
+ .closest( '.jetpack-search-filters-widget__filter' )
+ .attr( 'class', 'jetpack-search-filters-widget__filter' )
+ .addClass( 'is-' + selectVal );
+
+ generateFilterTitlePlaceholder( getContainer( this ) );
+
+ trackAndBumpMCStats( 'changed_filter_type', eventArgs );
+ } );
+
+ // enable showing sort controls only if showing search box is enabled
+ widget.on( 'change', '.jetpack-search-filters-widget__search-box-enabled', function() {
+ var checkbox = $( this ),
+ checkboxVal = checkbox.is(':checked'),
+ filterParent = checkbox.closest( '.jetpack-search-filters-widget' ),
+ sortControl = filterParent.find( '.jetpack-search-filters-widget__sort-controls-enabled' );
+
+ filterParent.toggleClass( 'hide-post-types' );
+
+ if ( checkboxVal ) {
+ sortControl.removeAttr( 'disabled' );
+ trackAndBumpMCStats( 'enabled_search_box', args.tracksEventData );
+ } else {
+ sortControl.prop( 'checked', false );
+ sortControl.prop( 'disabled', true );
+ trackAndBumpMCStats( 'disabled_search_box', args.tracksEventData );
+ }
+ } );
+
+ widget.on( 'change', '.jetpack-search-filters-widget__sort-controls-enabled', function() {
+ if ( $( this ).is( ':checked' ) ) {
+ trackAndBumpMCStats( 'enabled_sort_controls', args.tracksEventData );
+ } else {
+ trackAndBumpMCStats( 'disabled_sort_controls', args.tracksEventData );
+ }
+ } );
+
+ widget.on( 'click', '.jetpack-search-filters-widget__post-types-select input[type="checkbox"]', function( e ) {
+ var t = $( this );
+ var siblingsChecked = t.closest( '.jetpack-search-filters-widget' )
+ .find( '.jetpack-search-filters-widget__post-types-select input[type="checkbox"]:checked' );
+
+ if ( 0 === siblingsChecked.length ) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ trackAndBumpMCStats( 'attempted_no_post_types', args.tracksEventData );
+ }
+ } );
+
+ widget.on( 'change', '.jetpack-search-filters-widget__post-types-select input[type="checkbox"]', function() {
+ var t = $( this );
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer,
+ post_type: t.val()
+ };
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ if ( t.is( ':checked' ) ) {
+ trackAndBumpMCStats( 'added_post_type', eventArgs );
+ } else {
+ trackAndBumpMCStats( 'removed_post_type', eventArgs );
+ }
+ } );
+
+ widget.on( 'change', '.jetpack-search-filters-widget__sort-order', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.order = $( this ).val();
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_sort_order', eventArgs );
+ } );
+
+ widget.on( 'change', '.jetpack-search-filters-widget__taxonomy-select select', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.taxonomy = $( this ).val();
+
+ generateFilterTitlePlaceholder( getContainer( this ) );
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_taxonomy', eventArgs );
+ } );
+
+ widget.on( 'change', 'select.date-field-select', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.field = $( this ).val();
+
+ generateFilterTitlePlaceholder( getContainer( this ) );
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_date_field', eventArgs );
+ } );
+
+ widget.on( 'change', 'select.date-interval-select', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.interval = $( this ).val();
+
+ generateFilterTitlePlaceholder( getContainer( this ) );
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_date_interval', eventArgs );
+ } );
+
+ widget.on( 'change', 'input.filter-count', function() {
+ var eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.count = $( this ).val();
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ trackAndBumpMCStats( 'changed_filter_count', eventArgs );
+ } );
+
+ // add filter button
+ widget.on( 'click', '.jetpack-search-filters-widget__add-filter', function( e ) {
+ e.preventDefault();
+
+ var filtersContainer = $( this )
+ .closest( '.jetpack-search-filters-widget' )
+ .find( '.jetpack-search-filters-widget__filters' );
+
+ addFilter( filtersContainer, {
+ type: 'taxonomy',
+ taxonomy: '',
+ post_type: '',
+ field: '',
+ interval: '',
+ count: defaultFilterCount,
+ name_placeholder: '',
+ name: ''
+ } );
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+
+ // Trigger change event to let legacy widget admin know the widget state is "dirty"
+ filtersContainer
+ .find( '.jetpack-search-filters-widget__filter' )
+ .find( 'input, textarea, select' )
+ .change();
+
+ trackAndBumpMCStats( 'added_filter', args.tracksEventData );
+ } );
+
+ widget.on( 'click', '.jetpack-search-filters-widget__controls .delete', function( e ) {
+ e.preventDefault();
+ var filter = $( this ).closest( '.jetpack-search-filters-widget__filter' ),
+ eventArgs = {
+ is_customizer: args.tracksEventData.is_customizer
+ };
+
+ eventArgs.type = filter.find( '.filter-select' ).val();
+
+ switch ( eventArgs.type ) {
+ case 'taxonomy':
+ eventArgs.taxonomy = filter.find( '.jetpack-search-filters-widget__taxonomy-select select' ).val();
+ break;
+ case 'date_histogram':
+ eventArgs.dateField = filter.find( '.jetpack-search-filters-widget__date-histogram-select:first select' ).val();
+ eventArgs.dateInterval = filter.find( '.jetpack-search-filters-widget__date-histogram-select:nth-child( 2 ) select' ).val();
+ break;
+ }
+
+ eventArgs.filterCount = filter.find( '.filter-count' ).val();
+
+ trackAndBumpMCStats( 'deleted_filter', eventArgs );
+
+ filter.find( 'input, textarea, select' ).change();
+ filter.remove();
+
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+ } );
+
+ // make the filters sortable
+ $( '.jetpack-search-filters-widget__filters' ).sortable( {
+ placeholder: 'jetpack-search-filters-widget__filter-placeholder',
+ axis: 'y',
+ revert: true,
+ cancel: 'input,textarea,button,select,option,.jetpack-search-filters-widget__controls a',
+ change: function() {
+ if ( wp && wp.customize ) {
+ wp.customize.state( 'saved' ).set( false );
+ }
+ },
+ update: function( e, ui ) {
+ $( ui.item ).find( 'input, textarea, select' ).change();
+ }
+ } )
+ .disableSelection();
+ };
+
+ // When widgets are updated, remove and re-add listeners
+ $( document ).on( 'widget-updated widget-added', function( e, widget ) {
+ widget = $( widget );
+
+ var id = widget.attr( 'id' ),
+ isJetpackSearch = ( id && ( -1 !== id.indexOf( 'jetpack-search-filters' ) ) );
+
+ if ( ! isJetpackSearch ) {
+ return;
+ }
+
+ // Intentionally not tracking widget additions and updates here as these events
+ // seem noisy in the customizer. We'll track those via PHP.
+
+ widget.off( 'change', '.filter-select' );
+ widget.off( 'click', '.jetpack-search-filters-widget__controls .delete' );
+ widget.off( 'change', '.jetpack-search-filters-widget__use-filters' );
+ widget.off( 'change', '.jetpack-search-filters-widget__search-box-enabled' );
+ widget.off( 'change', '.jetpack-search-filters-widget__sort-controls-enabled' );
+ widget.off( 'change', '.jetpack-search-filters-widget__sort-controls-enabled' );
+ widget.off( 'change', '.jetpack-search-filters-widget__post-type-selector' );
+ widget.off( 'change', '.jetpack-search-filters-widget__sort-order' );
+ widget.off( 'change', '.jetpack-search-filters-widget__taxonomy-select' );
+ widget.off( 'change', '.jetpack-search-filters-widget__date-histogram-select:first select' );
+ widget.off( 'change', '.jetpack-search-filters-widget__date-histogram-select:eq(1) select' );
+ widget.off( 'click', '.jetpack-search-filters-widget__post-types-select input[type="checkbox"]' );
+ widget.off( 'click', '.jetpack-search-filters-widget__add-filter');
+
+ setListeners( widget );
+ } );
+
+ /**
+ * This function will fire both a Tracks and MC stat.
+ *
+ * Tracks: Will be prefixed by 'jetpack_widget_search_' and use underscores.
+ * MC: Will not be prefixed, and will use dashes.
+ *
+ * Logic borrowed from `idc-notice.js`.
+ *
+ * @param eventName string
+ * @param extraProps object
+ */
+ function trackAndBumpMCStats( eventName, extraProps ) {
+ if ( 'undefined' === typeof extraProps || 'object' !== typeof extraProps ) {
+ extraProps = {};
+ }
+
+ if ( eventName && eventName.length && 'undefined' !== typeof analytics && analytics.tracks && analytics.mc ) {
+ // Format for Tracks
+ eventName = eventName.replace( /-/g, '_' );
+ eventName = eventName.indexOf( 'jetpack_widget_search_' ) !== 0 ? 'jetpack_widget_search_' + eventName : eventName;
+ analytics.tracks.recordEvent( eventName, extraProps );
+
+ // Now format for MC stats
+ eventName = eventName.replace( 'jetpack_widget_search_', '' );
+ eventName = eventName.replace( /_/g, '-' );
+ analytics.mc.bumpStat( 'jetpack-search-widget', eventName );
+ }
+ }
+} )( jQuery, jetpack_search_filter_admin );
diff --git a/plugins/jetpack/modules/widgets/search/js/search-widget.js b/plugins/jetpack/modules/widgets/search/js/search-widget.js
new file mode 100644
index 00000000..8d1c4775
--- /dev/null
+++ b/plugins/jetpack/modules/widgets/search/js/search-widget.js
@@ -0,0 +1,15 @@
+jQuery( document ).ready( function() {
+ var filter_list = jQuery( '.jetpack-search-filters-widget__filter-list' );
+
+ filter_list.on( 'click', 'a', function() {
+ var checkbox = jQuery( this ).siblings( 'input[type="checkbox"]' );
+ checkbox.prop( 'checked', ! checkbox.prop( 'checked' ) );
+ } );
+
+ filter_list.find( 'input[type="checkbox"]' ).prop( 'disabled', false ).css( 'cursor', 'inherit' ).on( 'click', function() {
+ var anchor = jQuery( this ).siblings( 'a' );
+ if ( anchor.length ) {
+ window.location.href = anchor.prop( 'href' );
+ }
+ } );
+} );
diff --git a/plugins/jetpack/modules/widgets/top-posts.php b/plugins/jetpack/modules/widgets/top-posts.php
index 0178d111..a4b6b157 100644
--- a/plugins/jetpack/modules/widgets/top-posts.php
+++ b/plugins/jetpack/modules/widgets/top-posts.php
@@ -336,6 +336,8 @@ class Jetpack_Top_Posts_Widget extends WP_Widget {
$post['post_id'],
array(
'fallback_to_avatars' => true,
+ 'width' => (int) $width,
+ 'height' => (int) $height,
'avatar_size' => (int) $get_image_options['avatar_size'],
)
);
diff --git a/plugins/jetpack/modules/widgets/twitter-timeline.php b/plugins/jetpack/modules/widgets/twitter-timeline.php
index 9b4283b0..72e9242f 100644
--- a/plugins/jetpack/modules/widgets/twitter-timeline.php
+++ b/plugins/jetpack/modules/widgets/twitter-timeline.php
@@ -63,7 +63,13 @@ class Jetpack_Twitter_Timeline_Widget extends WP_Widget {
public function admin_scripts( $hook ) {
// This is still 'widgets.php' when managing widgets via the Customizer.
if ( 'widgets.php' === $hook ) {
- wp_enqueue_script( 'twitter-timeline-admin', plugins_url( 'twitter-timeline-admin.js', __FILE__ ) );
+ wp_enqueue_script(
+ 'twitter-timeline-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/widgets/twitter-timeline-admin.min.js',
+ 'modules/widgets/twitter-timeline-admin.js'
+ )
+ );
}
}
diff --git a/plugins/jetpack/modules/woocommerce-analytics/classes/wp-woocommerce-analytics-universal.php b/plugins/jetpack/modules/woocommerce-analytics/classes/wp-woocommerce-analytics-universal.php
new file mode 100644
index 00000000..9745b91c
--- /dev/null
+++ b/plugins/jetpack/modules/woocommerce-analytics/classes/wp-woocommerce-analytics-universal.php
@@ -0,0 +1,328 @@
+<?php
+/**
+ * Jetpack_WooCommerce_Analytics_Universal
+ *
+ * @package Jetpack
+ * @author Automattic
+ */
+
+/**
+ * Bail if accessed directly
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Class Jetpack_WooCommerce_Analytics_Universal
+ * Filters and Actions added to Store pages to perform analytics
+ */
+class Jetpack_WooCommerce_Analytics_Universal {
+ /**
+ * Jetpack_WooCommerce_Analytics_Universal constructor.
+ */
+ public function __construct() {
+ // loading _wca
+ add_action( 'wp_head', array( $this, 'wp_head_top' ), 1 );
+
+ // loading s.js
+ add_action( 'wp_head', array( $this, 'wp_head_bottom' ), 999999 );
+
+ // single product page view
+ add_action( 'woocommerce_after_single_product', array( $this, 'product_detail' ) );
+
+ // add to cart on single product page
+ add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'add_to_cart' ) );
+
+ // add to carts from list views (search, store etc.)
+ add_action( 'wp_footer', array( $this, 'loop_add_to_cart' ) );
+
+
+ add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) );
+ add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) );
+ add_action( 'wcct_before_cart_widget', array( $this, 'remove_from_cart' ) );
+ add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 );
+
+ // cart checkout
+ add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) );
+
+ // order confirmed
+ add_action( 'woocommerce_thankyou', array( $this, 'order_process' ), 10, 1 );
+ add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart_via_quantity' ), 10, 1 );
+ }
+
+ /**
+ * Make _wca available to queue events
+ */
+ public function wp_head_top() {
+ if ( is_cart() || is_checkout() || is_checkout_pay_page() || is_order_received_page() || is_add_payment_method_page() ) {
+ $prevent_referrer_code = "<script>window._wca_prevent_referrer = true;</script>";
+ echo "$prevent_referrer_code\r\n";
+ }
+ $wca_code = "<script>window._wca = window._wca || [];</script>";
+ echo "$wca_code\r\n";
+ }
+
+
+ /**
+ * Place script to call s.js, Store Analytics
+ */
+ public function wp_head_bottom() {
+ $filename = 's-' . gmdate( 'YW' ) . '.js';
+ $async_code = "<script async src='https://stats.wp.com/" . $filename . "'></script>";
+ echo "$async_code\r\n";
+ }
+
+ /**
+ * On a product page, add a click event listener to "Add to Cart" button click
+ */
+ public function add_to_cart() {
+
+ if ( ! is_single() ) {
+ return;
+ }
+
+ $blogid = Jetpack::get_option( 'id' );
+ global $product;
+
+ wc_enqueue_js(
+ "jQuery( '" . esc_js( '.single_add_to_cart_button' ) . "' ).click( function() {
+ _wca.push( {
+ '_en': 'woocommerceanalytics_add_to_cart',
+ 'blog_id': " . esc_js( $blogid ) . ",
+ 'pi': '" . esc_js( $product->get_id() ) . "',
+ 'pn' : '" . esc_js( $product->get_title() ) . "',
+ 'pq': jQuery( 'input.qty' ).val() ? jQuery( 'input.qty' ).val() : '1',
+ 'ui': '" . esc_js( $this->get_user_id() ) . "',
+ } );
+ } );"
+ );
+ }
+
+ /**
+ * On product lists or other non-product pages, add an event listener to "Add to Cart" button click
+ */
+ public function loop_add_to_cart() {
+ $blogid = Jetpack::get_option( 'id' );
+ $selector = '.add_to_cart_button:not(.product_type_variable, .product_type_grouped)';
+
+ wc_enqueue_js(
+ "jQuery( '" . esc_js( $selector ) . "' ).click( function() {
+ var productID = jQuery( this ).data( 'product_id' );
+ var productDetails = {
+ 'id': productID,
+ 'quantity': jQuery( this ).data( 'quantity' ),
+ };
+ _wca.push( {
+ '_en': 'woocommerceanalytics_product_view',
+ 'blog_id': '" . esc_js( $blogid ) . "',
+ 'pi': productDetails.id,
+ 'ui': '" . esc_js( $this->get_user_id() ) . "',
+ } );
+ _wca.push( {
+ '_en': 'woocommerceanalytics_add_to_cart',
+ 'blog_id': " . esc_js( $blogid ) . ",
+ 'pi': productDetails.id,
+ 'pq': productDetails.quantity,
+ 'ui': '" . esc_js( $this->get_user_id() ) . "',
+ } );
+ } );"
+ );
+ }
+
+ /**
+ * On the cart page, add an event listener for removal of product click
+ */
+ public function remove_from_cart() {
+
+ // We listen at div.woocommerce because the cart 'form' contents get forcibly
+ // updated and subsequent removals from cart would then not have this click
+ // handler attached.
+ $blogid = Jetpack::get_option( 'id' );
+ wc_enqueue_js(
+ "jQuery( 'div.woocommerce' ).on( 'click', 'a.remove', function() {
+ var productID = jQuery( this ).data( 'product_id' );
+ var quantity = jQuery( this ).parent().parent().find( '.qty' ).val()
+ var productDetails = {
+ 'id': productID,
+ 'quantity': quantity ? quantity : '1',
+ };
+ _wca.push( {
+ '_en': 'woocommerceanalytics_remove_from_cart',
+ 'blog_id': '" . esc_js( $blogid ) . "',
+ 'pi': productDetails.id,
+ 'pq': productDetails.quantity,
+ 'ui': '" . esc_js( $this->get_user_id() ) . "',
+ } );
+ } );"
+ );
+ }
+
+ /**
+ * Adds the product ID to the remove product link (for use by remove_from_cart above) if not present
+ *
+ * @param string $url url.
+ * @param string $key key.
+ * @return mixed.
+ */
+ public function remove_from_cart_attributes( $url, $key ) {
+ if ( false !== strpos( $url, 'data-product_id' ) ) {
+ return $url;
+ }
+
+ $item = WC()->cart->get_cart_item( $key );
+ $product = $item['data'];
+
+ $new_attributes = sprintf(
+ 'href="%s" data-product_id="%s" data-product_sku="%s"',
+ esc_attr( $url ),
+ esc_attr( $product->get_id() ),
+ esc_attr( $product->get_sku() )
+ );
+ $url = str_replace( 'href=', $new_attributes );
+ return $url;
+ }
+
+ /**
+ * Gather relevant product information
+ *
+ * @param array $product product
+ * @return array
+ */
+ public function get_item_details( $product ) {
+ return array(
+ 'id' => $product->get_id(),
+ 'name' => $product->get_title(),
+ 'category' => Jetpack_WooCommerce_Analytics_Utils::get_product_categories_concatenated( $product ),
+ 'price' => $product->get_price(),
+ );
+ }
+
+ /**
+ * Track a product page view
+ */
+ public function product_detail() {
+
+ global $product;
+ $blogid = Jetpack::get_option( 'id' );
+
+ $item_details = $this->get_item_details( $product );
+
+ wc_enqueue_js(
+ "_wca.push( {
+ '_en': 'woocommerceanalytics_product_view',
+ 'blog_id': '" . esc_js( $blogid ) . "',
+ 'pi': '" . esc_js( $item_details['id'] ) . "',
+ 'pn': '" . esc_js( $item_details['name'] ) . "',
+ 'pc': '" . esc_js( $item_details['category'] ) . "',
+ 'pp': '" . esc_js( $item_details['price'] ) . "',
+ 'ui': '" . esc_js( $this->get_user_id() ) . "',
+ } );"
+ );
+ }
+
+ /**
+ * On the Checkout page, trigger an event for each product in the cart
+ */
+ public function checkout_process() {
+
+ $universal_commands = array();
+ $cart = WC()->cart->get_cart();
+ $blogid = Jetpack::get_option( 'id' );
+
+ foreach ( $cart as $cart_item_key => $cart_item ) {
+ /**
+ * This filter is already documented in woocommerce/templates/cart/cart.php
+ */
+ $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
+
+ $item_details = $this->get_item_details( $product );
+
+ $universal_commands[] = "_wca.push( {
+ '_en': 'woocommerceanalytics_product_checkout',
+ 'blog_id': '" . esc_js( $blogid ) . "',
+ 'pi': '" . esc_js( $item_details['id'] ) . "',
+ 'pn': '" . esc_js( $item_details['name'] ) . "',
+ 'pc': '" . esc_js( $item_details['category'] ) . "',
+ 'pp': '" . esc_js( $item_details['price'] ) . "',
+ 'pq': '" . esc_js( $cart_item['quantity'] ) . "',
+ 'ui': '" . esc_js( $this->get_user_id() ) . "',
+ } );";
+ }
+
+ wc_enqueue_js( implode( "\r\n", $universal_commands ) );
+ }
+
+ /**
+ * After the checkout process, fire an event for each item in the order
+ *
+ * @param string $order_id Order Id.
+ */
+ public function order_process( $order_id ) {
+ $order = wc_get_order( $order_id );
+ $universal_commands = array();
+ $blogid = Jetpack::get_option( 'id' );
+
+ // loop through products in the order and queue a purchase event.
+ foreach ( $order->get_items() as $order_item_id => $order_item ) {
+ $product = $order->get_product_from_item( $order_item );
+
+ $item_details = $this->get_item_details( $product );
+
+ $universal_commands[] = "_wca.push( {
+ '_en': 'woocommerceanalytics_product_purchase',
+ 'blog_id': '" . esc_js( $blogid ) . "',
+ 'pi': '" . esc_js( $item_details['id'] ) . "',
+ 'pn': '" . esc_js( $item_details['name'] ) . "',
+ 'pc': '" . esc_js( $item_details['category'] ) . "',
+ 'pp': '" . esc_js( $item_details['price'] ) . "',
+ 'pq': '" . esc_js( $order_item->get_quantity() ) . "',
+ 'oi': '" . esc_js( $order->get_order_number() ) . "',
+ 'ui': '" . esc_js( $this->get_user_id() ) . "',
+ } );";
+ }
+
+ wc_enqueue_js( implode( "\r\n", $universal_commands ) );
+ }
+
+ /**
+ * Listen for clicks on the "Update Cart" button to know if an item has been removed by
+ * updating its quantity to zero
+ */
+ public function remove_from_cart_via_quantity() {
+ $blogid = Jetpack::get_option( 'id' );
+
+ wc_enqueue_js( "
+ jQuery( 'button[name=update_cart]' ).on( 'click', function() {
+ var cartItems = jQuery( '.cart_item' );
+ cartItems.each( function( item ) {
+ var qty = jQuery( this ).find( 'input.qty' );
+ if ( qty && qty.val() === '0' ) {
+ var productID = jQuery( this ).find( '.product-remove a' ).data( 'product_id' );
+ _wca.push( {
+ '_en': 'woocommerceanalytics_remove_from_cart',
+ 'blog_id': '" . esc_js( $blogid ) . "',
+ 'pi': productID,
+ 'ui': '" . esc_js( $this->get_user_id() ) . "',
+ } );
+ }
+ } );
+ } );
+ " );
+ }
+
+ /**
+ * Get the current user id
+ *
+ * @return int
+ */
+ public function get_user_id() {
+ if ( is_user_logged_in() ) {
+ $blogid = Jetpack::get_option( 'id' );
+ $userid = get_current_user_id();
+ return $blogid . ":" . $userid;
+ }
+ return 'null';
+ }
+
+}
diff --git a/plugins/jetpack/modules/woocommerce-analytics/classes/wp-woocommerce-analytics-utils.php b/plugins/jetpack/modules/woocommerce-analytics/classes/wp-woocommerce-analytics-utils.php
new file mode 100644
index 00000000..b27dc043
--- /dev/null
+++ b/plugins/jetpack/modules/woocommerce-analytics/classes/wp-woocommerce-analytics-utils.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Jetpack_WooCommerce_Analytics_Options provides a single interface to module options
+ *
+ * @package Jetpack
+ * @author Automattic
+ */
+
+/**
+ * Bail if accessed directly
+ */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Class Jetpack_WooCommerce_Analytics_Utils
+ * Utility function for WooCommerce Analytics
+ */
+class Jetpack_WooCommerce_Analytics_Utils {
+ /**
+ * Gets product categories or varation attributes as a formatted concatenated string
+ *
+ * @param array $product WC_Product.
+ * @return string
+ */
+ public static function get_product_categories_concatenated( $product ) {
+ if ( ! class_exists( 'WooCommerce' ) ) {
+ return '';
+ }
+
+ if ( ! $product ) {
+ return '';
+ }
+
+ $variation_data = $product->is_type( 'variation' ) ? wc_get_product_variation_attributes( $product->get_id() ) : '';
+ if ( is_array( $variation_data ) && ! empty( $variation_data ) ) {
+ $line = wc_get_formatted_variation( $variation_data, true );
+ } else {
+ $out = array();
+ $categories = get_the_terms( $product->get_id(), 'product_cat' );
+ if ( $categories ) {
+ foreach ( $categories as $category ) {
+ $out[] = $category->name;
+ }
+ }
+ $line = join( '/', $out );
+ }
+ return $line;
+ }
+}
diff --git a/plugins/jetpack/modules/woocommerce-analytics/wp-woocommerce-analytics.php b/plugins/jetpack/modules/woocommerce-analytics/wp-woocommerce-analytics.php
new file mode 100644
index 00000000..cc1c5ad0
--- /dev/null
+++ b/plugins/jetpack/modules/woocommerce-analytics/wp-woocommerce-analytics.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Jetpack_WooCommerce_Analytics is ported from the Jetpack_Google_Analytics code.
+ *
+ * @package Jetpack
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+require_once plugin_basename( 'classes/wp-woocommerce-analytics-utils.php' );
+require_once plugin_basename( 'classes/wp-woocommerce-analytics-universal.php' );
+
+/**
+ * Class Jetpack_WooCommerce_Analytics
+ * Instantiate WooCommerce Analytics
+ */
+class Jetpack_WooCommerce_Analytics {
+
+ /**
+ * Instance of this class
+ *
+ * @var Jetpack_WooCommerce_Analytics - Static property to hold our singleton instance
+ */
+ private static $instance = false;
+
+ /**
+ * Instance of the Universal functions
+ *
+ * @var Static property to hold concrete analytics impl that does the work (universal or legacy)
+ */
+ private static $analytics = false;
+
+ /**
+ * WooCommerce Analytics is only available to Jetpack connected WooCommerce stores with both plugins set to active
+ * and WooCommerce version 3.0 or higher
+ *
+ * @return bool
+ */
+ public static function shouldTrackStore() {
+ // Tracking only Site pages
+ if ( is_admin() ) {
+ return false;
+ }
+ // Don't track site admins
+ if ( is_user_logged_in() && in_array( 'administrator', wp_get_current_user()->roles ) ) {
+ return false;
+ }
+ // Make sure Jetpack is installed and active
+ if ( ! Jetpack::is_active() ) {
+ return false;
+ }
+ /**
+ * Make sure WooCommerce is installed and active
+ *
+ * This action is documented in https://docs.woocommerce.com/document/create-a-plugin
+ */
+ if ( ! in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
+ return false;
+ }
+
+ // Ensure the WooCommerce class exists and is a valid version
+ $minimum_woocommerce_active = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, '3.0', '>=' );
+ if ( ! $minimum_woocommerce_active ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * This is our constructor, which is private to force the use of get_instance()
+ *
+ * @return void
+ */
+ private function __construct() {
+ $analytics = new Jetpack_WooCommerce_Analytics_Universal();
+ }
+
+ /**
+ * Function to instantiate our class and make it a singleton
+ */
+ public static function get_instance() {
+ if ( ! Jetpack_WooCommerce_Analytics::shouldTrackStore() ) {
+ return;
+ }
+ if ( ! self::$instance ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+}
+
+global $jetpack_woocommerce_analytics;
+$jetpack_woocommerce_analytics = Jetpack_WooCommerce_Analytics::get_instance();
diff --git a/plugins/jetpack/modules/wordads/php/params.php b/plugins/jetpack/modules/wordads/php/params.php
index 9ec48911..dbce7145 100644
--- a/plugins/jetpack/modules/wordads/php/params.php
+++ b/plugins/jetpack/modules/wordads/php/params.php
@@ -13,7 +13,7 @@ class WordAds_Params {
'wordads_approved' => false,
'wordads_active' => false,
'wordads_house' => true,
- 'enable_header_ad' => false,
+ 'enable_header_ad' => true,
'wordads_second_belowpost' => true,
'wordads_display_front_page' => true,
'wordads_display_post' => true,
diff --git a/plugins/jetpack/modules/wordads/php/widgets.php b/plugins/jetpack/modules/wordads/php/widgets.php
index c7beccac..04fed064 100644
--- a/plugins/jetpack/modules/wordads/php/widgets.php
+++ b/plugins/jetpack/modules/wordads/php/widgets.php
@@ -8,6 +8,7 @@
class WordAds_Sidebar_Widget extends WP_Widget {
private static $allowed_tags = array( 'mrec', 'wideskyscraper' );
+ private static $num_widgets = 0;
function __construct() {
parent::__construct(
@@ -31,9 +32,14 @@ class WordAds_Sidebar_Widget extends WP_Widget {
$instance['unit'] = 'mrec';
}
+ self::$num_widgets++;
$about = __( 'Advertisements', 'jetpack' );
$width = WordAds::$ad_tag_ids[$instance['unit']]['width'];
$height = WordAds::$ad_tag_ids[$instance['unit']]['height'];
+ $unit_id = 1 == self::$num_widgets ? 3 : self::$num_widgets + 3; // 2nd belowpost is '4'
+ $section_id = 0 === $wordads->params->blog_id ?
+ WORDADS_API_TEST_ID :
+ $wordads->params->blog_id . $unit_id;
$snippet = '';
if ( $wordads->option( 'wordads_house', true ) ) {
@@ -46,13 +52,7 @@ class WordAds_Sidebar_Widget extends WP_Widget {
$snippet = $wordads->get_house_ad( $unit );
} else {
- $section_id = 0 === $wordads->params->blog_id ? WORDADS_API_TEST_ID : $wordads->params->blog_id . '3';
- $data_tags = ( $wordads->params->cloudflare ) ? ' data-cfasync="false"' : '';
- $snippet = <<<HTML
- <script$data_tags type='text/javascript'>
- (function(g){g.__ATA.initAd({sectionId:$section_id, width:$width, height:$height});})(window);
- </script>
-HTML;
+ $snippet = $wordads->get_ad_snippet( $section_id, $height, $width );
}
echo <<< HTML
@@ -108,10 +108,9 @@ HTML;
}
}
-add_action(
- 'widgets_init',
- create_function(
- '',
- 'return register_widget( "WordAds_Sidebar_Widget" );'
- )
-);
+function jetpack_wordads_widgets_init_callback() {
+ return register_widget( 'WordAds_Sidebar_Widget' );
+}
+
+add_action( 'widgets_init', 'jetpack_wordads_widgets_init_callback' );
+
diff --git a/plugins/jetpack/modules/wordads/wordads.php b/plugins/jetpack/modules/wordads/wordads.php
index 1f63d02f..dbad9102 100644
--- a/plugins/jetpack/modules/wordads/wordads.php
+++ b/plugins/jetpack/modules/wordads/wordads.php
@@ -113,10 +113,38 @@ class WordAds {
add_action( 'wp_head', array( $this, 'insert_head_meta' ), 20 );
add_action( 'wp_head', array( $this, 'insert_head_iponweb' ), 30 );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
- add_filter( 'the_content', array( $this, 'insert_ad' ) );
- add_filter( 'the_excerpt', array( $this, 'insert_ad' ) );
- if ( $this->option( 'enable_header_ad' ) ) {
+ /**
+ * Filters enabling ads in `the_content` filter
+ *
+ * @see https://jetpack.com/support/ads/
+ *
+ * @module wordads
+ *
+ * @since 5.8.0
+ *
+ * @param bool True to disable ads in `the_content`
+ */
+ if ( ! apply_filters( 'wordads_content_disable', false ) ) {
+ add_filter( 'the_content', array( $this, 'insert_ad' ) );
+ }
+
+ /**
+ * Filters enabling ads in `the_excerpt` filter
+ *
+ * @see https://jetpack.com/support/ads/
+ *
+ * @module wordads
+ *
+ * @since 5.8.0
+ *
+ * @param bool True to disable ads in `the_excerpt`
+ */
+ if ( ! apply_filters( 'wordads_excerpt_disable', false ) ) {
+ add_filter( 'the_excerpt', array( $this, 'insert_ad' ) );
+ }
+
+ if ( $this->option( 'enable_header_ad', true ) ) {
switch ( get_stylesheet() ) {
case 'twentyseventeen':
case 'twentyfifteen':
@@ -188,8 +216,7 @@ HTML;
<link rel='dns-prefetch' href='//cdn.switchadhub.com' />
<link rel='dns-prefetch' href='//delivery.g.switchadhub.com' />
<link rel='dns-prefetch' href='//delivery.swid.switchadhub.com' />
- <script$data_tags type="text/javascript" src="//s.pubmine.com/head.js"></script>
- <script$data_tags type="text/javascript" src="//static.criteo.net/js/ld/publishertag.js"></script>
+ <script$data_tags async type="text/javascript" src="//s.pubmine.com/head.js"></script>
HTML;
}
@@ -298,32 +325,25 @@ HTML;
$width = 300;
$height = 250;
$second_belowpost = '';
+ $snippet = '';
if ( 'top' == $spot ) {
// mrec for mobile, leaderboard for desktop
$section_id = 0 === $this->params->blog_id ? WORDADS_API_TEST_ID : $this->params->blog_id . '2';
$width = $this->params->mobile_device ? 300 : 728;
$height = $this->params->mobile_device ? 250 : 90;
$blocker_unit = $this->params->mobile_device ? 'top_mrec' : 'top';
+ $snippet = $this->get_ad_snippet( $section_id, $height, $width, $blocker_unit );
} else if ( 'belowpost' == $spot ) {
$section_id = 0 === $this->params->blog_id ? WORDADS_API_TEST_ID : $this->params->blog_id . '1';
$width = 300;
$height = 250;
+
+ $snippet = $this->get_ad_snippet( $section_id, $height, $width, 'mrec', 'float:left;margin-right:5px;margin-top:0px;' );
if ( $this->option( 'wordads_second_belowpost', true ) ) {
$section_id2 = 0 === $this->params->blog_id ? WORDADS_API_TEST_ID2 : $this->params->blog_id . '4';
- $second_belowpost =
- "g.__ATA.initAd({collapseEmpty:'after', sectionId:$section_id2, width:$width, height:$height});";
+ $snippet .= $this->get_ad_snippet( $section_id2, $height, $width, 'mrec2', 'float:left;margin-top:0px;' );
}
}
-
- $data_tags = ( $this->params->cloudflare ) ? ' data-cfasync="false"' : '';
- $snippet = <<<HTML
- <script$data_tags id='s$section_id' type='text/javascript'>
- (function(g){if('undefined'!=typeof g.__ATA){
- g.__ATA.initAd({collapseEmpty:'after', sectionId:$section_id, width:$width, height:$height});
- $second_belowpost
- }})(window);
- </script>
-HTML;
} else if ( 'house' == $type ) {
$leaderboard = 'top' == $spot && ! $this->params->mobile_device;
$snippet = $this->get_house_ad( $leaderboard ? 'leaderboard' : 'mrec' );
@@ -332,35 +352,50 @@ HTML;
}
}
- $ad_blocker_ad = 'iponweb' == $type ? $this->get_adblocker_ad( $blocker_unit ) : '';
- $second_belowpost_css = '';
- $double_mrec = '';
- if ( 'belowpost' == $spot && $this->option( 'wordads_second_belowpost', true ) ) {
- if ( 'iponweb' == $type ) {
- $ad_blocker_ad .= $this->get_adblocker_ad( 'mrec2' );
- }
-
- $double_mrec = 'wpmrec2x';
- $second_belowpost_css = <<<HTML
- <style type="text/css">
- div.wpmrec2x{max-width:610px;}
- div.wpmrec2x div.u > div{float:left;margin-right:10px;}
- div.wpmrec2x div.u > div:nth-child(3n){margin-right:0px;}
- </style>
-HTML;
- }
-
$header = 'top' == $spot ? 'wpcnt-header' : '';
$about = __( 'Advertisements', 'jetpack' );
return <<<HTML
- $second_belowpost_css
- <div class="wpcnt $header $double_mrec">
+ <div class="wpcnt $header">
<div class="wpa">
<span class="wpa-about">$about</span>
<div class="u $spot">
$snippet
</div>
- $ad_blocker_ad
+ </div>
+ </div>
+HTML;
+ }
+
+
+ /**
+ * Returns the snippet to be inserted into the ad unit
+ * @param int $section_id
+ * @param int $height
+ * @param int $width
+ * @param string $css
+ * @return string
+ *
+ * @since 5.7
+ */
+ function get_ad_snippet( $section_id, $height, $width, $adblock_unit = 'mrec', $css = '' ) {
+ $this->ads[] = array( 'id' => $section_id, 'width' => $width, 'height' => $height );
+ $data_tags = $this->params->cloudflare ? ' data-cfasync="false"' : '';
+ $adblock_ad = $this->get_adblocker_ad( $adblock_unit );
+
+ return <<<HTML
+ <div style="padding-bottom:15px;width:{$width}px;height:{$height}px;$css">
+ <div id="atatags-{$section_id}">
+ <script$data_tags type="text/javascript">
+ __ATA.cmd.push(function() {
+ __ATA.initSlot('atatags-{$section_id}', {
+ collapseEmpty: 'before',
+ sectionId: '{$section_id}',
+ width: {$width},
+ height: {$height}
+ });
+ });
+ </script>
+ $adblock_ad
</div>
</div>
HTML;
@@ -373,6 +408,7 @@ HTML;
* @since 5.3
*/
public function get_adblocker_ad( $unit = 'mrec' ) {
+ $data_tags = $this->params->cloudflare ? ' data-cfasync="false"' : '';
$criteo_id = mt_rand();
$height = 250;
$width = 300;
@@ -392,18 +428,9 @@ HTML;
}
return <<<HTML
- <div id="crt-$criteo_id" style="width:{$width}px;height:{$height}px;"></div>
- <script type="text/javascript">
- var o = document.getElementById('crt-$criteo_id');
- if ("undefined"!=typeof Criteo) {
- var p = o.parentNode;
- p.style.setProperty('display', 'inline-block', 'important');
- o.style.setProperty('display', 'block', 'important');
- Criteo.DisplayAcceptableAdIfAdblocked({zoneid:$zone_id,containerid:"crt-$criteo_id",collapseContainerIfNotAdblocked:true,"callifnotadblocked": function () {var o = document.getElementById('crt-$criteo_id'); o.style.setProperty('display','none','important');o.style.setProperty('visbility','hidden','important'); } });
- } else {
- o.style.setProperty('display', 'none', 'important');
- o.style.setProperty('visibility', 'hidden', 'important');
- }
+ <div id="crt-$criteo_id" style="width:{$width}px;height:{$height}px;display:none !important;"></div>
+ <script$data_tags type="text/javascript">
+ (function(){var c=function(){var a=document.getElementById("crt-{$criteo_id}");window.Criteo?(a.parentNode.style.setProperty("display","inline-block","important"),a.style.setProperty("display","block","important"),window.Criteo.DisplayAcceptableAdIfAdblocked({zoneid:{$zone_id},containerid:"crt-{$criteo_id}",collapseContainerIfNotAdblocked:!0,callifnotadblocked:function(){a.style.setProperty("display","none","important");a.style.setProperty("visbility","hidden","important")}})):(a.style.setProperty("display","none","important"),a.style.setProperty("visibility","hidden","important"))};if(window.Criteo)c();else{if(!__ATA.criteo.script){var b=document.createElement("script");b.src="//static.criteo.net/js/ld/publishertag.js";b.onload=function(){for(var a=0;a<__ATA.criteo.cmd.length;a++){var b=__ATA.criteo.cmd[a];"function"===typeof b&&b()}};(document.head||document.getElementsByTagName("head")[0]).appendChild(b);__ATA.criteo.script=b}__ATA.criteo.cmd.push(c)}})();
</script>
HTML;
}