Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .wp-env.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"wp-content/plugins/fake-log-messages.php": "./tests/e2e/src/wordpress-files/test-plugins/fake-log-messages.php",
"wp-content/plugins/fake-new-activation.php": "./tests/e2e/src/wordpress-files/test-plugins/fake-new-activation.php",
"wp-content/plugins/filter-instant-results-category-terms.php": "./tests/e2e/src/wordpress-files/test-plugins/filter-instant-results-category-terms.php",
"wp-content/plugins/filter-instant-results-excluded-post-types.php": "./tests/e2e/src/wordpress-files/test-plugins/filter-instant-results-excluded-post-types.php",
"wp-content/plugins/filter-instant-results-excluded-term-ids.php": "./tests/e2e/src/wordpress-files/test-plugins/filter-instant-results-excluded-term-ids.php",
"wp-content/plugins/filter-instant-results-per-page.php": "./tests/e2e/src/wordpress-files/test-plugins/filter-instant-results-per-page.php",
"wp-content/plugins/filter-instant-results-args-schema.php": "./tests/e2e/src/wordpress-files/test-plugins/filter-instant-results-args-schema.php",
"wp-content/plugins/filter-autosuggest-navigate-callback.php": "./tests/e2e/src/wordpress-files/test-plugins/filter-autosuggest-navigate-callback.php",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies.
*/
import { useApiSearch } from '../../../api-search';
import { postTypeLabels } from '../../config';
import { excludedPostTypes, postTypeLabels } from '../../config';
import CheckboxList from '../common/checkbox-list';
import Panel from '../common/panel';
import { ActiveConstraint } from '../tools/active-constraints';
Expand Down Expand Up @@ -44,6 +44,10 @@ export default ({ defaultIsOpen, label }) => {
return options;
}

if (excludedPostTypes?.includes(key)) {
return options;
}

options.push({
checked: selectedPostTypes.includes(key),
count: doc_count,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { applyFilters } from '@wordpress/hooks';
* Internal dependencies.
*/
import { useApiSearch } from '../../../api-search';
import { facets, postTypeLabels } from '../../config';
import { excludedTermIds, facets, postTypeLabels } from '../../config';
import CheckboxList from '../common/checkbox-list';
import Panel from '../common/panel';
import { ActiveConstraint } from '../tools/active-constraints';
Expand Down Expand Up @@ -64,6 +64,10 @@ export default ({ defaultIsOpen, label, postTypes, name }) => {
(options, { doc_count, key }) => {
const { name: label, parent, term_id, term_order } = JSON.parse(key);

if (excludedTermIds?.includes(term_id)) {
return options;
}

options.push({
checked: selectedTerms.includes(term_id),
count: doc_count,
Expand Down
4 changes: 4 additions & 0 deletions assets/js/instant-results/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const {
apiHost,
argsSchema,
currencyCode,
excludedPostTypes,
excludedTermIds,
facets,
isWooCommerce,
locale,
Expand Down Expand Up @@ -71,6 +73,8 @@ export {
apiHost,
argsSchema,
currencyCode,
excludedPostTypes,
excludedTermIds,
facets,
isWooCommerce,
isNumberedPagination,
Expand Down
54 changes: 53 additions & 1 deletion includes/classes/Feature/InstantResults/InstantResults.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ public function enqueue_frontend_assets() {
'apiHost' => ( 0 !== strpos( $api_endpoint, 'http' ) ) ? esc_url_raw( $this->host ) : '',
'argsSchema' => $this->get_args_schema(),
'currencyCode' => $this->is_woocommerce ? get_woocommerce_currency() : false,
'excludedPostTypes' => $this->get_excluded_post_types(),
'excludedTermIds' => $this->get_excluded_term_ids(),
'facets' => $this->get_facets_for_frontend(),
'highlightTag' => $this->settings['highlight_tag'],
'isWooCommerce' => $this->is_woocommerce,
Expand Down Expand Up @@ -278,7 +280,10 @@ public function get_template_endpoint(): string {
* @return string The search template as JSON.
*/
public function get_search_template(): string {
$post_types = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();
$post_types = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();
$excluded_post_types = $this->get_excluded_post_types();
$post_types = array_values( array_diff( $post_types, $excluded_post_types ) );

$post_statuses = get_post_stati(
[
'public' => true,
Expand Down Expand Up @@ -584,6 +589,53 @@ public function get_post_type_labels() {
return $labels;
}

/**
* Get post type slugs to exclude from the Instant Results post type filter.
*
* @since 5.4.0
* @return array Array of post type slugs to exclude.
*/
public function get_excluded_post_types() {
/**
* Filter post type slugs to exclude from Instant Results.
*
* Excluded post types are hidden from the Post Type facet and
* removed from the search template so their content does not
* appear in results. The search template is generated and
* uploaded to Elasticsearch when Instant Results settings are
* saved, so after changing this filter, re-save the Instant
* Results or Weighting settings to regenerate the template.
*
* @hook ep_instant_results_excluded_post_types
* @since 5.4.0
* @param {string[]} $excluded Post type slugs to exclude (e.g., ['page']).
* @return {string[]} Filtered exclusions.
*/
return apply_filters( 'ep_instant_results_excluded_post_types', [] );
}

/**
* Get term IDs to exclude from all Instant Results taxonomy filters.
*
* @since 5.4.0
* @return int[] Term IDs to exclude.
*/
public function get_excluded_term_ids() {
/**
* Filter term IDs to exclude from all Instant Results taxonomy filters.
*
* Excluded terms are hidden from every taxonomy facet
* (Category, Tag, etc.) but posts associated with those
* terms still appear in search results.
*
* @hook ep_instant_results_excluded_term_ids
* @since 5.4.0
* @param {int[]} $excluded Term IDs to exclude (e.g., [1, 120]).
* @return {int[]} Filtered exclusions.
*/
return apply_filters( 'ep_instant_results_excluded_term_ids', [] );
}

/**
* Get available facets.
*
Expand Down
85 changes: 85 additions & 0 deletions tests/e2e/src/specs/instant-results.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,91 @@ test.describe('Instant Results Feature', { tag: '@group1' }, () => {
'wpCli',
);
});

test('Is possible to exclude post types via ep_instant_results_excluded_post_types', async ({
loggedInPage,
}) => {
/**
* Activate test plugin and regenerate the search template so the
* excluded post type is dropped from the template.
*/
await maybeEnableFeature('instant-results');
await setCustomPostTypes();
await activatePlugin(
loggedInPage,
'filter-instant-results-excluded-post-types',
'wpCli',
);
await wpCli('elasticpress put-search-template', true);

/**
* Perform a search.
*/
const responsePromise = instantResultRequestPromise(loggedInPage, 'search=post');
await loggedInPage.goto('/');
await searchFor(loggedInPage, 'post');
await responsePromise;

/**
* The excluded post type should not appear in the Post Type facet.
*/
await expect(loggedInPage.locator('#ep-search-post-type-page')).toHaveCount(0);

await deactivatePlugin(
loggedInPage,
'filter-instant-results-excluded-post-types',
'wpCli',
);
await wpCli('elasticpress put-search-template', true);
});

test('Is possible to exclude term IDs via ep_instant_results_excluded_term_ids', async ({
loggedInPage,
}) => {
/**
* Activate test plugin.
*/
await maybeEnableFeature('instant-results');
await setCustomPostTypes();
await activatePlugin(
loggedInPage,
'filter-instant-results-excluded-term-ids',
'wpCli',
);

await goToAdminPage(loggedInPage, 'admin.php?page=elasticpress');
const apiResponsePromise = loggedInPage.waitForResponse(
'**/wp-json/elasticpress/v1/features*',
);

await loggedInPage.getByRole('button', { name: 'Live Search' }).click();
await loggedInPage.getByRole('button', { name: 'Instant Results' }).click();
await addInstantResultFilter(loggedInPage, '(category)');
await loggedInPage.getByRole('button', { name: 'Save changes' }).click();

await apiResponsePromise;

/**
* Perform a search.
*/
const responsePromise = instantResultRequestPromise(loggedInPage, 'search=block');
await loggedInPage.goto('/');
await searchFor(loggedInPage, 'block');
await responsePromise;

/**
* The excluded term should not appear in the category facet.
*/
await expect(
loggedInPage.locator('[id^="ep-search-tax-category-"]', { hasText: 'Markup' }),
).toHaveCount(0);

await deactivatePlugin(
loggedInPage,
'filter-instant-results-excluded-term-ids',
'wpCli',
);
});
});

test('Is possible to filter the arguments schema', async ({ loggedInPage }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* Plugin Name: Filter Instant Results Excluded Post Types
* Description: Excludes the "page" post type from Instant Results for test purposes.
* Version: 1.0.0
* Author: 10up Inc.
* License: GPLv2 or later
*
* @package ElasticPress_Tests_E2e
*/

add_filter(
'ep_instant_results_excluded_post_types',
function ( $excluded ) {
$excluded[] = 'page';
return $excluded;
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* Plugin Name: Filter Instant Results Excluded Term IDs
* Description: Excludes the "markup" category term from Instant Results for test purposes.
* Version: 1.0.0
* Author: 10up Inc.
* License: GPLv2 or later
*
* @package ElasticPress_Tests_E2e
*/

add_filter(
'ep_instant_results_excluded_term_ids',
function ( $excluded ) {
$term = get_term_by( 'slug', 'markup', 'category' );
if ( $term ) {
$excluded[] = $term->term_id;
}
return $excluded;
}
);
47 changes: 46 additions & 1 deletion tests/php/features/TestInstantResults.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,51 @@ function () {
$feature->after_update_feature( 'instant-results', [], [ 'active' => false ] );
}

/**
* Test that excluded post types are removed from the search template.
*
* @group instant-results
* @since 5.4.0
*/
public function test_search_template_excludes_post_types() {
$feature = \ElasticPress\Features::factory()->get_registered_feature( 'instant-results' );

$baseline = $feature->get_search_template();
$this->assertStringContainsString( '"page"', $baseline );

$post_types_filter = function () {
return [ 'page' ];
};
add_filter( 'ep_instant_results_excluded_post_types', $post_types_filter );

$template = $feature->get_search_template();

remove_filter( 'ep_instant_results_excluded_post_types', $post_types_filter );

$this->assertStringNotContainsString( '"page"', $template );
}

/**
* Test the get_excluded_term_ids method.
*
* @group instant-results
* @since 5.4.0
*/
public function test_get_excluded_term_ids() {
$feature = \ElasticPress\Features::factory()->get_registered_feature( 'instant-results' );

$term_ids_filter = function () {
return [ 1, 42 ];
};
add_filter( 'ep_instant_results_excluded_term_ids', $term_ids_filter );

$excluded = $feature->get_excluded_term_ids();

remove_filter( 'ep_instant_results_excluded_term_ids', $term_ids_filter );

$this->assertSame( [ 1, 42 ], $excluded );
}

/**
* Ensure invalid taxonomy values returned from ep_facet_include_taxonomies
* are skipped and logged via _doing_it_wrong().
Expand Down Expand Up @@ -266,7 +311,7 @@ public function test_invalid_taxonomy_from_facet_filter_triggers_doing_it_wrong_
$calls[0]['function']
);

$message = html_entity_decode( $calls[0]['message'] );
$message = html_entity_decode( $calls[0]['message'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );

$this->assertStringContainsString( 'not-a-real-taxonomy', $message );
$this->assertStringContainsString( 'Invalid taxonomy', $message );
Expand Down
Loading