PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-search/src/class-helper.php +385 -35 12.0.3 → 16.3 View file →
@@ -10,8 +10,12 @@
10 10 use Automattic\Jetpack\Status;
11 11 use GP_Locales;
12 12 use Jetpack; // TODO: Remove this once migrated.
13 13
14 +if ( ! defined( 'ABSPATH' ) ) {
15 + exit( 0 );
16 +}
17 +
14 18 /**
15 19 * Various helper functions for reuse throughout the Jetpack Search code.
16 20 */
17 21 class Helper {
@@ -176,12 +180,17 @@
176 180 if ( empty( $widget_filter['name'] ) ) {
177 181 $widget_filter['name'] = self::generate_widget_filter_name( $widget_filter );
178 182 }
179 183
180 - $type = ( isset( $widget_filter['type'] ) ) ? $widget_filter['type'] : '';
181 - $key = sprintf( '%s_%d', $type, count( $filters ) );
184 + $type = $widget_filter['type'] ?? '';
182 185
183 - $filters[ $key ] = $widget_filter;
186 + // If this is a product_attribute filter with no specific attribute, expand it to all global attributes.
187 + if ( 'product_attribute' === $type && empty( $widget_filter['attribute'] ) ) {
188 + $filters = self::expand_product_attribute_filters( $widget_filter, $filters );
189 + } else {
190 + $key = sprintf( '%s_%d', $type, count( $filters ) );
191 + $filters[ $key ] = $widget_filter;
192 + }
184 193 }
185 194 }
186 195
187 196 return $filters;
@@ -187,8 +196,47 @@
187 196 return $filters;
188 197 }
189 198
190 199 /**
200 + * Expands a product_attribute filter into individual filters for each attribute.
201 + *
202 + * @since 5.8.0
203 + *
204 + * @param array $widget_filter The filter configuration.
205 + * @param array $filters The existing filters array.
206 + * @return array The filters array with expanded product attribute filters.
207 + */
208 + private static function expand_product_attribute_filters( $widget_filter, $filters ) {
209 + if ( ! function_exists( 'wc_get_attribute_taxonomies' ) || ! function_exists( 'wc_attribute_taxonomy_name' ) ) {
210 + return $filters;
211 + }
212 +
213 + $product_attributes = wc_get_attribute_taxonomies();
214 + $included_attributes = isset( $widget_filter['included_attributes'] ) ? (array) $widget_filter['included_attributes'] : array();
215 +
216 + // If no attributes are explicitly included, show all attributes (backward compatibility).
217 + // Also optimize by treating "all selected" the same as "none selected" to avoid O(n²) in_array() checks.
218 + $show_all = empty( $included_attributes ) || count( $included_attributes ) === count( $product_attributes );
219 +
220 + foreach ( $product_attributes as $attribute ) {
221 + $attribute_name = wc_attribute_taxonomy_name( $attribute->attribute_name );
222 +
223 + if ( ! $show_all && ! in_array( $attribute_name, $included_attributes, true ) ) {
224 + continue;
225 + }
226 +
227 + $key = sprintf( 'product_attribute_%d', count( $filters ) );
228 + $expanded_filter = $widget_filter;
229 + $expanded_filter['attribute'] = $attribute_name;
230 + $expanded_filter['name'] = $attribute->attribute_label;
231 + unset( $expanded_filter['included_attributes'] );
232 + $filters[ $key ] = $expanded_filter;
233 + }
234 +
235 + return $filters;
236 + }
237 +
238 + /**
191 239 * Get the localized default label for a date filter.
192 240 *
193 241 * @since 5.8.0
194 242 *
@@ -277,8 +325,13 @@
277 325 } elseif ( isset( $tax->labels ) && isset( $tax->labels->name ) ) {
278 326 $name = $tax->labels->name;
279 327 }
280 328 break;
329 +
330 + case 'product_attribute':
331 + $name = _x( 'Product Attributes', 'label for filtering posts', 'jetpack-search-pkg' );
332 + break;
333 +
281 334 }
282 335
283 336 return $name;
284 337 }
@@ -363,9 +416,9 @@
363 416 $post_types_from_query = array();
364 417 } elseif ( is_array( $_GET['post_type'] ) ) {
365 418 $post_types_from_query = $_GET['post_type'];
366 419 } else {
367 - $post_types_from_query = (array) explode( ',', $_GET['post_type'] );
420 + $post_types_from_query = explode( ',', $_GET['post_type'] );
368 421 }
369 422 // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
370 423
371 424 $post_types_from_query = array_map( 'sanitize_key', $post_types_from_query );
@@ -437,8 +490,12 @@
437 490 break;
438 491 }
439 492
440 493 if ( 'filters' === $k ) {
494 + if ( ! is_countable( $new_instance['filters'] ) || ! is_countable( $old_instance['filters'] ) ) {
495 + continue;
496 + }
497 +
441 498 if ( count( $new_instance['filters'] ) !== count( $old_instance['filters'] ) ) {
442 499 $widget = $new_instance;
443 500 break;
444 501 }
@@ -454,9 +511,9 @@
454 511 }
455 512 }
456 513 }
457 514
458 - if ( empty( $action ) || empty( $widget ) ) {
515 + if ( empty( $widget ) ) {
459 516 return false;
460 517 }
461 518
462 519 return array(
@@ -681,8 +738,28 @@
681 738 return Options::site_has_vip_index() ? 1000 : 100;
682 739 }
683 740
684 741 /**
742 + * Resolves the effective results-per-page value for the Search 3.0 blocks.
743 + *
744 + * `$override` is the author-set `resultsPerPage` block attribute; `0` (or
745 + * any non-positive value) means "use the site's `posts_per_page` Reading
746 + * setting." A non-positive Reading setting (e.g. `-1`, "show all") falls
747 + * back to 10 rather than clamping down to a single result. Either way the
748 + * result is capped at `get_max_posts_per_page()`.
749 + *
750 + * @param int $override Author override, or 0 to use the site default.
751 + * @return int Clamped, always-positive results-per-page value.
752 + */
753 + public static function resolve_results_per_page( $override = 0 ) {
754 + $value = $override > 0 ? (int) $override : (int) get_option( 'posts_per_page' );
755 + if ( $value <= 0 ) {
756 + $value = 10;
757 + }
758 + return min( $value, self::get_max_posts_per_page() );
759 + }
760 +
761 + /**
685 762 * Returns the maximum offset for a search query.
686 763 *
687 764 * @since 5.8.0
688 765 *
@@ -749,9 +826,9 @@
749 826 * Sanitizes a checkbox value for writing to the database.
750 827 *
751 828 * @since 8.9.0
752 829 *
753 - * @param any $value from the customizer form.
830 + * @param mixed $value from the customizer form.
754 831 * @return string either '0' or '1'.
755 832 */
756 833 public static function sanitize_checkbox_value( $value ) {
757 834 return true === $value ? '1' : '0';
@@ -761,9 +838,9 @@
761 838 * Sanitizes a checkbox value for rendering the Customizer.
762 839 *
763 840 * @since 8.9.0
764 841 *
765 - * @param any $value from the database.
842 + * @param mixed $value from the database.
766 843 * @return boolean
767 844 */
768 845 public static function sanitize_checkbox_value_for_js( $value ) {
769 846 return '1' === $value;
@@ -835,9 +912,9 @@
835 912 )
836 913 );
837 914 $unexcluded_post_types = array_diff( $post_types, $excluded_post_types );
838 915 // NOTE: If all post types are being excluded, ignore the option value.
839 - if ( count( $unexcluded_post_types ) === 0 ) {
916 + if ( array() === $unexcluded_post_types ) {
840 917 $excluded_post_types = array();
841 918 }
842 919
843 920 $is_wpcom = static::is_wpcom();
@@ -844,44 +921,79 @@
844 921 $is_private_site = ( new Status() )->is_private_site();
845 922 $is_jetpack_photon_enabled = method_exists( 'Jetpack', 'is_module_active' ) && Jetpack::is_module_active( 'photon' );
846 923
847 924 $options = array(
848 - 'overlayOptions' => array(
849 - 'colorTheme' => get_option( $prefix . 'color_theme', 'light' ),
850 - 'enableInfScroll' => get_option( $prefix . 'inf_scroll', '1' ) === '1',
851 - 'enablePostDate' => get_option( $prefix . 'show_post_date', '1' ) === '1',
852 - 'enableSort' => get_option( $prefix . 'enable_sort', '1' ) === '1',
853 - 'highlightColor' => get_option( $prefix . 'highlight_color', '#FFC' ),
854 - 'overlayTrigger' => get_option( $prefix . 'overlay_trigger', Options::DEFAULT_OVERLAY_TRIGGER ),
855 - 'resultFormat' => get_option( $prefix . 'result_format', Options::RESULT_FORMAT_MINIMAL ),
856 - 'showPoweredBy' => ( new Plan() )->is_free_plan() || ( get_option( $prefix . 'show_powered_by', '1' ) === '1' ),
925 + 'overlayOptions' => array(
926 + 'colorTheme' => get_option( $prefix . 'color_theme', 'light' ),
927 + 'enableInfScroll' => get_option( $prefix . 'inf_scroll', '1' ) === '1',
928 + 'enableFilteringOpensOverlay' => get_option( $prefix . 'filtering_opens_overlay', '1' ) === '1',
929 + 'enablePostDate' => get_option( $prefix . 'show_post_date', '1' ) === '1',
930 + 'enableProductPrice' => get_option( $prefix . 'show_product_price', '1' ) === '1',
931 + 'enableSort' => get_option( $prefix . 'enable_sort', '1' ) === '1',
932 + 'highlightColor' => get_option( $prefix . 'highlight_color', '#FFC' ),
933 + 'overlayTrigger' => get_option( $prefix . 'overlay_trigger', Options::DEFAULT_OVERLAY_TRIGGER ),
934 + 'resultFormat' => get_option( $prefix . 'result_format', Options::RESULT_FORMAT_MINIMAL ),
935 + 'showPoweredBy' => ( new Plan() )->is_free_plan() || ( get_option( $prefix . 'show_powered_by', '1' ) === '1' ),
857 936
858 937 // These options require kicking off a new search.
859 - 'defaultSort' => get_option( $prefix . 'default_sort', 'relevance' ),
860 - 'excludedPostTypes' => $excluded_post_types,
938 + 'defaultSort' => get_option( $prefix . 'default_sort', 'relevance' ),
939 + 'excludedPostTypes' => $excluded_post_types,
940 +
941 + // Fallback image options
942 + 'fallbackImageUrl' => get_option( $prefix . 'fallback_image_url', '' ),
943 + 'enableFallbackImage' => get_option( $prefix . 'enable_fallback_image', '0' ) === '1',
861 944 ),
862 945
863 946 // core config.
864 - 'homeUrl' => home_url(),
865 - 'locale' => str_replace( '_', '-', self::is_valid_locale( get_locale() ) ? get_locale() : 'en_US' ),
866 - 'postsPerPage' => $posts_per_page,
867 - 'siteId' => self::get_wpcom_site_id(),
868 - 'postTypes' => $post_type_labels,
869 - 'webpackPublicPath' => plugins_url( '/build/instant-search/', __DIR__ ),
870 - 'isPhotonEnabled' => ( $is_wpcom || $is_jetpack_photon_enabled ) && ! $is_private_site,
871 - 'isFreePlan' => ( new Plan() )->is_free_plan(),
947 + 'homeUrl' => home_url(),
948 + 'locale' => str_replace( '_', '-', self::is_valid_locale( get_locale() ) ? get_locale() : 'en_US' ),
949 + 'postsPerPage' => $posts_per_page,
950 + 'siteId' => self::get_wpcom_site_id(),
951 + 'searchSuggestionsEnabled' => (bool) get_option( 'jetpack_search_suggestions_enabled', false ),
952 + 'postTypes' => $post_type_labels,
953 + 'webpackPublicPath' => plugins_url( '/build/instant-search/', __DIR__ ),
954 + 'isPhotonEnabled' => ( $is_wpcom || $is_jetpack_photon_enabled ) && ! $is_private_site,
955 + 'isFreePlan' => ( new Plan() )->is_free_plan(),
872 956
873 957 // config values related to private site support.
874 - 'apiRoot' => esc_url_raw( rest_url() ),
875 - 'apiNonce' => wp_create_nonce( 'wp_rest' ),
876 - 'isPrivateSite' => $is_private_site,
877 - 'isWpcom' => $is_wpcom,
958 + 'apiRoot' => esc_url_raw( rest_url() ),
959 + 'apiNonce' => wp_create_nonce( 'wp_rest' ),
960 + 'isPrivateSite' => $is_private_site,
961 + 'isWpcom' => $is_wpcom,
878 962
879 963 // widget info.
880 - 'hasOverlayWidgets' => count( $overlay_widget_ids ) > 0,
881 - 'widgets' => array_values( $widgets ),
882 - 'widgetsOutsideOverlay' => array_values( $widgets_outside_overlay ),
883 - 'hasNonSearchWidgets' => $has_non_search_widgets,
964 + 'hasOverlayWidgets' => is_countable( $overlay_widget_ids ) && count( $overlay_widget_ids ) > 0,
965 + 'widgets' => array_values( $widgets ),
966 + 'widgetsOutsideOverlay' => array_values( $widgets_outside_overlay ),
967 + 'hasNonSearchWidgets' => $has_non_search_widgets,
968 + /**
969 + * Whether to prevent tracking cookie reset. More information `pbmxuV-39H-p2`.
970 + *
971 + * @module search
972 + *
973 + * @since 0.41.0
974 + *
975 + * @param bool Prevent cookie reset for automattic sites as default value.
976 + */
977 + 'preventTrackingCookiesReset' => apply_filters( 'jetpack_instant_search_prevent_tracking_cookies_reset', function_exists( 'is_automattic' ) && is_automattic() ),
978 +
979 + /**
980 + * Whether to disable Tracks and TrainTracks analytics.
981 + *
982 + * This can be enabled via URL parameter (?disable_tracking=1) for testing,
983 + * or via the filter for permanent configuration. Useful for debugging issues
984 + * where tracking may interfere with search functionality, such as Safari's
985 + * advanced tracking protection.
986 + *
987 + * @module search
988 + *
989 + * @since 0.56.0
990 + *
991 + * @param bool $disable_tracking Whether to disable tracking. Default false.
992 + */
993 + 'disableTracking' => self::is_tracking_disabled() || apply_filters( 'jetpack_instant_search_disable_tracking', false ),
994 + 'aiAnswersEnabled' => AI_Answers::is_enabled(),
995 + 'aiMasterEnabled' => AI_Answers::is_master_enabled(),
884 996 );
885 997
886 998 /**
887 999 * Customize Instant Search Options.
@@ -895,8 +1007,224 @@
895 1007 return apply_filters( 'jetpack_instant_search_options', $options );
896 1008 }
897 1009
898 1010 /**
1011 + * Default highlight fields when `highlightFields` is unset in
1012 + * `jetpack_instant_search_options`. Matches instant search's JS default.
1013 + *
1014 + * @since 7.4.0
1015 + */
1016 + const DEFAULT_INSTANT_SEARCH_HIGHLIGHT_FIELDS = array( 'title', 'content', 'comments' );
1017 +
1018 + /**
1019 + * Fields added to API `fields` when searching additional blogs.
1020 + *
1021 + * @since 7.4.0
1022 + */
1023 + const MULTISITE_SEARCH_FIELD_NAMES = array( 'author', 'blog_name', 'blog_icon_url', 'blog_id' );
1024 +
1025 + /**
1026 + * Read Instant Search query-customization options from a filtered options array.
1027 + *
1028 + * @since 7.4.0
1029 + *
1030 + * @param array $options Raw `jetpack_instant_search_options` value.
1031 + * @return array Query options with keys:
1032 + * `highlightPhraseOnly`, `highlightFilterStopwords`, `highlightFields`,
1033 + * `additionalBlogIds`, `adminQueryFilter`, and `customResults`.
1034 + */
1035 + public static function parse_instant_search_query_options( array $options ): array {
1036 + $highlight_phrase_only = ! empty( $options['highlightPhraseOnly'] );
1037 +
1038 + // The v1.3 API rejects requests combining `highlight_filter_stopwords` with
1039 + // `highlight_phrase_only` (phrase-only wins server-side) — drop the stopword
1040 + // list so requests stay valid when a filter sets both.
1041 + $stopwords = array();
1042 + if ( ! $highlight_phrase_only && ! empty( $options['highlightFilterStopwords'] ) && is_array( $options['highlightFilterStopwords'] ) ) {
1043 + $stopwords = array_values(
1044 + array_filter(
1045 + $options['highlightFilterStopwords'],
1046 + static function ( $word ) {
1047 + return is_string( $word ) && '' !== $word;
1048 + }
1049 + )
1050 + );
1051 + }
1052 +
1053 + $highlight_fields = null;
1054 + if ( ! empty( $options['highlightFields'] ) && is_array( $options['highlightFields'] ) ) {
1055 + $highlight_fields = array_values(
1056 + array_filter(
1057 + $options['highlightFields'],
1058 + static function ( $field ) {
1059 + return is_string( $field ) && '' !== $field;
1060 + }
1061 + )
1062 + );
1063 + if ( array() === $highlight_fields ) {
1064 + $highlight_fields = null;
1065 + }
1066 + }
1067 +
1068 + $additional_blog_ids = array();
1069 + if ( ! empty( $options['additionalBlogIds'] ) && is_array( $options['additionalBlogIds'] ) ) {
1070 + $additional_blog_ids = array_values( $options['additionalBlogIds'] );
1071 + }
1072 +
1073 + $admin_query_filter = null;
1074 + if ( ! empty( $options['adminQueryFilter'] ) && is_array( $options['adminQueryFilter'] ) ) {
1075 + $admin_query_filter = $options['adminQueryFilter'];
1076 + }
1077 +
1078 + $custom_results = array();
1079 + if ( ! empty( $options['customResults'] ) && is_array( $options['customResults'] ) ) {
1080 + foreach ( $options['customResults'] as $rule ) {
1081 + if ( ! is_array( $rule ) ) {
1082 + continue;
1083 + }
1084 + $pattern = isset( $rule['pattern'] ) && is_string( $rule['pattern'] ) ? $rule['pattern'] : '';
1085 + $ids = isset( $rule['ids'] ) && is_array( $rule['ids'] ) ? array_values( $rule['ids'] ) : array();
1086 + if ( '' === $pattern || array() === $ids ) {
1087 + continue;
1088 + }
1089 + $custom_results[] = array(
1090 + 'pattern' => $pattern,
1091 + 'ids' => $ids,
1092 + );
1093 + }
1094 + }
1095 +
1096 + return array(
1097 + 'highlightPhraseOnly' => $highlight_phrase_only,
1098 + 'highlightFilterStopwords' => $stopwords,
1099 + 'highlightFields' => $highlight_fields,
1100 + 'additionalBlogIds' => $additional_blog_ids,
1101 + 'adminQueryFilter' => $admin_query_filter,
1102 + 'customResults' => $custom_results,
1103 + );
1104 + }
1105 +
1106 + /**
1107 + * Read Instant Search query-customization options via `jetpack_instant_search_options`.
1108 + *
1109 + * Passing an empty array into the filter matches `Filter_Static::read_raw_entries()`
1110 + * — callbacks only add keys.
1111 + *
1112 + * @since 7.4.0
1113 + *
1114 + * @return array Query options with keys:
1115 + * `highlightPhraseOnly`, `highlightFilterStopwords`, `highlightFields`,
1116 + * `additionalBlogIds`, `adminQueryFilter`, and `customResults`.
1117 + */
1118 + public static function get_instant_search_query_options(): array {
1119 + $options = apply_filters( 'jetpack_instant_search_options', array() );
1120 + if ( ! is_array( $options ) ) {
1121 + $options = array();
1122 + }
1123 +
1124 + return self::parse_instant_search_query_options( $options );
1125 + }
1126 +
1127 + /**
1128 + * Merge Instant Search query-customization options into v1.3 API args.
1129 + *
1130 + * Shared by Inline Search (Theme) and any server-side v1.3 callers.
1131 + *
1132 + * @since 7.4.0
1133 + *
1134 + * @param array $api_query_args API query arguments.
1135 + * @param array|null $options Filtered `jetpack_instant_search_options` value. Defaults to `generate_initial_javascript_state()`.
1136 + * @return array
1137 + */
1138 + public static function apply_instant_search_query_options_to_api_args( array $api_query_args, ?array $options = null ): array {
1139 + if ( null === $options ) {
1140 + $options = self::generate_initial_javascript_state();
1141 + }
1142 + if ( ! is_array( $options ) ) {
1143 + return $api_query_args;
1144 + }
1145 +
1146 + $query_options = self::parse_instant_search_query_options( $options );
1147 +
1148 + if ( null !== $query_options['adminQueryFilter'] ) {
1149 + $api_query_args['filter'] = array(
1150 + 'bool' => array(
1151 + 'filter' => $api_query_args['filter'] ?? array(),
1152 + 'must' => $query_options['adminQueryFilter'],
1153 + ),
1154 + );
1155 + }
1156 +
1157 + if ( $query_options['highlightPhraseOnly'] ) {
1158 + $api_query_args['highlight_phrase_only'] = true;
1159 + }
1160 +
1161 + if ( ! empty( $query_options['highlightFilterStopwords'] ) ) {
1162 + $api_query_args['highlight_filter_stopwords'] = $query_options['highlightFilterStopwords'];
1163 + }
1164 +
1165 + if ( null !== $query_options['highlightFields'] ) {
1166 + $api_query_args['highlight_fields'] = $query_options['highlightFields'];
1167 + $api_query_args['highlight'] = array(
1168 + 'fields' => $query_options['highlightFields'],
1169 + );
1170 + }
1171 +
1172 + if ( ! empty( $query_options['additionalBlogIds'] ) ) {
1173 + $api_query_args['additional_blog_ids'] = $query_options['additionalBlogIds'];
1174 + $fields = isset( $api_query_args['fields'] ) && is_array( $api_query_args['fields'] )
1175 + ? $api_query_args['fields']
1176 + : array();
1177 + $api_query_args['fields'] = array_values(
1178 + array_unique( array_merge( $fields, self::MULTISITE_SEARCH_FIELD_NAMES ) )
1179 + );
1180 + }
1181 +
1182 + $matched_custom_results = self::resolve_instant_search_custom_results(
1183 + (string) ( $api_query_args['query'] ?? '' ),
1184 + $query_options['customResults']
1185 + );
1186 + if ( null !== $matched_custom_results ) {
1187 + $api_query_args['custom_results'] = $matched_custom_results;
1188 + }
1189 +
1190 + return $api_query_args;
1191 + }
1192 +
1193 + /**
1194 + * Resolve `customResults` post IDs for a search query.
1195 + *
1196 + * Mirrors instant search's exact / `regex:` pattern matching.
1197 + *
1198 + * @since 7.4.0
1199 + *
1200 + * @param string $query Current search query.
1201 + * @param array $custom_results Normalized custom-results rules.
1202 + * @return array<int, int|string>|null
1203 + */
1204 + public static function resolve_instant_search_custom_results( string $query, array $custom_results ): ?array {
1205 + if ( array() === $custom_results ) {
1206 + return null;
1207 + }
1208 +
1209 + foreach ( $custom_results as $rule ) {
1210 + $pattern = $rule['pattern'];
1211 + $ids = $rule['ids'];
1212 + if ( 0 === strpos( $pattern, 'regex:' ) ) {
1213 + // Escape the delimiter so patterns containing `/` (e.g. `regex:docs/.*`) stay valid PCRE.
1214 + $regex = '/^' . str_replace( '/', '\/', substr( $pattern, strlen( 'regex:' ) ) ) . '$/';
1215 + if ( @preg_match( $regex, $query ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Invalid user regex should be skipped, not fatal.
1216 + return $ids;
1217 + }
1218 + } elseif ( $query === $pattern ) {
1219 + return $ids;
1220 + }
1221 + }
1222 +
1223 + return null;
1224 + }
1225 +
1226 + /**
899 1227 * Returns true if the site is a WordPress.com simple site, i.e. the code runs on WPCOM.
900 1228 */
901 1229 public static function is_wpcom() {
902 1230 return defined( 'IS_WPCOM' ) && constant( 'IS_WPCOM' );
@@ -966,6 +1294,28 @@
966 1294 public static function is_forced_new_pricing_202208() {
967 1295 $referrer = wp_get_referer();
968 1296 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
969 1297 return ( isset( $_GET['new_pricing_202208'] ) && $_GET['new_pricing_202208'] ) || $referrer && strpos( $referrer, 'new_pricing_202208=1' ) !== false;
1298 + }
1299 +
1300 + /**
1301 + * Returns true if tracking should be disabled via URL parameter, which is used for testing purposes.
1302 + *
1303 + * @since 0.56.0
1304 + *
1305 + * @return bool
1306 + */
1307 + public static function is_tracking_disabled() {
1308 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1309 + return isset( $_GET['disable_tracking'] ) && $_GET['disable_tracking'];
1310 + }
1311 +
1312 + /**
1313 + * Enqueue the WordPress.com Tracks library that drains `window._tkq` and
1314 + * sends the queued events. Shared by Instant Search and the Search blocks so
1315 + * the handle, src, and cache-busting version live in one place. Callers own
1316 + * the decision of whether to load it (e.g. the `is_tracking_disabled()` gate).
1317 + */
1318 + public static function enqueue_tracks_script() {
1319 + wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
970 1320 }
971 1321 }