From b6194a00c318f29116da18669f9e328eb327d589 Mon Sep 17 00:00:00 2001 From: Ivo Valchev Date: Fri, 21 Aug 2020 15:41:37 +0200 Subject: [PATCH 1/4] Clean up and refactor SelectQuery --- src/Storage/SelectQuery.php | 188 +++++++++++++++++++++--------------- 1 file changed, 108 insertions(+), 80 deletions(-) diff --git a/src/Storage/SelectQuery.php b/src/Storage/SelectQuery.php index e8649896e..f6e493905 100644 --- a/src/Storage/SelectQuery.php +++ b/src/Storage/SelectQuery.php @@ -4,9 +4,11 @@ namespace Bolt\Storage; +use Bolt\Common\Json; use Bolt\Configuration\Config; use Bolt\Configuration\Content\ContentType; use Bolt\Doctrine\JsonHelper; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\Query\Expr\Base; use Doctrine\ORM\Query\ParameterTypeInferer; use Doctrine\ORM\QueryBuilder; @@ -74,6 +76,9 @@ class SelectQuery implements QueryInterface 'author', ]; + /** @var string */ + protected $anything = 'anything'; + /** @var array */ private $referenceJoins = []; @@ -184,6 +189,7 @@ public function getWhereExpression(): ?Base return null; } $expr = $this->qb->expr()->andX(); + $em = $this->getQueryBuilder()->getEntityManager(); $this->referenceJoins = []; $this->taxonomyJoins = []; @@ -192,22 +198,31 @@ public function getWhereExpression(): ?Base foreach ($this->filters as $filter) { if (in_array($filter->getKey(), $this->coreFields, true)) { // For fields like `id`, `createdAt` and `status`, which are in the main `bolt_content` table - $expr = $expr->add($filter->getExpression()); + $expr = $expr->add($this->getCoreFieldExpression($filter)); } elseif (in_array($filter->getKey(), $this->referenceFields, true)) { // Special case for filtering on 'author' - $this->referenceJoins[$filter->getKey()] = $filter; - $expr = $expr->add($filter->getExpression()); + $expr = $expr->add($this->getReferenceFieldExpression($filter)); } elseif (in_array($filter->getKey(), $this->getTaxonomyFields(), true)) { // For when we're using a taxonomy type in the `where` - $this->taxonomyJoins[$filter->getKey()] = $filter; - $filterExpression = sprintf('taxonomies_%s.slug = :%s', $filter->getKey(), key($filter->getParameters())); - $expr = $expr->add($filterExpression); + $expr = $expr->add($this->getTaxonomyFieldExpression($filter)); + } elseif (in_array($filter->getKey(), [$this->anything], true)) { + // build all expressions + // put them in a wrapper OR expression + $anythingExpr = $this->qb->expr()->OrX(); + $core = $this->getCoreFieldExpression($filter); + $reference = $this->getReferenceFieldExpression($filter); + $taxonomy = $this->getTaxonomyFieldExpression($filter); + $regular = $this->getRegularFieldExpression($filter, $em); + $anythingExpr->addMultiple([$core, $reference, $taxonomy, $regular]); + $expr = $expr->add($anythingExpr); } else { // This means the name / value in the `where` is stored in the `bolt_field` table - $this->fieldJoins[$filter->getKey()] = $filter; + $expr = $expr->add($this->getRegularFieldExpression($filter, $em)); } } + $this->index = 1; + return $expr; } @@ -251,17 +266,18 @@ public function build(): QueryBuilder $dateFields = $this->getDateFields(); - if ($this->getWhereExpression()) { - $query->andWhere($this->getWhereExpression()); + $whereExpression = $this->getWhereExpression(); + if ($whereExpression) { + $query->andWhere($whereExpression); } foreach ($this->getWhereParameters() as $key => $param) { $fieldName = current(explode('_', $key)); - // Use strtotime on 'date' fields to allow selections like "today", "in 3 weeks" or "this year" if (in_array($fieldName, $dateFields, true) && (strtotime($param) !== false)) { $param = date('c', strtotime($param)); } + $query->setParameter($key, $param, ParameterTypeInferer::inferType($param)); } @@ -319,13 +335,12 @@ public function __toString(): string * * @throws \Exception */ - protected function processFilters(): void + public function processFilters(): void { $this->filters = []; foreach ($this->params as $key => $value) { $this->parser->setAlias('content'); - $filter = $this->parser->getFilter($key, $value); if ($filter) { $this->addFilter($filter); @@ -358,69 +373,13 @@ public function doTaxonomyJoins(): void */ public function doFieldJoins(): void { - $em = $this->qb->getEntityManager(); - foreach ($this->fieldJoins as $key => $filter) { - $index = $this->getAndIncrementIndex(); - $contentAlias = 'content_' . $index; - $fieldsAlias = 'fields_' . $index; - $translationsAlias = 'translations_' . $index; - $keyParam = 'field_' . $index; - - $originalLeftExpression = 'content.' . $key; - // LOWER() added to query to enable case insensitive search of JSON values. Used in conjunction with converting $params of setParameter() to lowercase. - $newLeftExpression = JsonHelper::wrapJsonFunction('LOWER(' . $translationsAlias . '.value)', null, $em->getConnection()); - - $where = $filter->getExpression(); - $exactWhere = str_replace($originalLeftExpression, $newLeftExpression, $where); - - // add containsWhere to allow searching of fields with Muiltiple JSON values (eg. Selectfield with mutiple entries). - preg_match_all('/\:([a-z]*_[0-9]+)/', $where, $matches); - $clauses = array_map(function ($m) use ($translationsAlias) { - return 'LOWER(' . $translationsAlias . '.value) LIKE :' . $m . '_JSON'; - }, $matches[1]); - $containsWhere = implode(' OR ', $clauses); - - // Create the subselect to filter on the value of fields - $innerQuery = $em - ->createQueryBuilder() - ->select($contentAlias . '.id') - ->from(\Bolt\Entity\Content::class, $contentAlias) - ->innerJoin($contentAlias . '.fields', $fieldsAlias) - ->innerJoin($fieldsAlias . '.translations', $translationsAlias) - ->andWhere($exactWhere); - - if (! empty($containsWhere)) { - $innerQuery->OrWhere($containsWhere); - } - - // Unless the field to which the 'where' applies is `anyColumn`, we - // Make certain it's narrowed down to that fieldname - if ($key !== 'anyField') { - $innerQuery->andWhere($fieldsAlias . '.name = :' . $keyParam); - $this->qb->setParameter($keyParam, $key); - } else { - //added to include taxonomies to be searched as part of contenttype filter at the backend and frontend if anyField param is set. - foreach ($filter->getParameters() as $value) { - $innerQuery->leftJoin($contentAlias . '.taxonomies', 'taxonomies_' . $index); - $this->qb->setParameter($key . '_1', $value); - $filterExpression = sprintf('LOWER(taxonomies_%s.slug) LIKE :%s', $index, $key . '_1'); - $innerQuery->orWhere($filterExpression); - } - } - + $contentAlias = 'content'; + $fieldsAlias = 'fields_' . $key; + $translationsAlias = 'translations_' . $key; $this->qb - ->andWhere($this->qb->expr()->in('content.id', $innerQuery->getDQL())); - - foreach ($filter->getParameters() as $key => $value) { - $value = JsonHelper::wrapJsonFunction(null, $value, $em->getConnection()); - $this->qb->setParameter($key, $value); - - if (! empty($containsWhere)) { - //remove % if present. Reformat JSON to work with both json enabled platforms and non json platforms. - $this->qb->setParameter($key . '_JSON', '%"' . str_replace(['["', '"]', '%'], '', $value) . '"%'); - } - } + ->leftJoin($contentAlias . '.fields', $fieldsAlias) + ->leftJoin($fieldsAlias . '.translations', $translationsAlias); } } @@ -472,13 +431,6 @@ public function incrementIndex(): void $this->index++; } - public function getAndIncrementIndex() - { - $this->incrementIndex(); - - return $this->getIndex(); - } - public function getCoreFields(): array { return $this->coreFields; @@ -488,4 +440,80 @@ public function getConfig(): Config { return $this->config; } + + private function getCoreFieldExpression(Filter $filter): string + { + if ($filter->getKey() !== $this->anything) { + return $filter->getExpression(); + } + + $original = $filter->getExpression(); + $expr = $this->qb->expr()->orX(); + + foreach ($this->coreFields as $core) { + $expr->add(preg_replace('/^(content\.)(anything)/', '$1' . $core, $original)); + } + + return $expr->__toString(); + } + + private function getReferenceFieldExpression(Filter $filter): string + { + if ($filter->getKey() !== $this->anything) { + $this->referenceJoins[$filter->getKey()] = $filter; + + return $filter->getExpression(); + } + + $this->referenceJoins['author'] = 'author'; + + $original = $filter->getExpression(); + $expr = $this->qb->expr()->orX(); + + foreach ($this->referenceFields as $reference) { + $expr->add(preg_replace('/^(content\.)(anything)/', 'content.' . $reference, $original)); + } + + return $expr->__toString(); + } + + private function getTaxonomyFieldExpression(Filter $filter): string + { + $this->taxonomyJoins[$filter->getKey()] = $filter; + + return sprintf('taxonomies_%s.slug = :%s', $filter->getKey(), key($filter->getParameters())); + } + + private function getRegularFieldExpression(Filter $filter, EntityManager $em): string + { + $this->fieldJoins[$filter->getKey()] = $filter; + $expr = $this->qb->expr()->andX(); + + // where clause for the value of the field + $valueAlias = sprintf('translations_%s.value', $filter->getKey()); + + $originalLeftExpression = 'content.' . $filter->getKey(); + // LOWER() added to query to enable case insensitive search of JSON values. Used in conjunction with converting $params of setParameter() to lowercase. + $newLeftExpression = JsonHelper::wrapJsonFunction('LOWER(' . $valueAlias . ')', null, $em->getConnection()); + $valueWhere = $filter->getExpression(); + $valueWhere = str_replace($originalLeftExpression, $newLeftExpression, $valueWhere); + $expr->add($valueWhere); + + // where clause for thh name of the field + if (! in_array($filter->getKey(), ['anyField', $this->anything], true)) { + // Add to DQL where clause + $nameAlias = sprintf('fields_%s.name', $filter->getKey()); + $nameParam = 'field_' . $filter->getKey(); + $nameExpression = sprintf('%s = :%s', $nameAlias, $nameParam); + $expr->add($nameExpression); + + // Create filter to set the parameter + $nameFilter = new Filter(); + $nameFilter->setKey($nameParam); + $nameFilter->setParameter($nameParam, $filter->getKey()); + $this->addFilter($nameFilter); + } + + return $expr->__toString(); + } } From 582c4a216a86a8b9fe035af019d7f9a7f859cdb3 Mon Sep 17 00:00:00 2001 From: Ivo Valchev Date: Fri, 21 Aug 2020 15:44:04 +0200 Subject: [PATCH 2/4] csfix --- src/Storage/SelectQuery.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storage/SelectQuery.php b/src/Storage/SelectQuery.php index f6e493905..e873a7a50 100644 --- a/src/Storage/SelectQuery.php +++ b/src/Storage/SelectQuery.php @@ -4,7 +4,6 @@ namespace Bolt\Storage; -use Bolt\Common\Json; use Bolt\Configuration\Config; use Bolt\Configuration\Content\ContentType; use Bolt\Doctrine\JsonHelper; From 2d33cd1cf893d173d3fc33a7c4acfa61b991eb1c Mon Sep 17 00:00:00 2001 From: Ivo Valchev Date: Fri, 21 Aug 2020 15:50:26 +0200 Subject: [PATCH 3/4] clean up --- src/Storage/SelectQuery.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Storage/SelectQuery.php b/src/Storage/SelectQuery.php index e873a7a50..bd6bb2102 100644 --- a/src/Storage/SelectQuery.php +++ b/src/Storage/SelectQuery.php @@ -220,8 +220,6 @@ public function getWhereExpression(): ?Base } } - $this->index = 1; - return $expr; } @@ -248,7 +246,7 @@ public function addFilter(Filter $filter): void * * @return Filter[] */ - public function getFilters(): array + protected function getFilters(): array { return $this->filters; } From cb447a3fa30da8c685a0e2140ec2c0bb6e1fd685 Mon Sep 17 00:00:00 2001 From: Ivo Valchev Date: Fri, 21 Aug 2020 15:55:31 +0200 Subject: [PATCH 4/4] typos. fixes --- src/Storage/SelectQuery.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Storage/SelectQuery.php b/src/Storage/SelectQuery.php index bd6bb2102..261484a17 100644 --- a/src/Storage/SelectQuery.php +++ b/src/Storage/SelectQuery.php @@ -246,7 +246,7 @@ public function addFilter(Filter $filter): void * * @return Filter[] */ - protected function getFilters(): array + public function getFilters(): array { return $this->filters; } @@ -332,7 +332,7 @@ public function __toString(): string * * @throws \Exception */ - public function processFilters(): void + protected function processFilters(): void { $this->filters = []; @@ -496,7 +496,7 @@ private function getRegularFieldExpression(Filter $filter, EntityManager $em): s $valueWhere = str_replace($originalLeftExpression, $newLeftExpression, $valueWhere); $expr->add($valueWhere); - // where clause for thh name of the field + // where clause for the name of the field if (! in_array($filter->getKey(), ['anyField', $this->anything], true)) { // Add to DQL where clause $nameAlias = sprintf('fields_%s.name', $filter->getKey());