Commit d49ad567 authored by Patrick Barroca's avatar Patrick Barroca 😁
Browse files

dev #93671 : custom search on authorities

parent 653757e7
Pipeline #8413 passed with stage
in 38 minutes and 38 seconds
- ticket #93671 : Recherche avancée : Ajout de la possibilité de chercher par autorité hiérarchique
\ No newline at end of file
......@@ -24,16 +24,15 @@ class AuthoritySearchController extends ZendAfi_Controller_Action {
public function indexAction() {
$criteres = (new Class_CriteresRecherche_Authority)->setParams($this->_request->getParams());
if ($this->_request->isPost()) {
$redirect_params = $criteres->getUrlCriteres();
return $this->_redirect($this->view->url($redirect_params, null, true),
['prependBase' => false]);
}
if ($this->_request->isPost())
return $this->_handlePost($criteres);
$prefs = $this->_getActionPreferences();
$this->view->titre = $prefs['titre'];
$this->view->tree_roots = $criteres->getParam('tree_roots');
$this->view->select_for = $criteres->getParam('select_for');
$action_params = $criteres->getUrlWithoutExpression();
$this->view->form = (new ZendAfi_Form_AuthoritySearch())
->setAction($this->view->url($action_params, null, true));
......@@ -67,4 +66,20 @@ class AuthoritySearchController extends ZendAfi_Controller_Action {
return $this->view->record = $record;
}
protected function _handlePost($criteres) {
$redirect_params = $criteres->getUrlCriteres();
if ($this->isPopupRequest())
$redirect_params['render'] = 'popup';
return $this->_redirect($this->view->url($redirect_params, null, true),
['prependBase' => false]);
}
protected function _redirect($url, array $options = array()) {
// always classical redirect even in popup
$this->_helper->redirector->gotoUrl($url, $options);
}
}
......@@ -95,23 +95,25 @@ class RechercheController extends ZendAfi_Controller_Action {
unset($params['q']);
}
$criteres_recherche = $this->newCriteresRecherches($params);
if ($multifacets = array_merge($this->_extractDynamicFacets($params),
$this->_extractMultifacetsPost())) {
$url = $this->newCriteresRecherches($params)
->getUrlWithMultifacetsUpdate($multifacets);
$url = $criteres_recherche->getUrlWithMultifacetsUpdate($multifacets);
isset($params ['titre']) ? ($url ['titre'] = $params ['titre']) : '';
return $this->_redirect($this->view->url($url, null, true),
['prependBase' => false]);
['prependBase' => false]);
}
$criteres_recherche = $this->newCriteresRecherches($params);
if ($this->view->statut == 'guidee')
$criteres_recherche->updateRubrique('guidee');
if ($this->_request->isPost()) {
$criteres_recherche = (new Class_CriteresRecherche_AuthoritiesParam($params))
->injectInto($criteres_recherche);
$criteria_params = $criteres_recherche->getUrlCriteresWithFacettes();
// preserve module as we may come from Telephone_RechercheController
$criteria_params['module'] = $this->_request->getModuleName();
......
......@@ -2,5 +2,5 @@
$this->openBoite($this->titre);
echo $this->renderForm($this->form);
echo $this->AuthoritySearch_Header($this->search_result);
echo $this->AuthoritySearch_Result($this->search_result, $this->record, $this->tree_roots);
echo $this->AuthoritySearch_Result($this->search_result, $this->record, $this->tree_roots, $this->select_for);
$this->closeBoite();
......@@ -94,7 +94,8 @@ class Class_CriteresRecherche extends Class_CriteresRecherche_Abstract {
'bib_select',
'serie',
'from',
'selection']);
'selection',
'authorities']);
}
......@@ -158,6 +159,12 @@ class Class_CriteresRecherche extends Class_CriteresRecherche_Abstract {
}
public function getAuthorities() {
return (new Class_CriteresRecherche_AuthoritiesParam)
->fromParamString($this->getParam('authorities'));
}
public function hasEmptyDomain() {
if( !$catalogue = Class_Catalogue::find($this->getParam('id_catalogue')))
return false;
......@@ -521,6 +528,8 @@ class Class_CriteresRecherche extends Class_CriteresRecherche_Abstract {
foreach($this->getMultiFacets() as $facet)
$visitor->visitMultiFacet($facet);
$this->getAuthorities()->acceptVisitor($visitor);
$filtres = $this->getFiltres();
foreach($filtres as $filtre)
$visitor->visitFiltre($filtre);
......@@ -792,6 +801,17 @@ class Class_CriteresRecherche extends Class_CriteresRecherche_Abstract {
}
public function getUrlRemoveAuthority($authority) {
$url = $this->getUrlRetourListe();
$url['authorities'] = $this->getAuthorities()
->without($authority)
->asParamString();
$url['page'] = null;
$url['genre'] = null;
return array_filter($url);
}
public function getUrlWithMultifacetsUpdate($update) {
$url = $this->getUrlRetourListe();
$multifacets = isset($url['multifacets']) ? explode('-', $url['multifacets']) : [];
......
......@@ -77,6 +77,13 @@ abstract class Class_CriteresRecherche_Abstract {
}
public function setParam($name, $value) {
$this->_params = array_merge($this->_params,
$this->filterParams([$name => $value]));
return $this;
}
public function getAvailablePageSize() {
$profil_param = (new Class_Profil_Preferences_SearchResult())->getPageSize($this->_profil);
$options = array_unique(['10' => 10,
......
<?php
/**
* Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class Class_CriteresRecherche_AuthoritiesParam {
const
NAME_PREFIX = 'authority_',
MODE_PREFIX = 'mode_',
PARAM_SEPARATOR = '-';
protected
$_authorities,
$_params;
public function __construct($params=[]) {
$this->_authorities = new Storm_Collection;
$this->_params = $params;
foreach($params as $k => $v)
$this->_importFrom($k, $v, $params);
$this->_params = null;
}
public function acceptVisitor($visitor) {
$this->_authorities
->eachDo(function($each) use($visitor)
{
$visitor->visitAuthority($each);
});
}
public function fromParamString($value) {
foreach(explode(static::PARAM_SEPARATOR, $value) as $part)
$this->_addFromParamString($part);
return $this;
}
protected function _addFromParamString($value) {
if ($one = Class_CriteresRecherche_AuthorityParam::fromParamString($value))
$this->_authorities->append($one);
return $this;
}
/**
* @param $criteres Class_CriteresRecherche_Abstract
* @return Class_CriteresRecherche_Abstract
*/
public function injectInto($criteres) {
return $this->_authorities->isEmpty()
? $criteres
: $criteres->setParam('authorities', $this->asParamString());
}
public function asParamString() {
$authorities = $this->_authorities
->collect(function($each) { return $each->asString(); });
return implode(static::PARAM_SEPARATOR, $authorities->getArrayCopy());
}
public function without($authority) {
$this->_authorities = $this->_authorities
->reject(function($each) use($authority)
{
return $authority->asString() == $each->asString();
});
return $this;
}
protected function _importFrom($name, $value, $context) {
if (!$this->_isAuthorityParam($name)
|| (!$record = Class_Notice::find($value))
|| (!$record->isAuthority()))
return;
foreach($record->getDynamicFacetRoots() as $root) {
$this->_authorities
->append($this->_newParam($root, $record->getId(), $this->_modeOf($name)));
}
}
protected function _modeOf($name) {
return $this->_getParam(static::MODE_PREFIX . $name);
}
protected function _getParam($name, $default=null) {
return array_key_exists($name, $this->_params)
? $this->_params[$name]
: $default;
}
protected function _newParam($facet, $id, $mode) {
return Class_CriteresRecherche_AuthorityParam::newWith($facet, $id, $mode);
}
protected function _isAuthorityParam($name) {
return static::NAME_PREFIX === substr($name, 0, strlen(static::NAME_PREFIX));
}
}
abstract class Class_CriteresRecherche_AuthorityParam {
use Trait_Translator;
const
PART_SEPARATOR = '_',
HIERARCHY_NONE = '0',
HIERARCHY_ALL = '1',
DUMMY_FACET = 'H0000';
protected
$_facet,
$_id,
$_mode;
public static function fromParamString($value) {
$parts = explode(static::PART_SEPARATOR, $value);
return (3 === count($parts))
? static::newWith($parts[0], $parts[1], $parts[2])
: null;
}
public static function newWith($facet, $id, $mode) {
if (null === $mode)
$mode = static::HIERARCHY_ALL;
return static::HIERARCHY_ALL === $mode
? new Class_CriteresRecherche_AuthorityParam_Hierarchical($facet, $id)
: new Class_CriteresRecherche_AuthorityParam_Flat($facet, $id);
}
public function __construct($facet, $id) {
$this->_facet = $facet;
$this->_id = $id;
}
/** @param $engine Class_MoteurRecherche */
abstract public function applyTo($engine);
public function getTypeLabel() {
return ($thesaurus = Class_CodifThesaurus::findFirstBy(['id_thesaurus' => substr($this->_facet, 1)]))
? $thesaurus->getLibelleFacette()
: $this->_('Autorité');
}
public function getLabel() {
$label = ($record = $this->_record())
? $record->getTitrePrincipal()
: $this->_('Inconnu');
return $label . $this->_specificsLabel();
}
abstract protected function _specificsLabel();
protected function _applyDummyFacetTo($engine) {
$this->_applyFacetsTo([static::DUMMY_FACET], $engine);
}
protected function _applyFacetsTo($facets, $engine) {
$engine
->setCondition('MATCH(facettes) AGAINST(\'+(' . implode(' ', $facets) . ')\' IN BOOLEAN MODE)');
}
public function _record() {
return (($record = Class_Notice::find($this->_id))
&& $record->isAuthority())
? $record
: null;
}
public function asString() {
return implode(static::PART_SEPARATOR,
[$this->_facet, $this->_id, $this->_mode]);
}
}
class Class_CriteresRecherche_AuthorityParam_Flat
extends Class_CriteresRecherche_AuthorityParam {
public function __construct($facet, $id) {
parent::__construct($facet, $id);
$this->_mode = static::HIERARCHY_NONE;
}
protected function _specificsLabel() {
return '';
}
/** @param $engine Class_MoteurRecherche */
public function applyTo($engine) {
if (!$record = $this->_record())
return;
($facets = $record->getDynamicFacetValues())
? $this->_applyFacetsTo($facets, $engine)
: $this->_applyDummyFacetTo($engine);
}
}
class Class_CriteresRecherche_AuthorityParam_Hierarchical
extends Class_CriteresRecherche_AuthorityParam {
public function __construct($facet, $id) {
parent::__construct($facet, $id);
$this->_mode = static::HIERARCHY_ALL;
}
protected function _specificsLabel() {
return $this->_(' (termes spécifiques inclus)');
}
/** @param $engine Class_MoteurRecherche */
public function applyTo($engine) {
if (!$record = $this->_record())
return;
$facets = $record->getDynamicFacetValues();
$this->_collectRecursiveFacetsFrom($record, $facets);
$facets
? $this->_applyFacetsTo($facets, $engine)
: $this->_applyDummyFacetTo($engine);
}
protected function _collectRecursiveFacetsFrom($record, &$facets) {
Class_Notice_AuthorityRelations::allFor($record)
->specifics()
->eachDo(function($relation) use(&$facets)
{
if (!$record = $relation->getRecord())
return;
foreach($record->getDynamicFacetValues() as $facet)
$facets[] = $facet;
$this->_collectRecursiveFacetsFrom($record, $facets);
});
}
}
\ No newline at end of file
......@@ -25,6 +25,7 @@ class Class_CriteresRecherche_Authority extends Class_CriteresRecherche_Abstract
$parameters = parent::getValidParameters();
$parameters[] = 'expressionRecherche';
$parameters[] = 'tree_roots';
$parameters[] = 'select_for';
return $parameters;
}
......
......@@ -723,6 +723,14 @@ class Class_MoteurRecherche {
}
/**
* @param $authority Class_CriteresRecherche_AuthorityParam
*/
public function visitAuthority($authority) {
$authority->applyTo($this);
}
public function setTypeCondition($type) {
if (in_array($type, [Class_Notice::TYPE_BIBLIOGRAPHIC,
Class_Notice::TYPE_AUTHORITY]))
......
......@@ -1879,4 +1879,25 @@ class Class_Notice extends Storm_Model_Abstract {
public function getFileContentFirstWords() {
return substr($this->getFileContent(), 0, 180) . '…';
}
public function getDynamicFacetValues() {
return (new Storm_Collection(Class_Notice_Facette::parseFacettesFromNoticeField($this->getFacettes())))
->select(function($facet) { return $facet->isDynamicFacet(); })
->collect(function($facet) { return $facet->getCle(); })
->getArrayCopy();
}
public function getDynamicFacetRoots() {
return (new Storm_Collection(Class_Notice_Facette::parseFacettesFromNoticeField($this->getFacettes())))
->select(function($facet) { return $facet->isDynamicFacetRoot(); })
->collect(function($facet) { return $facet->getCle(); })
->getArrayCopy();
}
public function isAuthority() {
return static::TYPE_AUTHORITY === $this->getType();
}
}
\ No newline at end of file
......@@ -26,13 +26,13 @@ class Class_Notice_AuthorityPartial {
const DEFAULT_AGENCY = 'Bokeh';
public function newWith($type, $id, $heading, $rules) {
$genaral_info = $this->getTimeSource()->dateFormat('Ymd') . 'afrey50 ba0';
$general_info = $this->getTimeSource()->dateFormat('Ymd') . 'afrey50 ba0';
return (new Class_NoticeUnimarc_Fluent())
->beAuthority()
->type($type)
->zoneWithContent('001', $id)
->zoneWithChildren('100', ['a' => $genaral_info])
->zoneWithChildren('100', ['a' => $general_info])
->zoneWithChildren('152', $rules)
->zoneWithChildren((new Class_Notice_AuthorityType)->zoneForType('2', $type),
['a' => $heading])
......
......@@ -140,4 +140,10 @@ class Class_Notice_AuthorityRelation {
public function isOther() {
return static::TYPE_OTHER === $this->_type;
}
public function getRecord() {
if ($item = Class_Exemplaire::findFirstAuthorityItemByOriginId($this->_id))
return $item->getNotice();
}
}
......@@ -126,4 +126,16 @@ class Class_Notice_Facette {
public function isAuthor() {
return Class_CodifAuteur::CODE_FACETTE == $this->getGroupCodeFromKey();
}
public function isDynamicFacet() {
return Class_CodifThesaurus::CODE_FACETTE === substr($this->_cle, 0, 1)
&& strlen($this->getValue()) === (Class_CodifThesaurus::ID_KEY_LENGTH * 2);
}
public function isDynamicFacetRoot() {
return Class_CodifThesaurus::CODE_FACETTE === substr($this->_cle, 0, 1)
&& strlen($this->getValue()) === Class_CodifThesaurus::ID_KEY_LENGTH;
}
}
\ No newline at end of file
......@@ -45,10 +45,13 @@ class Class_SearchFormLoader extends Storm_Model_Loader {
}
class Class_SearchForm extends Storm_Model_Abstract {
use Trait_TimeSource;
protected static $_includer;
protected static
$_includer,
$_throw_errors = false;
protected
$_table_name = 'search_form',
......@@ -66,6 +69,12 @@ class Class_SearchForm extends Storm_Model_Abstract {
}
/** @category testing */
public static function throwErrors($flag) {
static::$_throw_errors = $flag;
}
public static function getIncluder() {
return static::$_includer
? static::$_includer
......@@ -90,7 +99,7 @@ class Class_SearchForm extends Storm_Model_Abstract {
public function getFormInstance() {
return (new Class_SearchFormWrapper($this))->getFormInstance();
return (new Class_SearchFormWrapper($this, static::$_throw_errors))->getFormInstance();
}
}
......@@ -104,12 +113,14 @@ class Class_SearchFormWrapper {
protected
$_search_form,
$_throw_errors = false,
$_form,
$_errors = [];
public function __construct($search_form) {
public function __construct($search_form, $throw_errors) {
$this->_search_form = $search_form;
$this->_throw_errors = $throw_errors;
}