From 3ed35cc2eb1dd84748accf1119101272d3d88b3c Mon Sep 17 00:00:00 2001 From: Laurent Laffont <llaffont@afi-sa.fr> Date: Tue, 26 Nov 2019 16:59:36 +0100 Subject: [PATCH] dev#100949 : import events from openagenda (JSON Format) --- FEATURES/100949 | 10 + VERSIONS_WIP/100949 | 1 + .../controllers/ExternalAgendasController.php | 9 +- cosmogramme/sql/patch/patch_381.php | 10 + library/Class/Batch/ExternalAgenda.php | 1 - library/Class/ExternalAgenda.php | 100 ++++- .../ICalendar.php | 32 +- library/Class/ExternalAgenda/OpenAgenda.php | 206 ++++++++++ library/Class/ExternalAgenda/Provider.php | 44 +++ library/ZendAfi/Form/Admin/ExternalAgenda.php | 23 +- .../controllers/RecordCustomLinksTest.php | 14 +- tests/db/UpgradeDBTest.php | 23 ++ .../ExternalAgendasBatchTest.php | 12 +- .../ExternalAgendasOpenAgendaTest.php | 220 +++++++++++ .../ExternalAgendas/ExternalAgendasTest.php | 40 +- .../ExternalAgendas/open-agenda-1.json | 136 +++++++ .../ExternalAgendas/open-agenda-2.json | 374 ++++++++++++++++++ .../ExternalAgendas/open-agenda-3.json | 7 + 18 files changed, 1206 insertions(+), 56 deletions(-) create mode 100644 FEATURES/100949 create mode 100644 VERSIONS_WIP/100949 create mode 100644 cosmogramme/sql/patch/patch_381.php rename library/Class/{WebService => ExternalAgenda}/ICalendar.php (88%) create mode 100644 library/Class/ExternalAgenda/OpenAgenda.php create mode 100644 library/Class/ExternalAgenda/Provider.php create mode 100644 tests/scenarios/ExternalAgendas/ExternalAgendasOpenAgendaTest.php create mode 100644 tests/scenarios/ExternalAgendas/open-agenda-1.json create mode 100644 tests/scenarios/ExternalAgendas/open-agenda-2.json create mode 100644 tests/scenarios/ExternalAgendas/open-agenda-3.json diff --git a/FEATURES/100949 b/FEATURES/100949 new file mode 100644 index 00000000000..125c0094fac --- /dev/null +++ b/FEATURES/100949 @@ -0,0 +1,10 @@ + '100949' => + ['Label' => $this->_('Intégration d'Evénements publiés dans un Open-Agenda (JSON)'), + 'Desc' => $this->_('Les Calendriers Externes au format OpenAgenda peuvent être importés.'), + 'Image' => '', + 'Video' => 'https://youtube.com/watch?v=5leZZihcMKk', + 'Category' => $this->_('Import Calendriers'), + 'Right' => function($feature_description, $user) {return true;}, + 'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php?title=Agendas_Externes', + 'Test' => 'http://git.afi-sa.fr/afi/opacce/merge_requests/3329', + 'Date' => '2019-11-26'], \ No newline at end of file diff --git a/VERSIONS_WIP/100949 b/VERSIONS_WIP/100949 new file mode 100644 index 00000000000..d389b2730a5 --- /dev/null +++ b/VERSIONS_WIP/100949 @@ -0,0 +1 @@ + - ticket #100949 : Agenda Externe : Intégration d'événements publiés sur Open-Agenda (format JSON) \ No newline at end of file diff --git a/application/modules/admin/controllers/ExternalAgendasController.php b/application/modules/admin/controllers/ExternalAgendasController.php index 0c78b90ddca..d72ef5435ff 100644 --- a/application/modules/admin/controllers/ExternalAgendasController.php +++ b/application/modules/admin/controllers/ExternalAgendasController.php @@ -32,9 +32,12 @@ class Admin_ExternalAgendasController extends ZendAfi_Controller_Action { return $this->_redirectToIndex(); $this->view->titre = $this->_('Moissonnage des évènements de l\'agenda "%s"', $agenda->getLibelle()); - $results = $agenda->import(); - $this->view->new_events = $results['new']; - $this->view->updated_events = $results['update']; + + $agenda->import(function($created, $updated) + { + $this->view->new_events = $created; + $this->view->updated_events = $updated; + }); } diff --git a/cosmogramme/sql/patch/patch_381.php b/cosmogramme/sql/patch/patch_381.php new file mode 100644 index 00000000000..1149b2094d1 --- /dev/null +++ b/cosmogramme/sql/patch/patch_381.php @@ -0,0 +1,10 @@ +<?php +$adapter = Zend_Db_Table_Abstract::getDefaultAdapter(); +try { + $adapter->query("alter table external_agenda + add column `provider` text not null default ''" ); +} catch (Exception $e) {} +try { + $adapter->query("alter table external_agenda + add column `delete_orphan_events` tinyint(1) not null default 0" ); +} catch (Exception $e) {} diff --git a/library/Class/Batch/ExternalAgenda.php b/library/Class/Batch/ExternalAgenda.php index f69be445238..268c1ada38a 100644 --- a/library/Class/Batch/ExternalAgenda.php +++ b/library/Class/Batch/ExternalAgenda.php @@ -31,4 +31,3 @@ class Class_Batch_ExternalAgenda extends Class_Batch_Abstract { Class_ExternalAgenda::harvest($this->getLogger()); } } -?> \ No newline at end of file diff --git a/library/Class/ExternalAgenda.php b/library/Class/ExternalAgenda.php index a3584003760..7a97ee78788 100644 --- a/library/Class/ExternalAgenda.php +++ b/library/Class/ExternalAgenda.php @@ -21,22 +21,32 @@ class Class_ExternalAgendaLoader extends Storm_Model_Loader { + use Trait_Translator; public function harvest($logger) { - foreach (Class_ExternalAgenda::findAllBy(['autoharvest' => 1]) as $agenda) { - $results = $agenda->import(); - $logger->log($agenda->getLabel().":\n"); - $logger->log($agenda->_("Nombre d\'événements créés : %s\n",count($results['new']))); - Class_AdminVar::get('AGENDA_KEEP_LOCAL_CONTENT') - ? $logger->log($agenda->_("Nombre d\'événements non mis à jour : %s\n",count($results['update']))) - : $logger->log($agenda->_("Nombre d\'événements mis à jour : %s\n",count($results['update']))); - } + foreach (Class_ExternalAgenda::findAllBy(['autoharvest' => 1]) as $agenda) + $agenda + ->import(function($created, $updated) use($logger, $agenda) + { + $agenda->logImportOn($logger, $created->count(), $updated->count()); + }); + } + + + public function getAllProviderOptions() { + return [ Class_ExternalAgenda::ICALENDAR => $this->_('iCalendar (.ics)'), + Class_ExternalAgenda::OPEN_AGENDA => $this->_('OpenAgenda (.json)')]; } } + + class Class_ExternalAgenda extends Storm_Model_Abstract { - use Trait_Translator; + use Trait_Translator, Trait_TimeSource; + + const ICALENDAR = 1; + const OPEN_AGENDA = 2; protected $_table_name = 'external_agenda'; protected $_belongs_to = ['category' => ['model' => 'Class_ArticleCategorie', @@ -47,6 +57,13 @@ class Class_ExternalAgenda extends Storm_Model_Abstract { protected $_loader_class = 'Class_ExternalAgendaLoader'; + protected $_default_attribute_values = + [ + 'provider'=> self::ICALENDAR, + 'delete_orphan_events' => 0 + ]; + + public function getLibelle() { return $this->getLabel(); } @@ -59,17 +76,68 @@ class Class_ExternalAgenda extends Storm_Model_Abstract { } - public function import() { - $service = new Class_WebService_ICalendar(); + public function import($after_import=null) { + $service = $this->_newProvider(); $events = $service->import($this); - $results['new'] = $events->select('isNew'); - $results['update'] = $events->reject('isNew'); - $results['new']->eachDo('save'); + $events->eachDo([$this, 'deduplicateEvent']); + + $created = $events->select('isNew'); + $updated = $events->reject('isNew'); + + // must be after updated detection + $created->eachDo(function($model) + { + $model + ->setDateCreation(date('Y-m-d H:i:s', $this->getCurrentTime())) + ->save(); + }); if (!Class_AdminVar::get('AGENDA_KEEP_LOCAL_CONTENT')) - $results['update']->eachDo('save'); + $updated->eachDo(function ($model) { $model->updateDateMaj()->save(); }); + + if ($this->getDeleteOrphanEvents()) + $this->_deleteOrphanEvents($events); + + if ($after_import) + $after_import($created, $updated); + } + + + public function logImportOn($logger, $created_count, $updated_count) { + $logger->log($this->getLabel().":\n"); + $logger->log($this->_("Nombre d'événements créés : %s\n", $created_count)); + + $message = Class_AdminVar::get('AGENDA_KEEP_LOCAL_CONTENT') + ? $this->_("Nombre d'événements non mis à jour : %s\n", $updated_count) + : $this->_("Nombre d'événements mis à jour : %s\n", $updated_count); + + $logger->log($message); + } + + + public function deduplicateEvent($event){ + if ($existing_event = $this->findEventByUID($event->getIdOrigine())) + $event->setId($existing_event->getId()); + } + + + protected function _newProvider() { + $map = [static::OPEN_AGENDA => 'Class_ExternalAgenda_OpenAgenda', + static::ICALENDAR => 'Class_ExternalAgenda_ICalendar']; + + return array_key_exists($this->getProvider(), $map) + ? new $map[$this->getProvider()] + : new Class_ExternalAgenda_Provider(); + } + + + protected function _deleteOrphanEvents($updated_events) { + $delete_params = ['repository_origine' => $this->getRepositoryKey()]; + + if (!$updated_events->isEmpty()) + $delete_params['ID_ARTICLE not'] = $updated_events->collect('id')->getArrayCopy(); - return $results; + Class_Article::deleteBy($delete_params); } diff --git a/library/Class/WebService/ICalendar.php b/library/Class/ExternalAgenda/ICalendar.php similarity index 88% rename from library/Class/WebService/ICalendar.php rename to library/Class/ExternalAgenda/ICalendar.php index 1f5f9a74e43..a68eb95f3b6 100644 --- a/library/Class/WebService/ICalendar.php +++ b/library/Class/ExternalAgenda/ICalendar.php @@ -20,34 +20,25 @@ */ -class Class_WebService_ICalendar extends Class_WebService_Abstract { - use Trait_Translator; - +class Class_ExternalAgenda_ICalendar extends Class_ExternalAgenda_Provider { protected - $_events, $_current_event, - $_current_url, - $_external_agenda; + $_current_url; - public function import($external_agenda) { - $this->_external_agenda = $external_agenda; - $this->_events = new Storm_Model_Collection(); - $this->_current_event = null; + protected function _import() { + $this->_current_event = null; - $ics_content = $this->httpGet($external_agenda->getUrl()); + $ics_content = $this->httpGet($this->_external_agenda->getUrl()); $ics_content = preg_replace('|\n\s|', '', $ics_content); //see RFC2445 $lines = preg_split('|\r?\n|', $ics_content); array_map([$this, '_importLine'], $lines); - return $this->_events; + return $this; } - public function __call($method, $params) {} - - protected function _importLine($line) { if (!$line) return $this; @@ -59,6 +50,14 @@ class Class_WebService_ICalendar extends Class_WebService_Abstract { } + public function __call($name, $args) { + if ('on' == substr($name, 0, 2)) + return; + + throw new RuntimeException('Call to undefined method Class_ExternalAgenda_ICalendar::' . $name); + } + + protected function _importData($key, $value) { if (in_array($key, ['BEGIN', 'END'])) return $this->{'on' . $key . $value}(); @@ -116,9 +115,6 @@ class Class_WebService_ICalendar extends Class_WebService_Abstract { protected function onEventUID($value) { $value = md5($value); - if ($existing_event = $this->_external_agenda->findEventByUID($value)) - $this->_current_event->setId($existing_event->getId()); - $this->_current_event->setIdOrigine($value); $this->_events->append($this->_current_event); diff --git a/library/Class/ExternalAgenda/OpenAgenda.php b/library/Class/ExternalAgenda/OpenAgenda.php new file mode 100644 index 00000000000..f5f3f7a2307 --- /dev/null +++ b/library/Class/ExternalAgenda/OpenAgenda.php @@ -0,0 +1,206 @@ +<?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_ExternalAgenda_OpenAgenda extends Class_ExternalAgenda_Provider { + + + public function _import() { + $this->_loadPage(); + return $this; + } + + + protected function _loadPage($offset = 0, $event_count = 0){ + $content = json_decode($this->httpGet($this->_external_agenda->getUrl().'&offset='. (int) $offset),true); + + if (!$content) + return $this; + + array_map([$this, '_processEvent'], $content['events']); + $event_count += count($content['events']); + + return ($event_count < $content['total']) + ? $this->_loadPage( $offset + 1, $event_count) + : $this; + } + + + protected function _processEvent($datas){ + $event = new Class_ExternalAgenda_OpenAgenda_Event($datas); + + foreach($event->get('timings') as $timing) { + $article = $this->_buildArticleForTiming($event, $timing); + $this->_events->append($article); + } + + return $this; + } + + + protected function _buildArticleForTiming($event, $timing) { + return $this->_external_agenda + ->newEvent() + ->setTitre($event->getString('title')) + ->setContenu($event->getImageTagWithCredits().$event->getHtml().$event->getInfosTag()) + ->setIdOrigine($event->get('uid') . '_' . base64_encode($timing['start'])) + ->setDescription($event->getImageTag().'<p>'.$event->getString('description').'</p>') + ->setTags(implode(';', $event->getKeywords())) + ->setLieu($event->getLocation()) + ->setEventsDebut(date('Y-m-d H:i', strtotime($timing['start']))) + ->setEventsFin(date('Y-m-d H:i', strtotime($timing['end']))); + } +} + + + + +class Class_ExternalAgenda_OpenAgenda_Event { + use Trait_Translator; + + protected + $_event; + + public function __construct($event){ + $this->_event = $event; + } + + public function getImageTagWithCredits(){ + $imgsrc = $this->getImageTag(); + return ($credits = $this->getString('imageCredits')) + ? sprintf('<figure>%s<figcaption>%s</figcaption></figure>', + $imgsrc, + $this->_('Credits : %s', $credits)) + : $imgsrc; + } + + + public function getImageTag(){ + return ($src = $this->_event['image']) + ? sprintf('<img src="%s" alt=""/>',$src) + : ''; + } + + + public function getInfosTag() { + $infos = ''; + + $infos .= $this->_prepareConditionsString(); + $infos .= $this->_prepareAgeString(); + + if (!$infos) + return ''; + + return '<p>' . $this->_('Infos pratiques :') . '</p><dl>' . $infos . '</dl>'; + } + + protected function _addInfoElement($label, $description){ + return '<dt>' . $label . '</dt>' + . '<dd>' . $description . '</dd>'; + } + + + public function getKeywords(){ + if (! $keywords = $this->getArray('keywords')) + return []; + + return isset( $keywords['fr'] ) + ? $keywords['fr'] + : []; + } + + protected function _prepareConditionsString() { + return ($conditions = $this->getString('conditions')) + ? $this->_addInfoElement( + $this->_('Conditions'), + $conditions) + : ""; + } + + + protected function _prepareAgeString() { + return ($this->_event['age']) + ? $this->_addInfoElement( + $this->_('Âge'), + $this->_('de %s à %s ans', + $this->_event['age']['min'], + $this->_event['age']['max'])) + : ""; + } + + + + + public function getString($name){ + if (!(isset($this->_event[$name]) && $this->_event[$name])) + return ''; + + if (is_string($this->_event[$name])) + return $this->_event[$name]; + + return is_array($this->_event[$name]) + ? reset($this->_event[$name]) + : ''; + } + + + public function getArray($name){ + return ($data = $this->get($name)) && is_array($data) + ? $data + : []; + } + + + public function get($name) { + return $this->_event[$name]; + } + + + public function getHtml() { + return ($description_html = $this->getString('html')) + ? $description_html + : $this->getString('description'); + } + + + public function getLocation(){ + if ($lieu = Class_Lieu::findFirstBy(['latitude' => $this->get('latitude'), + 'longitude' => $this->get('longitude')])) + return $lieu; + + $lieu = new Class_Lieu(); + $lieu + ->setLibelle($this->getString('locationName')) + ->setLatitude($this->get('latitude')) + ->setLongitude($this->get('longitude')) + ->setAdresse(implode(',', explode(',', $this->getString('address'), -1))) + ->setCodePostal($this->getString('postalCode')) + ->setVille($this->getString('city')); + + $location = $this->getArray('location'); + $lieu + ->setTelephone(isset($location['phone']) ? (string)$location['phone'] : '') + ->setMail(isset($location['email']) ? (string)$location['email'] : '') + ->setUrl(isset($location['website']) ? (string)$location['website'] : '') + ->save(); + return $lieu; + } +} diff --git a/library/Class/ExternalAgenda/Provider.php b/library/Class/ExternalAgenda/Provider.php new file mode 100644 index 00000000000..31bc1e91460 --- /dev/null +++ b/library/Class/ExternalAgenda/Provider.php @@ -0,0 +1,44 @@ +<?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_ExternalAgenda_Provider extends Class_WebService_Abstract { + use Trait_Translator; + + protected + $_events, + $_external_agenda; + + + public function import($external_agenda) { + $this->_events = new Storm_Model_Collection(); + $this->_external_agenda = $external_agenda; + + $this->_import(); + + return $this->_events; + } + + + protected function _import() { + return $this; + } +} diff --git a/library/ZendAfi/Form/Admin/ExternalAgenda.php b/library/ZendAfi/Form/Admin/ExternalAgenda.php index 295cf12deba..fedf9843e09 100644 --- a/library/ZendAfi/Form/Admin/ExternalAgenda.php +++ b/library/ZendAfi/Form/Admin/ExternalAgenda.php @@ -42,6 +42,10 @@ class ZendAfi_Form_Admin_ExternalAgenda extends ZendAfi_Form { 'autoharvest', ['label' => $this->_('Moissonnage automatique'), ]) + ->addElement('checkbox', + 'delete_orphan_events', + ['label' => $this->_('Supprimer localement les événements supprimés dans le calendrier source'), + ]) ->addElement('comboCategories', 'cat_id', @@ -54,9 +58,22 @@ class ZendAfi_Form_Admin_ExternalAgenda extends ZendAfi_Form { ->addElement('select', 'id_lieu', ['label' => $this->_('Lieu'), - 'multiOptions' => ['0' => $this->_('Aucun')] + Class_Lieu::getAllLibelles()]); + 'multiOptions' => ['0' => $this->_('Aucun')] + Class_Lieu::getAllLibelles()]) + ->addElement('select', + 'provider', + ['label' => $this->_('Source'), + 'multiOptions' => Class_ExternalAgenda::getAllProviderOptions()]); - $elements = ['label', 'url','autoharvest', 'cat_id', 'id_lieu']; + $elements = + [ + 'label', + 'provider', + 'url', + 'autoharvest', + 'delete_orphan_events', + 'cat_id', + 'id_lieu' + ]; if (Class_AdminVar::isWorkFlowEnabled()) { $this->addElement('radio', 'status', @@ -68,5 +85,7 @@ class ZendAfi_Form_Admin_ExternalAgenda extends ZendAfi_Form { } $this->addDisplayGroup($elements, 'agenda', ['legend' => $this->_('Agenda')]); + Class_ScriptLoader::getInstance() + ->addJqueryReady('formSelectToggleVisibilityForElement( "#provider", $("select[name=\'id_lieu\']").closest("tr"), ["1"]);'); } } diff --git a/tests/application/modules/opac/controllers/RecordCustomLinksTest.php b/tests/application/modules/opac/controllers/RecordCustomLinksTest.php index 616be8c87f6..19c105fa14f 100644 --- a/tests/application/modules/opac/controllers/RecordCustomLinksTest.php +++ b/tests/application/modules/opac/controllers/RecordCustomLinksTest.php @@ -118,6 +118,8 @@ class RecordCustomLinksRechercheControllerWithBrazilTest extends RecordCustomLin public function setUp() { parent::setUp(); + Class_AdminVar::set('FEATURES_TRACKING_ENABLE', '0'); + $notice = $this->fixture('Class_Notice', ['id' => '888', 'type_doc' => Class_CodifTypeDoc::SONORE, 'unimarc' => file_get_contents(__DIR__ . '/../../../../fixtures/unimarc_brazil.txt')]); @@ -128,9 +130,17 @@ class RecordCustomLinksRechercheControllerWithBrazilTest extends RecordCustomLin 'liste_codes' => "TAN98"], 'viewnotice3' => ['links_zones' => '856-u-a']]]); + + $this->mock_sql = $this->mock() + ->whenCalled('fetchAll') + ->answers([ [888, ''] ]); + Zend_Registry::set('sql', $this->mock_sql); + Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice') - ->whenCalled('findAllBy') - ->answers([Class_Notice::find(888)]); + ->whenCalled('findAllByIds') + ->with([888], 10, null) + ->answers([Class_Notice::find(888)]) + ->beStrict(); $this->dispatch('/opac/recherche/simple/expressionRecherche/brazil'); } diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php index 956a2e1ec61..5cbf84e6960 100644 --- a/tests/db/UpgradeDBTest.php +++ b/tests/db/UpgradeDBTest.php @@ -2967,3 +2967,26 @@ class UpgradeDB_380_Test extends UpgradeDBTestCase { $this->assertIndex('hold_pnb', 'subscriber_id'); } } + + + + +class UpgradeDB_381_Test extends UpgradeDBTestCase { + public function prepare() { + $this + ->silentQuery('ALTER TABLE external_agenda DROP COLUMN provider') + ->silentQuery('ALTER TABLE external_agenda DROP COLUMN delete_orphan_events'); + } + + + /** @test */ + public function tableExternalAgendasShouldHaveColumnProviderText() { + $this->assertFieldType('external_agenda', 'provider', 'text'); + } + + + /** @test */ + public function tableExternalAgendasShouldHaveColumnDeleteOrphanEvents() { + $this->assertFieldType('external_agenda', 'delete_orphan_events', 'tinyint(1)'); + } +} diff --git a/tests/scenarios/ExternalAgendas/ExternalAgendasBatchTest.php b/tests/scenarios/ExternalAgendas/ExternalAgendasBatchTest.php index ac3c5ee4e20..078f97bbca1 100644 --- a/tests/scenarios/ExternalAgendas/ExternalAgendasBatchTest.php +++ b/tests/scenarios/ExternalAgendas/ExternalAgendasBatchTest.php @@ -67,6 +67,7 @@ class Class_Batch_ExternalAgendasBatchSimpleTest extends Class_Batch_ExternalAge 'label' => 'Personal Agenda', 'url' => 'http://my.server.com/calendar.ics', 'autoharvest' => 0, + 'delete_orphan_events' => 0, 'status' => Class_Article::STATUS_VALIDATED, 'category' => $events_category]); @@ -77,9 +78,10 @@ class Class_Batch_ExternalAgendasBatchSimpleTest extends Class_Batch_ExternalAge 'category' => $events_category, 'id_lieu' => 0, 'autoharvest' => 1, + 'delete_orphan_events' => 0, 'status' => Class_Article::STATUS_VALIDATED]); - Class_WebService_ICalendar::setDefaultHttpClient($this->mock() + Class_ExternalAgenda_ICalendar::setDefaultHttpClient($this->mock() ->whenCalled('open_url') ->with('http://external.agenda.com/export?type=ics') ->answers(file_get_contents(__DIR__.'/ical-event-second.ics'))); @@ -98,7 +100,7 @@ class Class_Batch_ExternalAgendasBatchSimpleTest extends Class_Batch_ExternalAge /** @test */ public function logShouldDisplayNumberOfCreatedEvents() { - $this->assertEquals("Extra Agenda:\nNombre d\'événements créés : 4\nNombre d\'événements mis à jour : 0\n",$this->_log); + $this->assertEquals("Extra Agenda:\nNombre d'événements créés : 4\nNombre d'événements mis à jour : 0\n",$this->_log); } } @@ -156,7 +158,7 @@ class Class_Batch_ExternalAgendasBatchRewriteTest extends Class_Batch_ExternalAg 'autoharvest' => 1, 'status' => Class_Article::STATUS_VALIDATED]); - Class_WebService_ICalendar::setDefaultHttpClient($this->mock() + Class_ExternalAgenda_ICalendar::setDefaultHttpClient($this->mock() ->whenCalled('open_url') ->with('http://external.agenda.com/export?type=ics') ->answers(file_get_contents(__DIR__.'/ical-event-second.ics'))); @@ -177,7 +179,7 @@ class Class_Batch_ExternalAgendasBatchRewriteTest extends Class_Batch_ExternalAg /** @test */ public function logShouldDisplayNumberOfCreatedEvents() { $this->_batch->run(); - $this->assertEquals("Extra Agenda:\nNombre d\'événements créés : 3\nNombre d\'événements non mis à jour : 1\n",$this->_log); + $this->assertEquals("Extra Agenda:\nNombre d'événements créés : 3\nNombre d'événements non mis à jour : 1\n",$this->_log); } @@ -196,6 +198,6 @@ class Class_Batch_ExternalAgendasBatchRewriteTest extends Class_Batch_ExternalAg $this->_batch->run(); Class_Article::clearCache(); $this->assertContains("animé par l'école et son quartier Parents/Enfants",Class_Article::find(1)->getContenu()); - $this->assertEquals("Extra Agenda:\nNombre d\'événements créés : 3\nNombre d\'événements mis à jour : 1\n",$this->_log); + $this->assertEquals("Extra Agenda:\nNombre d'événements créés : 3\nNombre d'événements mis à jour : 1\n",$this->_log); } } diff --git a/tests/scenarios/ExternalAgendas/ExternalAgendasOpenAgendaTest.php b/tests/scenarios/ExternalAgendas/ExternalAgendasOpenAgendaTest.php new file mode 100644 index 00000000000..572c63b31f3 --- /dev/null +++ b/tests/scenarios/ExternalAgendas/ExternalAgendasOpenAgendaTest.php @@ -0,0 +1,220 @@ +<?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 ExternalAgendasOpenAgendaAdminTest extends Admin_AbstractControllerTestCase { + protected $_storm_default_to_volatile=true; + + public function setup() { + parent::setup(); + + + + $events_category = $this->fixture('Class_ArticleCategorie', + ['id'=>123, + 'libelle' => 'Coding Gouter', + 'bib'=> $this->fixture('Class_Bib', + ['id'=>7, + 'libelle'=>'Joliville'])]); + + Class_AdminVar::set('AGENDA_KEEP_LOCAL_CONTENT',0); + + $time_source = new TimeSourceForTest('2019-11-01 08:00:00'); + + Class_ExternalAgenda::setTimeSource($time_source); + Class_Article::setTimeSource($time_source); + + $this->fixture('Class_ExternalAgenda', + [ 'id' => 12, + 'label' => 'agenda PNB', + 'url' => 'https://openagenda.com/agendas/36758196/events.json?lang=fr&key=e0f9e6b4302a439f99b78549b9d63fd6', + 'provider' => Class_ExternalAgenda::OPEN_AGENDA, + 'status' => Class_Article::STATUS_DRAFT, + 'delete_orphan_events' => 1, + 'category' => $events_category]); + Class_ExternalAgenda_OpenAgenda::setDefaultHttpClient($this->mock() + ->whenCalled('open_url') + ->with('https://openagenda.com/agendas/36758196/events.json?lang=fr&key=e0f9e6b4302a439f99b78549b9d63fd6&offset=0') + ->answers(file_get_contents(__DIR__.'/open-agenda-1.json')) + ->whenCalled('open_url') + ->with('https://openagenda.com/agendas/36758196/events.json?lang=fr&key=e0f9e6b4302a439f99b78549b9d63fd6&offset=1') + ->answers(file_get_contents(__DIR__.'/open-agenda-2.json')) + ->whenCalled('open_url') + ->with('https://openagenda.com/agendas/36758196/events.json?lang=fr&key=e0f9e6b4302a439f99b78549b9d63fd6&offset=2') + ->answers(file_get_contents(__DIR__.'/open-agenda-3.json')) + ->beStrict() + ); + + Class_ExternalAgenda::find(12)->import(); + } + + public function teardown() { + Class_ExternalAgenda::setTimeSource(null); + Class_Article::setTimeSource(null); + parent::tearDown(); + } + + + /** @test */ + public function countArticlesShouldBeSixteen() { + $this->assertCount(16, Class_Article::findAll()); + } + + + /** @test */ + public function firstArticleCreationDateShouldContains20191101() { + $this->assertContains('2019-11-01', Class_Article::find(1)->getDateCreation()); + } + + + /** @test */ + public function firstArticleDateMajShouldBeEmpty() { + $this->assertEquals('', Class_Article::find(1)->getDateMaj()); + } + + + /** @test */ + public function afterSecondImportCountArticlesShouldRemainsSixteen() { + Class_ExternalAgenda::find(12)->import(); + $this->assertCount(16, Class_Article::findAll()); + } + + + /** @test */ + public function afterImportOldArticleShouldBeDeleted() { + $this->fixture('Class_Article', + ['id' => 234, + 'titre' => 'Test', + 'description' => 'test', + 'contenu' => 'test', + 'repository_origine' => Class_Article::find(2)->getRepositoryOrigine() + ] + ); + Class_ExternalAgenda::find(12)->import(); + $this->assertCount(16, Class_Article::findAll()); + $this->assertNull(Class_Article::find(234)); + } + + + /** @test */ + public function afterImportWhenNotDeleteOrphanOldArticleShouldBePresent() { + Class_ExternalAgenda::find(12)->setDeleteOrphanEvents(0); + $this->fixture('Class_Article', + ['id' => 234, + 'titre' => 'Test', + 'description' => 'test', + 'contenu' => 'test', + 'repository_origine' => Class_Article::find(2)->getRepositoryOrigine() + ] + ); + Class_ExternalAgenda::find(12)->import(); + $this->assertCount(17, Class_Article::findAll()); + $this->assertNotNull(Class_Article::find(234)); + } + + + /** @test */ + public function firstArticleTitleShouldContainsPNB() { + $this->assertContains('PNB', Class_Article::find(1)->getTitre()); + } + + + /** @test */ + public function firstArticleDateMajShouldBe20191101() { + Class_ExternalAgenda::find(12)->import(); + Class_Article::clearCache(); + $this->assertEquals('2019-11-01 08:00:00', Class_Article::find(1)->getDateMaj()); + } + + + /** @test */ + public function firstArticleTagsShouldBeBugAndPNB() { + $this->assertEquals('bug;pnb', Class_Article::find(1)->getTags()); + } + + + /** @test */ + public function firstArticleSummaryShouldBeParfoisLesChosesNeFonctionnentPas() { + $this->assertEquals('<img src="https://cibul.s3.amazonaws.com/9c3729cce33140c5a011056c8168ec5b.base.image.jpg" alt=""/><p>parfois, les choses ne fonctionnent pas</p>', Class_Article::find(1)->getDescription()); + } + + + /** @test */ + public function firstArticleEventsDateShouldBe2019_11_25_10_30_To_12_30() { + $this->assertArraySubset(['events_debut' => '2019-11-25 10:30', + 'events_fin' => '2019-11-25 12:30'], + Class_Article::find(1)->getRawAttributes()); + } + + + /** @test */ + public function secondArticleEventsDateShouldBe2019_11_29_10_00_To_12_00() { + $this->assertArraySubset(['titre' => 'Une erreur PNB', + 'events_debut' => '2019-11-29 10:00', + 'events_fin' => '2019-11-29 12:00'], + Class_Article::find(2)->getRawAttributes()); + } + + + /** @test */ + public function secondArticleTitleShouldContainsPlanifBokeh() { + $this->assertContains('Planif Bokeh', Class_Article::find(4)->getTitre()); + } + + + + /** @test */ + public function numberOfLocationsShouldBeTwo() { + $this->assertCount(2, Class_Lieu::findAll()); + } + + + /** @test */ + public function secondLocationShouldBeAFIAnnecy() { + $this->assertEquals(['id' => 2, + 'libelle' => 'AFI Annecy', + 'adresse' => '11 boulevard du fier', + 'code_postal' => '74000', + 'ville' => 'Annecy', + 'pays' => 'FRANCE', + 'telephone' => '0123456789', + 'mail' => 'bokeh@afi-sa.net', + 'url' => 'https://www.afi-sa.net', + 'latitude' => 45.913614, + 'longitude' => 6.114212], + Class_Lieu::find(2)->getRawAttributes()); + } + + + + /** @test */ + public function firstArticleImageShouldContainsHTMLAndImage() { + $this->assertEquals('<figure><img src="https://cibul.s3.amazonaws.com/9c3729cce33140c5a011056c8168ec5b.base.image.jpg" alt=""/><figcaption>Credits : moi</figcaption></figure><p>Voyons ça dans une session de coding dojo</p><p>Infos pratiques :</p><dl><dt>Conditions</dt><dd>être geek</dd><dt>Âge</dt><dd>de 6 à 99 ans</dd></dl>', + Class_Article::find(1)->getContenu()); + } + + + /** @test */ + public function firstArticleIdOrigineShouldBe5519006_start_base64encoded() { + $this->assertEquals('5519006_MjAxOS0xMS0yNVQwOTozMDowMC4wMDBa', + Class_Article::find(1)->getIdOrigine()); + } +} diff --git a/tests/scenarios/ExternalAgendas/ExternalAgendasTest.php b/tests/scenarios/ExternalAgendas/ExternalAgendasTest.php index fb0581b801f..07475d01798 100644 --- a/tests/scenarios/ExternalAgendas/ExternalAgendasTest.php +++ b/tests/scenarios/ExternalAgendas/ExternalAgendasTest.php @@ -55,7 +55,7 @@ abstract class ExternalAgendasAdminTestCase extends Admin_AbstractControllerTest 'status' => Class_Article::STATUS_DRAFT, 'category' => $this->events_category]); - Class_WebService_ICalendar::setDefaultHttpClient($this->mock() + Class_ExternalAgenda_ICalendar::setDefaultHttpClient($this->mock() ->whenCalled('open_url') ->with('http://my.server.com/calendar.ics') ->answers(file_get_contents(__DIR__.'/ical-event-first.ics')) @@ -71,7 +71,7 @@ abstract class ExternalAgendasAdminTestCase extends Admin_AbstractControllerTest public function tearDown() { - Class_WebService_ICalendar::setDefaultHttpClient(null); + Class_ExternalAgenda_ICalendar::setDefaultHttpClient(null); parent::tearDown(); } } @@ -95,7 +95,7 @@ class ExternalAgendasAdminIndexTest extends ExternalAgendasAdminTestCase { 'cat_id' => 2]); Class_ArticleCategorie::find(2)->delete(); - Class_ExternalAgenda::find(125)->import($this); + Class_ExternalAgenda::find(125)->import(); $this->dispatch('/admin/external-agendas', true); } @@ -207,6 +207,17 @@ abstract class ExternalAgendasAdminAddTestCase extends ExternalAgendasAdminTestC 'Events'); } + /** @test */ + public function formShouldContainsSelectForProvider() { + $this->assertXPathContentContains('//form//select[@name="provider"]/option[@value="1"]','iCalendar (.ics)',$this->_response->getBody()); + } + + + /** @test */ + public function formShouldContainsCheckBoxDeleteOrphanEvents() { + $this->assertXPath('//form//input[@type="checkbox"][@name="delete_orphan_events"]'); + $this->assertXPathContentContains('//form//label[@for="delete_orphan_events"]','Supprimer localement les événements supprimés dans le calendrier source'); + } /** @test */ public function categorySelectorShouldContainsNoneOption() { @@ -525,7 +536,7 @@ class ExternalAgendasAdminDestroyEventsTest extends ExternalAgendasAdminTestCase 'cat_id' => 1, 'id_lieu' => 0, 'status' => Class_Article::STATUS_VALIDATED]); - Class_WebService_ICalendar::setDefaultHttpClient($this->mock() + Class_ExternalAgenda_ICalendar::setDefaultHttpClient($this->mock() ->whenCalled('open_url') ->with('http://external.agenda.com/export?type=ics') ->answers(file_get_contents(__DIR__.'/ical-event-second.ics')) @@ -533,8 +544,9 @@ class ExternalAgendasAdminDestroyEventsTest extends ExternalAgendasAdminTestCase Class_ExternalAgenda::find(34) ->setIdLieu(22) ->setStatus(Class_Article::STATUS_VALIDATED) - ->import($this); - $this->assertCount(4,Class_Article::findAllBy(['repository_origine' => 'External_Agenda:34'])); + ->import(); + + $this->assertCount(4, Class_Article::findAllBy(['repository_origine' => 'External_Agenda:34'])); $this->dispatch('/admin/external-agendas/destroy-events/id/34', true); } @@ -571,10 +583,20 @@ class ExternalAgendasAdminImportTest extends ExternalAgendasAdminTestCase { Class_AdminVar::set('WORKFLOW', 1); + $timesource = new TimeSourceForTest('2016-02-06 09:19:32'); + Class_Article::setTimeSource($timesource); + Class_ExternalAgenda::setTimeSource($timesource); + $this->dispatch('/admin/external-agendas/import/id/124', true); Class_Article::clearCache(); } + public function tearDown() { + Class_Article::setTimeSource(null); + Class_ExternalAgenda::setTimeSource(null); + parent::tearDown(); + } + /** @test */ public function titleShouldBeMoissonageDesEvenements() { @@ -708,9 +730,9 @@ class ExternalAgendasAdminSecondImportTest extends ExternalAgendasAdminTestCase Class_ExternalAgenda::find(124) ->setIdLieu(22) ->setStatus(Class_Article::STATUS_VALIDATED) - ->import($this); + ->import(); - Class_WebService_ICalendar::setDefaultHttpClient($this->mock() + Class_ExternalAgenda_ICalendar::setDefaultHttpClient($this->mock() ->whenCalled('open_url') ->with('http://my.server.com/calendar.ics') ->answers(file_get_contents(__DIR__.'/ical-event-second.ics')) @@ -769,7 +791,7 @@ class ExternalAgendasAdminErmesImportTest extends ExternalAgendasAdminTestCase { public function setUp() { parent::setUp(); - Class_WebService_ICalendar::setDefaultHttpClient($this->mock() + Class_ExternalAgenda_ICalendar::setDefaultHttpClient($this->mock() ->whenCalled('open_url') ->with('http://my.server.com/calendar.ics') ->answers(file_get_contents(__DIR__.'/ermes.ics')) diff --git a/tests/scenarios/ExternalAgendas/open-agenda-1.json b/tests/scenarios/ExternalAgendas/open-agenda-1.json new file mode 100644 index 00000000000..0a3068dea76 --- /dev/null +++ b/tests/scenarios/ExternalAgendas/open-agenda-1.json @@ -0,0 +1,136 @@ +{ + "readme": "Results are paginated. See: https://openagenda.zendesk.com/hc/fr/articles/203034982-L-export-JSON-d-un-agenda", + "total": 4, + "offset": 0, + "limit": 2, + "events": [ + { + "uid": 5519006, + "slug": "une-erreur-pnb", + "canonicalUrl": "https://openagenda.com/test-laurent/event/une-erreur-pnb", + "title": { + "fr": "Une erreur PNB" + }, + "description": { + "fr": "parfois, les choses ne fonctionnent pas" + }, + "longDescription": { + "fr": "Voyons ça dans une sessino de coding doja" + }, + "keywords": { + "fr": [ + "bug", + "pnb" + ] + }, + "html": { + "fr": "<p>Voyons ça dans une session de coding dojo</p>" + }, + "image": "https://cibul.s3.amazonaws.com/9c3729cce33140c5a011056c8168ec5b.base.image.jpg", + "thumbnail": "https://cibul.s3.amazonaws.com/9c3729cce33140c5a011056c8168ec5b.thumb.image.jpg", + "originalImage": "https://cibul.s3.amazonaws.com/9c3729cce33140c5a011056c8168ec5b.full.image.jpg", + "age": { + "min": 6, + "max": 99 + }, + "accessibility": [], + "updatedAt": "2019-11-26T14:12:06.000Z", + "createdAt": "2019-11-26T14:12:06.000Z", + "range": { + "fr": "25 novembre - 1 décembre", + "en": "25 November - 1 December" + }, + "imageCredits": "moi", + "origin": { + "uid": 36758196, + "title": "Test Laurent", + "url": "https://www.afi-sa.net", + "slug": "test-laurent", + "oaUrl": "https://openagenda.com/agendas/36758196" + }, + "conditions": { + "fr": "être geek" + }, + "registrationUrl": "llaffont@afi-sa.fr", + "locationName": "San pedro chez HDL", + "locationUid": 91517332, + "address": "San-Pédro", + "postalCode": null, + "city": null, + "district": null, + "department": "San Pedro", + "region": "Bas-Sassandra", + "latitude": 5.014152, + "longitude": -6.940224, + "timings": [ + { + "start": "2019-11-25T09:30:00.000Z", + "end": "2019-11-25T11:30:00.000Z" + }, + { + "start": "2019-11-29T09:00:00.000Z", + "end": "2019-11-29T11:00:00.000Z" + }, + { + "start": "2019-12-01T09:30:00.000Z", + "end": "2019-12-01T10:30:00.000Z" + } + ], + "location": { + "uid": 91517332, + "name": "San pedro chez HDL", + "slug": "san-pedro-chez-hdl", + "address": "San-Pédro", + "image": null, + "imageCredits": null, + "postalCode": null, + "city": null, + "district": null, + "department": "San Pedro", + "region": "Bas-Sassandra", + "latitude": 5.014152, + "longitude": -6.940224, + "description": null, + "access": null, + "countryCode": "ci", + "website": null, + "email": null, + "links": [], + "insee": null, + "phone": null, + "tags": null, + "timezone": "Africa/Abidjan", + "updatedAt": "2019-11-26T14:11:55.000Z", + "extId": null, + "country": { + "en": "Cote D'Ivoire", + "fr": "Cote D'Ivoire", + "de": "Elfenbeinküste", + "es": "Costa de Marfil", + "code": "CI" + } + }, + "registration": [ + { + "value": "llaffont@afi-sa.fr", + "type": "email", + "prefix": "mailto:" + } + ], + "firstDate": "2019-11-25", + "firstTimeStart": "09:30", + "firstTimeEnd": "11:30", + "lastDate": "2019-12-01", + "lastTimeStart": "09:30", + "lastTimeEnd": "10:30", + "featured": 0, + "hasPrivateCustomFields": false, + "custom": null, + "contributor": null, + "category": null, + "tags": [], + "tagGroups": [], + "linkedEvents": [] + } + ] +} diff --git a/tests/scenarios/ExternalAgendas/open-agenda-2.json b/tests/scenarios/ExternalAgendas/open-agenda-2.json new file mode 100644 index 00000000000..e01201dfb43 --- /dev/null +++ b/tests/scenarios/ExternalAgendas/open-agenda-2.json @@ -0,0 +1,374 @@ +{ + "readme": "Results are paginated. See: https://openagenda.zendesk.com/hc/fr/articles/203034982-L-export-JSON-d-un-agenda", + "total": 4, + "offset": 1, + "limit": 2, + "events": [ + { + "uid": 84528178, + "slug": "planif-bokeh", + "canonicalUrl": "https://openagenda.com/test-laurent/event/planif-bokeh", + "title": { + "fr": "Planif Bokeh" + }, + "description": { + "fr": "il faut planifier" + }, + "longDescription": { + "fr": "coucou" + }, + "keywords": null, + "html": { + "fr": "<p>coucou</p>" + }, + "image": "https://cibul.s3.amazonaws.com/47bdaf7b4fab4b08b933e53a38534dba.base.image.jpg", + "thumbnail": "https://cibul.s3.amazonaws.com/47bdaf7b4fab4b08b933e53a38534dba.thumb.image.jpg", + "originalImage": "https://cibul.s3.amazonaws.com/47bdaf7b4fab4b08b933e53a38534dba.full.image.jpg", + "age": null, + "accessibility": [], + "updatedAt": "2019-11-25T10:29:51.000Z", + "createdAt": "2019-11-25T10:25:06.000Z", + "range": { + "fr": "25 novembre 2019 - 27 janvier 2020, les lundis", + "en": "25 November 2019 - 27 January 2020, on mondays" + }, + "imageCredits": null, + "origin": { + "uid": 36758196, + "title": "Test Laurent", + "url": "https://www.afi-sa.net", + "slug": "test-laurent", + "oaUrl": "https://openagenda.com/agendas/36758196" + }, + "conditions": null, + "registrationUrl": null, + "locationName": "AFI Annecy", + "locationUid": 51796356, + "address": "11 boulevard du fier, 74000 Annecy", + "postalCode": "74000", + "city": "Annecy", + "district": "Annecy", + "department": "Haute-Savoie", + "region": "Auvergne-Rhône-Alpes", + "latitude": 45.913614, + "longitude": 6.114212, + "timings": [ + { + "start": "2019-11-25T10:00:00.000Z", + "end": "2019-11-25T10:30:00.000Z" + }, + { + "start": "2019-12-02T10:00:00.000Z", + "end": "2019-12-02T10:30:00.000Z" + }, + { + "start": "2019-12-09T10:00:00.000Z", + "end": "2019-12-09T10:30:00.000Z" + }, + { + "start": "2019-12-16T10:00:00.000Z", + "end": "2019-12-16T10:30:00.000Z" + }, + { + "start": "2019-12-23T10:00:00.000Z", + "end": "2019-12-23T10:30:00.000Z" + }, + { + "start": "2019-12-30T10:00:00.000Z", + "end": "2019-12-30T10:30:00.000Z" + }, + { + "start": "2020-01-06T10:00:00.000Z", + "end": "2020-01-06T10:30:00.000Z" + }, + { + "start": "2020-01-13T10:00:00.000Z", + "end": "2020-01-13T10:30:00.000Z" + }, + { + "start": "2020-01-20T10:00:00.000Z", + "end": "2020-01-20T10:30:00.000Z" + }, + { + "start": "2020-01-27T10:00:00.000Z", + "end": "2020-01-27T10:30:00.000Z" + } + ], + "location": { + "uid": 51796356, + "name": "AFI Annecy", + "slug": "afi-annecy", + "address": "11 boulevard du fier, 74000 Annecy", + "image": null, + "imageCredits": null, + "postalCode": "74000", + "city": "Annecy", + "district": "Annecy", + "department": "Haute-Savoie", + "region": "Auvergne-Rhône-Alpes", + "latitude": 45.913614, + "longitude": 6.114212, + "description": null, + "access": null, + "countryCode": "fr", + "website": "https://www.afi-sa.net", + "email": "bokeh@afi-sa.net", + "links": [], + "insee": "74010", + "phone": "0123456789", + "tags": null, + "timezone": "Europe/Paris", + "updatedAt": "2019-11-25T10:24:36.000Z", + "extId": null, + "country": { + "en": "France (Metropolitan)", + "fr": "France (Métropole)", + "de": "Frankreich (Metropolitan)", + "es": "Francia (Metropolitana)", + "code": "FR" + } + }, + "registration": [], + "firstDate": "2019-11-25", + "firstTimeStart": "11:00", + "firstTimeEnd": "11:30", + "lastDate": "2020-01-27", + "lastTimeStart": "11:00", + "lastTimeEnd": "11:30", + "featured": 0, + "hasPrivateCustomFields": false, + "custom": null, + "contributor": null, + "category": null, + "tags": [], + "tagGroups": [], + "linkedEvents": [] + }, + { + "uid": 51788120, + "slug": "bokehcon", + "canonicalUrl": "https://openagenda.com/test-laurent/event/bokehcon", + "title": { + "en": "BokehCon" + }, + "description": { + "en": "International Bokeh Conference" + }, + "longDescription": null, + "keywords": { + "en": [ + "bokeh" + ] + }, + "html": { + "en": null + }, + "image": false, + "thumbnail": false, + "originalImage": false, + "age": null, + "accessibility": [], + "updatedAt": "2019-11-27T09:10:30.000Z", + "createdAt": "2019-11-27T09:09:22.000Z", + "range": { + "fr": "16 et 17 décembre", + "en": "16 and 17 December" + }, + "origin": { + "uid": 36758196, + "title": "Test Laurent", + "url": "https://www.afi-sa.net", + "slug": "test-laurent", + "oaUrl": "https://openagenda.com/agendas/36758196" + }, + "conditions": null, + "registrationUrl": null, + "locationName": "AFI Annecy", + "locationUid": 51796356, + "address": "11 boulevard du fier, 74000 Annecy", + "postalCode": "74000", + "city": "Annecy", + "district": "Annecy", + "department": "Haute-Savoie", + "region": "Auvergne-Rhône-Alpes", + "latitude": 45.913614, + "longitude": 6.114212, + "timings": [ + { + "start": "2019-12-16T08:00:00.000Z", + "end": "2019-12-16T16:00:00.000Z" + }, + { + "start": "2019-12-17T08:00:00.000Z", + "end": "2019-12-17T16:00:00.000Z" + } + ], + "location": { + "uid": 51796356, + "name": "AFI Annecy", + "slug": "afi-annecy", + "address": "11 boulevard du fier, 74000 Annecy", + "image": null, + "imageCredits": null, + "postalCode": "74000", + "city": "Annecy", + "district": "Annecy", + "department": "Haute-Savoie", + "region": "Auvergne-Rhône-Alpes", + "latitude": 45.913614, + "longitude": 6.114212, + "description": null, + "access": null, + "countryCode": "fr", + "website": null, + "email": null, + "links": [], + "insee": "74010", + "phone": null, + "tags": null, + "timezone": "Europe/Paris", + "updatedAt": "2019-11-25T10:24:36.000Z", + "extId": null, + "country": { + "en": "France (Metropolitan)", + "fr": "France (Métropole)", + "de": "Frankreich (Metropolitan)", + "es": "Francia (Metropolitana)", + "code": "FR" + } + }, + "registration": [], + "firstDate": "2019-12-16", + "firstTimeStart": "09:00", + "firstTimeEnd": "17:00", + "lastDate": "2019-12-17", + "lastTimeStart": "09:00", + "lastTimeEnd": "17:00", + "featured": 0, + "hasPrivateCustomFields": false, + "custom": null, + "contributor": null, + "category": null, + "tags": [], + "tagGroups": [], + "linkedEvents": [] + }, + { + "uid": 32710419, + "slug": "atelier-de-traduction", + "canonicalUrl": "https://openagenda.com/test-laurent/event/atelier-de-traduction", + "title": { + "en": "Translation workshop", + "fr": "Atelier de traduction" + }, + "description": { + "en": "Come and translate Bokeh", + "fr": "Venez traduire Bokeh" + }, + "longDescription": { + "en": "Bokeh with your language", + "fr": "Bokeh dans votre langue" + }, + "keywords": { + "en": [ + "i18n" + ], + "fr": [ + "i18n" + ] + }, + "html": { + "en": "<p>Bokeh with your language</p>", + "fr": "<p>Bokeh dans votre langue</p>" + }, + "image": false, + "thumbnail": false, + "originalImage": false, + "age": null, + "accessibility": [], + "updatedAt": "2019-11-27T09:12:50.000Z", + "createdAt": "2019-11-27T09:12:50.000Z", + "range": { + "fr": "Lundi 22 juin 2020, 11h00", + "en": "Monday 22 June 2020, 11:00" + }, + "origin": { + "uid": 36758196, + "title": "Test Laurent", + "url": "https://www.afi-sa.net", + "slug": "test-laurent", + "oaUrl": "https://openagenda.com/agendas/36758196" + }, + "conditions": { + "en": "free entrance", + "fr": "entrée libre" + }, + "registrationUrl": null, + "locationName": "San pedro chez HDL", + "locationUid": 91517332, + "address": "San-Pédro", + "postalCode": null, + "city": null, + "district": null, + "department": "San Pedro", + "region": "Bas-Sassandra", + "latitude": 5.014152, + "longitude": -6.940224, + "timings": [ + { + "start": "2020-06-22T11:00:00.000Z", + "end": "2020-06-22T14:00:00.000Z" + } + ], + "location": { + "uid": 91517332, + "name": "San pedro chez HDL", + "slug": "san-pedro-chez-hdl", + "address": "San-Pédro", + "image": null, + "imageCredits": null, + "postalCode": null, + "city": null, + "district": null, + "department": "San Pedro", + "region": "Bas-Sassandra", + "latitude": 5.014152, + "longitude": -6.940224, + "description": null, + "access": null, + "countryCode": "ci", + "website": null, + "email": null, + "links": [], + "insee": null, + "phone": null, + "tags": null, + "timezone": "Africa/Abidjan", + "updatedAt": "2019-11-26T14:11:55.000Z", + "extId": null, + "country": { + "en": "Cote D'Ivoire", + "fr": "Cote D'Ivoire", + "de": "Elfenbeinküste", + "es": "Costa de Marfil", + "code": "CI" + } + }, + "registration": [], + "firstDate": "2020-06-22", + "firstTimeStart": "11:00", + "firstTimeEnd": "14:00", + "lastDate": "2020-06-22", + "lastTimeStart": "11:00", + "lastTimeEnd": "14:00", + "featured": 0, + "hasPrivateCustomFields": false, + "custom": null, + "contributor": null, + "category": null, + "tags": [], + "tagGroups": [], + "linkedEvents": [] + } + ] +} diff --git a/tests/scenarios/ExternalAgendas/open-agenda-3.json b/tests/scenarios/ExternalAgendas/open-agenda-3.json new file mode 100644 index 00000000000..d257f8f21e3 --- /dev/null +++ b/tests/scenarios/ExternalAgendas/open-agenda-3.json @@ -0,0 +1,7 @@ +{ + "readme": "Results are paginated. See: https://openagenda.zendesk.com/hc/fr/articles/203034982-L-export-JSON-d-un-agenda", + "total": 4, + "offset": 2, + "limit": 1, + "events": [] +} -- GitLab