mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-15 14:03:27 +00:00
Put flags in ES structure
This commit is contained in:
21
lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/Flag.php
Normal file
21
lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/Flag.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
||||
|
||||
use Assert\Assertion;
|
||||
|
||||
class Flag
|
||||
{
|
||||
private $name;
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
Assertion::string($name);
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\QueryException;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag as FlagStructure;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
|
||||
use Assert\Assertion;
|
||||
|
||||
class FlagStatement extends Node
|
||||
{
|
||||
@@ -21,12 +23,14 @@ class FlagStatement extends Node
|
||||
|
||||
public function buildQuery(QueryContext $context)
|
||||
{
|
||||
// TODO Ensure flag exists
|
||||
$key = RecordHelper::normalizeFlagKey($this->name);
|
||||
$field = sprintf('flags.%s', $key);
|
||||
$name = FlagStructure::normalizeName($this->name);
|
||||
$flag = $context->getFlag($name);
|
||||
if (!$flag) {
|
||||
throw new QueryException(sprintf('Flag "%s" does not exist', $this->name));
|
||||
}
|
||||
return [
|
||||
'term' => [
|
||||
$field => $this->set
|
||||
$flag->getIndexField() => $this->set
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Search\AggregationHelper;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\FacetsResponse;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryCompiler;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\LimitedStructure;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
|
||||
@@ -566,7 +567,7 @@ class ElasticSearchEngine implements SearchEngineInterface
|
||||
$databoxId = $databox->get_sbas_id();
|
||||
$statusStructure = $databox->getStatusStructure();
|
||||
foreach($statusStructure as $bit => $status) {
|
||||
$flags[$databoxId][$bit] = RecordHelper::normalizeFlagKey($status['labelon']);
|
||||
$flags[$databoxId][$bit] = Flag::normalizeName($status['labelon']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2014 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\Exception;
|
||||
|
||||
class StructureException extends Exception
|
||||
{
|
||||
}
|
@@ -39,6 +39,7 @@ class CoreHydrator implements HydratorInterface
|
||||
// Some casting
|
||||
$record['record_id'] = (int) $record['record_id'];
|
||||
$record['collection_id'] = (int) $record['collection_id'];
|
||||
$record['flags_bitfield'] = (int) $record['flags_bitfield'];
|
||||
// Some identifiers
|
||||
$record['id'] = $this->helper->getUniqueRecordId($this->databox_id, $record['record_id']);
|
||||
$record['base_id'] = $this->helper->getUniqueCollectionId($this->databox_id, $record['collection_id']);
|
||||
|
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2014 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator;
|
||||
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\StructureException;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
|
||||
use databox;
|
||||
|
||||
class FlagHydrator implements HydratorInterface
|
||||
{
|
||||
public function __construct(Structure $structure, databox $databox)
|
||||
{
|
||||
$this->field_names_map = self::buildFieldNamesMap($structure, $databox);
|
||||
}
|
||||
|
||||
private static function buildFieldNamesMap(Structure $structure, databox $databox)
|
||||
{
|
||||
$names_map = [];
|
||||
foreach ($structure->getAllFlags() as $name => $flag) {
|
||||
$bit = $flag->getBitPositionInDatabox($databox);
|
||||
if ($bit === null) {
|
||||
continue;
|
||||
}
|
||||
if (isset($names_map[$bit])) {
|
||||
throw new StructureException(sprintf('Duplicated flag for bit %d', $bit));
|
||||
}
|
||||
$names_map[$bit] = $name;
|
||||
}
|
||||
return $names_map;
|
||||
}
|
||||
|
||||
public function hydrateRecords(array &$records)
|
||||
{
|
||||
foreach ($records as &$record) {
|
||||
if (isset($record['flags_bitfield'])) {
|
||||
$record['flags'] = $this->bitfieldToFlagsMap($record['flags_bitfield']);
|
||||
var_dump($record['flags_bitfield'], $record['flags']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function bitfieldToFlagsMap($bitfield)
|
||||
{
|
||||
$flags = [];
|
||||
foreach ($this->field_names_map as $position => $name) {
|
||||
$flags[$name] = \databox_status::bitIsSet($bitfield, $position);
|
||||
}
|
||||
return $flags;
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Delegate\RecordListFetch
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Delegate\ScheduledFetcherDelegate;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Fetcher;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\CoreHydrator;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\FlagHydrator;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\MetadataHydrator;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\SubDefinitionHydrator;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\ThesaurusHydrator;
|
||||
@@ -143,6 +144,7 @@ class RecordIndexer
|
||||
new CoreHydrator($databox->get_sbas_id(), $databox->get_viewname(), $this->helper),
|
||||
new TitleHydrator($connection),
|
||||
new MetadataHydrator($connection, $this->structure, $this->helper),
|
||||
new FlagHydrator($this->structure, $databox),
|
||||
new ThesaurusHydrator($this->structure, $this->thesaurus, $candidateTerms),
|
||||
new SubDefinitionHydrator($connection)
|
||||
), $delegate);
|
||||
@@ -174,7 +176,7 @@ class RecordIndexer
|
||||
$params['id'] = $record['id'];
|
||||
unset($record['id']);
|
||||
$params['type'] = self::TYPE_NAME;
|
||||
$params['body'] = $this->transform($record);
|
||||
$params['body'] = $record;
|
||||
$bulk->index($params);
|
||||
}
|
||||
}
|
||||
@@ -307,37 +309,10 @@ class RecordIndexer
|
||||
private function getFlagsMapping()
|
||||
{
|
||||
$mapping = new Mapping();
|
||||
|
||||
foreach ($this->appbox->get_databoxes() as $databox) {
|
||||
foreach ($databox->getStatusStructure() as $bit => $status) {
|
||||
$key = RecordHelper::normalizeFlagKey($status['labelon']);
|
||||
// We only add to mapping new statuses
|
||||
if (!$mapping->has($key)) {
|
||||
$mapping->add($key, 'boolean');
|
||||
}
|
||||
}
|
||||
foreach ($this->structure->getAllFlags() as $name => $_) {
|
||||
$mapping->add($name, 'boolean');
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspired by ESRecordSerializer
|
||||
*
|
||||
* @todo complete, with all the other transformations
|
||||
* @todo convert this function in a HydratorInterface and inject into fetcher
|
||||
* @param $record
|
||||
*/
|
||||
private function transform($record)
|
||||
{
|
||||
$databox = $this->appbox->get_databox($record['databox_id']);
|
||||
|
||||
foreach ($databox->getStatusStructure() as $bit => $status) {
|
||||
$key = RecordHelper::normalizeFlagKey($status['labelon']);
|
||||
|
||||
$record['flags'][$key] = \databox_status::bitIsSet($record['flags_bitfield'], $bit);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\MergeException;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag;
|
||||
use appbox;
|
||||
use DateTime;
|
||||
use igorw;
|
||||
@@ -70,11 +71,6 @@ class RecordHelper
|
||||
return $this->collectionMap;
|
||||
}
|
||||
|
||||
public static function normalizeFlagKey($key)
|
||||
{
|
||||
return StringUtils::slugify($key, '_');
|
||||
}
|
||||
|
||||
public static function validateDate($date)
|
||||
{
|
||||
$d = DateTime::createFromFormat(Mapping::DATE_FORMAT_CAPTION_PHP, $date);
|
||||
|
@@ -6,6 +6,7 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Exception\QueryException;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\AST\Field as ASTField;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\AST\Flag;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
|
||||
|
||||
/**
|
||||
@@ -73,11 +74,15 @@ class QueryContext
|
||||
if ($name instanceof ASTField) {
|
||||
$name = $name->getValue();
|
||||
}
|
||||
$field = $this->structure->get($name);
|
||||
if (!$field) {
|
||||
return null;
|
||||
return $this->structure->get($name);
|
||||
}
|
||||
|
||||
public function getFlag($name)
|
||||
{
|
||||
if ($name instanceof Flag) {
|
||||
$name = $name->getName();
|
||||
}
|
||||
return $field;
|
||||
return $this->structure->getFlagByName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -86,7 +86,7 @@ class QueryVisitor implements Visit
|
||||
return $this->visitFlagStatementNode($element);
|
||||
|
||||
case NodeTypes::FLAG:
|
||||
return $this->visitString($element);
|
||||
return new AST\Flag($this->visitString($element));
|
||||
|
||||
case NodeTypes::DATABASE:
|
||||
return $this->visitDatabaseNode($element);
|
||||
@@ -275,9 +275,13 @@ class QueryVisitor implements Visit
|
||||
if ($node->getChildrenNumber() !== 2) {
|
||||
throw new \Exception('Flag statement can only have 2 childs.');
|
||||
}
|
||||
$flag = $node->getChild(0)->accept($this);
|
||||
if (!$flag instanceof AST\Flag) {
|
||||
throw new \Exception('Flag statement key must be a flag node.');
|
||||
}
|
||||
|
||||
return new AST\FlagStatement(
|
||||
$node->getChild(0)->accept($this),
|
||||
$flag->getName(),
|
||||
$this->visitBoolean($node->getChild(1))
|
||||
);
|
||||
}
|
||||
|
54
lib/Alchemy/Phrasea/SearchEngine/Elastic/Structure/Flag.php
Normal file
54
lib/Alchemy/Phrasea/SearchEngine/Elastic/Structure/Flag.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\Structure;
|
||||
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\StringUtils;
|
||||
use Assert\Assertion;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Flag
|
||||
{
|
||||
private $name;
|
||||
|
||||
public static function createFromLegacyStatus(array $status)
|
||||
{
|
||||
if (!isset($status['labelon'])) {
|
||||
throw new InvalidArgumentException('Status array must contain the "labelon" key.');
|
||||
}
|
||||
return new self(self::normalizeName($status['labelon']));
|
||||
}
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
Assertion::string($name);
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getIndexField()
|
||||
{
|
||||
return sprintf('flags.%s', $this->name);
|
||||
}
|
||||
|
||||
public static function normalizeName($key)
|
||||
{
|
||||
return StringUtils::slugify($key, '_');
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Rewrite to have all data injected at construct time in createFromLegacyStatus()
|
||||
*/
|
||||
public function getBitPositionInDatabox(\databox $databox)
|
||||
{
|
||||
foreach ($databox->getStatusStructure() as $bit => $status) {
|
||||
$candidate_name = self::normalizeName($status['labelon']);
|
||||
if ($candidate_name === $this->name) {
|
||||
return (int) $status['bit'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@
|
||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\Structure;
|
||||
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
|
||||
use Assert\Assertion;
|
||||
use DomainException;
|
||||
|
||||
final class GlobalStructure implements Structure
|
||||
@@ -17,6 +18,7 @@ final class GlobalStructure implements Structure
|
||||
private $private = array();
|
||||
/** @var Field[] */
|
||||
private $facets = array();
|
||||
private $flags = array();
|
||||
|
||||
/**
|
||||
* @param \databox[] $databoxes
|
||||
@@ -24,17 +26,32 @@ final class GlobalStructure implements Structure
|
||||
*/
|
||||
public static function createFromDataboxes(array $databoxes)
|
||||
{
|
||||
$structure = new self();
|
||||
$fields = [];
|
||||
$flags = [];
|
||||
foreach ($databoxes as $databox) {
|
||||
foreach ($databox->get_meta_structure() as $fieldStructure) {
|
||||
$field = Field::createFromLegacyField($fieldStructure);
|
||||
$structure->add($field);
|
||||
$fields[] = Field::createFromLegacyField($fieldStructure);
|
||||
}
|
||||
foreach ($databox->getStatusStructure() as $status) {
|
||||
$flags[] = Flag::createFromLegacyStatus($status);
|
||||
}
|
||||
}
|
||||
return $structure;
|
||||
return new self($fields, $flags);
|
||||
}
|
||||
|
||||
public function add(Field $field)
|
||||
public function __construct(array $fields, array $flags)
|
||||
{
|
||||
Assertion::allIsInstanceOf($fields, Field::class);
|
||||
Assertion::allIsInstanceOf($flags, Flag::class);
|
||||
foreach ($fields as $field) {
|
||||
$this->add($field);
|
||||
}
|
||||
foreach ($flags as $flag) {
|
||||
$this->flags[$flag->getName()] = $flag;
|
||||
}
|
||||
}
|
||||
|
||||
private function add(Field $field)
|
||||
{
|
||||
$name = $field->getName();
|
||||
if (isset($this->fields[$name])) {
|
||||
@@ -120,6 +137,17 @@ final class GlobalStructure implements Structure
|
||||
throw new DomainException(sprintf('Unknown field "%s".', $name));
|
||||
}
|
||||
|
||||
public function getAllFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
public function getFlagByName($name)
|
||||
{
|
||||
return isset($this->flags[$name]) ?
|
||||
$this->flags[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of collections indexed by field name.
|
||||
*
|
||||
|
@@ -75,6 +75,16 @@ final class LimitedStructure implements Structure
|
||||
return $this->structure->isPrivate($name);
|
||||
}
|
||||
|
||||
public function getAllFlags()
|
||||
{
|
||||
return $this->structure->getAllFlags();
|
||||
}
|
||||
|
||||
public function getFlagByName($name)
|
||||
{
|
||||
return $this->structure->getFlagByName($name);
|
||||
}
|
||||
|
||||
private function limit(array $fields)
|
||||
{
|
||||
$allowed_collections = $this->allowedCollections();
|
||||
|
@@ -24,4 +24,7 @@ interface Structure
|
||||
* @throws \DomainException
|
||||
*/
|
||||
public function isPrivate($name);
|
||||
|
||||
public function getAllFlags();
|
||||
public function getFlagByName($name);
|
||||
}
|
||||
|
Reference in New Issue
Block a user