Skip to content
Merged
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
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- Add support for custom taxonomies filtering ([#42](https://github.com/studiometa/wp-toolkit/issues/42), [#43](https://github.com/studiometa/wp-toolkit/pull/43), [ae541b5](https://github.com/studiometa/wp-toolkit/commit/ae541b5))

## v2.2.2 - 2024.04.19

### Fixed
Expand Down Expand Up @@ -49,9 +53,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Add an `EmailManager` to configure `PHPMailer` via environment variables ([#22](https://github.com/studiometa/wp-toolkit/pull/22))
- Add `enqueue_script($handle, $path)` and `enqueue_style($handle, $path)` method to the `AssetsManager` class ([#23](https://github.com/studiometa/wp-toolkit/pull/23))
- Add a `Plugin::disable` method to the `Plugin` helper class ([#26](https://github.com/studiometa/wp-toolkit/pull/26))
- Add a `request` helper function ([#26](https://github.com/studiometa/wp-toolkit/pull/26))
- Add a `request` helper function ([#26](https://github.com/studiometa/wp-toolkit/pull/26))
- Add a `Request` helper class ([#26](https://github.com/studiometa/wp-toolkit/pull/26))
- Add a `env` helper function ([#26](https://github.com/studiometa/wp-toolkit/pull/26))
- Add a `env` helper function ([#26](https://github.com/studiometa/wp-toolkit/pull/26))
- Add a `Env` helper class ([#26](https://github.com/studiometa/wp-toolkit/pull/26))

### Changed
Expand Down
100 changes: 98 additions & 2 deletions src/Managers/FacetsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
namespace Studiometa\WPToolkit\Managers;

use WP_Query;
use Timber\Timber;
use Twig\Environment;
use Twig\TwigFunction;
use Studiometa\WPToolkit\Managers\ManagerInterface;
Expand Down Expand Up @@ -63,9 +62,106 @@ public function add_facets_to_query(WP_Query &$query): void
return;
}

$tax_queries = [];
$regular_vars = [];

foreach ($this->facets as $query_var => $value) {
$query->query_vars[ $query_var ] = $value;
$taxonomy_data = $this->parse_taxonomy_query_var($query_var);

if ($taxonomy_data) {
$tax_queries[] = $this->build_tax_query($taxonomy_data, $value);
} else {
$regular_vars[$query_var] = $value;
}
}

// Apply regular query vars
foreach ($regular_vars as $query_var => $value) {
$query->query_vars[$query_var] = $value;
}

// Apply taxonomy queries
if (!empty($tax_queries)) {
$existing_tax_query = $query->get('tax_query', []);
if (!empty($existing_tax_query)) {
$tax_queries[] = $existing_tax_query;
}
$query->set('tax_query', $tax_queries);
}
}

/**
* Parse a query variable to determine if it's a taxonomy query.
*
* @param string $query_var The query variable name.
* @return array|null Array with taxonomy and operator data, or null if not a taxonomy query.
*/
private function parse_taxonomy_query_var(string $query_var): ?array
{
$taxonomies = get_taxonomies(['public' => true]);

// Direct taxonomy match
if (array_key_exists($query_var, $taxonomies)) {
return ['taxonomy' => $query_var, 'operator' => 'IN'];
}

// Check for suffixed taxonomy queries
$suffixes = ['__in', '__not_in', '__and', '__exists'];
foreach ($suffixes as $suffix) {
if (str_ends_with($query_var, $suffix)) {
$taxonomy = substr($query_var, 0, -strlen($suffix));
if (array_key_exists($taxonomy, $taxonomies)) {
$operator = match ($suffix) {
'__in' => 'IN',
'__not_in' => 'NOT IN',
'__and' => 'AND',
'__exists' => 'EXISTS',
};
return ['taxonomy' => $taxonomy, 'operator' => $operator];
}
}
}

return null;
}

/**
* Build a tax_query array for a given taxonomy and value.
*
* @param array $taxonomy_data Array with taxonomy and operator info.
* @param string|array $value The query value.
* @return array The tax_query array.
*/
private function build_tax_query(array $taxonomy_data, string|array $value): array
{
$tax_query = [
'taxonomy' => $taxonomy_data['taxonomy'],
'operator' => $taxonomy_data['operator'],
];

if ($taxonomy_data['operator'] === 'EXISTS') {
// EXISTS queries don't need terms/field
return $tax_query;
}

// Handle different value types
if (is_array($value)) {
$terms = $value;
} else {
$terms = array_map('trim', explode(',', $value));
}

// Determine if we're dealing with term IDs or slugs
$first_term = reset($terms);
if (is_numeric($first_term)) {
$tax_query['field'] = 'term_id';
$tax_query['terms'] = array_map('intval', $terms);
} else {
$tax_query['field'] = 'slug';
$tax_query['terms'] = $terms;
}

return $tax_query;
}

/**
Expand Down
159 changes: 159 additions & 0 deletions tests/Managers/FacetsManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,163 @@ public function test_it_should_add_a_twig_helper_function()
$this->assertTrue($function instanceof TwigFunction);
$this->assertTrue($function->getCallable()('cat') === 2);
}

public function test_it_should_handle_taxonomy_queries_with_slugs()
{
// Register a test taxonomy
register_taxonomy('test_taxonomy', 'post', ['public' => true]);

request()->query->set('facets', ['test_taxonomy' => 'slug1,slug2']);
$manager = new FacetsManager();
$manager->run();

$this->go_to('/?facets[test_taxonomy]=slug1,slug2');
global $wp_query;

$tax_query = $wp_query->get('tax_query');
$this->assertIsArray($tax_query);
$this->assertCount(1, $tax_query);
$this->assertEquals('test_taxonomy', $tax_query[0]['taxonomy']);
$this->assertEquals('IN', $tax_query[0]['operator']);
$this->assertEquals('slug', $tax_query[0]['field']);
$this->assertEquals(['slug1', 'slug2'], $tax_query[0]['terms']);
}

public function test_it_should_handle_taxonomy_queries_with_ids()
{
register_taxonomy('test_taxonomy', 'post', ['public' => true]);

request()->query->set('facets', ['test_taxonomy' => '1,2,3']);
$manager = new FacetsManager();
$manager->run();

$this->go_to('/?facets[test_taxonomy]=1,2,3');
global $wp_query;

$tax_query = $wp_query->get('tax_query');
$this->assertEquals('term_id', $tax_query[0]['field']);
$this->assertEquals([1, 2, 3], $tax_query[0]['terms']);
}

public function test_it_should_handle_taxonomy_not_in_queries()
{
register_taxonomy('test_taxonomy', 'post', ['public' => true]);

request()->query->set('facets', ['test_taxonomy__not_in' => 'excluded-slug']);
$manager = new FacetsManager();
$manager->run();

$this->go_to('/?facets[test_taxonomy__not_in]=excluded-slug');
global $wp_query;

$tax_query = $wp_query->get('tax_query');
$this->assertEquals('test_taxonomy', $tax_query[0]['taxonomy']);
$this->assertEquals('NOT IN', $tax_query[0]['operator']);
$this->assertEquals(['excluded-slug'], $tax_query[0]['terms']);
}

public function test_it_should_handle_taxonomy_and_queries()
{
register_taxonomy('test_taxonomy', 'post', ['public' => true]);

request()->query->set('facets', ['test_taxonomy__and' => 'slug1,slug2']);
$manager = new FacetsManager();
$manager->run();

$this->go_to('/?facets[test_taxonomy__and]=slug1,slug2');
global $wp_query;

$tax_query = $wp_query->get('tax_query');
$this->assertEquals('AND', $tax_query[0]['operator']);
}

public function test_it_should_handle_taxonomy_exists_queries()
{
register_taxonomy('test_taxonomy', 'post', ['public' => true]);

request()->query->set('facets', ['test_taxonomy__exists' => '1']);
$manager = new FacetsManager();
$manager->run();

$this->go_to('/?facets[test_taxonomy__exists]=1');
global $wp_query;

$tax_query = $wp_query->get('tax_query');
$this->assertEquals('test_taxonomy', $tax_query[0]['taxonomy']);
$this->assertEquals('EXISTS', $tax_query[0]['operator']);
$this->assertArrayNotHasKey('terms', $tax_query[0]);
$this->assertArrayNotHasKey('field', $tax_query[0]);
}

public function test_it_should_combine_taxonomy_and_regular_queries()
{
register_taxonomy('test_taxonomy', 'post', ['public' => true]);

request()->query->set('facets', [
'test_taxonomy' => 'test-slug',
'meta_key' => 'custom_field',
'posts_per_page' => 5
]);
$manager = new FacetsManager();
$manager->run();

$this->go_to('/?facets[test_taxonomy]=test-slug&facets[meta_key]=custom_field&facets[posts_per_page]=5');
global $wp_query;

// Check taxonomy query
$tax_query = $wp_query->get('tax_query');
$this->assertIsArray($tax_query);
$this->assertEquals('test_taxonomy', $tax_query[0]['taxonomy']);

// Check regular query vars
$this->assertEquals('custom_field', $wp_query->query_vars['meta_key']);
$this->assertEquals(5, $wp_query->query_vars['posts_per_page']);
}

public function test_it_should_handle_existing_tax_query()
{
register_taxonomy('test_taxonomy', 'post', ['public' => true]);

request()->query->set('facets', ['test_taxonomy' => 'new-slug']);
$manager = new FacetsManager();
$manager->run();

// Set up existing tax_query
$existing_tax_query = [
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => ['existing-category'],
'operator' => 'IN'
]
];

$this->go_to('/?facets[test_taxonomy]=new-slug');
global $wp_query;
$wp_query->set('tax_query', $existing_tax_query);

// Trigger the facets query modification
$manager->add_facets_to_query($wp_query);

$tax_query = $wp_query->get('tax_query');
$this->assertCount(2, $tax_query);
$this->assertEquals('test_taxonomy', $tax_query[0]['taxonomy']);
$this->assertEquals('category', $tax_query[1][0]['taxonomy']);
}

public function test_it_should_handle_array_values_in_facets()
{
register_taxonomy('test_taxonomy', 'post', ['public' => true]);

request()->query->set('facets', ['test_taxonomy' => ['slug1', 'slug2']]);
$manager = new FacetsManager();
$manager->run();

$this->go_to('/?facets[test_taxonomy][]=slug1&facets[test_taxonomy][]=slug2');
global $wp_query;

$tax_query = $wp_query->get('tax_query');
$this->assertEquals(['slug1', 'slug2'], $tax_query[0]['terms']);
$this->assertEquals('slug', $tax_query[0]['field']);
}
}
Loading