diff --git a/VERSIONS_WIP/99468 b/VERSIONS_WIP/99468 new file mode 100644 index 0000000000000000000000000000000000000000..6498ec3d41a1d514c6409f470578620d08549e1c --- /dev/null +++ b/VERSIONS_WIP/99468 @@ -0,0 +1 @@ + - ticket #99468 : Open Data : Ajout de la représentation des ouvertures au format OpenStreetMap opening_hours dans la liste des bibliothèques en JSON \ No newline at end of file diff --git a/application/modules/opac/controllers/BibController.php b/application/modules/opac/controllers/BibController.php index 6524a6972fb057d6cdd507c357e1411516c1b44f..9fdf4ef78c40472d13766b1a060db2e948e6a983 100644 --- a/application/modules/opac/controllers/BibController.php +++ b/application/modules/opac/controllers/BibController.php @@ -82,9 +82,10 @@ class BibController extends ZendAfi_Controller_Action { $openings = []; foreach($library->getOuvertures() as $ouverture) $openings[] = $ouverture->getRawAttributes(); - $fields['openings'] = $openings; + $fields['opening_hours'] = $library->getOpeningHours(); + $fields['latitude'] = ''; $fields['longitude'] = ''; @@ -165,7 +166,7 @@ class BibController extends ZendAfi_Controller_Action { } - function mapviewAction() { + public function mapviewAction() { if (!$library = Class_Bib::find((int)$this->_request->getParam('id_bib'))) return $this->_redirect('opac/bib/index'); @@ -310,6 +311,18 @@ class BibController extends ZendAfi_Controller_Action { } + public function openinghoursAction() { + if (!$library = Class_Bib::find((int)$this->_getParam('id'))) + return $this->_redirect('opac/bib/index'); + + $this->_helper->getHelper('viewRenderer')->setNoRender(true); + if ($layout = Zend_Layout::getMvcInstance()) + $layout->disableLayout(); + + echo $library->getOpeningHours(); + } + + public function enLirePlusAction() { $this->_initLibrary(); } diff --git a/library/Class/Bib.php b/library/Class/Bib.php index b3a82a12782df6679ec50c233917fbdd4ef51427..0f243d59ad022def6eac1f842cc60a04539e52ef 100644 --- a/library/Class/Bib.php +++ b/library/Class/Bib.php @@ -1024,4 +1024,9 @@ class Class_Bib extends Storm_Model_Abstract { ? USERFILESURL . static::BASE_PATH . $picture : BASE_URL . $picture; } + + + public function getOpeningHours() { + return (new Class_Bib_OpeningHours())->format($this); + } } diff --git a/library/Class/Bib/OpeningHours.php b/library/Class/Bib/OpeningHours.php new file mode 100644 index 0000000000000000000000000000000000000000..c52c3902225278567c21fcfd4df5d5a8b6f071c2 --- /dev/null +++ b/library/Class/Bib/OpeningHours.php @@ -0,0 +1,61 @@ +<?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_Bib_OpeningHours { + protected $_specs; + + public function format($library) { + if (!$library) + return ''; + + $this->_specs = new Storm_Collection(); + $visitor = new Class_Ouverture_Visitor; + $library->acceptOpeningsVisitor($visitor); + if (!$visitor->hasOpenings()) + return ''; + + $this->_collect($visitor->getDefault()); + + if ($library->isClosedOnHolidays()) + $this->_specs->append('PH off'); + + foreach($visitor->getPeriodical() as $grouped) + $this->_collect($grouped); + + $this->_collect($visitor->getExceptional()) + ->_collect($visitor->getClosure()); + + return implode('; ', $this->_specs->getArrayCopy()); + } + + + protected function _collect($openings) { + $specs = array_filter(array_map(function($each) + { + return $each->asOpeningHours(); + }, + $openings)); + $this->_specs->addAll($specs); + + return $this; + } +} diff --git a/library/Class/Ouverture.php b/library/Class/Ouverture.php index f82aad082b2b537ff82173efee584e6a76130d61..a464754d0f962c601cc08350682abc3806ab513d 100644 --- a/library/Class/Ouverture.php +++ b/library/Class/Ouverture.php @@ -324,11 +324,21 @@ class Class_Ouverture extends Storm_Model_Abstract { public function isClosed() { - foreach(['debut_matin', 'fin_matin', 'debut_apres_midi', 'fin_apres_midi'] as $field) - if (!$this->getLoader()->isValueClosed($this->callGetterByAttributeName($field))) - return false; + return $this->isClosedAm() && $this->isClosedPm(); + } + + + public function isClosedAm() { + $loader = $this->getLoader(); + return $loader->isValueClosed($this->getDebutMatin()) + && $loader->isValueClosed($this->getFinMatin()); + } - return true; + + public function isClosedPm() { + $loader = $this->getLoader(); + return $loader->isValueClosed($this->getDebutApresMidi()) + && $loader->isValueClosed($this->getFinApresMidi()); } @@ -338,4 +348,9 @@ class Class_Ouverture extends Storm_Model_Abstract { return (new static())->updateAttributes($attributes); } + + + public function asOpeningHours() { + return (new Class_Ouverture_OpeningHours)->format($this); + } } \ No newline at end of file diff --git a/library/Class/Ouverture/OpeningHours.php b/library/Class/Ouverture/OpeningHours.php new file mode 100644 index 0000000000000000000000000000000000000000..b62aec537e24bfbf7a5c79cf148ef8d0c6cb48de --- /dev/null +++ b/library/Class/Ouverture/OpeningHours.php @@ -0,0 +1,105 @@ +<?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 + */ + + +/** + * I format a Class_Ouverture in OSM opening_hours tag format + * + * https://wiki.openstreetmap.org/wiki/Key:opening_hours + */ +class Class_Ouverture_OpeningHours { + protected $_opening; + + public function __call($name, $args) { + if ($this->_opening) + return call_user_func_array([$this->_opening, $name], $args); + + throw new RuntimeException('Call to unknown method Class_Ouverture_OpeningHours::' . $name); + } + + + public function format($opening) { + if (!$opening) + return ''; + + $this->_opening = $opening; + + return $this->_day() . ' ' . $this->_times(); + } + + + protected function _day() { + if ($this->isExceptional()) + return $this->_date($this->getJour()); + + return $this->hasJourSemaine() + ? $this->_period() . $this->_weekday() + : ''; + } + + + protected function _date($day) { + return date('Y M d', strtotime($day)); + } + + + protected function _weekday() { + $map = [Class_Ouverture::LUNDI => 'Mo', + Class_Ouverture::MARDI => 'Tu', + Class_Ouverture::MERCREDI => 'We', + Class_Ouverture::JEUDI => 'Th', + Class_Ouverture::VENDREDI => 'Fr', + Class_Ouverture::SAMEDI => 'Sa', + Class_Ouverture::DIMANCHE => 'Su']; + + $code = $this->getJourSemaine(); + + return isset($map[$code]) + ? $map[$code] + : ''; + } + + + protected function _period() { + if (!$this->hasValidityRange()) + return ''; + + if ($this->hasValidityStart() + && $this->hasValidityEnd()) + return $this->_date($this->getValidityStart()) + . '-' . $this->_date($this->getValidityEnd()) + . ' '; + } + + + protected function _times() { + $parts = []; + if (!$this->isClosedAm()) + $parts[] = $this->getDebutMatin() . '-' . $this->getFinMatin(); + + if (!$this->isClosedPm()) + $parts[] = $this->getDebutApresMidi() . '-' . $this->getFinApresMidi(); + + return $parts + ? implode(',', $parts) + : 'off'; + } +} diff --git a/library/Class/Ouverture/Visitor.php b/library/Class/Ouverture/Visitor.php index 237c6f4cd2146ce0c399551a07c8605e1b15ac26..c0b08314d04e2f204f639413b436b3b402f352ac 100644 --- a/library/Class/Ouverture/Visitor.php +++ b/library/Class/Ouverture/Visitor.php @@ -110,15 +110,15 @@ class Class_Ouverture_Visitor { if ($this->_should_keep_all) return true; - $now = $this->getCurrentTime(); - $now_plus_30 = $this->_addToTime($now, 30); + $today_at_midnight = strtotime($this->getCurrentDate()); + $today_plus_30_at_midnight = $this->_addToTime($today_at_midnight, 30); if ($opening->isExceptional()) { $opening_day = strtotime($opening->getJour()); - return $now <= $opening_day && $now_plus_30 >= $opening_day; + return $today_at_midnight <= $opening_day && $today_plus_30_at_midnight >= $opening_day; } - return $opening->isValidDuring($now, $now_plus_30); + return $opening->isValidDuring($today_at_midnight, $today_plus_30_at_midnight); } @@ -160,7 +160,7 @@ class Class_Ouverture_Visitor { protected function _getType($type) { return $this->_hasType($type) ? $this->_openings[$type] - : null; + : []; } @@ -178,39 +178,6 @@ class Class_Ouverture_Visitor { } - protected function _renderTimeSegment($from, $to) { - if ($from == $to) - return $this->_renderHour($from); - - $from = $this->_renderHour($from); - $to = $this->_renderHour($to); - - return $from && $to - ? $from . ' - ' . $to - : ''; - } - - - protected function _renderHour($hour) { - return '00:00' == $hour ? '' : str_replace(':', 'h', $hour); - } - - - protected function _renderTimes($am, $pm) { - if ('' != $am && '' != $pm) { - if (substr($am, -5, 5) == substr($pm, 0, 5)) - return substr($am, 0, 5) . substr($pm, 5); - - return $am . ' / ' . $pm; - } - - if ($am) - return $am; - - return $pm; - } - - protected function _escapeInfo($info) { return str_replace('BR', '<br />', urldecode(str_replace('%0D%0A','BR', $info))); } diff --git a/tests/application/modules/opac/controllers/BibControllerIndexActionTest.php b/tests/application/modules/opac/controllers/BibControllerIndexActionTest.php index 2c09c38674b25591496e52c798fd75c1c95283af..d1991cf11f02d1755fff8fd80ee974d82fefd3c8 100644 --- a/tests/application/modules/opac/controllers/BibControllerIndexActionTest.php +++ b/tests/application/modules/opac/controllers/BibControllerIndexActionTest.php @@ -218,9 +218,11 @@ class BibControllerIndexActionFormatJsonTest extends AbstractControllerTestCase * @test * @depends annecyShouldHave2CustomFields */ - public function firstAnnecyFieldShouldBePublic($fields) { - $this->assertEquals('Public', $fields[0]->label); - $this->assertEquals('', $fields[0]->value); + public function annecyFieldPublicShouldBePresent($fields) { + $field = (new Storm_Collection($fields)) + ->detect(function($item) { return 'Public' == $item->label; }); + $this->assertNotNull($field); + $this->assertEquals('', $field->value); } @@ -228,9 +230,11 @@ class BibControllerIndexActionFormatJsonTest extends AbstractControllerTestCase * @test * @depends annecyShouldHave2CustomFields */ - public function secondAnnecyFieldShouldBeServices($fields) { - $this->assertEquals('Services', $fields[1]->label); - $this->assertEquals('Wifi;Restauration', $fields[1]->value); + public function annecyFieldServicesShouldBePresent($fields) { + $field = (new Storm_Collection($fields)) + ->detect(function($item) { return 'Services' == $item->label; }); + $this->assertNotNull($field); + $this->assertEquals('Wifi;Restauration', $field->value); } @@ -238,4 +242,10 @@ class BibControllerIndexActionFormatJsonTest extends AbstractControllerTestCase public function annecyShouldBeOpenedTuesday() { $this->assertEquals('2', $this->_json[0]->openings[0]->jour_semaine); } + + + /** @test */ + public function annecyShouldHaveOpeningHoursTuesday10to12ClosedOnPublicHolidays() { + $this->assertEquals('Tu 10:00-12:00; PH off', $this->_json[0]->opening_hours); + } } diff --git a/tests/application/modules/opac/controllers/BibControllerTest.php b/tests/application/modules/opac/controllers/BibControllerTest.php index 9c5b0a0b8c160207ac3770131dbfbeff8fc184a4..2e2cb69dfd4cf236e1c359f47b1971ef737aa003 100644 --- a/tests/application/modules/opac/controllers/BibControllerTest.php +++ b/tests/application/modules/opac/controllers/BibControllerTest.php @@ -936,6 +936,139 @@ class BibControllerBibViewAnnecyRangeOpeningsTest extends BibControllerLibraryWi +class BibControllerOpeningHoursAnnecyTest extends BibControllerWithZoneTestCase { + public function setUp() { + parent::setUp(); + + Class_Ouverture_Visitor::setTimeSource(new TimeSourceForTest('2019-12-11 09:16:55')); + + // open on mondays + $this->fixture('Class_Ouverture', + ['id' => 33, + 'id_site' => 4, + 'jour_semaine' => Class_Ouverture::LUNDI, + 'debut_matin' => '00:00', + 'fin_matin' => '00:00', + 'debut_apres_midi' => '18:00', + 'fin_apres_midi' => '19:30', + ]); + + // open on tuesdays + $this->fixture('Class_Ouverture', + ['id' => 34, + 'id_site' => 4, + 'jour_semaine' => Class_Ouverture::MARDI, + 'debut_matin' => '09:00', + 'fin_matin' => '12:00', + 'debut_apres_midi' => '13:00', + 'fin_apres_midi' => '19:30', + ]); + + // open on day + $this->fixture('Class_Ouverture', + ['id' => 35, + 'id_site' => 4, + 'jour' => '2019-12-11', + 'debut_matin' => '09:00', + 'fin_matin' => '12:00', + 'debut_apres_midi' => '00:00', + 'fin_apres_midi' => '00:00', + ]); + + // open on period + $this->fixture('Class_Ouverture', + ['id' => 36, + 'id_site' => 4, + 'jour_semaine' => Class_Ouverture::LUNDI, + 'debut_matin' => '09:00', + 'fin_matin' => '12:00', + 'debut_apres_midi' => '00:00', + 'fin_apres_midi' => '00:00', + 'validity_start' => '2019-12-01', + 'validity_end' => '2019-12-31' + ]); + + // closed on day + $this->fixture('Class_Ouverture', + ['id' => 37, + 'id_site' => 4, + 'jour' => '2019-12-24', + 'debut_matin' => '00:00', + 'fin_matin' => '00:00', + 'debut_apres_midi' => '00:00', + 'fin_apres_midi' => '00:00', + ]); + + // closed on period + $this->fixture('Class_Ouverture', + ['id' => 38, + 'id_site' => 4, + 'jour_semaine' => Class_Ouverture::LUNDI, + 'debut_matin' => '00:00', + 'fin_matin' => '00:00', + 'debut_apres_midi' => '00:00', + 'fin_apres_midi' => '00:00', + 'validity_start' => '2019-12-19', + 'validity_end' => '2019-12-23' + ]); + + Class_Bib::find(4)->setClosedOnHolidays(true)->assertSave(); + + $this->dispatch('bib/opening_hours/id/4', true); + } + + + public function tearDown() { + Class_Ouverture_Visitor::setTimeSource(null); + parent::tearDown(); + } + + + /** @test */ + public function shouldContainsEachMonday() { + $this->assertContains('Mo 18:00-19:30', $this->_response->getBody()); + } + + + /** @test */ + public function shouldContainsEachTuesday() { + $this->assertContains('Tu 09:00-12:00,13:00-19:30', $this->_response->getBody()); + } + + + /** @test */ + public function shouldContains2019_12_11() { + $this->assertContains('2019 Dec 11 09:00-12:00', $this->_response->getBody()); + } + + + /** @test */ + public function shouldContainsMondayFrom01_12To31_12() { + $this->assertContains('2019 Dec 01-2019 Dec 31 Mo 09:00-12:00', $this->_response->getBody()); + } + + + /** @test */ + public function shouldBeClosedOn2019_12_24() { + $this->assertContains('2019 Dec 24 off', $this->_response->getBody()); + } + + + /** @test */ + public function shouldBeClosedMondayFrom19_12To23_12() { + $this->assertContains('2019 Dec 19-2019 Dec 23 Mo off', $this->_response->getBody()); + } + + + /** @test */ + public function shouldBeClosedOnHolidays() { + $this->assertContains('PH off', $this->_response->getBody()); + } +} + + + + class BibControllerBibViewAnnecyWithOutdatedRangeOpeningsTest extends BibControllerBibViewTestCase { public function setUp() { parent::setUp();