Skip to content
Open
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
299 changes: 215 additions & 84 deletions src/Mvc/Controller/Plugin/FindResourcesFromIdentifiers.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,34 @@ public function __construct(Connection $connection, ApiManager $apiManager)
}

/**
* Find a list of resource ids from a list of identifiers.
* Find a list of resource ids from a list of identifiers (or one id).
*
* When there are true duplicates and case insensitive duplicates, the first
* case sensitive is returned, else the first case insensitive resource.
*
* All identifiers are returned, even without id.
*
* @todo Manage Media source html.
*
* @param array|string $identifiers Identifiers should be unique. If a
* string is sent, the result will be the resource.
* @param string|int|array $identifierName Property as integer or term,
* media ingester or "internal_id", or an array with multiple conditions.
* "internal_id", a media ingester (url or file), or an associative array with
* multiple conditions (for media source).
* @param string $resourceType The resource type if any.
* @return array|int|null Associative array with the identifiers as key and the ids
* or null as value. Order is kept, but duplicate identifiers are removed.
* If $identifiers is a string, return directly the resource id, or null.
* @return array|int|null|Object Associative array with the identifiers as key
* and the ids or null as value. Order is kept, but duplicate identifiers
* are removed. If $identifiers is a string, return directly the resource
* id, or null.
*/
public function __invoke($identifiers, $identifierName, $resourceType = null)
{
$isSingle = is_string($identifiers);
$isSingle = !is_array($identifiers);

if (empty($identifierName)) {
return $isSingle ? null : [];
}

if ($isSingle) {
$identifiers = [$identifiers];
}
Expand All @@ -85,119 +94,221 @@ public function __invoke($identifiers, $identifierName, $resourceType = null)
return $isSingle ? null : [];
}

$args = $this->normalizeArgs($identifierName, $resourceType);
if (empty($args)) {
return $isSingle ? null : [];
}
list($identifierType, $identifierName, $resourceType, $itemId) = $args;

$result = $this->findResources($identifierType, $identifiers, $identifierName, $resourceType, $itemId);
return $isSingle ? ($result ? reset($result) : null) : $result;
}

protected function findResources($identifierType, array $identifiers, $identifierName, $resourceType, $itemId)
{
switch ($identifierType) {
case 'o:id':
return $this->findResourcesFromInternalIds($identifiers, $resourceType);
case 'property':
return $this->findResourcesFromPropertyIds($identifiers, $identifierName, $resourceType);
case 'media_source':
return $this->findResourcesFromMediaSource($identifiers, $identifierName, $itemId);
}
}

protected function normalizeArgs($identifierName, $resourceType)
{
$identifierType = null;
// Process identifierName as an array.
$identifierTypeName = null;
$itemId = null;

// Process identifier metadata names as an array.
if (is_array($identifierName)) {
if (isset($identifierName['o:ingester'])) {
// TODO Currently, the media source cannot be html.
if ($identifierName['o:ingester'] === 'html') {
return $isSingle ? null : [];
return null;
}
$identifierType = 'media_source';
$identifierTypeName = $identifierName['o:ingester'];
$resourceType = 'media';
$itemId = empty($identifierName['o:item']['o:id']) ? null : $identifierName['o:item']['o:id'];
$identifierName = $identifierName['o:ingester'];
}
}
// Here, identifierName is a string or an integer.
elseif (in_array($identifierName, ['internal_id'])) {
$identifierType = 'internal_id';
// Next, identifierName is a string or an integer.
elseif (in_array($identifierName, ['internal_id', 'o:id'])) {
$identifierType = 'o:id';
$identifierTypeName = 'o:id';
} elseif (is_numeric($identifierName)) {
$identifierType = 'property';
$identifierName = (int) $identifierName;
// No check of the property id for quicker process.
$identifierTypeName = (int) $identifierName;
} elseif (in_array($identifierName, ['url', 'file'])) {
$identifierType = 'media_source';
$identifierTypeName = $identifierName;
$resourceType = 'media';
$itemId = null;
} else {
$result = $this->api
$properties = $this->api
->search('properties', ['term' => $identifierName])->getContent();
$identifierType = 'property';
$identifierName = $result ? $result[0]->id() : null;
if ($properties) {
$identifierType = 'property';
$identifierTypeName = $properties[0]->id();
}
}
if (empty($identifierName)) {
return $isSingle ? null : [];

if (empty($identifierTypeName)) {
return null;
}

if (!empty($resourceType)) {
$resourceTypes = [
'item_sets' => \Omeka\Entity\ItemSet::class,
'items' => \Omeka\Entity\Item::class,
'media' => \Omeka\Entity\Media::class,
'resources' => '',
// Avoid a check and make the plugin more flexible.
'Omeka\Entity\ItemSet' => \Omeka\Entity\ItemSet::class,
'Omeka\Entity\Item' => \Omeka\Entity\Item::class,
'Omeka\Entity\Media' => \Omeka\Entity\Media::class,
'Omeka\Entity\Resource' => '',
];
if (!isset($resourceTypes[$resourceType])) {
return $isSingle ? null : [];
if ($resourceType) {
$resourceType = $this->normalizeResourceType($resourceType);
if (is_null($resourceType)) {
return null;
}
$resourceType = $resourceTypes[$resourceType];
}

switch ($identifierType) {
case 'internal_id':
$result = $this->findResourcesFromInternalIds($identifiers, $resourceType);
break;
case 'property':
$result = $this->findResourcesFromPropertyIds($identifiers, $identifierName, $resourceType);
break;
case 'media_source':
$result = $this->findResourcesFromMediaSource($identifiers, $identifierName, $itemId);
break;
}
return [
$identifierType,
$identifierTypeName,
$resourceType,
$itemId,
];
}

return $isSingle ? ($result ? reset($result) : null) : $result;
protected function normalizeResourceType($resourceType)
{
$resourceTypes = [
'items' => \Omeka\Entity\Item::class,
'item_sets' => \Omeka\Entity\ItemSet::class,
'media' => \Omeka\Entity\Media::class,
'resources' => '',
'resource' => '',
'resource:item' => \Omeka\Entity\Item::class,
'resource:itemset' => \Omeka\Entity\ItemSet::class,
'resource:media' => \Omeka\Entity\Media::class,
// Avoid a check and make the plugin more flexible.
\Omeka\Entity\Item::class => \Omeka\Entity\Item::class,
\Omeka\Entity\ItemSet::class => \Omeka\Entity\ItemSet::class,
\Omeka\Entity\Media::class => \Omeka\Entity\Media::class,
\Omeka\Entity\Resource::class => '',
'o:item' => \Omeka\Entity\Item::class,
'o:item_set' => \Omeka\Entity\ItemSet::class,
'o:media' => \Omeka\Entity\Media::class,
// Other resource types.
'item' => \Omeka\Entity\Item::class,
'item_set' => \Omeka\Entity\ItemSet::class,
'item-set' => \Omeka\Entity\ItemSet::class,
'itemset' => \Omeka\Entity\ItemSet::class,
'resource:item_set' => \Omeka\Entity\ItemSet::class,
'resource:item-set' => \Omeka\Entity\ItemSet::class,
];
return isset($resourceTypes[$resourceType])
? $resourceTypes[$resourceType]
: null;
}

protected function findResourcesFromInternalIds($identifiers, $resourceType)
protected function findResourcesFromInternalIds(array $ids, $resourceType)
{
$ids = array_filter(array_map('intval', $ids));
if (empty($ids)) {
return [];
}

// The api manager doesn't manage this type of search.
$conn = $this->connection;
$identifiers = array_map('intval', $identifiers);
$quotedIdentifiers = implode(',', $identifiers);
$qb = $conn->createQueryBuilder()

$qb = $conn->createQueryBuilder();
$expr = $qb->expr();
$qb
->select('resource.id')
->from('resource', 'resource')
// ->andWhere('resource.id in (:ids)')
// ->setParameter(':ids', $identifiers)
->andWhere("resource.id in ($quotedIdentifiers)")
->addOrderBy('resource.id', 'ASC');

$parameters = [];
if (count($ids) === 1) {
$qb
->andWhere($expr->eq('resource.id', ':id'));
$parameters['id'] = reset($ids);
} else {
// Warning: there is a difference between qb / dbal and qb / orm for
// "in" in qb, when a placeholder is used, there should be one
// placeholder for each value for expr->in().
$placeholders = [];
foreach (array_values($ids) as $key => $value) {
$placeholder = 'id_' . $key;
$parameters[$placeholder] = $value;
$placeholders[] = ':' . $placeholder;
}
$qb
->andWhere($expr->in('resource.id', $placeholders));
}

if ($resourceType) {
$qb
->andWhere('resource.resource_type = :resource_type')
->setParameter(':resource_type', $resourceType);
->andWhere($expr->eq('resource.resource_type', ':resource_type'));
$parameters['resource_type'] = $resourceType;
}

$qb
->setParameters($parameters);

$stmt = $conn->executeQuery($qb, $qb->getParameters());
$result = $stmt->fetchAll(\PDO::FETCH_COLUMN);

// Reorder the result according to the input (simpler in php and there
// is no duplicated identifiers).
return array_replace(array_fill_keys($identifiers, null), array_combine($result, $result));
return array_replace(array_fill_keys($ids, null), array_combine($result, $result));
}

protected function findResourcesFromPropertyIds($identifiers, $identifierPropertyId, $resourceType)
protected function findResourcesFromPropertyIds(array $identifiers, $identifierPropertyId, $resourceType)
{
// The api manager doesn't manage this type of search.
$conn = $this->connection;

// Search in multiple resource types in one time.
$quotedIdentifiers = array_map([$conn, 'quote'], $identifiers);
$quotedIdentifiers = implode(',', $quotedIdentifiers);
$qb = $conn->createQueryBuilder()
->select('value.value as identifier', 'value.resource_id as id')
$qb = $conn->createQueryBuilder();
$expr = $qb->expr();
$qb
->select('value.value AS identifier', 'value.resource_id AS id')
->from('value', 'value')
->leftJoin('value', 'resource', 'resource', 'value.resource_id = resource.id')
->andwhere('value.property_id = :property_id')
->setParameter(':property_id', $identifierPropertyId)
// ->andWhere('value.value in (:values)')
// ->setParameter(':values', $identifiers)
->andWhere("value.value in ($quotedIdentifiers)")
// ->andWhere($expr->in('value.property_id', $propertyIds))
// ->andWhere($expr->in('value.value', $identifiers))
->addOrderBy('resource.id', 'ASC')
->addOrderBy('value.id', 'ASC');

$parameters = [];
if (count($identifiers) === 1) {
$qb
->andWhere($expr->eq('value.value', ':identifier'));
$parameters['identifier'] = reset($identifiers);
} else {
// Warning: there is a difference between qb / dbal and qb / orm for
// "in" in qb, when a placeholder is used, there should be one
// placeholder for each value for expr->in().
$placeholders = [];
foreach (array_values($identifiers) as $key => $value) {
$placeholder = 'value_' . $key;
$parameters[$placeholder] = $value;
$placeholders[] = ':' . $placeholder;
}
$qb
->andWhere($expr->in('value.value', $placeholders));
}

$qb
->andWhere($expr->eq('value.property_id', ':property_id'));
$parameters['property_id'] = $identifierPropertyId;

if ($resourceType) {
$qb
->andWhere('resource.resource_type = :resource_type')
->setParameter(':resource_type', $resourceType);
->andWhere($expr->eq('resource.resource_type', ':resource_type'));
$parameters['resource_type'] = $resourceType;
}

$qb
->setParameters($parameters);

$stmt = $conn->executeQuery($qb, $qb->getParameters());
// $stmt->fetchAll(\PDO::FETCH_KEY_PAIR) cannot be used, because it
// replaces the first id by later ids in case of true duplicates.
Expand All @@ -206,28 +317,50 @@ protected function findResourcesFromPropertyIds($identifiers, $identifierPropert
return $this->cleanResult($identifiers, $result);
}

protected function findResourcesFromMediaSource($identifiers, $ingesterName, $itemId = null)
protected function findResourcesFromMediaSource(array $identifiers, $ingesterName, $itemId = null)
{
// The api manager doesn't manage this type of search.
$conn = $this->connection;

// Search in multiple resource types in one time.
$quotedIdentifiers = array_map([$conn, 'quote'], $identifiers);
$quotedIdentifiers = implode(',', $quotedIdentifiers);
$qb = $conn->createQueryBuilder()
->select('media.source as identifier', 'media.id as id')
$qb = $conn->createQueryBuilder();
$expr = $qb->expr();
$qb
->select('media.source AS identifier', 'media.id AS id')
->from('media', 'media')
->andwhere('media.ingester = :ingester')
->setParameter(':ingester', $ingesterName)
// ->andWhere('media.source in (:sources)')
// ->setParameter(':sources', $identifiers)
->andwhere("media.source in ($quotedIdentifiers)")
->andWhere('media.ingester = :ingester')
// ->andWhere('media.source IN (' . implode(',', array_map([$conn, 'quote'], $identifiers)) . ')')
->addOrderBy('media.id', 'ASC');

$parameters = [];
$parameters['ingester'] = $ingesterName;

if (count($identifiers) === 1) {
$qb
->andWhere($expr->eq('media.source', ':identifier'));
$parameters['identifier'] = reset($identifiers);
} else {
// Warning: there is a difference between qb / dbal and qb / orm for
// "in" in qb, when a placeholder is used, there should be one
// placeholder for each value for expr->in().
$placeholders = [];
foreach (array_values($identifiers) as $key => $value) {
$placeholder = 'value_' . $key;
$parameters[$placeholder] = $value;
$placeholders[] = ':' . $placeholder;
}
$qb
->andWhere($expr->in('media.source', $placeholders));
}

if ($itemId) {
$qb
->andWhere('media.item_id = :item_id')
->setParameter(':item_id', $itemId);
->andWhere($expr->eq('media.item_id', ':item_id'));
$parameters['item_id'] = $itemId;
}

$qb
->setParameters($parameters);

$stmt = $conn->executeQuery($qb, $qb->getParameters());
// $stmt->fetchAll(\PDO::FETCH_KEY_PAIR) cannot be used, because it
// replaces the first id by later ids in case of true duplicates.
Expand All @@ -238,9 +371,7 @@ protected function findResourcesFromMediaSource($identifiers, $ingesterName, $it

/**
* Reorder the result according to the input (simpler in php and there is no
* duplicated identifiers). When there are true duplicates, it returns the
* first. When there are case insensitive duplicates, it returns the first
* too.
* duplicated identifiers).
*
* @param array $identifiers
* @param array $result
Expand Down