From 94eb91fc342253ee16049dfbf4b1be43c7e37abe Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 11:01:52 +0200 Subject: [PATCH 01/14] :construction: Empty Term dimension --- src/Dimension/Term.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/Dimension/Term.php diff --git a/src/Dimension/Term.php b/src/Dimension/Term.php new file mode 100644 index 0000000..0a48b94 --- /dev/null +++ b/src/Dimension/Term.php @@ -0,0 +1,31 @@ +options = $options; + $this->wpdb = $wpdb; + } + + public function join($aliasCount = 0) + { + return ''; + } + + public function search($searchWord, $aliasCount = 0) + { + return '0=1'; + } +} From 244cb4fb1050824b045b74f8171911a7cd91e658 Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 11:07:54 +0200 Subject: [PATCH 02/14] :construction: Join term relationship table --- src/Dimension/Term.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Dimension/Term.php b/src/Dimension/Term.php index 0a48b94..246e2b4 100644 --- a/src/Dimension/Term.php +++ b/src/Dimension/Term.php @@ -7,6 +7,7 @@ final class Term implements Dimension { private $options; + private $tableAlias = 'searchTerm'; private $wpdb; public function __construct(wpdb $wpdb, array $options) @@ -21,7 +22,12 @@ public function __construct(wpdb $wpdb, array $options) public function join($aliasCount = 0) { - return ''; + $tableAlias = $this->tableAlias . $aliasCount; + + $sql = "INNER JOIN {$this->wpdb->term_relationships} AS {$tableAlias} "; + $sql .= "ON ({$this->wpdb->posts}.ID = {$tableAlias}.object_id)"; + + return $sql; } public function search($searchWord, $aliasCount = 0) From 3ecc764f1d4f12a03de21fc424acd367a8048ecf Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 14:14:18 +0200 Subject: [PATCH 03/14] :sparkles: Search by term --- src/Dimension/Term.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Dimension/Term.php b/src/Dimension/Term.php index 246e2b4..010c869 100644 --- a/src/Dimension/Term.php +++ b/src/Dimension/Term.php @@ -32,6 +32,27 @@ public function join($aliasCount = 0) public function search($searchWord, $aliasCount = 0) { - return '0=1'; + $tableAlias = $this->tableAlias . $aliasCount; + $searchWord = $this->wpdb->esc_like($searchWord); + + $termIds = array_map('absint', $this->termsFor($searchWord)); + + if (count($termIds) == 0) { + return; + } + + $termIds = implode(',', $termIds); + + return "{$tableAlias}.term_taxonomy_id IN({$termIds})"; + } + + private function termsFor(string $searchWord) + { + return $this->wpdb->get_col($this->wpdb->prepare("SELECT {$this->wpdb->term_taxonomy}.term_id + FROM {$this->wpdb->term_taxonomy} + INNER JOIN {$this->wpdb->terms} USING(term_id) + WHERE taxonomy = %s + AND {$this->wpdb->terms}.name LIKE %s + ", $this->options['taxonomy'], "%{$searchWord}%")); } } From db01641c2af28af4c349c2fd94f0dfc1653dff0f Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 14:14:36 +0200 Subject: [PATCH 04/14] :sparkles: Filter empty searches --- src/Hook/Posts.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Hook/Posts.php b/src/Hook/Posts.php index 45c3a81..68ccc6a 100644 --- a/src/Hook/Posts.php +++ b/src/Hook/Posts.php @@ -73,6 +73,8 @@ public function search($sql, WP_Query $query) $searches[] = $dimension->search($searchWord, $index); } + $searches = array_filter($searches); + $search = '(' . implode($or, $searches) . ')' . $or; $clause = preg_replace('/' . $or . '/', $or . $search, $clause, 1); From 24cb891a1c8715e316e6e03e1ec28b3d80aff6c1 Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 14:29:05 +0200 Subject: [PATCH 05/14] :white_check_mark: Test term constructor --- tests/Search/Dimension/TermTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/Search/Dimension/TermTest.php diff --git a/tests/Search/Dimension/TermTest.php b/tests/Search/Dimension/TermTest.php new file mode 100644 index 0000000..9fb08e0 --- /dev/null +++ b/tests/Search/Dimension/TermTest.php @@ -0,0 +1,25 @@ +wpdb = Mockery::mock('wpdb'); + } + + public function testKeyRequired() + { + $this->expectException(BadMethodCallException::class); + $meta = new Term($this->wpdb, []); + } +} From dad86b019e3f1d8bbdefb3874e6793488929742d Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 14:29:24 +0200 Subject: [PATCH 06/14] :white_check_mark: Test term join --- tests/Search/Dimension/TermTest.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/Search/Dimension/TermTest.php b/tests/Search/Dimension/TermTest.php index 9fb08e0..09bcdc1 100644 --- a/tests/Search/Dimension/TermTest.php +++ b/tests/Search/Dimension/TermTest.php @@ -22,4 +22,31 @@ public function testKeyRequired() $this->expectException(BadMethodCallException::class); $meta = new Term($this->wpdb, []); } + + public function testJoin() + { + $tableAliasCount = 2; + $tableAlias = $this->tableAlias . $tableAliasCount; + + $this->wpdb->posts = 'wp_posts'; + $this->wpdb->term_relationships = 'wp_term_relationships'; + + $expectation = "INNER JOIN {$this->wpdb->term_relationships} AS {$tableAlias} "; + $expectation .= "ON ({$this->wpdb->posts}.ID = {$tableAlias}.object_id)"; + + $term = $this->create('taxonomyName'); + + $result = $term->join($tableAliasCount); + + $this->assertEquals($expectation, $result); + } + + private function create($taxonomy) + { + $term = new Term($this->wpdb, [ + 'taxonomy' => $taxonomy, + ]); + + return $term; + } } From bdb1a892f7bf5723b6635993dca9e79c1ad2ba3d Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 15:06:55 +0200 Subject: [PATCH 07/14] :art: Whitespace --- src/Dimension/Term.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dimension/Term.php b/src/Dimension/Term.php index 010c869..9770870 100644 --- a/src/Dimension/Term.php +++ b/src/Dimension/Term.php @@ -43,7 +43,7 @@ public function search($searchWord, $aliasCount = 0) $termIds = implode(',', $termIds); - return "{$tableAlias}.term_taxonomy_id IN({$termIds})"; + return "{$tableAlias}.term_taxonomy_id IN ({$termIds})"; } private function termsFor(string $searchWord) From d49cfc383109054257ff1e550017113493532359 Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 15:08:58 +0200 Subject: [PATCH 08/14] :white_check_mark: Test term search --- tests/Search/Dimension/TermTest.php | 43 +++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/tests/Search/Dimension/TermTest.php b/tests/Search/Dimension/TermTest.php index 09bcdc1..18db320 100644 --- a/tests/Search/Dimension/TermTest.php +++ b/tests/Search/Dimension/TermTest.php @@ -4,6 +4,7 @@ use BadMethodCallException; use Mockery; use Trendwerk\Search\Dimension\Term; +use WP_Mock; final class TermTest extends TestCase { @@ -15,6 +16,10 @@ public function setUp() parent::setUp(); $this->wpdb = Mockery::mock('wpdb'); + $this->wpdb->posts = 'wp_posts'; + $this->wpdb->term_relationships = 'wp_term_relationships'; + $this->wpdb->term_taxonomy = 'wp_term_taxonomy'; + $this->wpdb->terms = 'wp_terms'; } public function testKeyRequired() @@ -28,9 +33,6 @@ public function testJoin() $tableAliasCount = 2; $tableAlias = $this->tableAlias . $tableAliasCount; - $this->wpdb->posts = 'wp_posts'; - $this->wpdb->term_relationships = 'wp_term_relationships'; - $expectation = "INNER JOIN {$this->wpdb->term_relationships} AS {$tableAlias} "; $expectation .= "ON ({$this->wpdb->posts}.ID = {$tableAlias}.object_id)"; @@ -41,6 +43,41 @@ public function testJoin() $this->assertEquals($expectation, $result); } + public function testSearch() + { + $this->search('Testterm', 'testTaxonomy'); + } + + private function search($searchWord, $taxonomy, $tableAliasCount = 0) + { + $tableAlias = $this->tableAlias . $tableAliasCount; + $termIds = [18, 12]; + $expectation = "{$tableAlias}.term_taxonomy_id IN (18,12)"; + + $term = $this->create($taxonomy); + + WP_Mock::wpPassthruFunction('absint', ['times' => 2]); + + $this->wpdb->shouldReceive('esc_like') + ->once() + ->with($searchWord) + ->andReturn($searchWord); + + $this->wpdb->shouldReceive('prepare') + ->once() + ->andReturnUsing(function ($sql, $taxonomy, $search) { + return sprintf($sql, $taxonomy, $search); + }); + + $this->wpdb->shouldReceive('get_col') + ->once() + ->andReturn($termIds); + + $result = $term->search($searchWord, $tableAliasCount); + + $this->assertEquals($expectation, $result); + } + private function create($taxonomy) { $term = new Term($this->wpdb, [ From 033eacee10acc2c710bbaa4c1e4d02f74d004a56 Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 15:11:54 +0200 Subject: [PATCH 09/14] :bug: Fix primitive typehint (PHP 5.6) --- src/Dimension/Term.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dimension/Term.php b/src/Dimension/Term.php index 9770870..7ebe987 100644 --- a/src/Dimension/Term.php +++ b/src/Dimension/Term.php @@ -46,7 +46,7 @@ public function search($searchWord, $aliasCount = 0) return "{$tableAlias}.term_taxonomy_id IN ({$termIds})"; } - private function termsFor(string $searchWord) + private function termsFor($searchWord) { return $this->wpdb->get_col($this->wpdb->prepare("SELECT {$this->wpdb->term_taxonomy}.term_id FROM {$this->wpdb->term_taxonomy} From 21483064eb797f9ee8ad6dbd2299584306a164fa Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 15:21:02 +0200 Subject: [PATCH 10/14] :white_check_mark: Test no terms found --- tests/Search/Dimension/TermTest.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/Search/Dimension/TermTest.php b/tests/Search/Dimension/TermTest.php index 18db320..fa27d23 100644 --- a/tests/Search/Dimension/TermTest.php +++ b/tests/Search/Dimension/TermTest.php @@ -47,16 +47,26 @@ public function testSearch() { $this->search('Testterm', 'testTaxonomy'); } + + public function testNoHit() + { + $this->search('Testterm', 'testTaxonomy', 2, []); + } - private function search($searchWord, $taxonomy, $tableAliasCount = 0) + private function search($searchWord, $taxonomy, $tableAliasCount = 0, $foundTermIds = [18, 12]) { $tableAlias = $this->tableAlias . $tableAliasCount; - $termIds = [18, 12]; - $expectation = "{$tableAlias}.term_taxonomy_id IN (18,12)"; + + if (count($foundTermIds) == 0) { + $expectation = ''; + } else { + $termIds = implode(',', $foundTermIds); + $expectation = "{$tableAlias}.term_taxonomy_id IN ({$termIds})"; + } $term = $this->create($taxonomy); - WP_Mock::wpPassthruFunction('absint', ['times' => 2]); + WP_Mock::wpPassthruFunction('absint', ['times' => count($foundTermIds)]); $this->wpdb->shouldReceive('esc_like') ->once() @@ -71,7 +81,7 @@ private function search($searchWord, $taxonomy, $tableAliasCount = 0) $this->wpdb->shouldReceive('get_col') ->once() - ->andReturn($termIds); + ->andReturn($foundTermIds); $result = $term->search($searchWord, $tableAliasCount); From e11429a677fb8c7c6da6ea912cdd28fc04551944 Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 15:21:31 +0200 Subject: [PATCH 11/14] :white_check_mark: Test alias count --- tests/Search/Dimension/TermTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Search/Dimension/TermTest.php b/tests/Search/Dimension/TermTest.php index fa27d23..ad87829 100644 --- a/tests/Search/Dimension/TermTest.php +++ b/tests/Search/Dimension/TermTest.php @@ -47,6 +47,11 @@ public function testSearch() { $this->search('Testterm', 'testTaxonomy'); } + + public function testSearchAliasCount() + { + $this->search('Term', 'taxonomy', 2); + } public function testNoHit() { From 8cf4ced5f6a1f6f45977227009466d3001858f25 Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 15:28:07 +0200 Subject: [PATCH 12/14] :memo: Term dimension README --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd14cf1..e25a047 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,27 @@ Available options for constructing an instance of `Meta`: | `key` | `null` | Yes | The `meta_key` to search for | `compare` | `=` | No | The database comparison that should be made for the meta key. Currently supports `LIKE` and `=`. When using `LIKE`, make sure to include a percent symbol (`%`) in your `key` parameter as a wildcard. See [Example](#example) +### Terms +```php +$search->addDimension(new \Trendwerk\Search\Dimension\Term($wpdb, [ + 'taxonomy' => 'taxonomyName', +])); +``` + +Available options for constructing an instance of `Term`: + +| Parameter | Default | Required | Description | +| :--- | :--- | :--- | :--- | +| `taxonomy` | `null` | Yes | The `taxonomy` which terms should be included in search + ## Example ```php use Trendwerk\Search\Dimension\Meta; +use Trendwerk\Search\Dimension\Term; use Trendwerk\Search\Search; -$search = Search(); +$search = new Search(); $search->init(); $search->addDimension(new Meta($wpdb, [ @@ -70,4 +84,8 @@ $search->addDimension(new Meta($wpdb, [ $search->addDimension(new Meta($wpdb, [ 'key' => 'firstName', ])); + +$search->addDimension(new Term($wpdb, [ + 'taxonomy' => 'category', +])); ``` From 408372f66a68e3c7903e97287af2bb9f900d99f2 Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Mon, 22 May 2017 15:28:55 +0200 Subject: [PATCH 13/14] :memo: Fix first parameter of meta dimension constructor --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e25a047..66ac257 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ $search->addDimension($dimension); ### Meta ```php -$metaDimension = new \Trendwerk\Search\Dimension\Meta([ +$metaDimension = new \Trendwerk\Search\Dimension\Meta($wpdb, [ 'key' => 'firstName', ]); From e824cd95d860351b035162696825da215961de50 Mon Sep 17 00:00:00 2001 From: Harold Angenent Date: Wed, 24 May 2017 11:16:50 +0200 Subject: [PATCH 14/14] :memo: Improve README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 66ac257..4ec10b9 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Search [![Build Status](https://travis-ci.org/trendwerk/search.svg?branch=master)](https://travis-ci.org/trendwerk/search) [![codecov](https://codecov.io/gh/trendwerk/search/branch/master/graph/badge.svg)](https://codecov.io/gh/trendwerk/search) -Basic extensions for searching in WordPress. Currently only supports searching in `postmeta`. +Basic extensions for searching in WordPress. Quick links: [Install](#install) | [Usage](#usage) | [Dimensions](#dimensions) | [Example](#example) -_Note: This basic extension is not very scalable and meant for smaller databases. This package could get slow for complex meta searches. In that case, [Elasticsearch](https://www.elastic.co/) would be a better solution._ +_Note: This basic extension is not very scalable and meant for smaller databases. This package could get slow for complex searches. In that case, [Elasticsearch](https://www.elastic.co/) would be a better solution._ ## Install ```sh @@ -27,7 +27,7 @@ $search->init(); This code should be run when bootstrapping your theme. ### Dimensions -Currently this package only supports metadata as a search dimension. Dimensions can be added by using `addDimension`: +Currently this package supports metadata and terms as search dimensions. Dimensions can be added by using `addDimension`: ```php $search->addDimension($dimension);