From d49ad5671770b4095fd3bd54991a87bb277a6ac8 Mon Sep 17 00:00:00 2001 From: Patrick Barroca <pbarroca@afi-sa.fr> Date: Wed, 18 Sep 2019 19:39:39 +0200 Subject: [PATCH] dev #93671 : custom search on authorities --- VERSIONS_WIP/93671 | 1 + .../controllers/AuthoritySearchController.php | 25 +- .../opac/controllers/RechercheController.php | 12 +- .../scripts/authority-search/index.phtml | 2 +- library/Class/CriteresRecherche.php | 22 +- library/Class/CriteresRecherche/Abstract.php | 7 + .../CriteresRecherche/AuthoritiesParam.php | 297 ++++++++++++++++++ library/Class/CriteresRecherche/Authority.php | 1 + library/Class/MoteurRecherche.php | 8 + library/Class/Notice.php | 21 ++ library/Class/Notice/AuthorityPartial.php | 4 +- library/Class/Notice/AuthorityRelation.php | 6 + library/Class/Notice/Facette.php | 12 + library/Class/SearchForm.php | 43 ++- library/Trait/SearchCriteriaVisitor.php | 2 + library/ZendAfi/Form/AuthoritySearch.php | 2 +- .../Form/Decorator/AuthorityPicker.php | 78 +++++ library/ZendAfi/Form/Element/Authority.php | 72 +++++ .../View/Helper/AuthoritySearch/Record.php | 26 +- .../AuthoritySearch/RecordRelations.php | 26 +- .../Helper/AuthoritySearch/RecordUsages.php | 11 +- .../View/Helper/AuthoritySearch/Result.php | 8 +- .../View/Helper/TagCriteresRecherche.php | 10 +- public/opac/css/global.css | 7 + public/opac/js/authority-search/style.css | 122 +++++++ .../js/authority_picker/authority_picker.js | 60 ++++ public/opac/js/authority_picker/tests.html | 42 +++ public/opac/js/authority_picker/tests.js | 100 ++++++ .../controllers/RechercheControllerTest.php | 118 +++++++ .../AdvancedSearch/AdvancedSearchTest.php | 100 +++++- .../scenarios/Authorities/AuthoritiesTest.php | 17 + .../SearchResult/SearchResultTest.php | 63 +++- 32 files changed, 1251 insertions(+), 74 deletions(-) create mode 100644 VERSIONS_WIP/93671 create mode 100644 library/Class/CriteresRecherche/AuthoritiesParam.php create mode 100644 library/ZendAfi/Form/Decorator/AuthorityPicker.php create mode 100644 library/ZendAfi/Form/Element/Authority.php create mode 100644 public/opac/js/authority-search/style.css create mode 100644 public/opac/js/authority_picker/authority_picker.js create mode 100644 public/opac/js/authority_picker/tests.html create mode 100644 public/opac/js/authority_picker/tests.js diff --git a/VERSIONS_WIP/93671 b/VERSIONS_WIP/93671 new file mode 100644 index 00000000000..b391b24b5a3 --- /dev/null +++ b/VERSIONS_WIP/93671 @@ -0,0 +1 @@ + - ticket #93671 : Recherche avancée : Ajout de la possibilité de chercher par autorité hiérarchique \ No newline at end of file diff --git a/application/modules/opac/controllers/AuthoritySearchController.php b/application/modules/opac/controllers/AuthoritySearchController.php index 166e9c2c829..0237848e24e 100644 --- a/application/modules/opac/controllers/AuthoritySearchController.php +++ b/application/modules/opac/controllers/AuthoritySearchController.php @@ -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); + } } diff --git a/application/modules/opac/controllers/RechercheController.php b/application/modules/opac/controllers/RechercheController.php index 1b5d0343d82..c37f6b50044 100644 --- a/application/modules/opac/controllers/RechercheController.php +++ b/application/modules/opac/controllers/RechercheController.php @@ -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(); diff --git a/application/modules/opac/views/scripts/authority-search/index.phtml b/application/modules/opac/views/scripts/authority-search/index.phtml index 5d1459f3ed3..93c27770726 100644 --- a/application/modules/opac/views/scripts/authority-search/index.phtml +++ b/application/modules/opac/views/scripts/authority-search/index.phtml @@ -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(); diff --git a/library/Class/CriteresRecherche.php b/library/Class/CriteresRecherche.php index 4338012f610..1ac68048874 100644 --- a/library/Class/CriteresRecherche.php +++ b/library/Class/CriteresRecherche.php @@ -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']) : []; diff --git a/library/Class/CriteresRecherche/Abstract.php b/library/Class/CriteresRecherche/Abstract.php index 382255cc9dd..b46d14d40e7 100644 --- a/library/Class/CriteresRecherche/Abstract.php +++ b/library/Class/CriteresRecherche/Abstract.php @@ -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, diff --git a/library/Class/CriteresRecherche/AuthoritiesParam.php b/library/Class/CriteresRecherche/AuthoritiesParam.php new file mode 100644 index 00000000000..bde7be0f070 --- /dev/null +++ b/library/Class/CriteresRecherche/AuthoritiesParam.php @@ -0,0 +1,297 @@ +<?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 diff --git a/library/Class/CriteresRecherche/Authority.php b/library/Class/CriteresRecherche/Authority.php index c24cef1e66a..7bd8fb2ae07 100644 --- a/library/Class/CriteresRecherche/Authority.php +++ b/library/Class/CriteresRecherche/Authority.php @@ -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; } diff --git a/library/Class/MoteurRecherche.php b/library/Class/MoteurRecherche.php index 603d397f7df..068227e38fd 100644 --- a/library/Class/MoteurRecherche.php +++ b/library/Class/MoteurRecherche.php @@ -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])) diff --git a/library/Class/Notice.php b/library/Class/Notice.php index dff85e46d3b..0bf3cb620da 100644 --- a/library/Class/Notice.php +++ b/library/Class/Notice.php @@ -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 diff --git a/library/Class/Notice/AuthorityPartial.php b/library/Class/Notice/AuthorityPartial.php index 75a70bf9861..57b3f3cc3c4 100644 --- a/library/Class/Notice/AuthorityPartial.php +++ b/library/Class/Notice/AuthorityPartial.php @@ -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]) diff --git a/library/Class/Notice/AuthorityRelation.php b/library/Class/Notice/AuthorityRelation.php index 03e06fb0d50..3bcae857e0f 100644 --- a/library/Class/Notice/AuthorityRelation.php +++ b/library/Class/Notice/AuthorityRelation.php @@ -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(); + } } diff --git a/library/Class/Notice/Facette.php b/library/Class/Notice/Facette.php index ab1a48e33d3..33e67e940f5 100644 --- a/library/Class/Notice/Facette.php +++ b/library/Class/Notice/Facette.php @@ -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 diff --git a/library/Class/SearchForm.php b/library/Class/SearchForm.php index 4f85bcaf2eb..b349e755a18 100644 --- a/library/Class/SearchForm.php +++ b/library/Class/SearchForm.php @@ -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; } @@ -128,15 +139,8 @@ class Class_SearchFormWrapper { if (!$validator->isValid($content)) return $this->addError($this->_('Le fichier lié à ce formulaire n\'est pas valide : %s', $validator->getError())); - $includer = Class_SearchForm::getIncluder(); $form = new ZendAfi_Form_AdvancedSearch(); - - $runtime_error = ''; - try { - $includer($file->getRealpath(), $form); - } catch(Exception $e) { - $runtime_error = $e->getMessage(); - } + $runtime_error = $this->_include($file->getRealpath(), $form); if ($runtime_error) return $this->addError($this->_('Le fichier lié à ce formulaire a provoqué une erreur d\'exécution : %s', @@ -146,6 +150,23 @@ class Class_SearchFormWrapper { } + protected function _include($path, $form) { + $includer = Class_SearchForm::getIncluder(); + + if ($this->_throw_errors) { + $includer($path, $form); + return ''; + } + + try { + $includer($path, $form); + return ''; + } catch(Exception $e) { + return $e->getMessage(); + } + } + + public function hasError() { return !empty($this->_errors); } diff --git a/library/Trait/SearchCriteriaVisitor.php b/library/Trait/SearchCriteriaVisitor.php index 71478b94c17..e3a02c21fb2 100644 --- a/library/Trait/SearchCriteriaVisitor.php +++ b/library/Trait/SearchCriteriaVisitor.php @@ -57,4 +57,6 @@ trait Trait_SearchCriteriaVisitor { public function visitBookmarkedSearch($bookmark, $version) {} public function visitRecordType($type) {} + + public function visitAuthority($authority) {} } \ No newline at end of file diff --git a/library/ZendAfi/Form/AuthoritySearch.php b/library/ZendAfi/Form/AuthoritySearch.php index 59366814ef6..e40aa6a2e96 100644 --- a/library/ZendAfi/Form/AuthoritySearch.php +++ b/library/ZendAfi/Form/AuthoritySearch.php @@ -24,7 +24,7 @@ class ZendAfi_Form_AuthoritySearch extends ZendAfi_Form { public function init() { parent::init(); $this - ->addElement('text', 'expressionRecherche', []) + ->addElement('text', 'expressionRecherche') ->addElement('submit', 'run', ['label' => $this->_('Rechercher')]); } } diff --git a/library/ZendAfi/Form/Decorator/AuthorityPicker.php b/library/ZendAfi/Form/Decorator/AuthorityPicker.php new file mode 100644 index 00000000000..045163186e0 --- /dev/null +++ b/library/ZendAfi/Form/Decorator/AuthorityPicker.php @@ -0,0 +1,78 @@ +<?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 ZendAfi_Form_Decorator_AuthorityPicker extends Zend_Form_Decorator_Abstract { + use Trait_Translator; + const NAME_PREFIX = 'authority_'; + const MODE_PREFIX = 'mode_authority_'; + + public function render($content) { + $view = $this->_element->getView(); + $container_id = 'authority_picker_' . $this->_element->getId(); + $input_id = static::NAME_PREFIX . $this->_element->getName(); + $mode_id = static::MODE_PREFIX . $this->_element->getName(); + + Class_ScriptLoader::getInstance() + ->addOPACScript('authority_picker/authority_picker.js') + ->addJQueryReady('$("#' . $container_id .'").authority_picker({ +name:' . json_encode($input_id) . ', +pick_url:' . json_encode($view->url($this->_getParams(), null, true)) .' +})'); + + return $content + . $view->tag('div', + $view->formHidden($input_id, $this->_element->getValue()) + + . $view->tag('span', $this->_element->getRecordLabel()) + + . $view->button_New((new Class_Entity) + ->setText($this->_element->getPickButtonLabel()) + ->setAttribs(['onclick' => 'return false;'])) + + . $view->button_Cancel((new Class_Entity) + ->setText($this->_element->getResetButtonLabel()) + ->setAttribs(['onclick' => 'return false;'])) + + . $view->formLabel($mode_id, + $view->formCheckbox($mode_id, 1, ['checked' => true]) + . ' ' + . $this->_('Inclure les termes spécifiques'), + ['escape' => false]) + , + ['id' => $container_id]); + } + + + protected function _getParams() { + $params = ['controller' => 'authority-search', + 'action' => 'index', + 'select_for' => $this->_element->getId()]; + + if ($facets = $this->_element->getAttrib('facets')) + $params['facettes'] = $facets; + + if ($tree_roots = $this->_element->getAttrib('tree_roots')) + $params['tree_roots'] = $tree_roots; + + return $params; + } +} diff --git a/library/ZendAfi/Form/Element/Authority.php b/library/ZendAfi/Form/Element/Authority.php new file mode 100644 index 00000000000..3042bee37ad --- /dev/null +++ b/library/ZendAfi/Form/Element/Authority.php @@ -0,0 +1,72 @@ +<?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 ZendAfi_Form_Element_Authority extends Zend_Form_Element_Xhtml { + use Trait_Translator; + + protected + $_pick_button_label, + $_reset_button_label; + + public function __construct($spec, $options = null) { + parent::__construct($spec, $options); + + $decorators = $this->_decorators; + $this->_decorators = ['AuthorityPicker' => new ZendAfi_Form_Decorator_AuthorityPicker()]; + + foreach ($decorators as $name => $value) + $this->_decorators[$name] = $value; + + $this->removeDecorator('ViewHelper'); + } + + + public function getRecordLabel() { + return ($id = $this->getValue()) && ($record = Class_Notice::find($id)) + ? $record->getTitrePrincipal() + : $this->_('non sélectionné'); + } + + + public function setPickButtonLabel($label) { + $this->_pick_button_label = $label; + } + + + public function getPickButtonLabel() { + return $this->_pick_button_label + ? $this->_pick_button_label + : $this->_('Choisir'); + } + + + public function setResetButtonLabel($label) { + $this->_reset_button_label = $label; + } + + + public function getResetButtonLabel() { + return $this->_reset_button_label + ? $this->_reset_button_label + : $this->_('Annuler'); + } +} diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/Record.php b/library/ZendAfi/View/Helper/AuthoritySearch/Record.php index 109328a27d5..892b15464a9 100644 --- a/library/ZendAfi/View/Helper/AuthoritySearch/Record.php +++ b/library/ZendAfi/View/Helper/AuthoritySearch/Record.php @@ -21,15 +21,35 @@ class ZendAfi_View_Helper_AuthoritySearch_Record extends ZendAfi_View_Helper_BaseHelper { - public function AuthoritySearch_Record($record) { + public function AuthoritySearch_Record($record, $select_for) { if (!$record) return ''; + Class_ScriptLoader::getInstance()->addOPACScriptStyleSheet('authority-search/style.css'); + return - $this->_tag('h2', $record->getTitrePrincipal()) - . $this->view->AuthoritySearch_RecordRelations($record) + $this->_tag('div', + $this->_renderLabel($record, $select_for) + . $this->view->AuthoritySearch_RecordRelations($record), + ['class' => 'grid-wrapper']) . $this->view->AuthoritySearch_RecordNotes($record) . $this->view->AuthoritySearch_RecordUsages($record) ; } + + + protected function _renderLabel($record, $select_for) { + $label = $record->getTitrePrincipal(); + if ($select_for) { + $label .= ' ' . $this->view + ->button((new Class_Entity) + ->setText($this->_('Choisir')) + ->setAttribs(['onclick' => 'return false;', + 'data-record' => $record->getId(), + 'data-label' => $label, + 'class' => 'authority_pick'])); + } + + return $this->_tag('div', $this->_tag('h2', $label), ['class' => 'term']); + } } \ No newline at end of file diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/RecordRelations.php b/library/ZendAfi/View/Helper/AuthoritySearch/RecordRelations.php index 7b0c8bb9eae..828a710e75c 100644 --- a/library/ZendAfi/View/Helper/AuthoritySearch/RecordRelations.php +++ b/library/ZendAfi/View/Helper/AuthoritySearch/RecordRelations.php @@ -31,18 +31,18 @@ class ZendAfi_View_Helper_AuthoritySearch_RecordRelations extends ZendAfi_View_H $relations = Class_Notice_AuthorityRelations::allFor($record); $parts = []; - foreach([$this->_('Terme générique') => $relations->generics(), - $this->_('Terme spécifique') => $relations->specifics(), - $this->_('Terme rejeté') => $relations->rejects(), - $this->_('Terme associé') => $relations->links()] - as $label => $relations) - $parts[] = $this->_renderLabelledRelations($label, $relations); + foreach([[$this->_('Terme générique'), $relations->generics(), 'generic', 'north'], + [$this->_('Terme spécifique'), $relations->specifics(), 'specific', 'south'], + [$this->_('Terme rejeté'), $relations->rejects(), 'reject', 'west'], + [$this->_('Terme associé'), $relations->links(), 'link', 'east']] + as $params) + $parts[] = call_user_func_array([$this, '_renderLabelledRelations'], $params); return implode(array_filter($parts)); } - protected function _renderLabelledRelations($label, $relations) { + protected function _renderLabelledRelations($label, $relations, $class, $direction) { if ($relations->isEmpty()) return ''; @@ -53,8 +53,16 @@ class ZendAfi_View_Helper_AuthoritySearch_RecordRelations extends ZendAfi_View_H }); return - $this->_tag('h3', $label) - . $this->_tag('ul', implode($items->getArrayCopy())); + $this->_tag('div', + $this->_tag('h3', $label) + . $this->_tag('ul', implode($items->getArrayCopy())), + ['class' => $class]) + . $this->_renderArrow($direction); + } + + + protected function _renderArrow($direction) { + return $this->_tag('div', $this->_tag('i', ''), ['class' => 'arrow ' . $direction]); } diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/RecordUsages.php b/library/ZendAfi/View/Helper/AuthoritySearch/RecordUsages.php index b459f158f03..86a590c8895 100644 --- a/library/ZendAfi/View/Helper/AuthoritySearch/RecordUsages.php +++ b/library/ZendAfi/View/Helper/AuthoritySearch/RecordUsages.php @@ -30,10 +30,7 @@ class ZendAfi_View_Helper_AuthoritySearch_RecordUsages extends ZendAfi_View_Help protected function _renderUsage($record) { - $facets = array_filter($record->getFacetCodes(), - function($facet) { return $this->_isDynamicFacetValue($facet); }); - - if (!$facets) + if (!$facets = $record->getDynamicFacetValues()) return $this->_('Utilisé dans aucune notice'); $criterias = (new Class_CriteresRecherche()) @@ -51,12 +48,6 @@ class ZendAfi_View_Helper_AuthoritySearch_RecordUsages extends ZendAfi_View_Help } - protected function _isDynamicFacetValue($facet) { - return Class_CodifThesaurus::CODE_FACETTE === substr($facet, 0, 1) - && strlen($facet) === (Class_CodifThesaurus::ID_KEY_LENGTH * 2) + 1; - } - - protected function _countByCriterias($criterias) { return (new Class_MoteurRecherche) ->beNotExtensible() diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/Result.php b/library/ZendAfi/View/Helper/AuthoritySearch/Result.php index cffde8f0256..346600c3f54 100644 --- a/library/ZendAfi/View/Helper/AuthoritySearch/Result.php +++ b/library/ZendAfi/View/Helper/AuthoritySearch/Result.php @@ -26,20 +26,20 @@ class ZendAfi_View_Helper_AuthoritySearch_Result extends ZendAfi_View_Helper_Bas $_record, $_record_path = []; - public function AuthoritySearch_Result($result, $record, $tree_roots) { + public function AuthoritySearch_Result($result, $record, $tree_roots, $select_for) { $this->_result = $result; $this->_record = $record; return $this->_tag('div', - $this->_renderRecordOrResult() + $this->_renderRecordOrResult($select_for) . $this->_renderTree($tree_roots) . $this->_tag('div', '', ['class' => 'clear']), ['class' => 'conteneur_simple']); } - protected function _renderRecordOrResult() { - $html = ($record = $this->view->AuthoritySearch_Record($this->_record)) + protected function _renderRecordOrResult($select_for) { + $html = ($record = $this->view->AuthoritySearch_Record($this->_record, $select_for)) ? $record : $this->_renderRecords(); diff --git a/library/ZendAfi/View/Helper/TagCriteresRecherche.php b/library/ZendAfi/View/Helper/TagCriteresRecherche.php index 9b30ff62f34..b5943300821 100644 --- a/library/ZendAfi/View/Helper/TagCriteresRecherche.php +++ b/library/ZendAfi/View/Helper/TagCriteresRecherche.php @@ -57,8 +57,7 @@ class ZendAfi_View_Helper_TagCriteresRecherche extends ZendAfi_View_Helper_BaseH 'dewey' => ($libelle = Class_AdminVar::get('FACETTE_DEWEY_LIBELLE')) ? $libelle :$this->_('Dewey / pcdm4'), 'collection' => $this->_('Collection') ]; - if (isset($criteres_recherche)) - $criteres_recherche->acceptVisitor($this); + $criteres_recherche->acceptVisitor($this); } @@ -234,4 +233,11 @@ class ZendAfi_View_Helper_TagCriteresRecherche extends ZendAfi_View_Helper_BaseH $this->htmlAppend($this->getSuppressionImgUrlForLibelle($label, $url)); } + + + public function visitAuthority($authority) { + $label = $authority->getTypeLabel() . ': ' . $authority->getLabel(); + $url = $this->_criteres_recherche->getUrlRemoveAuthority($authority); + $this->htmlAppend($this->getSuppressionImgUrlForLibelle($label, $url)); + } } \ No newline at end of file diff --git a/public/opac/css/global.css b/public/opac/css/global.css index 68f09d0a805..aa70f33ecfa 100644 --- a/public/opac/css/global.css +++ b/public/opac/css/global.css @@ -3788,4 +3788,11 @@ a[href*="bookmarked-searches/notify"] img { #holds_view + ul { justify-content: center; display: flex; +} + + +/** ARIA utilities **/ +.visuallyhidden { + position: absolute; + top:-9999px; } \ No newline at end of file diff --git a/public/opac/js/authority-search/style.css b/public/opac/js/authority-search/style.css new file mode 100644 index 00000000000..093b0d85095 --- /dev/null +++ b/public/opac/js/authority-search/style.css @@ -0,0 +1,122 @@ +@supports (display: grid) { + .grid-wrapper { + margin-top: 10px; + display:grid; + grid-template-columns: 3fr 1fr 3fr 1fr 3fr; + grid-gap: 0; + grid-auto-rows: minmax(15px, auto); + } + + + .grid-wrapper div { + border: 1px solid black; + } + + + .grid-wrapper div.arrow { + border: none; + } + + + .grid-wrapper div.arrow i { + border: solid black; + border-width: 0 3px 3px 0; + display: inline-block; + padding: 3px; + } + + .grid-wrapper div.east { + grid-column: 4 / 5; + grid-row: 3; + text-align: center; + align-self: center; + } + + .grid-wrapper div.west i, + .grid-wrapper div.east i{ + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); + } + + + .grid-wrapper div.west { + grid-column: 2 / 3; + grid-row: 3; + text-align: center; + align-self: center; + } + + + .grid-wrapper div.north { + grid-column: 3 / 4; + grid-row: 2; + text-align: center; + align-self: center; + } + + .grid-wrapper div.north i, + .grid-wrapper div.south i{ + transform: rotate(45deg); + -webkit-transform: rotate(45deg); + } + + + .grid-wrapper div.south { + grid-column: 3 / 4; + grid-row: 4; + text-align: center; + align-self: center; + } + + + .grid-wrapper div h3 { + font-size: 1.1em; + text-align: center; + padding: 4px; + margin: 0; + border-bottom: 1px solid black; + } + + + .grid-wrapper div ul { + list-style: none; + padding: 10px; + margin: 0; + } + + + .grid-wrapper .term { + grid-column: 3 / 4; + grid-row: 3; + } + + + .grid-wrapper .term h2 { + text-align: center; + } + + + + .grid-wrapper .generic { + grid-column: 3 / 4; + grid-row: 1; + } + + + .grid-wrapper .reject { + grid-column: 1 / 2; + grid-row: 3; + } + + + .grid-wrapper .link { + grid-column: 5 / 6; + grid-row: 3; + } + + + .grid-wrapper .specific { + grid-column: 3 / 4; + grid-row: 5; + } +} \ No newline at end of file diff --git a/public/opac/js/authority_picker/authority_picker.js b/public/opac/js/authority_picker/authority_picker.js new file mode 100644 index 00000000000..2deb8b13f5e --- /dev/null +++ b/public/opac/js/authority_picker/authority_picker.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 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 + */ + +(function( $ ) { + $.fn.authority_picker = function( options ) { + if (!options.name || !options.pick_url) + return; + + var value_holder = this.find('#'+ options.name); + var pick_button = this.find('button').eq(0); + var reset_button = this.find('button').eq(1); + var label_span = this.find('span').first(); + + opacDialogRegisterOnOpen(function() { + $('#opac-dialog button.authority_pick').click(on_pick); + }); + + pick_button.click(function(e) { + e.preventDefault(); + var suffix = '/render/popup'; + var current_value = value_holder.val(); + if (current_value) + suffix += '/record_id/' + current_value; + + opacDialogFromUrl(addPath(options.pick_url, suffix)); + }); + + reset_button.click(function(e) { + e.preventDefault(); + value_holder.val(''); + label_span.html('non sélectionné'); + }); + + function on_pick(e) { + e.preventDefault(); + var id = $(this).attr('data-record'); + var label = $(this).attr('data-label'); + label_span.html(label); + value_holder.val(id); + opacDialogClose(); + } + }; +} (jQuery)); diff --git a/public/opac/js/authority_picker/tests.html b/public/opac/js/authority_picker/tests.html new file mode 100644 index 00000000000..bc9c0600a21 --- /dev/null +++ b/public/opac/js/authority_picker/tests.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<!-- +/** + * Copyright (c) 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 + */ +--> +<html> + <head> + <meta charset="utf-8"> + <title>QUnit tests</title> + <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-git.css"> + </head> + <body> + <div id="qunit"></div> + <div id="qunit-fixture"></div> + <script type="text/javascript" src="../../../admin/js/jquery-3.2.1.min.js"></script> + <script src="authority_picker.js"></script> + <script src="http://code.jquery.com/qunit/qunit-1.13.0.js"></script> + <script src="tests.js"></script> + <div id="opac-dialog"> + <h2>Authority + <button class="authority_pick" data-record="3334923" data-label="Authority">Choisir</button> + </h2> + </div> + </body> +</html> diff --git a/public/opac/js/authority_picker/tests.js b/public/opac/js/authority_picker/tests.js new file mode 100644 index 00000000000..e5d69eaaff8 --- /dev/null +++ b/public/opac/js/authority_picker/tests.js @@ -0,0 +1,100 @@ +/** + * Copyright (c) 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 + */ + +function call_authority_picker_for(options) { + var insertion_point = $('<div>\ +<input type="hidden" id="authority_subject" name="authority_subject">\ +<span>non sélectionné</span>\ +<button class="button new" onclick="return false;" title="" data-url="/recherche/add">Choisir</button>\ +<button class="button undo" onclick="return false;" title="Annuler mes modifications" data-url="/recherche/avancee">Annuler</button>\ +</div>'); + insertion_point.authority_picker(options); + return insertion_point; +} + + +var on_open_listeners = []; +window.opacDialogRegisterOnOpen = function(callback) { + on_open_listeners.push(callback); + callback(); +} + +var opened_urls = []; +window.opacDialogFromUrl = function(url) { + opened_urls.push(url); +}; + +window.opacDialogClose = function() {}; +window.confirm = function() { return true; }; +window.addPath = function(base, suffix) { return base + suffix; }; + +var insertion_point; + +QUnit.module('authority_picker'); + +QUnit.testStart(function() { + on_open_listeners = []; + opened_urls = []; + $('button.authority_pick').off('click'); + + insertion_point = call_authority_picker_for({ + name:"authority_subject", + pick_url: "/authority-search/index/select_for/subject" + }); +}); + + +test('plugin is defined', function() { + ok($.fn.authority_picker); +}); + + +test('on open register', function() { + equal(on_open_listeners.length, 1, 'opacDialogRegisterOnOpen has been called'); +}); + + +test('on empty', function () { + equal($._data(insertion_point.find("button")[0],'events').click.length, 1, + 'pick button is clickable in ' + insertion_point.html()); + equal($._data(insertion_point.find("button")[1],'events').click.length, 1, + 'reset button is clickable in ' + insertion_point.html()); + + equal(insertion_point.find('input').first().val(), '', 'value should be empty' + insertion_point.html()); + equal(insertion_point.find('span').first().html(), 'non sélectionné', 'label should be default' + insertion_point.html()); + + $('button.authority_pick').first().click(); + + equal(insertion_point.find('input').first().val(), '3334923', 'value has been set' + insertion_point.html()); + equal(insertion_point.find('span').first().html(), 'Authority', 'label has been set' + insertion_point.html()); + + insertion_point.find("button")[1].click(); + equal(insertion_point.find('input').first().val(), '', 'value has been reset' + insertion_point.html()); + equal(insertion_point.find('span').first().html(), 'non sélectionné', 'label has been reset' + insertion_point.html()); +}); + + +test('on already selected', function() { + insertion_point.find('input').first().val('9998988'); + insertion_point.find('span').first().html('Cosmo999'); + insertion_point.find("button").first().click(); + + equal(opened_urls[0], '/authority-search/index/select_for/subject/render/popup/record_id/9998988'); +}); diff --git a/tests/application/modules/opac/controllers/RechercheControllerTest.php b/tests/application/modules/opac/controllers/RechercheControllerTest.php index df8befcbeda..5774d00ab0b 100644 --- a/tests/application/modules/opac/controllers/RechercheControllerTest.php +++ b/tests/application/modules/opac/controllers/RechercheControllerTest.php @@ -3531,4 +3531,122 @@ class RechercheControllerNoExtensionTest extends AbstractControllerTestCase { public function noResultShouldBeDisplay() { $this->assertXPathContentContains('//div[@class="liste_notices"]/h2', 'Aucun résultat trouvé'); } +} + + + + +abstract class RechercheControllerAuthoritiesTestCase extends RechercheControllerNoticeTestCase { + protected + $_default_storm_to_volatile = true, + $_current_id = 8898; + + protected function _fixtureRecord($id_origine, $child_id='', $with_thesaurus=true) { + $autority = (new Class_Notice_AuthorityPartial) + ->newWith(Class_Notice_AuthorityType::SUBJECT, + $id_origine, + 'Authority ' . $this->_current_id, + ['b' => 'CUSTOM']); + + if ($child_id) + $autority->zoneWithChildren('550', ['3' => $child_id, '5' => 'h']); + + $thesaurus = $with_thesaurus + ? ('HDOCU' . $this->_current_id) + : ''; + + $this->fixture('Class_Notice', + ['id' => $this->_current_id, + 'type_doc' => Class_Notice_AuthorityType::SUBJECT, + 'facettes' => 'HDOCU ' . $thesaurus, + 'type' => Class_Notice::TYPE_AUTHORITY, + 'unimarc' => $autority->render()]); + + $this->fixture('Class_Exemplaire', + ['id' => $this->_current_id, + 'type' => Class_Notice::TYPE_AUTHORITY, + 'id_origine' => $id_origine, + 'id_notice' => $this->_current_id]); + + $this->_current_id++; + } +} + + + + +class RechercheControllerAuthoritiesRecordHeirarchyWithHolesTest + extends RechercheControllerAuthoritiesTestCase { + + public function setUp() { + parent::setUp(); + + $this->fixture('Class_CodifThesaurus', + ['id' => 78, + 'id_thesaurus' => 'DOCU', + 'libelle' => 'Documentary']); + + $this->_fixtureRecord('189260', '111111', false); + $this->_fixtureRecord('111111', '222222'); + $this->_fixtureRecord('222222', '333333', false); + $this->_fixtureRecord('333333', ''); + + $this->mock_sql = $this->mock()->beStrict(); + Zend_Registry::set('sql', $this->mock_sql); + } + + + /** @test */ + public function withoutHierarchyAndNoFacetShouldQueryOnDummyFacet() { + $this->mock_sql->whenCalled('fetchAll') + ->with("select id_notice, facettes from notices Where (MATCH(facettes) AGAINST('+(H0000)' IN BOOLEAN MODE)) and type=1", true, false) + ->answers([]); + + $this->dispatch('/opac/recherche/simple/authorities/HDOCU_8898_0'); + $this->assertTrue($this->mock_sql->methodHasBeenCalled('fetchAll')); + } + + + /** @test */ + public function withoutHierarchyAndNoFacetShouldDisplayDocumentaryAuthority8898() { + $this->mock_sql->whenCalled('fetchAll') + ->with("select id_notice, facettes from notices Where (MATCH(facettes) AGAINST('+(H0000)' IN BOOLEAN MODE)) and type=1", true, false) + ->answers([]); + + $this->dispatch('/opac/recherche/simple/authorities/HDOCU_8898_0'); + $this->assertXPathContentContains('//a', 'Documentary: Authority 8898'); + } + + + /** @test */ + public function withoutHierarchyAndLocalFacetShouldQueryOnLocalFacet() { + $this->mock_sql->whenCalled('fetchAll') + ->with("select id_notice, facettes from notices Where (MATCH(facettes) AGAINST('+(HDOCU8899)' IN BOOLEAN MODE)) and type=1", true, false) + ->answers([]); + + $this->dispatch('/opac/recherche/simple/authorities/HDOCU_8899_0'); + $this->assertTrue($this->mock_sql->methodHasBeenCalled('fetchAll')); + } + + + /** @test */ + public function withHierarchyAndNoFacetShouldQueryOnChildrenFacets() { + $this->mock_sql->whenCalled('fetchAll') + ->with("select id_notice, facettes from notices Where (MATCH(facettes) AGAINST('+(HDOCU8899 HDOCU8901)' IN BOOLEAN MODE)) and type=1", true, false) + ->answers([]); + + $this->dispatch('/opac/recherche/simple/authorities/HDOCU_8898_1'); + $this->assertTrue($this->mock_sql->methodHasBeenCalled('fetchAll')); + } + + + /** @test */ + public function withHierarchyAndLocalFacetShouldQueryOnLocalAndChildrenFacet() { + $this->mock_sql->whenCalled('fetchAll') + ->with("select id_notice, facettes from notices Where (MATCH(facettes) AGAINST('+(HDOCU8899 HDOCU8901)' IN BOOLEAN MODE)) and type=1", true, false) + ->answers([]); + + $this->dispatch('/opac/recherche/simple/authorities/HDOCU_8899_1'); + $this->assertTrue($this->mock_sql->methodHasBeenCalled('fetchAll')); + } } \ No newline at end of file diff --git a/tests/scenarios/AdvancedSearch/AdvancedSearchTest.php b/tests/scenarios/AdvancedSearch/AdvancedSearchTest.php index 2f6b309de68..ead89e07b1e 100644 --- a/tests/scenarios/AdvancedSearch/AdvancedSearchTest.php +++ b/tests/scenarios/AdvancedSearch/AdvancedSearchTest.php @@ -70,7 +70,7 @@ abstract class AdvancedSearchTestCase extends AbstractControllerTestCase { -class AdvancedSearchTest extends AdvancedSearchTestCase { +class AdvancedSearchDefaultTest extends AdvancedSearchTestCase { /** @test */ public function formActionShouldBeRechercheSimple() { $this->assertXPath('//form[contains(@action,"/recherche/simple")]'); @@ -173,7 +173,7 @@ class AdvancedSearchTest extends AdvancedSearchTestCase { -class AdvancedSearchWithCustomFormDefaultTest extends AdvancedSearchTest { +class AdvancedSearchWithCustomFormDefaultTest extends AdvancedSearchDefaultTest { protected function _prepareFixtures() { parent::_prepareFixtures(); Class_AdminVar::set('CUSTOM_SEARCH_FORM', 1); @@ -202,6 +202,7 @@ abstract class AdvancedSearchCustomFormSelectedTestCase extends AdvancedSearchTe public function tearDown() { Class_FileManager::reset(); Class_SearchForm::setIncluder(null); + Class_SearchForm::throwErrors(false); parent::tearDown(); } @@ -452,14 +453,13 @@ class AdvancedSearchFormWithDateSelectorsTest extends AdvancedSearchCustomFormSe 'libelle' => 'Jour de publication', 'id_thesaurus' => 'JPUB', 'id_origine' => null, - 'code' => 'JBUB', + 'code' => 'JPUB', 'rule_zone' => '995', 'rule_label_field' => 'w', 'rule_label_start_pos' => 9, 'rule_label_length' => 2 ]); - $this->fixture('Class_CodifThesaurus', ['id' => 34, 'libelle' => '3', @@ -534,8 +534,76 @@ class AdvancedSearchFormWithDateSelectorsTest extends AdvancedSearchCustomFormSe -abstract class AdvancedSearchValidCustomFormsSelectedAndPublishedTestCase extends AdvancedSearchCustomFormSelectedTestCase { - protected function _prepareFixtures() { +class AdvancedSearchFormWithAuthoritiesCriteriaTest + extends AdvancedSearchCustomFormSelectedTestCase { + + protected function _prepareFixtures() { + parent::_prepareFixtures(); + + $form_filename = 'userfiles/forms/form.php'; + $this->fixture('Class_SearchForm', ['id' => 3, 'filename' => $form_filename]); + $file_system = $this->mock() + ->whenCalled('directoryAt')->with($form_filename)->answers(false) + ->whenCalled('fileAt')->with($form_filename) + ->answers((new Class_FileManager) + ->setId($form_filename) + ->setRealpath($form_filename) + ->setPath($form_filename) + ->setName('form.php') + ->setBasename('form.php') + ->setParentPath('userfiles/forms') + ->setWritable(true)) + ->whenCalled('getContent')->with($form_filename) + ->answers('<?php ?>') + ; + + Class_FileManager::setFileSystem($file_system); + Class_SearchForm::setIncluder( + function($path, $form) { + $form + ->addElement('authority', 'motcle', + ['label' => 'Mot-clé TESS', + 'facets' => 'HMOTS', + 'tree_roots' => '230988']) + ->addUniqDisplayGroup('yeah'); + }); + + Class_SearchForm::throwErrors(true); + } + + + /** @test */ + public function authorityCriteriaShouldBePresent() { + $this->assertXPath('//input[@name="authority_motcle"]'); + } + + + /** @test */ + public function authorityModeShouldBePresent() { + $this->assertXPath('//input[@type="checkbox"][@name="mode_authority_motcle"][@checked]'); + } + + + /** @test */ + public function buttonToSelectAuthorityShouldBePresent() { + $this->assertXPathContentContains('//button', 'Choisir'); + } + + + /** @test */ + public function scriptToSelectAuthorityShouldBePresent() { + $this->assertXPath('//script[contains(@src, "/opac/js/authority_picker/authority_picker.js")]'); + $this->assertXPathContentContains('//script', 'pick_url:'); + } +} + + + + +abstract class AdvancedSearchValidCustomFormsSelectedAndPublishedTestCase + extends AdvancedSearchCustomFormSelectedTestCase { + + protected function _prepareFixtures() { parent::_prepareFixtures(); $author_search_form = 'userfiles/forms/author_search_form.php'; @@ -602,7 +670,8 @@ abstract class AdvancedSearchValidCustomFormsSelectedAndPublishedTestCase extend -class AdvancedSearchValidCustomFormsSelectedAndPublishedTest extends AdvancedSearchValidCustomFormsSelectedAndPublishedTestCase { +class AdvancedSearchValidCustomFormsSelectedAndPublishedTest + extends AdvancedSearchValidCustomFormsSelectedAndPublishedTestCase { /** @test */ public function authorFieldShouldBePresent() { @@ -642,24 +711,21 @@ class AdvancedSearchValidCustomFormsSelectedAndPublishedTest extends AdvancedSea /** @test */ public function formOnFirstTabActionShouldBeRechercheSimpleFormIdZero() { - $this->assertXPath('//form[contains(@action, "/recherche/simple/form_id/0")]'); + $this->assertXPath('//form[contains(@action, "/recherche/simple/")][contains(@action, "/form_id/0")]'); } /** @test */ public function formOnSecondTabActionShouldBeRechercheSimpleFormIdOne() { - $this->assertXPath('//form[contains(@action, "/recherche/simple/form_id/1")]'); + $this->assertXPath('//form[contains(@action, "/recherche/simple/")][contains(@action, "/form_id/1")]'); } } -class AdvancedSearchResetSecondFormTest extends AdvancedSearchValidCustomFormsSelectedAndPublishedTestCase { - public function setUp() { - AbstractControllerTestCase::setUp(); - $this->_prepareFixtures(); - } +class AdvancedSearchResetSecondFormTest + extends AdvancedSearchValidCustomFormsSelectedAndPublishedTestCase { /** @test */ public function resetFormIdOneShouldActivateFormIdOne() { @@ -809,9 +875,9 @@ class AdvancedSearchMultiFacetsPostDispatchTest extends Admin_AbstractController /** @test */ public function mixMultifacetsAndDynamicFacetsShouldMergeFacets() { - $this->postDispatch('/recherche/simple', [ 'custom_multifacets_author' => ['A12', 'A42'], - 'custom_multifacets_subject' => 'M608', - 'rech_HDOCU' => 'SIFI']); + $this->postDispatch('/recherche/simple', ['custom_multifacets_author' => ['A12', 'A42'], + 'custom_multifacets_subject' => 'M608', + 'rech_HDOCU' => 'SIFI']); $this->assertRedirectTo('/recherche/simple/multifacets/HDOCU0001-A12-A42-M608'); } } \ No newline at end of file diff --git a/tests/scenarios/Authorities/AuthoritiesTest.php b/tests/scenarios/Authorities/AuthoritiesTest.php index 12e313c9e1c..691a7529a65 100644 --- a/tests/scenarios/Authorities/AuthoritiesTest.php +++ b/tests/scenarios/Authorities/AuthoritiesTest.php @@ -383,6 +383,23 @@ class AuthoritiesAuthoritySearchControllerWithRecordIdTest +class AuthoritiesAuthoritySearchControllerWithRecordIdInPopupTest + extends AuthoritiesAuthoritySearchControllerWithRecordIdTest { + + protected function _urlToRecord($id) { + return '/authority-search/index/select_for/auth/expressionRecherche/local/record_id/' . $id; + } + + + /** @test */ + public function shouldContainsLinkToSelectCurrentAuthority() { + $this->assertXPathContentContains('//button', 'Choisir'); + } +} + + + + class AuthoritiesNoticeAjaxControllerTest extends AuthoritiesTestCase { public function setUp() { parent::setUp(); diff --git a/tests/scenarios/SearchResult/SearchResultTest.php b/tests/scenarios/SearchResult/SearchResultTest.php index 2e4c525cbd2..9bfed222d7b 100644 --- a/tests/scenarios/SearchResult/SearchResultTest.php +++ b/tests/scenarios/SearchResult/SearchResultTest.php @@ -271,8 +271,6 @@ class SearchResultWithDynamicFacetTest extends AbstractControllerTestCase { 'code' => 'DOCU', 'rule_zone' => '995', 'rule_label_field' => 't']); - - } @@ -281,4 +279,63 @@ class SearchResultWithDynamicFacetTest extends AbstractControllerTestCase { $this->dispatch('/opac/recherche/simple/rech_HDOCU/SIFI', true); $this->assertRedirectTo('/recherche/simple/multifacets/HDOCU0001'); } -} \ No newline at end of file +} + + + + +class SearchResultWithAuthorityRecordTest + extends AbstractControllerTestCase { + + protected + $_storm_default_to_volatile = true, + $_current_id = 8898; + + public function setUp() { + parent::setUp(); + + $this->_fixtureRecord('189260'); + $this->_fixtureRecord('190455'); + } + + + protected function _fixtureRecord($id_origine, $child_id='', $with_thesaurus=true) { + $autority = (new Class_Notice_AuthorityPartial) + ->newWith(Class_Notice_AuthorityType::SUBJECT, + $id_origine, + uniqid(), + ['b' => 'CUSTOM']); + + if ($child_id) + $autority->zoneWithChildren('550', ['3' => $child_id, '5' => 'h']); + + $thesaurus = $with_thesaurus + ? ('HDOCU' . $this->_current_id) + : ''; + + $this->fixture('Class_Notice', + ['id' => $this->_current_id, + 'facettes' => 'HDOCU ' . $thesaurus, + 'type' => Class_Notice::TYPE_AUTHORITY, + 'unimarc' => $autority->render()]); + + $this->fixture('Class_Exemplaire', + ['id' => $this->_current_id, + 'type' => Class_Notice::TYPE_AUTHORITY, + 'id_origine' => $id_origine, + 'id_notice' => $this->_current_id]); + + $this->_current_id++; + } + + + /** @test */ + public function shouldRedirectToSearchWith8898NoHierarchyAnd8899AndItsHierarchy() { + $this->postDispatch('/opac/recherche/simple', + ['authority_anything' => '8898', + 'mode_authority_anything' => '0', + 'authority_nothing' => '8899', + 'mode_authority_nothing' => '1']); + $this->assertRedirectTo('/recherche/simple/authorities/HDOCU_8898_0-HDOCU_8899_1'); + } +} -- GitLab