Skip to content

Commit

Permalink
Added start with, end with and list properties search (omeka/omeka-s#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel-KM committed Dec 16, 2018
1 parent 041a9b9 commit 959642f
Show file tree
Hide file tree
Showing 5 changed files with 507 additions and 3 deletions.
198 changes: 196 additions & 2 deletions Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

require_once __DIR__ . '/src/Module/AbstractGenericModule.php';

use Doctrine\ORM\QueryBuilder;
use Omeka\Api\Adapter\AbstractResourceEntityAdapter;
use Next\Module\AbstractGenericModule;
use Zend\EventManager\Event;
use Zend\EventManager\SharedEventManagerInterface;
Expand Down Expand Up @@ -61,12 +63,17 @@ public function attachListeners(SharedEventManagerInterface $sharedEventManager)

public function apiSearchQuery(Event $event)
{
// Add the random sort.
$adapter = $event->getTarget();
$qb = $event->getParam('queryBuilder');
$query = $event->getParam('request')->getContent();

// Add the random sort.
if (isset($query['sort_by']) && $query['sort_by'] === 'random') {
$qb = $event->getParam('queryBuilder');
$qb->orderBy('RAND()');
}

// Advanced property sorts.
$this->buildPropertyQuery($qb, $query, $adapter);
}

/**
Expand Down Expand Up @@ -218,4 +225,191 @@ public function formAddElementsResourceBatchUpdateForm(Event $event)
],
]);
}

/**
* Build query on value.
*
* Complete \Omeka\Api\Adapter\AbstractResourceEntityAdapter::buildPropertyQuery()
*
* Note: because this filter is separate from the core one, all the
* properties are rechecked to avoid a issue with the joiner (or/and).
* @todo Find a way to not recheck all arguments used to search properties as long as it's not in the core.
*
* Query format:
*
* - property[{index}][joiner]: "and" OR "or" joiner with previous query
* - property[{index}][property]: property ID
* - property[{index}][text]: search text
* - property[{index}][type]: search type
* - eq: is exactly
* - neq: is not exactly
* - in: contains
* - nin: does not contain
* - ex: has any value
* - nex: has no value
* - list: is in list
* - nlist: is not in list
* - sw: starts with
* - nsw: does not start with
* - ew: ends with
* - new: does not end with
* - res: has resource
* - nres: has no resource
*
* @param QueryBuilder $qb
* @param array $query
* @param AbstractResourceEntityAdapter $adapter
*/
protected function buildPropertyQuery(QueryBuilder $qb, array $query, AbstractResourceEntityAdapter $adapter)
{
if (!isset($query['property']) || !is_array($query['property'])) {
return;
}
$valuesJoin = $adapter->getEntityClass() . '.values';
$where = '';
$expr = $qb->expr();

foreach ($query['property'] as $queryRow) {
if (!(is_array($queryRow)
&& array_key_exists('property', $queryRow)
&& array_key_exists('type', $queryRow)
)) {
continue;
}
$propertyId = $queryRow['property'];
$queryType = $queryRow['type'];
$joiner = isset($queryRow['joiner']) ? $queryRow['joiner'] : null;
$value = isset($queryRow['text']) ? $queryRow['text'] : null;

if (!strlen($value) && $queryType !== 'nex' && $queryType !== 'ex') {
continue;
}

$valuesAlias = $adapter->createAlias();
$positive = true;

switch ($queryType) {
case 'neq':
$positive = false;
// No break.
case 'eq':
$param = $adapter->createNamedParameter($qb, $value);
$predicateExpr = $expr->orX(
$expr->eq("$valuesAlias.value", $param),
$expr->eq("$valuesAlias.uri", $param)
);
break;

case 'nin':
$positive = false;
// No break.
case 'in':
$param = $adapter->createNamedParameter($qb, "%$value%");
$predicateExpr = $expr->orX(
$expr->like("$valuesAlias.value", $param),
$expr->like("$valuesAlias.uri", $param)
);
break;

case 'nlist':
$positive = false;
// No break.
case 'list':
$list = is_array($value) ? $value : explode("\n", $value);
$list = array_filter(array_map('trim', $list), 'strlen');
if (empty($list)) {
continue 2;
}
$param = $adapter->createNamedParameter($qb, $list);
$predicateExpr = $expr->orX(
$expr->in("$valuesAlias.value", $param),
$expr->in("$valuesAlias.uri", $param)
);
break;

case 'nsw':
$positive = false;
// No break.
case 'sw':
$param = $adapter->createNamedParameter($qb, "$value%");
$predicateExpr = $expr->orX(
$expr->like("$valuesAlias.value", $param),
$expr->like("$valuesAlias.uri", $param)
);
break;

case 'new':
$positive = false;
// No break.
case 'ew':
$param = $adapter->createNamedParameter($qb, "%$value");
$predicateExpr = $expr->orX(
$expr->like("$valuesAlias.value", $param),
$expr->like("$valuesAlias.uri", $param)
);
break;

case 'nres':
$positive = false;
// No break.
case 'res':
$predicateExpr = $expr->eq(
"$valuesAlias.valueResource",
$adapter->createNamedParameter($qb, $value)
);
break;

case 'nex':
$positive = false;
// No break.
case 'ex':
$predicateExpr = $expr->isNotNull("$valuesAlias.id");
break;

default:
continue 2;
}

$joinConditions = [];
// Narrow to specific property, if one is selected
if ($propertyId) {
if (is_numeric($propertyId)) {
$propertyId = (int) $propertyId;
} else {
$property = $adapter->getPropertyByTerm($propertyId);
if ($property) {
$propertyId = $property->getId();
} else {
$propertyId = 0;
}
}
$joinConditions[] = $expr->eq("$valuesAlias.property", (int) $propertyId);
}

if ($positive) {
$whereClause = '(' . $predicateExpr . ')';
} else {
$joinConditions[] = $predicateExpr;
$whereClause = $expr->isNull("$valuesAlias.id");
}

if ($joinConditions) {
$qb->leftJoin($valuesJoin, $valuesAlias, 'WITH', $expr->andX(...$joinConditions));
} else {
$qb->leftJoin($valuesJoin, $valuesAlias);
}

if ($where == '') {
$where = $whereClause;
} elseif ($joiner == 'or') {
$where .= " OR $whereClause";
} else {
$where .= " AND $whereClause";
}
}

if ($where) {
$qb->andWhere($where);
}
}
}
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ Display resources in a random order, for example for a carousel or a featured
resource. Simply add `sort_by=random` to the query when needed, in particular
in the page block `Browse preview` (fix [#1281]).

#### Advanced search by start with, end with, or in list

Allow to do more advanced search in public or admin board on values of the
properties: start with, end with, in list (fix [#1274], [#1276]).

### Admin

#### Trim property values
Expand Down Expand Up @@ -136,8 +141,11 @@ Copyright
[Omeka S]: https://omeka.org/s
[Next]: https://github.com/Daniel-KM/Omeka-S-module-Next
[shortcode as a page]: https://github.com/omeka/plugin-SimplePages/pull/24
[#1283]: https://github.com/omeka/omeka-s/issues/1283
[#1258]: https://github.com/omeka/omeka-s/issues/1258
[#1274]: https://github.com/omeka/omeka-s/issues/1274
[#1276]: https://github.com/omeka/omeka-s/issues/1276
[#1281]: https://github.com/omeka/omeka-s/issues/1281
[#1283]: https://github.com/omeka/omeka-s/issues/1283
[Installing a module]: http://dev.omeka.org/docs/s/user-manual/modules/#installing-modules
[module issues]: https://github.com/Daniel-KM/Omeka-S-module-Next/issues
[CeCILL v2.1]: https://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html
Expand Down
1 change: 1 addition & 0 deletions config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
],
'view_helpers' => [
'invokables' => [
'searchFilters' => View\Helper\SearchFilters::class,
'userBar' => View\Helper\UserBar::class,
],
'factories' => [
Expand Down
Loading

0 comments on commit 959642f

Please sign in to comment.