mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-24 02:13:15 +00:00
New search style
This commit is contained in:
171
lib/Alchemy/Phrasea/Controller/Api/SearchController.php
Normal file
171
lib/Alchemy/Phrasea/Controller/Api/SearchController.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Controller\Api;
|
||||
|
||||
use Alchemy\Phrasea\Controller\Controller;
|
||||
use Alchemy\Phrasea\Model\Manipulator\UserManipulator;
|
||||
use Alchemy\Phrasea\Model\RecordInterface;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineLogger;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineResult;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineSuggestion;
|
||||
use Assert\Assertion;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
/**
|
||||
* Search Records
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function searchAction(Request $request)
|
||||
{
|
||||
list($ret, $search_result) = $this->searchAndFormatEngineResult($request);
|
||||
|
||||
/** @var SearchEngineResult $search_result */
|
||||
$ret['search_type'] = $search_result->getOptions()->getSearchType();
|
||||
$ret['results'] = [];
|
||||
|
||||
foreach ($this->convertSearchResultToRecords($search_result->getResults()) as $record) {
|
||||
$ret['results'][] = [
|
||||
'databox_id' => $record->getDataboxId(),
|
||||
'record_id' => $record->getRecordId(),
|
||||
'collection_id' => $record->getCollectionId(),
|
||||
'updated_at' => $record->getUpdated(),
|
||||
];
|
||||
}
|
||||
|
||||
return Result::create($request, $ret)->createResponse();
|
||||
}
|
||||
|
||||
private function searchAndFormatEngineResult(Request $request)
|
||||
{
|
||||
$options = SearchEngineOptions::fromRequest($this->app, $request);
|
||||
|
||||
$offsetStart = (int) ($request->get('offset_start') ?: 0);
|
||||
$perPage = (int) $request->get('per_page') ?: 10;
|
||||
|
||||
$query = (string) $request->get('query');
|
||||
$this->getSearchEngine()->resetCache();
|
||||
|
||||
$search_result = $this->getSearchEngine()->query($query, $offsetStart, $perPage, $options);
|
||||
|
||||
$this->getUserManipulator()->logQuery($this->getAuthenticatedUser(), $search_result->getQuery());
|
||||
|
||||
foreach ($options->getDataboxes() as $databox) {
|
||||
$colls = array_map(function (\collection $collection) {
|
||||
return $collection->get_coll_id();
|
||||
}, array_filter($options->getCollections(), function (\collection $collection) use ($databox) {
|
||||
return $collection->get_databox()->get_sbas_id() == $databox->get_sbas_id();
|
||||
}));
|
||||
|
||||
$this->getSearchEngineLogger()
|
||||
->log($databox, $search_result->getQuery(), $search_result->getTotal(), $colls);
|
||||
}
|
||||
|
||||
$this->getSearchEngine()->clearCache();
|
||||
|
||||
$ret = [
|
||||
'offset_start' => $offsetStart,
|
||||
'per_page' => $perPage,
|
||||
'available_results' => $search_result->getAvailable(),
|
||||
'total_results' => $search_result->getTotal(),
|
||||
'error' => (string)$search_result->getError(),
|
||||
'warning' => (string)$search_result->getWarning(),
|
||||
'query_time' => $search_result->getDuration(),
|
||||
'search_indexes' => $search_result->getIndexes(),
|
||||
'suggestions' => array_map(
|
||||
function (SearchEngineSuggestion $suggestion) {
|
||||
return $suggestion->toArray();
|
||||
},
|
||||
$search_result->getSuggestions()->toArray()
|
||||
),
|
||||
'facets' => $search_result->getFacets(),
|
||||
'results' => [],
|
||||
];
|
||||
|
||||
return [$ret, $search_result];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchEngineInterface
|
||||
*/
|
||||
private function getSearchEngine()
|
||||
{
|
||||
return $this->app['phraseanet.SE'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserManipulator
|
||||
*/
|
||||
private function getUserManipulator()
|
||||
{
|
||||
return $this->app['manipulator.user'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchEngineLogger
|
||||
*/
|
||||
private function getSearchEngineLogger()
|
||||
{
|
||||
return $this->app['phraseanet.SE.logger'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RecordInterface[] $records
|
||||
* @return array[]
|
||||
*/
|
||||
private function groupRecordIdsPerDataboxId($records)
|
||||
{
|
||||
$number = 0;
|
||||
$perDataboxRecordIds = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
$databoxId = $record->getDataboxId();
|
||||
|
||||
if (!isset($perDataboxRecordIds[$databoxId])) {
|
||||
$perDataboxRecordIds[$databoxId] = [];
|
||||
}
|
||||
|
||||
$perDataboxRecordIds[$databoxId][$record->getRecordId()] = $number++;
|
||||
}
|
||||
|
||||
return $perDataboxRecordIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RecordInterface[] $records
|
||||
* @return \record_adapter[]
|
||||
*/
|
||||
private function convertSearchResultToRecords($records)
|
||||
{
|
||||
Assertion::allIsInstanceOf($records, RecordInterface::class);
|
||||
|
||||
$perDataboxRecordIds = $this->groupRecordIdsPerDataboxId($records);
|
||||
|
||||
$records = [];
|
||||
|
||||
foreach ($perDataboxRecordIds as $databoxId => $recordIds) {
|
||||
$databox = $this->findDataboxById($databoxId);
|
||||
|
||||
foreach ($databox->getRecordRepository()->findByRecordIds(array_keys($recordIds)) as $record) {
|
||||
$records[$recordIds[$record->getRecordId()]] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($records);
|
||||
|
||||
return $records;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\ControllerProvider\Api;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Alchemy\Phrasea\Controller\Api\BasketController;
|
||||
use Alchemy\Phrasea\Controller\Api\SearchController;
|
||||
use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait;
|
||||
use Alchemy\Phrasea\Core\Event\Listener\OAuthListener;
|
||||
use Silex\Application;
|
||||
@@ -35,6 +36,12 @@ class V2 implements ControllerProviderInterface, ServiceProviderInterface
|
||||
->setJsonBodyHelper($app['json.body_helper']);
|
||||
}
|
||||
);
|
||||
|
||||
$app['controller.api.v2.search'] = $app->share(
|
||||
function (PhraseaApplication $app) {
|
||||
return new SearchController($app);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
@@ -63,6 +70,8 @@ class V2 implements ControllerProviderInterface, ServiceProviderInterface
|
||||
->bind('api_v2_basket_records_reorder');
|
||||
$this->addBasketMiddleware($app, $controller);
|
||||
|
||||
$controllers->match('/search/', 'controller.api.v2.search:searchAction');
|
||||
|
||||
return $controllers;
|
||||
}
|
||||
|
||||
|
||||
@@ -310,6 +310,7 @@ class ElasticSearchEngine implements SearchEngineInterface
|
||||
$query['query_string'] = json_encode($params['body']);
|
||||
|
||||
return new SearchEngineResult(
|
||||
$options,
|
||||
$results, // ArrayCollection of results
|
||||
json_encode($query),
|
||||
$res['took'], // duration
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
@@ -31,9 +30,44 @@ class SearchEngineResult
|
||||
/** @var FacetsResponse */
|
||||
protected $facets;
|
||||
|
||||
public function __construct(ArrayCollection $results, $query, $duration, $offsetStart, $available, $total, $error,
|
||||
$warning, ArrayCollection $suggestions, $propositions, $indexes, FacetsResponse $facets = null)
|
||||
{
|
||||
/**
|
||||
* @var SearchEngineOptions
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* SearchEngineResult constructor.
|
||||
* @param SearchEngineOptions $options
|
||||
* @param ArrayCollection $results
|
||||
* @param string $query
|
||||
* @param float $duration
|
||||
* @param int $offsetStart
|
||||
* @param int $available
|
||||
* @param int $total
|
||||
* @param mixed $error
|
||||
* @param mixed $warning
|
||||
* @param ArrayCollection $suggestions
|
||||
* @param array $propositions
|
||||
* @param array $indexes
|
||||
* @param FacetsResponse|null $facets
|
||||
*/
|
||||
public function __construct(
|
||||
SearchEngineOptions $options,
|
||||
ArrayCollection $results,
|
||||
$query,
|
||||
$duration,
|
||||
$offsetStart,
|
||||
$available,
|
||||
$total,
|
||||
$error,
|
||||
$warning,
|
||||
ArrayCollection $suggestions,
|
||||
$propositions,
|
||||
$indexes,
|
||||
FacetsResponse $facets = null
|
||||
) {
|
||||
$this->options = $options;
|
||||
|
||||
$this->results = $results;
|
||||
$this->query = $query;
|
||||
$this->duration = (float) $duration;
|
||||
@@ -46,8 +80,14 @@ class SearchEngineResult
|
||||
$this->propositions = $propositions;
|
||||
$this->indexes = $indexes;
|
||||
$this->facets = $facets;
|
||||
}
|
||||
|
||||
return $this;
|
||||
/**
|
||||
* @return SearchEngineOptions
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\SearchEngine;
|
||||
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineResult;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineSuggestion;
|
||||
@@ -17,6 +18,7 @@ class SearchEngineResultTest extends \PhraseanetTestCase
|
||||
*/
|
||||
public function testBasic()
|
||||
{
|
||||
$options = new SearchEngineOptions();
|
||||
$results = new ArrayCollection([
|
||||
self::$DI['record_2']
|
||||
]);
|
||||
@@ -34,9 +36,7 @@ class SearchEngineResultTest extends \PhraseanetTestCase
|
||||
$propositions = new ArrayCollection();
|
||||
$indexes = 'new-index';
|
||||
|
||||
$result = new SearchEngineResult($results, $query, $duration,
|
||||
$offsetStart, $available, $total, $error, $warning,
|
||||
$suggestions, $propositions, $indexes);
|
||||
$result = new SearchEngineResult($options, $results, $query, $duration, $offsetStart, $available, $total, $error, $warning, $suggestions, $propositions, $indexes);
|
||||
|
||||
$this->assertEquals($warning, $result->getWarning());
|
||||
$this->assertEquals(2, $result->getTotalPages(23));
|
||||
@@ -55,6 +55,7 @@ class SearchEngineResultTest extends \PhraseanetTestCase
|
||||
|
||||
public function testWithOffsetStartAtZero()
|
||||
{
|
||||
$options = new SearchEngineOptions();
|
||||
$results = new ArrayCollection([
|
||||
self::$DI['record_2']
|
||||
]);
|
||||
@@ -72,9 +73,7 @@ class SearchEngineResultTest extends \PhraseanetTestCase
|
||||
$propositions = new ArrayCollection();
|
||||
$indexes = 'new-index';
|
||||
|
||||
$result = new SearchEngineResult($results, $query, $duration,
|
||||
$offsetStart, $available, $total, $error, $warning,
|
||||
$suggestions, $propositions, $indexes);
|
||||
$result = new SearchEngineResult($options, $results, $query, $duration, $offsetStart, $available, $total, $error, $warning, $suggestions, $propositions, $indexes);
|
||||
|
||||
$this->assertEquals(1, $result->getCurrentPage(10));
|
||||
$this->assertEquals(1, $result->getCurrentPage(25));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Alchemy\Phrasea\Model\Entities\ElasticsearchRecord;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineResult;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
@@ -205,6 +206,7 @@ abstract class PhraseanetAuthenticatedWebTestCase extends \PhraseanetAuthenticat
|
||||
$elasticsearchRecord->setRecordId($record->get_record_id());
|
||||
|
||||
$result = new SearchEngineResult(
|
||||
new SearchEngineOptions(),
|
||||
new ArrayCollection([$elasticsearchRecord]), // Records
|
||||
'', // Query
|
||||
0, // Duration
|
||||
|
||||
Reference in New Issue
Block a user